@samitouri / QOS-React / commits / f463b731ee

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;