react-motion
Advanced tools
| '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']; |
+109
| // 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 |
+10
-1
@@ -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; |
+8
-2
| { | ||
| "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" | ||
| } | ||
| } |
+9
-8
@@ -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 @@ ### <Spring /> | ||
| ### <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' |
-137
| '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'; |
-396
| 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]; | ||
| } |
-110
| 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
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
16
6.67%285
0.35%2
-66.67%38244
-18.23%4
300%15
7.14%693
-35.65%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added