Skip to content

Commit

Permalink
[change] update the Animated implementation
Browse files Browse the repository at this point in the history
Replaces the 'animated' package with the latest implementation from
React Native. Requires a few imports to be replaced.

Close #716
Fix #714
Fix #688
  • Loading branch information
necolas committed Dec 6, 2017
1 parent b7e970f commit 0dfe319
Show file tree
Hide file tree
Showing 37 changed files with 4,468 additions and 28 deletions.
1 change: 0 additions & 1 deletion .flowconfig
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
.*/__tests__/.*
.*/benchmarks/.*
.*/docs/.*
.*/node_modules/animated/*
.*/node_modules/babel-plugin-transform-react-remove-prop-types/*

[include]
Expand Down
5 changes: 2 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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",
Expand Down
24 changes: 11 additions & 13 deletions src/apis/Animated/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
4 changes: 2 additions & 2 deletions src/apis/Easing/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
25 changes: 25 additions & 0 deletions src/modules/NativeEventEmitter/index.js
Original file line number Diff line number Diff line change
@@ -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;
2 changes: 1 addition & 1 deletion src/modules/NativeModules/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
// NativeModules shim
const NativeModules = {};
export default NativeModules;
module.exports = NativeModules;
196 changes: 196 additions & 0 deletions src/vendor/Animated/AnimatedEvent.js
Original file line number Diff line number Diff line change
@@ -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<?Mapping>,
) {
// 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<?Mapping>;
_listeners: Array<Function> = [];
_callListeners: Function;
_attachedEvent: ?{
detach: () => void,
};
__isNative: boolean;

constructor(argMapping: Array<?Mapping>, 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};
Loading

0 comments on commit 0dfe319

Please sign in to comment.