Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split up AnimatedImplementation.js #15485

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
195 changes: 195 additions & 0 deletions Libraries/Animated/src/AnimatedEvent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
/**
* 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.
*
* @flow
* @format
*/
'use strict';

const AnimatedValue = require('./nodes/AnimatedValue');
const NativeAnimatedHelper = require('./NativeAnimatedHelper');
const ReactNative = require('ReactNative');

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 = ReactNative.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 (__DEV__) {
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) This comment
* suppresses an error found when Flow v0.53 was deployed. 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