diff --git a/.flowconfig b/.flowconfig index 9a733d85..ee67ef33 100644 --- a/.flowconfig +++ b/.flowconfig @@ -2,7 +2,6 @@ .*/__tests__/.* .*/benchmarks/.* .*/docs/.* -.*/node_modules/animated/* .*/node_modules/babel-plugin-transform-react-remove-prop-types/* [include] diff --git a/package.json b/package.json index 4c49f4a1..060dd698 100644 --- a/package.json +++ b/package.json @@ -19,12 +19,12 @@ "docs:start": "cd docs && yarn && yarn start", "docs:release": "cd docs && yarn release", "flow": "flow", - "fmt": "find babel benchmarks docs jest src -name '*.js' | grep -v -E '(node_modules|dist)' | xargs yarn fmt:cmd", + "fmt": "find babel benchmarks docs jest src -name '*.js' | grep -v -E '(node_modules|dist|vendor)' | xargs yarn fmt:cmd", "fmt:cmd": "prettier --print-width=100 --single-quote --write", "jest": "jest", "jest:watch": "yarn test --watch", "lint": "yarn lint:cmd babel benchmarks docs jest src", - "lint:cmd": "eslint --ignore-path .gitignore --fix", + "lint:cmd": "eslint --ignore-path .gitignore --ignore-pattern '/src/vendor/*' --fix", "precommit": "lint-staged", "release": "yarn lint && yarn test && yarn build && npm publish", "test": "flow && jest" @@ -61,7 +61,6 @@ ] }, "dependencies": { - "animated": "^0.2.0", "array-find-index": "^1.0.2", "babel-runtime": "^6.26.0", "create-react-class": "^15.6.2", diff --git a/src/apis/Animated/index.js b/src/apis/Animated/index.js index 4093fa38..496b3cbe 100644 --- a/src/apis/Animated/index.js +++ b/src/apis/Animated/index.js @@ -3,27 +3,25 @@ * All rights reserved. * * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. * * @providesModule Animated - * @noflow + * @flow */ -import Animated from 'animated'; +import AnimatedImplementation from '../../vendor/Animated/AnimatedImplementation'; import Image from '../../components/Image'; import ScrollView from '../../components/ScrollView'; -import StyleSheet from '../StyleSheet'; import Text from '../../components/Text'; import View from '../../components/View'; -Animated.inject.FlattenStyle(StyleSheet.flatten); - -const AnimatedImplementation = { - ...Animated, - Image: Animated.createAnimatedComponent(Image), - ScrollView: Animated.createAnimatedComponent(ScrollView), - Text: Animated.createAnimatedComponent(Text), - View: Animated.createAnimatedComponent(View) +const Animated = { + ...AnimatedImplementation, + Image: AnimatedImplementation.createAnimatedComponent(Image), + ScrollView: AnimatedImplementation.createAnimatedComponent(ScrollView), + View: AnimatedImplementation.createAnimatedComponent(View), + Text: AnimatedImplementation.createAnimatedComponent(Text) }; -export default AnimatedImplementation; +export default Animated; diff --git a/src/apis/Easing/index.js b/src/apis/Easing/index.js index f0b43f31..238a0120 100644 --- a/src/apis/Easing/index.js +++ b/src/apis/Easing/index.js @@ -6,8 +6,8 @@ * LICENSE file in the root directory of this source tree. * * @providesModule Easing - * @noflow + * @flow */ -import Easing from 'animated/lib/Easing'; +import Easing from '../../vendor/Animated/Easing'; export default Easing; diff --git a/src/modules/NativeEventEmitter/index.js b/src/modules/NativeEventEmitter/index.js new file mode 100644 index 00000000..c716b9a3 --- /dev/null +++ b/src/modules/NativeEventEmitter/index.js @@ -0,0 +1,25 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule NativeEventEmitter + * @noflow + */ +'use strict'; + +class NativeEventEmitter { + addListener() {} + emit() {} + listeners() {} + once() {} + removeAllListeners() {} + removeCurrentListener() {} + removeListener() {} + removeSubscription() {} +} + +module.exports = NativeEventEmitter; diff --git a/src/modules/NativeModules/index.js b/src/modules/NativeModules/index.js index 6964e433..38f12a98 100644 --- a/src/modules/NativeModules/index.js +++ b/src/modules/NativeModules/index.js @@ -1,3 +1,3 @@ // NativeModules shim const NativeModules = {}; -export default NativeModules; +module.exports = NativeModules; diff --git a/src/vendor/Animated/AnimatedEvent.js b/src/vendor/Animated/AnimatedEvent.js new file mode 100644 index 00000000..2521ee3c --- /dev/null +++ b/src/vendor/Animated/AnimatedEvent.js @@ -0,0 +1,196 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule AnimatedEvent + * @noflow + * @format + */ +'use strict'; + +const AnimatedValue = require('./nodes/AnimatedValue'); +const NativeAnimatedHelper = require('./NativeAnimatedHelper'); +const findNodeHandle = require('../../modules/findNodeHandle').default; + +const invariant = require('fbjs/lib/invariant'); +const {shouldUseNativeDriver} = require('./NativeAnimatedHelper'); + +export type Mapping = {[key: string]: Mapping} | AnimatedValue; +export type EventConfig = { + listener?: ?Function, + useNativeDriver?: boolean, +}; + +function attachNativeEvent( + viewRef: any, + eventName: string, + argMapping: Array, +) { + // Find animated values in `argMapping` and create an array representing their + // key path inside the `nativeEvent` object. Ex.: ['contentOffset', 'x']. + const eventMappings = []; + + const traverse = (value, path) => { + if (value instanceof AnimatedValue) { + value.__makeNative(); + + eventMappings.push({ + nativeEventPath: path, + animatedValueTag: value.__getNativeTag(), + }); + } else if (typeof value === 'object') { + for (const key in value) { + traverse(value[key], path.concat(key)); + } + } + }; + + invariant( + argMapping[0] && argMapping[0].nativeEvent, + 'Native driven events only support animated values contained inside `nativeEvent`.', + ); + + // Assume that the event containing `nativeEvent` is always the first argument. + traverse(argMapping[0].nativeEvent, []); + + const viewTag = findNodeHandle(viewRef); + + eventMappings.forEach(mapping => { + NativeAnimatedHelper.API.addAnimatedEventToView( + viewTag, + eventName, + mapping, + ); + }); + + return { + detach() { + eventMappings.forEach(mapping => { + NativeAnimatedHelper.API.removeAnimatedEventFromView( + viewTag, + eventName, + mapping.animatedValueTag, + ); + }); + }, + }; +} + +class AnimatedEvent { + _argMapping: Array; + _listeners: Array = []; + _callListeners: Function; + _attachedEvent: ?{ + detach: () => void, + }; + __isNative: boolean; + + constructor(argMapping: Array, config?: EventConfig = {}) { + this._argMapping = argMapping; + if (config.listener) { + this.__addListener(config.listener); + } + this._callListeners = this._callListeners.bind(this); + this._attachedEvent = null; + this.__isNative = shouldUseNativeDriver(config); + + if (process.env.NODE_ENV !== 'production') { + this._validateMapping(); + } + } + + __addListener(callback: Function): void { + this._listeners.push(callback); + } + + __removeListener(callback: Function): void { + this._listeners = this._listeners.filter(listener => listener !== callback); + } + + __attach(viewRef: any, eventName: string) { + invariant( + this.__isNative, + 'Only native driven events need to be attached.', + ); + + this._attachedEvent = attachNativeEvent( + viewRef, + eventName, + this._argMapping, + ); + } + + __detach(viewTag: any, eventName: string) { + invariant( + this.__isNative, + 'Only native driven events need to be detached.', + ); + + this._attachedEvent && this._attachedEvent.detach(); + } + + __getHandler() { + if (this.__isNative) { + return this._callListeners; + } + + return (...args: any) => { + const traverse = (recMapping, recEvt, key) => { + if (typeof recEvt === 'number' && recMapping instanceof AnimatedValue) { + recMapping.setValue(recEvt); + } else if (typeof recMapping === 'object') { + for (const mappingKey in recMapping) { + /* $FlowFixMe(>=0.53.0 site=react_native_fb,react_native_oss) This + * comment suppresses an error when upgrading Flow's support for + * React. To see the error delete this comment and run Flow. */ + traverse(recMapping[mappingKey], recEvt[mappingKey], mappingKey); + } + } + }; + + if (!this.__isNative) { + this._argMapping.forEach((mapping, idx) => { + traverse(mapping, args[idx], 'arg' + idx); + }); + } + this._callListeners(...args); + }; + } + + _callListeners(...args) { + this._listeners.forEach(listener => listener(...args)); + } + + _validateMapping() { + const traverse = (recMapping, recEvt, key) => { + if (typeof recEvt === 'number') { + invariant( + recMapping instanceof AnimatedValue, + 'Bad mapping of type ' + + typeof recMapping + + ' for key ' + + key + + ', event value must map to AnimatedValue', + ); + return; + } + invariant( + typeof recMapping === 'object', + 'Bad mapping of type ' + typeof recMapping + ' for key ' + key, + ); + invariant( + typeof recEvt === 'object', + 'Bad event of type ' + typeof recEvt + ' for key ' + key, + ); + for (const mappingKey in recMapping) { + traverse(recMapping[mappingKey], recEvt[mappingKey], mappingKey); + } + }; + } +} + +module.exports = {AnimatedEvent, attachNativeEvent}; diff --git a/src/vendor/Animated/AnimatedImplementation.js b/src/vendor/Animated/AnimatedImplementation.js new file mode 100644 index 00000000..ef566927 --- /dev/null +++ b/src/vendor/Animated/AnimatedImplementation.js @@ -0,0 +1,676 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule AnimatedImplementation + * @flow + * @format + * @preventMunge + */ +'use strict'; + +const {AnimatedEvent, attachNativeEvent} = require('./AnimatedEvent'); +const AnimatedAddition = require('./nodes/AnimatedAddition'); +const AnimatedDiffClamp = require('./nodes/AnimatedDiffClamp'); +const AnimatedDivision = require('./nodes/AnimatedDivision'); +const AnimatedInterpolation = require('./nodes/AnimatedInterpolation'); +const AnimatedModulo = require('./nodes/AnimatedModulo'); +const AnimatedMultiplication = require('./nodes/AnimatedMultiplication'); +const AnimatedNode = require('./nodes/AnimatedNode'); +const AnimatedProps = require('./nodes/AnimatedProps'); +const AnimatedTracking = require('./nodes/AnimatedTracking'); +const AnimatedValue = require('./nodes/AnimatedValue'); +const AnimatedValueXY = require('./nodes/AnimatedValueXY'); +const DecayAnimation = require('./animations/DecayAnimation'); +const SpringAnimation = require('./animations/SpringAnimation'); +const TimingAnimation = require('./animations/TimingAnimation'); + +const createAnimatedComponent = require('./createAnimatedComponent'); + +import type { + AnimationConfig, + EndCallback, + EndResult, +} from './animations/Animation'; +import type {TimingAnimationConfig} from './animations/TimingAnimation'; +import type {DecayAnimationConfig} from './animations/DecayAnimation'; +import type {SpringAnimationConfig} from './animations/SpringAnimation'; +import type {Mapping, EventConfig} from './AnimatedEvent'; + +type CompositeAnimation = { + start: (callback?: ?EndCallback) => void, + stop: () => void, + reset: () => void, + _startNativeLoop: (iterations?: number) => void, + _isUsingNativeDriver: () => boolean, +}; + +const add = function( + a: AnimatedNode | number, + b: AnimatedNode | number, +): AnimatedAddition { + return new AnimatedAddition(a, b); +}; + +const divide = function( + a: AnimatedNode | number, + b: AnimatedNode | number, +): AnimatedDivision { + return new AnimatedDivision(a, b); +}; + +const multiply = function( + a: AnimatedNode | number, + b: AnimatedNode | number, +): AnimatedMultiplication { + return new AnimatedMultiplication(a, b); +}; + +const modulo = function(a: AnimatedNode, modulus: number): AnimatedModulo { + return new AnimatedModulo(a, modulus); +}; + +const diffClamp = function( + a: AnimatedNode, + min: number, + max: number, +): AnimatedDiffClamp { + return new AnimatedDiffClamp(a, min, max); +}; + +const _combineCallbacks = function( + callback: ?EndCallback, + config: AnimationConfig, +) { + if (callback && config.onComplete) { + return (...args) => { + config.onComplete && config.onComplete(...args); + callback && callback(...args); + }; + } else { + return callback || config.onComplete; + } +}; + +const maybeVectorAnim = function( + value: AnimatedValue | AnimatedValueXY, + config: Object, + anim: (value: AnimatedValue, config: Object) => CompositeAnimation, +): ?CompositeAnimation { + if (value instanceof AnimatedValueXY) { + const configX = {...config}; + const configY = {...config}; + for (const key in config) { + const {x, y} = config[key]; + if (x !== undefined && y !== undefined) { + configX[key] = x; + configY[key] = y; + } + } + const aX = anim((value: AnimatedValueXY).x, configX); + const aY = anim((value: AnimatedValueXY).y, configY); + // We use `stopTogether: false` here because otherwise tracking will break + // because the second animation will get stopped before it can update. + return parallel([aX, aY], {stopTogether: false}); + } + return null; +}; + +const spring = function( + value: AnimatedValue | AnimatedValueXY, + config: SpringAnimationConfig, +): CompositeAnimation { + const start = function( + animatedValue: AnimatedValue | AnimatedValueXY, + configuration: SpringAnimationConfig, + callback?: ?EndCallback, + ): void { + callback = _combineCallbacks(callback, configuration); + const singleValue: any = animatedValue; + const singleConfig: any = configuration; + singleValue.stopTracking(); + if (configuration.toValue instanceof AnimatedNode) { + singleValue.track( + new AnimatedTracking( + singleValue, + configuration.toValue, + SpringAnimation, + singleConfig, + callback, + ), + ); + } else { + singleValue.animate(new SpringAnimation(singleConfig), callback); + } + }; + return ( + maybeVectorAnim(value, config, spring) || { + start: function(callback?: ?EndCallback): void { + start(value, config, callback); + }, + + stop: function(): void { + value.stopAnimation(); + }, + + reset: function(): void { + value.resetAnimation(); + }, + + _startNativeLoop: function(iterations?: number): void { + const singleConfig = {...config, iterations}; + start(value, singleConfig); + }, + + _isUsingNativeDriver: function(): boolean { + return config.useNativeDriver || false; + }, + } + ); +}; + +const timing = function( + value: AnimatedValue | AnimatedValueXY, + config: TimingAnimationConfig, +): CompositeAnimation { + const start = function( + animatedValue: AnimatedValue | AnimatedValueXY, + configuration: TimingAnimationConfig, + callback?: ?EndCallback, + ): void { + callback = _combineCallbacks(callback, configuration); + const singleValue: any = animatedValue; + const singleConfig: any = configuration; + singleValue.stopTracking(); + if (configuration.toValue instanceof AnimatedNode) { + singleValue.track( + new AnimatedTracking( + singleValue, + configuration.toValue, + TimingAnimation, + singleConfig, + callback, + ), + ); + } else { + singleValue.animate(new TimingAnimation(singleConfig), callback); + } + }; + + return ( + maybeVectorAnim(value, config, timing) || { + start: function(callback?: ?EndCallback): void { + start(value, config, callback); + }, + + stop: function(): void { + value.stopAnimation(); + }, + + reset: function(): void { + value.resetAnimation(); + }, + + _startNativeLoop: function(iterations?: number): void { + const singleConfig = {...config, iterations}; + start(value, singleConfig); + }, + + _isUsingNativeDriver: function(): boolean { + return config.useNativeDriver || false; + }, + } + ); +}; + +const decay = function( + value: AnimatedValue | AnimatedValueXY, + config: DecayAnimationConfig, +): CompositeAnimation { + const start = function( + animatedValue: AnimatedValue | AnimatedValueXY, + configuration: DecayAnimationConfig, + callback?: ?EndCallback, + ): void { + callback = _combineCallbacks(callback, configuration); + const singleValue: any = animatedValue; + const singleConfig: any = configuration; + singleValue.stopTracking(); + singleValue.animate(new DecayAnimation(singleConfig), callback); + }; + + return ( + maybeVectorAnim(value, config, decay) || { + start: function(callback?: ?EndCallback): void { + start(value, config, callback); + }, + + stop: function(): void { + value.stopAnimation(); + }, + + reset: function(): void { + value.resetAnimation(); + }, + + _startNativeLoop: function(iterations?: number): void { + const singleConfig = {...config, iterations}; + start(value, singleConfig); + }, + + _isUsingNativeDriver: function(): boolean { + return config.useNativeDriver || false; + }, + } + ); +}; + +const sequence = function( + animations: Array, +): CompositeAnimation { + let current = 0; + return { + start: function(callback?: ?EndCallback) { + const onComplete = function(result) { + if (!result.finished) { + callback && callback(result); + return; + } + + current++; + + if (current === animations.length) { + callback && callback(result); + return; + } + + animations[current].start(onComplete); + }; + + if (animations.length === 0) { + callback && callback({finished: true}); + } else { + animations[current].start(onComplete); + } + }, + + stop: function() { + if (current < animations.length) { + animations[current].stop(); + } + }, + + reset: function() { + animations.forEach((animation, idx) => { + if (idx <= current) { + animation.reset(); + } + }); + current = 0; + }, + + _startNativeLoop: function() { + throw new Error( + 'Loops run using the native driver cannot contain Animated.sequence animations', + ); + }, + + _isUsingNativeDriver: function(): boolean { + return false; + }, + }; +}; + +type ParallelConfig = { + stopTogether?: boolean, // If one is stopped, stop all. default: true +}; +const parallel = function( + animations: Array, + config?: ?ParallelConfig, +): CompositeAnimation { + let doneCount = 0; + // Make sure we only call stop() at most once for each animation + const hasEnded = {}; + const stopTogether = !(config && config.stopTogether === false); + + const result = { + start: function(callback?: ?EndCallback) { + if (doneCount === animations.length) { + callback && callback({finished: true}); + return; + } + + animations.forEach((animation, idx) => { + const cb = function(endResult) { + hasEnded[idx] = true; + doneCount++; + if (doneCount === animations.length) { + doneCount = 0; + callback && callback(endResult); + return; + } + + if (!endResult.finished && stopTogether) { + result.stop(); + } + }; + + if (!animation) { + cb({finished: true}); + } else { + animation.start(cb); + } + }); + }, + + stop: function(): void { + animations.forEach((animation, idx) => { + !hasEnded[idx] && animation.stop(); + hasEnded[idx] = true; + }); + }, + + reset: function(): void { + animations.forEach((animation, idx) => { + animation.reset(); + hasEnded[idx] = false; + doneCount = 0; + }); + }, + + _startNativeLoop: function() { + throw new Error( + 'Loops run using the native driver cannot contain Animated.parallel animations', + ); + }, + + _isUsingNativeDriver: function(): boolean { + return false; + }, + }; + + return result; +}; + +const delay = function(time: number): CompositeAnimation { + // Would be nice to make a specialized implementation + return timing(new AnimatedValue(0), {toValue: 0, delay: time, duration: 0}); +}; + +const stagger = function( + time: number, + animations: Array, +): CompositeAnimation { + return parallel( + animations.map((animation, i) => { + return sequence([delay(time * i), animation]); + }), + ); +}; + +type LoopAnimationConfig = {iterations: number}; + +const loop = function( + animation: CompositeAnimation, + {iterations = -1}: LoopAnimationConfig = {}, +): CompositeAnimation { + let isFinished = false; + let iterationsSoFar = 0; + return { + start: function(callback?: ?EndCallback) { + const restart = function(result: EndResult = {finished: true}): void { + if ( + isFinished || + iterationsSoFar === iterations || + result.finished === false + ) { + callback && callback(result); + } else { + iterationsSoFar++; + animation.reset(); + animation.start(restart); + } + }; + if (!animation || iterations === 0) { + callback && callback({finished: true}); + } else { + if (animation._isUsingNativeDriver()) { + animation._startNativeLoop(iterations); + } else { + restart(); // Start looping recursively on the js thread + } + } + }, + + stop: function(): void { + isFinished = true; + animation.stop(); + }, + + reset: function(): void { + iterationsSoFar = 0; + isFinished = false; + animation.reset(); + }, + + _startNativeLoop: function() { + throw new Error( + 'Loops run using the native driver cannot contain Animated.loop animations', + ); + }, + + _isUsingNativeDriver: function(): boolean { + return animation._isUsingNativeDriver(); + }, + }; +}; + +function forkEvent( + event: ?AnimatedEvent | ?Function, + listener: Function, +): AnimatedEvent | Function { + if (!event) { + return listener; + } else if (event instanceof AnimatedEvent) { + event.__addListener(listener); + return event; + } else { + return (...args) => { + typeof event === 'function' && event(...args); + listener(...args); + }; + } +} + +function unforkEvent( + event: ?AnimatedEvent | ?Function, + listener: Function, +): void { + if (event && event instanceof AnimatedEvent) { + event.__removeListener(listener); + } +} + +const event = function(argMapping: Array, config?: EventConfig): any { + const animatedEvent = new AnimatedEvent(argMapping, config); + if (animatedEvent.__isNative) { + return animatedEvent; + } else { + return animatedEvent.__getHandler(); + } +}; + +/** + * The `Animated` library is designed to make animations fluid, powerful, and + * easy to build and maintain. `Animated` focuses on declarative relationships + * between inputs and outputs, with configurable transforms in between, and + * simple `start`/`stop` methods to control time-based animation execution. + * + * See http://facebook.github.io/react-native/docs/animated.html + */ +module.exports = { + /** + * Standard value class for driving animations. Typically initialized with + * `new Animated.Value(0);` + * + * See http://facebook.github.io/react-native/docs/animated.html#value + */ + Value: AnimatedValue, + /** + * 2D value class for driving 2D animations, such as pan gestures. + * + * See https://facebook.github.io/react-native/releases/next/docs/animatedvaluexy.html + */ + ValueXY: AnimatedValueXY, + /** + * Exported to use the Interpolation type in flow. + * + * See http://facebook.github.io/react-native/docs/animated.html#interpolation + */ + Interpolation: AnimatedInterpolation, + /** + * Exported for ease of type checking. All animated values derive from this + * class. + * + * See http://facebook.github.io/react-native/docs/animated.html#node + */ + Node: AnimatedNode, + + /** + * Animates a value from an initial velocity to zero based on a decay + * coefficient. + * + * See http://facebook.github.io/react-native/docs/animated.html#decay + */ + decay, + /** + * Animates a value along a timed easing curve. The Easing module has tons of + * predefined curves, or you can use your own function. + * + * See http://facebook.github.io/react-native/docs/animated.html#timing + */ + timing, + /** + * Animates a value according to an analytical spring model based on + * damped harmonic oscillation. + * + * See http://facebook.github.io/react-native/docs/animated.html#spring + */ + spring, + + /** + * Creates a new Animated value composed from two Animated values added + * together. + * + * See http://facebook.github.io/react-native/docs/animated.html#add + */ + add, + + /** + * Creates a new Animated value composed by dividing the first Animated value + * by the second Animated value. + * + * See http://facebook.github.io/react-native/docs/animated.html#divide + */ + divide, + + /** + * Creates a new Animated value composed from two Animated values multiplied + * together. + * + * See http://facebook.github.io/react-native/docs/animated.html#multiply + */ + multiply, + + /** + * Creates a new Animated value that is the (non-negative) modulo of the + * provided Animated value. + * + * See http://facebook.github.io/react-native/docs/animated.html#modulo + */ + modulo, + + /** + * Create a new Animated value that is limited between 2 values. It uses the + * difference between the last value so even if the value is far from the + * bounds it will start changing when the value starts getting closer again. + * + * See http://facebook.github.io/react-native/docs/animated.html#diffclamp + */ + diffClamp, + + /** + * Starts an animation after the given delay. + * + * See http://facebook.github.io/react-native/docs/animated.html#delay + */ + delay, + /** + * Starts an array of animations in order, waiting for each to complete + * before starting the next. If the current running animation is stopped, no + * following animations will be started. + * + * See http://facebook.github.io/react-native/docs/animated.html#sequence + */ + sequence, + /** + * Starts an array of animations all at the same time. By default, if one + * of the animations is stopped, they will all be stopped. You can override + * this with the `stopTogether` flag. + * + * See http://facebook.github.io/react-native/docs/animated.html#parallel + */ + parallel, + /** + * Array of animations may run in parallel (overlap), but are started in + * sequence with successive delays. Nice for doing trailing effects. + * + * See http://facebook.github.io/react-native/docs/animated.html#stagger + */ + stagger, + /** + * Loops a given animation continuously, so that each time it reaches the + * end, it resets and begins again from the start. + * + * See http://facebook.github.io/react-native/docs/animated.html#loop + */ + loop, + + /** + * Takes an array of mappings and extracts values from each arg accordingly, + * then calls `setValue` on the mapped outputs. + * + * See http://facebook.github.io/react-native/docs/animated.html#event + */ + event, + + /** + * Make any React component Animatable. Used to create `Animated.View`, etc. + * + * See http://facebook.github.io/react-native/docs/animated.html#createanimatedcomponent + */ + createAnimatedComponent, + + /** + * Imperative API to attach an animated value to an event on a view. Prefer + * using `Animated.event` with `useNativeDrive: true` if possible. + * + * See http://facebook.github.io/react-native/docs/animated.html#attachnativeevent + */ + attachNativeEvent, + + /** + * Advanced imperative API for snooping on animated events that are passed in + * through props. Use values directly where possible. + * + * See http://facebook.github.io/react-native/docs/animated.html#forkevent + */ + forkEvent, + unforkEvent, + + __PropsOnlyForTests: AnimatedProps, +}; diff --git a/src/vendor/Animated/Easing.js b/src/vendor/Animated/Easing.js new file mode 100644 index 00000000..988f8164 --- /dev/null +++ b/src/vendor/Animated/Easing.js @@ -0,0 +1,303 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule Easing + * @flow + */ +'use strict'; + +let ease; + +/** + * The `Easing` module implements common easing functions. This module is used + * by [Animate.timing()](docs/animate.html#timing) to convey physically + * believable motion in animations. + * + * You can find a visualization of some common easing functions at + * http://easings.net/ + * + * ### Predefined animations + * + * The `Easing` module provides several predefined animations through the + * following methods: + * + * - [`back`](docs/easing.html#back) provides a simple animation where the + * object goes slightly back before moving forward + * - [`bounce`](docs/easing.html#bounce) provides a bouncing animation + * - [`ease`](docs/easing.html#ease) provides a simple inertial animation + * - [`elastic`](docs/easing.html#elastic) provides a simple spring interaction + * + * ### Standard functions + * + * Three standard easing functions are provided: + * + * - [`linear`](docs/easing.html#linear) + * - [`quad`](docs/easing.html#quad) + * - [`cubic`](docs/easing.html#cubic) + * + * The [`poly`](docs/easing.html#poly) function can be used to implement + * quartic, quintic, and other higher power functions. + * + * ### Additional functions + * + * Additional mathematical functions are provided by the following methods: + * + * - [`bezier`](docs/easing.html#bezier) provides a cubic bezier curve + * - [`circle`](docs/easing.html#circle) provides a circular function + * - [`sin`](docs/easing.html#sin) provides a sinusoidal function + * - [`exp`](docs/easing.html#exp) provides an exponential function + * + * The following helpers are used to modify other easing functions. + * + * - [`in`](docs/easing.html#in) runs an easing function forwards + * - [`inOut`](docs/easing.html#inout) makes any easing function symmetrical + * - [`out`](docs/easing.html#out) runs an easing function backwards + */ +class Easing { + /** + * A stepping function, returns 1 for any positive value of `n`. + */ + /* $FlowFixMe(>=0.59.0 site=react_native_fb) This comment suppresses an error + * caught by Flow 0.59 which was not caught before. Most likely, this error + * is because an exported function parameter is missing an annotation. + * Without an annotation, these parameters are uncovered by Flow. */ + static step0(n) { + return n > 0 ? 1 : 0; + } + + /** + * A stepping function, returns 1 if `n` is greater than or equal to 1. + */ + /* $FlowFixMe(>=0.59.0 site=react_native_fb) This comment suppresses an error + * caught by Flow 0.59 which was not caught before. Most likely, this error + * is because an exported function parameter is missing an annotation. + * Without an annotation, these parameters are uncovered by Flow. */ + static step1(n) { + return n >= 1 ? 1 : 0; + } + + /** + * A linear function, `f(t) = t`. Position correlates to elapsed time one to + * one. + * + * http://cubic-bezier.com/#0,0,1,1 + */ + /* $FlowFixMe(>=0.59.0 site=react_native_fb) This comment suppresses an error + * caught by Flow 0.59 which was not caught before. Most likely, this error + * is because an exported function parameter is missing an annotation. + * Without an annotation, these parameters are uncovered by Flow. */ + static linear(t) { + return t; + } + + /** + * A simple inertial interaction, similar to an object slowly accelerating to + * speed. + * + * http://cubic-bezier.com/#.42,0,1,1 + */ + static ease(t: number): number { + if (!ease) { + ease = Easing.bezier(0.42, 0, 1, 1); + } + return ease(t); + } + + /** + * A quadratic function, `f(t) = t * t`. Position equals the square of elapsed + * time. + * + * http://easings.net/#easeInQuad + */ + /* $FlowFixMe(>=0.59.0 site=react_native_fb) This comment suppresses an error + * caught by Flow 0.59 which was not caught before. Most likely, this error + * is because an exported function parameter is missing an annotation. + * Without an annotation, these parameters are uncovered by Flow. */ + static quad(t) { + return t * t; + } + + /** + * A cubic function, `f(t) = t * t * t`. Position equals the cube of elapsed + * time. + * + * http://easings.net/#easeInCubic + */ + /* $FlowFixMe(>=0.59.0 site=react_native_fb) This comment suppresses an error + * caught by Flow 0.59 which was not caught before. Most likely, this error + * is because an exported function parameter is missing an annotation. + * Without an annotation, these parameters are uncovered by Flow. */ + static cubic(t) { + return t * t * t; + } + + /** + * A power function. Position is equal to the Nth power of elapsed time. + * + * n = 4: http://easings.net/#easeInQuart + * n = 5: http://easings.net/#easeInQuint + */ + /* $FlowFixMe(>=0.59.0 site=react_native_fb) This comment suppresses an error + * caught by Flow 0.59 which was not caught before. Most likely, this error + * is because an exported function parameter is missing an annotation. + * Without an annotation, these parameters are uncovered by Flow. */ + static poly(n) { + /* $FlowFixMe(>=0.59.0 site=react_native_fb) This comment suppresses an + * error caught by Flow 0.59 which was not caught before. Most likely, this + * error is because an exported function parameter is missing an + * annotation. Without an annotation, these parameters are uncovered by + * Flow. */ + return (t) => Math.pow(t, n); + } + + /** + * A sinusoidal function. + * + * http://easings.net/#easeInSine + */ + /* $FlowFixMe(>=0.59.0 site=react_native_fb) This comment suppresses an error + * caught by Flow 0.59 which was not caught before. Most likely, this error + * is because an exported function parameter is missing an annotation. + * Without an annotation, these parameters are uncovered by Flow. */ + static sin(t) { + return 1 - Math.cos(t * Math.PI / 2); + } + + /** + * A circular function. + * + * http://easings.net/#easeInCirc + */ + /* $FlowFixMe(>=0.59.0 site=react_native_fb) This comment suppresses an error + * caught by Flow 0.59 which was not caught before. Most likely, this error + * is because an exported function parameter is missing an annotation. + * Without an annotation, these parameters are uncovered by Flow. */ + static circle(t) { + return 1 - Math.sqrt(1 - t * t); + } + + /** + * An exponential function. + * + * http://easings.net/#easeInExpo + */ + /* $FlowFixMe(>=0.59.0 site=react_native_fb) This comment suppresses an error + * caught by Flow 0.59 which was not caught before. Most likely, this error + * is because an exported function parameter is missing an annotation. + * Without an annotation, these parameters are uncovered by Flow. */ + static exp(t) { + return Math.pow(2, 10 * (t - 1)); + } + + /** + * A simple elastic interaction, similar to a spring oscillating back and + * forth. + * + * Default bounciness is 1, which overshoots a little bit once. 0 bounciness + * doesn't overshoot at all, and bounciness of N > 1 will overshoot about N + * times. + * + * http://easings.net/#easeInElastic + */ + static elastic(bounciness: number = 1): (t: number) => number { + const p = bounciness * Math.PI; + return (t) => 1 - Math.pow(Math.cos(t * Math.PI / 2), 3) * Math.cos(t * p); + } + + /** + * Use with `Animated.parallel()` to create a simple effect where the object + * animates back slightly as the animation starts. + * + * Wolfram Plot: + * + * - http://tiny.cc/back_default (s = 1.70158, default) + */ + static back(s: number): (t: number) => number { + if (s === undefined) { + s = 1.70158; + } + return (t) => t * t * ((s + 1) * t - s); + } + + /** + * Provides a simple bouncing effect. + * + * http://easings.net/#easeInBounce + */ + static bounce(t: number): number { + if (t < 1 / 2.75) { + return 7.5625 * t * t; + } + + if (t < 2 / 2.75) { + t -= 1.5 / 2.75; + return 7.5625 * t * t + 0.75; + } + + if (t < 2.5 / 2.75) { + t -= 2.25 / 2.75; + return 7.5625 * t * t + 0.9375; + } + + t -= 2.625 / 2.75; + return 7.5625 * t * t + 0.984375; + } + + /** + * Provides a cubic bezier curve, equivalent to CSS Transitions' + * `transition-timing-function`. + * + * A useful tool to visualize cubic bezier curves can be found at + * http://cubic-bezier.com/ + */ + static bezier( + x1: number, + y1: number, + x2: number, + y2: number + ): (t: number) => number { + const _bezier = require('./bezier'); + return _bezier(x1, y1, x2, y2); + } + + /** + * Runs an easing function forwards. + */ + static in( + easing: (t: number) => number, + ): (t: number) => number { + return easing; + } + + /** + * Runs an easing function backwards. + */ + static out( + easing: (t: number) => number, + ): (t: number) => number { + return (t) => 1 - easing(1 - t); + } + + /** + * Makes any easing function symmetrical. The easing function will run + * forwards for half of the duration, then backwards for the rest of the + * duration. + */ + static inOut( + easing: (t: number) => number, + ): (t: number) => number { + return (t) => { + if (t < 0.5) { + return easing(t * 2) / 2; + } + return 1 - easing((1 - t) * 2) / 2; + }; + } +} + +module.exports = Easing; diff --git a/src/vendor/Animated/NativeAnimatedHelper.js b/src/vendor/Animated/NativeAnimatedHelper.js new file mode 100644 index 00000000..9ba6ab54 --- /dev/null +++ b/src/vendor/Animated/NativeAnimatedHelper.js @@ -0,0 +1,259 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule NativeAnimatedHelper + * @flow + * @format + */ +'use strict'; + +const invariant = require('fbjs/lib/invariant'); +const NativeModules = require('../../modules/NativeModules'); +const NativeEventEmitter = require('../../modules/NativeEventEmitter'); + +import type {AnimationConfig} from './animations/Animation'; +import type {EventConfig} from './AnimatedEvent'; + +const NativeAnimatedModule = NativeModules.NativeAnimatedModule; + +let __nativeAnimatedNodeTagCount = 1; /* used for animated nodes */ +let __nativeAnimationIdCount = 1; /* used for started animations */ + +type EndResult = {finished: boolean}; +type EndCallback = (result: EndResult) => void; +type EventMapping = { + nativeEventPath: Array, + animatedValueTag: ?number, +}; + +let nativeEventEmitter; + +/** + * Simple wrappers around NativeAnimatedModule to provide flow and autocmplete support for + * the native module methods + */ +const API = { + createAnimatedNode: function(tag: ?number, config: Object): void { + assertNativeAnimatedModule(); + NativeAnimatedModule.createAnimatedNode(tag, config); + }, + startListeningToAnimatedNodeValue: function(tag: ?number) { + assertNativeAnimatedModule(); + NativeAnimatedModule.startListeningToAnimatedNodeValue(tag); + }, + stopListeningToAnimatedNodeValue: function(tag: ?number) { + assertNativeAnimatedModule(); + NativeAnimatedModule.stopListeningToAnimatedNodeValue(tag); + }, + connectAnimatedNodes: function(parentTag: ?number, childTag: ?number): void { + assertNativeAnimatedModule(); + NativeAnimatedModule.connectAnimatedNodes(parentTag, childTag); + }, + disconnectAnimatedNodes: function( + parentTag: ?number, + childTag: ?number, + ): void { + assertNativeAnimatedModule(); + NativeAnimatedModule.disconnectAnimatedNodes(parentTag, childTag); + }, + startAnimatingNode: function( + animationId: ?number, + nodeTag: ?number, + config: Object, + endCallback: EndCallback, + ): void { + assertNativeAnimatedModule(); + NativeAnimatedModule.startAnimatingNode( + animationId, + nodeTag, + config, + endCallback, + ); + }, + stopAnimation: function(animationId: ?number) { + assertNativeAnimatedModule(); + NativeAnimatedModule.stopAnimation(animationId); + }, + setAnimatedNodeValue: function(nodeTag: ?number, value: ?number): void { + assertNativeAnimatedModule(); + NativeAnimatedModule.setAnimatedNodeValue(nodeTag, value); + }, + setAnimatedNodeOffset: function(nodeTag: ?number, offset: ?number): void { + assertNativeAnimatedModule(); + NativeAnimatedModule.setAnimatedNodeOffset(nodeTag, offset); + }, + flattenAnimatedNodeOffset: function(nodeTag: ?number): void { + assertNativeAnimatedModule(); + NativeAnimatedModule.flattenAnimatedNodeOffset(nodeTag); + }, + extractAnimatedNodeOffset: function(nodeTag: ?number): void { + assertNativeAnimatedModule(); + NativeAnimatedModule.extractAnimatedNodeOffset(nodeTag); + }, + connectAnimatedNodeToView: function( + nodeTag: ?number, + viewTag: ?number, + ): void { + assertNativeAnimatedModule(); + NativeAnimatedModule.connectAnimatedNodeToView(nodeTag, viewTag); + }, + disconnectAnimatedNodeFromView: function( + nodeTag: ?number, + viewTag: ?number, + ): void { + assertNativeAnimatedModule(); + NativeAnimatedModule.disconnectAnimatedNodeFromView(nodeTag, viewTag); + }, + dropAnimatedNode: function(tag: ?number): void { + assertNativeAnimatedModule(); + NativeAnimatedModule.dropAnimatedNode(tag); + }, + addAnimatedEventToView: function( + viewTag: ?number, + eventName: string, + eventMapping: EventMapping, + ) { + assertNativeAnimatedModule(); + NativeAnimatedModule.addAnimatedEventToView( + viewTag, + eventName, + eventMapping, + ); + }, + removeAnimatedEventFromView( + viewTag: ?number, + eventName: string, + animatedNodeTag: ?number, + ) { + assertNativeAnimatedModule(); + NativeAnimatedModule.removeAnimatedEventFromView( + viewTag, + eventName, + animatedNodeTag, + ); + }, +}; + +/** + * Styles allowed by the native animated implementation. + * + * In general native animated implementation should support any numeric property that doesn't need + * to be updated through the shadow view hierarchy (all non-layout properties). + */ +const STYLES_WHITELIST = { + opacity: true, + transform: true, + /* ios styles */ + shadowOpacity: true, + shadowRadius: true, + /* legacy android transform properties */ + scaleX: true, + scaleY: true, + translateX: true, + translateY: true, +}; + +const TRANSFORM_WHITELIST = { + translateX: true, + translateY: true, + scale: true, + scaleX: true, + scaleY: true, + rotate: true, + rotateX: true, + rotateY: true, + perspective: true, +}; + +function validateTransform(configs: Array): void { + configs.forEach(config => { + if (!TRANSFORM_WHITELIST.hasOwnProperty(config.property)) { + throw new Error( + `Property '${config.property}' is not supported by native animated module`, + ); + } + }); +} + +function validateStyles(styles: Object): void { + for (var key in styles) { + if (!STYLES_WHITELIST.hasOwnProperty(key)) { + throw new Error( + `Style property '${key}' is not supported by native animated module`, + ); + } + } +} + +function validateInterpolation(config: Object): void { + var SUPPORTED_INTERPOLATION_PARAMS = { + inputRange: true, + outputRange: true, + extrapolate: true, + extrapolateRight: true, + extrapolateLeft: true, + }; + for (var key in config) { + if (!SUPPORTED_INTERPOLATION_PARAMS.hasOwnProperty(key)) { + throw new Error( + `Interpolation property '${key}' is not supported by native animated module`, + ); + } + } +} + +function generateNewNodeTag(): number { + return __nativeAnimatedNodeTagCount++; +} + +function generateNewAnimationId(): number { + return __nativeAnimationIdCount++; +} + +function assertNativeAnimatedModule(): void { + invariant(NativeAnimatedModule, 'Native animated module is not available'); +} + +let _warnedMissingNativeAnimated = false; + +function shouldUseNativeDriver(config: AnimationConfig | EventConfig): boolean { + if (config.useNativeDriver && !NativeAnimatedModule) { + if (!_warnedMissingNativeAnimated) { + console.warn( + 'Animated: `useNativeDriver` is not supported because the native ' + + 'animated module is missing. Falling back to JS-based animation. To ' + + 'resolve this, add `RCTAnimation` module to this app, or remove ' + + '`useNativeDriver`. ' + + 'More info: https://github.com/facebook/react-native/issues/11094#issuecomment-263240420', + ); + _warnedMissingNativeAnimated = true; + } + return false; + } + + return config.useNativeDriver || false; +} + +const NativeAnimatedHelper = { + API, + validateStyles, + validateTransform, + validateInterpolation, + generateNewNodeTag, + generateNewAnimationId, + assertNativeAnimatedModule, + shouldUseNativeDriver, + get nativeEventEmitter() { + if (!nativeEventEmitter) { + nativeEventEmitter = new NativeEventEmitter(NativeAnimatedModule); + } + return nativeEventEmitter; + }, +}; + +module.exports = NativeAnimatedHelper; diff --git a/src/vendor/Animated/SHA b/src/vendor/Animated/SHA new file mode 100644 index 00000000..c21c3495 --- /dev/null +++ b/src/vendor/Animated/SHA @@ -0,0 +1 @@ +facebook/react-native@71006f74cdafdae7212c8a10603fb972c6ee338c diff --git a/src/vendor/Animated/SpringConfig.js b/src/vendor/Animated/SpringConfig.js new file mode 100644 index 00000000..e74d167e --- /dev/null +++ b/src/vendor/Animated/SpringConfig.js @@ -0,0 +1,102 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule SpringConfig + * @flow + */ + +'use strict'; + +type SpringConfigType = { + stiffness: number, + damping: number, +}; + +function stiffnessFromOrigamiValue(oValue) { + return (oValue - 30) * 3.62 + 194; +} + +function dampingFromOrigamiValue(oValue) { + return (oValue - 8) * 3 + 25; +} + +function fromOrigamiTensionAndFriction( + tension: number, + friction: number, +): SpringConfigType { + return { + stiffness: stiffnessFromOrigamiValue(tension), + damping: dampingFromOrigamiValue(friction), + }; +} + +function fromBouncinessAndSpeed( + bounciness: number, + speed: number, +): SpringConfigType { + function normalize(value, startValue, endValue) { + return (value - startValue) / (endValue - startValue); + } + + function projectNormal(n, start, end) { + return start + (n * (end - start)); + } + + function linearInterpolation(t, start, end) { + return t * end + (1 - t) * start; + } + + function quadraticOutInterpolation(t, start, end) { + return linearInterpolation(2 * t - t * t, start, end); + } + + function b3Friction1(x) { + return (0.0007 * Math.pow(x, 3)) - + (0.031 * Math.pow(x, 2)) + 0.64 * x + 1.28; + } + + function b3Friction2(x) { + return (0.000044 * Math.pow(x, 3)) - + (0.006 * Math.pow(x, 2)) + 0.36 * x + 2; + } + + function b3Friction3(x) { + return (0.00000045 * Math.pow(x, 3)) - + (0.000332 * Math.pow(x, 2)) + 0.1078 * x + 5.84; + } + + function b3Nobounce(tension) { + if (tension <= 18) { + return b3Friction1(tension); + } else if (tension > 18 && tension <= 44) { + return b3Friction2(tension); + } else { + return b3Friction3(tension); + } + } + + var b = normalize(bounciness / 1.7, 0, 20); + b = projectNormal(b, 0, 0.8); + var s = normalize(speed / 1.7, 0, 20); + var bouncyTension = projectNormal(s, 0.5, 200); + var bouncyFriction = quadraticOutInterpolation( + b, + b3Nobounce(bouncyTension), + 0.01 + ); + + return { + stiffness: stiffnessFromOrigamiValue(bouncyTension), + damping: dampingFromOrigamiValue(bouncyFriction), + }; +} + +module.exports = { + fromOrigamiTensionAndFriction, + fromBouncinessAndSpeed, +}; diff --git a/src/vendor/Animated/animations/Animation.js b/src/vendor/Animated/animations/Animation.js new file mode 100644 index 00000000..2ee6a6f9 --- /dev/null +++ b/src/vendor/Animated/animations/Animation.js @@ -0,0 +1,73 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule Animation + * @flow + * @format + */ +'use strict'; + +const NativeAnimatedHelper = require('../NativeAnimatedHelper'); + +import type AnimatedValue from '../nodes/AnimatedValue'; + +export type EndResult = {finished: boolean}; +export type EndCallback = (result: EndResult) => void; + +export type AnimationConfig = { + isInteraction?: boolean, + useNativeDriver?: boolean, + onComplete?: ?EndCallback, + iterations?: number, +}; + +// Important note: start() and stop() will only be called at most once. +// Once an animation has been stopped or finished its course, it will +// not be reused. +class Animation { + __active: boolean; + __isInteraction: boolean; + __nativeId: number; + __onEnd: ?EndCallback; + __iterations: number; + start( + fromValue: number, + onUpdate: (value: number) => void, + onEnd: ?EndCallback, + previousAnimation: ?Animation, + animatedValue: AnimatedValue, + ): void {} + stop(): void { + if (this.__nativeId) { + NativeAnimatedHelper.API.stopAnimation(this.__nativeId); + } + } + __getNativeAnimationConfig(): any { + // Subclasses that have corresponding animation implementation done in native + // should override this method + throw new Error('This animation type cannot be offloaded to native'); + } + // Helper function for subclasses to make sure onEnd is only called once. + __debouncedOnEnd(result: EndResult): void { + const onEnd = this.__onEnd; + this.__onEnd = null; + onEnd && onEnd(result); + } + __startNativeAnimation(animatedValue: AnimatedValue): void { + animatedValue.__makeNative(); + this.__nativeId = NativeAnimatedHelper.generateNewAnimationId(); + NativeAnimatedHelper.API.startAnimatingNode( + this.__nativeId, + animatedValue.__getNativeTag(), + this.__getNativeAnimationConfig(), + this.__debouncedOnEnd.bind(this), + ); + } +} + +module.exports = Animation; diff --git a/src/vendor/Animated/animations/DecayAnimation.js b/src/vendor/Animated/animations/DecayAnimation.js new file mode 100644 index 00000000..652bd9c9 --- /dev/null +++ b/src/vendor/Animated/animations/DecayAnimation.js @@ -0,0 +1,112 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule DecayAnimation + * @flow + * @format + */ +'use strict'; + +const Animation = require('./Animation'); + +const {shouldUseNativeDriver} = require('../NativeAnimatedHelper'); + +import type {AnimationConfig, EndCallback} from './Animation'; +import type AnimatedValue from '../nodes/AnimatedValue'; + +export type DecayAnimationConfig = AnimationConfig & { + velocity: number | {x: number, y: number}, + deceleration?: number, +}; + +export type DecayAnimationConfigSingle = AnimationConfig & { + velocity: number, + deceleration?: number, +}; + +class DecayAnimation extends Animation { + _startTime: number; + _lastValue: number; + _fromValue: number; + _deceleration: number; + _velocity: number; + _onUpdate: (value: number) => void; + _animationFrame: any; + _useNativeDriver: boolean; + + constructor(config: DecayAnimationConfigSingle) { + super(); + this._deceleration = + config.deceleration !== undefined ? config.deceleration : 0.998; + this._velocity = config.velocity; + this._useNativeDriver = shouldUseNativeDriver(config); + this.__isInteraction = + config.isInteraction !== undefined ? config.isInteraction : true; + this.__iterations = config.iterations !== undefined ? config.iterations : 1; + } + + __getNativeAnimationConfig() { + return { + type: 'decay', + deceleration: this._deceleration, + velocity: this._velocity, + iterations: this.__iterations, + }; + } + + start( + fromValue: number, + onUpdate: (value: number) => void, + onEnd: ?EndCallback, + previousAnimation: ?Animation, + animatedValue: AnimatedValue, + ): void { + this.__active = true; + this._lastValue = fromValue; + this._fromValue = fromValue; + this._onUpdate = onUpdate; + this.__onEnd = onEnd; + this._startTime = Date.now(); + if (this._useNativeDriver) { + this.__startNativeAnimation(animatedValue); + } else { + this._animationFrame = requestAnimationFrame(this.onUpdate.bind(this)); + } + } + + onUpdate(): void { + const now = Date.now(); + + const value = + this._fromValue + + this._velocity / + (1 - this._deceleration) * + (1 - Math.exp(-(1 - this._deceleration) * (now - this._startTime))); + + this._onUpdate(value); + + if (Math.abs(this._lastValue - value) < 0.1) { + this.__debouncedOnEnd({finished: true}); + return; + } + + this._lastValue = value; + if (this.__active) { + this._animationFrame = requestAnimationFrame(this.onUpdate.bind(this)); + } + } + + stop(): void { + super.stop(); + this.__active = false; + global.cancelAnimationFrame(this._animationFrame); + this.__debouncedOnEnd({finished: false}); + } +} + +module.exports = DecayAnimation; diff --git a/src/vendor/Animated/animations/SpringAnimation.js b/src/vendor/Animated/animations/SpringAnimation.js new file mode 100644 index 00000000..67dee081 --- /dev/null +++ b/src/vendor/Animated/animations/SpringAnimation.js @@ -0,0 +1,342 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule SpringAnimation + * @flow + * @format + */ +'use strict'; + +const AnimatedValue = require('../nodes/AnimatedValue'); +const AnimatedValueXY = require('../nodes/AnimatedValueXY'); +const Animation = require('./Animation'); +const SpringConfig = require('../SpringConfig'); + +const invariant = require('fbjs/lib/invariant'); +const {shouldUseNativeDriver} = require('../NativeAnimatedHelper'); + +import type {AnimationConfig, EndCallback} from './Animation'; + +export type SpringAnimationConfig = AnimationConfig & { + toValue: number | AnimatedValue | {x: number, y: number} | AnimatedValueXY, + overshootClamping?: boolean, + restDisplacementThreshold?: number, + restSpeedThreshold?: number, + velocity?: number | {x: number, y: number}, + bounciness?: number, + speed?: number, + tension?: number, + friction?: number, + stiffness?: number, + damping?: number, + mass?: number, + delay?: number, +}; + +export type SpringAnimationConfigSingle = AnimationConfig & { + toValue: number | AnimatedValue, + overshootClamping?: boolean, + restDisplacementThreshold?: number, + restSpeedThreshold?: number, + velocity?: number, + bounciness?: number, + speed?: number, + tension?: number, + friction?: number, + stiffness?: number, + damping?: number, + mass?: number, + delay?: number, +}; + +function withDefault(value: ?T, defaultValue: T): T { + if (value === undefined || value === null) { + return defaultValue; + } + return value; +} + +class SpringAnimation extends Animation { + _overshootClamping: boolean; + _restDisplacementThreshold: number; + _restSpeedThreshold: number; + _lastVelocity: number; + _startPosition: number; + _lastPosition: number; + _fromValue: number; + _toValue: any; + _stiffness: number; + _damping: number; + _mass: number; + _initialVelocity: number; + _delay: number; + _timeout: any; + _startTime: number; + _lastTime: number; + _frameTime: number; + _onUpdate: (value: number) => void; + _animationFrame: any; + _useNativeDriver: boolean; + + constructor(config: SpringAnimationConfigSingle) { + super(); + + this._overshootClamping = withDefault(config.overshootClamping, false); + this._restDisplacementThreshold = withDefault( + config.restDisplacementThreshold, + 0.001, + ); + this._restSpeedThreshold = withDefault(config.restSpeedThreshold, 0.001); + this._initialVelocity = withDefault(config.velocity, 0); + this._lastVelocity = withDefault(config.velocity, 0); + this._toValue = config.toValue; + this._delay = withDefault(config.delay, 0); + this._useNativeDriver = shouldUseNativeDriver(config); + this.__isInteraction = + config.isInteraction !== undefined ? config.isInteraction : true; + this.__iterations = config.iterations !== undefined ? config.iterations : 1; + + if ( + config.stiffness !== undefined || + config.damping !== undefined || + config.mass !== undefined + ) { + invariant( + config.bounciness === undefined && + config.speed === undefined && + config.tension === undefined && + config.friction === undefined, + 'You can define one of bounciness/speed, tension/friction, or stiffness/damping/mass, but not more than one', + ); + this._stiffness = withDefault(config.stiffness, 100); + this._damping = withDefault(config.damping, 10); + this._mass = withDefault(config.mass, 1); + } else if (config.bounciness !== undefined || config.speed !== undefined) { + // Convert the origami bounciness/speed values to stiffness/damping + // We assume mass is 1. + invariant( + config.tension === undefined && + config.friction === undefined && + config.stiffness === undefined && + config.damping === undefined && + config.mass === undefined, + 'You can define one of bounciness/speed, tension/friction, or stiffness/damping/mass, but not more than one', + ); + const springConfig = SpringConfig.fromBouncinessAndSpeed( + withDefault(config.bounciness, 8), + withDefault(config.speed, 12), + ); + this._stiffness = springConfig.stiffness; + this._damping = springConfig.damping; + this._mass = 1; + } else { + // Convert the origami tension/friction values to stiffness/damping + // We assume mass is 1. + const springConfig = SpringConfig.fromOrigamiTensionAndFriction( + withDefault(config.tension, 40), + withDefault(config.friction, 7), + ); + this._stiffness = springConfig.stiffness; + this._damping = springConfig.damping; + this._mass = 1; + } + + invariant(this._stiffness > 0, 'Stiffness value must be greater than 0'); + invariant(this._damping > 0, 'Damping value must be greater than 0'); + invariant(this._mass > 0, 'Mass value must be greater than 0'); + } + + __getNativeAnimationConfig() { + return { + type: 'spring', + overshootClamping: this._overshootClamping, + restDisplacementThreshold: this._restDisplacementThreshold, + restSpeedThreshold: this._restSpeedThreshold, + stiffness: this._stiffness, + damping: this._damping, + mass: this._mass, + initialVelocity: withDefault(this._initialVelocity, this._lastVelocity), + toValue: this._toValue, + iterations: this.__iterations, + }; + } + + start( + fromValue: number, + onUpdate: (value: number) => void, + onEnd: ?EndCallback, + previousAnimation: ?Animation, + animatedValue: AnimatedValue, + ): void { + this.__active = true; + this._startPosition = fromValue; + this._lastPosition = this._startPosition; + + this._onUpdate = onUpdate; + this.__onEnd = onEnd; + this._lastTime = Date.now(); + this._frameTime = 0.0; + + if (previousAnimation instanceof SpringAnimation) { + const internalState = previousAnimation.getInternalState(); + this._lastPosition = internalState.lastPosition; + this._lastVelocity = internalState.lastVelocity; + // Set the initial velocity to the last velocity + this._initialVelocity = this._lastVelocity; + this._lastTime = internalState.lastTime; + } + + const start = () => { + if (this._useNativeDriver) { + this.__startNativeAnimation(animatedValue); + } else { + this.onUpdate(); + } + }; + + // If this._delay is more than 0, we start after the timeout. + if (this._delay) { + this._timeout = setTimeout(start, this._delay); + } else { + start(); + } + } + + getInternalState(): Object { + return { + lastPosition: this._lastPosition, + lastVelocity: this._lastVelocity, + lastTime: this._lastTime, + }; + } + + /** + * This spring model is based off of a damped harmonic oscillator + * (https://en.wikipedia.org/wiki/Harmonic_oscillator#Damped_harmonic_oscillator). + * + * We use the closed form of the second order differential equation: + * + * x'' + (2ζ⍵_0)x' + ⍵^2x = 0 + * + * where + * ⍵_0 = √(k / m) (undamped angular frequency of the oscillator), + * ζ = c / 2√mk (damping ratio), + * c = damping constant + * k = stiffness + * m = mass + * + * The derivation of the closed form is described in detail here: + * http://planetmath.org/sites/default/files/texpdf/39745.pdf + * + * This algorithm happens to match the algorithm used by CASpringAnimation, + * a QuartzCore (iOS) API that creates spring animations. + */ + onUpdate(): void { + // If for some reason we lost a lot of frames (e.g. process large payload or + // stopped in the debugger), we only advance by 4 frames worth of + // computation and will continue on the next frame. It's better to have it + // running at faster speed than jumping to the end. + const MAX_STEPS = 64; + let now = Date.now(); + if (now > this._lastTime + MAX_STEPS) { + now = this._lastTime + MAX_STEPS; + } + + const deltaTime = (now - this._lastTime) / 1000; + this._frameTime += deltaTime; + + const c: number = this._damping; + const m: number = this._mass; + const k: number = this._stiffness; + const v0: number = -this._initialVelocity; + + const zeta = c / (2 * Math.sqrt(k * m)); // damping ratio + const omega0 = Math.sqrt(k / m); // undamped angular frequency of the oscillator (rad/ms) + const omega1 = omega0 * Math.sqrt(1.0 - zeta * zeta); // exponential decay + const x0 = this._toValue - this._startPosition; // calculate the oscillation from x0 = 1 to x = 0 + + let position = 0.0; + let velocity = 0.0; + const t = this._frameTime; + if (zeta < 1) { + // Under damped + const envelope = Math.exp(-zeta * omega0 * t); + position = + this._toValue - + envelope * + ((v0 + zeta * omega0 * x0) / omega1 * Math.sin(omega1 * t) + + x0 * Math.cos(omega1 * t)); + // This looks crazy -- it's actually just the derivative of the + // oscillation function + velocity = + zeta * + omega0 * + envelope * + (Math.sin(omega1 * t) * (v0 + zeta * omega0 * x0) / omega1 + + x0 * Math.cos(omega1 * t)) - + envelope * + (Math.cos(omega1 * t) * (v0 + zeta * omega0 * x0) - + omega1 * x0 * Math.sin(omega1 * t)); + } else { + // Critically damped + const envelope = Math.exp(-omega0 * t); + position = this._toValue - envelope * (x0 + (v0 + omega0 * x0) * t); + velocity = + envelope * (v0 * (t * omega0 - 1) + t * x0 * (omega0 * omega0)); + } + + this._lastTime = now; + this._lastPosition = position; + this._lastVelocity = velocity; + + this._onUpdate(position); + if (!this.__active) { + // a listener might have stopped us in _onUpdate + return; + } + + // Conditions for stopping the spring animation + let isOvershooting = false; + if (this._overshootClamping && this._stiffness !== 0) { + if (this._startPosition < this._toValue) { + isOvershooting = position > this._toValue; + } else { + isOvershooting = position < this._toValue; + } + } + const isVelocity = Math.abs(velocity) <= this._restSpeedThreshold; + let isDisplacement = true; + if (this._stiffness !== 0) { + isDisplacement = + Math.abs(this._toValue - position) <= this._restDisplacementThreshold; + } + + if (isOvershooting || (isVelocity && isDisplacement)) { + if (this._stiffness !== 0) { + // Ensure that we end up with a round value + this._lastPosition = this._toValue; + this._lastVelocity = 0; + this._onUpdate(this._toValue); + } + + this.__debouncedOnEnd({finished: true}); + return; + } + this._animationFrame = requestAnimationFrame(this.onUpdate.bind(this)); + } + + stop(): void { + super.stop(); + this.__active = false; + clearTimeout(this._timeout); + global.cancelAnimationFrame(this._animationFrame); + this.__debouncedOnEnd({finished: false}); + } +} + +module.exports = SpringAnimation; diff --git a/src/vendor/Animated/animations/TimingAnimation.js b/src/vendor/Animated/animations/TimingAnimation.js new file mode 100644 index 00000000..14d3e3dc --- /dev/null +++ b/src/vendor/Animated/animations/TimingAnimation.js @@ -0,0 +1,155 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule TimingAnimation + * @flow + * @format + */ +'use strict'; + +const AnimatedValue = require('../nodes/AnimatedValue'); +const AnimatedValueXY = require('../nodes/AnimatedValueXY'); +const Animation = require('./Animation'); + +const {shouldUseNativeDriver} = require('../NativeAnimatedHelper'); + +import type {AnimationConfig, EndCallback} from './Animation'; + +export type TimingAnimationConfig = AnimationConfig & { + toValue: number | AnimatedValue | {x: number, y: number} | AnimatedValueXY, + easing?: (value: number) => number, + duration?: number, + delay?: number, +}; + +export type TimingAnimationConfigSingle = AnimationConfig & { + toValue: number | AnimatedValue, + easing?: (value: number) => number, + duration?: number, + delay?: number, +}; + +let _easeInOut; +function easeInOut() { + if (!_easeInOut) { + const Easing = require('../Easing'); + _easeInOut = Easing.inOut(Easing.ease); + } + return _easeInOut; +} + +class TimingAnimation extends Animation { + _startTime: number; + _fromValue: number; + _toValue: any; + _duration: number; + _delay: number; + _easing: (value: number) => number; + _onUpdate: (value: number) => void; + _animationFrame: any; + _timeout: any; + _useNativeDriver: boolean; + + constructor(config: TimingAnimationConfigSingle) { + super(); + this._toValue = config.toValue; + this._easing = config.easing !== undefined ? config.easing : easeInOut(); + this._duration = config.duration !== undefined ? config.duration : 500; + this._delay = config.delay !== undefined ? config.delay : 0; + this.__iterations = config.iterations !== undefined ? config.iterations : 1; + this.__isInteraction = + config.isInteraction !== undefined ? config.isInteraction : true; + this._useNativeDriver = shouldUseNativeDriver(config); + } + + __getNativeAnimationConfig(): any { + const frameDuration = 1000.0 / 60.0; + const frames = []; + for (let dt = 0.0; dt < this._duration; dt += frameDuration) { + frames.push(this._easing(dt / this._duration)); + } + frames.push(this._easing(1)); + return { + type: 'frames', + frames, + toValue: this._toValue, + iterations: this.__iterations, + }; + } + + start( + fromValue: number, + onUpdate: (value: number) => void, + onEnd: ?EndCallback, + previousAnimation: ?Animation, + animatedValue: AnimatedValue, + ): void { + this.__active = true; + this._fromValue = fromValue; + this._onUpdate = onUpdate; + this.__onEnd = onEnd; + + const start = () => { + // Animations that sometimes have 0 duration and sometimes do not + // still need to use the native driver when duration is 0 so as to + // not cause intermixed JS and native animations. + if (this._duration === 0 && !this._useNativeDriver) { + this._onUpdate(this._toValue); + this.__debouncedOnEnd({finished: true}); + } else { + this._startTime = Date.now(); + if (this._useNativeDriver) { + this.__startNativeAnimation(animatedValue); + } else { + this._animationFrame = requestAnimationFrame( + this.onUpdate.bind(this), + ); + } + } + }; + if (this._delay) { + this._timeout = setTimeout(start, this._delay); + } else { + start(); + } + } + + onUpdate(): void { + const now = Date.now(); + if (now >= this._startTime + this._duration) { + if (this._duration === 0) { + this._onUpdate(this._toValue); + } else { + this._onUpdate( + this._fromValue + this._easing(1) * (this._toValue - this._fromValue), + ); + } + this.__debouncedOnEnd({finished: true}); + return; + } + + this._onUpdate( + this._fromValue + + this._easing((now - this._startTime) / this._duration) * + (this._toValue - this._fromValue), + ); + if (this.__active) { + this._animationFrame = requestAnimationFrame(this.onUpdate.bind(this)); + } + } + + stop(): void { + super.stop(); + this.__active = false; + clearTimeout(this._timeout); + global.cancelAnimationFrame(this._animationFrame); + this.__debouncedOnEnd({finished: false}); + } +} + +module.exports = TimingAnimation; diff --git a/src/vendor/Animated/bezier.js b/src/vendor/Animated/bezier.js new file mode 100644 index 00000000..2bd59493 --- /dev/null +++ b/src/vendor/Animated/bezier.js @@ -0,0 +1,108 @@ +/** + * BezierEasing - use bezier curve for transition easing function + * https://github.com/gre/bezier-easing + * + * @copyright 2014-2015 Gaëtan Renaudeau. MIT License. + * @providesModule bezier + * @noflow + */ +'use strict'; + + // These values are established by empiricism with tests (tradeoff: performance VS precision) + var NEWTON_ITERATIONS = 4; + var NEWTON_MIN_SLOPE = 0.001; + var SUBDIVISION_PRECISION = 0.0000001; + var SUBDIVISION_MAX_ITERATIONS = 10; + + var kSplineTableSize = 11; + var kSampleStepSize = 1.0 / (kSplineTableSize - 1.0); + + var float32ArraySupported = typeof Float32Array === 'function'; + + function A (aA1, aA2) { return 1.0 - 3.0 * aA2 + 3.0 * aA1; } + function B (aA1, aA2) { return 3.0 * aA2 - 6.0 * aA1; } + function C (aA1) { return 3.0 * aA1; } + + // Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2. + function calcBezier (aT, aA1, aA2) { return ((A(aA1, aA2) * aT + B(aA1, aA2)) * aT + C(aA1)) * aT; } + + // Returns dx/dt given t, x1, and x2, or dy/dt given t, y1, and y2. + function getSlope (aT, aA1, aA2) { return 3.0 * A(aA1, aA2) * aT * aT + 2.0 * B(aA1, aA2) * aT + C(aA1); } + + function binarySubdivide (aX, aA, aB, mX1, mX2) { + var currentX, currentT, i = 0; + do { + currentT = aA + (aB - aA) / 2.0; + currentX = calcBezier(currentT, mX1, mX2) - aX; + if (currentX > 0.0) { + aB = currentT; + } else { + aA = currentT; + } + } while (Math.abs(currentX) > SUBDIVISION_PRECISION && ++i < SUBDIVISION_MAX_ITERATIONS); + return currentT; + } + + function newtonRaphsonIterate (aX, aGuessT, mX1, mX2) { + for (var i = 0; i < NEWTON_ITERATIONS; ++i) { + var currentSlope = getSlope(aGuessT, mX1, mX2); + if (currentSlope === 0.0) { + return aGuessT; + } + var currentX = calcBezier(aGuessT, mX1, mX2) - aX; + aGuessT -= currentX / currentSlope; + } + return aGuessT; + } + + module.exports = function bezier (mX1, mY1, mX2, mY2) { + if (!(0 <= mX1 && mX1 <= 1 && 0 <= mX2 && mX2 <= 1)) { // eslint-disable-line yoda + throw new Error('bezier x values must be in [0, 1] range'); + } + + // Precompute samples table + var sampleValues = float32ArraySupported ? new Float32Array(kSplineTableSize) : new Array(kSplineTableSize); + if (mX1 !== mY1 || mX2 !== mY2) { + for (var i = 0; i < kSplineTableSize; ++i) { + sampleValues[i] = calcBezier(i * kSampleStepSize, mX1, mX2); + } + } + + function getTForX (aX) { + var intervalStart = 0.0; + var currentSample = 1; + var lastSample = kSplineTableSize - 1; + + for (; currentSample !== lastSample && sampleValues[currentSample] <= aX; ++currentSample) { + intervalStart += kSampleStepSize; + } + --currentSample; + + // Interpolate to provide an initial guess for t + var dist = (aX - sampleValues[currentSample]) / (sampleValues[currentSample + 1] - sampleValues[currentSample]); + var guessForT = intervalStart + dist * kSampleStepSize; + + var initialSlope = getSlope(guessForT, mX1, mX2); + if (initialSlope >= NEWTON_MIN_SLOPE) { + return newtonRaphsonIterate(aX, guessForT, mX1, mX2); + } else if (initialSlope === 0.0) { + return guessForT; + } else { + return binarySubdivide(aX, intervalStart, intervalStart + kSampleStepSize, mX1, mX2); + } + } + + return function BezierEasing (x) { + if (mX1 === mY1 && mX2 === mY2) { + return x; // linear + } + // Because JavaScript number are imprecise, we should guarantee the extremes are right. + if (x === 0) { + return 0; + } + if (x === 1) { + return 1; + } + return calcBezier(getTForX(x), mY1, mY2); + }; + }; diff --git a/src/vendor/Animated/createAnimatedComponent.js b/src/vendor/Animated/createAnimatedComponent.js new file mode 100644 index 00000000..6a424f2a --- /dev/null +++ b/src/vendor/Animated/createAnimatedComponent.js @@ -0,0 +1,200 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule createAnimatedComponent + * @flow + * @format + */ +'use strict'; + +const {AnimatedEvent} = require('./AnimatedEvent'); +const AnimatedProps = require('./nodes/AnimatedProps'); +const React = require('react'); +const ViewStylePropTypes = require('../../components/View/ViewStylePropTypes').default; + +function createAnimatedComponent(Component: any): any { + class AnimatedComponent extends React.Component { + _component: any; + _invokeAnimatedPropsCallbackOnMount: boolean = false; + _prevComponent: any; + _propsAnimated: AnimatedProps; + _eventDetachers: Array = []; + _setComponentRef: Function; + + static __skipSetNativeProps_FOR_TESTS_ONLY = false; + + constructor(props: Object) { + super(props); + this._setComponentRef = this._setComponentRef.bind(this); + } + + componentWillUnmount() { + this._propsAnimated && this._propsAnimated.__detach(); + this._detachNativeEvents(); + } + + setNativeProps(props) { + this._component.setNativeProps(props); + } + + componentWillMount() { + this._attachProps(this.props); + } + + componentDidMount() { + if (this._invokeAnimatedPropsCallbackOnMount) { + this._invokeAnimatedPropsCallbackOnMount = false; + this._animatedPropsCallback(); + } + + this._propsAnimated.setNativeView(this._component); + this._attachNativeEvents(); + } + + _attachNativeEvents() { + // Make sure to get the scrollable node for components that implement + // `ScrollResponder.Mixin`. + const scrollableNode = this._component.getScrollableNode + ? this._component.getScrollableNode() + : this._component; + + for (const key in this.props) { + const prop = this.props[key]; + if (prop instanceof AnimatedEvent && prop.__isNative) { + prop.__attach(scrollableNode, key); + this._eventDetachers.push(() => prop.__detach(scrollableNode, key)); + } + } + } + + _detachNativeEvents() { + this._eventDetachers.forEach(remove => remove()); + this._eventDetachers = []; + } + + // The system is best designed when setNativeProps is implemented. It is + // able to avoid re-rendering and directly set the attributes that changed. + // However, setNativeProps can only be implemented on leaf native + // components. If you want to animate a composite component, you need to + // re-render it. In this case, we have a fallback that uses forceUpdate. + _animatedPropsCallback = () => { + if (this._component == null) { + // AnimatedProps is created in will-mount because it's used in render. + // But this callback may be invoked before mount in async mode, + // In which case we should defer the setNativeProps() call. + // React may throw away uncommitted work in async mode, + // So a deferred call won't always be invoked. + this._invokeAnimatedPropsCallbackOnMount = true; + } else if ( + AnimatedComponent.__skipSetNativeProps_FOR_TESTS_ONLY || + typeof this._component.setNativeProps !== 'function' + ) { + this.forceUpdate(); + } else if (!this._propsAnimated.__isNative) { + this._component.setNativeProps( + this._propsAnimated.__getAnimatedValue(), + ); + } else { + throw new Error( + 'Attempting to run JS driven animation on animated ' + + 'node that has been moved to "native" earlier by starting an ' + + 'animation with `useNativeDriver: true`', + ); + } + }; + + _attachProps(nextProps) { + const oldPropsAnimated = this._propsAnimated; + + this._propsAnimated = new AnimatedProps( + nextProps, + this._animatedPropsCallback, + ); + + // When you call detach, it removes the element from the parent list + // of children. If it goes to 0, then the parent also detaches itself + // and so on. + // An optimization is to attach the new elements and THEN detach the old + // ones instead of detaching and THEN attaching. + // This way the intermediate state isn't to go to 0 and trigger + // this expensive recursive detaching to then re-attach everything on + // the very next operation. + oldPropsAnimated && oldPropsAnimated.__detach(); + } + + componentWillReceiveProps(newProps) { + this._attachProps(newProps); + } + + componentDidUpdate(prevProps) { + if (this._component !== this._prevComponent) { + this._propsAnimated.setNativeView(this._component); + } + if (this._component !== this._prevComponent || prevProps !== this.props) { + this._detachNativeEvents(); + this._attachNativeEvents(); + } + } + + render() { + const props = this._propsAnimated.__getValue(); + return ( + + ); + } + + _setComponentRef(c) { + this._prevComponent = this._component; + this._component = c; + } + + // A third party library can use getNode() + // to get the node reference of the decorated component + getNode() { + return this._component; + } + } + + const propTypes = Component.propTypes; + + AnimatedComponent.propTypes = { + style: function(props, propName, componentName) { + if (!propTypes) { + return; + } + + for (const key in ViewStylePropTypes) { + if (!propTypes[key] && props[key] !== undefined) { + console.warn( + 'You are setting the style `{ ' + + key + + ': ... }` as a prop. You ' + + 'should nest it in a style object. ' + + 'E.g. `{ style: { ' + + key + + ': ... } }`', + ); + } + } + }, + }; + + return AnimatedComponent; +} + +module.exports = createAnimatedComponent; diff --git a/src/vendor/Animated/nodes/AnimatedAddition.js b/src/vendor/Animated/nodes/AnimatedAddition.js new file mode 100644 index 00000000..53d33883 --- /dev/null +++ b/src/vendor/Animated/nodes/AnimatedAddition.js @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule AnimatedAddition + * @flow + * @format + */ +'use strict'; + +const AnimatedInterpolation = require('./AnimatedInterpolation'); +const AnimatedNode = require('./AnimatedNode'); +const AnimatedValue = require('./AnimatedValue'); +const AnimatedWithChildren = require('./AnimatedWithChildren'); + +import type {InterpolationConfigType} from './AnimatedInterpolation'; + +class AnimatedAddition extends AnimatedWithChildren { + _a: AnimatedNode; + _b: AnimatedNode; + + constructor(a: AnimatedNode | number, b: AnimatedNode | number) { + super(); + this._a = typeof a === 'number' ? new AnimatedValue(a) : a; + this._b = typeof b === 'number' ? new AnimatedValue(b) : b; + } + + __makeNative() { + this._a.__makeNative(); + this._b.__makeNative(); + super.__makeNative(); + } + + __getValue(): number { + return this._a.__getValue() + this._b.__getValue(); + } + + interpolate(config: InterpolationConfigType): AnimatedInterpolation { + return new AnimatedInterpolation(this, config); + } + + __attach(): void { + this._a.__addChild(this); + this._b.__addChild(this); + } + + __detach(): void { + this._a.__removeChild(this); + this._b.__removeChild(this); + super.__detach(); + } + + __getNativeConfig(): any { + return { + type: 'addition', + input: [this._a.__getNativeTag(), this._b.__getNativeTag()], + }; + } +} + +module.exports = AnimatedAddition; diff --git a/src/vendor/Animated/nodes/AnimatedDiffClamp.js b/src/vendor/Animated/nodes/AnimatedDiffClamp.js new file mode 100644 index 00000000..eac26a10 --- /dev/null +++ b/src/vendor/Animated/nodes/AnimatedDiffClamp.js @@ -0,0 +1,73 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule AnimatedDiffClamp + * @flow + * @format + */ +'use strict'; + +const AnimatedInterpolation = require('./AnimatedInterpolation'); +const AnimatedNode = require('./AnimatedNode'); +const AnimatedWithChildren = require('./AnimatedWithChildren'); + +import type {InterpolationConfigType} from './AnimatedInterpolation'; + +class AnimatedDiffClamp extends AnimatedWithChildren { + _a: AnimatedNode; + _min: number; + _max: number; + _value: number; + _lastValue: number; + + constructor(a: AnimatedNode, min: number, max: number) { + super(); + + this._a = a; + this._min = min; + this._max = max; + this._value = this._lastValue = this._a.__getValue(); + } + + __makeNative() { + this._a.__makeNative(); + super.__makeNative(); + } + + interpolate(config: InterpolationConfigType): AnimatedInterpolation { + return new AnimatedInterpolation(this, config); + } + + __getValue(): number { + const value = this._a.__getValue(); + const diff = value - this._lastValue; + this._lastValue = value; + this._value = Math.min(Math.max(this._value + diff, this._min), this._max); + return this._value; + } + + __attach(): void { + this._a.__addChild(this); + } + + __detach(): void { + this._a.__removeChild(this); + super.__detach(); + } + + __getNativeConfig(): any { + return { + type: 'diffclamp', + input: this._a.__getNativeTag(), + min: this._min, + max: this._max, + }; + } +} + +module.exports = AnimatedDiffClamp; diff --git a/src/vendor/Animated/nodes/AnimatedDivision.js b/src/vendor/Animated/nodes/AnimatedDivision.js new file mode 100644 index 00000000..8d658f45 --- /dev/null +++ b/src/vendor/Animated/nodes/AnimatedDivision.js @@ -0,0 +1,70 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule AnimatedDivision + * @flow + * @format + */ +'use strict'; + +const AnimatedInterpolation = require('./AnimatedInterpolation'); +const AnimatedNode = require('./AnimatedNode'); +const AnimatedValue = require('./AnimatedValue'); +const AnimatedWithChildren = require('./AnimatedWithChildren'); + +import type {InterpolationConfigType} from './AnimatedInterpolation'; + +class AnimatedDivision extends AnimatedWithChildren { + _a: AnimatedNode; + _b: AnimatedNode; + + constructor(a: AnimatedNode | number, b: AnimatedNode | number) { + super(); + this._a = typeof a === 'number' ? new AnimatedValue(a) : a; + this._b = typeof b === 'number' ? new AnimatedValue(b) : b; + } + + __makeNative() { + this._a.__makeNative(); + this._b.__makeNative(); + super.__makeNative(); + } + + __getValue(): number { + const a = this._a.__getValue(); + const b = this._b.__getValue(); + if (b === 0) { + console.error('Detected division by zero in AnimatedDivision'); + } + return a / b; + } + + interpolate(config: InterpolationConfigType): AnimatedInterpolation { + return new AnimatedInterpolation(this, config); + } + + __attach(): void { + this._a.__addChild(this); + this._b.__addChild(this); + } + + __detach(): void { + this._a.__removeChild(this); + this._b.__removeChild(this); + super.__detach(); + } + + __getNativeConfig(): any { + return { + type: 'division', + input: [this._a.__getNativeTag(), this._b.__getNativeTag()], + }; + } +} + +module.exports = AnimatedDivision; diff --git a/src/vendor/Animated/nodes/AnimatedInterpolation.js b/src/vendor/Animated/nodes/AnimatedInterpolation.js new file mode 100644 index 00000000..8d66c216 --- /dev/null +++ b/src/vendor/Animated/nodes/AnimatedInterpolation.js @@ -0,0 +1,388 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule AnimatedInterpolation + * @flow + * @format + */ +/* eslint no-bitwise: 0 */ +'use strict'; + +const AnimatedNode = require('./AnimatedNode'); +const AnimatedWithChildren = require('./AnimatedWithChildren'); +const NativeAnimatedHelper = require('../NativeAnimatedHelper'); + +const invariant = require('fbjs/lib/invariant'); +const normalizeColor = require('normalize-css-color'); + +type ExtrapolateType = 'extend' | 'identity' | 'clamp'; + +export type InterpolationConfigType = { + inputRange: Array, + /* $FlowFixMe(>=0.38.0 site=react_native_fb,react_native_oss) - Flow error + * detected during the deployment of v0.38.0. To see the error, remove this + * comment and run flow + */ + outputRange: Array | Array, + easing?: (input: number) => number, + extrapolate?: ExtrapolateType, + extrapolateLeft?: ExtrapolateType, + extrapolateRight?: ExtrapolateType, +}; + +const linear = t => t; + +/** + * Very handy helper to map input ranges to output ranges with an easing + * function and custom behavior outside of the ranges. + */ +function createInterpolation( + config: InterpolationConfigType, +): (input: number) => number | string { + if (config.outputRange && typeof config.outputRange[0] === 'string') { + return createInterpolationFromStringOutputRange(config); + } + + const outputRange: Array = (config.outputRange: any); + checkInfiniteRange('outputRange', outputRange); + + const inputRange = config.inputRange; + checkInfiniteRange('inputRange', inputRange); + checkValidInputRange(inputRange); + + invariant( + inputRange.length === outputRange.length, + 'inputRange (' + + inputRange.length + + ') and outputRange (' + + outputRange.length + + ') must have the same length', + ); + + const easing = config.easing || linear; + + let extrapolateLeft: ExtrapolateType = 'extend'; + if (config.extrapolateLeft !== undefined) { + extrapolateLeft = config.extrapolateLeft; + } else if (config.extrapolate !== undefined) { + extrapolateLeft = config.extrapolate; + } + + let extrapolateRight: ExtrapolateType = 'extend'; + if (config.extrapolateRight !== undefined) { + extrapolateRight = config.extrapolateRight; + } else if (config.extrapolate !== undefined) { + extrapolateRight = config.extrapolate; + } + + return input => { + invariant( + typeof input === 'number', + 'Cannot interpolation an input which is not a number', + ); + + const range = findRange(input, inputRange); + return interpolate( + input, + inputRange[range], + inputRange[range + 1], + outputRange[range], + outputRange[range + 1], + easing, + extrapolateLeft, + extrapolateRight, + ); + }; +} + +function interpolate( + input: number, + inputMin: number, + inputMax: number, + outputMin: number, + outputMax: number, + easing: (input: number) => number, + extrapolateLeft: ExtrapolateType, + extrapolateRight: ExtrapolateType, +) { + let result = input; + + // Extrapolate + if (result < inputMin) { + if (extrapolateLeft === 'identity') { + return result; + } else if (extrapolateLeft === 'clamp') { + result = inputMin; + } else if (extrapolateLeft === 'extend') { + // noop + } + } + + if (result > inputMax) { + if (extrapolateRight === 'identity') { + return result; + } else if (extrapolateRight === 'clamp') { + result = inputMax; + } else if (extrapolateRight === 'extend') { + // noop + } + } + + if (outputMin === outputMax) { + return outputMin; + } + + if (inputMin === inputMax) { + if (input <= inputMin) { + return outputMin; + } + return outputMax; + } + + // Input Range + if (inputMin === -Infinity) { + result = -result; + } else if (inputMax === Infinity) { + result = result - inputMin; + } else { + result = (result - inputMin) / (inputMax - inputMin); + } + + // Easing + result = easing(result); + + // Output Range + if (outputMin === -Infinity) { + result = -result; + } else if (outputMax === Infinity) { + result = result + outputMin; + } else { + result = result * (outputMax - outputMin) + outputMin; + } + + return result; +} + +function colorToRgba(input: string): string { + let int32Color = normalizeColor(input); + if (int32Color === null) { + return input; + } + + int32Color = int32Color || 0; + + const r = (int32Color & 0xff000000) >>> 24; + const g = (int32Color & 0x00ff0000) >>> 16; + const b = (int32Color & 0x0000ff00) >>> 8; + const a = (int32Color & 0x000000ff) / 255; + + return `rgba(${r}, ${g}, ${b}, ${a})`; +} + +const stringShapeRegex = /[0-9\.-]+/g; + +/** + * Supports string shapes by extracting numbers so new values can be computed, + * and recombines those values into new strings of the same shape. Supports + * things like: + * + * rgba(123, 42, 99, 0.36) // colors + * -45deg // values with units + */ +function createInterpolationFromStringOutputRange( + config: InterpolationConfigType, +): (input: number) => string { + let outputRange: Array = (config.outputRange: any); + invariant(outputRange.length >= 2, 'Bad output range'); + outputRange = outputRange.map(colorToRgba); + checkPattern(outputRange); + + // ['rgba(0, 100, 200, 0)', 'rgba(50, 150, 250, 0.5)'] + // -> + // [ + // [0, 50], + // [100, 150], + // [200, 250], + // [0, 0.5], + // ] + /* $FlowFixMe(>=0.18.0): `outputRange[0].match()` can return `null`. Need to + * guard against this possibility. + */ + const outputRanges = outputRange[0].match(stringShapeRegex).map(() => []); + outputRange.forEach(value => { + /* $FlowFixMe(>=0.18.0): `value.match()` can return `null`. Need to guard + * against this possibility. + */ + value.match(stringShapeRegex).forEach((number, i) => { + outputRanges[i].push(+number); + }); + }); + + /* $FlowFixMe(>=0.18.0): `outputRange[0].match()` can return `null`. Need to + * guard against this possibility. + */ + const interpolations = outputRange[0] + .match(stringShapeRegex) + .map((value, i) => { + return createInterpolation({ + ...config, + outputRange: outputRanges[i], + }); + }); + + // rgba requires that the r,g,b are integers.... so we want to round them, but we *dont* want to + // round the opacity (4th column). + const shouldRound = isRgbOrRgba(outputRange[0]); + + return input => { + let i = 0; + // 'rgba(0, 100, 200, 0)' + // -> + // 'rgba(${interpolations[0](input)}, ${interpolations[1](input)}, ...' + return outputRange[0].replace(stringShapeRegex, () => { + const val = +interpolations[i++](input); + const rounded = + shouldRound && i < 4 ? Math.round(val) : Math.round(val * 1000) / 1000; + return String(rounded); + }); + }; +} + +function isRgbOrRgba(range) { + return typeof range === 'string' && range.startsWith('rgb'); +} + +function checkPattern(arr: Array) { + const pattern = arr[0].replace(stringShapeRegex, ''); + for (let i = 1; i < arr.length; ++i) { + invariant( + pattern === arr[i].replace(stringShapeRegex, ''), + 'invalid pattern ' + arr[0] + ' and ' + arr[i], + ); + } +} + +function findRange(input: number, inputRange: Array) { + let i; + for (i = 1; i < inputRange.length - 1; ++i) { + if (inputRange[i] >= input) { + break; + } + } + return i - 1; +} + +function checkValidInputRange(arr: Array) { + invariant(arr.length >= 2, 'inputRange must have at least 2 elements'); + for (let i = 1; i < arr.length; ++i) { + invariant( + arr[i] >= arr[i - 1], + /* $FlowFixMe(>=0.13.0) - In the addition expression below this comment, + * one or both of the operands may be something that doesn't cleanly + * convert to a string, like undefined, null, and object, etc. If you really + * mean this implicit string conversion, you can do something like + * String(myThing) + */ + 'inputRange must be monotonically increasing ' + arr, + ); + } +} + +function checkInfiniteRange(name: string, arr: Array) { + invariant(arr.length >= 2, name + ' must have at least 2 elements'); + invariant( + arr.length !== 2 || arr[0] !== -Infinity || arr[1] !== Infinity, + /* $FlowFixMe(>=0.13.0) - In the addition expression below this comment, + * one or both of the operands may be something that doesn't cleanly convert + * to a string, like undefined, null, and object, etc. If you really mean + * this implicit string conversion, you can do something like + * String(myThing) + */ + name + 'cannot be ]-infinity;+infinity[ ' + arr, + ); +} + +class AnimatedInterpolation extends AnimatedWithChildren { + // Export for testing. + static __createInterpolation = createInterpolation; + + _parent: AnimatedNode; + _config: InterpolationConfigType; + _interpolation: (input: number) => number | string; + + constructor(parent: AnimatedNode, config: InterpolationConfigType) { + super(); + this._parent = parent; + this._config = config; + this._interpolation = createInterpolation(config); + } + + __makeNative() { + this._parent.__makeNative(); + super.__makeNative(); + } + + __getValue(): number | string { + const parentValue: number = this._parent.__getValue(); + invariant( + typeof parentValue === 'number', + 'Cannot interpolate an input which is not a number.', + ); + return this._interpolation(parentValue); + } + + interpolate(config: InterpolationConfigType): AnimatedInterpolation { + return new AnimatedInterpolation(this, config); + } + + __attach(): void { + this._parent.__addChild(this); + } + + __detach(): void { + this._parent.__removeChild(this); + super.__detach(); + } + + __transformDataType(range: Array) { + // Change the string array type to number array + // So we can reuse the same logic in iOS and Android platform + return range.map(function(value) { + if (typeof value !== 'string') { + return value; + } + if (/deg$/.test(value)) { + const degrees = parseFloat(value) || 0; + const radians = degrees * Math.PI / 180.0; + return radians; + } else { + // Assume radians + return parseFloat(value) || 0; + } + }); + } + + __getNativeConfig(): any { + if (process.env.NODE_ENV !== 'production') { + NativeAnimatedHelper.validateInterpolation(this._config); + } + + return { + inputRange: this._config.inputRange, + // Only the `outputRange` can contain strings so we don't need to tranform `inputRange` here + outputRange: this.__transformDataType(this._config.outputRange), + extrapolateLeft: + this._config.extrapolateLeft || this._config.extrapolate || 'extend', + extrapolateRight: + this._config.extrapolateRight || this._config.extrapolate || 'extend', + type: 'interpolation', + }; + } +} + +module.exports = AnimatedInterpolation; diff --git a/src/vendor/Animated/nodes/AnimatedModulo.js b/src/vendor/Animated/nodes/AnimatedModulo.js new file mode 100644 index 00000000..3ff75e73 --- /dev/null +++ b/src/vendor/Animated/nodes/AnimatedModulo.js @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule AnimatedModulo + * @flow + * @format + */ +'use strict'; + +const AnimatedInterpolation = require('./AnimatedInterpolation'); +const AnimatedNode = require('./AnimatedNode'); +const AnimatedWithChildren = require('./AnimatedWithChildren'); + +import type {InterpolationConfigType} from './AnimatedInterpolation'; + +class AnimatedModulo extends AnimatedWithChildren { + _a: AnimatedNode; + _modulus: number; + + constructor(a: AnimatedNode, modulus: number) { + super(); + this._a = a; + this._modulus = modulus; + } + + __makeNative() { + this._a.__makeNative(); + super.__makeNative(); + } + + __getValue(): number { + return ( + (this._a.__getValue() % this._modulus + this._modulus) % this._modulus + ); + } + + interpolate(config: InterpolationConfigType): AnimatedInterpolation { + return new AnimatedInterpolation(this, config); + } + + __attach(): void { + this._a.__addChild(this); + } + + __detach(): void { + this._a.__removeChild(this); + super.__detach(); + } + + __getNativeConfig(): any { + return { + type: 'modulus', + input: this._a.__getNativeTag(), + modulus: this._modulus, + }; + } +} + +module.exports = AnimatedModulo; diff --git a/src/vendor/Animated/nodes/AnimatedMultiplication.js b/src/vendor/Animated/nodes/AnimatedMultiplication.js new file mode 100644 index 00000000..d30ce259 --- /dev/null +++ b/src/vendor/Animated/nodes/AnimatedMultiplication.js @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule AnimatedMultiplication + * @flow + * @format + */ +'use strict'; + +const AnimatedInterpolation = require('./AnimatedInterpolation'); +const AnimatedNode = require('./AnimatedNode'); +const AnimatedValue = require('./AnimatedValue'); +const AnimatedWithChildren = require('./AnimatedWithChildren'); + +import type {InterpolationConfigType} from './AnimatedInterpolation'; + +class AnimatedMultiplication extends AnimatedWithChildren { + _a: AnimatedNode; + _b: AnimatedNode; + + constructor(a: AnimatedNode | number, b: AnimatedNode | number) { + super(); + this._a = typeof a === 'number' ? new AnimatedValue(a) : a; + this._b = typeof b === 'number' ? new AnimatedValue(b) : b; + } + + __makeNative() { + this._a.__makeNative(); + this._b.__makeNative(); + super.__makeNative(); + } + + __getValue(): number { + return this._a.__getValue() * this._b.__getValue(); + } + + interpolate(config: InterpolationConfigType): AnimatedInterpolation { + return new AnimatedInterpolation(this, config); + } + + __attach(): void { + this._a.__addChild(this); + this._b.__addChild(this); + } + + __detach(): void { + this._a.__removeChild(this); + this._b.__removeChild(this); + super.__detach(); + } + + __getNativeConfig(): any { + return { + type: 'multiplication', + input: [this._a.__getNativeTag(), this._b.__getNativeTag()], + }; + } +} + +module.exports = AnimatedMultiplication; diff --git a/src/vendor/Animated/nodes/AnimatedNode.js b/src/vendor/Animated/nodes/AnimatedNode.js new file mode 100644 index 00000000..c29f4b7f --- /dev/null +++ b/src/vendor/Animated/nodes/AnimatedNode.js @@ -0,0 +1,73 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule AnimatedNode + * @flow + * @format + */ +'use strict'; + +const NativeAnimatedHelper = require('../NativeAnimatedHelper'); + +const invariant = require('fbjs/lib/invariant'); + +// Note(vjeux): this would be better as an interface but flow doesn't +// support them yet +class AnimatedNode { + __attach(): void {} + __detach(): void { + if (this.__isNative && this.__nativeTag != null) { + NativeAnimatedHelper.API.dropAnimatedNode(this.__nativeTag); + this.__nativeTag = undefined; + } + } + __getValue(): any {} + __getAnimatedValue(): any { + return this.__getValue(); + } + __addChild(child: AnimatedNode) {} + __removeChild(child: AnimatedNode) {} + __getChildren(): Array { + return []; + } + + /* Methods and props used by native Animated impl */ + __isNative: boolean; + __nativeTag: ?number; + __makeNative() { + if (!this.__isNative) { + throw new Error('This node cannot be made a "native" animated node'); + } + } + __getNativeTag(): ?number { + NativeAnimatedHelper.assertNativeAnimatedModule(); + invariant( + this.__isNative, + 'Attempt to get native tag from node not marked as "native"', + ); + if (this.__nativeTag == null) { + const nativeTag: ?number = NativeAnimatedHelper.generateNewNodeTag(); + NativeAnimatedHelper.API.createAnimatedNode( + nativeTag, + this.__getNativeConfig(), + ); + this.__nativeTag = nativeTag; + } + return this.__nativeTag; + } + __getNativeConfig(): Object { + throw new Error( + 'This JS animated node type cannot be used as native animated node', + ); + } + toJSON(): any { + return this.__getValue(); + } +} + +module.exports = AnimatedNode; diff --git a/src/vendor/Animated/nodes/AnimatedProps.js b/src/vendor/Animated/nodes/AnimatedProps.js new file mode 100644 index 00000000..6396d3a1 --- /dev/null +++ b/src/vendor/Animated/nodes/AnimatedProps.js @@ -0,0 +1,167 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule AnimatedProps + * @flow + * @format + */ +'use strict'; + +const {AnimatedEvent} = require('../AnimatedEvent'); +const AnimatedNode = require('./AnimatedNode'); +const AnimatedStyle = require('./AnimatedStyle'); +const NativeAnimatedHelper = require('../NativeAnimatedHelper'); +const findNodeHandle = require('../../../modules/findNodeHandle').default; + +const invariant = require('fbjs/lib/invariant'); + +class AnimatedProps extends AnimatedNode { + _props: Object; + _animatedView: any; + _callback: () => void; + + constructor(props: Object, callback: () => void) { + super(); + if (props.style) { + props = { + ...props, + style: new AnimatedStyle(props.style), + }; + } + this._props = props; + this._callback = callback; + this.__attach(); + } + + __getValue(): Object { + const props = {}; + for (const key in this._props) { + const value = this._props[key]; + if (value instanceof AnimatedNode) { + if (!value.__isNative || value instanceof AnimatedStyle) { + // We cannot use value of natively driven nodes this way as the value we have access from + // JS may not be up to date. + props[key] = value.__getValue(); + } + } else if (value instanceof AnimatedEvent) { + props[key] = value.__getHandler(); + } else { + props[key] = value; + } + } + return props; + } + + __getAnimatedValue(): Object { + const props = {}; + for (const key in this._props) { + const value = this._props[key]; + if (value instanceof AnimatedNode) { + props[key] = value.__getAnimatedValue(); + } + } + return props; + } + + __attach(): void { + for (const key in this._props) { + const value = this._props[key]; + if (value instanceof AnimatedNode) { + value.__addChild(this); + } + } + } + + __detach(): void { + if (this.__isNative && this._animatedView) { + this.__disconnectAnimatedView(); + } + for (const key in this._props) { + const value = this._props[key]; + if (value instanceof AnimatedNode) { + value.__removeChild(this); + } + } + super.__detach(); + } + + update(): void { + this._callback(); + } + + __makeNative(): void { + if (!this.__isNative) { + this.__isNative = true; + for (const key in this._props) { + const value = this._props[key]; + if (value instanceof AnimatedNode) { + value.__makeNative(); + } + } + if (this._animatedView) { + this.__connectAnimatedView(); + } + } + } + + setNativeView(animatedView: any): void { + if (this._animatedView === animatedView) { + return; + } + this._animatedView = animatedView; + if (this.__isNative) { + this.__connectAnimatedView(); + } + } + + __connectAnimatedView(): void { + invariant(this.__isNative, 'Expected node to be marked as "native"'); + const nativeViewTag: ?number = findNodeHandle( + this._animatedView, + ); + invariant( + nativeViewTag != null, + 'Unable to locate attached view in the native tree', + ); + NativeAnimatedHelper.API.connectAnimatedNodeToView( + this.__getNativeTag(), + nativeViewTag, + ); + } + + __disconnectAnimatedView(): void { + invariant(this.__isNative, 'Expected node to be marked as "native"'); + const nativeViewTag: ?number = findNodeHandle( + this._animatedView, + ); + invariant( + nativeViewTag != null, + 'Unable to locate attached view in the native tree', + ); + NativeAnimatedHelper.API.disconnectAnimatedNodeFromView( + this.__getNativeTag(), + nativeViewTag, + ); + } + + __getNativeConfig(): Object { + const propsConfig = {}; + for (const propKey in this._props) { + const value = this._props[propKey]; + if (value instanceof AnimatedNode) { + propsConfig[propKey] = value.__getNativeTag(); + } + } + return { + type: 'props', + props: propsConfig, + }; + } +} + +module.exports = AnimatedProps; diff --git a/src/vendor/Animated/nodes/AnimatedStyle.js b/src/vendor/Animated/nodes/AnimatedStyle.js new file mode 100644 index 00000000..5e0fc354 --- /dev/null +++ b/src/vendor/Animated/nodes/AnimatedStyle.js @@ -0,0 +1,127 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule AnimatedStyle + * @noflow + * @format + */ +'use strict'; + +const AnimatedNode = require('./AnimatedNode'); +const AnimatedTransform = require('./AnimatedTransform'); +const AnimatedWithChildren = require('./AnimatedWithChildren'); +const NativeAnimatedHelper = require('../NativeAnimatedHelper'); + +const flattenStyle = require('../../../apis/StyleSheet/flattenStyle').default; + +class AnimatedStyle extends AnimatedWithChildren { + _style: Object; + + constructor(style: any) { + super(); + style = flattenStyle(style) || {}; + if (style.transform) { + style = { + ...style, + transform: new AnimatedTransform(style.transform), + }; + } + this._style = style; + } + + // Recursively get values for nested styles (like iOS's shadowOffset) + _walkStyleAndGetValues(style) { + const updatedStyle = {}; + for (const key in style) { + const value = style[key]; + if (value instanceof AnimatedNode) { + if (!value.__isNative) { + // We cannot use value of natively driven nodes this way as the value we have access from + // JS may not be up to date. + updatedStyle[key] = value.__getValue(); + } + } else if (value && !Array.isArray(value) && typeof value === 'object') { + // Support animating nested values (for example: shadowOffset.height) + updatedStyle[key] = this._walkStyleAndGetValues(value); + } else { + updatedStyle[key] = value; + } + } + return updatedStyle; + } + + __getValue(): Object { + return this._walkStyleAndGetValues(this._style); + } + + // Recursively get animated values for nested styles (like iOS's shadowOffset) + _walkStyleAndGetAnimatedValues(style) { + const updatedStyle = {}; + for (const key in style) { + const value = style[key]; + if (value instanceof AnimatedNode) { + updatedStyle[key] = value.__getAnimatedValue(); + } else if (value && !Array.isArray(value) && typeof value === 'object') { + // Support animating nested values (for example: shadowOffset.height) + updatedStyle[key] = this._walkStyleAndGetAnimatedValues(value); + } + } + return updatedStyle; + } + + __getAnimatedValue(): Object { + return this._walkStyleAndGetAnimatedValues(this._style); + } + + __attach(): void { + for (const key in this._style) { + const value = this._style[key]; + if (value instanceof AnimatedNode) { + value.__addChild(this); + } + } + } + + __detach(): void { + for (const key in this._style) { + const value = this._style[key]; + if (value instanceof AnimatedNode) { + value.__removeChild(this); + } + } + super.__detach(); + } + + __makeNative() { + super.__makeNative(); + for (const key in this._style) { + const value = this._style[key]; + if (value instanceof AnimatedNode) { + value.__makeNative(); + } + } + } + + __getNativeConfig(): Object { + const styleConfig = {}; + for (const styleKey in this._style) { + if (this._style[styleKey] instanceof AnimatedNode) { + styleConfig[styleKey] = this._style[styleKey].__getNativeTag(); + } + // Non-animated styles are set using `setNativeProps`, no need + // to pass those as a part of the node config + } + NativeAnimatedHelper.validateStyles(styleConfig); + return { + type: 'style', + style: styleConfig, + }; + } +} + +module.exports = AnimatedStyle; diff --git a/src/vendor/Animated/nodes/AnimatedTracking.js b/src/vendor/Animated/nodes/AnimatedTracking.js new file mode 100644 index 00000000..1a54f78a --- /dev/null +++ b/src/vendor/Animated/nodes/AnimatedTracking.js @@ -0,0 +1,67 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule AnimatedTracking + * @flow + * @format + */ +'use strict'; + +const AnimatedValue = require('./AnimatedValue'); +const AnimatedNode = require('./AnimatedNode'); + +import type {EndCallback} from '../animations/Animation'; + +class AnimatedTracking extends AnimatedNode { + _value: AnimatedValue; + _parent: AnimatedNode; + _callback: ?EndCallback; + _animationConfig: Object; + _animationClass: any; + + constructor( + value: AnimatedValue, + parent: AnimatedNode, + animationClass: any, + animationConfig: Object, + callback?: ?EndCallback, + ) { + super(); + this._value = value; + this._parent = parent; + this._animationClass = animationClass; + this._animationConfig = animationConfig; + this._callback = callback; + this.__attach(); + } + + __getValue(): Object { + return this._parent.__getValue(); + } + + __attach(): void { + this._parent.__addChild(this); + } + + __detach(): void { + this._parent.__removeChild(this); + super.__detach(); + } + + update(): void { + this._value.animate( + new this._animationClass({ + ...this._animationConfig, + toValue: (this._animationConfig.toValue: any).__getValue(), + }), + this._callback, + ); + } +} + +module.exports = AnimatedTracking; diff --git a/src/vendor/Animated/nodes/AnimatedTransform.js b/src/vendor/Animated/nodes/AnimatedTransform.js new file mode 100644 index 00000000..c95aa16f --- /dev/null +++ b/src/vendor/Animated/nodes/AnimatedTransform.js @@ -0,0 +1,123 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule AnimatedTransform + * @flow + * @format + */ +'use strict'; + +const AnimatedNode = require('./AnimatedNode'); +const AnimatedWithChildren = require('./AnimatedWithChildren'); +const NativeAnimatedHelper = require('../NativeAnimatedHelper'); + +class AnimatedTransform extends AnimatedWithChildren { + _transforms: Array; + + constructor(transforms: Array) { + super(); + this._transforms = transforms; + } + + __makeNative() { + super.__makeNative(); + this._transforms.forEach(transform => { + for (const key in transform) { + const value = transform[key]; + if (value instanceof AnimatedNode) { + value.__makeNative(); + } + } + }); + } + + __getValue(): Array { + return this._transforms.map(transform => { + const result = {}; + for (const key in transform) { + const value = transform[key]; + if (value instanceof AnimatedNode) { + result[key] = value.__getValue(); + } else { + result[key] = value; + } + } + return result; + }); + } + + __getAnimatedValue(): Array { + return this._transforms.map(transform => { + const result = {}; + for (const key in transform) { + const value = transform[key]; + if (value instanceof AnimatedNode) { + result[key] = value.__getAnimatedValue(); + } else { + // All transform components needed to recompose matrix + result[key] = value; + } + } + return result; + }); + } + + __attach(): void { + this._transforms.forEach(transform => { + for (const key in transform) { + const value = transform[key]; + if (value instanceof AnimatedNode) { + value.__addChild(this); + } + } + }); + } + + __detach(): void { + this._transforms.forEach(transform => { + for (const key in transform) { + const value = transform[key]; + if (value instanceof AnimatedNode) { + value.__removeChild(this); + } + } + }); + super.__detach(); + } + + __getNativeConfig(): any { + const transConfigs = []; + + this._transforms.forEach(transform => { + for (const key in transform) { + const value = transform[key]; + if (value instanceof AnimatedNode) { + transConfigs.push({ + type: 'animated', + property: key, + nodeTag: value.__getNativeTag(), + }); + } else { + transConfigs.push({ + type: 'static', + property: key, + value, + }); + } + } + }); + + NativeAnimatedHelper.validateTransform(transConfigs); + return { + type: 'transform', + transforms: transConfigs, + }; + } +} + +module.exports = AnimatedTransform; diff --git a/src/vendor/Animated/nodes/AnimatedValue.js b/src/vendor/Animated/nodes/AnimatedValue.js new file mode 100644 index 00000000..b0e9e0c5 --- /dev/null +++ b/src/vendor/Animated/nodes/AnimatedValue.js @@ -0,0 +1,338 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule AnimatedValue + * @flow + * @format + */ +'use strict'; + +const AnimatedInterpolation = require('./AnimatedInterpolation'); +const AnimatedNode = require('./AnimatedNode'); +const AnimatedWithChildren = require('./AnimatedWithChildren'); +const InteractionManager = require('../../../apis/InteractionManager').default; +const NativeAnimatedHelper = require('../NativeAnimatedHelper'); + +import type Animation, {EndCallback} from '../animations/Animation'; +import type {InterpolationConfigType} from './AnimatedInterpolation'; + +const NativeAnimatedAPI = NativeAnimatedHelper.API; + +type ValueListenerCallback = (state: {value: number}) => void; + +let _uniqueId = 1; + +/** + * Animated works by building a directed acyclic graph of dependencies + * transparently when you render your Animated components. + * + * new Animated.Value(0) + * .interpolate() .interpolate() new Animated.Value(1) + * opacity translateY scale + * style transform + * View#234 style + * View#123 + * + * A) Top Down phase + * When an Animated.Value is updated, we recursively go down through this + * graph in order to find leaf nodes: the views that we flag as needing + * an update. + * + * B) Bottom Up phase + * When a view is flagged as needing an update, we recursively go back up + * in order to build the new value that it needs. The reason why we need + * this two-phases process is to deal with composite props such as + * transform which can receive values from multiple parents. + */ +function _flush(rootNode: AnimatedValue): void { + const animatedStyles = new Set(); + function findAnimatedStyles(node) { + if (typeof node.update === 'function') { + animatedStyles.add(node); + } else { + node.__getChildren().forEach(findAnimatedStyles); + } + } + findAnimatedStyles(rootNode); + /* $FlowFixMe */ + animatedStyles.forEach(animatedStyle => animatedStyle.update()); +} + +/** + * Standard value for driving animations. One `Animated.Value` can drive + * multiple properties in a synchronized fashion, but can only be driven by one + * mechanism at a time. Using a new mechanism (e.g. starting a new animation, + * or calling `setValue`) will stop any previous ones. + * + * See http://facebook.github.io/react-native/docs/animatedvalue.html + */ +class AnimatedValue extends AnimatedWithChildren { + _value: number; + _startingValue: number; + _offset: number; + _animation: ?Animation; + _tracking: ?AnimatedNode; + _listeners: {[key: string]: ValueListenerCallback}; + __nativeAnimatedValueListener: ?any; + + constructor(value: number) { + super(); + this._startingValue = this._value = value; + this._offset = 0; + this._animation = null; + this._listeners = {}; + } + + __detach() { + this.stopAnimation(); + super.__detach(); + } + + __getValue(): number { + return this._value + this._offset; + } + + __makeNative() { + super.__makeNative(); + + if (Object.keys(this._listeners).length) { + this._startListeningToNativeValueUpdates(); + } + } + + /** + * Directly set the value. This will stop any animations running on the value + * and update all the bound properties. + * + * See http://facebook.github.io/react-native/docs/animatedvalue.html#setvalue + */ + setValue(value: number): void { + if (this._animation) { + this._animation.stop(); + this._animation = null; + } + this._updateValue( + value, + !this.__isNative /* don't perform a flush for natively driven values */, + ); + if (this.__isNative) { + NativeAnimatedAPI.setAnimatedNodeValue(this.__getNativeTag(), value); + } + } + + /** + * Sets an offset that is applied on top of whatever value is set, whether via + * `setValue`, an animation, or `Animated.event`. Useful for compensating + * things like the start of a pan gesture. + * + * See http://facebook.github.io/react-native/docs/animatedvalue.html#setoffset + */ + setOffset(offset: number): void { + this._offset = offset; + if (this.__isNative) { + NativeAnimatedAPI.setAnimatedNodeOffset(this.__getNativeTag(), offset); + } + } + + /** + * Merges the offset value into the base value and resets the offset to zero. + * The final output of the value is unchanged. + * + * See http://facebook.github.io/react-native/docs/animatedvalue.html#flattenoffset + */ + flattenOffset(): void { + this._value += this._offset; + this._offset = 0; + if (this.__isNative) { + NativeAnimatedAPI.flattenAnimatedNodeOffset(this.__getNativeTag()); + } + } + + /** + * Sets the offset value to the base value, and resets the base value to zero. + * The final output of the value is unchanged. + * + * See http://facebook.github.io/react-native/docs/animatedvalue.html#extractoffset + */ + extractOffset(): void { + this._offset += this._value; + this._value = 0; + if (this.__isNative) { + NativeAnimatedAPI.extractAnimatedNodeOffset(this.__getNativeTag()); + } + } + + /** + * Adds an asynchronous listener to the value so you can observe updates from + * animations. This is useful because there is no way to + * synchronously read the value because it might be driven natively. + * + * See http://facebook.github.io/react-native/docs/animatedvalue.html#addlistener + */ + addListener(callback: ValueListenerCallback): string { + const id = String(_uniqueId++); + this._listeners[id] = callback; + if (this.__isNative) { + this._startListeningToNativeValueUpdates(); + } + return id; + } + + /** + * Unregister a listener. The `id` param shall match the identifier + * previously returned by `addListener()`. + * + * See http://facebook.github.io/react-native/docs/animatedvalue.html#removelistener + */ + removeListener(id: string): void { + delete this._listeners[id]; + if (this.__isNative && Object.keys(this._listeners).length === 0) { + this._stopListeningForNativeValueUpdates(); + } + } + + /** + * Remove all registered listeners. + * + * See http://facebook.github.io/react-native/docs/animatedvalue.html#removealllisteners + */ + removeAllListeners(): void { + this._listeners = {}; + if (this.__isNative) { + this._stopListeningForNativeValueUpdates(); + } + } + + _startListeningToNativeValueUpdates() { + if (this.__nativeAnimatedValueListener) { + return; + } + + NativeAnimatedAPI.startListeningToAnimatedNodeValue(this.__getNativeTag()); + this.__nativeAnimatedValueListener = NativeAnimatedHelper.nativeEventEmitter.addListener( + 'onAnimatedValueUpdate', + data => { + if (data.tag !== this.__getNativeTag()) { + return; + } + this._updateValue(data.value, false /* flush */); + }, + ); + } + + _stopListeningForNativeValueUpdates() { + if (!this.__nativeAnimatedValueListener) { + return; + } + + this.__nativeAnimatedValueListener.remove(); + this.__nativeAnimatedValueListener = null; + NativeAnimatedAPI.stopListeningToAnimatedNodeValue(this.__getNativeTag()); + } + + /** + * Stops any running animation or tracking. `callback` is invoked with the + * final value after stopping the animation, which is useful for updating + * state to match the animation position with layout. + * + * See http://facebook.github.io/react-native/docs/animatedvalue.html#stopanimation + */ + stopAnimation(callback?: ?(value: number) => void): void { + this.stopTracking(); + this._animation && this._animation.stop(); + this._animation = null; + callback && callback(this.__getValue()); + } + + /** + * Stops any animation and resets the value to its original. + * + * See http://facebook.github.io/react-native/docs/animatedvalue.html#resetanimation + */ + resetAnimation(callback?: ?(value: number) => void): void { + this.stopAnimation(callback); + this._value = this._startingValue; + } + + /** + * Interpolates the value before updating the property, e.g. mapping 0-1 to + * 0-10. + */ + interpolate(config: InterpolationConfigType): AnimatedInterpolation { + return new AnimatedInterpolation(this, config); + } + + /** + * Typically only used internally, but could be used by a custom Animation + * class. + * + * See http://facebook.github.io/react-native/docs/animatedvalue.html#animate + */ + animate(animation: Animation, callback: ?EndCallback): void { + let handle = null; + if (animation.__isInteraction) { + handle = InteractionManager.createInteractionHandle(); + } + const previousAnimation = this._animation; + this._animation && this._animation.stop(); + this._animation = animation; + animation.start( + this._value, + value => { + // Natively driven animations will never call into that callback, therefore we can always + // pass flush = true to allow the updated value to propagate to native with setNativeProps + this._updateValue(value, true /* flush */); + }, + result => { + this._animation = null; + if (handle !== null) { + InteractionManager.clearInteractionHandle(handle); + } + callback && callback(result); + }, + previousAnimation, + this, + ); + } + + /** + * Typically only used internally. + */ + stopTracking(): void { + this._tracking && this._tracking.__detach(); + this._tracking = null; + } + + /** + * Typically only used internally. + */ + track(tracking: AnimatedNode): void { + this.stopTracking(); + this._tracking = tracking; + } + + _updateValue(value: number, flush: boolean): void { + this._value = value; + if (flush) { + _flush(this); + } + for (const key in this._listeners) { + this._listeners[key]({value: this.__getValue()}); + } + } + + __getNativeConfig(): Object { + return { + type: 'value', + value: this._value, + offset: this._offset, + }; + } +} + +module.exports = AnimatedValue; diff --git a/src/vendor/Animated/nodes/AnimatedValueXY.js b/src/vendor/Animated/nodes/AnimatedValueXY.js new file mode 100644 index 00000000..d04fe094 --- /dev/null +++ b/src/vendor/Animated/nodes/AnimatedValueXY.js @@ -0,0 +1,197 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule AnimatedValueXY + * @flow + * @format + */ +'use strict'; + +const AnimatedValue = require('./AnimatedValue'); +const AnimatedWithChildren = require('./AnimatedWithChildren'); + +const invariant = require('fbjs/lib/invariant'); + +type ValueXYListenerCallback = (value: {x: number, y: number}) => void; + +let _uniqueId = 1; + +/** + * 2D Value for driving 2D animations, such as pan gestures. Almost identical + * API to normal `Animated.Value`, but multiplexed. + * + * See http://facebook.github.io/react-native/docs/animatedvaluexy.html + */ +class AnimatedValueXY extends AnimatedWithChildren { + x: AnimatedValue; + y: AnimatedValue; + _listeners: {[key: string]: {x: string, y: string}}; + + constructor( + valueIn?: ?{x: number | AnimatedValue, y: number | AnimatedValue}, + ) { + super(); + const value: any = valueIn || {x: 0, y: 0}; // @flowfixme: shouldn't need `: any` + if (typeof value.x === 'number' && typeof value.y === 'number') { + this.x = new AnimatedValue(value.x); + this.y = new AnimatedValue(value.y); + } else { + invariant( + value.x instanceof AnimatedValue && value.y instanceof AnimatedValue, + 'AnimatedValueXY must be initalized with an object of numbers or ' + + 'AnimatedValues.', + ); + this.x = value.x; + this.y = value.y; + } + this._listeners = {}; + } + + /** + * Directly set the value. This will stop any animations running on the value + * and update all the bound properties. + * + * See http://facebook.github.io/react-native/docs/animatedvaluexy.html#setvalue + */ + setValue(value: {x: number, y: number}) { + this.x.setValue(value.x); + this.y.setValue(value.y); + } + + /** + * Sets an offset that is applied on top of whatever value is set, whether + * via `setValue`, an animation, or `Animated.event`. Useful for compensating + * things like the start of a pan gesture. + * + * See http://facebook.github.io/react-native/docs/animatedvaluexy.html#setoffset + */ + setOffset(offset: {x: number, y: number}) { + this.x.setOffset(offset.x); + this.y.setOffset(offset.y); + } + + /** + * Merges the offset value into the base value and resets the offset to zero. + * The final output of the value is unchanged. + * + * See http://facebook.github.io/react-native/docs/animatedvaluexy.html#flattenoffset + */ + flattenOffset(): void { + this.x.flattenOffset(); + this.y.flattenOffset(); + } + + /** + * Sets the offset value to the base value, and resets the base value to + * zero. The final output of the value is unchanged. + * + * See http://facebook.github.io/react-native/docs/animatedvaluexy.html#extractoffset + */ + extractOffset(): void { + this.x.extractOffset(); + this.y.extractOffset(); + } + + __getValue(): {x: number, y: number} { + return { + x: this.x.__getValue(), + y: this.y.__getValue(), + }; + } + + /** + * Stops any animation and resets the value to its original. + * + * See http://facebook.github.io/react-native/docs/animatedvaluexy.html#resetanimation + */ + resetAnimation(callback?: (value: {x: number, y: number}) => void): void { + this.x.resetAnimation(); + this.y.resetAnimation(); + callback && callback(this.__getValue()); + } + + /** + * Stops any running animation or tracking. `callback` is invoked with the + * final value after stopping the animation, which is useful for updating + * state to match the animation position with layout. + * + * See http://facebook.github.io/react-native/docs/animatedvaluexy.html#stopanimation + */ + stopAnimation(callback?: (value: {x: number, y: number}) => void): void { + this.x.stopAnimation(); + this.y.stopAnimation(); + callback && callback(this.__getValue()); + } + + /** + * Adds an asynchronous listener to the value so you can observe updates from + * animations. This is useful because there is no way to synchronously read + * the value because it might be driven natively. + * + * Returns a string that serves as an identifier for the listener. + * + * See http://facebook.github.io/react-native/docs/animatedvaluexy.html#addlistener + */ + addListener(callback: ValueXYListenerCallback): string { + const id = String(_uniqueId++); + const jointCallback = ({value: number}) => { + callback(this.__getValue()); + }; + this._listeners[id] = { + x: this.x.addListener(jointCallback), + y: this.y.addListener(jointCallback), + }; + return id; + } + + /** + * Unregister a listener. The `id` param shall match the identifier + * previously returned by `addListener()`. + * + * See http://facebook.github.io/react-native/docs/animatedvaluexy.html#removelistener + */ + removeListener(id: string): void { + this.x.removeListener(this._listeners[id].x); + this.y.removeListener(this._listeners[id].y); + delete this._listeners[id]; + } + + /** + * Remove all registered listeners. + * + * See http://facebook.github.io/react-native/docs/animatedvaluexy.html#removealllisteners + */ + removeAllListeners(): void { + this.x.removeAllListeners(); + this.y.removeAllListeners(); + this._listeners = {}; + } + + /** + * Converts `{x, y}` into `{left, top}` for use in style. + * + * See http://facebook.github.io/react-native/docs/animatedvaluexy.html#getlayout + */ + getLayout(): {[key: string]: AnimatedValue} { + return { + left: this.x, + top: this.y, + }; + } + + /** + * Converts `{x, y}` into a useable translation transform. + * + * See http://facebook.github.io/react-native/docs/animatedvaluexy.html#gettranslatetransform + */ + getTranslateTransform(): Array<{[key: string]: AnimatedValue}> { + return [{translateX: this.x}, {translateY: this.y}]; + } +} + +module.exports = AnimatedValueXY; diff --git a/src/vendor/Animated/nodes/AnimatedWithChildren.js b/src/vendor/Animated/nodes/AnimatedWithChildren.js new file mode 100644 index 00000000..4676437a --- /dev/null +++ b/src/vendor/Animated/nodes/AnimatedWithChildren.js @@ -0,0 +1,77 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule AnimatedWithChildren + * @flow + * @format + */ +'use strict'; + +const AnimatedNode = require('./AnimatedNode'); +const NativeAnimatedHelper = require('../NativeAnimatedHelper'); + +class AnimatedWithChildren extends AnimatedNode { + _children: Array; + + constructor() { + super(); + this._children = []; + } + + __makeNative() { + if (!this.__isNative) { + this.__isNative = true; + for (const child of this._children) { + child.__makeNative(); + NativeAnimatedHelper.API.connectAnimatedNodes( + this.__getNativeTag(), + child.__getNativeTag(), + ); + } + } + } + + __addChild(child: AnimatedNode): void { + if (this._children.length === 0) { + this.__attach(); + } + this._children.push(child); + if (this.__isNative) { + // Only accept "native" animated nodes as children + child.__makeNative(); + NativeAnimatedHelper.API.connectAnimatedNodes( + this.__getNativeTag(), + child.__getNativeTag(), + ); + } + } + + __removeChild(child: AnimatedNode): void { + const index = this._children.indexOf(child); + if (index === -1) { + console.warn("Trying to remove a child that doesn't exist"); + return; + } + if (this.__isNative && child.__isNative) { + NativeAnimatedHelper.API.disconnectAnimatedNodes( + this.__getNativeTag(), + child.__getNativeTag(), + ); + } + this._children.splice(index, 1); + if (this._children.length === 0) { + this.__detach(); + } + } + + __getChildren(): Array { + return this._children; + } +} + +module.exports = AnimatedWithChildren; diff --git a/src/vendor/PooledClass/index.js b/src/vendor/PooledClass/index.js index 923cbdef..16c758a6 100644 --- a/src/vendor/PooledClass/index.js +++ b/src/vendor/PooledClass/index.js @@ -7,6 +7,8 @@ * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. + * + * From React 16.0.0 */ import invariant from 'fbjs/lib/invariant'; diff --git a/src/vendor/dangerousStyleValue/index.js b/src/vendor/dangerousStyleValue/index.js index 710c156c..38f8bc01 100644 --- a/src/vendor/dangerousStyleValue/index.js +++ b/src/vendor/dangerousStyleValue/index.js @@ -9,6 +9,7 @@ * of patent rights can be found in the PATENTS file in the same directory. * * @providesModule dangerousStyleValue + * From React 16.0.0 */ import isUnitlessNumber from '../../modules/unitlessNumbers'; diff --git a/src/vendor/setValueForStyles/index.js b/src/vendor/setValueForStyles/index.js index 5ce55b59..146754f4 100644 --- a/src/vendor/setValueForStyles/index.js +++ b/src/vendor/setValueForStyles/index.js @@ -8,6 +8,7 @@ * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. * + * From React 16.0.0 */ import dangerousStyleValue from '../dangerousStyleValue'; diff --git a/src/vendor/warnValidStyle/index.js b/src/vendor/warnValidStyle/index.js index cfaf05e5..69bf70ce 100644 --- a/src/vendor/warnValidStyle/index.js +++ b/src/vendor/warnValidStyle/index.js @@ -9,6 +9,7 @@ * of patent rights can be found in the PATENTS file in the same directory. * * @providesModule warnValidStyle + * From React 16.0.0 */ var emptyFunction = require('fbjs/lib/emptyFunction'); diff --git a/yarn.lock b/yarn.lock index 44e6d7f0..2cccac54 100644 --- a/yarn.lock +++ b/yarn.lock @@ -169,13 +169,6 @@ amdefine@>=0.0.4: version "1.0.1" resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" -animated@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/animated/-/animated-0.2.0.tgz#1a0e96f097b3fbc5b64d7eddc723bcc0a6f97633" - dependencies: - invariant "^2.2.0" - normalize-css-color "^1.0.1" - ansi-align@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-2.0.0.tgz#c36aeccba563b89ceb556f3690f0b1d9e3547f7f" @@ -3925,7 +3918,7 @@ nopt@~3.0.6: dependencies: abbrev "1" -normalize-css-color@^1.0.1, normalize-css-color@^1.0.2: +normalize-css-color@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/normalize-css-color/-/normalize-css-color-1.0.2.tgz#02991e97cccec6623fe573afbbf0de6a1f3e9f8d"