270 lines
8.8 KiB
JavaScript
270 lines
8.8 KiB
JavaScript
![]() |
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
|
||
|
|
||
|
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
||
|
|
||
|
import React from 'react';
|
||
|
import { findNodeHandle, Platform, StyleSheet } from 'react-native';
|
||
|
import ReanimatedEventEmitter from './ReanimatedEventEmitter';
|
||
|
import AnimatedEvent from './core/AnimatedEvent';
|
||
|
import AnimatedNode from './core/AnimatedNode';
|
||
|
import AnimatedValue from './core/AnimatedValue';
|
||
|
import { createOrReusePropsNode } from './core/AnimatedProps';
|
||
|
import invariant from 'fbjs/lib/invariant';
|
||
|
const NODE_MAPPING = new Map();
|
||
|
|
||
|
function listener(data) {
|
||
|
const component = NODE_MAPPING.get(data.viewTag);
|
||
|
component && component._updateFromNative(data.props);
|
||
|
}
|
||
|
|
||
|
function dummyListener() {// empty listener we use to assign to listener properties for which animated
|
||
|
// event is used.
|
||
|
}
|
||
|
|
||
|
export default function createAnimatedComponent(Component) {
|
||
|
invariant(typeof Component !== 'function' || Component.prototype && Component.prototype.isReactComponent, '`createAnimatedComponent` does not support stateless functional components; ' + 'use a class component instead.');
|
||
|
|
||
|
class AnimatedComponent extends React.Component {
|
||
|
constructor(props) {
|
||
|
super(props);
|
||
|
|
||
|
_defineProperty(this, "_invokeAnimatedPropsCallbackOnMount", false);
|
||
|
|
||
|
_defineProperty(this, "_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 (typeof this._component.setNativeProps !== 'function') {
|
||
|
this.forceUpdate();
|
||
|
} else {
|
||
|
this._component.setNativeProps(this._propsAnimated.__getValue());
|
||
|
}
|
||
|
});
|
||
|
|
||
|
_defineProperty(this, "_setComponentRef", c => {
|
||
|
if (c !== this._component) {
|
||
|
this._component = c;
|
||
|
}
|
||
|
});
|
||
|
|
||
|
this._attachProps(this.props);
|
||
|
}
|
||
|
|
||
|
componentWillUnmount() {
|
||
|
this._detachPropUpdater();
|
||
|
|
||
|
this._propsAnimated && this._propsAnimated.__detach();
|
||
|
|
||
|
this._detachNativeEvents();
|
||
|
}
|
||
|
|
||
|
setNativeProps(props) {
|
||
|
this._component.setNativeProps(props);
|
||
|
}
|
||
|
|
||
|
componentDidMount() {
|
||
|
if (this._invokeAnimatedPropsCallbackOnMount) {
|
||
|
this._invokeAnimatedPropsCallbackOnMount = false;
|
||
|
|
||
|
this._animatedPropsCallback();
|
||
|
}
|
||
|
|
||
|
this._propsAnimated.setNativeView(this._component);
|
||
|
|
||
|
this._attachNativeEvents();
|
||
|
|
||
|
this._attachPropUpdater();
|
||
|
}
|
||
|
|
||
|
_getEventViewRef() {
|
||
|
// Make sure to get the scrollable node for components that implement
|
||
|
// `ScrollResponder.Mixin`.
|
||
|
return this._component.getScrollableNode ? this._component.getScrollableNode() : this._component;
|
||
|
}
|
||
|
|
||
|
_attachNativeEvents() {
|
||
|
const node = this._getEventViewRef();
|
||
|
|
||
|
for (const key in this.props) {
|
||
|
const prop = this.props[key];
|
||
|
|
||
|
if (prop instanceof AnimatedEvent) {
|
||
|
prop.attachEvent(node, key);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
_detachNativeEvents() {
|
||
|
const node = this._getEventViewRef();
|
||
|
|
||
|
for (const key in this.props) {
|
||
|
const prop = this.props[key];
|
||
|
|
||
|
if (prop instanceof AnimatedEvent) {
|
||
|
prop.detachEvent(node, key);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
_reattachNativeEvents(prevProps) {
|
||
|
const node = this._getEventViewRef();
|
||
|
|
||
|
const attached = new Set();
|
||
|
const nextEvts = new Set();
|
||
|
|
||
|
for (const key in this.props) {
|
||
|
const prop = this.props[key];
|
||
|
|
||
|
if (prop instanceof AnimatedEvent) {
|
||
|
nextEvts.add(prop.__nodeID);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for (const key in prevProps) {
|
||
|
const prop = this.props[key];
|
||
|
|
||
|
if (prop instanceof AnimatedEvent) {
|
||
|
if (!nextEvts.has(prop.__nodeID)) {
|
||
|
// event was in prev props but not in current props, we detach
|
||
|
prop.detachEvent(node, key);
|
||
|
} else {
|
||
|
// event was in prev and is still in current props
|
||
|
attached.add(prop.__nodeID);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for (const key in this.props) {
|
||
|
const prop = this.props[key];
|
||
|
|
||
|
if (prop instanceof AnimatedEvent && !attached.has(prop.__nodeID)) {
|
||
|
// not yet attached
|
||
|
prop.attachEvent(node, key);
|
||
|
}
|
||
|
}
|
||
|
} // 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 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.
|
||
|
|
||
|
|
||
|
_attachProps(nextProps) {
|
||
|
const oldPropsAnimated = this._propsAnimated;
|
||
|
this._propsAnimated = createOrReusePropsNode(nextProps, this._animatedPropsCallback, oldPropsAnimated); // If prop node has been reused we don't need to call into "__detach"
|
||
|
|
||
|
if (oldPropsAnimated !== this._propsAnimated) {
|
||
|
// 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();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
_updateFromNative(props) {
|
||
|
this._component.setNativeProps(props);
|
||
|
}
|
||
|
|
||
|
_attachPropUpdater() {
|
||
|
const viewTag = findNodeHandle(this);
|
||
|
NODE_MAPPING.set(viewTag, this);
|
||
|
|
||
|
if (NODE_MAPPING.size === 1) {
|
||
|
ReanimatedEventEmitter.addListener('onReanimatedPropsChange', listener);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
_detachPropUpdater() {
|
||
|
const viewTag = findNodeHandle(this);
|
||
|
NODE_MAPPING.delete(viewTag);
|
||
|
|
||
|
if (NODE_MAPPING.size === 0) {
|
||
|
ReanimatedEventEmitter.removeAllListeners('onReanimatedPropsChange');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
componentDidUpdate(prevProps) {
|
||
|
this._attachProps(this.props);
|
||
|
|
||
|
this._reattachNativeEvents(prevProps);
|
||
|
|
||
|
this._propsAnimated.setNativeView(this._component);
|
||
|
}
|
||
|
|
||
|
_filterNonAnimatedStyle(inputStyle) {
|
||
|
const style = {};
|
||
|
|
||
|
for (const key in inputStyle) {
|
||
|
const value = inputStyle[key];
|
||
|
|
||
|
if (key !== 'transform') {
|
||
|
if (value instanceof AnimatedValue) {
|
||
|
style[key] = value._startingValue;
|
||
|
} else if (!(value instanceof AnimatedNode)) {
|
||
|
style[key] = value;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return style;
|
||
|
}
|
||
|
|
||
|
_filterNonAnimatedProps(inputProps) {
|
||
|
const props = {};
|
||
|
|
||
|
for (const key in inputProps) {
|
||
|
const value = inputProps[key];
|
||
|
|
||
|
if (key === 'style') {
|
||
|
props[key] = this._filterNonAnimatedStyle(StyleSheet.flatten(value));
|
||
|
} else if (value instanceof AnimatedEvent) {
|
||
|
// we cannot filter out event listeners completely as some components
|
||
|
// rely on having a callback registered in order to generate events
|
||
|
// alltogether. Therefore we provide a dummy callback here to allow
|
||
|
// native event dispatcher to hijack events.
|
||
|
props[key] = dummyListener;
|
||
|
} else if (value instanceof AnimatedValue) {
|
||
|
props[key] = value._startingValue;
|
||
|
} else if (!(value instanceof AnimatedNode)) {
|
||
|
props[key] = value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return props;
|
||
|
}
|
||
|
|
||
|
render() {
|
||
|
const props = this._filterNonAnimatedProps(this.props);
|
||
|
|
||
|
const platformProps = Platform.select({
|
||
|
web: {},
|
||
|
default: {
|
||
|
collapsable: false
|
||
|
}
|
||
|
});
|
||
|
return /*#__PURE__*/React.createElement(Component, _extends({}, props, {
|
||
|
ref: this._setComponentRef
|
||
|
}, platformProps));
|
||
|
} // A third party library can use getNode()
|
||
|
// to get the node reference of the decorated component
|
||
|
|
||
|
|
||
|
getNode() {
|
||
|
return this._component;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
AnimatedComponent.displayName = "AnimatedComponent(".concat(Component.displayName || Component.name || 'Component', ")");
|
||
|
return AnimatedComponent;
|
||
|
}
|
||
|
//# sourceMappingURL=createAnimatedComponent.js.map
|