Interpolate method, input validation, linting, refactoring. (#17)

Interpolate validations are taken from `Animated`.
This commit is contained in:
Dylan Vann
2018-06-07 14:24:48 -04:00
committed by Krzysztof Magiera
parent 401227f8f1
commit ad8b0cf534
23 changed files with 929 additions and 212 deletions

17
.eslintrc.js Normal file
View File

@@ -0,0 +1,17 @@
module.exports = {
parser: 'babel-eslint',
extends: [
'standard',
'prettier',
'prettier/flowtype',
'prettier/react',
'prettier/standard',
],
plugins: ['react', 'react-native', 'import'],
env: {
'react-native/react-native': true,
},
rules: {
'import/no-unresolved': 2,
},
};

View File

@@ -12,7 +12,6 @@ const {
eq,
and,
add,
call,
multiply,
lessThan,
startClock,
@@ -20,13 +19,12 @@ const {
clockRunning,
block,
timing,
debug,
spring,
Value,
Clock,
event,
interpolate,
defined,
Extrapolate,
} = Animated;
function runSpring(clock, value, velocity, dest) {
@@ -60,41 +58,6 @@ function runSpring(clock, value, velocity, dest) {
];
}
function runTiming(clock, value, dest) {
const state = {
finished: new Value(1),
position: new Value(value),
time: new Value(0),
frameTime: new Value(0),
};
const config = {
duration: 500,
toValue: new Value(0),
easing: Easing.inOut(Easing.ease),
};
const reset = [
set(state.finished, 0),
set(state.time, 0),
set(state.frameTime, 0),
];
return block([
cond(and(state.finished, eq(state.position, value)), [
...reset,
set(config.toValue, dest),
]),
cond(and(state.finished, eq(state.position, dest)), [
...reset,
set(config.toValue, value),
]),
cond(clockRunning(clock), 0, startClock(clock)),
timing(clock, state, config),
state.position,
]);
}
const getAnimation = (min, max) => {
const clock = new Clock();
const state = {
@@ -173,7 +136,7 @@ export default class AnimatedBounds extends Component {
this._transX = interpolate(this._transX, {
inputRange: [-100, 100],
outputRange: [-100, 100],
extrapolate: 'clamp',
extrapolate: Extrapolate.CLAMP,
});
const min = getAnimation(-100, -50);
@@ -181,7 +144,7 @@ export default class AnimatedBounds extends Component {
this._transXA = interpolate(this._transX, {
inputRange: [-100, 100],
outputRange: [min, max],
extrapolate: 'clamp',
extrapolate: Extrapolate.CLAMP,
});
this.min = min;
this.max = max;

View File

@@ -1,5 +1,4 @@
import React, { Component } from 'react';
import { StyleSheet, View } from 'react-native';
import Animated, { Easing } from 'react-native-reanimated';
import Box from '../Box';
import Row from '../Row';
@@ -9,20 +8,12 @@ const {
cond,
eq,
and,
add,
call,
multiply,
lessThan,
startClock,
stopClock,
clockRunning,
block,
timing,
debug,
spring,
Value,
Clock,
event,
interpolate,
} = Animated;

View File

@@ -10,18 +10,12 @@ const {
cond,
sub,
eq,
and,
add,
call,
multiply,
lessThan,
startClock,
stopClock,
clockRunning,
block,
timing,
debug,
spring,
Value,
Clock,
event,
@@ -60,41 +54,6 @@ function runSpring(clock, value, velocity, dest) {
];
}
function runTiming(clock, value, dest) {
const state = {
finished: new Value(1),
position: new Value(value),
time: new Value(0),
frameTime: new Value(0),
};
const config = {
duration: 500,
toValue: new Value(0),
easing: Easing.inOut(Easing.ease),
};
const reset = [
set(state.finished, 0),
set(state.time, 0),
set(state.frameTime, 0),
];
return block([
cond(and(state.finished, eq(state.position, value)), [
...reset,
set(config.toValue, dest),
]),
cond(and(state.finished, eq(state.position, dest)), [
...reset,
set(config.toValue, value),
]),
cond(clockRunning(clock), 0, startClock(clock)),
timing(clock, state, config),
state.position,
]);
}
export default class WithDrag extends Component {
constructor(props) {
super(props);

View File

@@ -1,11 +1,12 @@
{
"name": "react-native-reanimated",
"version": "1.0.0-alpha.3",
"description":
"More powerfull alternative to Animated library for React Native",
"description": "More powerfull alternative to Animated library for React Native",
"scripts": {
"start": "node node_modules/react-native/local-cli/cli.js start",
"test": "jest",
"format": "prettier --write './src/**'",
"lint": "eslint --fix './src/**'",
"precommit": "lint-staged"
},
"main": "src/Animated.js",
@@ -40,8 +41,18 @@
"preset": "jest-react-native"
},
"devDependencies": {
"babel-eslint": "^8.2.3",
"babel-jest": "16.0.0",
"babel-preset-react-native": "1.9.0",
"eslint": "^4.19.1",
"eslint-config-prettier": "^2.9.0",
"eslint-config-standard": "^11.0.0",
"eslint-plugin-import": "^2.12.0",
"eslint-plugin-node": "^6.0.1",
"eslint-plugin-promise": "^3.8.0",
"eslint-plugin-react": "^7.8.2",
"eslint-plugin-react-native": "^3.2.1",
"eslint-plugin-standard": "^3.1.0",
"husky": "^0.14.3",
"jest": "16.0.2",
"jest-react-native": "16.0.0",
@@ -52,7 +63,7 @@
},
"lint-staged": {
"*.js": [
"prettier --write --print-width 80 --tab-width 2 --single-quote --jsx-bracket-same-line=true --trailing-comma=es5",
"prettier --write",
"git add"
]
}

7
prettier.config.js Normal file
View File

@@ -0,0 +1,7 @@
module.exports = {
printWidth: 80,
singleQuote: true,
trailingComma: 'es5',
jsxBracketSameLine: true,
tabWidth: 2,
};

View File

@@ -15,7 +15,6 @@ let NATIVE_PROPS_WHITELIST = {
borderColor: true,
borderEndColor: true,
borderLeftColor: true,
backgroundColor: true,
borderStartColor: true,
borderTopColor: true,
/* ios styles */

View File

@@ -1,7 +1,5 @@
import {
greaterThan,
cond,
greaterOrEq,
lessThan,
multiply,
pow,
@@ -9,7 +7,6 @@ import {
sqrt,
sub,
add,
debug,
divide,
} from './base';
import AnimatedBezier from './core/AnimatedBezier';

View File

@@ -33,9 +33,9 @@ function getSlope(aT, aA1, aA2) {
}
function binarySubdivide(aX, aA, aB, mX1, mX2) {
var currentX,
currentT,
i = 0;
var currentX = 0;
var currentT = 0;
var i = 0;
do {
currentT = aA + (aB - aA) / 2.0;
currentX = calcBezier(currentT, mX1, mX2) - aX;
@@ -64,8 +64,7 @@ function newtonRaphsonIterate(aX, aGuessT, mX1, mX2) {
}
function bezier(mX1, mY1, mX2, mY2) {
if (!(0 <= mX1 && mX1 <= 1 && 0 <= mX2 && mX2 <= 1)) {
// eslint-disable-line yoda
if (!(mX1 >= 0 && mX1 <= 1 && mX2 >= 0 && mX2 <= 1)) {
throw new Error('bezier x values must be in [0, 1] range');
}

View File

@@ -106,7 +106,7 @@ export default class AnimatedNode {
}
__onEvaluate() {
throw new Excaption('Missing implementation of onEvaluate');
throw new Error('Missing implementation of onEvaluate');
}
__getProps() {

View File

@@ -40,10 +40,12 @@ const OPERATIONS = {
// comparing
lessThan: infix((a, b) => a < b),
/* eslint-disable-next-line eqeqeq */
eq: infix((a, b) => a == b),
greaterThan: infix((a, b) => a > b),
lessOrEq: infix((a, b) => a <= b),
greaterOrEq: infix((a, b) => a >= b),
/* eslint-disable-next-line eqeqeq */
neq: infix((a, b) => a != b),
};

View File

@@ -1,5 +1,6 @@
import AnimatedNode from './AnimatedNode';
import { val } from '../utils';
import interpolate from '../derived/interpolate';
function sanitizeValue(value) {
return value === null || value === undefined ? value : Number(value);
@@ -27,4 +28,8 @@ export default class AnimatedValue extends AnimatedNode {
this._value = value;
this.__forceUpdateCache(value);
}
interpolate(config) {
return interpolate(this, config);
}
}

View File

@@ -34,6 +34,7 @@ export default function createAnimatedComponent(Component) {
_eventDetachers = [];
/* eslint-disable-next-line camelcase */
static __skipSetNativeProps_FOR_TESTS_ONLY = false;
constructor(props) {

5
src/derived/abs.js Normal file
View File

@@ -0,0 +1,5 @@
import { cond, lessThan, multiply } from '../base';
export default function abs(a) {
return cond(lessThan(a, 0), multiply(-1, a), a);
}

7
src/derived/acc.js Normal file
View File

@@ -0,0 +1,7 @@
import { set, add } from '../base';
import AnimatedValue from '../core/AnimatedValue';
export default function acc(v) {
const acc = new AnimatedValue(0);
return set(acc, add(acc, v));
}

26
src/derived/color.js Normal file
View File

@@ -0,0 +1,26 @@
import { cond, lessThan, multiply, round, add, sub } from '../base';
import { Platform } from 'react-native';
import AnimatedValue from '../core/AnimatedValue';
export default function color(r, g, b, a = 1) {
if (a instanceof AnimatedValue) {
a = round(multiply(a, 255));
} else {
a = Math.round(a * 255);
}
const color = add(
multiply(a, 1 << 24),
multiply(r, 1 << 16),
multiply(g, 1 << 8),
b
);
if (Platform.OS === 'android') {
// on Android color is represented as signed 32 bit int
return cond(
lessThan(color, (1 << 31) >>> 0),
color,
sub(color, Math.pow(2, 32))
);
}
return color;
}

12
src/derived/diff.js Normal file
View File

@@ -0,0 +1,12 @@
import { cond, block, defined, sub, set } from '../base';
import AnimatedValue from '../core/AnimatedValue';
export default function diff(v) {
const stash = new AnimatedValue(0);
const prev = new AnimatedValue();
return block([
set(stash, cond(defined(prev), sub(v, prev), 0)),
set(prev, v),
stash,
]);
}

13
src/derived/diffClamp.js Normal file
View File

@@ -0,0 +1,13 @@
import { cond, defined, set, add } from '../base';
import AnimatedValue from '../core/AnimatedValue';
import { min } from './min';
import { max } from './max';
import { diff } from './diff';
export default function diffClamp(a, minVal, maxVal) {
const value = new AnimatedValue();
return set(
value,
min(max(add(cond(defined(value), value, a), diff(a)), minVal), maxVal)
);
}

8
src/derived/index.js Normal file
View File

@@ -0,0 +1,8 @@
export { default as abs } from './abs';
export { default as acc } from './acc';
export { default as color } from './color';
export { default as diff } from './diff';
export { default as diffClamp } from './diffClamp';
export { default as interpolate, Extrapolate } from './interpolate';
export { default as max } from './max';
export { default as min } from './min';

View File

@@ -1,102 +1,25 @@
import { Platform } from 'react-native';
import {
cond,
lessThan,
greaterThan,
multiply,
block,
defined,
sub,
set,
add,
divide,
round,
} from './base';
import AnimatedValue from './core/AnimatedValue';
import { adapt } from './utils';
greaterThan,
} from '../base';
import invariant from 'fbjs/lib/invariant';
import AnimatedNode from '../core/AnimatedNode';
export const abs = function(a) {
return cond(lessThan(a, 0), multiply(-1, a), a);
};
export const min = function(a, b) {
a = adapt(a);
b = adapt(b);
return cond(lessThan(a, b), a, b);
};
export const max = function(a, b) {
a = adapt(a);
b = adapt(b);
return cond(lessThan(a, b), b, a);
};
export const diff = function(v) {
const stash = new AnimatedValue(0);
const prev = new AnimatedValue();
return block([
set(stash, cond(defined(prev), sub(v, prev), 0)),
set(prev, v),
stash,
]);
};
export const color = function(r, g, b, a = 1) {
if (a instanceof AnimatedValue) {
a = round(multiply(a, 255));
} else {
a = Math.round(a * 255);
}
const color = add(
multiply(a, 1 << 24),
multiply(r, 1 << 16),
multiply(g, 1 << 8),
b
);
if (Platform.OS === 'android') {
// on Android color is represented as signed 32 bit int
return cond(
lessThan(color, (1 << 31) >>> 0),
color,
sub(color, Math.pow(2, 32))
);
}
return color;
};
export const acc = function(v) {
const acc = new AnimatedValue(0);
return set(acc, add(acc, v));
};
export const diffClamp = function(a, minVal, maxVal) {
const value = new AnimatedValue();
return set(
value,
min(max(add(cond(defined(value), value, a), diff(a)), minVal), maxVal)
);
};
const interpolateInternalSingle = function(
value,
inputRange,
outputRange,
offset
) {
function interpolateInternalSingle(value, inputRange, outputRange, offset) {
const inS = inputRange[offset];
const inE = inputRange[offset + 1];
const outS = outputRange[offset];
const outE = outputRange[offset + 1];
const progress = divide(sub(value, inS), sub(inE, inS));
return add(outS, multiply(progress, sub(outE, outS)));
};
}
const interpolateInternal = function(
value,
inputRange,
outputRange,
offset = 0
) {
function interpolateInternal(value, inputRange, outputRange, offset = 0) {
if (inputRange.length - offset === 2) {
return interpolateInternalSingle(value, inputRange, outputRange, offset);
}
@@ -105,7 +28,7 @@ const interpolateInternal = function(
interpolateInternalSingle(value, inputRange, outputRange, offset),
interpolateInternal(value, inputRange, outputRange, offset + 1)
);
};
}
export const Extrapolate = {
EXTEND: 'extend',
@@ -113,7 +36,44 @@ export const Extrapolate = {
IDENTITY: 'identity',
};
export const interpolate = function(value, config) {
function checkNonDecreasing(name, arr) {
for (let i = 1; i < arr.length; ++i) {
// We can't validate animated nodes in JS.
if (arr[i] instanceof AnimatedNode || arr[i - 1] instanceof AnimatedNode)
continue;
invariant(
arr[i] >= arr[i - 1],
'%s must be monotonically non-decreasing. (%s)',
name,
arr
);
}
}
function checkMinElements(name, arr) {
invariant(
arr.length >= 2,
'%s must have at least 2 elements. (%s)',
name,
arr
);
}
function checkValidNumbers(name, arr) {
for (let i = 0; i < arr.length; i++) {
// We can't validate animated nodes in JS.
if (arr[i] instanceof AnimatedNode) continue;
invariant(
Number.isFinite(arr[i]),
'%s cannot include %s. (%s)',
name,
arr[i],
arr
);
}
}
export default function interpolate(value, config) {
const {
inputRange,
outputRange,
@@ -121,6 +81,16 @@ export const interpolate = function(value, config) {
extrapolateLeft,
extrapolateRight,
} = config;
checkMinElements('inputRange', inputRange);
checkValidNumbers('inputRange', inputRange);
checkMinElements('outputRange', outputRange);
checkValidNumbers('outputRange', outputRange);
checkNonDecreasing('inputRange', inputRange);
invariant(
inputRange.length === outputRange.length,
'inputRange and outputRange must be the same length.'
);
const left = extrapolateLeft || extrapolate;
const right = extrapolateRight || extrapolate;
let output = interpolateInternal(value, inputRange, outputRange);
@@ -148,4 +118,4 @@ export const interpolate = function(value, config) {
}
return output;
};
}

8
src/derived/max.js Normal file
View File

@@ -0,0 +1,8 @@
import { cond, lessThan } from '../base';
import { adapt } from '../utils';
export default function max(a, b) {
a = adapt(a);
b = adapt(b);
return cond(lessThan(a, b), b, a);
}

8
src/derived/min.js Normal file
View File

@@ -0,0 +1,8 @@
import { cond, lessThan } from '../base';
import { adapt } from '../utils';
export default function min(a, b) {
a = adapt(a);
b = adapt(b);
return cond(lessThan(a, b), a, b);
}

753
yarn.lock

File diff suppressed because it is too large Load Diff