Launch Week Day 5: Introducing Reachability for PHP.Learn More
Socket
Book a DemoSign in
Socket

react-motion

Package Overview
Dependencies
Maintainers
1
Versions
27
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

react-motion - npm Package Compare versions

Comparing version
0.1.0
to
0.2.0
+133
lib/animationLoop.js
'use strict';
exports.__esModule = true;
exports['default'] = createAnimationLoop;
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
var _filter = require('./filter');
var _filter2 = _interopRequireDefault(_filter);
var _performanceNow = require('performance-now');
var _performanceNow2 = _interopRequireDefault(_performanceNow);
var _raf = require('raf');
var _raf2 = _interopRequireDefault(_raf);
function renderSubscriber(alpha, subscriber) {
// subscriber.render: this.animationRender
subscriber.render(alpha, subscriber.value, subscriber.prevValue);
return subscriber.active;
}
var prototype = {
running: false,
shouldStop: false,
lastTime: 0,
accumulatedTime: 0,
// step: this.animationStep
// render: this.animationRender
// value: state
subscribe: function subscribe(step, render, value) {
var subscriber = {
value: value,
prevValue: value,
step: step,
render: render,
active: true
};
this.subscribers.push(subscriber);
return function unsubscribe() {
subscriber.active = false;
};
},
loop: function loop() {
var currentTime = _performanceNow2['default']();
if (this.shouldStop) {
this.running = this.shouldStop = false;
return;
}
var timeStep = this.timeStep;
// delta
var frameTime = currentTime - this.lastTime;
this.lastTime = currentTime;
this.accumulatedTime += frameTime * this.timeScale;
if (this.accumulatedTime > timeStep * this.maxSteps) {
this.accumulatedTime = 0;
}
while (this.accumulatedTime > 0) {
this.subscribers.forEach(this.step); // animationLoop.step
this.accumulatedTime -= timeStep;
}
// Render and filter in one iteration.
this.subscribers = _filter2['default'](this.subscribers, renderSubscriber, 1 + this.accumulatedTime / timeStep);
if (this.subscribers.length === 0) {
this.shouldStop = true;
}
_raf2['default'](this.loop);
},
start: function start() {
if (this.subscribers.length) {
if (this.shouldStop) {
this.shouldStop = false;
} else if (!this.running) {
this.running = true;
this.lastTime = _performanceNow2['default']();
this.accumulatedTime = 0;
_raf2['default'](this.loop);
}
}
}
};
// stop() {
// this.shouldStop = true;
// return this;
// },
function createAnimationLoop(_ref) {
var timeStep = _ref.timeStep;
var timeScale = _ref.timeScale;
var maxSteps = _ref.maxSteps;
var animationLoop = Object.create(prototype);
animationLoop.loop = animationLoop.loop.bind(animationLoop);
animationLoop.subscribers = [];
// timeStep is in milliseconds
animationLoop.timeStep = timeStep * 1000; // seconds
animationLoop.timeScale = timeScale;
animationLoop.maxSteps = maxSteps;
animationLoop.step = function (subscriber) {
if (subscriber.active) {
var value = subscriber.value; // value = this.state
subscriber.prevValue = value;
subscriber.value = subscriber.step(timeStep, value); // animationStep
}
};
return animationLoop;
}
module.exports = exports['default'];
// Just like Array.prototype.filter, but passes third argument as the first
// argument to the callback. This is to allocating an inline callback (that
// refers to something outside as a closure) in the filter call.
"use strict";
exports.__esModule = true;
exports["default"] = filter;
function filter(array, callback, argument) {
var ret = [];
var len = array.length;
var index = 0;
while (index < len) {
if (callback(argument, array[index], index, array)) {
ret.push(array[index]);
}
index++;
}
return ret;
}
module.exports = exports["default"];
'use strict';
exports.__esModule = true;
exports['default'] = mapTree;
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
var _lodashIsPlainObject = require('lodash.isPlainObject');
// currenly a helper used for producing a tree of the same shape as the
// input(s), but with different values. It's technically not a real `map`
// equivalent for trees, since it skips calling f on non-numbers.
// TODO: probably doesn't need path, stop allocating uselessly
// TODO: don't need to map over many trees anymore
// TODO: skipping non-numbers is weird and non-generic. Use pre-order traversal
// assume trees are of the same shape
var _lodashIsPlainObject2 = _interopRequireDefault(_lodashIsPlainObject);
function _mapTree(path, f, trees) {
var t1 = trees[0];
if (typeof t1 === 'number') {
return f.apply(undefined, [path].concat(trees));
}
if (Array.isArray(t1)) {
return t1.map(function (_, i) {
return _mapTree([].concat(path, [i]), f, trees.map(function (val) {
return val[i];
}));
});
}
if (_lodashIsPlainObject2['default'](t1)) {
return Object.keys(t1).reduce(function (newTree, key) {
newTree[key] = _mapTree([].concat(path, [key]), f, trees.map(function (val) {
return val[key];
}));
return newTree;
}, {});
}
// return last one just because
return trees[trees.length - 1];
}
function mapTree(f) {
for (var _len = arguments.length, rest = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
rest[_key - 1] = arguments[_key];
}
return _mapTree([], f, rest);
}
module.exports = exports['default'];
// this function is allocation-less thanks to babel, which transforms the tail
// calls into loops
"use strict";
exports.__esModule = true;
exports["default"] = mergeDiff;
function mergeDiffArr2(_x, _x2, _x3, _x4, _x5, _x6, _x7) {
var _again = true;
_function: while (_again) {
var arrA = _x,
arrB = _x2,
collB = _x3,
indexA = _x4,
indexB = _x5,
onRemove = _x6,
accum = _x7;
endA = endB = keyA = keyB = fill = fill = undefined;
_again = false;
var endA = indexA === arrA.length;
var endB = indexB === arrB.length;
var keyA = arrA[indexA];
var keyB = arrB[indexB];
if (endA && endB) {
// returning null here, otherwise lint complains that we're not expecting
// a return value in subsequent calls. We know what we're doing.
return null;
}
if (endA) {
accum[keyB] = collB[keyB];
_x = arrA;
_x2 = arrB;
_x3 = collB;
_x4 = indexA;
_x5 = indexB + 1;
_x6 = onRemove;
_x7 = accum;
_again = true;
continue _function;
}
if (endB) {
var fill = onRemove(keyA);
if (fill != null) {
accum[keyA] = fill;
}
_x = arrA;
_x2 = arrB;
_x3 = collB;
_x4 = indexA + 1;
_x5 = indexB;
_x6 = onRemove;
_x7 = accum;
_again = true;
continue _function;
}
if (keyA === keyB) {
accum[keyA] = collB[keyA];
_x = arrA;
_x2 = arrB;
_x3 = collB;
_x4 = indexA + 1;
_x5 = indexB + 1;
_x6 = onRemove;
_x7 = accum;
_again = true;
continue _function;
}
if (!collB.hasOwnProperty(keyA)) {
var fill = onRemove(keyA);
if (fill != null) {
accum[keyA] = fill;
}
_x = arrA;
_x2 = arrB;
_x3 = collB;
_x4 = indexA + 1;
_x5 = indexB;
_x6 = onRemove;
_x7 = accum;
_again = true;
continue _function;
}
_x = arrA;
_x2 = arrB;
_x3 = collB;
_x4 = indexA + 1;
_x5 = indexB;
_x6 = onRemove;
_x7 = accum;
_again = true;
continue _function;
}
}
function mergeDiff(a, b, onRemove) {
var ret = {};
// if anyone can make this work without allocating the arrays here, we'll
// give you a medal
mergeDiffArr2(Object.keys(a), Object.keys(b), b, 0, 0, onRemove, ret);
return ret;
}
module.exports = exports["default"];
'use strict';
exports.__esModule = true;
exports['default'] = noVelocity;
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
var _lodashIsPlainObject = require('lodash.isPlainObject');
var _lodashIsPlainObject2 = _interopRequireDefault(_lodashIsPlainObject);
function noVelocity(coll) {
if (Array.isArray(coll)) {
return coll.every(noVelocity);
}
if (_lodashIsPlainObject2['default'](coll)) {
return Object.keys(coll).every(function (key) {
return key === 'config' ? true : noVelocity(coll[key]);
});
}
return typeof coll === 'number' ? coll === 0 : true;
}
module.exports = exports['default'];
"use strict";
exports.__esModule = true;
exports["default"] = reorderKeys;
function reorderKeys(obj, f) {
return f(Object.keys(obj)).reduce(function (ret, key) {
ret[key] = obj[key];
return ret;
}, {});
}
module.exports = exports["default"];
+13
-3
Legend:
- [I]: improvement
- [F]: fix
- [B]: fix
- [B]: Breaking
- [F]: Fix
- [I]: Improvement
### 0.2.0 (July 22th 2015)
- [B] `willLeave` returning `false` will now keep the key. Only `null` and `undefined` will serve as a signal to kill the disappeared key.
- [B] `willLeave` previously failed to expose the second argument `correspondingValueOfKeyThatJustLeft`. It's now exposed correctly.
- [F] Definitively fix the previous problem of mis-detecting React Element as object.
- [F] `willLeave` is now called only once per disappearing key. It was called more than once previously as a implementation detail. Though you should never have put side-effects in `willLeave`. It's still discouraged now.
- [F] If you have some `this.props.handlerThatSetStateAndUnmountsSpringInOwnerRender()` in `Spring`'s `endValue`, Spring's already scheduled `requestAnimationFrame` will no longer cause an extra `setState` since it's unmounted. But in general, _please_ don't put side-effect in `endValue`.
- [I] Stabilize the spring algorithm. No more erratic behavior with a big amount of animated items or tab switching (which usually slows down `requestAnimationFrame`). #57
- [I] Partial (total?) support for IE9 by using a `requestAnimationFrame` polyfill.
### 0.1.0 (July 14th 2015)
- [B] Breaking API: `TransitionSpring`'s `willEnter`'s callback signature is now `(keyThatEnters, correspondingValue, endValueYouJustSpecified, currentInterpolatedValue, currentSpeed)` (added `correspondingValue` as the second argument). Same for `willLeave`.
- [B] `Spring` is now no longer exposed as a default, but simply as "Spring": `require('react-motion').Spring`. Or `import {Spring} from 'react-motion'`.

