This repository has been archived on 2022-03-12. You can view files and clone it, but cannot push or open issues or pull requests.
2021-04-02 02:24:13 +03:00

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