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