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