1 | import React from 'react';
|
2 | import { findNodeHandle, Platform, StyleSheet } from 'react-native';
|
3 | import ReanimatedEventEmitter from './ReanimatedEventEmitter';
|
4 |
|
5 | import AnimatedEvent from './core/AnimatedEvent';
|
6 | import AnimatedNode from './core/AnimatedNode';
|
7 | import AnimatedValue from './core/AnimatedValue';
|
8 | import { createOrReusePropsNode } from './core/AnimatedProps';
|
9 |
|
10 | import invariant from 'fbjs/lib/invariant';
|
11 |
|
12 | const NODE_MAPPING = new Map();
|
13 |
|
14 | function listener(data) {
|
15 | const component = NODE_MAPPING.get(data.viewTag);
|
16 | component && component._updateFromNative(data.props);
|
17 | }
|
18 |
|
19 | function dummyListener() {
|
20 |
|
21 |
|
22 | }
|
23 |
|
24 | export default function createAnimatedComponent(Component) {
|
25 | invariant(
|
26 | typeof Component !== 'function' ||
|
27 | (Component.prototype && Component.prototype.isReactComponent),
|
28 | '`createAnimatedComponent` does not support stateless functional components; ' +
|
29 | 'use a class component instead.'
|
30 | );
|
31 |
|
32 | class AnimatedComponent extends React.Component {
|
33 | _invokeAnimatedPropsCallbackOnMount = false;
|
34 |
|
35 | constructor(props) {
|
36 | super(props);
|
37 | this._attachProps(this.props);
|
38 | }
|
39 |
|
40 | componentWillUnmount() {
|
41 | this._detachPropUpdater();
|
42 | this._propsAnimated && this._propsAnimated.__detach();
|
43 | this._detachNativeEvents();
|
44 | }
|
45 |
|
46 | setNativeProps(props) {
|
47 | this._component.setNativeProps(props);
|
48 | }
|
49 |
|
50 | componentDidMount() {
|
51 | if (this._invokeAnimatedPropsCallbackOnMount) {
|
52 | this._invokeAnimatedPropsCallbackOnMount = false;
|
53 | this._animatedPropsCallback();
|
54 | }
|
55 |
|
56 | this._propsAnimated.setNativeView(this._component);
|
57 | this._attachNativeEvents();
|
58 | this._attachPropUpdater();
|
59 | }
|
60 |
|
61 | _getEventViewRef() {
|
62 |
|
63 |
|
64 | return this._component.getScrollableNode
|
65 | ? this._component.getScrollableNode()
|
66 | : this._component;
|
67 | }
|
68 |
|
69 | _attachNativeEvents() {
|
70 | const node = this._getEventViewRef();
|
71 |
|
72 | for (const key in this.props) {
|
73 | const prop = this.props[key];
|
74 | if (prop instanceof AnimatedEvent) {
|
75 | prop.attachEvent(node, key);
|
76 | }
|
77 | }
|
78 | }
|
79 |
|
80 | _detachNativeEvents() {
|
81 | const node = this._getEventViewRef();
|
82 |
|
83 | for (const key in this.props) {
|
84 | const prop = this.props[key];
|
85 | if (prop instanceof AnimatedEvent) {
|
86 | prop.detachEvent(node, key);
|
87 | }
|
88 | }
|
89 | }
|
90 |
|
91 | _reattachNativeEvents(prevProps) {
|
92 | const node = this._getEventViewRef();
|
93 | const attached = new Set();
|
94 | const nextEvts = new Set();
|
95 | for (const key in this.props) {
|
96 | const prop = this.props[key];
|
97 | if (prop instanceof AnimatedEvent) {
|
98 | nextEvts.add(prop.__nodeID);
|
99 | }
|
100 | }
|
101 | for (const key in prevProps) {
|
102 | const prop = this.props[key];
|
103 | if (prop instanceof AnimatedEvent) {
|
104 | if (!nextEvts.has(prop.__nodeID)) {
|
105 |
|
106 | prop.detachEvent(node, key);
|
107 | } else {
|
108 |
|
109 | attached.add(prop.__nodeID);
|
110 | }
|
111 | }
|
112 | }
|
113 | for (const key in this.props) {
|
114 | const prop = this.props[key];
|
115 | if (prop instanceof AnimatedEvent && !attached.has(prop.__nodeID)) {
|
116 |
|
117 | prop.attachEvent(node, key);
|
118 | }
|
119 | }
|
120 | }
|
121 |
|
122 |
|
123 |
|
124 |
|
125 |
|
126 |
|
127 | _animatedPropsCallback = () => {
|
128 | if (this._component == null) {
|
129 |
|
130 |
|
131 |
|
132 |
|
133 |
|
134 | this._invokeAnimatedPropsCallbackOnMount = true;
|
135 | } else if (typeof this._component.setNativeProps !== 'function') {
|
136 | this.forceUpdate();
|
137 | } else {
|
138 | this._component.setNativeProps(this._propsAnimated.__getValue());
|
139 | }
|
140 | };
|
141 |
|
142 | _attachProps(nextProps) {
|
143 | const oldPropsAnimated = this._propsAnimated;
|
144 |
|
145 | this._propsAnimated = createOrReusePropsNode(
|
146 | nextProps,
|
147 | this._animatedPropsCallback,
|
148 | oldPropsAnimated
|
149 | );
|
150 |
|
151 | if (oldPropsAnimated !== this._propsAnimated) {
|
152 |
|
153 |
|
154 |
|
155 |
|
156 |
|
157 |
|
158 |
|
159 |
|
160 | oldPropsAnimated && oldPropsAnimated.__detach();
|
161 | }
|
162 | }
|
163 |
|
164 | _updateFromNative(props) {
|
165 | this._component.setNativeProps(props);
|
166 | }
|
167 |
|
168 | _attachPropUpdater() {
|
169 | const viewTag = findNodeHandle(this);
|
170 | NODE_MAPPING.set(viewTag, this);
|
171 | if (NODE_MAPPING.size === 1) {
|
172 | ReanimatedEventEmitter.addListener('onReanimatedPropsChange', listener);
|
173 | }
|
174 | }
|
175 |
|
176 | _detachPropUpdater() {
|
177 | const viewTag = findNodeHandle(this);
|
178 | NODE_MAPPING.delete(viewTag);
|
179 | if (NODE_MAPPING.size === 0) {
|
180 | ReanimatedEventEmitter.removeAllListeners('onReanimatedPropsChange');
|
181 | }
|
182 | }
|
183 |
|
184 | componentDidUpdate(prevProps) {
|
185 | this._attachProps(this.props);
|
186 | this._reattachNativeEvents(prevProps);
|
187 |
|
188 | this._propsAnimated.setNativeView(this._component);
|
189 | }
|
190 |
|
191 | _setComponentRef = c => {
|
192 | if (c !== this._component) {
|
193 | this._component = c;
|
194 | }
|
195 | };
|
196 |
|
197 | _filterNonAnimatedStyle(inputStyle) {
|
198 | const style = {};
|
199 | for (const key in inputStyle) {
|
200 | const value = inputStyle[key];
|
201 | if (key !== 'transform') {
|
202 | if (value instanceof AnimatedValue) {
|
203 | style[key] = value._startingValue;
|
204 | } else if (!(value instanceof AnimatedNode)) {
|
205 | style[key] = value;
|
206 | }
|
207 | }
|
208 | }
|
209 | return style;
|
210 | }
|
211 |
|
212 | _filterNonAnimatedProps(inputProps) {
|
213 | const props = {};
|
214 | for (const key in inputProps) {
|
215 | const value = inputProps[key];
|
216 | if (key === 'style') {
|
217 | props[key] = this._filterNonAnimatedStyle(StyleSheet.flatten(value));
|
218 | } else if (value instanceof AnimatedEvent) {
|
219 |
|
220 |
|
221 |
|
222 |
|
223 | props[key] = dummyListener;
|
224 | } else if (value instanceof AnimatedValue) {
|
225 | props[key] = value._startingValue;
|
226 | } else if (!(value instanceof AnimatedNode)) {
|
227 | props[key] = value;
|
228 | }
|
229 | }
|
230 | return props;
|
231 | }
|
232 |
|
233 | render() {
|
234 | const props = this._filterNonAnimatedProps(this.props);
|
235 | const platformProps = Platform.select({
|
236 | web: {},
|
237 | default: { collapsable: false },
|
238 | });
|
239 | return (
|
240 | <Component {...props} ref={this._setComponentRef} {...platformProps} />
|
241 | );
|
242 | }
|
243 |
|
244 |
|
245 |
|
246 | getNode() {
|
247 | return this._component;
|
248 | }
|
249 | }
|
250 |
|
251 | AnimatedComponent.displayName = `AnimatedComponent(${Component.displayName ||
|
252 | Component.name ||
|
253 | 'Component'})`;
|
254 |
|
255 | return AnimatedComponent;
|
256 | }
|