svelte/motion

closes #1875
pull/1941/head
Rich Harris 7 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.* /internal.*
/store.js /store.js
/easing.js /easing.js
/motion.*
/transition.js /transition.js
/scratch/ /scratch/
/coverage/ /coverage/

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

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

@ -341,7 +341,7 @@ export default class EachBlockWrapper extends Wrapper {
block.builders.update.addBlock(deindent` block.builders.update.addBlock(deindent`
const ${this.vars.each_block_value} = ${snippet}; 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();`} ${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}); ${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();`} ${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) { if (this.block.hasOutros) {
destroy = deindent` destroy = deindent`
@groupOutros(); @group_outros();
for (; #i < ${iterations}.length; #i += 1) ${outroBlock}(#i, 1); for (; #i < ${iterations}.length; #i += 1) ${outroBlock}(#i, 1);
`; `;
} else { } else {

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

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

@ -368,7 +368,7 @@ export default class InlineComponentWrapper extends Wrapper {
block.builders.update.addBlock(deindent` block.builders.update.addBlock(deindent`
if (${switch_value} !== (${switch_value} = ${snippet})) { if (${switch_value} !== (${switch_value} = ${snippet})) {
if (${name}) { if (${name}) {
@groupOutros(); @group_outros();
const old_component = ${name}; const old_component = ${name};
old_component.$$.fragment.o(() => { old_component.$$.fragment.o(() => {
old_component.$destroy(); 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; if (!from) return;
const to = node.getBoundingClientRect(); const to = node.getBoundingClientRect();
if (from.left === to.left && from.right === to.right && from.top === to.top && from.bottom === to.bottom) return; 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; let running = true;
const delay = 'delay' in info ? info.delay : 0; let started = false;
const ease = info.easing || linear; let name;
const start = window.performance.now() + delay;
const end = start + duration;
const program = {
a: 0,
t: 0,
b: 1,
delta: 1,
duration,
start,
end
};
const cssText = node.style.cssText; const cssText = node.style.cssText;
const animation = { function start() {
pending: delay ? program : null, if (css) {
program: delay ? null : program,
running: true,
start() {
if (info.css) {
if (delay) node.style.cssText = cssText; if (delay) node.style.cssText = cssText;
const rule = generateRule(program, ease, info.css); name = create_rule({ a: 0, b: 1, delta: 1, duration }, easing, css);
program.name = `__svelte_${hash(rule)}`;
transitionManager.addRule(rule, program.name);
node.style.animation = (node.style.animation || '') node.style.animation = (node.style.animation || '')
.split(', ') .split(', ')
.filter(anim => anim && (program.delta < 0 || !/__svelte/.test(anim))) .filter(anim => anim && !/__svelte/.test(anim))
.concat(`${program.name} ${program.duration}ms linear 1 forwards`) .concat(`${name} ${duration}ms linear 1 forwards`)
.join(', '); .join(', ');
} }
animation.program = program; started = true;
animation.pending = null; }
},
function stop() {
if (css) delete_rule(node, name);
running = false;
}
update: now => { const { abort, promise } = loop(now => {
const p = now - program.start; if (!started && now >= start_time) {
const t = program.a + program.delta * ease(p / program.duration); start();
if (info.tick) info.tick(t, 1 - t); }
},
done() { if (started && now >= end) {
if (info.tick) info.tick(1, 0); tick(1, 0);
animation.stop(); stop();
}, }
stop() { if (!running) {
if (info.css) transitionManager.deleteRule(node, program.name); return false;
animation.running = false;
} }
};
transitionManager.add(animation); if (started) {
const p = now - start_time;
const t = 0 + 1 * easing(p / duration);
tick(t, 1 - t);
}
if (info.tick) info.tick(0, 1); return true;
});
if (delay) { if (delay) {
if (info.css) node.style.cssText += info.css(0, 1); if (css) node.style.cssText += css(0, 1);
} else { } else {
animation.start(); start();
} }
return animation; tick(0, 1);
return stop;
} }
export function fixPosition(node) { export function fix_position(node) {
const style = getComputedStyle(node); const style = getComputedStyle(node);
if (style.position !== 'absolute' && style.position !== 'fixed') { if (style.position !== 'absolute' && style.position !== 'fixed') {

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

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

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

@ -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 */ /* 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) { function get_each_context(ctx, list, i) {
const child_ctx = Object.create(ctx); 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)} // (19:0) {#each things as thing (thing.id)}
function create_each_block(component, key_1, ctx) { 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 { return {
key: key_1, key: key_1,
@ -38,13 +38,13 @@ function create_each_block(component, key_1, ctx) {
}, },
f() { f() {
fixPosition(div); fix_position(div);
if (animation) animation.stop(); stop_animation();
}, },
a() { a() {
if (animation) animation.stop(); stop_animation();
animation = wrapAnimation(div, rect, foo, {}); stop_animation = animate(div, rect, foo, {});
}, },
d(detach) { d(detach) {

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

Loading…
Cancel
Save