@@ -9,0 +19,0 @@ - [B] `Spring` and `TransitionSpring`'s `children` function now expect a ReactElement. The components will no longer wrap the return value in a `div` for you. #44 #20

@@ -5,2 +5,8 @@ 'use strict';

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
var _reorderKeys = require('./reorderKeys');
var _reorderKeys2 = _interopRequireDefault(_reorderKeys);
var _Spring = require('./Spring');

@@ -10,2 +16,5 @@

exports.TransitionSpring = _Spring.TransitionSpring;
exports.utils = _Spring.utils;
var utils = {
reorderKeys: _reorderKeys2['default']
};
exports.utils = utils;
+225
-316
'use strict';
exports.__esModule = true;
exports.updateCurrVals = updateCurrVals;
exports.updateCurrV = updateCurrV;
exports.noSpeed = noSpeed;
exports.updateCurrValue = updateCurrValue;
exports.updateCurrVelocity = updateCurrVelocity;

@@ -14,4 +13,10 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

var _utils = require('./utils');
var _mapTree = require('./mapTree');
var _mapTree2 = _interopRequireDefault(_mapTree);
var _lodashIsPlainObject = require('lodash.isPlainObject');
var _lodashIsPlainObject2 = _interopRequireDefault(_lodashIsPlainObject);
var _stepper = require('./stepper');

@@ -21,4 +26,23 @@

