Compare commits

...

5 Commits

Author SHA1 Message Date
Nicolas Gallagher
1963e9109a 0.0.48 2016-10-27 21:12:04 -07:00
Nicolas Gallagher
14072c7471 [fix] View event handling
Fix #238
2016-10-27 21:00:17 -07:00
Nicolas Gallagher
0af6dc00fc [change] Image 'source' dimensions and RN layout
Adds support for 'width' and 'height' set via the 'source' property.
Emulates RN image layout (i.e., no dimensions by default).

Fix #10
2016-10-23 19:19:52 -07:00
Nicolas Gallagher
c9d401f09a [fix] Image resizeMode style
Fixes an issue in production where 'resizeMode' is deleted from style
simple objects, preventing it from being applied at render time.

Fix #233
2016-10-23 14:50:25 -07:00
Nicolas Gallagher
8aeeed0ef7 [fix] accept number or string for flexBasis style
Fix #230
2016-10-20 10:36:40 -07:00
7 changed files with 70 additions and 37 deletions

View File

@@ -218,7 +218,7 @@ const examples = [
render: function() { render: function() {
return ( return (
<Image <Image
source={{uri: 'http://facebook.github.io/react/img/logo_og.png'}} source={{ uri: 'http://facebook.github.io/react/img/logo_og.png', width: 1200, height: 630 }}
style={styles.base} style={styles.base}
/> />
); );

View File

@@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import { storiesOf, action } from '@kadira/storybook'; import { storiesOf, action } from '@kadira/storybook';
import { ScrollView, StyleSheet, Text, View } from 'react-native' import { ScrollView, StyleSheet, Text, TouchableHighlight, View } from 'react-native'
storiesOf('component: ScrollView', module) storiesOf('component: ScrollView', module)
.add('vertical', () => ( .add('vertical', () => (
@@ -13,7 +13,7 @@ storiesOf('component: ScrollView', module)
> >
{Array.from({ length: 50 }).map((item, i) => ( {Array.from({ length: 50 }).map((item, i) => (
<View key={i} style={styles.box}> <View key={i} style={styles.box}>
<Text>{i}</Text> <TouchableHighlight onPress={() => {}}><Text>{i}</Text></TouchableHighlight>
</View> </View>
))} ))}
</ScrollView> </ScrollView>
@@ -39,7 +39,6 @@ storiesOf('component: ScrollView', module)
const styles = StyleSheet.create({ const styles = StyleSheet.create({
box: { box: {
alignItems: 'center',
flexGrow: 1, flexGrow: 1,
justifyContent: 'center', justifyContent: 'center',
borderWidth: 1 borderWidth: 1

View File

@@ -1,6 +1,6 @@
{ {
"name": "react-native-web", "name": "react-native-web",
"version": "0.0.47", "version": "0.0.48",
"description": "React Native for Web", "description": "React Native for Web",
"main": "dist/index.js", "main": "dist/index.js",
"files": [ "files": [

View File

@@ -34,8 +34,8 @@ suite('components/Image', () => {
test('sets background image when value is an object', () => { test('sets background image when value is an object', () => {
const defaultSource = { uri: 'https://google.com/favicon.ico' }; const defaultSource = { uri: 'https://google.com/favicon.ico' };
const image = shallow(<Image defaultSource={defaultSource} />); const image = shallow(<Image defaultSource={defaultSource} />);
const backgroundImage = StyleSheet.flatten(image.prop('style')).backgroundImage; const style = StyleSheet.flatten(image.prop('style'));
assert(backgroundImage.indexOf(defaultSource.uri) > -1); assert(style.backgroundImage.indexOf(defaultSource.uri) > -1);
}); });
test('sets background image when value is a string', () => { test('sets background image when value is a string', () => {
@@ -45,13 +45,30 @@ suite('components/Image', () => {
const backgroundImage = StyleSheet.flatten(image.prop('style')).backgroundImage; const backgroundImage = StyleSheet.flatten(image.prop('style')).backgroundImage;
assert(backgroundImage.indexOf(defaultSource) > -1); assert(backgroundImage.indexOf(defaultSource) > -1);
}); });
test('sets "height" and "width" styles if missing', () => {
const defaultSource = { uri: 'https://google.com/favicon.ico', height: 10, width: 20 };
const image = mount(<Image defaultSource={defaultSource} />);
const html = image.html();
assert(html.indexOf('height: 10px') > -1);
assert(html.indexOf('width: 20px') > -1);
});
test('does not override "height" and "width" styles', () => {
const defaultSource = { uri: 'https://google.com/favicon.ico', height: 10, width: 20 };
const image = mount(<Image defaultSource={defaultSource} style={{ height: 20, width: 40 }} />);
const html = image.html();
assert(html.indexOf('height: 20px') > -1);
assert(html.indexOf('width: 40px') > -1);
});
}); });
test('prop "onError"', function (done) { test('prop "onError"', function (done) {
this.timeout(5000); this.timeout(5000);
mount(<Image onError={onError} source={{ uri: 'https://google.com/favicon.icox' }} />); const image = mount(<Image onError={onError} source={{ uri: 'https://google.com/favicon.icox' }} />);
function onError(e) { function onError(e) {
assert.equal(e.nativeEvent.type, 'error'); assert.ok(e.nativeEvent.error);
image.unmount();
done(); done();
} }
}); });
@@ -63,6 +80,7 @@ suite('components/Image', () => {
assert.equal(e.nativeEvent.type, 'load'); assert.equal(e.nativeEvent.type, 'load');
const hasBackgroundImage = (image.html()).indexOf('url(&quot;https://google.com/favicon.ico&quot;)') > -1; const hasBackgroundImage = (image.html()).indexOf('url(&quot;https://google.com/favicon.ico&quot;)') > -1;
assert.equal(hasBackgroundImage, true); assert.equal(hasBackgroundImage, true);
image.unmount();
done(); done();
} }
}); });
@@ -74,6 +92,7 @@ suite('components/Image', () => {
assert.ok(true); assert.ok(true);
const hasBackgroundImage = (image.html()).indexOf('url(&quot;https://google.com/favicon.ico&quot;)') > -1; const hasBackgroundImage = (image.html()).indexOf('url(&quot;https://google.com/favicon.ico&quot;)') > -1;
assert.equal(hasBackgroundImage, true); assert.equal(hasBackgroundImage, true);
image.unmount();
done(); done();
} }
}); });
@@ -121,10 +140,11 @@ suite('components/Image', () => {
test('sets background image when value is an object', (done) => { test('sets background image when value is an object', (done) => {
const source = { uri: 'https://google.com/favicon.ico' }; const source = { uri: 'https://google.com/favicon.ico' };
mount(<Image onLoad={onLoad} source={source} />); const image = mount(<Image onLoad={onLoad} source={source} />);
function onLoad(e) { function onLoad(e) {
const src = e.nativeEvent.target.src; const src = e.nativeEvent.target.src;
assert.equal(src, source.uri); assert.equal(src, source.uri);
image.unmount();
done(); done();
} }
}); });
@@ -132,10 +152,11 @@ suite('components/Image', () => {
test('sets background image when value is a string', (done) => { test('sets background image when value is a string', (done) => {
// emulate require-ed asset // emulate require-ed asset
const source = 'https://google.com/favicon.ico'; const source = 'https://google.com/favicon.ico';
mount(<Image onLoad={onLoad} source={source} />); const image = mount(<Image onLoad={onLoad} source={source} />);
function onLoad(e) { function onLoad(e) {
const src = e.nativeEvent.target.src; const src = e.nativeEvent.target.src;
assert.equal(src, source); assert.equal(src, source);
image.unmount();
done(); done();
} }
}); });

View File

@@ -17,11 +17,20 @@ const STATUS_IDLE = 'IDLE';
const ImageSourcePropType = PropTypes.oneOfType([ const ImageSourcePropType = PropTypes.oneOfType([
PropTypes.shape({ PropTypes.shape({
uri: PropTypes.string.isRequired height: PropTypes.number,
uri: PropTypes.string.isRequired,
width: PropTypes.number
}), }),
PropTypes.string PropTypes.string
]); ]);
const resolveAssetDimensions = (source) => {
if (typeof source === 'object') {
const { height, width } = source;
return { height, width };
}
};
const resolveAssetSource = (source) => { const resolveAssetSource = (source) => {
return ((typeof source === 'object') ? source.uri : source) || null; return ((typeof source === 'object') ? source.uri : source) || null;
}; };
@@ -93,20 +102,26 @@ class Image extends Component {
} = this.props; } = this.props;
const displayImage = resolveAssetSource(!isLoaded ? defaultSource : source); const displayImage = resolveAssetSource(!isLoaded ? defaultSource : source);
const imageSizeStyle = resolveAssetDimensions(!isLoaded ? defaultSource : source);
const backgroundImage = displayImage ? `url("${displayImage}")` : null; const backgroundImage = displayImage ? `url("${displayImage}")` : null;
let style = StyleSheet.flatten(this.props.style); const originalStyle = StyleSheet.flatten(this.props.style);
const resizeMode = this.props.resizeMode || originalStyle.resizeMode || ImageResizeMode.cover;
const resizeMode = this.props.resizeMode || style.resizeMode || ImageResizeMode.cover; const style = StyleSheet.flatten([
// remove 'resizeMode' style, as it is not supported by View (N.B. styles are frozen in dev) styles.initial,
style = process.env.NODE_ENV !== 'production' ? { ...style } : style; imageSizeStyle,
originalStyle,
backgroundImage && { backgroundImage },
resizeModeStyles[resizeMode]
]);
// View doesn't support 'resizeMode' as a style
delete style.resizeMode; delete style.resizeMode;
/** /**
* Image is a non-stretching View. The image is displayed as a background * The image is displayed as a background image to support `resizeMode`.
* image to support `resizeMode`. The HTML image is hidden but used to * The HTML image is hidden but used to provide the correct responsive
* provide the correct responsive image dimensions, and to support the * image dimensions, and to support the image context menu. Child content
* image context menu. Child content is rendered into an element absolutely * is rendered into an element absolutely positioned over the image.
* positioned over the image.
*/ */
return ( return (
<View <View
@@ -114,12 +129,7 @@ class Image extends Component {
accessibilityRole='img' accessibilityRole='img'
accessible={accessible} accessible={accessible}
onLayout={onLayout} onLayout={onLayout}
style={[ style={style}
styles.initial,
style,
backgroundImage && { backgroundImage },
resizeModeStyles[resizeMode]
]}
testID={testID} testID={testID}
> >
{createDOMElement('img', { src: displayImage, style: styles.img })} {createDOMElement('img', { src: displayImage, style: styles.img })}
@@ -149,14 +159,18 @@ class Image extends Component {
} }
} }
_onError = (e) => { _onError = () => {
const { onError } = this.props; const { onError, source } = this.props;
const event = { nativeEvent: e };
this._destroyImageLoader(); this._destroyImageLoader();
this._updateImageState(STATUS_ERRORED);
this._onLoadEnd(); this._onLoadEnd();
if (onError) { onError(event); } this._updateImageState(STATUS_ERRORED);
if (onError) {
onError({
nativeEvent: {
error: `Failed to load resource ${resolveAssetSource(source)} (404)`
}
});
}
} }
_onLoad = (e) => { _onLoad = (e) => {
@@ -191,7 +205,6 @@ class Image extends Component {
const styles = StyleSheet.create({ const styles = StyleSheet.create({
initial: { initial: {
alignSelf: 'flex-start',
backgroundColor: 'transparent', backgroundColor: 'transparent',
backgroundPosition: 'center', backgroundPosition: 'center',
backgroundRepeat: 'no-repeat', backgroundRepeat: 'no-repeat',

View File

@@ -124,10 +124,10 @@ class View extends Component {
_normalizeEventForHandler(handler, handlerName) { _normalizeEventForHandler(handler, handlerName) {
// Browsers fire mouse events after touch events. This causes the // Browsers fire mouse events after touch events. This causes the
// ResponderEvents and their handlers to fire twice for Touchables. // 'onResponderRelease' handler to be called twice for Touchables.
// Auto-fix this issue by calling 'preventDefault' to cancel the mouse // Auto-fix this issue by calling 'preventDefault' to cancel the mouse
// events. // events.
const shouldCancelEvent = handlerName.indexOf('onResponder') === 0; const shouldCancelEvent = handlerName === 'onResponderRelease';
return (e) => { return (e) => {
e.nativeEvent = normalizeNativeEvent(e.nativeEvent); e.nativeEvent = normalizeNativeEvent(e.nativeEvent);

View File

@@ -36,7 +36,7 @@ const LayoutPropTypes = {
alignItems: oneOf([ 'baseline', 'center', 'flex-end', 'flex-start', 'stretch' ]), alignItems: oneOf([ 'baseline', 'center', 'flex-end', 'flex-start', 'stretch' ]),
alignSelf: oneOf([ 'auto', 'baseline', 'center', 'flex-end', 'flex-start', 'stretch' ]), alignSelf: oneOf([ 'auto', 'baseline', 'center', 'flex-end', 'flex-start', 'stretch' ]),
flex: number, flex: number,
flexBasis: string, flexBasis: numberOrString,
flexDirection: oneOf([ 'column', 'column-reverse', 'row', 'row-reverse' ]), flexDirection: oneOf([ 'column', 'column-reverse', 'row', 'row-reverse' ]),
flexGrow: number, flexGrow: number,
flexShrink: number, flexShrink: number,