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