var FRAME_RATE = 1 / 60;
var _noVelocity = require('./noVelocity');
var _noVelocity2 = _interopRequireDefault(_noVelocity);
var _mergeDiff = require('./mergeDiff');
var _mergeDiff2 = _interopRequireDefault(_mergeDiff);
var _animationLoop = require('./animationLoop');
var _animationLoop2 = _interopRequireDefault(_animationLoop);
var animationLoop = _animationLoop2['default']({
// Fixed time step in seconds.
timeStep: 1 / 60,
// Slow-mo anyone? Give 0.1 a try.
timeScale: 1,
// Pause if we have more than this many steps worth of accumulated time.
maxSteps: 10
});
function zero() {

@@ -28,93 +52,43 @@ return 0;

// TODO: test
function mergeDiff(_x, _x2, _x3, _x4) {
var _again = true;
_function: while (_again) {
var collA = _x,
collB = _x2,
onRemove = _x3,
accum = _x4;
a = aa = b = bb = undefined;
_again = false;
var a = collA[0];
var aa = collA.slice(1);
var b = collB[0];
var bb = collB.slice(1);
if (collA.length === 0 && collB.length === 0) {
return accum;
// TODO: refactor common logic with updateCurrValue and updateCurrVelocity
function interpolateValue(alpha, nextValue, prevValue) {
if (nextValue === null) {
return null;
}
if (prevValue == null) {
return nextValue;
}
if (typeof nextValue === 'number') {
// https://github.com/chenglou/react-motion/pull/57#issuecomment-121924628
return nextValue * alpha + prevValue * (1 - alpha);
}
if (nextValue.val != null && nextValue.config && nextValue.config.length === 0) {
return nextValue;
}
if (nextValue.val != null) {
var ret = {
val: interpolateValue(alpha, nextValue.val, prevValue.val)
};
if (nextValue.config) {
ret.config = nextValue.config;
}
if (collA.length === 0) {
return accum.concat(collB);
}
if (collB.length === 0) {
if (onRemove(a)) {
_x = aa;
_x2 = collB;
_x3 = onRemove;
_x4 = accum;
_again = true;
continue _function;
}
_x = aa;
_x2 = collB;
_x3 = onRemove;
_x4 = accum.concat(a);
_again = true;
continue _function;
}
if (a === b) {
// fails for ([undefined], [], () => true). but don't do that
_x = aa;
_x2 = bb;
_x3 = onRemove;
_x4 = accum.concat(a);
_again = true;
continue _function;
}
if (collB.indexOf(a) === -1) {
if (onRemove(a)) {
_x = aa;
_x2 = collB;
_x3 = onRemove;
_x4 = accum;
_again = true;
continue _function;
}
_x = aa;
_x2 = collB;
_x3 = onRemove;
_x4 = accum.concat(a);
_again = true;
continue _function;
}
_x = aa;
_x2 = collB;
_x3 = onRemove;
_x4 = accum;
_again = true;
continue _function;
return ret;
}
if (Array.isArray(nextValue)) {
return nextValue.map(function (_, i) {
return interpolateValue(alpha, nextValue[i], prevValue[i]);
});
}
if (_lodashIsPlainObject2['default'](nextValue)) {
return Object.keys(nextValue).reduce(function (ret, key) {
ret[key] = interpolateValue(alpha, nextValue[key], prevValue[key]);
return ret;
}, {});
}
return nextValue;
}
function mergeDiffObj(a, b, onRemove) {
var keys = mergeDiff(Object.keys(a), Object.keys(b), function (_a) {
return !onRemove(_a);
}, []);
var ret = {};
keys.forEach(function (key) {
if (b.hasOwnProperty(key)) {
ret[key] = b[key];
} else {
ret[key] = onRemove(key);
}
});
// TODO: refactor common logic with updateCurrVelocity
return ret;
}
// TODO: refactor common logic with updateCurrV
function updateCurrVals(frameRate, currVals, currV, endValue, k, b) {
function updateCurrValue(frameRate, currValue, currVelocity, endValue, k, b) {
if (endValue === null) {

@@ -128,3 +102,3 @@ return null;

// TODO: do something to stepper to make this not allocate (2 steppers?)
return _stepper2['default'](frameRate, currVals, currV, endValue, k, b)[0];
return _stepper2['default'](frameRate, currValue, currVelocity, endValue, k, b)[0];
}

@@ -141,3 +115,3 @@ if (endValue.val != null && endValue.config && endValue.config.length === 0) {

var ret = {
val: updateCurrVals(frameRate, currVals.val, currV.val, endValue.val, _k, _b)
val: updateCurrValue(frameRate, currValue.val, currVelocity.val, endValue.val, _k, _b)
};

@@ -151,17 +125,10 @@ if (endValue.config) {

return endValue.map(function (_, i) {
return updateCurrVals(frameRate, currVals[i], currV[i], endValue[i], k, b);
return updateCurrValue(frameRate, currValue[i], currVelocity[i], endValue[i], k, b);
});
}
if (_utils.isPlainObject(endValue)) {
var _ret = (function () {
var ret = {};
Object.keys(endValue).forEach(function (key) {
ret[key] = updateCurrVals(frameRate, currVals[key], currV[key], endValue[key], k, b);
});
return {
v: ret
};
})();
if (typeof _ret === 'object') return _ret.v;
if (_lodashIsPlainObject2['default'](endValue)) {
return Object.keys(endValue).reduce(function (ret, key) {
ret[key] = updateCurrValue(frameRate, currValue[key], currVelocity[key], endValue[key], k, b);
return ret;
}, {});
}

@@ -171,3 +138,3 @@ return endValue;

function updateCurrV(frameRate, currVals, currV, endValue, k, b) {
function updateCurrVelocity(frameRate, currValue, currVelocity, endValue, k, b) {
if (endValue === null) {

@@ -178,9 +145,9 @@ return null;

if (k == null || b == null) {
return _utils.mapTree(zero, currV);
return _mapTree2['default'](zero, currVelocity);
}
// TODO: do something to stepper to make this not allocate (2 steppers?)
return _stepper2['default'](frameRate, currVals, currV, endValue, k, b)[1];
return _stepper2['default'](frameRate, currValue, currVelocity, endValue, k, b)[1];
}
if (endValue.val != null && endValue.config && endValue.config.length === 0) {
return _utils.mapTree(zero, currV);
return _mapTree2['default'](zero, currVelocity);
}

@@ -194,3 +161,3 @@ if (endValue.val != null) {

var ret = {
val: updateCurrV(frameRate, currVals.val, currV.val, endValue.val, _k, _b)
val: updateCurrVelocity(frameRate, currValue.val, currVelocity.val, endValue.val, _k, _b)
};

@@ -204,33 +171,14 @@ if (endValue.config) {

return endValue.map(function (_, i) {
return updateCurrV(frameRate, currVals[i], currV[i], endValue[i], k, b);
return updateCurrVelocity(frameRate, currValue[i], currVelocity[i], endValue[i], k, b);
});
}
if (_utils.isPlainObject(endValue)) {
var _ret2 = (function () {
var ret = {};
Object.keys(endValue).forEach(function (key) {
ret[key] = updateCurrV(frameRate, currVals[key], currV[key], endValue[key], k, b);
});
return {
v: ret
};
})();
if (typeof _ret2 === 'object') return _ret2.v;
if (_lodashIsPlainObject2['default'](endValue)) {
return Object.keys(endValue).reduce(function (ret, key) {
ret[key] = updateCurrVelocity(frameRate, currValue[key], currVelocity[key], endValue[key], k, b);
return ret;
}, {});
}
return _utils.mapTree(zero, currV);
return _mapTree2['default'](zero, currVelocity);
}
function noSpeed(coll) {
if (Array.isArray(coll)) {
return coll.every(noSpeed);
}
if (_utils.isPlainObject(coll)) {
return Object.keys(coll).every(function (key) {
return key === 'config' ? true : noSpeed(coll[key]);
});
}
return typeof coll === 'number' ? coll === 0 : true;
}
var Spring = _react2['default'].createClass({

@@ -248,8 +196,8 @@ displayName: 'Spring',

if (typeof endValue === 'function') {
// TODO: provide warning for failing to provide base case
endValue = endValue();
}
return {
currVals: endValue,
currV: _utils.mapTree(zero, endValue),
now: null
currValue: endValue,
currVelocity: _mapTree2['default'](zero, endValue)
};

@@ -259,70 +207,73 @@ },

componentDidMount: function componentDidMount() {
this.raf(true, false);
this.startAnimating();
},
componentWillReceiveProps: function componentWillReceiveProps() {
this.raf(true, false);
this.startAnimating();
},
unsubscribeAnimation: null,
// used in animationRender
hasUnmounted: false,
componentWillUnmount: function componentWillUnmount() {
cancelAnimationFrame(this._rafID);
if (this.unsubscribeAnimation) {
this.unsubscribeAnimation();
this.unsubscribeAnimation = null;
}
this.hasUnmounted = true;
},
_rafID: null,
startAnimating: function startAnimating() {
if (!this.unsubscribeAnimation) {
// means we're not animating
this.unsubscribeAnimation = animationLoop.subscribe(this.animationStep, this.animationRender, this.state);
animationLoop.start();
}
},
raf: function raf(justStarted, isLastRaf) {
var _this = this;
animationStep: function animationStep(timeStep, state) {
var currValue = state.currValue;
var currVelocity = state.currVelocity;
var endValue = this.props.endValue;
if (justStarted && this._rafID != null) {
// already rafing
return;
if (typeof endValue === 'function') {
endValue = endValue(currValue);
}
this._rafID = requestAnimationFrame(function () {
var _state = _this.state;
var currVals = _state.currVals;
var currV = _state.currV;
var now = _state.now;
var endValue = _this.props.endValue;
if (typeof endValue === 'function') {
endValue = endValue(currVals);
var newCurrValue = updateCurrValue(timeStep, currValue, currVelocity, endValue);
var newCurrVelocity = updateCurrVelocity(timeStep, currValue, currVelocity, endValue);
if (_noVelocity2['default'](currVelocity) && _noVelocity2['default'](newCurrVelocity)) {
// check explanation in `animationRender`
if (!this.hasUnmounted) {
this.unsubscribeAnimation();
this.unsubscribeAnimation = null;
}
var frameRate = now && !justStarted ? (Date.now() - now) / 1000 : FRAME_RATE;
}
var newCurrVals = updateCurrVals(frameRate, currVals, currV, endValue);
var newCurrV = updateCurrV(frameRate, currVals, currV, endValue);
return {
currValue: newCurrValue,
currVelocity: newCurrVelocity
};
},
_this.setState(function () {
return {
currVals: newCurrVals,
currV: newCurrV,
now: Date.now()
};
animationRender: function animationRender(alpha, nextState, prevState) {
// `this.hasUnmounted` might be true in the following condition:
// user does some checks in `endValue` and calls an owner handler
// owner sets state in the callback, triggering a re-render
// re-render unmounts the Spring
if (!this.hasUnmounted) {
this.setState({
currValue: interpolateValue(alpha, nextState.currValue, prevState.currValue),
currVelocity: nextState.currVelocity
});
var stop = noSpeed(newCurrV);
if (stop && !justStarted) {
// this flag is necessary, because in `endValue` callback, the user
// might check that the current value has reached the destination, and
// decide to return a new destination value. However, since s/he's
// accessing the last tick's current value, and if we stop rafing after
// speed is 0, the next `endValue` is never called and we never detect
// the new chained animation. isLastRaf ensures that we raf a single
// more time in case the user wants to chain another animation at the
// end of this one
if (isLastRaf) {
_this._rafID = null;
} else {
_this.raf(false, true);
}
} else {
_this.raf(false, false);
}
});
}
},
render: function render() {
var currVals = this.state.currVals;
var currValue = this.state.currValue;
return _react2['default'].Children.only(this.props.children(currVals));
return _react2['default'].Children.only(this.props.children(currValue));
}

@@ -336,5 +287,20 @@ });

propTypes: {
endValue: _react.PropTypes.oneOfType([_react.PropTypes.func, _react.PropTypes.object]).isRequired,
willLeave: _react.PropTypes.oneOfType([_react.PropTypes.func, _react.PropTypes.object, _react.PropTypes.array]),
willEnter: _react.PropTypes.oneOfType([_react.PropTypes.func, _react.PropTypes.object, _react.PropTypes.array]),
endValue: _react.PropTypes.oneOfType([_react.PropTypes.func, _react.PropTypes.objectOf({
key: _react.PropTypes.any.isRequired
})]).
// coming soon
// PropTypes.arrayOf(PropTypes.shape({
// key: PropTypes.any.isRequired,
// })),
// PropTypes.arrayOf(PropTypes.element),
isRequired,
willLeave: _react.PropTypes.oneOfType([_react.PropTypes.func]),
// PropTypes.object,
// PropTypes.array,
// TODO: numbers? strings?
willEnter: _react.PropTypes.oneOfType([_react.PropTypes.func]),
// PropTypes.object,
// PropTypes.array,
children: _react.PropTypes.func.isRequired

@@ -361,5 +327,4 @@ },

return {
currVals: endValue,
currV: _utils.mapTree(zero, endValue),
now: null
currValue: endValue,
currVelocity: _mapTree2['default'](zero, endValue)
};

@@ -369,148 +334,92 @@ },

componentDidMount: function componentDidMount() {
this.raf(true, false);
this.startAnimating();
},
componentWillReceiveProps: function componentWillReceiveProps() {
this.raf(true, false);
this.startAnimating();
},
unsubscribeAnimation: null,
// used in animationRender
hasUnmounted: false,
componentWillUnmount: function componentWillUnmount() {
cancelAnimationFrame(this._rafID);
if (this.unsubscribeAnimation) {
this.unsubscribeAnimation();
this.unsubscribeAnimation = undefined;
}
},
_rafID: null,
startAnimating: function startAnimating() {
if (!this.unsubscribeAnimation) {
this.unsubscribeAnimation = animationLoop.subscribe(this.animationStep, this.animationRender, this.state);
animationLoop.start();
}
},
raf: function raf(justStarted, isLastRaf) {
var _this2 = this;
animationStep: function animationStep(timeStep, state) {
var currValue = state.currValue;
var currVelocity = state.currVelocity;
var endValue = this.props.endValue;
var _props = this.props;
var willEnter = _props.willEnter;
var willLeave = _props.willLeave;
if (justStarted && this._rafID != null) {
// already rafing
return;
if (typeof endValue === 'function') {
endValue = endValue(currValue);
}
this._rafID = requestAnimationFrame(function () {
var _state2 = _this2.state;
var currVals = _state2.currVals;
var currV = _state2.currV;
var now = _this2.state.now;
var endValue = _this2.props.endValue;
var _props = _this2.props;
var willEnter = _props.willEnter;
var willLeave = _props.willLeave;
if (typeof endValue === 'function') {
endValue = endValue(currVals);
}
var mergedValue = undefined;
// only other option is obj
mergedValue = _mergeDiff2['default'](currValue, endValue,
// TODO: stop allocating like crazy in this whole code path
function (key) {
return willLeave(key, currValue[key], endValue, currValue, currVelocity);
});
var mergedVals = undefined;
if (Array.isArray(endValue)) {
(function () {
var currValsObj = {};
currVals.forEach(function (objWithKey) {
currValsObj[objWithKey.key] = objWithKey;
});
var hasNewKey = false;
Object.keys(mergedValue).filter(function (key) {
return !currValue.hasOwnProperty(key);
}).forEach(function (key) {
hasNewKey = true;
var enterValue = willEnter(key, mergedValue[key], endValue, currValue, currVelocity);
currValue[key] = enterValue;
mergedValue[key] = enterValue;
currVelocity[key] = _mapTree2['default'](zero, currValue[key]);
});
var endValueObj = {};
endValue.forEach(function (objWithKey) {
endValueObj[objWithKey.key] = objWithKey;
});
var currVObj = {};
endValue.forEach(function (objWithKey) {
currVObj[objWithKey.key] = objWithKey;
});
var newCurrValue = updateCurrValue(timeStep, currValue, currVelocity, mergedValue);
var newCurrVelocity = updateCurrVelocity(timeStep, currValue, currVelocity, mergedValue);
var mergedValsObj = mergeDiffObj(currValsObj, endValueObj, function (key) {
return willLeave(key, endValue, currVals, currV);
});
var mergedValsKeys = Object.keys(mergedValsObj);
mergedVals = mergedValsKeys.map(function (key) {
return mergedValsObj[key];
});
mergedValsKeys.filter(function (key) {
return !currValsObj.hasOwnProperty(key);
}).forEach(function (key) {
currValsObj[key] = willEnter(key, mergedValsObj[key], endValue, currVals, currV);
currVObj[key] = _utils.mapTree(zero, currValsObj[key]);
});
currVals = Object.keys(currValsObj).map(function (key) {
return currValsObj[key];
});
currV = Object.keys(currVObj).map(function (key) {
return currVObj[key];
});
})();
} else {
// only other option is obj
mergedVals = mergeDiffObj(currVals, endValue,
// TODO: stop allocating like crazy in this whole code path
function (key) {
return willLeave(key, endValue, currVals, currV);
});
// TODO: check if this is necessary
currVals = _utils.clone(currVals);
currV = _utils.clone(currV);
Object.keys(mergedVals).filter(function (key) {
return !currVals.hasOwnProperty(key);
}).forEach(function (key) {
// TODO: param format changed, check other demos
currVals[key] = willEnter(key, mergedVals[key], endValue, currVals, currV);
currV[key] = _utils.mapTree(zero, currVals[key]);
});
if (_noVelocity2['default'](currVelocity) && _noVelocity2['default'](newCurrVelocity) && !hasNewKey) {
// check explanation in `Spring.animationRender`
if (!this.hasUnmounted) {
this.unsubscribeAnimation();
this.unsubscribeAnimation = undefined;
}
}
var frameRate = now && !justStarted ? (Date.now() - now) / 1000 : FRAME_RATE;
return {
currValue: newCurrValue,
currVelocity: newCurrVelocity
};
},
var newCurrVals = updateCurrVals(frameRate, currVals, currV, mergedVals);
var newCurrV = updateCurrV(frameRate, currVals, currV, mergedVals);
_this2.setState(function () {
return {
currVals: newCurrVals,
currV: newCurrV,
now: Date.now()
};
animationRender: function animationRender(alpha, nextState, prevState) {
// See comment in Spring.
if (!this.hasUnmounted) {
this.setState({
currValue: interpolateValue(alpha, nextState.currValue, prevState.currValue),
currVelocity: nextState.currVelocity
});
var stop = noSpeed(newCurrV);
if (stop && !justStarted) {
if (isLastRaf) {
_this2._rafID = null;
} else {
_this2.raf(false, true);
}
} else {
_this2.raf(false, false);
}
});
}
},
render: function render() {
var currVals = this.state.currVals;
var currValue = this.state.currValue;
return _react2['default'].Children.only(this.props.children(currVals));
return _react2['default'].Children.only(this.props.children(currValue));
}
});
exports.TransitionSpring = TransitionSpring;
function reorderKeys(obj, f) {
var ret = {};
f(Object.keys(obj)).forEach(function (key) {
ret[key] = obj[key];
});
return ret;
}
var utils = {
reorderKeys: reorderKeys
};
exports.utils = utils;
// coming soon
// PropTypes.arrayOf(PropTypes.shape({
// key: PropTypes.any.isRequired,
// })),
// PropTypes.arrayOf(PropTypes.element),
// TODO: numbers? strings?
exports.TransitionSpring = TransitionSpring;
{
"name": "react-motion",
"version": "0.1.0",
"version": "0.2.0",
"description": "A spring that solves your animation problems.",

@@ -20,2 +20,3 @@ "main": "lib/react-motion.js",

"isparta": "^3.0.3",
"lodash.range": "^3.0.1",
"mocha": "^2.2.5",

@@ -52,3 +53,8 @@ "react-hot-loader": "^1.2.8",

"author": "chenglou",
"license": "MIT"
"license": "MIT",
"dependencies": {
"lodash.isplainobject": "^3.2.0",
"performance-now": "^0.2.0",
"raf": "^3.1.0"
}
}

@@ -25,3 +25,3 @@ # React-Motion

[Check](https://cdn.rawgit.com/chenglou/react-motion/cffb3894f42e4825178d9c7c0313b2f4e9e65ab2/demo0/index.html) [Out](https://cdn.rawgit.com/chenglou/react-motion/cffb3894f42e4825178d9c7c0313b2f4e9e65ab2/demo1/index.html) [The](https://cdn.rawgit.com/chenglou/react-motion/cffb3894f42e4825178d9c7c0313b2f4e9e65ab2/demo2/index.html) [Cool](https://cdn.rawgit.com/chenglou/react-motion/cffb3894f42e4825178d9c7c0313b2f4e9e65ab2/demo3/index.html) [Demos](https://cdn.rawgit.com/chenglou/react-motion/072fef7b84b2d57187643baa4156ee2a7374655f/demo4/index.html).
[Check](https://cdn.rawgit.com/chenglou/react-motion/3b5be548cd08630a836562a053576ff91f94b93f/demo0/index.html) [Out](https://cdn.rawgit.com/chenglou/react-motion/3b5be548cd08630a836562a053576ff91f94b93f/demo1/index.html) [The](https://cdn.rawgit.com/chenglou/react-motion/3b5be548cd08630a836562a053576ff91f94b93f/demo2/index.html) [Cool](https://cdn.rawgit.com/chenglou/react-motion/3b5be548cd08630a836562a053576ff91f94b93f/demo3/index.html) [Demos](https://cdn.rawgit.com/chenglou/react-motion/3b5be548cd08630a836562a053576ff91f94b93f/demo4/index.html).

@@ -67,3 +67,3 @@ ## What does this library try to solve?

The library exports a default `Spring`, a `TransitionSpring` and `utils`.
The library exports a `Spring`, a `TransitionSpring` and `utils`.

@@ -189,9 +189,9 @@ ### &lt;Spring />

### &lt;TransitionSpring />
Like `Spring`, but can takes two other props: `willEnter` and `willLeave`. Throughout this section, please remember that ""
Like `Spring`, but can take two other props: `willEnter` and `willLeave`. Throughout this section, please remember that
`endValue`: now constrained to an object of the shape `{key => yourStuff}` (the data is constrained to this shape, but that doesn't mean the way you use your interpolated value has to be). When your the `endValue` provide differs from the current interpolated value by an added/removed key:
`endValue`: now constrained to an object (or a callback `currentValue -> object`) of the shape `{key => yourStuff}` (the data is constrained to this shape, but that doesn't mean the way you use your interpolated value has to be). When `endValue` differs from the current interpolated value by an added/removed key:
`willEnter`: a callback that's called **once** and is passed `(keyThatEnters, endValueYouJustSpecified, currentInterpolatedValue, currentSpeed)`. Return an object/array configuration that'll serve as the starting value of that new key. That configuration will be merged into `endValue`. The default value of `willEnter` is `(key, endValue) => endValue[key]`. It returns the same configuration as the one you just specified in `endValue`. In other words, the start and end are the same: no animation.
`willEnter`: a callback that's called **once** and is passed `(keyThatEnters, correspondingValueOfKey, endValueYouJustSpecified, currentInterpolatedValue, currentSpeed)`. Return an object/array configuration that'll serve as the starting value of that new key. That configuration will be merged into `endValue`. The default value of `willEnter` is `(key, endValue) => endValue[key]`. It returns the same configuration as the one you just specified in `endValue`. In other words, the start and end are the same: no animation.
`willLeave`: a callback that's called **many** times and is passed `(keyThatLeaves, endValueYouJustSpecified, currentInterpolatedValue, currentSpeed)`. Return an object/array configuration (which will serve as the new `endValue[keyThatLeaves]` and merged into `endValue`) to indicate you still want to keep the item around. Otherwise, return `null`.
`willLeave`: a callback that's called **many** times and is passed `(keyThatLeaves, correspondingValueOfKeyThatJustLeft, endValueYouJustSpecified, currentInterpolatedValue, currentSpeed)`. Return an object/array configuration (which will serve as the new `endValue[keyThatLeaves]` and merged into `endValue`) to indicate you still want to keep the item around. Otherwise, return `null`.

@@ -234,3 +234,3 @@ #### Sample Usage

willLeave(key, endValue, currentValue, currentSpeed) {
willLeave(key, value, endValue, currentValue, currentSpeed) {
if (currentValue[key].opacity.val === 0 && currentSpeed[key].opacity.val === 0) {

@@ -270,4 +270,5 @@ return null; // kill component when opacity reaches 0

);
})}}
})}
</div>
}
</TransitionSpring>

@@ -274,0 +275,0 @@ );

@@ -6,2 +6,3 @@ var webpack = require('webpack');

var config = {
devtool: 'sourcemap',
entry: {

@@ -8,0 +9,0 @@ index: './src/react-motion.js'

'use strict';
exports.__esModule = true;
exports.isPlainObject = isPlainObject;
exports.clone = clone;
exports.mapTree = mapTree;
exports.reinsert = reinsert;
exports.clamp = clamp;
exports.range = range;
function isPlainObject(obj) {
return obj ? typeof obj === 'object' && Object.getPrototypeOf(obj) === Object.prototype : false;
}
// damn it JS
function clone(coll) {
return JSON.parse(JSON.stringify(coll));
}
// export function eq(a, b) {
// return JSON.stringify(a) === JSON.stringify(b);
// }
// currenly a helper used for producing a tree of the same shape as the
// input(s), but with different values. It's technically not a real `map`
// equivalent for trees, since it skips calling f on non-numbers.
// TODO: probably doesn't need path, stop allocating uselessly
// TODO: don't need to map over many trees anymore
// TODO: skipping non-numbers is weird and non-generic. Use pre-order traversal
// assume trees are of the same shape
function _mapTree(path, f, trees) {
var t1 = trees[0];
if (typeof t1 === 'number') {
return f.apply(undefined, [path].concat(trees));
}
if (Array.isArray(t1)) {
return t1.map(function (_, i) {
return _mapTree([].concat(path, [i]), f, trees.map(function (val) {
return val[i];
}));
});
}
if (isPlainObject(t1)) {
var _ret = (function () {
var newTree = {};
Object.keys(t1).forEach(function (key) {
newTree[key] = _mapTree([].concat(path, [key]), f, trees.map(function (val) {
return val[key];
}));
});
return {
v: newTree
};
})();
if (typeof _ret === 'object') return _ret.v;
}
// return last one just because
return trees[trees.length - 1];
}
function mapTree(f) {
for (var _len = arguments.length, rest = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
rest[_key - 1] = arguments[_key];
}
return _mapTree([], f, rest);
}
// function _reshapeTree(path, a, b, f) {
// if (a == null) {
// throw new Error('wtf2');
// }
// if (b == null) {
// return f(path, a);
// }
// if (Array.isArray(a)) {
// return a.map((val, i) => _reshapeTree([...path, i], val, b[i], f));
// }
// if (Object.prototype.toString.call(a) === '[object Object]') {
// const newTree = {};
// Object.keys(a).forEach(key => {
// newTree[key] = _reshapeTree([...path, key], a[key], b[key], f);
// });
// return newTree;
// }
// return b;
// }
// export function reshapeTree(a, b, f) {
// return _reshapeTree([], a, b, f);
// }
// export function toOj(vals, keys) {
// const ret = {};
// vals.forEach((val, i) => ret[keys[i]] = val);
// return ret;
// }
// export function toArr(obj) {
// const keys = Object.keys(obj);
// const vals = keys.map(k => obj[k]);
// return [keys, vals];
// }
// TODO: these are for a demos, not for the library. Move
function reinsert(arr, from, to) {
var _arr = arr.slice(0);
var val = _arr[from];
_arr.splice(from, 1);
_arr.splice(to, 0, val);
return _arr;
}
function clamp(n, min, max) {
return Math.max(Math.min(n, max), min);
}
function range(start, afterStop) {
var _afterStop = afterStop;
var _start = start;
if (afterStop == null) {
_afterStop = start;
_start = 0;
}
var ret = [];
for (var i = _start; i < _afterStop; i++) {
ret.push(i);
}
return ret;
}
export {Spring, TransitionSpring, utils} from './Spring';
import React, {PropTypes} from 'react';
import {mapTree, clone, isPlainObject} from './utils';
import stepper from './stepper';
const FRAME_RATE = 1 / 60;
function zero() {
return 0;
}
// TODO: test
function mergeDiff(collA, collB, onRemove, accum) {
const [a, ...aa] = collA;
const [b, ...bb] = collB;
if (collA.length === 0 && collB.length === 0) {
return accum;
}
if (collA.length === 0) {
return accum.concat(collB);
}
if (collB.length === 0) {
if (onRemove(a)) {
return mergeDiff(aa, collB, onRemove, accum);
}
return mergeDiff(aa, collB, onRemove, accum.concat(a));
}
if (a === b) { // fails for ([undefined], [], () => true). but don't do that
return mergeDiff(aa, bb, onRemove, accum.concat(a));
}
if (collB.indexOf(a) === -1) {
if (onRemove(a)) {
return mergeDiff(aa, collB, onRemove, accum);
}
return mergeDiff(aa, collB, onRemove, accum.concat(a));
}
return mergeDiff(aa, collB, onRemove, accum);
}
function mergeDiffObj(a, b, onRemove) {
const keys = mergeDiff(Object.keys(a), Object.keys(b), _a => !onRemove(_a), []);
const ret = {};
keys.forEach(key => {
if (b.hasOwnProperty(key)) {
ret[key] = b[key];
} else {
ret[key] = onRemove(key);
}
});
return ret;
}
// TODO: refactor common logic with updateCurrV
export function updateCurrVals(frameRate, currVals, currV, endValue, k, b) {
if (endValue === null) {
return null;
}
if (typeof endValue === 'number') {
if (k == null || b == null) {
return endValue;
}
// TODO: do something to stepper to make this not allocate (2 steppers?)
return stepper(frameRate, currVals, currV, endValue, k, b)[0];
}
if (endValue.val != null && endValue.config && endValue.config.length === 0) {
return endValue;
}
if (endValue.val != null) {
const [_k, _b] = endValue.config || [170, 26];
let ret = {
val: updateCurrVals(frameRate, currVals.val, currV.val, endValue.val, _k, _b),
};
if (endValue.config) {
ret.config = endValue.config;
}
return ret;
}
if (Array.isArray(endValue)) {
return endValue.map((_, i) => updateCurrVals(frameRate, currVals[i], currV[i], endValue[i], k, b));
}
if (isPlainObject(endValue)) {
const ret = {};
Object.keys(endValue).forEach(key => {
ret[key] = updateCurrVals(frameRate, currVals[key], currV[key], endValue[key], k, b);
});
return ret;
}
return endValue;
}
export function updateCurrV(frameRate, currVals, currV, endValue, k, b) {
if (endValue === null) {
return null;
}
if (typeof endValue === 'number') {
if (k == null || b == null) {
return mapTree(zero, currV);
}
// TODO: do something to stepper to make this not allocate (2 steppers?)
return stepper(frameRate, currVals, currV, endValue, k, b)[1];
}
if (endValue.val != null && endValue.config && endValue.config.length === 0) {
return mapTree(zero, currV);
}
if (endValue.val != null) {
const [_k, _b] = endValue.config || [170, 26];
let ret = {
val: updateCurrV(frameRate, currVals.val, currV.val, endValue.val, _k, _b),
};
if (endValue.config) {
ret.config = endValue.config;
}
return ret;
}
if (Array.isArray(endValue)) {
return endValue.map((_, i) => updateCurrV(frameRate, currVals[i], currV[i], endValue[i], k, b));
}
if (isPlainObject(endValue)) {
const ret = {};
Object.keys(endValue).forEach(key => {
ret[key] = updateCurrV(frameRate, currVals[key], currV[key], endValue[key], k, b);
});
return ret;
}
return mapTree(zero, currV);
}
export function noSpeed(coll) {
if (Array.isArray(coll)) {
return coll.every(noSpeed);
}
if (isPlainObject(coll)) {
return Object.keys(coll).every(key => key === 'config' ? true : noSpeed(coll[key]));
}
return typeof coll === 'number' ? coll === 0 : true;
}
export const Spring = React.createClass({
propTypes: {
endValue: PropTypes.oneOfType([
PropTypes.func,
PropTypes.object,
PropTypes.array,
]).isRequired,
children: PropTypes.func.isRequired,
},
getInitialState() {
let {endValue} = this.props;
if (typeof endValue === 'function') {
endValue = endValue();
}
return {
currVals: endValue,
currV: mapTree(zero, endValue),
now: null,
};
},
componentDidMount() {
this.raf(true, false);
},
componentWillReceiveProps() {
this.raf(true, false);
},
componentWillUnmount() {
cancelAnimationFrame(this._rafID);
},
_rafID: null,
raf(justStarted, isLastRaf) {
if (justStarted && this._rafID != null) {
// already rafing
return;
}
this._rafID = requestAnimationFrame(() => {
const {currVals, currV, now} = this.state;
let {endValue} = this.props;
if (typeof endValue === 'function') {
endValue = endValue(currVals);
}
const frameRate = now && !justStarted ? (Date.now() - now) / 1000 : FRAME_RATE;
const newCurrVals = updateCurrVals(frameRate, currVals, currV, endValue);
const newCurrV = updateCurrV(frameRate, currVals, currV, endValue);
this.setState(() => {
return {
currVals: newCurrVals,
currV: newCurrV,
now: Date.now(),
};
});
const stop = noSpeed(newCurrV);
if (stop && !justStarted) {
// this flag is necessary, because in `endValue` callback, the user
// might check that the current value has reached the destination, and
// decide to return a new destination value. However, since s/he's
// accessing the last tick's current value, and if we stop rafing after
// speed is 0, the next `endValue` is never called and we never detect
// the new chained animation. isLastRaf ensures that we raf a single
// more time in case the user wants to chain another animation at the
// end of this one
if (isLastRaf) {
this._rafID = null;
} else {
this.raf(false, true);
}
} else {
this.raf(false, false);
}
});
},
render() {
const {currVals} = this.state;
return React.Children.only(this.props.children(currVals));
},
});
export const TransitionSpring = React.createClass({
propTypes: {
endValue: PropTypes.oneOfType([
PropTypes.func,
PropTypes.object,
// coming soon
// PropTypes.arrayOf(PropTypes.shape({
// key: PropTypes.any.isRequired,
// })),
// PropTypes.arrayOf(PropTypes.element),
]).isRequired,
willLeave: PropTypes.oneOfType([
PropTypes.func,
PropTypes.object,
PropTypes.array,
// TODO: numbers? strings?
]),
willEnter: PropTypes.oneOfType([
PropTypes.func,
PropTypes.object,
PropTypes.array,
]),
children: PropTypes.func.isRequired,
},
getDefaultProps() {
return {
willEnter: (key, value) => value,
willLeave: () => null,
};
},
getInitialState() {
let {endValue} = this.props;
if (typeof endValue === 'function') {
endValue = endValue();
}
return {
currVals: endValue,
currV: mapTree(zero, endValue),
now: null,
};
},
componentDidMount() {
this.raf(true, false);
},
componentWillReceiveProps() {
this.raf(true, false);
},
componentWillUnmount() {
cancelAnimationFrame(this._rafID);
},
_rafID: null,
raf(justStarted, isLastRaf) {
if (justStarted && this._rafID != null) {
// already rafing
return;
}
this._rafID = requestAnimationFrame(() => {
let {currVals, currV} = this.state;
const {now} = this.state;
let {endValue} = this.props;
const {willEnter, willLeave} = this.props;
if (typeof endValue === 'function') {
endValue = endValue(currVals);
}
let mergedVals;
if (Array.isArray(endValue)) {
let currValsObj = {};
currVals.forEach(objWithKey => {
currValsObj[objWithKey.key] = objWithKey;
});
let endValueObj = {};
endValue.forEach(objWithKey => {
endValueObj[objWithKey.key] = objWithKey;
});
let currVObj = {};
endValue.forEach(objWithKey => {
currVObj[objWithKey.key] = objWithKey;
});
const mergedValsObj = mergeDiffObj(
currValsObj,
endValueObj,
key => willLeave(key, endValue, currVals, currV)
);
let mergedValsKeys = Object.keys(mergedValsObj);
mergedVals = mergedValsKeys.map(key => mergedValsObj[key]);
mergedValsKeys
.filter(key => !currValsObj.hasOwnProperty(key))
.forEach(key => {
currValsObj[key] = willEnter(key, mergedValsObj[key], endValue, currVals, currV);
currVObj[key] = mapTree(zero, currValsObj[key]);
});
currVals = Object.keys(currValsObj).map(key => currValsObj[key]);
currV = Object.keys(currVObj).map(key => currVObj[key]);
} else {
// only other option is obj
mergedVals = mergeDiffObj(
currVals,
endValue,
// TODO: stop allocating like crazy in this whole code path
key => willLeave(key, endValue, currVals, currV)
);
// TODO: check if this is necessary
currVals = clone(currVals);
currV = clone(currV);
Object.keys(mergedVals)
.filter(key => !currVals.hasOwnProperty(key))
.forEach(key => {
// TODO: param format changed, check other demos
currVals[key] = willEnter(key, mergedVals[key], endValue, currVals, currV);
currV[key] = mapTree(zero, currVals[key]);
});
}
const frameRate = now && !justStarted ? (Date.now() - now) / 1000 : FRAME_RATE;
const newCurrVals = updateCurrVals(frameRate, currVals, currV, mergedVals);
const newCurrV = updateCurrV(frameRate, currVals, currV, mergedVals);
this.setState(() => {
return {
currVals: newCurrVals,
currV: newCurrV,
now: Date.now(),
};
});
const stop = noSpeed(newCurrV);
if (stop && !justStarted) {
if (isLastRaf) {
this._rafID = null;
} else {
this.raf(false, true);
}
} else {
this.raf(false, false);
}
});
},
render() {
const {currVals} = this.state;
return React.Children.only(this.props.children(currVals));
},
});
function reorderKeys(obj, f) {
const ret = {};
f(Object.keys(obj)).forEach(key => {
ret[key] = obj[key];
});
return ret;
}
export const utils = {
reorderKeys,
};
const errorMargin = 0.0001;
export default function stepper(frameRate, x, v, destX, k, b) {
// Spring stiffness, in kg / s^2
// for animations, destX is really spring length (spring at rest). initial
// position is considered as the stretched/compressed position of a spring
const Fspring = -k * (x - destX);
// Damping constant, in kg / s
const Fdamper = -b * v;
// usually we put mass here, but for animation purposes, specifying mass is a
// bit redundant. you could simply adjust k and b accordingly
// let a = (Fspring + Fdamper) / mass;
const a = Fspring + Fdamper;
const newV = v + a * frameRate;
const newX = x + newV * frameRate;
if (Math.abs(newV - v) < errorMargin && Math.abs(newX - x) < errorMargin) {
return [destX, 0];
}
return [newX, newV];
}
export function isPlainObject(obj) {
return obj ? typeof obj === 'object' &&
Object.getPrototypeOf(obj) === Object.prototype : false;
}
// damn it JS
export function clone(coll) {
return JSON.parse(JSON.stringify(coll));
}
// export function eq(a, b) {
// return JSON.stringify(a) === JSON.stringify(b);
// }
// currenly a helper used for producing a tree of the same shape as the
// input(s), but with different values. It's technically not a real `map`
// equivalent for trees, since it skips calling f on non-numbers.
// TODO: probably doesn't need path, stop allocating uselessly
// TODO: don't need to map over many trees anymore
// TODO: skipping non-numbers is weird and non-generic. Use pre-order traversal
// assume trees are of the same shape
function _mapTree(path, f, trees) {
const t1 = trees[0];
if (typeof t1 === 'number') {
return f(path, ...trees);
}
if (Array.isArray(t1)) {
return t1.map((_, i) => _mapTree([...path, i], f, trees.map(val => val[i])));
}
if (isPlainObject(t1)) {
const newTree = {};
Object.keys(t1).forEach(key => {
newTree[key] = _mapTree([...path, key], f, trees.map(val => val[key]));
});
return newTree;
}
// return last one just because
return trees[trees.length - 1];
}
export function mapTree(f, ...rest) {
return _mapTree([], f, rest);
}
// function _reshapeTree(path, a, b, f) {
// if (a == null) {
// throw new Error('wtf2');
// }
// if (b == null) {
// return f(path, a);
// }
// if (Array.isArray(a)) {
// return a.map((val, i) => _reshapeTree([...path, i], val, b[i], f));
// }
// if (Object.prototype.toString.call(a) === '[object Object]') {
// const newTree = {};
// Object.keys(a).forEach(key => {
// newTree[key] = _reshapeTree([...path, key], a[key], b[key], f);
// });
// return newTree;
// }
// return b;
// }
// export function reshapeTree(a, b, f) {
// return _reshapeTree([], a, b, f);
// }
// export function toOj(vals, keys) {
// const ret = {};
// vals.forEach((val, i) => ret[keys[i]] = val);
// return ret;
// }
// export function toArr(obj) {
// const keys = Object.keys(obj);
// const vals = keys.map(k => obj[k]);
// return [keys, vals];
// }
// TODO: these are for a demos, not for the library. Move
export function reinsert(arr, from, to) {
const _arr = arr.slice(0);
const val = _arr[from];
_arr.splice(from, 1);
_arr.splice(to, 0, val);
return _arr;
}
export function clamp(n, min, max) {
return Math.max(Math.min(n, max), min);
}
export function range(start, afterStop) {
let _afterStop = afterStop;
let _start = start;
if (afterStop == null) {
_afterStop = start;
_start = 0;
}
const ret = [];
for (let i = _start; i < _afterStop; i++) {
ret.push(i);
}
return ret;
}

Sorry, the diff of this file is not supported yet