Copy files from the react-native repo
Sebastian Markbage committed
Mar 24, 2016 at 15:40 UTC
f463b731ee5fce75ccf1df43d6174527016a0379
35 files changed
+4377
src/renderers/native/ReactIOS/IOSDefaultEventPluginOrder.js
new
+19
@@ -0,0 +1,19 @@
1
+/**
2
+ * Copyright (c) 2015-present, Facebook, Inc.
3
+ * All rights reserved.
4
+ *
5
+ * This source code is licensed under the BSD-style license found in the
6
+ * LICENSE file in the root directory of this source tree. An additional grant
7
+ * of patent rights can be found in the PATENTS file in the same directory.
8
+ *
9
+ * @providesModule IOSDefaultEventPluginOrder
10
+ * @flow
11
+ */
12
+'use strict';
13
+
14
+var IOSDefaultEventPluginOrder = [
15
+ 'ResponderEventPlugin',
16
+ 'IOSNativeBridgeEventPlugin'
17
+];
18
+
19
+module.exports = IOSDefaultEventPluginOrder;
src/renderers/native/ReactIOS/IOSNativeBridgeEventPlugin.js
new
+75
@@ -0,0 +1,75 @@
1
+/**
2
+ * Copyright (c) 2015-present, Facebook, Inc.
3
+ * All rights reserved.
4
+ *
5
+ * This source code is licensed under the BSD-style license found in the
6
+ * LICENSE file in the root directory of this source tree. An additional grant
7
+ * of patent rights can be found in the PATENTS file in the same directory.
8
+ *
9
+ * @providesModule IOSNativeBridgeEventPlugin
10
+ * @flow
11
+ */
12
+'use strict';
13
+
14
+var EventPropagators = require('EventPropagators');
15
+var SyntheticEvent = require('SyntheticEvent');
16
+var UIManager = require('UIManager');
17
+
18
+var merge = require('merge');
19
+var warning = require('fbjs/lib/warning');
20
+
21
+var customBubblingEventTypes = UIManager.customBubblingEventTypes;
22
+var customDirectEventTypes = UIManager.customDirectEventTypes;
23
+
24
+var allTypesByEventName = {};
25
+
26
+for (var bubblingTypeName in customBubblingEventTypes) {
27
+ allTypesByEventName[bubblingTypeName] = customBubblingEventTypes[bubblingTypeName];
28
+}
29
+
30
+for (var directTypeName in customDirectEventTypes) {
31
+ warning(
32
+ !customBubblingEventTypes[directTypeName],
33
+ 'Event cannot be both direct and bubbling: %s',
34
+ directTypeName
35
+ );
36
+ allTypesByEventName[directTypeName] = customDirectEventTypes[directTypeName];
37
+}
38
+
39
+var IOSNativeBridgeEventPlugin = {
40
+
41
+ eventTypes: merge(customBubblingEventTypes, customDirectEventTypes),
42
+
43
+ /**
44
+ * @param {string} topLevelType Record from `EventConstants`.
45
+ * @param {DOMEventTarget} topLevelTarget The listening component root node.
46
+ * @param {string} topLevelTargetID ID of `topLevelTarget`.
47
+ * @param {object} nativeEvent Native browser event.
48
+ * @return {*} An accumulation of synthetic events.
49
+ * @see {EventPluginHub.extractEvents}
50
+ */
51
+ extractEvents: function(
52
+ topLevelType: string,
53
+ topLevelTarget: EventTarget,
54
+ topLevelTargetID: string,
55
+ nativeEvent: Event
56
+ ): ?Object {
57
+ var bubbleDispatchConfig = customBubblingEventTypes[topLevelType];
58
+ var directDispatchConfig = customDirectEventTypes[topLevelType];
59
+ var event = SyntheticEvent.getPooled(
60
+ bubbleDispatchConfig || directDispatchConfig,
61
+ topLevelTargetID,
62
+ nativeEvent
63
+ );
64
+ if (bubbleDispatchConfig) {
65
+ EventPropagators.accumulateTwoPhaseDispatches(event);
66
+ } else if (directDispatchConfig) {
67
+ EventPropagators.accumulateDirectDispatches(event);
68
+ } else {
69
+ return null;
70
+ }
71
+ return event;
72
+ }
73
+};
74
+
75
+module.exports = IOSNativeBridgeEventPlugin;
src/renderers/native/ReactIOS/NativeMethodsMixin.js
new
+219
@@ -0,0 +1,219 @@
1
+/**
2
+ * Copyright (c) 2015-present, Facebook, Inc.
3
+ * All rights reserved.
4
+ *
5
+ * This source code is licensed under the BSD-style license found in the
6
+ * LICENSE file in the root directory of this source tree. An additional grant
7
+ * of patent rights can be found in the PATENTS file in the same directory.
8
+ *
9
+ * @providesModule NativeMethodsMixin
10
+ * @flow
11
+ */
12
+'use strict';
13
+
14
+var ReactNativeAttributePayload = require('ReactNativeAttributePayload');
15
+var TextInputState = require('TextInputState');
16
+var UIManager = require('UIManager');
17
+
18
+var findNodeHandle = require('findNodeHandle');
19
+var invariant = require('fbjs/lib/invariant');
20
+
21
+type MeasureOnSuccessCallback = (
22
+ x: number,
23
+ y: number,
24
+ width: number,
25
+ height: number,
26
+ pageX: number,
27
+ pageY: number
28
+) => void
29
+
30
+type MeasureInWindowOnSuccessCallback = (
31
+ x: number,
32
+ y: number,
33
+ width: number,
34
+ height: number,
35
+) => void
36
+
37
+type MeasureLayoutOnSuccessCallback = (
38
+ left: number,
39
+ top: number,
40
+ width: number,
41
+ height: number
42
+) => void
43
+
44
+function warnForStyleProps(props, validAttributes) {
45
+ for (var key in validAttributes.style) {
46
+ if (!(validAttributes[key] || props[key] === undefined)) {
47
+ console.error(
48
+ 'You are setting the style `{ ' + key + ': ... }` as a prop. You ' +
49
+ 'should nest it in a style object. ' +
50
+ 'E.g. `{ style: { ' + key + ': ... } }`'
51
+ );
52
+ }
53
+ }
54
+}
55
+
56
+/**
57
+ * `NativeMethodsMixin` provides methods to access the underlying native
58
+ * component directly. This can be useful in cases when you want to focus
59
+ * a view or measure its on-screen dimensions, for example.
60
+ *
61
+ * The methods described here are available on most of the default components
62
+ * provided by React Native. Note, however, that they are *not* available on
63
+ * composite components that aren't directly backed by a native view. This will
64
+ * generally include most components that you define in your own app. For more
65
+ * information, see [Direct
66
+ * Manipulation](docs/direct-manipulation.html).
67
+ */
68
+var NativeMethodsMixin = {
69
+ /**
70
+ * Determines the location on screen, width, and height of the given view and
71
+ * returns the values via an async callback. If successful, the callback will
72
+ * be called with the following arguments:
73
+ *
74
+ * - x
75
+ * - y
76
+ * - width
77
+ * - height
78
+ * - pageX
79
+ * - pageY
80
+ *
81
+ * Note that these measurements are not available until after the rendering
82
+ * has been completed in native. If you need the measurements as soon as
83
+ * possible, consider using the [`onLayout`
84
+ * prop](docs/view.html#onlayout) instead.
85
+ */
86
+ measure: function(callback: MeasureOnSuccessCallback) {
87
+ UIManager.measure(
88
+ findNodeHandle(this),
89
+ mountSafeCallback(this, callback)
90
+ );
91
+ },
92
+
93
+ /**
94
+ * Determines the location of the given view in the window and returns the
95
+ * values via an async callback. If the React root view is embedded in
96
+ * another native view, this will give you the absolute coordinates. If
97
+ * successful, the callback will be called with the following
98
+ * arguments:
99
+ *
100
+ * - x
101
+ * - y
102
+ * - width
103
+ * - height
104
+ *
105
+ * Note that these measurements are not available until after the rendering
106
+ * has been completed in native.
107
+ */
108
+ measureInWindow: function(callback: MeasureInWindowOnSuccessCallback) {
109
+ UIManager.measureInWindow(
110
+ findNodeHandle(this),
111
+ mountSafeCallback(this, callback)
112
+ );
113
+ },
114
+
115
+ /**
116
+ * Like [`measure()`](#measure), but measures the view relative an ancestor,
117
+ * specified as `relativeToNativeNode`. This means that the returned x, y
118
+ * are relative to the origin x, y of the ancestor view.
119
+ *
120
+ * As always, to obtain a native node handle for a component, you can use
121
+ * `React.findNodeHandle(component)`.
122
+ */
123
+ measureLayout: function(
124
+ relativeToNativeNode: number,
125
+ onSuccess: MeasureLayoutOnSuccessCallback,
126
+ onFail: () => void /* currently unused */
127
+ ) {
128
+ UIManager.measureLayout(
129
+ findNodeHandle(this),
130
+ relativeToNativeNode,
131
+ mountSafeCallback(this, onFail),
132
+ mountSafeCallback(this, onSuccess)
133
+ );
134
+ },
135
+
136
+ /**
137
+ * This function sends props straight to native. They will not participate in
138
+ * future diff process - this means that if you do not include them in the
139
+ * next render, they will remain active (see [Direct
140
+ * Manipulation](docs/direct-manipulation.html)).
141
+ */
142
+ setNativeProps: function(nativeProps: Object) {
143
+ if (__DEV__) {
144
+ warnForStyleProps(nativeProps, this.viewConfig.validAttributes);
145
+ }
146
+
147
+ var updatePayload = ReactNativeAttributePayload.create(
148
+ nativeProps,
149
+ this.viewConfig.validAttributes
150
+ );
151
+
152
+ UIManager.updateView(
153
+ findNodeHandle(this),
154
+ this.viewConfig.uiViewClassName,
155
+ updatePayload
156
+ );
157
+ },
158
+
159
+ /**
160
+ * Requests focus for the given input or view. The exact behavior triggered
161
+ * will depend on the platform and type of view.
162
+ */
163
+ focus: function() {
164
+ TextInputState.focusTextInput(findNodeHandle(this));
165
+ },
166
+
167
+ /**
168
+ * Removes focus from an input or view. This is the opposite of `focus()`.
169
+ */
170
+ blur: function() {
171
+ TextInputState.blurTextInput(findNodeHandle(this));
172
+ }
173
+};
174
+
175
+function throwOnStylesProp(component, props) {
176
+ if (props.styles !== undefined) {
177
+ var owner = component._owner || null;
178
+ var name = component.constructor.displayName;
179
+ var msg = '`styles` is not a supported property of `' + name + '`, did ' +
180
+ 'you mean `style` (singular)?';
181
+ if (owner && owner.constructor && owner.constructor.displayName) {
182
+ msg += '\n\nCheck the `' + owner.constructor.displayName + '` parent ' +
183
+ ' component.';
184
+ }
185
+ throw new Error(msg);
186
+ }
187
+}
188
+if (__DEV__) {
189
+ // hide this from Flow since we can't define these properties outside of
190
+ // __DEV__ without actually implementing them (setting them to undefined
191
+ // isn't allowed by ReactClass)
192
+ var NativeMethodsMixin_DEV = (NativeMethodsMixin: any);
193
+ invariant(
194
+ !NativeMethodsMixin_DEV.componentWillMount &&
195
+ !NativeMethodsMixin_DEV.componentWillReceiveProps,
196
+ 'Do not override existing functions.'
197
+ );
198
+ NativeMethodsMixin_DEV.componentWillMount = function () {
199
+ throwOnStylesProp(this, this.props);
200
+ };
201
+ NativeMethodsMixin_DEV.componentWillReceiveProps = function (newProps) {
202
+ throwOnStylesProp(this, newProps);
203
+ };
204
+}
205
+
206
+/**
207
+ * In the future, we should cleanup callbacks by cancelling them instead of
208
+ * using this.
209
+ */
210
+var mountSafeCallback = function(context: ReactComponent, callback: ?Function): any {
211
+ return function() {
212
+ if (!callback || (context.isMounted && !context.isMounted())) {
213
+ return;
214
+ }
215
+ return callback.apply(context, arguments);
216
+ };
217
+};
218
+
219
+module.exports = NativeMethodsMixin;
src/renderers/native/ReactIOS/YellowBox.js
new
+364
@@ -0,0 +1,364 @@
1
+/**
2
+ * Copyright (c) 2015-present, Facebook, Inc.
3
+ * All rights reserved.
4
+ *
5
+ * This source code is licensed under the BSD-style license found in the
6
+ * LICENSE file in the root directory of this source tree. An additional grant
7
+ * of patent rights can be found in the PATENTS file in the same directory.
8
+ *
9
+ * @providesModule YellowBox
10
+ * @flow
11
+ */
12
+
13
+'use strict';
14
+
15
+const EventEmitter = require('EventEmitter');
16
+import type EmitterSubscription from 'EmitterSubscription';
17
+const Platform = require('Platform');
18
+const React = require('React');
19
+const StyleSheet = require('StyleSheet');
20
+
21
+const _warningEmitter = new EventEmitter();
22
+const _warningMap = new Map();
23
+
24
+/**
25
+ * YellowBox renders warnings at the bottom of the app being developed.
26
+ *
27
+ * Warnings help guard against subtle yet significant issues that can impact the
28
+ * quality of the app. This "in your face" style of warning allows developers to
29
+ * notice and correct these issues as quickly as possible.
30
+ *
31
+ * By default, the warning box is enabled in `__DEV__`. Set the following flag
32
+ * to disable it (and call `console.warn` to update any rendered <YellowBox>):
33
+ *
34
+ * console.disableYellowBox = true;
35
+ * console.warn('YellowBox is disabled.');
36
+ *
37
+ * Warnings can be ignored programmatically by setting the array:
38
+ *
39
+ * console.ignoredYellowBox = ['Warning: ...'];
40
+ *
41
+ * Strings in `console.ignoredYellowBox` can be a prefix of the warning that
42
+ * should be ignored.
43
+ */
44
+
45
+if (__DEV__) {
46
+ const {error, warn} = console;
47
+ console.error = function() {
48
+ error.apply(console, arguments);
49
+ // Show yellow box for the `warning` module.
50
+ if (typeof arguments[0] === 'string' &&
51
+ arguments[0].startsWith('Warning: ')) {
52
+ updateWarningMap.apply(null, arguments);
53
+ }
54
+ };
55
+ console.warn = function() {
56
+ warn.apply(console, arguments);
57
+ updateWarningMap.apply(null, arguments);
58
+ };
59
+}
60
+
61
+/**
62
+ * Simple function for formatting strings.
63
+ *
64
+ * Replaces placeholders with values passed as extra arguments
65
+ *
66
+ * @param {string} format the base string
67
+ * @param ...args the values to insert
68
+ * @return {string} the replaced string
69
+ */
70
+function sprintf(format, ...args) {
71
+ var index = 0;
72
+ return format.replace(/%s/g, match => args[index++]);
73
+}
74
+
75
+function updateWarningMap(format, ...args): void {
76
+ const stringifySafe = require('stringifySafe');
77
+
78
+ format = String(format);
79
+ const argCount = (format.match(/%s/g) || []).length;
80
+ const warning = [
81
+ sprintf(format, ...args.slice(0, argCount)),
82
+ ...args.slice(argCount).map(stringifySafe),
83
+ ].join(' ');
84
+
85
+ const count = _warningMap.has(warning) ? _warningMap.get(warning) : 0;
86
+ _warningMap.set(warning, count + 1);
87
+ _warningEmitter.emit('warning', _warningMap);
88
+}
89
+
90
+function isWarningIgnored(warning: string): boolean {
91
+ return (
92
+ Array.isArray(console.ignoredYellowBox) &&
93
+ console.ignoredYellowBox.some(
94
+ ignorePrefix => warning.startsWith(ignorePrefix)
95
+ )
96
+ );
97
+}
98
+
99
+const WarningRow = ({count, warning, onPress}) => {
100
+ const Text = require('Text');
101
+ const TouchableHighlight = require('TouchableHighlight');
102
+ const View = require('View');
103
+
104
+ const countText = count > 1 ?
105
+ <Text style={styles.listRowCount}>{'(' + count + ') '}</Text> :
106
+ null;
107
+
108
+ return (
109
+ <View style={styles.listRow}>
110
+ <TouchableHighlight
111
+ activeOpacity={0.5}
112
+ onPress={onPress}
113
+ style={styles.listRowContent}
114
+ underlayColor="transparent">
115
+ <Text style={styles.listRowText} numberOfLines={2}>
116
+ {countText}
117
+ {warning}
118
+ </Text>
119
+ </TouchableHighlight>
120
+ </View>
121
+ );
122
+};
123
+
124
+const WarningInspector = ({
125
+ count,
126
+ warning,
127
+ onClose,
128
+ onDismiss,
129
+ onDismissAll,
130
+}) => {
131
+ const ScrollView = require('ScrollView');
132
+ const Text = require('Text');
133
+ const TouchableHighlight = require('TouchableHighlight');
134
+ const View = require('View');
135
+
136
+ const countSentence =
137
+ 'Warning encountered ' + count + ' time' + (count - 1 ? 's' : '') + '.';
138
+
139
+ return (
140
+ <TouchableHighlight
141
+ activeOpacity={0.95}
142
+ underlayColor={backgroundColor(0.8)}
143
+ onPress={onClose}
144
+ style={styles.inspector}>
145
+ <View style={styles.inspectorContent}>
146
+ <View style={styles.inspectorCount}>
147
+ <Text style={styles.inspectorCountText}>{countSentence}</Text>
148
+ </View>
149
+ <ScrollView style={styles.inspectorWarning}>
150
+ <Text style={styles.inspectorWarningText}>{warning}</Text>
151
+ </ScrollView>
152
+ <View style={styles.inspectorButtons}>
153
+ <TouchableHighlight
154
+ activeOpacity={0.5}
155
+ onPress={onDismiss}
156
+ style={styles.inspectorButton}
157
+ underlayColor="transparent">
158
+ <Text style={styles.inspectorButtonText}>
159
+ Dismiss
160
+ </Text>
161
+ </TouchableHighlight>
162
+ <TouchableHighlight
163
+ activeOpacity={0.5}
164
+ onPress={onDismissAll}
165
+ style={styles.inspectorButton}
166
+ underlayColor="transparent">
167
+ <Text style={styles.inspectorButtonText}>
168
+ Dismiss All
169
+ </Text>
170
+ </TouchableHighlight>
171
+ </View>
172
+ </View>
173
+ </TouchableHighlight>
174
+ );
175
+};
176
+
177
+class YellowBox extends React.Component {
178
+ state: {
179
+ inspecting: ?string;
180
+ warningMap: Map;
181
+ };
182
+ _listener: ?EmitterSubscription;
183
+
184
+ constructor(props: mixed, context: mixed) {
185
+ super(props, context);
186
+ this.state = {
187
+ inspecting: null,
188
+ warningMap: _warningMap,
189
+ };
190
+ this.dismissWarning = warning => {
191
+ const {inspecting, warningMap} = this.state;
192
+ if (warning) {
193
+ warningMap.delete(warning);
194
+ } else {
195
+ warningMap.clear();
196
+ }
197
+ this.setState({
198
+ inspecting: (warning && inspecting !== warning) ? inspecting : null,
199
+ warningMap,
200
+ });
201
+ };
202
+ }
203
+
204
+ componentDidMount() {
205
+ let scheduled = null;
206
+ this._listener = _warningEmitter.addListener('warning', warningMap => {
207
+ // Use `setImmediate` because warnings often happen during render, but
208
+ // state cannot be set while rendering.
209
+ scheduled = scheduled || setImmediate(() => {
210
+ scheduled = null;
211
+ this.setState({
212
+ warningMap,
213
+ });
214
+ });
215
+ });
216
+ }
217
+
218
+ componentWillUnmount() {
219
+ if (this._listener) {
220
+ this._listener.remove();
221
+ }
222
+ }
223
+
224
+ render() {
225
+ if (console.disableYellowBox || this.state.warningMap.size === 0) {
226
+ return null;
227
+ }
228
+ const ScrollView = require('ScrollView');
229
+ const View = require('View');
230
+
231
+ const inspecting = this.state.inspecting;
232
+ const inspector = inspecting !== null ?
233
+ <WarningInspector
234
+ count={this.state.warningMap.get(inspecting)}
235
+ warning={inspecting}
236
+ onClose={() => this.setState({inspecting: null})}
237
+ onDismiss={() => this.dismissWarning(inspecting)}
238
+ onDismissAll={() => this.dismissWarning(null)}
239
+ /> :
240
+ null;
241
+
242
+ const rows = [];
243
+ this.state.warningMap.forEach((count, warning) => {
244
+ if (!isWarningIgnored(warning)) {
245
+ rows.push(
246
+ <WarningRow
247
+ key={warning}
248
+ count={count}
249
+ warning={warning}
250
+ onPress={() => this.setState({inspecting: warning})}
251
+ onDismiss={() => this.dismissWarning(warning)}
252
+ />
253
+ );
254
+ }
255
+ });
256
+
257
+ const listStyle = [
258
+ styles.list,
259
+ // Additional `0.4` so the 5th row can peek into view.
260
+ {height: Math.min(rows.length, 4.4) * (rowGutter + rowHeight)},
261
+ ];
262
+ return (
263
+ <View style={inspector ? styles.fullScreen : listStyle}>
264
+ <ScrollView style={listStyle} scrollsToTop={false}>
265
+ {rows}
266
+ </ScrollView>
267
+ {inspector}
268
+ </View>
269
+ );
270
+ }
271
+}
272
+
273
+const backgroundColor = opacity => 'rgba(250, 186, 48, ' + opacity + ')';
274
+const textColor = 'white';
275
+const rowGutter = 1;
276
+const rowHeight = 46;
277
+
278
+var styles = StyleSheet.create({
279
+ fullScreen: {
280
+ backgroundColor: 'transparent',
281
+ position: 'absolute',
282
+ left: 0,
283
+ right: 0,
284
+ top: 0,
285
+ bottom: 0,
286
+ },
287
+ inspector: {
288
+ backgroundColor: backgroundColor(0.95),
289
+ flex: 1,
290
+ },
291
+ inspectorContainer: {
292
+ flex: 1,
293
+ },
294
+ inspectorButtons: {
295
+ flexDirection: 'row',
296
+ position: 'absolute',
297
+ left: 0,
298
+ right: 0,
299
+ bottom: 0,
300
+ },
301
+ inspectorButton: {
302
+ flex: 1,
303
+ padding: 22,
304
+ },
305
+ inspectorButtonText: {
306
+ color: textColor,
307
+ fontSize: 14,
308
+ opacity: 0.8,
309
+ textAlign: 'center',
310
+ },
311
+ inspectorContent: {
312
+ flex: 1,
313
+ paddingTop: 5,
314
+ },
315
+ inspectorCount: {
316
+ padding: 15,
317
+ paddingBottom: 0,
318
+ },
319
+ inspectorCountText: {
320
+ color: textColor,
321
+ fontSize: 14,
322
+ },
323
+ inspectorWarning: {
324
+ padding: 15,
325
+ position: 'absolute',
326
+ top: 39,
327
+ bottom: 60,
328
+ },
329
+ inspectorWarningText: {
330
+ color: textColor,
331
+ fontSize: 16,
332
+ fontWeight: '600',
333
+ },
334
+ list: {
335
+ backgroundColor: 'transparent',
336
+ position: 'absolute',
337
+ left: 0,
338
+ right: 0,
339
+ bottom: 0,
340
+ },
341
+ listRow: {
342
+ position: 'relative',
343
+ backgroundColor: backgroundColor(0.95),
344
+ flex: 1,
345
+ height: rowHeight,
346
+ marginTop: rowGutter,
347
+ },
348
+ listRowContent: {
349
+ flex: 1,
350
+ },
351
+ listRowCount: {
352
+ color: 'rgba(255, 255, 255, 0.5)',
353
+ },
354
+ listRowText: {
355
+ color: textColor,
356
+ position: 'absolute',
357
+ left: 0,
358
+ top: Platform.OS === 'android' ? 5 : 7,
359
+ marginLeft: 15,
360
+ marginRight: 15,
361
+ },
362
+});
363
+
364
+module.exports = YellowBox;
src/renderers/native/ReactIOS/renderApplication.android.js
new
+139
@@ -0,0 +1,139 @@
1
+/**
2
+ * Copyright (c) 2015-present, Facebook, Inc.
3
+ * All rights reserved.
4
+ *
5
+ * This source code is licensed under the BSD-style license found in the
6
+ * LICENSE file in the root directory of this source tree. An additional grant
7
+ * of patent rights can be found in the PATENTS file in the same directory.
8
+ *
9
+ * @providesModule renderApplication
10
+ */
11
+
12
+'use strict';
13
+
14
+var Inspector = require('Inspector');
15
+var Portal = require('Portal');
16
+var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
17
+var React = require('React');
18
+var StyleSheet = require('StyleSheet');
19
+var Subscribable = require('Subscribable');
20
+var View = require('View');
21
+
22
+var invariant = require('fbjs/lib/invariant');
23
+
24
+var YellowBox = __DEV__ ? require('YellowBox') : null;
25
+
26
+// require BackAndroid so it sets the default handler that exits the app if no listeners respond
27
+require('BackAndroid');
28
+
29
+var AppContainer = React.createClass({
30
+ mixins: [Subscribable.Mixin],
31
+
32
+ getInitialState: function() {
33
+ return {
34
+ enabled: __DEV__,
35
+ inspectorVisible: false,
36
+ rootNodeHandle: null,
37
+ rootImportanceForAccessibility: 'auto',
38
+ };
39
+ },
40
+
41
+ toggleElementInspector: function() {
42
+ this.setState({
43
+ inspectorVisible: !this.state.inspectorVisible,
44
+ rootNodeHandle: React.findNodeHandle(this.refs.main),
45
+ });
46
+ },
47
+
48
+ componentDidMount: function() {
49
+ this.addListenerOn(
50
+ RCTDeviceEventEmitter,
51
+ 'toggleElementInspector',
52
+ this.toggleElementInspector
53
+ );
54
+
55
+ this._unmounted = false;
56
+ },
57
+
58
+ renderInspector: function() {
59
+ return this.state.inspectorVisible ?
60
+ <Inspector
61
+ rootTag={this.props.rootTag}
62
+ inspectedViewTag={this.state.rootNodeHandle}
63
+ /> :
64
+ null;
65
+ },
66
+
67
+ componentWillUnmount: function() {
68
+ this._unmounted = true;
69
+ },
70
+
71
+ setRootAccessibility: function(modalVisible) {
72
+ if (this._unmounted) {
73
+ return;
74
+ }
75
+
76
+ this.setState({
77
+ rootImportanceForAccessibility: modalVisible ? 'no-hide-descendants' : 'auto',
78
+ });
79
+ },
80
+
81
+ render: function() {
82
+ var RootComponent = this.props.rootComponent;
83
+ var appView =
84
+ <View
85
+ ref="main"
86
+ collapsable={!this.state.inspectorVisible}
87
+ style={styles.appContainer}>
88
+ <RootComponent
89
+ {...this.props.initialProps}
90
+ rootTag={this.props.rootTag}
91
+ importantForAccessibility={this.state.rootImportanceForAccessibility}/>
92
+ <Portal
93
+ onModalVisibilityChanged={this.setRootAccessibility}/>
94
+ </View>;
95
+ let yellowBox = null;
96
+ if (__DEV__) {
97
+ yellowBox = <YellowBox />;
98
+ }
99
+ return this.state.enabled ?
100
+ <View style={styles.appContainer}>
101
+ {appView}
102
+ {yellowBox}
103
+ {this.renderInspector()}
104
+ </View> :
105
+ appView;
106
+ }
107
+});
108
+
109
+function renderApplication<D, P, S>(
110
+ RootComponent: ReactClass<P>,
111
+ initialProps: P,
112
+ rootTag: any
113
+) {
114
+ invariant(
115
+ rootTag,
116
+ 'Expect to have a valid rootTag, instead got ', rootTag
117
+ );
118
+ React.render(
119
+ <AppContainer
120
+ rootComponent={RootComponent}
121
+ initialProps={initialProps}
122
+ rootTag={rootTag} />,
123
+ rootTag
124
+ );
125
+}
126
+
127
+var styles = StyleSheet.create({
128
+ // This is needed so the application covers the whole screen
129
+ // and therefore the contents of the Portal are not clipped.
130
+ appContainer: {
131
+ position: 'absolute',
132
+ left: 0,
133
+ top: 0,
134
+ right: 0,
135
+ bottom: 0,
136
+ },
137
+});
138
+
139
+module.exports = renderApplication;
src/renderers/native/ReactIOS/renderApplication.ios.js
new
+96
@@ -0,0 +1,96 @@
1
+/**
2
+ * Copyright (c) 2015-present, Facebook, Inc.
3
+ * All rights reserved.
4
+ *
5
+ * This source code is licensed under the BSD-style license found in the
6
+ * LICENSE file in the root directory of this source tree. An additional grant
7
+ * of patent rights can be found in the PATENTS file in the same directory.
8
+ *
9
+ * @providesModule renderApplication
10
+ * @noflow
11
+ */
12
+
13
+'use strict';
14
+
15
+var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
16
+var React = require('React');
17
+var StyleSheet = require('StyleSheet');
18
+var Subscribable = require('Subscribable');
19
+var View = require('View');
20
+
21
+var invariant = require('fbjs/lib/invariant');
22
+
23
+var Inspector = __DEV__ ? require('Inspector') : null;
24
+var YellowBox = __DEV__ ? require('YellowBox') : null;
25
+
26
+var AppContainer = React.createClass({
27
+ mixins: [Subscribable.Mixin],
28
+
29
+ getInitialState: function() {
30
+ return { inspector: null };
31
+ },
32
+
33
+ toggleElementInspector: function() {
34
+ var inspector = !__DEV__ || this.state.inspector
35
+ ? null
36
+ : <Inspector
37
+ rootTag={this.props.rootTag}
38
+ inspectedViewTag={React.findNodeHandle(this.refs.main)}
39
+ />;
40
+ this.setState({inspector});
41
+ },
42
+
43
+ componentDidMount: function() {
44
+ this.addListenerOn(
45
+ RCTDeviceEventEmitter,
46
+ 'toggleElementInspector',
47
+ this.toggleElementInspector
48
+ );
49
+ },
50
+
51
+ render: function() {
52
+ let yellowBox = null;
53
+ if (__DEV__) {
54
+ yellowBox = <YellowBox />;
55
+ }
56
+ return (
57
+ <View style={styles.appContainer}>
58
+ <View collapsible={false} style={styles.appContainer} ref="main">
59
+ {this.props.children}
60
+ </View>
61
+ {yellowBox}
62
+ {this.state.inspector}
63
+ </View>
64
+ );
65
+ }
66
+});
67
+
68
+function renderApplication<D, P, S>(
69
+ RootComponent: ReactClass<P>,
70
+ initialProps: P,
71
+ rootTag: any
72
+) {
73
+ invariant(
74
+ rootTag,
75
+ 'Expect to have a valid rootTag, instead got ', rootTag
76
+ );
77
+ /* eslint-disable jsx-no-undef-with-namespace */
78
+ React.render(
79
+ <AppContainer rootTag={rootTag}>
80
+ <RootComponent
81
+ {...initialProps}
82
+ rootTag={rootTag}
83
+ />
84
+ </AppContainer>,
85
+ rootTag
86
+ );
87
+ /* eslint-enable jsx-no-undef-with-namespace */
88
+}
89
+
90
+var styles = StyleSheet.create({
91
+ appContainer: {
92
+ flex: 1,
93
+ },
94
+});
95
+
96
+module.exports = renderApplication;
src/renderers/native/ReactIOS/requireNativeComponent.js
new
+127
@@ -0,0 +1,127 @@
1
+/**
2
+ * Copyright (c) 2015-present, Facebook, Inc.
3
+ * All rights reserved.
4
+ *
5
+ * This source code is licensed under the BSD-style license found in the
6
+ * LICENSE file in the root directory of this source tree. An additional grant
7
+ * of patent rights can be found in the PATENTS file in the same directory.
8
+ *
9
+ * @providesModule requireNativeComponent
10
+ * @flow
11
+ */
12
+'use strict';
13
+
14
+var ReactNativeStyleAttributes = require('ReactNativeStyleAttributes');
15
+var UIManager = require('UIManager');
16
+var UnimplementedView = require('UnimplementedView');
17
+
18
+var createReactNativeComponentClass = require('createReactNativeComponentClass');
19
+
20
+var insetsDiffer = require('insetsDiffer');
21
+var pointsDiffer = require('pointsDiffer');
22
+var matricesDiffer = require('matricesDiffer');
23
+var processColor = require('processColor');
24
+var resolveAssetSource = require('resolveAssetSource');
25
+var sizesDiffer = require('sizesDiffer');
26
+var verifyPropTypes = require('verifyPropTypes');
27
+var warning = require('fbjs/lib/warning');
28
+
29
+/**
30
+ * Used to create React components that directly wrap native component
31
+ * implementations. Config information is extracted from data exported from the
32
+ * UIManager module. You should also wrap the native component in a
33
+ * hand-written component with full propTypes definitions and other
34
+ * documentation - pass the hand-written component in as `componentInterface` to
35
+ * verify all the native props are documented via `propTypes`.
36
+ *
37
+ * If some native props shouldn't be exposed in the wrapper interface, you can
38
+ * pass null for `componentInterface` and call `verifyPropTypes` directly
39
+ * with `nativePropsToIgnore`;
40
+ *
41
+ * Common types are lined up with the appropriate prop differs with
42
+ * `TypeToDifferMap`. Non-scalar types not in the map default to `deepDiffer`.
43
+ */
44
+import type { ComponentInterface } from 'verifyPropTypes';
45
+
46
+function requireNativeComponent(
47
+ viewName: string,
48
+ componentInterface?: ?ComponentInterface,
49
+ extraConfig?: ?{nativeOnly?: Object},
50
+): Function {
51
+ var viewConfig = UIManager[viewName];
52
+ if (!viewConfig || !viewConfig.NativeProps) {
53
+ warning(false, 'Native component for "%s" does not exist', viewName);
54
+ return UnimplementedView;
55
+ }
56
+ var nativeProps = {
57
+ ...UIManager.RCTView.NativeProps,
58
+ ...viewConfig.NativeProps,
59
+ };
60
+ viewConfig.uiViewClassName = viewName;
61
+ viewConfig.validAttributes = {};
62
+ viewConfig.propTypes = componentInterface && componentInterface.propTypes;
63
+ for (var key in nativeProps) {
64
+ var useAttribute = false;
65
+ var attribute = {};
66
+
67
+ var differ = TypeToDifferMap[nativeProps[key]];
68
+ if (differ) {
69
+ attribute.diff = differ;
70
+ useAttribute = true;
71
+ }
72
+
73
+ var processor = TypeToProcessorMap[nativeProps[key]];
74
+ if (processor) {
75
+ attribute.process = processor;
76
+ useAttribute = true;
77
+ }
78
+
79
+ viewConfig.validAttributes[key] = useAttribute ? attribute : true;
80
+ }
81
+
82
+ // Unfortunately, the current set up puts the style properties on the top
83
+ // level props object. We also need to add the nested form for API
84
+ // compatibility. This allows these props on both the top level and the
85
+ // nested style level. TODO: Move these to nested declarations on the
86
+ // native side.
87
+ viewConfig.validAttributes.style = ReactNativeStyleAttributes;
88
+
89
+ if (__DEV__) {
90
+ componentInterface && verifyPropTypes(
91
+ componentInterface,
92
+ viewConfig,
93
+ extraConfig && extraConfig.nativeOnly
94
+ );
95
+ }
96
+ return createReactNativeComponentClass(viewConfig);
97
+}
98
+
99
+var TypeToDifferMap = {
100
+ // iOS Types
101
+ CATransform3D: matricesDiffer,
102
+ CGPoint: pointsDiffer,
103
+ CGSize: sizesDiffer,
104
+ UIEdgeInsets: insetsDiffer,
105
+ // Android Types
106
+ // (not yet implemented)
107
+};
108
+
109
+function processColorArray(colors: []): [] {
110
+ return colors && colors.map(processColor);
111
+}
112
+
113
+var TypeToProcessorMap = {
114
+ // iOS Types
115
+ CGColor: processColor,
116
+ CGColorArray: processColorArray,
117
+ UIColor: processColor,
118
+ UIColorArray: processColorArray,
119
+ CGImage: resolveAssetSource,
120
+ UIImage: resolveAssetSource,
121
+ RCTImageSource: resolveAssetSource,
122
+ // Android Types
123
+ Color: processColor,
124
+ ColorArray: processColorArray,
125
+};
126
+
127
+module.exports = requireNativeComponent;
src/renderers/native/ReactIOS/verifyPropTypes.js
new
+61
@@ -0,0 +1,61 @@
1
+/**
2
+ * Copyright (c) 2015-present, Facebook, Inc.
3
+ * All rights reserved.
4
+ *
5
+ * This source code is licensed under the BSD-style license found in the
6
+ * LICENSE file in the root directory of this source tree. An additional grant
7
+ * of patent rights can be found in the PATENTS file in the same directory.
8
+ *
9
+ * @providesModule verifyPropTypes
10
+ * @flow
11
+ */
12
+'use strict';
13
+
14
+var ReactNativeStyleAttributes = require('ReactNativeStyleAttributes');
15
+
16
+export type ComponentInterface = ReactClass<any> | {
17
+ name?: string;
18
+ displayName?: string;
19
+ propTypes: Object;
20
+};
21
+
22
+function verifyPropTypes(
23
+ componentInterface: ComponentInterface,
24
+ viewConfig: Object,
25
+ nativePropsToIgnore?: ?Object
26
+) {
27
+ if (!viewConfig) {
28
+ return; // This happens for UnimplementedView.
29
+ }
30
+ var componentName = componentInterface.name ||
31
+ componentInterface.displayName ||
32
+ 'unknown';
33
+ if (!componentInterface.propTypes) {
34
+ throw new Error(
35
+ '`' + componentName + '` has no propTypes defined`'
36
+ );
37
+ }
38
+
39
+ var nativeProps = viewConfig.NativeProps;
40
+ for (var prop in nativeProps) {
41
+ if (!componentInterface.propTypes[prop] &&
42
+ !ReactNativeStyleAttributes[prop] &&
43
+ (!nativePropsToIgnore || !nativePropsToIgnore[prop])) {
44
+ var message;
45
+ if (componentInterface.propTypes.hasOwnProperty(prop)) {
46
+ message = '`' + componentName + '` has incorrectly defined propType for native prop `' +
47
+ viewConfig.uiViewClassName + '.' + prop + '` of native type `' + nativeProps[prop];
48
+ } else {
49
+ message = '`' + componentName + '` has no propType for native prop `' +
50
+ viewConfig.uiViewClassName + '.' + prop + '` of native type `' +
51
+ nativeProps[prop] + '`';
52
+ };
53
+ message += '\nIf you haven\'t changed this prop yourself, this usually means that ' +
54
+ 'your versions of the native code and JavaScript code are out of sync. Updating both ' +
55
+ 'should make this error go away.';
56
+ throw new Error(message);
57
+ }
58
+ }
59
+}
60
+
61
+module.exports = verifyPropTypes;
src/renderers/native/ReactNative/React.js
new
+14
@@ -0,0 +1,14 @@
1
+/**
2
+ * Copyright (c) 2015-present, Facebook, Inc.
3
+ * All rights reserved.
4
+ *
5
+ * This source code is licensed under the BSD-style license found in the
6
+ * LICENSE file in the root directory of this source tree. An additional grant
7
+ * of patent rights can be found in the PATENTS file in the same directory.
8
+ *
9
+ * @providesModule React
10
+ * @flow
11
+ */
12
+'use strict';
13
+
14
+module.exports = require('ReactNative');
src/renderers/native/ReactNative/ReactDOM.js
new
+16
@@ -0,0 +1,16 @@
1
+/**
2
+ * Copyright 2004-present Facebook. All Rights Reserved.
3
+ *
4
+ * @providesModule ReactDOM
5
+ */
6
+
7
+'use strict';
8
+
9
+var ReactUpdates = require('ReactUpdates');
10
+
11
+// Temporary shim required for ReactTestUtils and Relay.
12
+var ReactDOM = {
13
+ unstable_batchedUpdates: ReactUpdates.batchedUpdates,
14
+};
15
+
16
+module.exports = ReactDOM;
src/renderers/native/ReactNative/ReactNative.js
new
+138
@@ -0,0 +1,138 @@
1
+/**
2
+ * Copyright (c) 2015-present, Facebook, Inc.
3
+ * All rights reserved.
4
+ *
5
+ * This source code is licensed under the BSD-style license found in the
6
+ * LICENSE file in the root directory of this source tree. An additional grant
7
+ * of patent rights can be found in the PATENTS file in the same directory.
8
+ *
9
+ * @providesModule ReactNative
10
+ * @flow
11
+ */
12
+'use strict';
13
+
14
+// Require ReactNativeDefaultInjection first for its side effects of setting up
15
+// the JS environment
16
+var ReactNativeDefaultInjection = require('ReactNativeDefaultInjection');
17
+
18
+var ReactChildren = require('ReactChildren');
19
+var ReactClass = require('ReactClass');
20
+var ReactComponent = require('ReactComponent');
21
+var ReactCurrentOwner = require('ReactCurrentOwner');
22
+var ReactElement = require('ReactElement');
23
+var ReactElementValidator = require('ReactElementValidator');
24
+var ReactInstanceHandles = require('ReactInstanceHandles');
25
+var ReactNativeMount = require('ReactNativeMount');
26
+var ReactPropTypes = require('ReactPropTypes');
27
+var ReactUpdates = require('ReactUpdates');
28
+
29
+var findNodeHandle = require('findNodeHandle');
30
+var invariant = require('fbjs/lib/invariant');
31
+var onlyChild = require('onlyChild');
32
+var warning = require('fbjs/lib/warning');
33
+
34
+ReactNativeDefaultInjection.inject();
35
+
36
+var createElement = ReactElement.createElement;
37
+var createFactory = ReactElement.createFactory;
38
+var cloneElement = ReactElement.cloneElement;
39
+
40
+if (__DEV__) {
41
+ createElement = ReactElementValidator.createElement;
42
+ createFactory = ReactElementValidator.createFactory;
43
+ cloneElement = ReactElementValidator.cloneElement;
44
+}
45
+
46
+var resolveDefaultProps = function(element) {
47
+ // Could be optimized, but not currently in heavy use.
48
+ var defaultProps = element.type.defaultProps;
49
+ var props = element.props;
50
+ for (var propName in defaultProps) {
51
+ if (props[propName] === undefined) {
52
+ props[propName] = defaultProps[propName];
53
+ }
54
+ }
55
+};
56
+
57
+// Experimental optimized element creation
58
+var augmentElement = function(element: ReactElement): ReactElement {
59
+ if (__DEV__) {
60
+ invariant(
61
+ false,
62
+ 'This optimized path should never be used in DEV mode because ' +
63
+ 'it does not provide validation. Check your JSX transform.'
64
+ );
65
+ }
66
+ element._owner = ReactCurrentOwner.current;
67
+ if (element.type.defaultProps) {
68
+ resolveDefaultProps(element);
69
+ }
70
+ return element;
71
+};
72
+
73
+var render = function(
74
+ element: ReactElement,
75
+ mountInto: number,
76
+ callback?: ?(() => void)
77
+): ?ReactComponent {
78
+ return ReactNativeMount.renderComponent(element, mountInto, callback);
79
+};
80
+
81
+var ReactNative = {
82
+ hasReactNativeInitialized: false,
83
+ Children: {
84
+ map: ReactChildren.map,
85
+ forEach: ReactChildren.forEach,
86
+ count: ReactChildren.count,
87
+ toArray: ReactChildren.toArray,
88
+ only: onlyChild
89
+ },
90
+ Component: ReactComponent,
91
+ PropTypes: ReactPropTypes,
92
+ createClass: ReactClass.createClass,
93
+ createElement: createElement,
94
+ createFactory: createFactory,
95
+ cloneElement: cloneElement,
96
+ _augmentElement: augmentElement,
97
+ findNodeHandle: findNodeHandle,
98
+ render: render,
99
+ unmountComponentAtNode: ReactNativeMount.unmountComponentAtNode,
100
+
101
+ /* eslint-disable camelcase */
102
+ unstable_batchedUpdates: ReactUpdates.batchedUpdates,
103
+ /* eslint-enable camelcase */
104
+
105
+ // Hook for JSX spread, don't use this for anything else.
106
+ __spread: Object.assign,
107
+
108
+ unmountComponentAtNodeAndRemoveContainer: ReactNativeMount.unmountComponentAtNodeAndRemoveContainer,
109
+ isValidClass: ReactElement.isValidFactory,
110
+ isValidElement: ReactElement.isValidElement,
111
+
112
+ // Deprecations (remove for 0.13)
113
+ renderComponent: function(
114
+ element: ReactElement,
115
+ mountInto: number,
116
+ callback?: ?(() => void)
117
+ ): ?ReactComponent {
118
+ warning('Use React.render instead of React.renderComponent');
119
+ return ReactNative.render(element, mountInto, callback);
120
+ },
121
+};
122
+
123
+// Inject the runtime into a devtools global hook regardless of browser.
124
+// Allows for debugging when the hook is injected on the page.
125
+/* globals __REACT_DEVTOOLS_GLOBAL_HOOK__ */
126
+if (
127
+ typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== 'undefined' &&
128
+ typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.inject === 'function') {
129
+ __REACT_DEVTOOLS_GLOBAL_HOOK__.inject({
130
+ CurrentOwner: ReactCurrentOwner,
131
+ InstanceHandles: ReactInstanceHandles,
132
+ Mount: ReactNativeMount,
133
+ Reconciler: require('ReactReconciler'),
134
+ TextComponent: require('ReactNativeTextComponent'),
135
+ });
136
+}
137
+
138
+module.exports = ReactNative;
src/renderers/native/ReactNative/ReactNativeAttributePayload.js
new
+546
@@ -0,0 +1,546 @@
1
+/**
2
+ * Copyright (c) 2015-present, Facebook, Inc.
3
+ * All rights reserved.
4
+ *
5
+ * This source code is licensed under the BSD-style license found in the
6
+ * LICENSE file in the root directory of this source tree. An additional grant
7
+ * of patent rights can be found in the PATENTS file in the same directory.
8
+ *
9
+ * @providesModule ReactNativeAttributePayload
10
+ * @flow
11
+ */
12
+'use strict';
13
+
14
+var Platform = require('Platform');
15
+var ReactNativePropRegistry = require('ReactNativePropRegistry');
16
+
17
+var deepDiffer = require('deepDiffer');
18
+var flattenStyle = require('flattenStyle');
19
+
20
+var emptyObject = {};
21
+
22
+/**
23
+ * Create a payload that contains all the updates between two sets of props.
24
+ *
25
+ * These helpers are all encapsulated into a single module, because they use
26
+ * mutation as a performance optimization which leads to subtle shared
27
+ * dependencies between the code paths. To avoid this mutable state leaking
28
+ * across modules, I've kept them isolated to this module.
29
+ */
30
+
31
+type AttributeDiffer = (prevProp: mixed, nextProp: mixed) => boolean;
32
+type AttributePreprocessor = (nextProp: mixed) => mixed;
33
+
34
+type CustomAttributeConfiguration =
35
+ { diff: AttributeDiffer, process: AttributePreprocessor } |
36
+ { diff: AttributeDiffer } |
37
+ { process: AttributePreprocessor };
38
+
39
+type AttributeConfiguration =
40
+ { [key: string]: (
41
+ CustomAttributeConfiguration | AttributeConfiguration /*| boolean*/
42
+ ) };
43
+
44
+type NestedNode = Array<NestedNode> | Object | number;
45
+
46
+// Tracks removed keys
47
+var removedKeys = null;
48
+var removedKeyCount = 0;
49
+
50
+function translateKey(propKey: string) : string {
51
+ if (propKey === 'transform') {
52
+ // We currently special case the key for `transform`. iOS uses the
53
+ // transformMatrix name and Android uses the decomposedMatrix name.
54
+ // TODO: We could unify these names and just use the name `transform`
55
+ // all the time. Just need to update the native side.
56
+ if (Platform.OS === 'android') {
57
+ return 'decomposedMatrix';
58
+ } else {
59
+ return 'transformMatrix';
60
+ }
61
+ }
62
+ return propKey;
63
+}
64
+
65
+function defaultDiffer(prevProp: mixed, nextProp: mixed) : boolean {
66
+ if (typeof nextProp !== 'object' || nextProp === null) {
67
+ // Scalars have already been checked for equality
68
+ return true;
69
+ } else {
70
+ // For objects and arrays, the default diffing algorithm is a deep compare
71
+ return deepDiffer(prevProp, nextProp);
72
+ }
73
+}
74
+
75
+function resolveObject(idOrObject: number | Object) : Object {
76
+ if (typeof idOrObject === 'number') {
77
+ return ReactNativePropRegistry.getByID(idOrObject);
78
+ }
79
+ return idOrObject;
80
+}
81
+
82
+function restoreDeletedValuesInNestedArray(
83
+ updatePayload: Object,
84
+ node: NestedNode,
85
+ validAttributes: AttributeConfiguration
86
+) {
87
+ if (Array.isArray(node)) {
88
+ var i = node.length;
89
+ while (i-- && removedKeyCount > 0) {
90
+ restoreDeletedValuesInNestedArray(
91
+ updatePayload,
92
+ node[i],
93
+ validAttributes
94
+ );
95
+ }
96
+ } else if (node && removedKeyCount > 0) {
97
+ var obj = resolveObject(node);
98
+ for (var propKey in removedKeys) {
99
+ if (!removedKeys[propKey]) {
100
+ continue;
101
+ }
102
+ var nextProp = obj[propKey];
103
+ if (nextProp === undefined) {
104
+ continue;
105
+ }
106
+
107
+ var attributeConfig = validAttributes[propKey];
108
+ if (!attributeConfig) {
109
+ continue; // not a valid native prop
110
+ }
111
+
112
+ if (typeof nextProp === 'function') {
113
+ nextProp = true;
114
+ }
115
+ if (typeof nextProp === 'undefined') {
116
+ nextProp = null;
117
+ }
118
+
119
+ if (typeof attributeConfig !== 'object') {
120
+ // case: !Object is the default case
121
+ updatePayload[propKey] = nextProp;
122
+ } else if (typeof attributeConfig.diff === 'function' ||
123
+ typeof attributeConfig.process === 'function') {
124
+ // case: CustomAttributeConfiguration
125
+ var nextValue = typeof attributeConfig.process === 'function' ?
126
+ attributeConfig.process(nextProp) :
127
+ nextProp;
128
+ updatePayload[propKey] = nextValue;
129
+ }
130
+ removedKeys[propKey] = false;
131
+ removedKeyCount--;
132
+ }
133
+ }
134
+}
135
+
136
+function diffNestedArrayProperty(
137
+ updatePayload:? Object,
138
+ prevArray: Array<NestedNode>,
139
+ nextArray: Array<NestedNode>,
140
+ validAttributes: AttributeConfiguration
141
+) : ?Object {
142
+ var minLength = prevArray.length < nextArray.length ?
143
+ prevArray.length :
144
+ nextArray.length;
145
+ var i;
146
+ for (i = 0; i < minLength; i++) {
147
+ // Diff any items in the array in the forward direction. Repeated keys
148
+ // will be overwritten by later values.
149
+ updatePayload = diffNestedProperty(
150
+ updatePayload,
151
+ prevArray[i],
152
+ nextArray[i],
153
+ validAttributes
154
+ );
155
+ }
156
+ for (; i < prevArray.length; i++) {
157
+ // Clear out all remaining properties.
158
+ updatePayload = clearNestedProperty(
159
+ updatePayload,
160
+ prevArray[i],
161
+ validAttributes
162
+ );
163
+ }
164
+ for (; i < nextArray.length; i++) {
165
+ // Add all remaining properties.
166
+ updatePayload = addNestedProperty(
167
+ updatePayload,
168
+ nextArray[i],
169
+ validAttributes
170
+ );
171
+ }
172
+ return updatePayload;
173
+}
174
+
175
+function diffNestedProperty(
176
+ updatePayload:? Object,
177
+ prevProp: NestedNode,
178
+ nextProp: NestedNode,
179
+ validAttributes: AttributeConfiguration
180
+) : ?Object {
181
+
182
+ if (!updatePayload && prevProp === nextProp) {
183
+ // If no properties have been added, then we can bail out quickly on object
184
+ // equality.
185
+ return updatePayload;
186
+ }
187
+
188
+ if (!prevProp || !nextProp) {
189
+ if (nextProp) {
190
+ return addNestedProperty(
191
+ updatePayload,
192
+ nextProp,
193
+ validAttributes
194
+ );
195
+ }
196
+ if (prevProp) {
197
+ return clearNestedProperty(
198
+ updatePayload,
199
+ prevProp,
200
+ validAttributes
201
+ );
202
+ }
203
+ return updatePayload;
204
+ }
205
+
206
+ if (!Array.isArray(prevProp) && !Array.isArray(nextProp)) {
207
+ // Both are leaves, we can diff the leaves.
208
+ return diffProperties(
209
+ updatePayload,
210
+ resolveObject(prevProp),
211
+ resolveObject(nextProp),
212
+ validAttributes
213
+ );
214
+ }
215
+
216
+ if (Array.isArray(prevProp) && Array.isArray(nextProp)) {
217
+ // Both are arrays, we can diff the arrays.
218
+ return diffNestedArrayProperty(
219
+ updatePayload,
220
+ prevProp,
221
+ nextProp,
222
+ validAttributes
223
+ );
224
+ }
225
+
226
+ if (Array.isArray(prevProp)) {
227
+ return diffProperties(
228
+ updatePayload,
229
+ // $FlowFixMe - We know that this is always an object when the input is.
230
+ flattenStyle(prevProp),
231
+ // $FlowFixMe - We know that this isn't an array because of above flow.
232
+ resolveObject(nextProp),
233
+ validAttributes
234
+ );
235
+ }
236
+
237
+ return diffProperties(
238
+ updatePayload,
239
+ resolveObject(prevProp),
240
+ // $FlowFixMe - We know that this is always an object when the input is.
241
+ flattenStyle(nextProp),
242
+ validAttributes
243
+ );
244
+}
245
+
246
+/**
247
+ * addNestedProperty takes a single set of props and valid attribute
248
+ * attribute configurations. It processes each prop and adds it to the
249
+ * updatePayload.
250
+ */
251
+function addNestedProperty(
252
+ updatePayload:? Object,
253
+ nextProp: NestedNode,
254
+ validAttributes: AttributeConfiguration
255
+) {
256
+ if (!nextProp) {
257
+ return updatePayload;
258
+ }
259
+
260
+ if (!Array.isArray(nextProp)) {
261
+ // Add each property of the leaf.
262
+ return addProperties(
263
+ updatePayload,
264
+ resolveObject(nextProp),
265
+ validAttributes
266
+ );
267
+ }
268
+
269
+ for (var i = 0; i < nextProp.length; i++) {
270
+ // Add all the properties of the array.
271
+ updatePayload = addNestedProperty(
272
+ updatePayload,
273
+ nextProp[i],
274
+ validAttributes
275
+ );
276
+ }
277
+
278
+ return updatePayload;
279
+}
280
+
281
+/**
282
+ * clearNestedProperty takes a single set of props and valid attributes. It
283
+ * adds a null sentinel to the updatePayload, for each prop key.
284
+ */
285
+function clearNestedProperty(
286
+ updatePayload:? Object,
287
+ prevProp: NestedNode,
288
+ validAttributes: AttributeConfiguration
289
+) : ?Object {
290
+ if (!prevProp) {
291
+ return updatePayload;
292
+ }
293
+
294
+ if (!Array.isArray(prevProp)) {
295
+ // Add each property of the leaf.
296
+ return clearProperties(
297
+ updatePayload,
298
+ resolveObject(prevProp),
299
+ validAttributes
300
+ );
301
+ }
302
+
303
+ for (var i = 0; i < prevProp.length; i++) {
304
+ // Add all the properties of the array.
305
+ updatePayload = clearNestedProperty(
306
+ updatePayload,
307
+ prevProp[i],
308
+ validAttributes
309
+ );
310
+ }
311
+ return updatePayload;
312
+}
313
+
314
+/**
315
+ * diffProperties takes two sets of props and a set of valid attributes
316
+ * and write to updatePayload the values that changed or were deleted.
317
+ * If no updatePayload is provided, a new one is created and returned if
318
+ * anything changed.
319
+ */
320
+function diffProperties(
321
+ updatePayload: ?Object,
322
+ prevProps: Object,
323
+ nextProps: Object,
324
+ validAttributes: AttributeConfiguration
325
+): ?Object {
326
+ var attributeConfig : ?(CustomAttributeConfiguration | AttributeConfiguration);
327
+ var nextProp;
328
+ var prevProp;
329
+ var altKey;
330
+
331
+ for (var propKey in nextProps) {
332
+ attributeConfig = validAttributes[propKey];
333
+ if (!attributeConfig) {
334
+ continue; // not a valid native prop
335
+ }
336
+
337
+ altKey = translateKey(propKey);
338
+ if (!validAttributes[altKey]) {
339
+ // If there is no config for the alternative, bail out. Helps ART.
340
+ altKey = propKey;
341
+ }
342
+
343
+ prevProp = prevProps[propKey];
344
+ nextProp = nextProps[propKey];
345
+
346
+ // functions are converted to booleans as markers that the associated
347
+ // events should be sent from native.
348
+ if (typeof nextProp === 'function') {
349
+ nextProp = (true : any);
350
+ // If nextProp is not a function, then don't bother changing prevProp
351
+ // since nextProp will win and go into the updatePayload regardless.
352
+ if (typeof prevProp === 'function') {
353
+ prevProp = (true : any);
354
+ }
355
+ }
356
+
357
+ // An explicit value of undefined is treated as a null because it overrides
358
+ // any other preceeding value.
359
+ if (typeof nextProp === 'undefined') {
360
+ nextProp = (null : any);
361
+ if (typeof prevProp === 'undefined') {
362
+ prevProp = (null : any);
363
+ }
364
+ }
365
+
366
+ if (removedKeys) {
367
+ removedKeys[propKey] = false;
368
+ }
369
+
370
+ if (updatePayload && updatePayload[altKey] !== undefined) {
371
+ // Something else already triggered an update to this key because another
372
+ // value diffed. Since we're now later in the nested arrays our value is
373
+ // more important so we need to calculate it and override the existing
374
+ // value. It doesn't matter if nothing changed, we'll set it anyway.
375
+
376
+ // Pattern match on: attributeConfig
377
+ if (typeof attributeConfig !== 'object') {
378
+ // case: !Object is the default case
379
+ updatePayload[altKey] = nextProp;
380
+ } else if (typeof attributeConfig.diff === 'function' ||
381
+ typeof attributeConfig.process === 'function') {
382
+ // case: CustomAttributeConfiguration
383
+ var nextValue = typeof attributeConfig.process === 'function' ?
384
+ attributeConfig.process(nextProp) :
385
+ nextProp;
386
+ updatePayload[altKey] = nextValue;
387
+ }
388
+ continue;
389
+ }
390
+
391
+ if (prevProp === nextProp) {
392
+ continue; // nothing changed
393
+ }
394
+
395
+ // Pattern match on: attributeConfig
396
+ if (typeof attributeConfig !== 'object') {
397
+ // case: !Object is the default case
398
+ if (defaultDiffer(prevProp, nextProp)) {
399
+ // a normal leaf has changed
400
+ (updatePayload || (updatePayload = {}))[altKey] = nextProp;
401
+ }
402
+ } else if (typeof attributeConfig.diff === 'function' ||
403
+ typeof attributeConfig.process === 'function') {
404
+ // case: CustomAttributeConfiguration
405
+ var shouldUpdate = prevProp === undefined || (
406
+ typeof attributeConfig.diff === 'function' ?
407
+ attributeConfig.diff(prevProp, nextProp) :
408
+ defaultDiffer(prevProp, nextProp)
409
+ );
410
+ if (shouldUpdate) {
411
+ var nextValue = typeof attributeConfig.process === 'function' ?
412
+ attributeConfig.process(nextProp) :
413
+ nextProp;
414
+ (updatePayload || (updatePayload = {}))[altKey] = nextValue;
415
+ }
416
+ } else {
417
+ // default: fallthrough case when nested properties are defined
418
+ removedKeys = null;
419
+ removedKeyCount = 0;
420
+ updatePayload = diffNestedProperty(
421
+ updatePayload,
422
+ prevProp,
423
+ nextProp,
424
+ attributeConfig
425
+ );
426
+ if (removedKeyCount > 0 && updatePayload) {
427
+ restoreDeletedValuesInNestedArray(
428
+ updatePayload,
429
+ nextProp,
430
+ attributeConfig
431
+ );
432
+ removedKeys = null;
433
+ }
434
+ }
435
+ }
436
+
437
+ // Also iterate through all the previous props to catch any that have been
438
+ // removed and make sure native gets the signal so it can reset them to the
439
+ // default.
440
+ for (var propKey in prevProps) {
441
+ if (nextProps[propKey] !== undefined) {
442
+ continue; // we've already covered this key in the previous pass
443
+ }
444
+ attributeConfig = validAttributes[propKey];
445
+ if (!attributeConfig) {
446
+ continue; // not a valid native prop
447
+ }
448
+
449
+ altKey = translateKey(propKey);
450
+ if (!attributeConfig[altKey]) {
451
+ // If there is no config for the alternative, bail out. Helps ART.
452
+ altKey = propKey;
453
+ }
454
+
455
+ if (updatePayload && updatePayload[altKey] !== undefined) {
456
+ // This was already updated to a diff result earlier.
457
+ continue;
458
+ }
459
+
460
+ prevProp = prevProps[propKey];
461
+ if (prevProp === undefined) {
462
+ continue; // was already empty anyway
463
+ }
464
+ // Pattern match on: attributeConfig
465
+ if (typeof attributeConfig !== 'object' ||
466
+ typeof attributeConfig.diff === 'function' ||
467
+ typeof attributeConfig.process === 'function') {
468
+
469
+ // case: CustomAttributeConfiguration | !Object
470
+ // Flag the leaf property for removal by sending a sentinel.
471
+ (updatePayload || (updatePayload = {}))[altKey] = null;
472
+ if (!removedKeys) {
473
+ removedKeys = {};
474
+ }
475
+ if (!removedKeys[propKey]) {
476
+ removedKeys[propKey] = true;
477
+ removedKeyCount++;
478
+ }
479
+ } else {
480
+ // default:
481
+ // This is a nested attribute configuration where all the properties
482
+ // were removed so we need to go through and clear out all of them.
483
+ updatePayload = clearNestedProperty(
484
+ updatePayload,
485
+ prevProp,
486
+ attributeConfig
487
+ );
488
+ }
489
+ }
490
+ return updatePayload;
491
+}
492
+
493
+/**
494
+ * addProperties adds all the valid props to the payload after being processed.
495
+ */
496
+function addProperties(
497
+ updatePayload: ?Object,
498
+ props: Object,
499
+ validAttributes: AttributeConfiguration
500
+) : ?Object {
501
+ // TODO: Fast path
502
+ return diffProperties(updatePayload, emptyObject, props, validAttributes);
503
+}
504
+
505
+/**
506
+ * clearProperties clears all the previous props by adding a null sentinel
507
+ * to the payload for each valid key.
508
+ */
509
+function clearProperties(
510
+ updatePayload: ?Object,
511
+ prevProps: Object,
512
+ validAttributes: AttributeConfiguration
513
+) :? Object {
514
+ // TODO: Fast path
515
+ return diffProperties(updatePayload, prevProps, emptyObject, validAttributes);
516
+}
517
+
518
+var ReactNativeAttributePayload = {
519
+
520
+ create: function(
521
+ props: Object,
522
+ validAttributes: AttributeConfiguration
523
+ ) : ?Object {
524
+ return addProperties(
525
+ null, // updatePayload
526
+ props,
527
+ validAttributes
528
+ );
529
+ },
530
+
531
+ diff: function(
532
+ prevProps: Object,
533
+ nextProps: Object,
534
+ validAttributes: AttributeConfiguration
535
+ ) : ?Object {
536
+ return diffProperties(
537
+ null, // updatePayload
538
+ prevProps,
539
+ nextProps,
540
+ validAttributes
541
+ );
542
+ }
543
+
544
+};
545
+
546
+module.exports = ReactNativeAttributePayload;
src/renderers/native/ReactNative/ReactNativeBaseComponent.js
new
+239
@@ -0,0 +1,239 @@
1
+/**
2
+ * Copyright (c) 2015-present, Facebook, Inc.
3
+ * All rights reserved.
4
+ *
5
+ * This source code is licensed under the BSD-style license found in the
6
+ * LICENSE file in the root directory of this source tree. An additional grant
7
+ * of patent rights can be found in the PATENTS file in the same directory.
8
+ *
9
+ * @providesModule ReactNativeBaseComponent
10
+ * @flow
11
+ */
12
+'use strict';
13
+
14
+var NativeMethodsMixin = require('NativeMethodsMixin');
15
+var ReactNativeAttributePayload = require('ReactNativeAttributePayload');
16
+var ReactNativeEventEmitter = require('ReactNativeEventEmitter');
17
+var ReactNativeTagHandles = require('ReactNativeTagHandles');
18
+var ReactMultiChild = require('ReactMultiChild');
19
+var UIManager = require('UIManager');
20
+
21
+var deepFreezeAndThrowOnMutationInDev = require('deepFreezeAndThrowOnMutationInDev');
22
+var invariant = require('fbjs/lib/invariant');
23
+var warning = require('fbjs/lib/warning');
24
+
25
+var registrationNames = ReactNativeEventEmitter.registrationNames;
26
+var putListener = ReactNativeEventEmitter.putListener;
27
+var deleteListener = ReactNativeEventEmitter.deleteListener;
28
+var deleteAllListeners = ReactNativeEventEmitter.deleteAllListeners;
29
+
30
+type ReactNativeBaseComponentViewConfig = {
31
+ validAttributes: Object;
32
+ uiViewClassName: string;
33
+}
34
+
35
+// require('UIManagerStatTracker').install(); // uncomment to enable
36
+
37
+/**
38
+ * @constructor ReactNativeBaseComponent
39
+ * @extends ReactComponent
40
+ * @extends ReactMultiChild
41
+ * @param {!object} UIKit View Configuration.
42
+ */
43
+var ReactNativeBaseComponent = function(
44
+ viewConfig: ReactNativeBaseComponentViewConfig
45
+) {
46
+ this.viewConfig = viewConfig;
47
+};
48
+
49
+/**
50
+ * Mixin for containers that contain UIViews. NOTE: markup is rendered markup
51
+ * which is a `viewID` ... see the return value for `mountComponent` !
52
+ */
53
+ReactNativeBaseComponent.Mixin = {
54
+ getPublicInstance: function() {
55
+ // TODO: This should probably use a composite wrapper
56
+ return this;
57
+ },
58
+
59
+ construct: function(element) {
60
+ this._currentElement = element;
61
+ },
62
+
63
+ unmountComponent: function() {
64
+ deleteAllListeners(this._rootNodeID);
65
+ this.unmountChildren();
66
+ this._rootNodeID = null;
67
+ },
68
+
69
+ /**
70
+ * Every native component is responsible for allocating its own `tag`, and
71
+ * issuing the native `createView` command. But it is not responsible for
72
+ * recording the fact that its own `rootNodeID` is associated with a
73
+ * `nodeHandle`. Only the code that actually adds its `nodeHandle` (`tag`) as
74
+ * a child of a container can confidently record that in
75
+ * `ReactNativeTagHandles`.
76
+ */
77
+ initializeChildren: function(children, containerTag, transaction, context) {
78
+ var mountImages = this.mountChildren(children, transaction, context);
79
+ // In a well balanced tree, half of the nodes are in the bottom row and have
80
+ // no children - let's avoid calling out to the native bridge for a large
81
+ // portion of the children.
82
+ if (mountImages.length) {
83
+
84
+ // TODO: Pool these per platform view class. Reusing the `mountImages`
85
+ // array would likely be a jit deopt.
86
+ var createdTags = [];
87
+ for (var i = 0, l = mountImages.length; i < l; i++) {
88
+ var mountImage = mountImages[i];
89
+ var childTag = mountImage.tag;
90
+ var childID = mountImage.rootNodeID;
91
+ warning(
92
+ mountImage && mountImage.rootNodeID && mountImage.tag,
93
+ 'Mount image returned does not have required data'
94
+ );
95
+ ReactNativeTagHandles.associateRootNodeIDWithMountedNodeHandle(
96
+ childID,
97
+ childTag
98
+ );
99
+ createdTags[i] = mountImage.tag;
100
+ }
101
+ UIManager.setChildren(containerTag, createdTags);
102
+ }
103
+ },
104
+
105
+ /**
106
+ * Updates the component's currently mounted representation.
107
+ *
108
+ * @param {object} nextElement
109
+ * @param {ReactReconcileTransaction} transaction
110
+ * @param {object} context
111
+ * @internal
112
+ */
113
+ receiveComponent: function(nextElement, transaction, context) {
114
+ var prevElement = this._currentElement;
115
+ this._currentElement = nextElement;
116
+
117
+ if (__DEV__) {
118
+ for (var key in this.viewConfig.validAttributes) {
119
+ if (nextElement.props.hasOwnProperty(key)) {
120
+ deepFreezeAndThrowOnMutationInDev(nextElement.props[key]);
121
+ }
122
+ }
123
+ }
124
+
125
+ var updatePayload = ReactNativeAttributePayload.diff(
126
+ prevElement.props,
127
+ nextElement.props,
128
+ this.viewConfig.validAttributes
129
+ );
130
+
131
+ if (updatePayload) {
132
+ UIManager.updateView(
133
+ ReactNativeTagHandles.mostRecentMountedNodeHandleForRootNodeID(this._rootNodeID),
134
+ this.viewConfig.uiViewClassName,
135
+ updatePayload
136
+ );
137
+ }
138
+
139
+ this._reconcileListenersUponUpdate(
140
+ prevElement.props,
141
+ nextElement.props
142
+ );
143
+ this.updateChildren(nextElement.props.children, transaction, context);
144
+ },
145
+
146
+ /**
147
+ * @param {object} initialProps Native component props.
148
+ */
149
+ _registerListenersUponCreation: function(initialProps) {
150
+ for (var key in initialProps) {
151
+ // NOTE: The check for `!props[key]`, is only possible because this method
152
+ // registers listeners the *first* time a component is created.
153
+ if (registrationNames[key] && initialProps[key]) {
154
+ var listener = initialProps[key];
155
+ putListener(this._rootNodeID, key, listener);
156
+ }
157
+ }
158
+ },
159
+
160
+ /**
161
+ * Reconciles event listeners, adding or removing if necessary.
162
+ * @param {object} prevProps Native component props including events.
163
+ * @param {object} nextProps Next native component props including events.
164
+ */
165
+ _reconcileListenersUponUpdate: function(prevProps, nextProps) {
166
+ for (var key in nextProps) {
167
+ if (registrationNames[key] && (nextProps[key] !== prevProps[key])) {
168
+ if (nextProps[key]) {
169
+ putListener(this._rootNodeID, key, nextProps[key]);
170
+ } else {
171
+ deleteListener(this._rootNodeID, key);
172
+ }
173
+ }
174
+ }
175
+ },
176
+
177
+ /**
178
+ * @param {string} rootID Root ID of this subtree.
179
+ * @param {Transaction} transaction For creating/updating.
180
+ * @return {string} Unique iOS view tag.
181
+ */
182
+ mountComponent: function(rootID, transaction, context) {
183
+ this._rootNodeID = rootID;
184
+
185
+ var tag = ReactNativeTagHandles.allocateTag();
186
+
187
+ if (__DEV__) {
188
+ for (var key in this.viewConfig.validAttributes) {
189
+ if (this._currentElement.props.hasOwnProperty(key)) {
190
+ deepFreezeAndThrowOnMutationInDev(this._currentElement.props[key]);
191
+ }
192
+ }
193
+ }
194
+
195
+ var updatePayload = ReactNativeAttributePayload.create(
196
+ this._currentElement.props,
197
+ this.viewConfig.validAttributes
198
+ );
199
+
200
+ var nativeTopRootID = ReactNativeTagHandles.getNativeTopRootIDFromNodeID(rootID);
201
+ if (nativeTopRootID == null) {
202
+ invariant(
203
+ false,
204
+ 'nativeTopRootID not found for tag ' + tag + ' view type ' +
205
+ this.viewConfig.uiViewClassName + ' with rootID ' + rootID);
206
+ }
207
+ UIManager.createView(
208
+ tag,
209
+ this.viewConfig.uiViewClassName,
210
+ ReactNativeTagHandles.rootNodeIDToTag[nativeTopRootID],
211
+ updatePayload
212
+ );
213
+
214
+ this._registerListenersUponCreation(this._currentElement.props);
215
+ this.initializeChildren(
216
+ this._currentElement.props.children,
217
+ tag,
218
+ transaction,
219
+ context
220
+ );
221
+ return {
222
+ rootNodeID: rootID,
223
+ tag: tag
224
+ };
225
+ }
226
+};
227
+
228
+/**
229
+ * Order of mixins is important. ReactNativeBaseComponent overrides methods in
230
+ * ReactMultiChild.
231
+ */
232
+Object.assign(
233
+ ReactNativeBaseComponent.prototype,
234
+ ReactMultiChild.Mixin,
235
+ ReactNativeBaseComponent.Mixin,
236
+ NativeMethodsMixin
237
+);
238
+
239
+module.exports = ReactNativeBaseComponent;
src/renderers/native/ReactNative/ReactNativeBaseComponentEnvironment.js
new
+42
@@ -0,0 +1,42 @@
1
+/**
2
+ * Copyright (c) 2015-present, Facebook, Inc.
3
+ * All rights reserved.
4
+ *
5
+ * This source code is licensed under the BSD-style license found in the
6
+ * LICENSE file in the root directory of this source tree. An additional grant
7
+ * of patent rights can be found in the PATENTS file in the same directory.
8
+ *
9
+ * @providesModule ReactNativeComponentEnvironment
10
+ * @flow
11
+ */
12
+'use strict';
13
+
14
+var ReactNativeDOMIDOperations = require('ReactNativeDOMIDOperations');
15
+var ReactNativeReconcileTransaction = require('ReactNativeReconcileTransaction');
16
+
17
+var ReactNativeComponentEnvironment = {
18
+
19
+ processChildrenUpdates: ReactNativeDOMIDOperations.dangerouslyProcessChildrenUpdates,
20
+
21
+ replaceNodeWithMarkupByID: ReactNativeDOMIDOperations.dangerouslyReplaceNodeWithMarkupByID,
22
+
23
+ /**
24
+ * Nothing to do for UIKit bridge.
25
+ *
26
+ * @private
27
+ */
28
+ unmountIDFromEnvironment: function(/*rootNodeID*/) {
29
+
30
+ },
31
+
32
+ /**
33
+ * @param {DOMElement} Element to clear.
34
+ */
35
+ clearNode: function(/*containerView*/) {
36
+
37
+ },
38
+
39
+ ReactReconcileTransaction: ReactNativeReconcileTransaction,
40
+};
41
+
42
+module.exports = ReactNativeComponentEnvironment;
src/renderers/native/ReactNative/ReactNativeDOMIDOperations.js
new
+102
@@ -0,0 +1,102 @@
1
+/**
2
+ * Copyright (c) 2015-present, Facebook, Inc.
3
+ * All rights reserved.
4
+ *
5
+ * This source code is licensed under the BSD-style license found in the
6
+ * LICENSE file in the root directory of this source tree. An additional grant
7
+ * of patent rights can be found in the PATENTS file in the same directory.
8
+ *
9
+ * @providesModule ReactNativeDOMIDOperations
10
+ * @flow
11
+ */
12
+'use strict';
13
+
14
+var ReactNativeTagHandles = require('ReactNativeTagHandles');
15
+var ReactMultiChildUpdateTypes = require('ReactMultiChildUpdateTypes');
16
+var ReactPerf = require('ReactPerf');
17
+var UIManager = require('UIManager');
18
+
19
+/**
20
+ * Updates a component's children by processing a series of updates.
21
+ * For each of the update/create commands, the `fromIndex` refers to the index
22
+ * that the item existed at *before* any of the updates are applied, and the
23
+ * `toIndex` refers to the index after *all* of the updates are applied
24
+ * (including deletes/moves). TODO: refactor so this can be shared with
25
+ * DOMChildrenOperations.
26
+ *
27
+ * @param {array<object>} updates List of update configurations.
28
+ * @param {array<string>} markup List of markup strings - in the case of React
29
+ * IOS, the ids of new components assumed to be already created.
30
+ */
31
+var dangerouslyProcessChildrenUpdates = function(childrenUpdates, markupList) {
32
+ if (!childrenUpdates.length) {
33
+ return;
34
+ }
35
+ var byContainerTag = {};
36
+ // Group by parent ID - send them across the bridge in separate commands per
37
+ // containerID.
38
+ for (var i = 0; i < childrenUpdates.length; i++) {
39
+ var update = childrenUpdates[i];
40
+ var containerTag = ReactNativeTagHandles.mostRecentMountedNodeHandleForRootNodeID(update.parentID);
41
+ var updates = byContainerTag[containerTag] || (byContainerTag[containerTag] = {});
42
+ if (update.type === ReactMultiChildUpdateTypes.MOVE_EXISTING) {
43
+ (updates.moveFromIndices || (updates.moveFromIndices = [])).push(update.fromIndex);
44
+ (updates.moveToIndices || (updates.moveToIndices = [])).push(update.toIndex);
45
+ } else if (update.type === ReactMultiChildUpdateTypes.REMOVE_NODE) {
46
+ (updates.removeAtIndices || (updates.removeAtIndices = [])).push(update.fromIndex);
47
+ } else if (update.type === ReactMultiChildUpdateTypes.INSERT_MARKUP) {
48
+ var mountImage = markupList[update.markupIndex];
49
+ var tag = mountImage.tag;
50
+ var rootNodeID = mountImage.rootNodeID;
51
+ ReactNativeTagHandles.associateRootNodeIDWithMountedNodeHandle(rootNodeID, tag);
52
+ (updates.addAtIndices || (updates.addAtIndices = [])).push(update.toIndex);
53
+ (updates.addChildTags || (updates.addChildTags = [])).push(tag);
54
+ }
55
+ }
56
+ // Note this enumeration order will be different on V8! Move `byContainerTag`
57
+ // to a sparse array as soon as we confirm there are not horrible perf
58
+ // penalties.
59
+ for (var updateParentTagString in byContainerTag) {
60
+ var updateParentTagNumber = +updateParentTagString;
61
+ var childUpdatesToSend = byContainerTag[updateParentTagNumber];
62
+ UIManager.manageChildren(
63
+ updateParentTagNumber,
64
+ childUpdatesToSend.moveFromIndices,
65
+ childUpdatesToSend.moveToIndices,
66
+ childUpdatesToSend.addChildTags,
67
+ childUpdatesToSend.addAtIndices,
68
+ childUpdatesToSend.removeAtIndices
69
+ );
70
+ }
71
+};
72
+
73
+/**
74
+ * Operations used to process updates to DOM nodes. This is made injectable via
75
+ * `ReactComponent.DOMIDOperations`.
76
+ */
77
+var ReactNativeDOMIDOperations = {
78
+ dangerouslyProcessChildrenUpdates: ReactPerf.measure(
79
+ // FIXME(frantic): #4441289 Hack to avoid modifying react-tools
80
+ 'ReactDOMIDOperations',
81
+ 'dangerouslyProcessChildrenUpdates',
82
+ dangerouslyProcessChildrenUpdates
83
+ ),
84
+
85
+ /**
86
+ * Replaces a view that exists in the document with markup.
87
+ *
88
+ * @param {string} id ID of child to be replaced.
89
+ * @param {string} markup Mount image to replace child with id.
90
+ */
91
+ dangerouslyReplaceNodeWithMarkupByID: ReactPerf.measure(
92
+ 'ReactDOMIDOperations',
93
+ 'dangerouslyReplaceNodeWithMarkupByID',
94
+ function(id, mountImage) {
95
+ var oldTag = ReactNativeTagHandles.mostRecentMountedNodeHandleForRootNodeID(id);
96
+ UIManager.replaceExistingNonRootView(oldTag, mountImage.tag);
97
+ ReactNativeTagHandles.associateRootNodeIDWithMountedNodeHandle(id, mountImage.tag);
98
+ }
99
+ ),
100
+};
101
+
102
+module.exports = ReactNativeDOMIDOperations;
src/renderers/native/ReactNative/ReactNativeDefaultInjection.js
new
+113
@@ -0,0 +1,113 @@
1
+/**
2
+ * Copyright (c) 2015-present, Facebook, Inc.
3
+ * All rights reserved.
4
+ *
5
+ * This source code is licensed under the BSD-style license found in the
6
+ * LICENSE file in the root directory of this source tree. An additional grant
7
+ * of patent rights can be found in the PATENTS file in the same directory.
8
+ *
9
+ * @providesModule ReactNativeDefaultInjection
10
+ * @flow
11
+ */
12
+'use strict';
13
+
14
+/**
15
+ * Make sure essential globals are available and are patched correctly. Please don't remove this
16
+ * line. Bundles created by react-packager `require` it before executing any application code. This
17
+ * ensures it exists in the dependency graph and can be `require`d.
18
+ */
19
+require('InitializeJavaScriptAppEngine');
20
+
21
+var EventPluginHub = require('EventPluginHub');
22
+var EventPluginUtils = require('EventPluginUtils');
23
+var IOSDefaultEventPluginOrder = require('IOSDefaultEventPluginOrder');
24
+var IOSNativeBridgeEventPlugin = require('IOSNativeBridgeEventPlugin');
25
+var NodeHandle = require('NodeHandle');
26
+var ReactElement = require('ReactElement');
27
+var ReactComponentEnvironment = require('ReactComponentEnvironment');
28
+var ReactDefaultBatchingStrategy = require('ReactDefaultBatchingStrategy');
29
+var ReactEmptyComponent = require('ReactEmptyComponent');
30
+var ReactInstanceHandles = require('ReactInstanceHandles');
31
+var ReactNativeComponentEnvironment = require('ReactNativeComponentEnvironment');
32
+var ReactNativeGlobalInteractionHandler = require('ReactNativeGlobalInteractionHandler');
33
+var ReactNativeGlobalResponderHandler = require('ReactNativeGlobalResponderHandler');
34
+var ReactNativeMount = require('ReactNativeMount');
35
+var ReactNativeTextComponent = require('ReactNativeTextComponent');
36
+var ReactNativeComponent = require('ReactNativeComponent');
37
+var ReactUpdates = require('ReactUpdates');
38
+var ResponderEventPlugin = require('ResponderEventPlugin');
39
+var UniversalWorkerNodeHandle = require('UniversalWorkerNodeHandle');
40
+
41
+var invariant = require('fbjs/lib/invariant');
42
+
43
+// Just to ensure this gets packaged, since its only caller is from Native.
44
+require('RCTEventEmitter');
45
+require('RCTLog');
46
+require('JSTimersExecution');
47
+
48
+function inject() {
49
+ /**
50
+ * Inject module for resolving DOM hierarchy and plugin ordering.
51
+ */
52
+ EventPluginHub.injection.injectEventPluginOrder(IOSDefaultEventPluginOrder);
53
+ EventPluginHub.injection.injectInstanceHandle(ReactInstanceHandles);
54
+
55
+ ResponderEventPlugin.injection.injectGlobalResponderHandler(
56
+ ReactNativeGlobalResponderHandler
57
+ );
58
+
59
+ ResponderEventPlugin.injection.injectGlobalInteractionHandler(
60
+ ReactNativeGlobalInteractionHandler
61
+ );
62
+
63
+ /**
64
+ * Some important event plugins included by default (without having to require
65
+ * them).
66
+ */
67
+ EventPluginHub.injection.injectEventPluginsByName({
68
+ 'ResponderEventPlugin': ResponderEventPlugin,
69
+ 'IOSNativeBridgeEventPlugin': IOSNativeBridgeEventPlugin
70
+ });
71
+
72
+ ReactUpdates.injection.injectReconcileTransaction(
73
+ ReactNativeComponentEnvironment.ReactReconcileTransaction
74
+ );
75
+
76
+ ReactUpdates.injection.injectBatchingStrategy(
77
+ ReactDefaultBatchingStrategy
78
+ );
79
+
80
+ ReactComponentEnvironment.injection.injectEnvironment(
81
+ ReactNativeComponentEnvironment
82
+ );
83
+
84
+ var EmptyComponent = () => {
85
+ // Can't import View at the top because it depends on React to make its composite
86
+ var View = require('View');
87
+ return ReactElement.createElement(View, {
88
+ collapsable: true,
89
+ style: { position: 'absolute' }
90
+ });
91
+ };
92
+ ReactEmptyComponent.injection.injectEmptyComponent(EmptyComponent);
93
+
94
+ EventPluginUtils.injection.injectMount(ReactNativeMount);
95
+
96
+ ReactNativeComponent.injection.injectTextComponentClass(
97
+ ReactNativeTextComponent
98
+ );
99
+ ReactNativeComponent.injection.injectGenericComponentClass(function(tag) {
100
+ // Show a nicer error message for non-function tags
101
+ var info = '';
102
+ if (typeof tag === 'string' && /^[a-z]/.test(tag)) {
103
+ info += ' Each component name should start with an uppercase letter.';
104
+ }
105
+ invariant(false, 'Expected a component class, got %s.%s', tag, info);
106
+ });
107
+
108
+ NodeHandle.injection.injectImplementation(UniversalWorkerNodeHandle);
109
+}
110
+
111
+module.exports = {
112
+ inject: inject,
113
+};
src/renderers/native/ReactNative/ReactNativeEventEmitter.js
new
+214
@@ -0,0 +1,214 @@
1
+/**
2
+ * Copyright (c) 2015-present, Facebook, Inc.
3
+ * All rights reserved.
4
+ *
5
+ * This source code is licensed under the BSD-style license found in the
6
+ * LICENSE file in the root directory of this source tree. An additional grant
7
+ * of patent rights can be found in the PATENTS file in the same directory.
8
+ *
9
+ * @providesModule ReactNativeEventEmitter
10
+ * @flow
11
+ */
12
+'use strict';
13
+
14
+var EventPluginHub = require('EventPluginHub');
15
+var ReactEventEmitterMixin = require('ReactEventEmitterMixin');
16
+var ReactNativeTagHandles = require('ReactNativeTagHandles');
17
+var NodeHandle = require('NodeHandle');
18
+var EventConstants = require('EventConstants');
19
+
20
+var merge = require('merge');
21
+var warning = require('fbjs/lib/warning');
22
+
23
+var topLevelTypes = EventConstants.topLevelTypes;
24
+
25
+/**
26
+ * Version of `ReactBrowserEventEmitter` that works on the receiving side of a
27
+ * serialized worker boundary.
28
+ */
29
+
30
+// Shared default empty native event - conserve memory.
31
+var EMPTY_NATIVE_EVENT = {};
32
+
33
+/**
34
+ * Selects a subsequence of `Touch`es, without destroying `touches`.
35
+ *
36
+ * @param {Array<Touch>} touches Deserialized touch objects.
37
+ * @param {Array<number>} indices Indices by which to pull subsequence.
38
+ * @return {Array<Touch>} Subsequence of touch objects.
39
+ */
40
+var touchSubsequence = function(touches, indices) {
41
+ var ret = [];
42
+ for (var i = 0; i < indices.length; i++) {
43
+ ret.push(touches[indices[i]]);
44
+ }
45
+ return ret;
46
+};
47
+
48
+/**
49
+ * TODO: Pool all of this.
50
+ *
51
+ * Destroys `touches` by removing touch objects at indices `indices`. This is
52
+ * to maintain compatibility with W3C touch "end" events, where the active
53
+ * touches don't include the set that has just been "ended".
54
+ *
55
+ * @param {Array<Touch>} touches Deserialized touch objects.
56
+ * @param {Array<number>} indices Indices to remove from `touches`.
57
+ * @return {Array<Touch>} Subsequence of removed touch objects.
58
+ */
59
+var removeTouchesAtIndices = function(
60
+ touches: Array<Object>,
61
+ indices: Array<number>
62
+): Array<Object> {
63
+ var rippedOut = [];
64
+ // use an unsafe downcast to alias to nullable elements,
65
+ // so we can delete and then compact.
66
+ var temp: Array<?Object> = (touches: Array<any>);
67
+ for (var i = 0; i < indices.length; i++) {
68
+ var index = indices[i];
69
+ rippedOut.push(touches[index]);
70
+ temp[index] = null;
71
+ }
72
+ var fillAt = 0;
73
+ for (var j = 0; j < temp.length; j++) {
74
+ var cur = temp[j];
75
+ if (cur !== null) {
76
+ temp[fillAt++] = cur;
77
+ }
78
+ }
79
+ temp.length = fillAt;
80
+ return rippedOut;
81
+};
82
+
83
+/**
84
+ * `ReactNativeEventEmitter` is used to attach top-level event listeners. For example:
85
+ *
86
+ * ReactNativeEventEmitter.putListener('myID', 'onClick', myFunction);
87
+ *
88
+ * This would allocate a "registration" of `('onClick', myFunction)` on 'myID'.
89
+ *
90
+ * @internal
91
+ */
92
+var ReactNativeEventEmitter = merge(ReactEventEmitterMixin, {
93
+
94
+ registrationNames: EventPluginHub.registrationNameModules,
95
+
96
+ putListener: EventPluginHub.putListener,
97
+
98
+ getListener: EventPluginHub.getListener,
99
+
100
+ deleteListener: EventPluginHub.deleteListener,
101
+
102
+ deleteAllListeners: EventPluginHub.deleteAllListeners,
103
+
104
+ /**
105
+ * Internal version of `receiveEvent` in terms of normalized (non-tag)
106
+ * `rootNodeID`.
107
+ *
108
+ * @see receiveEvent.
109
+ *
110
+ * @param {rootNodeID} rootNodeID React root node ID that event occurred on.
111
+ * @param {TopLevelType} topLevelType Top level type of event.
112
+ * @param {object} nativeEventParam Object passed from native.
113
+ */
114
+ _receiveRootNodeIDEvent: function(
115
+ rootNodeID: ?string,
116
+ topLevelType: string,
117
+ nativeEventParam: Object
118
+ ) {
119
+ var nativeEvent = nativeEventParam || EMPTY_NATIVE_EVENT;
120
+ ReactNativeEventEmitter.handleTopLevel(
121
+ topLevelType,
122
+ rootNodeID,
123
+ rootNodeID,
124
+ nativeEvent,
125
+ nativeEvent.target
126
+ );
127
+ },
128
+
129
+ /**
130
+ * Publicly exposed method on module for native objc to invoke when a top
131
+ * level event is extracted.
132
+ * @param {rootNodeID} rootNodeID React root node ID that event occurred on.
133
+ * @param {TopLevelType} topLevelType Top level type of event.
134
+ * @param {object} nativeEventParam Object passed from native.
135
+ */
136
+ receiveEvent: function(
137
+ tag: number,
138
+ topLevelType: string,
139
+ nativeEventParam: Object
140
+ ) {
141
+ var rootNodeID = ReactNativeTagHandles.tagToRootNodeID[tag];
142
+ ReactNativeEventEmitter._receiveRootNodeIDEvent(
143
+ rootNodeID,
144
+ topLevelType,
145
+ nativeEventParam
146
+ );
147
+ },
148
+
149
+ /**
150
+ * Simple multi-wrapper around `receiveEvent` that is intended to receive an
151
+ * efficient representation of `Touch` objects, and other information that
152
+ * can be used to construct W3C compliant `Event` and `Touch` lists.
153
+ *
154
+ * This may create dispatch behavior that differs than web touch handling. We
155
+ * loop through each of the changed touches and receive it as a single event.
156
+ * So two `touchStart`/`touchMove`s that occur simultaneously are received as
157
+ * two separate touch event dispatches - when they arguably should be one.
158
+ *
159
+ * This implementation reuses the `Touch` objects themselves as the `Event`s
160
+ * since we dispatch an event for each touch (though that might not be spec
161
+ * compliant). The main purpose of reusing them is to save allocations.
162
+ *
163
+ * TODO: Dispatch multiple changed touches in one event. The bubble path
164
+ * could be the first common ancestor of all the `changedTouches`.
165
+ *
166
+ * One difference between this behavior and W3C spec: cancelled touches will
167
+ * not appear in `.touches`, or in any future `.touches`, though they may
168
+ * still be "actively touching the surface".
169
+ *
170
+ * Web desktop polyfills only need to construct a fake touch event with
171
+ * identifier 0, also abandoning traditional click handlers.
172
+ */
173
+ receiveTouches: function(
174
+ eventTopLevelType: string,
175
+ touches: Array<Object>,
176
+ changedIndices: Array<number>
177
+ ) {
178
+ var changedTouches =
179
+ eventTopLevelType === topLevelTypes.topTouchEnd ||
180
+ eventTopLevelType === topLevelTypes.topTouchCancel ?
181
+ removeTouchesAtIndices(touches, changedIndices) :
182
+ touchSubsequence(touches, changedIndices);
183
+
184
+ for (var jj = 0; jj < changedTouches.length; jj++) {
185
+ var touch = changedTouches[jj];
186
+ // Touch objects can fulfill the role of `DOM` `Event` objects if we set
187
+ // the `changedTouches`/`touches`. This saves allocations.
188
+ touch.changedTouches = changedTouches;
189
+ touch.touches = touches;
190
+ var nativeEvent = touch;
191
+ var rootNodeID = null;
192
+ var target = nativeEvent.target;
193
+ if (target !== null && target !== undefined) {
194
+ if (target < ReactNativeTagHandles.tagsStartAt) {
195
+ if (__DEV__) {
196
+ warning(
197
+ false,
198
+ 'A view is reporting that a touch occured on tag zero.'
199
+ );
200
+ }
201
+ } else {
202
+ rootNodeID = NodeHandle.getRootNodeID(target);
203
+ }
204
+ }
205
+ ReactNativeEventEmitter._receiveRootNodeIDEvent(
206
+ rootNodeID,
207
+ eventTopLevelType,
208
+ nativeEvent
209
+ );
210
+ }
211
+ }
212
+});
213
+
214
+module.exports = ReactNativeEventEmitter;
src/renderers/native/ReactNative/ReactNativeGlobalInteractionHandler.js
new
+33
@@ -0,0 +1,33 @@
1
+/**
2
+ * Copyright (c) 2015-present, Facebook, Inc.
3
+ * All rights reserved.
4
+ *
5
+ * This source code is licensed under the BSD-style license found in the
6
+ * LICENSE file in the root directory of this source tree. An additional grant
7
+ * of patent rights can be found in the PATENTS file in the same directory.
8
+ *
9
+ * @providesModule ReactNativeGlobalInteractionHandler
10
+ * @flow
11
+ */
12
+'use strict';
13
+
14
+var InteractionManager = require('InteractionManager');
15
+
16
+// Interaction handle is created/cleared when responder is granted or
17
+// released/terminated.
18
+var interactionHandle = null;
19
+
20
+var ReactNativeGlobalInteractionHandler = {
21
+ onChange: function(numberActiveTouches: number) {
22
+ if (numberActiveTouches === 0) {
23
+ if (interactionHandle) {
24
+ InteractionManager.clearInteractionHandle(interactionHandle);
25
+ interactionHandle = null;
26
+ }
27
+ } else if (!interactionHandle) {
28
+ interactionHandle = InteractionManager.createInteractionHandle();
29
+ }
30
+ }
31
+};
32
+
33
+module.exports = ReactNativeGlobalInteractionHandler;
src/renderers/native/ReactNative/ReactNativeGlobalResponderHandler.js
new
+30
@@ -0,0 +1,30 @@
1
+/**
2
+ * Copyright (c) 2015-present, Facebook, Inc.
3
+ * All rights reserved.
4
+ *
5
+ * This source code is licensed under the BSD-style license found in the
6
+ * LICENSE file in the root directory of this source tree. An additional grant
7
+ * of patent rights can be found in the PATENTS file in the same directory.
8
+ *
9
+ * @providesModule ReactNativeGlobalResponderHandler
10
+ * @flow
11
+ */
12
+'use strict';
13
+
14
+var ReactNativeTagHandles = require('ReactNativeTagHandles');
15
+var UIManager = require('UIManager');
16
+
17
+var ReactNativeGlobalResponderHandler = {
18
+ onChange: function(from: string, to: string, blockNativeResponder: boolean) {
19
+ if (to !== null) {
20
+ UIManager.setJSResponder(
21
+ ReactNativeTagHandles.mostRecentMountedNodeHandleForRootNodeID(to),
22
+ blockNativeResponder
23
+ );
24
+ } else {
25
+ UIManager.clearJSResponder();
26
+ }
27
+ }
28
+};
29
+
30
+module.exports = ReactNativeGlobalResponderHandler;
src/renderers/native/ReactNative/ReactNativeMount.js
new
+270
@@ -0,0 +1,270 @@
1
+/**
2
+ * Copyright (c) 2015-present, Facebook, Inc.
3
+ * All rights reserved.
4
+ *
5
+ * This source code is licensed under the BSD-style license found in the
6
+ * LICENSE file in the root directory of this source tree. An additional grant
7
+ * of patent rights can be found in the PATENTS file in the same directory.
8
+ *
9
+ * @providesModule ReactNativeMount
10
+ * @flow
11
+ */
12
+'use strict';
13
+
14
+var ReactElement = require('ReactElement');
15
+var ReactNativeTagHandles = require('ReactNativeTagHandles');
16
+var ReactPerf = require('ReactPerf');
17
+var ReactReconciler = require('ReactReconciler');
18
+var ReactUpdateQueue = require('ReactUpdateQueue');
19
+var ReactUpdates = require('ReactUpdates');
20
+var UIManager = require('UIManager');
21
+
22
+var emptyObject = require('fbjs/lib/emptyObject');
23
+var instantiateReactComponent = require('instantiateReactComponent');
24
+var shouldUpdateReactComponent = require('shouldUpdateReactComponent');
25
+
26
+function instanceNumberToChildRootID(rootNodeID, instanceNumber) {
27
+ return rootNodeID + '[' + instanceNumber + ']';
28
+}
29
+
30
+/**
31
+ * Temporary (?) hack so that we can store all top-level pending updates on
32
+ * composites instead of having to worry about different types of components
33
+ * here.
34
+ */
35
+var TopLevelWrapper = function() {};
36
+TopLevelWrapper.prototype.isReactComponent = {};
37
+if (__DEV__) {
38
+ TopLevelWrapper.displayName = 'TopLevelWrapper';
39
+}
40
+TopLevelWrapper.prototype.render = function() {
41
+ // this.props is actually a ReactElement
42
+ return this.props;
43
+};
44
+
45
+/**
46
+ * Mounts this component and inserts it into the DOM.
47
+ *
48
+ * @param {ReactComponent} componentInstance The instance to mount.
49
+ * @param {number} rootID ID of the root node.
50
+ * @param {number} container container element to mount into.
51
+ * @param {ReactReconcileTransaction} transaction
52
+ */
53
+function mountComponentIntoNode(
54
+ componentInstance,
55
+ rootID,
56
+ container,
57
+ transaction) {
58
+ var markup = ReactReconciler.mountComponent(
59
+ componentInstance, rootID, transaction, emptyObject
60
+ );
61
+ componentInstance._renderedComponent._topLevelWrapper = componentInstance;
62
+ ReactNativeMount._mountImageIntoNode(markup, container);
63
+}
64
+
65
+/**
66
+ * Batched mount.
67
+ *
68
+ * @param {ReactComponent} componentInstance The instance to mount.
69
+ * @param {number} rootID ID of the root node.
70
+ * @param {number} container container element to mount into.
71
+ */
72
+function batchedMountComponentIntoNode(
73
+ componentInstance,
74
+ rootID,
75
+ container) {
76
+ var transaction = ReactUpdates.ReactReconcileTransaction.getPooled();
77
+ transaction.perform(
78
+ mountComponentIntoNode,
79
+ null,
80
+ componentInstance,
81
+ rootID,
82
+ container,
83
+ transaction
84
+ );
85
+ ReactUpdates.ReactReconcileTransaction.release(transaction);
86
+}
87
+
88
+/**
89
+ * As soon as `ReactMount` is refactored to not rely on the DOM, we can share
90
+ * code between the two. For now, we'll hard code the ID logic.
91
+ */
92
+var ReactNativeMount = {
93
+ instanceCount: 0,
94
+
95
+ _instancesByContainerID: {},
96
+
97
+ // these two functions are needed by React Devtools
98
+ findNodeHandle: require('findNodeHandle'),
99
+ nativeTagToRootNodeID: function (nativeTag: number): string {
100
+ return ReactNativeTagHandles.tagToRootNodeID[nativeTag];
101
+ },
102
+
103
+ /**
104
+ * @param {ReactComponent} instance Instance to render.
105
+ * @param {containerTag} containerView Handle to native view tag
106
+ */
107
+ renderComponent: function(
108
+ nextElement: ReactElement,
109
+ containerTag: number,
110
+ callback?: ?(() => void)
111
+ ): ?ReactComponent {
112
+ var nextWrappedElement = new ReactElement(
113
+ TopLevelWrapper,
114
+ null,
115
+ null,
116
+ null,
117
+ null,
118
+ null,
119
+ nextElement
120
+ );
121
+
122
+ var topRootNodeID = ReactNativeTagHandles.tagToRootNodeID[containerTag];
123
+ if (topRootNodeID) {
124
+ var prevComponent = ReactNativeMount._instancesByContainerID[topRootNodeID];
125
+ if (prevComponent) {
126
+ var prevWrappedElement = prevComponent._currentElement;
127
+ var prevElement = prevWrappedElement.props;
128
+ if (shouldUpdateReactComponent(prevElement, nextElement)) {
129
+ ReactUpdateQueue.enqueueElementInternal(prevComponent, nextWrappedElement);
130
+ if (callback) {
131
+ ReactUpdateQueue.enqueueCallbackInternal(prevComponent, callback);
132
+ }
133
+ return prevComponent;
134
+ } else {
135
+ ReactNativeMount.unmountComponentAtNode(containerTag);
136
+ }
137
+ }
138
+ }
139
+
140
+ if (!ReactNativeTagHandles.reactTagIsNativeTopRootID(containerTag)) {
141
+ console.error('You cannot render into anything but a top root');
142
+ return;
143
+ }
144
+
145
+ var topRootNodeID = ReactNativeTagHandles.allocateRootNodeIDForTag(containerTag);
146
+ ReactNativeTagHandles.associateRootNodeIDWithMountedNodeHandle(
147
+ topRootNodeID,
148
+ containerTag
149
+ );
150
+
151
+ var instance = instantiateReactComponent(nextWrappedElement);
152
+ ReactNativeMount._instancesByContainerID[topRootNodeID] = instance;
153
+
154
+ var childRootNodeID = instanceNumberToChildRootID(
155
+ topRootNodeID,
156
+ ReactNativeMount.instanceCount++
157
+ );
158
+
159
+ // The initial render is synchronous but any updates that happen during
160
+ // rendering, in componentWillMount or componentDidMount, will be batched
161
+ // according to the current batching strategy.
162
+
163
+ ReactUpdates.batchedUpdates(
164
+ batchedMountComponentIntoNode,
165
+ instance,
166
+ childRootNodeID,
167
+ topRootNodeID
168
+ );
169
+ var component = instance.getPublicInstance();
170
+ if (callback) {
171
+ callback.call(component);
172
+ }
173
+ return component;
174
+ },
175
+
176
+ /**
177
+ * @param {View} view View tree image.
178
+ * @param {number} containerViewID View to insert sub-view into.
179
+ */
180
+ _mountImageIntoNode: ReactPerf.measure(
181
+ // FIXME(frantic): #4441289 Hack to avoid modifying react-tools
182
+ 'ReactComponentBrowserEnvironment',
183
+ 'mountImageIntoNode',
184
+ function(mountImage, containerID) {
185
+ // Since we now know that the `mountImage` has been mounted, we can
186
+ // mark it as such.
187
+ ReactNativeTagHandles.associateRootNodeIDWithMountedNodeHandle(
188
+ mountImage.rootNodeID,
189
+ mountImage.tag
190
+ );
191
+ UIManager.setChildren(
192
+ ReactNativeTagHandles.mostRecentMountedNodeHandleForRootNodeID(containerID),
193
+ [mountImage.tag]
194
+ );
195
+ }
196
+ ),
197
+
198
+ /**
199
+ * Standard unmounting of the component that is rendered into `containerID`,
200
+ * but will also execute a command to remove the actual container view
201
+ * itself. This is useful when a client is cleaning up a React tree, and also
202
+ * knows that the container will no longer be needed. When executing
203
+ * asynchronously, it's easier to just have this method be the one that calls
204
+ * for removal of the view.
205
+ */
206
+ unmountComponentAtNodeAndRemoveContainer: function(
207
+ containerTag: number
208
+ ) {
209
+ ReactNativeMount.unmountComponentAtNode(containerTag);
210
+ // call back into native to remove all of the subviews from this container
211
+ UIManager.removeRootView(containerTag);
212
+ },
213
+
214
+ /**
215
+ * Unmount component at container ID by iterating through each child component
216
+ * that has been rendered and unmounting it. There should just be one child
217
+ * component at this time.
218
+ */
219
+ unmountComponentAtNode: function(containerTag: number): boolean {
220
+ if (!ReactNativeTagHandles.reactTagIsNativeTopRootID(containerTag)) {
221
+ console.error('You cannot render into anything but a top root');
222
+ return false;
223
+ }
224
+
225
+ var containerID = ReactNativeTagHandles.tagToRootNodeID[containerTag];
226
+ var instance = ReactNativeMount._instancesByContainerID[containerID];
227
+ if (!instance) {
228
+ return false;
229
+ }
230
+ ReactNativeMount.unmountComponentFromNode(instance, containerID);
231
+ delete ReactNativeMount._instancesByContainerID[containerID];
232
+ return true;
233
+ },
234
+
235
+ /**
236
+ * Unmounts a component and sends messages back to iOS to remove its subviews.
237
+ *
238
+ * @param {ReactComponent} instance React component instance.
239
+ * @param {string} containerID ID of container we're removing from.
240
+ * @final
241
+ * @internal
242
+ * @see {ReactNativeMount.unmountComponentAtNode}
243
+ */
244
+ unmountComponentFromNode: function(
245
+ instance: ReactComponent,
246
+ containerID: string
247
+ ) {
248
+ // Call back into native to remove all of the subviews from this container
249
+ ReactReconciler.unmountComponent(instance);
250
+ var containerTag =
251
+ ReactNativeTagHandles.mostRecentMountedNodeHandleForRootNodeID(containerID);
252
+ UIManager.removeSubviewsFromContainerWithID(containerTag);
253
+ },
254
+
255
+ getNode: function(rootNodeID: string): number {
256
+ return ReactNativeTagHandles.rootNodeIDToTag[rootNodeID];
257
+ },
258
+
259
+ getID: function(nativeTag: number): string {
260
+ return ReactNativeTagHandles.tagToRootNodeID[nativeTag];
261
+ }
262
+};
263
+
264
+ReactNativeMount.renderComponent = ReactPerf.measure(
265
+ 'ReactMount',
266
+ '_renderNewRootComponent',
267
+ ReactNativeMount.renderComponent
268
+);
269
+
270
+module.exports = ReactNativeMount;
src/renderers/native/ReactNative/ReactNativePropRegistry.js
new
+44
@@ -0,0 +1,44 @@
1
+/**
2
+ * Copyright (c) 2015-present, Facebook, Inc.
3
+ * All rights reserved.
4
+ *
5
+ * This source code is licensed under the BSD-style license found in the
6
+ * LICENSE file in the root directory of this source tree. An additional grant
7
+ * of patent rights can be found in the PATENTS file in the same directory.
8
+ *
9
+ * @providesModule ReactNativePropRegistry
10
+ * @flow
11
+ */
12
+'use strict';
13
+
14
+var objects = {};
15
+var uniqueID = 1;
16
+var emptyObject = {};
17
+
18
+class ReactNativePropRegistry {
19
+ static register(object: Object): number {
20
+ var id = ++uniqueID;
21
+ if (__DEV__) {
22
+ Object.freeze(object);
23
+ }
24
+ objects[id] = object;
25
+ return id;
26
+ }
27
+
28
+ static getByID(id: number): Object {
29
+ if (!id) {
30
+ // Used in the style={[condition && id]} pattern,
31
+ // we want it to be a no-op when the value is false or null
32
+ return emptyObject;
33
+ }
34
+
35
+ var object = objects[id];
36
+ if (!object) {
37
+ console.warn('Invalid style with id `' + id + '`. Skipping ...');
38
+ return emptyObject;
39
+ }
40
+ return object;
41
+ }
42
+}
43
+
44
+module.exports = ReactNativePropRegistry;
src/renderers/native/ReactNative/ReactNativeReconcileTransaction.js
new
+103
@@ -0,0 +1,103 @@
1
+/**
2
+ * Copyright (c) 2015-present, Facebook, Inc.
3
+ * All rights reserved.
4
+ *
5
+ * This source code is licensed under the BSD-style license found in the
6
+ * LICENSE file in the root directory of this source tree. An additional grant
7
+ * of patent rights can be found in the PATENTS file in the same directory.
8
+ *
9
+ * @providesModule ReactNativeReconcileTransaction
10
+ * @flow
11
+ */
12
+'use strict';
13
+
14
+var CallbackQueue = require('CallbackQueue');
15
+var PooledClass = require('PooledClass');
16
+var Transaction = require('Transaction');
17
+
18
+/**
19
+ * Provides a `CallbackQueue` queue for collecting `onDOMReady` callbacks during
20
+ * the performing of the transaction.
21
+ */
22
+var ON_DOM_READY_QUEUEING = {
23
+ /**
24
+ * Initializes the internal `onDOMReady` queue.
25
+ */
26
+ initialize: function() {
27
+ this.reactMountReady.reset();
28
+ },
29
+
30
+ /**
31
+ * After DOM is flushed, invoke all registered `onDOMReady` callbacks.
32
+ */
33
+ close: function() {
34
+ this.reactMountReady.notifyAll();
35
+ }
36
+};
37
+
38
+/**
39
+ * Executed within the scope of the `Transaction` instance. Consider these as
40
+ * being member methods, but with an implied ordering while being isolated from
41
+ * each other.
42
+ */
43
+var TRANSACTION_WRAPPERS = [ON_DOM_READY_QUEUEING];
44
+
45
+/**
46
+ * Currently:
47
+ * - The order that these are listed in the transaction is critical:
48
+ * - Suppresses events.
49
+ * - Restores selection range.
50
+ *
51
+ * Future:
52
+ * - Restore document/overflow scroll positions that were unintentionally
53
+ * modified via DOM insertions above the top viewport boundary.
54
+ * - Implement/integrate with customized constraint based layout system and keep
55
+ * track of which dimensions must be remeasured.
56
+ *
57
+ * @class ReactNativeReconcileTransaction
58
+ */
59
+function ReactNativeReconcileTransaction() {
60
+ this.reinitializeTransaction();
61
+ this.reactMountReady = CallbackQueue.getPooled(null);
62
+}
63
+
64
+var Mixin = {
65
+ /**
66
+ * @see Transaction
67
+ * @abstract
68
+ * @final
69
+ * @return {array<object>} List of operation wrap procedures.
70
+ * TODO: convert to array<TransactionWrapper>
71
+ */
72
+ getTransactionWrappers: function() {
73
+ return TRANSACTION_WRAPPERS;
74
+ },
75
+
76
+ /**
77
+ * @return {object} The queue to collect `onDOMReady` callbacks with.
78
+ * TODO: convert to ReactMountReady
79
+ */
80
+ getReactMountReady: function() {
81
+ return this.reactMountReady;
82
+ },
83
+
84
+ /**
85
+ * `PooledClass` looks for this, and will invoke this before allowing this
86
+ * instance to be reused.
87
+ */
88
+ destructor: function() {
89
+ CallbackQueue.release(this.reactMountReady);
90
+ this.reactMountReady = null;
91
+ }
92
+};
93
+
94
+Object.assign(
95
+ ReactNativeReconcileTransaction.prototype,
96
+ Transaction.Mixin,
97
+ ReactNativeReconcileTransaction,
98
+ Mixin
99
+);
100
+
101
+PooledClass.addPoolingTo(ReactNativeReconcileTransaction);
102
+
103
+module.exports = ReactNativeReconcileTransaction;
src/renderers/native/ReactNative/ReactNativeTagHandles.js
new
+113
@@ -0,0 +1,113 @@
1
+/**
2
+ * Copyright (c) 2015-present, Facebook, Inc.
3
+ * All rights reserved.
4
+ *
5
+ * This source code is licensed under the BSD-style license found in the
6
+ * LICENSE file in the root directory of this source tree. An additional grant
7
+ * of patent rights can be found in the PATENTS file in the same directory.
8
+ *
9
+ * @providesModule ReactNativeTagHandles
10
+ * @flow
11
+ */
12
+'use strict';
13
+
14
+var invariant = require('fbjs/lib/invariant');
15
+var warning = require('fbjs/lib/warning');
16
+
17
+/**
18
+ * Keeps track of allocating and associating native "tags" which are numeric,
19
+ * unique view IDs. All the native tags are negative numbers, to avoid
20
+ * collisions, but in the JS we keep track of them as positive integers to store
21
+ * them effectively in Arrays. So we must refer to them as "inverses" of the
22
+ * native tags (that are * normally negative).
23
+ *
24
+ * It *must* be the case that every `rootNodeID` always maps to the exact same
25
+ * `tag` forever. The easiest way to accomplish this is to never delete
26
+ * anything from this table.
27
+ * Why: Because `dangerouslyReplaceNodeWithMarkupByID` relies on being able to
28
+ * unmount a component with a `rootNodeID`, then mount a new one in its place,
29
+ */
30
+var INITIAL_TAG_COUNT = 1;
31
+var NATIVE_TOP_ROOT_ID_SEPARATOR = '{TOP_LEVEL}';
32
+var ReactNativeTagHandles = {
33
+ tagsStartAt: INITIAL_TAG_COUNT,
34
+ tagCount: INITIAL_TAG_COUNT,
35
+
36
+ allocateTag: function(): number {
37
+ // Skip over root IDs as those are reserved for native
38
+ while (this.reactTagIsNativeTopRootID(ReactNativeTagHandles.tagCount)) {
39
+ ReactNativeTagHandles.tagCount++;
40
+ }
41
+ var tag = ReactNativeTagHandles.tagCount;
42
+ ReactNativeTagHandles.tagCount++;
43
+ return tag;
44
+ },
45
+
46
+ /**
47
+ * This associates the *last* observed *native* mounting between `rootNodeID`
48
+ * and some `tag`. This association doesn't imply that `rootNodeID` is still
49
+ * natively mounted as `tag`. The only reason why we don't clear the
50
+ * association when the `rootNodeID` is unmounted, is that we don't have a
51
+ * convenient time to disassociate them (otherwise we would).
52
+ * `unmountComponent` isn't the correct time because that doesn't imply that
53
+ * the native node has been natively unmounted.
54
+ */
55
+ associateRootNodeIDWithMountedNodeHandle: function(
56
+ rootNodeID: ?string,
57
+ tag: ?number
58
+ ) {
59
+ warning(rootNodeID && tag, 'Root node or tag is null when associating');
60
+ if (rootNodeID && tag) {
61
+ ReactNativeTagHandles.tagToRootNodeID[tag] = rootNodeID;
62
+ ReactNativeTagHandles.rootNodeIDToTag[rootNodeID] = tag;
63
+ }
64
+ },
65
+
66
+ allocateRootNodeIDForTag: function(tag: number): string {
67
+ invariant(
68
+ this.reactTagIsNativeTopRootID(tag),
69
+ 'Expect a native root tag, instead got ', tag
70
+ );
71
+ return '.r[' + tag + ']' + NATIVE_TOP_ROOT_ID_SEPARATOR;
72
+ },
73
+
74
+ reactTagIsNativeTopRootID: function(reactTag: number): bool {
75
+ // We reserve all tags that are 1 mod 10 for native root views
76
+ return reactTag % 10 === 1;
77
+ },
78
+
79
+ getNativeTopRootIDFromNodeID: function(nodeID: ?string): ?string {
80
+ if (!nodeID) {
81
+ return null;
82
+ }
83
+ var index = nodeID.indexOf(NATIVE_TOP_ROOT_ID_SEPARATOR);
84
+ if (index === -1) {
85
+ return null;
86
+ }
87
+ return nodeID.substr(0, index + NATIVE_TOP_ROOT_ID_SEPARATOR.length);
88
+ },
89
+
90
+ /**
91
+ * Returns the native `nodeHandle` (`tag`) that was most recently *natively*
92
+ * mounted at the `rootNodeID`. Just because a React component has been
93
+ * mounted, that doesn't mean that its native node has been mounted. The
94
+ * native node is mounted when we actually make the call to insert the
95
+ * `nodeHandle` (`tag`) into the native hierarchy.
96
+ *
97
+ * @param {string} rootNodeID Root node ID to find most recently mounted tag
98
+ * for. Again, this doesn't imply that it is still currently mounted.
99
+ * @return {number} Tag ID of native view for most recent mounting of
100
+ * `rootNodeID`.
101
+ */
102
+ mostRecentMountedNodeHandleForRootNodeID: function(
103
+ rootNodeID: string
104
+ ): number {
105
+ return ReactNativeTagHandles.rootNodeIDToTag[rootNodeID];
106
+ },
107
+
108
+ tagToRootNodeID: ([] : Array<string>),
109
+
110
+ rootNodeIDToTag: ({} : {[key: string]: number})
111
+};
112
+
113
+module.exports = ReactNativeTagHandles;
src/renderers/native/ReactNative/ReactNativeTextComponent.js
new
+78
@@ -0,0 +1,78 @@
1
+/**
2
+ * Copyright (c) 2015-present, Facebook, Inc.
3
+ * All rights reserved.
4
+ *
5
+ * This source code is licensed under the BSD-style license found in the
6
+ * LICENSE file in the root directory of this source tree. An additional grant
7
+ * of patent rights can be found in the PATENTS file in the same directory.
8
+ *
9
+ * @providesModule ReactNativeTextComponent
10
+ */
11
+
12
+'use strict';
13
+
14
+var ReactNativeTagHandles = require('ReactNativeTagHandles');
15
+var UIManager = require('UIManager');
16
+
17
+var invariant = require('fbjs/lib/invariant');
18
+
19
+var ReactNativeTextComponent = function(props) {
20
+ // This constructor and its argument is currently used by mocks.
21
+};
22
+
23
+Object.assign(ReactNativeTextComponent.prototype, {
24
+
25
+ construct: function(text) {
26
+ // This is really a ReactText (ReactNode), not a ReactElement
27
+ this._currentElement = text;
28
+ this._stringText = '' + text;
29
+ this._rootNodeID = null;
30
+ },
31
+
32
+ mountComponent: function(rootID, transaction, context) {
33
+ invariant(
34
+ context.isInAParentText,
35
+ 'RawText "' + this._stringText + '" must be wrapped in an explicit ' +
36
+ '<Text> component.'
37
+ );
38
+ this._rootNodeID = rootID;
39
+ var tag = ReactNativeTagHandles.allocateTag();
40
+ var nativeTopRootID = ReactNativeTagHandles.getNativeTopRootIDFromNodeID(rootID);
41
+ UIManager.createView(
42
+ tag,
43
+ 'RCTRawText',
44
+ nativeTopRootID ? ReactNativeTagHandles.rootNodeIDToTag[nativeTopRootID] : null,
45
+ {text: this._stringText}
46
+ );
47
+ return {
48
+ rootNodeID: rootID,
49
+ tag: tag,
50
+ };
51
+ },
52
+
53
+ receiveComponent: function(nextText, transaction, context) {
54
+ if (nextText !== this._currentElement) {
55
+ this._currentElement = nextText;
56
+ var nextStringText = '' + nextText;
57
+ if (nextStringText !== this._stringText) {
58
+ this._stringText = nextStringText;
59
+ UIManager.updateView(
60
+ ReactNativeTagHandles.mostRecentMountedNodeHandleForRootNodeID(
61
+ this._rootNodeID
62
+ ),
63
+ 'RCTRawText',
64
+ {text: this._stringText}
65
+ );
66
+ }
67
+ }
68
+ },
69
+
70
+ unmountComponent: function() {
71
+ this._currentElement = null;
72
+ this._stringText = null;
73
+ this._rootNodeID = null;
74
+ }
75
+
76
+});
77
+
78
+module.exports = ReactNativeTextComponent;
src/renderers/native/ReactNative/UIManagerStatTracker.js
new
+57
@@ -0,0 +1,57 @@
1
+/**
2
+ * Copyright (c) 2015-present, Facebook, Inc.
3
+ * All rights reserved.
4
+ *
5
+ * This source code is licensed under the BSD-style license found in the
6
+ * LICENSE file in the root directory of this source tree. An additional grant
7
+ * of patent rights can be found in the PATENTS file in the same directory.
8
+ *
9
+ * @providesModule UIManagerStatTracker
10
+ * @flow
11
+ */
12
+'use strict';
13
+
14
+var UIManager = require('UIManager');
15
+
16
+var installed = false;
17
+var UIManagerStatTracker = {
18
+ install: function() {
19
+ if (installed) {
20
+ return;
21
+ }
22
+ installed = true;
23
+ var statLogHandle;
24
+ var stats = {};
25
+ function printStats() {
26
+ console.log({UIManagerStatTracker: stats});
27
+ statLogHandle = null;
28
+ }
29
+ function incStat(key: string, increment: number) {
30
+ stats[key] = (stats[key] || 0) + increment;
31
+ if (!statLogHandle) {
32
+ statLogHandle = setImmediate(printStats);
33
+ }
34
+ }
35
+ var createViewOrig = UIManager.createView;
36
+ UIManager.createView = function(tag, className, rootTag, props) {
37
+ incStat('createView', 1);
38
+ incStat('setProp', Object.keys(props || []).length);
39
+ createViewOrig(tag, className, rootTag, props);
40
+ };
41
+ var updateViewOrig = UIManager.updateView;
42
+ UIManager.updateView = function(tag, className, props) {
43
+ incStat('updateView', 1);
44
+ incStat('setProp', Object.keys(props || []).length);
45
+ updateViewOrig(tag, className, props);
46
+ };
47
+ var manageChildrenOrig = UIManager.manageChildren;
48
+ UIManager.manageChildren = function(tag, moveFrom, moveTo, addTags, addIndices, remove) {
49
+ incStat('manageChildren', 1);
50
+ incStat('move', Object.keys(moveFrom || []).length);
51
+ incStat('remove', Object.keys(remove || []).length);
52
+ manageChildrenOrig(tag, moveFrom, moveTo, addTags, addIndices, remove);
53
+ };
54
+ },
55
+};
56
+
57
+module.exports = UIManagerStatTracker;
src/renderers/native/ReactNative/__tests__/ReactNativeAttributePayload-test.js
new
+231
@@ -0,0 +1,231 @@
1
+/**
2
+ * Copyright (c) 2013-present, Facebook, Inc.
3
+ * All rights reserved.
4
+ *
5
+ * This source code is licensed under the BSD-style license found in the
6
+ * LICENSE file in the root directory of this source tree. An additional grant
7
+ * of patent rights can be found in the PATENTS file in the same directory.
8
+ *
9
+ */
10
+'use strict';
11
+
12
+jest.dontMock('ReactNativeAttributePayload');
13
+jest.dontMock('ReactNativePropRegistry');
14
+jest.dontMock('deepDiffer');
15
+jest.dontMock('flattenStyle');
16
+
17
+var ReactNativeAttributePayload = require('ReactNativeAttributePayload');
18
+var ReactNativePropRegistry = require('ReactNativePropRegistry');
19
+
20
+var diff = ReactNativeAttributePayload.diff;
21
+
22
+describe('ReactNativeAttributePayload', function() {
23
+
24
+ it('should work with simple example', () => {
25
+ expect(diff(
26
+ {a: 1, c: 3},
27
+ {b: 2, c: 3},
28
+ {a: true, b: true}
29
+ )).toEqual({a: null, b: 2});
30
+ });
31
+
32
+ it('should skip fields that are equal', () => {
33
+ expect(diff(
34
+ {a: 1, b: 'two', c: true, d: false, e: undefined, f: 0},
35
+ {a: 1, b: 'two', c: true, d: false, e: undefined, f: 0},
36
+ {a: true, b: true, c: true, d: true, e: true, f: true}
37
+ )).toEqual(null);
38
+ });
39
+
40
+ it('should remove fields', () => {
41
+ expect(diff(
42
+ {a: 1},
43
+ {},
44
+ {a: true}
45
+ )).toEqual({a: null});
46
+ });
47
+
48
+ it('should remove fields that are set to undefined', () => {
49
+ expect(diff(
50
+ {a: 1},
51
+ {a: undefined},
52
+ {a: true}
53
+ )).toEqual({a: null});
54
+ });
55
+
56
+ it('should ignore invalid fields', () => {
57
+ expect(diff(
58
+ {a: 1},
59
+ {b: 2},
60
+ {}
61
+ )).toEqual(null);
62
+ });
63
+
64
+ it('should use the diff attribute', () => {
65
+ var diffA = jest.genMockFunction().mockImpl((a, b) => true);
66
+ var diffB = jest.genMockFunction().mockImpl((a, b) => false);
67
+ expect(diff(
68
+ {a: [1], b: [3]},
69
+ {a: [2], b: [4]},
70
+ {a: {diff: diffA}, b: {diff: diffB}}
71
+ )).toEqual({a: [2]});
72
+ expect(diffA).toBeCalledWith([1], [2]);
73
+ expect(diffB).toBeCalledWith([3], [4]);
74
+ });
75
+
76
+ it('should not use the diff attribute on addition/removal', () => {
77
+ var diffA = jest.genMockFunction();
78
+ var diffB = jest.genMockFunction();
79
+ expect(diff(
80
+ {a: [1]},
81
+ {b: [2]},
82
+ {a: {diff: diffA}, b: {diff: diffB}}
83
+ )).toEqual({a: null, b: [2]});
84
+ expect(diffA).not.toBeCalled();
85
+ expect(diffB).not.toBeCalled();
86
+ });
87
+
88
+ it('should do deep diffs of Objects by default', () => {
89
+ expect(diff(
90
+ {a: [1], b: {k: [3,4]}, c: {k: [4,4]} },
91
+ {a: [2], b: {k: [3,4]}, c: {k: [4,5]} },
92
+ {a: true, b: true, c: true}
93
+ )).toEqual({a: [2], c: {k: [4,5]}});
94
+ });
95
+
96
+ it('should work with undefined styles', () => {
97
+ expect(diff(
98
+ { style: { a: '#ffffff', b: 1 } },
99
+ { style: undefined },
100
+ { style: { b: true } }
101
+ )).toEqual({ b: null });
102
+ expect(diff(
103
+ { style: undefined },
104
+ { style: { a: '#ffffff', b: 1 } },
105
+ { style: { b: true } }
106
+ )).toEqual({ b: 1 });
107
+ expect(diff(
108
+ { style: undefined },
109
+ { style: undefined },
110
+ { style: { b: true } }
111
+ )).toEqual(null);
112
+ });
113
+
114
+ it('should work with empty styles', () => {
115
+ expect(diff(
116
+ {a: 1, c: 3},
117
+ {},
118
+ {a: true, b: true}
119
+ )).toEqual({a: null});
120
+ expect(diff(
121
+ {},
122
+ {a: 1, c: 3},
123
+ {a: true, b: true}
124
+ )).toEqual({a: 1});
125
+ expect(diff(
126
+ {},
127
+ {},
128
+ {a: true, b: true}
129
+ )).toEqual(null);
130
+ });
131
+
132
+ it('should flatten nested styles and predefined styles', () => {
133
+ var validStyleAttribute = { someStyle: { foo: true, bar: true } };
134
+
135
+ expect(diff(
136
+ {},
137
+ { someStyle: [{ foo: 1 }, { bar: 2 }]},
138
+ validStyleAttribute
139
+ )).toEqual({ foo: 1, bar: 2 });
140
+
141
+ expect(diff(
142
+ { someStyle: [{ foo: 1 }, { bar: 2 }]},
143
+ {},
144
+ validStyleAttribute
145
+ )).toEqual({ foo: null, bar: null });
146
+
147
+ var barStyle = ReactNativePropRegistry.register({
148
+ bar: 3,
149
+ });
150
+
151
+ expect(diff(
152
+ {},
153
+ { someStyle: [[{ foo: 1 }, { foo: 2 }], barStyle]},
154
+ validStyleAttribute
155
+ )).toEqual({ foo: 2, bar: 3 });
156
+ });
157
+
158
+ it('should reset a value to a previous if it is removed', () => {
159
+ var validStyleAttribute = { someStyle: { foo: true, bar: true } };
160
+
161
+ expect(diff(
162
+ { someStyle: [{ foo: 1 }, { foo: 3 }]},
163
+ { someStyle: [{ foo: 1 }, { bar: 2 }]},
164
+ validStyleAttribute
165
+ )).toEqual({ foo: 1, bar: 2 });
166
+ });
167
+
168
+ it('should not clear removed props if they are still in another slot', () => {
169
+ var validStyleAttribute = { someStyle: { foo: true, bar: true } };
170
+
171
+ expect(diff(
172
+ { someStyle: [{}, { foo: 3, bar: 2 }]},
173
+ { someStyle: [{ foo: 3 }, { bar: 2 }]},
174
+ validStyleAttribute
175
+ )).toEqual({ foo: 3 }); // this should ideally be null. heuristic tradeoff.
176
+
177
+ expect(diff(
178
+ { someStyle: [{}, { foo: 3, bar: 2 }]},
179
+ { someStyle: [{ foo: 1, bar: 1 }, { bar: 2 }]},
180
+ validStyleAttribute
181
+ )).toEqual({ bar: 2, foo: 1 });
182
+ });
183
+
184
+ it('should clear a prop if a later style is explicit null/undefined', () => {
185
+ var validStyleAttribute = { someStyle: { foo: true, bar: true } };
186
+ expect(diff(
187
+ { someStyle: [{}, { foo: 3, bar: 2 }]},
188
+ { someStyle: [{ foo: 1 }, { bar: 2, foo: null }]},
189
+ validStyleAttribute
190
+ )).toEqual({ foo: null });
191
+
192
+ expect(diff(
193
+ { someStyle: [{ foo: 3 }, { foo: null, bar: 2 }]},
194
+ { someStyle: [{ foo: null }, { bar: 2 }]},
195
+ validStyleAttribute
196
+ )).toEqual({ foo: null });
197
+
198
+ expect(diff(
199
+ { someStyle: [{ foo: 1 }, { foo: null }]},
200
+ { someStyle: [{ foo: 2 }, { foo: null }]},
201
+ validStyleAttribute
202
+ )).toEqual({ foo: null }); // this should ideally be null. heuristic.
203
+
204
+ // Test the same case with object equality because an early bailout doesn't
205
+ // work in this case.
206
+ var fooObj = { foo: 3 };
207
+ expect(diff(
208
+ { someStyle: [{ foo: 1 }, fooObj]},
209
+ { someStyle: [{ foo: 2 }, fooObj]},
210
+ validStyleAttribute
211
+ )).toEqual({ foo: 3 }); // this should ideally be null. heuristic.
212
+
213
+ expect(diff(
214
+ { someStyle: [{ foo: 1 }, { foo: 3 }]},
215
+ { someStyle: [{ foo: 2 }, { foo: undefined }]},
216
+ validStyleAttribute
217
+ )).toEqual({ foo: null }); // this should ideally be null. heuristic.
218
+ });
219
+
220
+ // Function properties are just markers to native that events should be sent.
221
+ it('should convert functions to booleans', () => {
222
+ // Note that if the property changes from one function to another, we don't
223
+ // need to send an update.
224
+ expect(diff(
225
+ {a: function() { return 1; }, b: function() { return 2; }, c: 3},
226
+ {b: function() { return 9; }, c: function() { return 3; }, },
227
+ {a: true, b: true, c: true}
228
+ )).toEqual({a: null, c: true});
229
+ });
230
+
231
+ });
src/renderers/native/ReactNative/createReactNativeComponentClass.js
new
+46
@@ -0,0 +1,46 @@
1
+/**
2
+ * Copyright (c) 2015-present, Facebook, Inc.
3
+ * All rights reserved.
4
+ *
5
+ * This source code is licensed under the BSD-style license found in the
6
+ * LICENSE file in the root directory of this source tree. An additional grant
7
+ * of patent rights can be found in the PATENTS file in the same directory.
8
+ *
9
+ * @providesModule createReactNativeComponentClass
10
+ * @flow
11
+ */
12
+
13
+'use strict';
14
+
15
+var ReactNativeBaseComponent = require('ReactNativeBaseComponent');
16
+
17
+// See also ReactNativeBaseComponent
18
+type ReactNativeBaseComponentViewConfig = {
19
+ validAttributes: Object;
20
+ uiViewClassName: string;
21
+ propTypes?: Object,
22
+}
23
+
24
+/**
25
+ * @param {string} config iOS View configuration.
26
+ * @private
27
+ */
28
+var createReactNativeComponentClass = function(
29
+ viewConfig: ReactNativeBaseComponentViewConfig
30
+): ReactClass<any> {
31
+ var Constructor = function(element) {
32
+ this._currentElement = element;
33
+
34
+ this._rootNodeID = null;
35
+ this._renderedChildren = null;
36
+ };
37
+ Constructor.displayName = viewConfig.uiViewClassName;
38
+ Constructor.viewConfig = viewConfig;
39
+ Constructor.propTypes = viewConfig.propTypes;
40
+ Constructor.prototype = new ReactNativeBaseComponent(viewConfig);
41
+ Constructor.prototype.constructor = Constructor;
42
+
43
+ return ((Constructor: any): ReactClass);
44
+};
45
+
46
+module.exports = createReactNativeComponentClass;
src/renderers/native/ReactNative/findNodeHandle.js
new
+112
@@ -0,0 +1,112 @@
1
+/**
2
+ * Copyright (c) 2015-present, Facebook, Inc.
3
+ * All rights reserved.
4
+ *
5
+ * This source code is licensed under the BSD-style license found in the
6
+ * LICENSE file in the root directory of this source tree. An additional grant
7
+ * of patent rights can be found in the PATENTS file in the same directory.
8
+ *
9
+ * @providesModule findNodeHandle
10
+ * @flow
11
+ */
12
+
13
+'use strict';
14
+
15
+var ReactCurrentOwner = require('ReactCurrentOwner');
16
+var ReactInstanceMap = require('ReactInstanceMap');
17
+var ReactNativeTagHandles = require('ReactNativeTagHandles');
18
+
19
+var invariant = require('fbjs/lib/invariant');
20
+var warning = require('fbjs/lib/warning');
21
+
22
+/**
23
+ * ReactNative vs ReactWeb
24
+ * -----------------------
25
+ * React treats some pieces of data opaquely. This means that the information
26
+ * is first class (it can be passed around), but cannot be inspected. This
27
+ * allows us to build infrastructure that reasons about resources, without
28
+ * making assumptions about the nature of those resources, and this allows that
29
+ * infra to be shared across multiple platforms, where the resources are very
30
+ * different. General infra (such as `ReactMultiChild`) reasons opaquely about
31
+ * the data, but platform specific code (such as `ReactNativeBaseComponent`) can
32
+ * make assumptions about the data.
33
+ *
34
+ *
35
+ * `rootNodeID`, uniquely identifies a position in the generated native view
36
+ * tree. Many layers of composite components (created with `React.createClass`)
37
+ * can all share the same `rootNodeID`.
38
+ *
39
+ * `nodeHandle`: A sufficiently unambiguous way to refer to a lower level
40
+ * resource (dom node, native view etc). The `rootNodeID` is sufficient for web
41
+ * `nodeHandle`s, because the position in a tree is always enough to uniquely
42
+ * identify a DOM node (we never have nodes in some bank outside of the
43
+ * document). The same would be true for `ReactNative`, but we must maintain a
44
+ * mapping that we can send efficiently serializable
45
+ * strings across native boundaries.
46
+ *
47
+ * Opaque name TodaysWebReact FutureWebWorkerReact ReactNative
48
+ * ----------------------------------------------------------------------------
49
+ * nodeHandle N/A rootNodeID tag
50
+ */
51
+
52
+function findNodeHandle(componentOrHandle: any): ?number {
53
+ if (__DEV__) {
54
+ var owner = ReactCurrentOwner.current;
55
+ if (owner !== null) {
56
+ warning(
57
+ owner._warnedAboutRefsInRender,
58
+ '%s is accessing findNodeHandle inside its render(). ' +
59
+ 'render() should be a pure function of props and state. It should ' +
60
+ 'never access something that requires stale data from the previous ' +
61
+ 'render, such as refs. Move this logic to componentDidMount and ' +
62
+ 'componentDidUpdate instead.',
63
+ owner.getName() || 'A component'
64
+ );
65
+ owner._warnedAboutRefsInRender = true;
66
+ }
67
+ }
68
+ if (componentOrHandle == null) {
69
+ return null;
70
+ }
71
+ if (typeof componentOrHandle === 'number') {
72
+ // Already a node handle
73
+ return componentOrHandle;
74
+ }
75
+
76
+ var component = componentOrHandle;
77
+
78
+ // TODO (balpert): Wrap iOS native components in a composite wrapper, then
79
+ // ReactInstanceMap.get here will always succeed for mounted components
80
+ var internalInstance = ReactInstanceMap.get(component);
81
+ if (internalInstance) {
82
+ return ReactNativeTagHandles.rootNodeIDToTag[internalInstance._rootNodeID];
83
+ } else {
84
+ var rootNodeID = component._rootNodeID;
85
+ if (rootNodeID) {
86
+ return ReactNativeTagHandles.rootNodeIDToTag[rootNodeID];
87
+ } else {
88
+ invariant(
89
+ (
90
+ // Native
91
+ typeof component === 'object' &&
92
+ '_rootNodeID' in component
93
+ ) || (
94
+ // Composite
95
+ component.render != null &&
96
+ typeof component.render === 'function'
97
+ ),
98
+ 'findNodeHandle(...): Argument is not a component ' +
99
+ '(type: %s, keys: %s)',
100
+ typeof component,
101
+ Object.keys(component)
102
+ );
103
+ invariant(
104
+ false,
105
+ 'findNodeHandle(...): Unable to find node handle for unmounted ' +
106
+ 'component.'
107
+ );
108
+ }
109
+ }
110
+}
111
+
112
+module.exports = findNodeHandle;
src/renderers/native/vendor/react/browser/eventPlugins/PanResponder.js
new
+378
@@ -0,0 +1,378 @@
1
+/**
2
+ * @providesModule PanResponder
3
+ */
4
+
5
+"use strict";
6
+
7
+var TouchHistoryMath = require('TouchHistoryMath');
8
+
9
+var currentCentroidXOfTouchesChangedAfter =
10
+ TouchHistoryMath.currentCentroidXOfTouchesChangedAfter;
11
+var currentCentroidYOfTouchesChangedAfter =
12
+ TouchHistoryMath.currentCentroidYOfTouchesChangedAfter;
13
+var previousCentroidXOfTouchesChangedAfter =
14
+ TouchHistoryMath.previousCentroidXOfTouchesChangedAfter;
15
+var previousCentroidYOfTouchesChangedAfter =
16
+ TouchHistoryMath.previousCentroidYOfTouchesChangedAfter;
17
+var currentCentroidX = TouchHistoryMath.currentCentroidX;
18
+var currentCentroidY = TouchHistoryMath.currentCentroidY;
19
+
20
+/**
21
+ * `PanResponder` reconciles several touches into a single gesture. It makes
22
+ * single-touch gestures resilient to extra touches, and can be used to
23
+ * recognize simple multi-touch gestures.
24
+ *
25
+ * It provides a predictable wrapper of the responder handlers provided by the
26
+ * [gesture responder system](docs/gesture-responder-system.html).
27
+ * For each handler, it provides a new `gestureState` object alongside the
28
+ * native event object:
29
+ *
30
+ * ```
31
+ * onPanResponderMove: (event, gestureState) => {}
32
+ * ```
33
+ *
34
+ * A native event is a synthetic touch event with the following form:
35
+ *
36
+ * - `nativeEvent`
37
+ * + `changedTouches` - Array of all touch events that have changed since the last event
38
+ * + `identifier` - The ID of the touch
39
+ * + `locationX` - The X position of the touch, relative to the element
40
+ * + `locationY` - The Y position of the touch, relative to the element
41
+ * + `pageX` - The X position of the touch, relative to the root element
42
+ * + `pageY` - The Y position of the touch, relative to the root element
43
+ * + `target` - The node id of the element receiving the touch event
44
+ * + `timestamp` - A time identifier for the touch, useful for velocity calculation
45
+ * + `touches` - Array of all current touches on the screen
46
+ *
47
+ * A `gestureState` object has the following:
48
+ *
49
+ * - `stateID` - ID of the gestureState- persisted as long as there at least
50
+ * one touch on screen
51
+ * - `moveX` - the latest screen coordinates of the recently-moved touch
52
+ * - `moveY` - the latest screen coordinates of the recently-moved touch
53
+ * - `x0` - the screen coordinates of the responder grant
54
+ * - `y0` - the screen coordinates of the responder grant
55
+ * - `dx` - accumulated distance of the gesture since the touch started
56
+ * - `dy` - accumulated distance of the gesture since the touch started
57
+ * - `vx` - current velocity of the gesture
58
+ * - `vy` - current velocity of the gesture
59
+ * - `numberActiveTouches` - Number of touches currently on screen
60
+ *
61
+ * ### Basic Usage
62
+ *
63
+ * ```
64
+ * componentWillMount: function() {
65
+ * this._panResponder = PanResponder.create({
66
+ * // Ask to be the responder:
67
+ * onStartShouldSetPanResponder: (evt, gestureState) => true,
68
+ * onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
69
+ * onMoveShouldSetPanResponder: (evt, gestureState) => true,
70
+ * onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,
71
+ *
72
+ * onPanResponderGrant: (evt, gestureState) => {
73
+ * // The guesture has started. Show visual feedback so the user knows
74
+ * // what is happening!
75
+ *
76
+ * // gestureState.{x,y}0 will be set to zero now
77
+ * },
78
+ * onPanResponderMove: (evt, gestureState) => {
79
+ * // The most recent move distance is gestureState.move{X,Y}
80
+ *
81
+ * // The accumulated gesture distance since becoming responder is
82
+ * // gestureState.d{x,y}
83
+ * },
84
+ * onPanResponderTerminationRequest: (evt, gestureState) => true,
85
+ * onPanResponderRelease: (evt, gestureState) => {
86
+ * // The user has released all touches while this view is the
87
+ * // responder. This typically means a gesture has succeeded
88
+ * },
89
+ * onPanResponderTerminate: (evt, gestureState) => {
90
+ * // Another component has become the responder, so this gesture
91
+ * // should be cancelled
92
+ * },
93
+ * onShouldBlockNativeResponder: (evt, gestureState) => {
94
+ * // Returns whether this component should block native components from becoming the JS
95
+ * // responder. Returns true by default. Is currently only supported on android.
96
+ * return true;
97
+ * },
98
+ * });
99
+ * },
100
+ *
101
+ * render: function() {
102
+ * return (
103
+ * <View {...this._panResponder.panHandlers} />
104
+ * );
105
+ * },
106
+ *
107
+ * ```
108
+ *
109
+ * ### Working Example
110
+ *
111
+ * To see it in action, try the
112
+ * [PanResponder example in UIExplorer](https://github.com/facebook/react-native/blob/master/Examples/UIExplorer/PanResponderExample.js)
113
+ */
114
+
115
+var PanResponder = {
116
+
117
+ /**
118
+ *
119
+ * A graphical explanation of the touch data flow:
120
+ *
121
+ * +----------------------------+ +--------------------------------+
122
+ * | ResponderTouchHistoryStore | |TouchHistoryMath |
123
+ * +----------------------------+ +----------+---------------------+
124
+ * |Global store of touchHistory| |Allocation-less math util |
125
+ * |including activeness, start | |on touch history (centroids |
126
+ * |position, prev/cur position.| |and multitouch movement etc) |
127
+ * | | | |
128
+ * +----^-----------------------+ +----^---------------------------+
129
+ * | |
130
+ * | (records relevant history |
131
+ * | of touches relevant for |
132
+ * | implementing higher level |
133
+ * | gestures) |
134
+ * | |
135
+ * +----+-----------------------+ +----|---------------------------+
136
+ * | ResponderEventPlugin | | | Your App/Component |
137
+ * +----------------------------+ +----|---------------------------+
138
+ * |Negotiates which view gets | Low level | | High level |
139
+ * |onResponderMove events. | events w/ | +-+-------+ events w/ |
140
+ * |Also records history into | touchHistory| | Pan | multitouch + |
141
+ * |ResponderTouchHistoryStore. +---------------->Responder+-----> accumulative|
142
+ * +----------------------------+ attached to | | | distance and |
143
+ * each event | +---------+ velocity. |
144
+ * | |
145
+ * | |
146
+ * +--------------------------------+
147
+ *
148
+ *
149
+ *
150
+ * Gesture that calculates cumulative movement over time in a way that just
151
+ * "does the right thing" for multiple touches. The "right thing" is very
152
+ * nuanced. When moving two touches in opposite directions, the cumulative
153
+ * distance is zero in each dimension. When two touches move in parallel five
154
+ * pixels in the same direction, the cumulative distance is five, not ten. If
155
+ * two touches start, one moves five in a direction, then stops and the other
156
+ * touch moves fives in the same direction, the cumulative distance is ten.
157
+ *
158
+ * This logic requires a kind of processing of time "clusters" of touch events
159
+ * so that two touch moves that essentially occur in parallel but move every
160
+ * other frame respectively, are considered part of the same movement.
161
+ *
162
+ * Explanation of some of the non-obvious fields:
163
+ *
164
+ * - moveX/moveY: If no move event has been observed, then `(moveX, moveY)` is
165
+ * invalid. If a move event has been observed, `(moveX, moveY)` is the
166
+ * centroid of the most recently moved "cluster" of active touches.
167
+ * (Currently all move have the same timeStamp, but later we should add some
168
+ * threshold for what is considered to be "moving"). If a palm is
169
+ * accidentally counted as a touch, but a finger is moving greatly, the palm
170
+ * will move slightly, but we only want to count the single moving touch.
171
+ * - x0/y0: Centroid location (non-cumulative) at the time of becoming
172
+ * responder.
173
+ * - dx/dy: Cumulative touch distance - not the same thing as sum of each touch
174
+ * distance. Accounts for touch moves that are clustered together in time,
175
+ * moving the same direction. Only valid when currently responder (otherwise,
176
+ * it only represents the drag distance below the threshold).
177
+ * - vx/vy: Velocity.
178
+ */
179
+
180
+ _initializeGestureState: function(gestureState) {
181
+ gestureState.moveX = 0;
182
+ gestureState.moveY = 0;
183
+ gestureState.x0 = 0;
184
+ gestureState.y0 = 0;
185
+ gestureState.dx = 0;
186
+ gestureState.dy = 0;
187
+ gestureState.vx = 0;
188
+ gestureState.vy = 0;
189
+ gestureState.numberActiveTouches = 0;
190
+ // All `gestureState` accounts for timeStamps up until:
191
+ gestureState._accountsForMovesUpTo = 0;
192
+ },
193
+
194
+ /**
195
+ * This is nuanced and is necessary. It is incorrect to continuously take all
196
+ * active *and* recently moved touches, find the centroid, and track how that
197
+ * result changes over time. Instead, we must take all recently moved
198
+ * touches, and calculate how the centroid has changed just for those
199
+ * recently moved touches, and append that change to an accumulator. This is
200
+ * to (at least) handle the case where the user is moving three fingers, and
201
+ * then one of the fingers stops but the other two continue.
202
+ *
203
+ * This is very different than taking all of the recently moved touches and
204
+ * storing their centroid as `dx/dy`. For correctness, we must *accumulate
205
+ * changes* in the centroid of recently moved touches.
206
+ *
207
+ * There is also some nuance with how we handle multiple moved touches in a
208
+ * single event. With the way `ReactNativeEventEmitter` dispatches touches as
209
+ * individual events, multiple touches generate two 'move' events, each of
210
+ * them triggering `onResponderMove`. But with the way `PanResponder` works,
211
+ * all of the gesture inference is performed on the first dispatch, since it
212
+ * looks at all of the touches (even the ones for which there hasn't been a
213
+ * native dispatch yet). Therefore, `PanResponder` does not call
214
+ * `onResponderMove` passed the first dispatch. This diverges from the
215
+ * typical responder callback pattern (without using `PanResponder`), but
216
+ * avoids more dispatches than necessary.
217
+ */
218
+ _updateGestureStateOnMove: function(gestureState, touchHistory) {
219
+ gestureState.numberActiveTouches = touchHistory.numberActiveTouches;
220
+ gestureState.moveX = currentCentroidXOfTouchesChangedAfter(
221
+ touchHistory,
222
+ gestureState._accountsForMovesUpTo
223
+ );
224
+ gestureState.moveY = currentCentroidYOfTouchesChangedAfter(
225
+ touchHistory,
226
+ gestureState._accountsForMovesUpTo
227
+ );
228
+ var movedAfter = gestureState._accountsForMovesUpTo;
229
+ var prevX = previousCentroidXOfTouchesChangedAfter(touchHistory, movedAfter);
230
+ var x = currentCentroidXOfTouchesChangedAfter(touchHistory, movedAfter);
231
+ var prevY = previousCentroidYOfTouchesChangedAfter(touchHistory, movedAfter);
232
+ var y = currentCentroidYOfTouchesChangedAfter(touchHistory, movedAfter);
233
+ var nextDX = gestureState.dx + (x - prevX);
234
+ var nextDY = gestureState.dy + (y - prevY);
235
+
236
+ // TODO: This must be filtered intelligently.
237
+ var dt =
238
+ (touchHistory.mostRecentTimeStamp - gestureState._accountsForMovesUpTo);
239
+ gestureState.vx = (nextDX - gestureState.dx) / dt;
240
+ gestureState.vy = (nextDY - gestureState.dy) / dt;
241
+
242
+ gestureState.dx = nextDX;
243
+ gestureState.dy = nextDY;
244
+ gestureState._accountsForMovesUpTo = touchHistory.mostRecentTimeStamp;
245
+ },
246
+
247
+ /**
248
+ * @param {object} config Enhanced versions of all of the responder callbacks
249
+ * that provide not only the typical `ResponderSyntheticEvent`, but also the
250
+ * `PanResponder` gesture state. Simply replace the word `Responder` with
251
+ * `PanResponder` in each of the typical `onResponder*` callbacks. For
252
+ * example, the `config` object would look like:
253
+ *
254
+ * - `onMoveShouldSetPanResponder: (e, gestureState) => {...}`
255
+ * - `onMoveShouldSetPanResponderCapture: (e, gestureState) => {...}`
256
+ * - `onStartShouldSetPanResponder: (e, gestureState) => {...}`
257
+ * - `onStartShouldSetPanResponderCapture: (e, gestureState) => {...}`
258
+ * - `onPanResponderReject: (e, gestureState) => {...}`
259
+ * - `onPanResponderGrant: (e, gestureState) => {...}`
260
+ * - `onPanResponderStart: (e, gestureState) => {...}`
261
+ * - `onPanResponderEnd: (e, gestureState) => {...}`
262
+ * - `onPanResponderRelease: (e, gestureState) => {...}`
263
+ * - `onPanResponderMove: (e, gestureState) => {...}`
264
+ * - `onPanResponderTerminate: (e, gestureState) => {...}`
265
+ * - `onPanResponderTerminationRequest: (e, gestureState) => {...}`
266
+ * - `onShouldBlockNativeResponder: (e, gestureState) => {...}`
267
+ *
268
+ * In general, for events that have capture equivalents, we update the
269
+ * gestureState once in the capture phase and can use it in the bubble phase
270
+ * as well.
271
+ *
272
+ * Be careful with onStartShould* callbacks. They only reflect updated
273
+ * `gestureState` for start/end events that bubble/capture to the Node.
274
+ * Once the node is the responder, you can rely on every start/end event
275
+ * being processed by the gesture and `gestureState` being updated
276
+ * accordingly. (numberActiveTouches) may not be totally accurate unless you
277
+ * are the responder.
278
+ */
279
+ create: function(config) {
280
+ var gestureState = {
281
+ // Useful for debugging
282
+ stateID: Math.random(),
283
+ };
284
+ PanResponder._initializeGestureState(gestureState);
285
+ var panHandlers = {
286
+ onStartShouldSetResponder: function(e) {
287
+ return config.onStartShouldSetPanResponder === undefined ? false :
288
+ config.onStartShouldSetPanResponder(e, gestureState);
289
+ },
290
+ onMoveShouldSetResponder: function(e) {
291
+ return config.onMoveShouldSetPanResponder === undefined ? false :
292
+ config.onMoveShouldSetPanResponder(e, gestureState);
293
+ },
294
+ onStartShouldSetResponderCapture: function(e) {
295
+ // TODO: Actually, we should reinitialize the state any time
296
+ // touches.length increases from 0 active to > 0 active.
297
+ if (e.nativeEvent.touches.length === 1) {
298
+ PanResponder._initializeGestureState(gestureState);
299
+ }
300
+ gestureState.numberActiveTouches = e.touchHistory.numberActiveTouches;
301
+ return config.onStartShouldSetPanResponderCapture !== undefined ?
302
+ config.onStartShouldSetPanResponderCapture(e, gestureState) : false;
303
+ },
304
+
305
+ onMoveShouldSetResponderCapture: function(e) {
306
+ var touchHistory = e.touchHistory;
307
+ // Responder system incorrectly dispatches should* to current responder
308
+ // Filter out any touch moves past the first one - we would have
309
+ // already processed multi-touch geometry during the first event.
310
+ if (gestureState._accountsForMovesUpTo === touchHistory.mostRecentTimeStamp) {
311
+ return false;
312
+ }
313
+ PanResponder._updateGestureStateOnMove(gestureState, touchHistory);
314
+ return config.onMoveShouldSetPanResponderCapture ?
315
+ config.onMoveShouldSetPanResponderCapture(e, gestureState) : false;
316
+ },
317
+
318
+ onResponderGrant: function(e) {
319
+ gestureState.x0 = currentCentroidX(e.touchHistory);
320
+ gestureState.y0 = currentCentroidY(e.touchHistory);
321
+ gestureState.dx = 0;
322
+ gestureState.dy = 0;
323
+ config.onPanResponderGrant && config.onPanResponderGrant(e, gestureState);
324
+ // TODO: t7467124 investigate if this can be removed
325
+ return config.onShouldBlockNativeResponder === undefined ? true :
326
+ config.onShouldBlockNativeResponder();
327
+ },
328
+
329
+ onResponderReject: function(e) {
330
+ config.onPanResponderReject && config.onPanResponderReject(e, gestureState);
331
+ },
332
+
333
+ onResponderRelease: function(e) {
334
+ config.onPanResponderRelease && config.onPanResponderRelease(e, gestureState);
335
+ PanResponder._initializeGestureState(gestureState);
336
+ },
337
+
338
+ onResponderStart: function(e) {
339
+ var touchHistory = e.touchHistory;
340
+ gestureState.numberActiveTouches = touchHistory.numberActiveTouches;
341
+ config.onPanResponderStart && config.onPanResponderStart(e, gestureState);
342
+ },
343
+
344
+ onResponderMove: function(e) {
345
+ var touchHistory = e.touchHistory;
346
+ // Guard against the dispatch of two touch moves when there are two
347
+ // simultaneously changed touches.
348
+ if (gestureState._accountsForMovesUpTo === touchHistory.mostRecentTimeStamp) {
349
+ return;
350
+ }
351
+ // Filter out any touch moves past the first one - we would have
352
+ // already processed multi-touch geometry during the first event.
353
+ PanResponder._updateGestureStateOnMove(gestureState, touchHistory);
354
+ config.onPanResponderMove && config.onPanResponderMove(e, gestureState);
355
+ },
356
+
357
+ onResponderEnd: function(e) {
358
+ var touchHistory = e.touchHistory;
359
+ gestureState.numberActiveTouches = touchHistory.numberActiveTouches;
360
+ config.onPanResponderEnd && config.onPanResponderEnd(e, gestureState);
361
+ },
362
+
363
+ onResponderTerminate: function(e) {
364
+ config.onPanResponderTerminate &&
365
+ config.onPanResponderTerminate(e, gestureState);
366
+ PanResponder._initializeGestureState(gestureState);
367
+ },
368
+
369
+ onResponderTerminationRequest: function(e) {
370
+ return config.onPanResponderTerminationRequest === undefined ? true :
371
+ config.onPanResponderTerminationRequest(e, gestureState);
372
+ },
373
+ };
374
+ return {panHandlers: panHandlers};
375
+ },
376
+};
377
+
378
+module.exports = PanResponder;
src/renderers/native/vendor/react/browser/eventPlugins/TouchHistoryMath.js
new
+122
@@ -0,0 +1,122 @@
1
+/**
2
+ * @providesModule TouchHistoryMath
3
+ */
4
+
5
+"use strict";
6
+
7
+var TouchHistoryMath = {
8
+ /**
9
+ * This code is optimized and not intended to look beautiful. This allows
10
+ * computing of touch centroids that have moved after `touchesChangedAfter`
11
+ * timeStamp. You can compute the current centroid involving all touches
12
+ * moves after `touchesChangedAfter`, or you can compute the previous
13
+ * centroid of all touches that were moved after `touchesChangedAfter`.
14
+ *
15
+ * @param {TouchHistoryMath} touchHistory Standard Responder touch track
16
+ * data.
17
+ * @param {number} touchesChangedAfter timeStamp after which moved touches
18
+ * are considered "actively moving" - not just "active".
19
+ * @param {boolean} isXAxis Consider `x` dimension vs. `y` dimension.
20
+ * @param {boolean} ofCurrent Compute current centroid for actively moving
21
+ * touches vs. previous centroid of now actively moving touches.
22
+ * @return {number} value of centroid in specified dimension.
23
+ */
24
+ centroidDimension: function(touchHistory, touchesChangedAfter, isXAxis, ofCurrent) {
25
+ var touchBank = touchHistory.touchBank;
26
+ var total = 0;
27
+ var count = 0;
28
+
29
+ var oneTouchData = touchHistory.numberActiveTouches === 1 ?
30
+ touchHistory.touchBank[touchHistory.indexOfSingleActiveTouch] : null;
31
+
32
+ if (oneTouchData !== null) {
33
+ if (oneTouchData.touchActive && oneTouchData.currentTimeStamp > touchesChangedAfter) {
34
+ total += ofCurrent && isXAxis ? oneTouchData.currentPageX :
35
+ ofCurrent && !isXAxis ? oneTouchData.currentPageY :
36
+ !ofCurrent && isXAxis ? oneTouchData.previousPageX :
37
+ oneTouchData.previousPageY;
38
+ count = 1;
39
+ }
40
+ } else {
41
+ for (var i = 0; i < touchBank.length; i++) {
42
+ var touchTrack = touchBank[i];
43
+ if (touchTrack !== null &&
44
+ touchTrack !== undefined &&
45
+ touchTrack.touchActive &&
46
+ touchTrack.currentTimeStamp >= touchesChangedAfter) {
47
+ var toAdd; // Yuck, program temporarily in invalid state.
48
+ if (ofCurrent && isXAxis) {
49
+ toAdd = touchTrack.currentPageX;
50
+ } else if (ofCurrent && !isXAxis) {
51
+ toAdd = touchTrack.currentPageY;
52
+ } else if (!ofCurrent && isXAxis) {
53
+ toAdd = touchTrack.previousPageX;
54
+ } else {
55
+ toAdd = touchTrack.previousPageY;
56
+ }
57
+ total += toAdd;
58
+ count++;
59
+ }
60
+ }
61
+ }
62
+ return count > 0 ? total / count : TouchHistoryMath.noCentroid;
63
+ },
64
+
65
+ currentCentroidXOfTouchesChangedAfter: function(touchHistory, touchesChangedAfter) {
66
+ return TouchHistoryMath.centroidDimension(
67
+ touchHistory,
68
+ touchesChangedAfter,
69
+ true, // isXAxis
70
+ true // ofCurrent
71
+ );
72
+ },
73
+
74
+ currentCentroidYOfTouchesChangedAfter: function(touchHistory, touchesChangedAfter) {
75
+ return TouchHistoryMath.centroidDimension(
76
+ touchHistory,
77
+ touchesChangedAfter,
78
+ false, // isXAxis
79
+ true // ofCurrent
80
+ );
81
+ },
82
+
83
+ previousCentroidXOfTouchesChangedAfter: function(touchHistory, touchesChangedAfter) {
84
+ return TouchHistoryMath.centroidDimension(
85
+ touchHistory,
86
+ touchesChangedAfter,
87
+ true, // isXAxis
88
+ false // ofCurrent
89
+ );
90
+ },
91
+
92
+ previousCentroidYOfTouchesChangedAfter: function(touchHistory, touchesChangedAfter) {
93
+ return TouchHistoryMath.centroidDimension(
94
+ touchHistory,
95
+ touchesChangedAfter,
96
+ false, // isXAxis
97
+ false // ofCurrent
98
+ );
99
+ },
100
+
101
+ currentCentroidX: function(touchHistory) {
102
+ return TouchHistoryMath.centroidDimension(
103
+ touchHistory,
104
+ 0, // touchesChangedAfter
105
+ true, // isXAxis
106
+ true // ofCurrent
107
+ );
108
+ },
109
+
110
+ currentCentroidY: function(touchHistory) {
111
+ return TouchHistoryMath.centroidDimension(
112
+ touchHistory,
113
+ 0, // touchesChangedAfter
114
+ false, // isXAxis
115
+ true // ofCurrent
116
+ );
117
+ },
118
+
119
+ noCentroid: -1,
120
+};
121
+
122
+module.exports = TouchHistoryMath;
src/renderers/native/vendor/react/core/clamp.js
new
+35
@@ -0,0 +1,35 @@
1
+/**
2
+ * @generated SignedSource<<ec51291ea6059cf23faa74f8644d17b1>>
3
+ *
4
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
5
+ * !! This file is a check-in of a static_upstream project! !!
6
+ * !! !!
7
+ * !! You should not modify this file directly. Instead: !!
8
+ * !! 1) Use `fjs use-upstream` to temporarily replace this with !!
9
+ * !! the latest version from upstream. !!
10
+ * !! 2) Make your changes, test them, etc. !!
11
+ * !! 3) Use `fjs push-upstream` to copy your changes back to !!
12
+ * !! static_upstream. !!
13
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
14
+ *
15
+ * @providesModule clamp
16
+ * @typechecks
17
+ */
18
+
19
+ /**
20
+ * @param {number} value
21
+ * @param {number} min
22
+ * @param {number} max
23
+ * @return {number}
24
+ */
25
+function clamp(min, value, max) {
26
+ if (value < min) {
27
+ return min;
28
+ }
29
+ if (value > max) {
30
+ return max;
31
+ }
32
+ return value;
33
+}
34
+
35
+module.exports = clamp;
src/renderers/native/vendor/react/platform/NodeHandle.js
new
+83
@@ -0,0 +1,83 @@
1
+/**
2
+ * @providesModule NodeHandle
3
+ */
4
+
5
+ /**
6
+ * A "handle" is a serializable representation of the underlying platform's
7
+ * native node abstraction. This allows reasoning about nodes behind a thread
8
+ * (worker) boundary. On some platforms (DOM main thread) the node handle *is*
9
+ * an actual DOM node - `NodeHandle` (and potentially other libraries)
10
+ * abstract away those differences so you can write code that doesn't depend
11
+ * on whether or not you are running in a worker. For example, you could write
12
+ * application code:
13
+ *
14
+ *
15
+ * SomeLibrary.measureNodeHandle(myNodeHandle, cb)
16
+ *
17
+ * Where `measureNodeHandle` knows how to handle actual DOM nodes if running
18
+ * in a worker thread, and knows how to handle numeric IDs if running in a
19
+ * worker thread.
20
+ *
21
+ * The only other requirement of a platform/environment is that it always be
22
+ * possible to extract the React rootNodeID in a blocking manner (see
23
+ * `getRootNodeID`).
24
+ *
25
+ * +------------------+ +------------------+ +------------------+
26
+ * | | | | | |
27
+ * | ReactJS | | YourUtilities | | Animation Utils |
28
+ * | | | | | |
29
+ * +------------------+ +------------------+ +------------------+
30
+ *
31
+ * +------------------------------------------------------------+
32
+ * | Async Platform Independent Node Interface |
33
+ * +------------------------------------------------------------+
34
+ * | |
35
+ * | NodeIterface: |
36
+ * | -measure(nodeHandle, cb) |
37
+ * | -setProperties(nodeHandle, cb) |
38
+ * | -manageChildren(nodeHandle, nodeHandles, cb) |
39
+ * | ... |
40
+ * | |
41
+ * | Note: This may be a simplification. We could break up much |
42
+ * | of this functionality into several smaller libraries, each |
43
+ * | one requiring a . |
44
+ * +------------------------------------------------------------+
45
+ *
46
+ * +------------------------------------------------------------+
47
+ * | Platform Implementations |
48
+ * | ----------------------------------------- |
49
+ * | React Canvas | React DOM Worker | React DOM main |
50
+ * +------------------------------------------------------------+
51
+ * | | | |
52
+ * |-measure(..) |-measure(..) |-measure(..) |
53
+ * |-setProperties(..) |-setProperties(..) |-setProperties(..) |
54
+ * |-manageChildren(..)|-manageChildren(..) |-manageChildren(..)|
55
+ * | ... | ... | ... |
56
+ * +-----------------------------o------------------------------+
57
+ * | Worker simply ^
58
+ * | marshals commands |
59
+ * | to Web DOM thread. |
60
+ * +---------------------+
61
+ */
62
+var NodeHandle = {
63
+ /**
64
+ * Injection
65
+ */
66
+ injection: {
67
+ injectImplementation: function(Impl) {
68
+ NodeHandle._Implementation = Impl;
69
+ }
70
+ },
71
+
72
+ _Implementation: null,
73
+
74
+ /**
75
+ * @param {NodeHandle} nodeHandle The handle to the low level resource.
76
+ * @return {string} React root node ID.
77
+ */
78
+ getRootNodeID: function(nodeHandle) {
79
+ return NodeHandle._Implementation.getRootNodeID(nodeHandle);
80
+ }
81
+};
82
+
83
+module.exports = NodeHandle;
src/renderers/native/vendor/react/platformImplementations/universal/worker/UniversalWorkerNodeHandle.js
new
+19
@@ -0,0 +1,19 @@
1
+/**
2
+ * @providesModule UniversalWorkerNodeHandle
3
+ */
4
+
5
+var ReactNativeTagHandles = require('ReactNativeTagHandles');
6
+
7
+var invariant = require('fbjs/lib/invariant');
8
+
9
+var UniversalWorkerNodeHandle = {
10
+ getRootNodeID: function(nodeHandle) {
11
+ invariant(
12
+ nodeHandle !== undefined && nodeHandle !== null && nodeHandle !== 0,
13
+ 'No node handle defined'
14
+ );
15
+ return ReactNativeTagHandles.tagToRootNodeID[nodeHandle];
16
+ }
17
+};
18
+
19
+module.exports = UniversalWorkerNodeHandle;
src/renderers/native/vendor/react/vendor/core/ExecutionEnvironment.android.js
new
+51
@@ -0,0 +1,51 @@
1
+/**
2
+ * Copyright 2013-2014 Facebook, Inc.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ *
16
+ * @providesModule ExecutionEnvironment
17
+ *
18
+ * NB: This is a temporary override that has not yet been merged upstream. It is NOT actually part
19
+ * of react-core (yet)
20
+ *
21
+ * Stubs SignedSource<<7afee88a05412d0c4eef54817419648e>>
22
+ */
23
+
24
+/*jslint evil: true */
25
+
26
+"use strict";
27
+
28
+var canUseDOM = false;
29
+
30
+/**
31
+ * Simple, lightweight module assisting with the detection and context of
32
+ * Worker. Helps avoid circular dependencies and allows code to reason about
33
+ * whether or not they are in a Worker, even if they never include the main
34
+ * `ReactWorker` dependency.
35
+ */
36
+var ExecutionEnvironment = {
37
+
38
+ canUseDOM: canUseDOM,
39
+
40
+ canUseWorkers: typeof Worker !== 'undefined',
41
+
42
+ canUseEventListeners:
43
+ canUseDOM && !!(window.addEventListener || window.attachEvent),
44
+
45
+ canUseViewport: canUseDOM && !!window.screen,
46
+
47
+ isInWorker: !canUseDOM // For now, this is true - might change in the future.
48
+
49
+};
50
+
51
+module.exports = ExecutionEnvironment;
src/renderers/native/vendor/react/vendor/core/ExecutionEnvironment.ios.js
new
+48
@@ -0,0 +1,48 @@
1
+/**
2
+ * Copyright 2013-2014 Facebook, Inc.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ *
16
+ * @providesModule ExecutionEnvironment
17
+ *
18
+ * Stubs SignedSource<<7afee88a05412d0c4eef54817419648e>>
19
+ */
20
+
21
+/*jslint evil: true */
22
+
23
+"use strict";
24
+
25
+var canUseDOM = false;
26
+
27
+/**
28
+ * Simple, lightweight module assisting with the detection and context of
29
+ * Worker. Helps avoid circular dependencies and allows code to reason about
30
+ * whether or not they are in a Worker, even if they never include the main
31
+ * `ReactWorker` dependency.
32
+ */
33
+var ExecutionEnvironment = {
34
+
35
+ canUseDOM: canUseDOM,
36
+
37
+ canUseWorkers: typeof Worker !== 'undefined',
38
+
39
+ canUseEventListeners:
40
+ canUseDOM && !!(window.addEventListener || window.attachEvent),
41
+
42
+ canUseViewport: canUseDOM && !!window.screen,
43
+
44
+ isInWorker: !canUseDOM // For now, this is true - might change in the future.
45
+
46
+};
47
+
48
+module.exports = ExecutionEnvironment;