diff --git a/Examples/UIExplorer/ImageExample.js b/Examples/UIExplorer/ImageExample.js
index b115e0f0b..b4c54f997 100644
--- a/Examples/UIExplorer/ImageExample.js
+++ b/Examples/UIExplorer/ImageExample.js
@@ -226,10 +226,8 @@ exports.examples = [
Contain
@@ -238,10 +236,8 @@ exports.examples = [
Cover
@@ -250,10 +246,8 @@ exports.examples = [
Stretch
diff --git a/Libraries/Components/ScrollView/ScrollView.js b/Libraries/Components/ScrollView/ScrollView.js
index fc7fc7223..0308688b5 100644
--- a/Libraries/Components/ScrollView/ScrollView.js
+++ b/Libraries/Components/ScrollView/ScrollView.js
@@ -17,7 +17,6 @@ var PointPropType = require('PointPropType');
var RCTScrollView = require('NativeModules').UIManager.RCTScrollView;
var RCTScrollViewConsts = RCTScrollView.Constants;
var React = require('React');
-var ReactIOSTagHandles = require('ReactIOSTagHandles');
var ReactIOSViewAttributes = require('ReactIOSViewAttributes');
var RCTUIManager = require('NativeModules').UIManager;
var ScrollResponder = require('ScrollResponder');
@@ -32,6 +31,7 @@ var flattenStyle = require('flattenStyle');
var insetsDiffer = require('insetsDiffer');
var invariant = require('invariant');
var pointsDiffer = require('pointsDiffer');
+var requireNativeComponent = require('requireNativeComponent');
var PropTypes = React.PropTypes;
@@ -73,6 +73,12 @@ var ScrollView = React.createClass({
* the `alwaysBounce*` props are true. The default value is true.
*/
bounces: PropTypes.bool,
+ /**
+ * When true, gestures can drive zoom past min/max and the zoom will animate
+ * to the min/max value at gesture end, otherwise the zoom will not exceed
+ * the limits.
+ */
+ bouncesZoom: PropTypes.bool,
/**
* When true, the scroll view bounces horizontally when it reaches the end
* even if the content is smaller than the scroll view itself. The default
@@ -120,6 +126,16 @@ var ScrollView = React.createClass({
* instead of vertically in a column. The default value is false.
*/
horizontal: PropTypes.bool,
+ /**
+ * When true, the ScrollView will try to lock to only vertical or horizontal
+ * scrolling while dragging. The default value is false.
+ */
+ directionalLockEnabled: PropTypes.bool,
+ /**
+ * When false, once tracking starts, won't try to drag if the touch moves.
+ * The default value is true.
+ */
+ canCancelContentTouches: PropTypes.bool,
/**
* Determines whether the keyboard gets dismissed in response to a drag.
* - 'none' (the default), drags do not dismiss the keyboard.
@@ -359,6 +375,7 @@ if (Platform.OS === 'android') {
validAttributes: validAttributes,
uiViewClassName: 'RCTScrollView',
});
+ var RCTScrollView = requireNativeComponent('RCTScrollView', ScrollView);
}
module.exports = ScrollView;
diff --git a/Libraries/Components/SliderIOS/SliderIOS.js b/Libraries/Components/SliderIOS/SliderIOS.js
index ae2475d33..81815ba34 100644
--- a/Libraries/Components/SliderIOS/SliderIOS.js
+++ b/Libraries/Components/SliderIOS/SliderIOS.js
@@ -12,6 +12,7 @@
'use strict';
var NativeMethodsMixin = require('NativeMethodsMixin');
+var Platform = require('Platform');
var PropTypes = require('ReactPropTypes');
var React = require('React');
var ReactIOSViewAttributes = require('ReactIOSViewAttributes');
@@ -21,6 +22,7 @@ var View = require('View');
var createReactIOSNativeComponentClass =
require('createReactIOSNativeComponentClass');
var merge = require('merge');
+var requireNativeComponent = require('requireNativeComponent');
type Event = Object;
@@ -96,16 +98,20 @@ var styles = StyleSheet.create({
},
});
-var validAttributes = {
- ...ReactIOSViewAttributes.UIView,
- value: true,
- minimumValue: true,
- maximumValue: true,
-};
+if (Platform.OS === 'ios') {
+ var RCTSlider = requireNativeComponent('RCTSlider', SliderIOS);
+} else {
+ var validAttributes = {
+ ...ReactIOSViewAttributes.UIView,
+ value: true,
+ minimumValue: true,
+ maximumValue: true,
+ };
-var RCTSlider = createReactIOSNativeComponentClass({
- validAttributes: validAttributes,
- uiViewClassName: 'RCTSlider',
-});
+ var RCTSlider = createReactIOSNativeComponentClass({
+ validAttributes: validAttributes,
+ uiViewClassName: 'RCTSlider',
+ });
+}
module.exports = SliderIOS;
diff --git a/Libraries/Image/Image.ios.js b/Libraries/Image/Image.ios.js
index 5f3dfdd98..fed358e13 100644
--- a/Libraries/Image/Image.ios.js
+++ b/Libraries/Image/Image.ios.js
@@ -14,6 +14,7 @@
var EdgeInsetsPropType = require('EdgeInsetsPropType');
var NativeMethodsMixin = require('NativeMethodsMixin');
var NativeModules = require('NativeModules');
+var Platform = require('Platform');
var PropTypes = require('ReactPropTypes');
var ImageResizeMode = require('ImageResizeMode');
var ImageStylePropTypes = require('ImageStylePropTypes');
@@ -27,7 +28,9 @@ var flattenStyle = require('flattenStyle');
var insetsDiffer = require('insetsDiffer');
var invariant = require('invariant');
var merge = require('merge');
+var requireNativeComponent = require('requireNativeComponent');
var warning = require('warning');
+var verifyPropTypes = require('verifyPropTypes');
/**
* A react component for displaying different types of images,
@@ -64,6 +67,13 @@ var Image = React.createClass({
source: PropTypes.shape({
uri: PropTypes.string,
}),
+ /**
+ * A static image to display while downloading the final image off the
+ * network.
+ */
+ defaultSource: PropTypes.shape({
+ uri: PropTypes.string,
+ }),
/**
* Whether this element should be revealed as an accessible element.
*/
@@ -80,6 +90,11 @@ var Image = React.createClass({
* [Apple documentation](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIImage_Class/index.html#//apple_ref/occ/instm/UIImage/resizableImageWithCapInsets)
*/
capInsets: EdgeInsetsPropType,
+ /**
+ * Determines how to resize the image when the frame doesn't match the raw
+ * image dimensions.
+ */
+ resizeMode: PropTypes.oneOf(['cover', 'contain', 'stretch']),
style: StyleSheetPropType(ImageStylePropTypes),
/**
* A unique identifier for this element to be used in UI Automation
@@ -104,6 +119,12 @@ var Image = React.createClass({
},
render: function() {
+ for (var prop in nativeOnlyProps) {
+ if (this.props[prop] !== undefined) {
+ console.warn('Prop `' + prop + ' = ' + this.props[prop] + '` should ' +
+ 'not be set directly on Image.');
+ }
+ }
var style = flattenStyle([styles.base, this.props.style]);
invariant(style, "style must be initialized");
var source = this.props.source;
@@ -119,28 +140,36 @@ var Image = React.createClass({
if (this.props.style && this.props.style.tintColor) {
warning(RawImage === RCTStaticImage, 'tintColor style only supported on static images.');
}
-
+ var resizeMode = this.props.resizeMode || style.resizeMode;
var contentModes = NativeModules.UIManager.UIView.ContentMode;
- var resizeMode;
- if (style.resizeMode === ImageResizeMode.stretch) {
- resizeMode = contentModes.ScaleToFill;
- } else if (style.resizeMode === ImageResizeMode.contain) {
- resizeMode = contentModes.ScaleAspectFit;
+ var contentMode;
+ if (resizeMode === ImageResizeMode.stretch) {
+ contentMode = contentModes.ScaleToFill;
+ } else if (resizeMode === ImageResizeMode.contain) {
+ contentMode = contentModes.ScaleAspectFit;
} else { // ImageResizeMode.cover or undefined
- resizeMode = contentModes.ScaleAspectFill;
+ contentMode = contentModes.ScaleAspectFill;
}
var nativeProps = merge(this.props, {
style,
- resizeMode,
+ contentMode,
tintColor: style.tintColor,
});
+ if (Platform.OS === 'android') {
+ // TODO: update android native code to not need this
+ nativeProps.resizeMode = contentMode;
+ delete nativeProps.contentMode;
+ }
if (isStored) {
nativeProps.imageTag = source.uri;
} else {
nativeProps.src = source.uri;
}
+ if (this.props.defaultSource) {
+ nativeProps.defaultImageSrc = this.props.defaultSource.uri;
+ }
return ;
}
});
@@ -151,24 +180,39 @@ var styles = StyleSheet.create({
},
});
-var CommonImageViewAttributes = merge(ReactIOSViewAttributes.UIView, {
- accessible: true,
- accessibilityLabel: true,
- capInsets: {diff: insetsDiffer}, // UIEdgeInsets=UIEdgeInsetsZero
- imageTag: true,
- resizeMode: true,
+if (Platform.OS === 'android') {
+ var CommonImageViewAttributes = merge(ReactIOSViewAttributes.UIView, {
+ accessible: true,
+ accessibilityLabel: true,
+ capInsets: {diff: insetsDiffer}, // UIEdgeInsets=UIEdgeInsetsZero
+ imageTag: true,
+ resizeMode: true,
+ src: true,
+ testID: PropTypes.string,
+ });
+
+ var RCTStaticImage = createReactIOSNativeComponentClass({
+ validAttributes: merge(CommonImageViewAttributes, { tintColor: true }),
+ uiViewClassName: 'RCTStaticImage',
+ });
+
+ var RCTNetworkImage = createReactIOSNativeComponentClass({
+ validAttributes: merge(CommonImageViewAttributes, { defaultImageSrc: true }),
+ uiViewClassName: 'RCTNetworkImageView',
+ });
+} else {
+ var RCTStaticImage = requireNativeComponent('RCTStaticImage', null);
+ var RCTNetworkImage = requireNativeComponent('RCTNetworkImageView', null);
+}
+var nativeOnlyProps = {
src: true,
- testID: PropTypes.string,
-});
-
-var RCTStaticImage = createReactIOSNativeComponentClass({
- validAttributes: merge(CommonImageViewAttributes, { tintColor: true }),
- uiViewClassName: 'RCTStaticImage',
-});
-
-var RCTNetworkImage = createReactIOSNativeComponentClass({
- validAttributes: merge(CommonImageViewAttributes, { defaultImageSrc: true }),
- uiViewClassName: 'RCTNetworkImageView',
-});
+ defaultImageSrc: true,
+ imageTag: true,
+ contentMode: true,
+};
+if (__DEV__) {
+ verifyPropTypes(Image, RCTStaticImage.viewConfig, nativeOnlyProps);
+ verifyPropTypes(Image, RCTNetworkImage.viewConfig, nativeOnlyProps);
+}
module.exports = Image;
diff --git a/Libraries/Image/RCTNetworkImageViewManager.m b/Libraries/Image/RCTNetworkImageViewManager.m
index 005b726cf..2ecf69971 100644
--- a/Libraries/Image/RCTNetworkImageViewManager.m
+++ b/Libraries/Image/RCTNetworkImageViewManager.m
@@ -29,6 +29,6 @@ RCT_EXPORT_MODULE()
RCT_REMAP_VIEW_PROPERTY(defaultImageSrc, defaultImage, UIImage)
RCT_REMAP_VIEW_PROPERTY(src, imageURL, NSURL)
-RCT_REMAP_VIEW_PROPERTY(resizeMode, contentMode, UIViewContentMode)
+RCT_EXPORT_VIEW_PROPERTY(contentMode, UIViewContentMode)
@end
diff --git a/Libraries/Image/RCTStaticImageManager.m b/Libraries/Image/RCTStaticImageManager.m
index 2d80117e4..87a50d8fe 100644
--- a/Libraries/Image/RCTStaticImageManager.m
+++ b/Libraries/Image/RCTStaticImageManager.m
@@ -26,7 +26,7 @@ RCT_EXPORT_MODULE()
}
RCT_EXPORT_VIEW_PROPERTY(capInsets, UIEdgeInsets)
-RCT_REMAP_VIEW_PROPERTY(resizeMode, contentMode, UIViewContentMode)
+RCT_EXPORT_VIEW_PROPERTY(contentMode, UIViewContentMode)
RCT_CUSTOM_VIEW_PROPERTY(src, NSURL, RCTStaticImage)
{
if (json) {
diff --git a/Libraries/ReactIOS/ReactIOSStyleAttributes.js b/Libraries/ReactIOS/ReactIOSStyleAttributes.js
index b332bec21..5f83523e2 100644
--- a/Libraries/ReactIOS/ReactIOSStyleAttributes.js
+++ b/Libraries/ReactIOS/ReactIOSStyleAttributes.js
@@ -10,22 +10,23 @@
* @flow
*/
-"use strict";
+'use strict';
+var ImageStylePropTypes = require('ImageStylePropTypes');
var TextStylePropTypes = require('TextStylePropTypes');
var ViewStylePropTypes = require('ViewStylePropTypes');
-var deepDiffer = require('deepDiffer');
var keyMirror = require('keyMirror');
var matricesDiffer = require('matricesDiffer');
-var merge = require('merge');
+var sizesDiffer = require('sizesDiffer');
-var ReactIOSStyleAttributes = merge(
- keyMirror(ViewStylePropTypes),
- keyMirror(TextStylePropTypes)
-);
+var ReactIOSStyleAttributes = {
+ ...keyMirror(ViewStylePropTypes),
+ ...keyMirror(TextStylePropTypes),
+ ...keyMirror(ImageStylePropTypes),
+};
ReactIOSStyleAttributes.transformMatrix = { diff: matricesDiffer };
-ReactIOSStyleAttributes.shadowOffset = { diff: deepDiffer };
+ReactIOSStyleAttributes.shadowOffset = { diff: sizesDiffer };
module.exports = ReactIOSStyleAttributes;
diff --git a/Libraries/ReactIOS/requireNativeComponent.js b/Libraries/ReactIOS/requireNativeComponent.js
index 0231e7f5f..55ad8a6b9 100644
--- a/Libraries/ReactIOS/requireNativeComponent.js
+++ b/Libraries/ReactIOS/requireNativeComponent.js
@@ -20,18 +20,27 @@ var insetsDiffer = require('insetsDiffer');
var pointsDiffer = require('pointsDiffer');
var matricesDiffer = require('matricesDiffer');
var sizesDiffer = require('sizesDiffer');
+var verifyPropTypes = require('verifyPropTypes');
/**
* Used to create React components that directly wrap native component
* implementations. Config information is extracted from data exported from the
- * RCTUIManager module. It is still strongly preferred that you wrap the native
- * component in a hand-written component with full propTypes definitions and
- * other documentation.
+ * RCTUIManager 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 `wrapperComponent` 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 `wrapperComponent` 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`.
*/
-function requireNativeComponent(viewName: string): Function {
+function requireNativeComponent(
+ viewName: string,
+ wrapperComponent: ?Function
+): Function {
var viewConfig = RCTUIManager.viewConfigs && RCTUIManager.viewConfigs[viewName];
if (!viewConfig) {
return UnimplementedView;
@@ -46,6 +55,9 @@ function requireNativeComponent(viewName: string): Function {
var differ = TypeToDifferMap[nativeProps[key].type] || deepDiffer;
viewConfig.validAttributes[key] = {diff: differ};
}
+ if (__DEV__) {
+ wrapperComponent && verifyPropTypes(wrapperComponent, viewConfig);
+ }
return createReactIOSNativeComponentClass(viewConfig);
}
diff --git a/Libraries/ReactIOS/verifyPropTypes.js b/Libraries/ReactIOS/verifyPropTypes.js
new file mode 100644
index 000000000..032e572ec
--- /dev/null
+++ b/Libraries/ReactIOS/verifyPropTypes.js
@@ -0,0 +1,40 @@
+/**
+ * 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 ReactIOSStyleAttributes = require('ReactIOSStyleAttributes');
+var View = require('View');
+
+function verifyPropTypes(
+ component: Function,
+ viewConfig: Object,
+ nativePropsToIgnore?: Object
+) {
+ if (!viewConfig) {
+ return; // This happens for UnimplementedView.
+ }
+ var nativeProps = viewConfig.nativeProps;
+ for (var prop in viewConfig.nativeProps) {
+ if (!component.propTypes[prop] &&
+ !View.propTypes[prop] &&
+ !ReactIOSStyleAttributes[prop] &&
+ (!nativePropsToIgnore || !nativePropsToIgnore[prop])) {
+ throw new Error(
+ '`' + component.displayName + '` has no propType for native prop `' +
+ viewConfig.uiViewClassName + '.' + prop + '` of native type `' +
+ nativeProps[prop].type + '`'
+ );
+ }
+ }
+}
+
+module.exports = verifyPropTypes;
diff --git a/React/Views/RCTViewManager.m b/React/Views/RCTViewManager.m
index 9758a0944..3c8485374 100644
--- a/React/Views/RCTViewManager.m
+++ b/React/Views/RCTViewManager.m
@@ -61,7 +61,6 @@ RCT_EXPORT_MODULE()
#pragma mark - View properties
RCT_EXPORT_VIEW_PROPERTY(accessibilityLabel, NSString)
-RCT_EXPORT_VIEW_PROPERTY(hidden, BOOL)
RCT_EXPORT_VIEW_PROPERTY(backgroundColor, UIColor)
RCT_REMAP_VIEW_PROPERTY(accessible, isAccessibilityElement, BOOL)
RCT_REMAP_VIEW_PROPERTY(testID, accessibilityIdentifier, NSString)