Compare commits

..

25 Commits

Author SHA1 Message Date
Nicolas Gallagher
3b661d8d6d 0.0.49 2016-11-03 09:16:00 -07:00
Nicolas Gallagher
22d20706e3 Add React Native TextInput examples 2016-11-03 08:56:26 -07:00
Nicolas Gallagher
0b2813b186 [fix] View when 'style' is not defined 2016-11-03 08:52:25 -07:00
Nicolas Gallagher
b248de552d Fix tests 2016-11-03 08:51:51 -07:00
Nicolas Gallagher
2b826dc7f4 [add] TextInput support for selection 2016-10-28 23:37:19 -07:00
Nicolas Gallagher
b46acd4f50 [fix] TextInputState focus management 2016-10-28 22:12:20 -07:00
Nicolas Gallagher
5a03cb25cb [add] TextInput support for blurOnSubmit and onSubmitEditing 2016-10-28 21:15:35 -07:00
Nicolas Gallagher
44e60d12e3 [change] TextInput support for autoCorrect and autoComplete 2016-10-28 10:51:05 -07:00
Nicolas Gallagher
fc60f8d332 [add] TextInput support for autoCapitalize 2016-10-28 10:36:06 -07:00
Nicolas Gallagher
2a65ca6fc0 [add] TextInput support for isFocused 2016-10-27 22:31:43 -07:00
Nicolas Gallagher
9db3bd7e67 [add] TextInput support for onKeyPress
Fix #215
2016-10-27 22:17:59 -07:00
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
Nicolas Gallagher
f5d0f73b4f 0.0.47 2016-10-13 10:30:49 -07:00
Xiaohan Zhang
ee7d367062 [fix] AsyncStorage.mergeItem to support deep merge
Mirrors behaviour of react-native
2016-10-13 10:27:51 -07:00
Nicolas Gallagher
dbd607ce47 0.0.46 2016-10-10 17:12:28 -07:00
Nicolas Gallagher
373cb38ca9 Fix lint error 2016-10-10 09:19:20 -07:00
Nicolas Gallagher
4576b42365 Correct the Image docs 2016-10-09 16:53:41 -07:00
Nicolas Gallagher
5a5707855b [fix] CSS reset 2016-10-04 16:26:03 -07:00
Paul Le Cam
0c76cc5d80 [fix] how ListView uses ScrollView
* Bind `_setScrollViewRef` to instance
* Fix getting ScrollView props for ListView
2016-09-19 16:24:25 -07:00
Nicolas Gallagher
d64df129b2 [fix] remove default 'input' border-radius 2016-09-12 11:46:47 -07:00
Nicolas Gallagher
763c5444ce Add 'ProgressBar' to README 2016-09-06 12:51:59 -07:00
22 changed files with 1210 additions and 174 deletions

View File

@@ -55,6 +55,7 @@ Exported modules:
* [`ActivityIndicator`](docs/components/ActivityIndicator.md)
* [`Image`](docs/components/Image.md)
* [`ListView`](docs/components/ListView.md)
* [`ProgressBar`](docs/components/ProgressBar.md)
* [`ScrollView`](docs/components/ScrollView.md)
* [`Switch`](docs/components/Switch.md)
* [`Text`](docs/components/Text.md)

View File

@@ -46,7 +46,7 @@ Invoked when load either succeeds or fails,
Invoked on load start.
**resizeMode**: oneOf('center', 'contain', 'cover', 'none', 'repeat', 'stretch') = 'stretch'
**resizeMode**: oneOf('center', 'contain', 'cover', 'none', 'repeat', 'stretch') = 'cover'
Determines how to resize the image when the frame doesn't match the raw image
dimensions.

View File

@@ -20,15 +20,37 @@ Unsupported React Native props:
[...View props](./View.md)
(web) **autoComplete**: bool = false
**autoCapitalize**: oneOf('characters', 'none', 'sentences', 'words') = 'sentences'
Indicates whether the value of the control can be automatically completed by the browser.
Automatically capitalize certain characters (only available in Chrome and iOS Safari).
* `characters`: Automatically capitalize all characters.
* `none`: Completely disables automatic capitalization
* `sentences`: Automatically capitalize the first letter of sentences.
* `words`: Automatically capitalize the first letter of words.
(web) **autoComplete**: string
Indicates whether the value of the control can be automatically completed by
the browser. [Accepted values](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input).
**autoCorrect**: bool = true
Automatically correct spelling mistakes (only available in iOS Safari).
**autoFocus**: bool = false
If true, focuses the input on `componentDidMount`. Only the first form element
If `true`, focuses the input on `componentDidMount`. Only the first form element
in a document with `autofocus` is focused.
**blurOnSubmit**: bool
If `true`, the text field will blur when submitted. The default value is `true`
for single-line fields and `false` for multiline fields. Note, for multiline
fields setting `blurOnSubmit` to `true` means that pressing return will blur
the field and trigger the `onSubmitEditing` event instead of inserting a
newline into the field.
**clearTextOnFocus**: bool = false
If `true`, clears the text field automatically when focused.
@@ -87,19 +109,19 @@ as an argument to the callback handler.
Callback that is called when the text input is focused.
(web) **onSelectionChange**: function
**onKeyPress**: function
Callback that is called when the text input's selection changes. The following
object is passed as an argument to the callback handler.
Callback that is called when a key is pressed. Pressed key value is passed as
an argument to the callback handler. Fires before `onChange` callbacks.
```js
{
selectionDirection,
selectionEnd,
selectionStart,
nativeEvent
}
```
**onSelectionChange**: function
Callback that is called when the text input's selection changes. This will be called with
`{ nativeEvent: { selection: { start, end } } }`.
**onSubmitEditing**: function
Callback that is called when the keyboard's submit button is pressed.
**placeholder**: string
@@ -117,6 +139,10 @@ passwords stay secure.
(Not available when `multiline` is `true`.)
**selection**: { start: number, end: ?number }
The start and end of the text input's selection. Set start and end to the same value to position the cursor.
**selectTextOnFocus**: bool = false
If `true`, all text will automatically be selected on focus.
@@ -152,6 +178,10 @@ Clear the text from the underlying DOM input.
Focus the underlying DOM input.
**isFocused()**
Returns `true` if the input is currently focused; `false` otherwise.
## Examples
```js

View File

@@ -218,7 +218,7 @@ const examples = [
render: function() {
return (
<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}
/>
);

View File

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

View File

@@ -1,41 +1,884 @@
import React from 'react';
import { storiesOf, action } from '@kadira/storybook';
import { StyleSheet, TextInput, View } from 'react-native'
import { StyleSheet, Text, TextInput, View } from 'react-native'
storiesOf('component: TextInput', module)
.add('tbd', () => (
<View>
<TextInput
defaultValue='Default textInput'
keyboardType='default'
onBlur={(e) => { console.log('TextInput.onBlur', e) }}
onChange={(e) => { console.log('TextInput.onChange', e) }}
onChangeText={(e) => { console.log('TextInput.onChangeText', e) }}
onFocus={(e) => { console.log('TextInput.onFocus', e) }}
onSelectionChange={(e) => { console.log('TextInput.onSelectionChange', e) }}
/>
<TextInput keyboardType='search' style={styles.textInput} />
<TextInput secureTextEntry style={styles.textInput} />
<TextInput defaultValue='read only' editable={false} style={styles.textInput} />
<TextInput
style={[ styles.textInput, { flex:1, height: 60, padding: 20, fontSize: 20, textAlign: 'center' } ]}
keyboardType='email-address' placeholder='you@domain.com' placeholderTextColor='red'
/>
<TextInput keyboardType='numeric' style={styles.textInput} />
<TextInput keyboardType='phone-pad' style={styles.textInput} />
<TextInput defaultValue='https://delete-me' keyboardType='url' placeholder='https://www.some-website.com' selectTextOnFocus style={styles.textInput} />
<TextInput
defaultValue='default value'
maxNumberOfLines={10}
multiline
numberOfLines={5}
style={styles.textInput}
/>
</View>
))
/**
* 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.
*
* The examples provided by Facebook are for non-commercial testing and
* evaluation purposes only.
*
* Facebook reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* @flow
*/
const styles = StyleSheet.create({
textInput: {
borderWidth: 1
class WithLabel extends React.Component {
render() {
return (
<View style={styles.labelContainer}>
<View style={styles.label}>
<Text>{this.props.label}</Text>
</View>
{this.props.children}
</View>
);
}
})
}
class TextEventsExample extends React.Component {
state = {
curText: '<No Event>',
prevText: '<No Event>',
prev2Text: '<No Event>',
prev3Text: '<No Event>',
};
updateText = (text) => {
this.setState((state) => {
return {
curText: text,
prevText: state.curText,
prev2Text: state.prevText,
prev3Text: state.prev2Text,
};
});
};
render() {
return (
<View>
<TextInput
autoCapitalize="none"
placeholder="Enter text to see events"
autoCorrect={false}
onFocus={() => this.updateText('onFocus')}
onBlur={() => this.updateText('onBlur')}
onChange={(event) => this.updateText(
'onChange text: ' + event.nativeEvent.text
)}
onEndEditing={(event) => this.updateText(
'onEndEditing text: ' + event.nativeEvent.text
)}
onSubmitEditing={(event) => this.updateText(
'onSubmitEditing text: ' + event.nativeEvent.text
)}
onSelectionChange={(event) => this.updateText(
'onSelectionChange range: ' +
event.nativeEvent.selection.start + ',' +
event.nativeEvent.selection.end
)}
onKeyPress={(event) => {
this.updateText('onKeyPress key: ' + event.nativeEvent.key);
}}
style={styles.default}
/>
<Text style={styles.eventLabel}>
{this.state.curText}{'\n'}
(prev: {this.state.prevText}){'\n'}
(prev2: {this.state.prev2Text}){'\n'}
(prev3: {this.state.prev3Text})
</Text>
</View>
);
}
}
class AutoExpandingTextInput extends React.Component {
state: any;
constructor(props) {
super(props);
this.state = {
text: 'React Native enables you to build world-class application experiences on native platforms using a consistent developer experience based on JavaScript and React. The focus of React Native is on developer efficiency across all the platforms you care about — learn once, write anywhere. Facebook uses React Native in multiple production apps and will continue investing in React Native.',
height: 0,
};
}
render() {
return (
<TextInput
{...this.props}
multiline={true}
onChangeText={(text) => {
this.setState({text});
}}
onContentSizeChange={(event) => {
this.setState({height: event.nativeEvent.contentSize.height});
}}
style={[styles.default, {height: Math.max(35, this.state.height)}]}
value={this.state.text}
/>
);
}
}
class RewriteExample extends React.Component {
state: any;
constructor(props) {
super(props);
this.state = {text: ''};
}
render() {
var limit = 20;
var remainder = limit - this.state.text.length;
var remainderColor = remainder > 5 ? 'blue' : 'red';
return (
<View style={styles.rewriteContainer}>
<TextInput
multiline={false}
maxLength={limit}
onChangeText={(text) => {
text = text.replace(/ /g, '_');
this.setState({text});
}}
style={styles.default}
value={this.state.text}
/>
<Text style={[styles.remainder, {color: remainderColor}]}>
{remainder}
</Text>
</View>
);
}
}
class RewriteExampleInvalidCharacters extends React.Component {
state: any;
constructor(props) {
super(props);
this.state = {text: ''};
}
render() {
return (
<View style={styles.rewriteContainer}>
<TextInput
multiline={false}
onChangeText={(text) => {
this.setState({text: text.replace(/\s/g, '')});
}}
style={styles.default}
value={this.state.text}
/>
</View>
);
}
}
class TokenizedTextExample extends React.Component {
state: any;
constructor(props) {
super(props);
this.state = {text: 'Hello #World'};
}
render() {
//define delimiter
let delimiter = /\s+/;
//split string
let _text = this.state.text;
let token, index, parts = [];
while (_text) {
delimiter.lastIndex = 0;
token = delimiter.exec(_text);
if (token === null) {
break;
}
index = token.index;
if (token[0].length === 0) {
index = 1;
}
parts.push(_text.substr(0, index));
parts.push(token[0]);
index = index + token[0].length;
_text = _text.slice(index);
}
parts.push(_text);
//highlight hashtags
parts = parts.map((text) => {
if (/^#/.test(text)) {
return <Text key={text} style={styles.hashtag}>{text}</Text>;
} else {
return text;
}
});
return (
<View>
<TextInput
multiline={true}
style={styles.multiline}
onChangeText={(text) => {
this.setState({text});
}}>
<Text>{parts}</Text>
</TextInput>
</View>
);
}
}
class BlurOnSubmitExample extends React.Component {
focusNextField = (nextField) => {
this.refs[nextField].focus();
};
render() {
return (
<View>
<TextInput
ref="1"
style={styles.default}
placeholder="blurOnSubmit = false"
returnKeyType="next"
blurOnSubmit={false}
onSubmitEditing={() => this.focusNextField('2')}
/>
<TextInput
ref="2"
style={styles.default}
keyboardType="email-address"
placeholder="blurOnSubmit = false"
returnKeyType="next"
blurOnSubmit={false}
onSubmitEditing={() => this.focusNextField('3')}
/>
<TextInput
ref="3"
style={styles.default}
keyboardType="url"
placeholder="blurOnSubmit = false"
returnKeyType="next"
blurOnSubmit={false}
onSubmitEditing={() => this.focusNextField('4')}
/>
<TextInput
ref="4"
style={styles.default}
keyboardType="numeric"
placeholder="blurOnSubmit = false"
blurOnSubmit={false}
onSubmitEditing={() => this.focusNextField('5')}
/>
<TextInput
ref="5"
style={styles.default}
keyboardType="numbers-and-punctuation"
placeholder="blurOnSubmit = true"
returnKeyType="done"
/>
</View>
);
}
}
type SelectionExampleState = {
selection: {
start: number;
end?: number;
};
value: string;
};
class SelectionExample extends React.Component {
state: SelectionExampleState;
_textInput: any;
constructor(props) {
super(props);
this.state = {
selection: {start: 0, end: 0},
value: props.value
};
}
onSelectionChange({nativeEvent: {selection}}) {
this.setState({selection});
}
getRandomPosition() {
var length = this.state.value.length;
return Math.round(Math.random() * length);
}
select(start, end) {
this._textInput.focus();
this.setState({selection: {start, end}});
}
selectRandom() {
var positions = [this.getRandomPosition(), this.getRandomPosition()].sort();
this.select(...positions);
}
placeAt(position) {
this.select(position, position);
}
placeAtRandom() {
this.placeAt(this.getRandomPosition());
}
render() {
var length = this.state.value.length;
return (
<View>
<TextInput
multiline={this.props.multiline}
onChangeText={(value) => this.setState({value})}
onSelectionChange={this.onSelectionChange.bind(this)}
ref={textInput => (this._textInput = textInput)}
selection={this.state.selection}
style={this.props.style}
value={this.state.value}
/>
<View>
<Text>
selection = {JSON.stringify(this.state.selection)}
</Text>
<Text onPress={this.placeAt.bind(this, 0)}>
Place at Start (0, 0)
</Text>
<Text onPress={this.placeAt.bind(this, length)}>
Place at End ({length}, {length})
</Text>
<Text onPress={this.placeAtRandom.bind(this)}>
Place at Random
</Text>
<Text onPress={this.select.bind(this, 0, length)}>
Select All
</Text>
<Text onPress={this.selectRandom.bind(this)}>
Select Random
</Text>
</View>
</View>
);
}
}
var styles = StyleSheet.create({
page: {
paddingBottom: 300,
},
default: {
height: 26,
borderWidth: 0.5,
borderColor: '#0f0f0f',
flex: 1,
fontSize: 13,
padding: 4,
},
multiline: {
borderWidth: 0.5,
borderColor: '#0f0f0f',
flex: 1,
fontSize: 13,
height: 50,
padding: 4,
marginBottom: 4,
},
multilineWithFontStyles: {
color: 'blue',
fontWeight: 'bold',
fontSize: 18,
fontFamily: 'Cochin',
height: 60,
},
multilineChild: {
width: 50,
height: 40,
position: 'absolute',
right: 5,
backgroundColor: 'red',
},
eventLabel: {
margin: 3,
fontSize: 12,
},
labelContainer: {
flexDirection: 'row',
marginVertical: 2,
flex: 1,
},
label: {
width: 115,
alignItems: 'flex-end',
marginRight: 10,
paddingTop: 2,
},
rewriteContainer: {
flexDirection: 'row',
alignItems: 'center',
},
remainder: {
textAlign: 'right',
width: 24,
},
hashtag: {
color: 'blue',
fontWeight: 'bold',
},
});
const examples = [
{
title: 'Auto-focus',
render: function() {
return (
<View>
<TextInput
autoFocus={true}
style={styles.default}
accessibilityLabel="I am the accessibility label for text input"
/>
</View>
);
}
},
{
title: "Live Re-Write (<sp> -> '_') + maxLength",
render: function() {
return <RewriteExample />;
}
},
{
title: 'Live Re-Write (no spaces allowed)',
render: function() {
return <RewriteExampleInvalidCharacters />;
}
},
{
title: 'Auto-capitalize',
render: function() {
return (
<View>
<WithLabel label="none">
<TextInput
autoCapitalize="none"
style={styles.default}
/>
</WithLabel>
<WithLabel label="sentences">
<TextInput
autoCapitalize="sentences"
style={styles.default}
/>
</WithLabel>
<WithLabel label="words">
<TextInput
autoCapitalize="words"
style={styles.default}
/>
</WithLabel>
<WithLabel label="characters">
<TextInput
autoCapitalize="characters"
style={styles.default}
/>
</WithLabel>
</View>
);
}
},
{
title: 'Auto-correct',
render: function() {
return (
<View>
<WithLabel label="true">
<TextInput autoCorrect={true} style={styles.default} />
</WithLabel>
<WithLabel label="false">
<TextInput autoCorrect={false} style={styles.default} />
</WithLabel>
</View>
);
}
},
{
title: 'Keyboard types',
render: function() {
var keyboardTypes = [
'default',
'ascii-capable',
'numbers-and-punctuation',
'url',
'number-pad',
'phone-pad',
'name-phone-pad',
'email-address',
'decimal-pad',
'twitter',
'web-search',
'numeric',
];
var examples = keyboardTypes.map((type) => {
return (
<WithLabel key={type} label={type}>
<TextInput
keyboardType={type}
style={styles.default}
/>
</WithLabel>
);
});
return <View>{examples}</View>;
}
},
{
title: 'Keyboard appearance',
render: function() {
var keyboardAppearance = [
'default',
'light',
'dark',
];
var examples = keyboardAppearance.map((type) => {
return (
<WithLabel key={type} label={type}>
<TextInput
keyboardAppearance={type}
style={styles.default}
/>
</WithLabel>
);
});
return <View>{examples}</View>;
}
},
{
title: 'Return key types',
render: function() {
var returnKeyTypes = [
'default',
'go',
'google',
'join',
'next',
'route',
'search',
'send',
'yahoo',
'done',
'emergency-call',
];
var examples = returnKeyTypes.map((type) => {
return (
<WithLabel key={type} label={type}>
<TextInput
returnKeyType={type}
style={styles.default}
/>
</WithLabel>
);
});
return <View>{examples}</View>;
}
},
{
title: 'Enable return key automatically',
render: function() {
return (
<View>
<WithLabel label="true">
<TextInput enablesReturnKeyAutomatically={true} style={styles.default} />
</WithLabel>
</View>
);
}
},
{
title: 'Secure text entry',
render: function() {
return (
<View>
<WithLabel label="true">
<TextInput secureTextEntry={true} style={styles.default} defaultValue="abc" />
</WithLabel>
</View>
);
}
},
{
title: 'Event handling',
render: function(): React.Element<any> { return <TextEventsExample />; },
},
{
title: 'Colored input text',
render: function() {
return (
<View>
<TextInput
style={[styles.default, {color: 'blue'}]}
defaultValue="Blue"
/>
<TextInput
style={[styles.default, {color: 'green'}]}
defaultValue="Green"
/>
</View>
);
}
},
{
title: 'Colored highlight/cursor for text input',
render: function() {
return (
<View>
<TextInput
style={styles.default}
selectionColor={"green"}
defaultValue="Highlight me"
/>
<TextInput
style={styles.default}
selectionColor={"rgba(86, 76, 205, 1)"}
defaultValue="Highlight me"
/>
</View>
);
}
},
{
title: 'Clear button mode',
render: function () {
return (
<View>
<WithLabel label="never">
<TextInput
style={styles.default}
clearButtonMode="never"
/>
</WithLabel>
<WithLabel label="while editing">
<TextInput
style={styles.default}
clearButtonMode="while-editing"
/>
</WithLabel>
<WithLabel label="unless editing">
<TextInput
style={styles.default}
clearButtonMode="unless-editing"
/>
</WithLabel>
<WithLabel label="always">
<TextInput
style={styles.default}
clearButtonMode="always"
/>
</WithLabel>
</View>
);
}
},
{
title: 'Clear and select',
render: function() {
return (
<View>
<WithLabel label="clearTextOnFocus">
<TextInput
placeholder="text is cleared on focus"
defaultValue="text is cleared on focus"
style={styles.default}
clearTextOnFocus={true}
/>
</WithLabel>
<WithLabel label="selectTextOnFocus">
<TextInput
placeholder="text is selected on focus"
defaultValue="text is selected on focus"
style={styles.default}
selectTextOnFocus={true}
/>
</WithLabel>
</View>
);
}
},
{
title: 'Blur on submit',
render: function(): React.Element<any> { return <BlurOnSubmitExample />; },
},
{
title: 'Multiline blur on submit',
render: function() {
return (
<View>
<TextInput
style={styles.multiline}
placeholder="blurOnSubmit = true"
returnKeyType="next"
blurOnSubmit={true}
multiline={true}
onSubmitEditing={event => alert(event.nativeEvent.text)}
/>
</View>
);
}
},
{
title: 'Multiline',
render: function() {
return (
<View>
<TextInput
placeholder="multiline text input"
multiline={true}
style={styles.multiline}
/>
<TextInput
placeholder="multiline text input with font styles and placeholder"
multiline={true}
clearTextOnFocus={true}
autoCorrect={true}
autoCapitalize="words"
placeholderTextColor="red"
keyboardType="url"
style={[styles.multiline, styles.multilineWithFontStyles]}
/>
<TextInput
placeholder="multiline text input with max length"
maxLength={5}
multiline={true}
style={styles.multiline}
/>
<TextInput
placeholder="uneditable multiline text input"
editable={false}
multiline={true}
style={styles.multiline}
/>
<TextInput
defaultValue="uneditable multiline text input with phone number detection: 88888888."
editable={false}
multiline={true}
style={styles.multiline}
dataDetectorTypes="phoneNumber"
/>
<TextInput
placeholder="multiline with children"
multiline={true}
enablesReturnKeyAutomatically={true}
returnKeyType="go"
style={styles.multiline}>
<View style={styles.multilineChild}/>
</TextInput>
</View>
);
}
},
{
title: 'Number of lines',
render: function() {
return (
<View>
<TextInput
multiline={true}
numberOfLines={4}
style={[ styles.multiline, { height: 'auto' } ]}
/>
</View>
);
}
},
{
title: 'Auto-expanding',
render: function() {
return (
<View>
<AutoExpandingTextInput
placeholder="height increases with content"
enablesReturnKeyAutomatically={true}
returnKeyType="default"
/>
</View>
);
}
},
{
title: 'Attributed text',
render: function() {
return <TokenizedTextExample />;
}
},
{
title: 'Text selection & cursor placement',
render: function() {
return (
<View>
<SelectionExample
style={styles.default}
value="text selection can be changed"
/>
<SelectionExample
multiline
style={styles.multiline}
value={"multiline text selection\ncan also be changed"}
/>
</View>
);
}
},
{
title: 'TextInput maxLength',
render: function() {
return (
<View>
<WithLabel label="maxLength: 5">
<TextInput
maxLength={5}
style={styles.default}
/>
</WithLabel>
<WithLabel label="maxLength: 5 with placeholder">
<TextInput
maxLength={5}
placeholder="ZIP code entry"
style={styles.default}
/>
</WithLabel>
<WithLabel label="maxLength: 5 with default value already set">
<TextInput
maxLength={5}
defaultValue="94025"
style={styles.default}
/>
</WithLabel>
<WithLabel label="maxLength: 5 with very long default value already set">
<TextInput
maxLength={5}
defaultValue="9402512345"
style={styles.default}
/>
</WithLabel>
</View>
);
}
}
];
examples.forEach((example) => {
storiesOf('component: TextInput', module)
.add(example.title, () => example.render())
});

View File

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

View File

@@ -1,5 +1,65 @@
/* eslint-env mocha */
import assert from 'assert';
import AsyncStorage from '..';
const waterfall = (fns, cb) => {
const _waterfall = (...args) => {
const fn = (fns || []).shift();
if (typeof fn === 'function') {
fn(...args, (err, ...nextArgs) => {
if (err) {
return cb(err);
} else {
return _waterfall(...nextArgs);
}
});
} else {
cb(null, ...args);
}
};
_waterfall();
};
suite('apis/AsyncStorage', () => {
test.skip('NO TEST COVERAGE', () => {});
suite('mergeLocalStorageItem', () => {
test('should have same behavior as react-native', (done) => {
// https://facebook.github.io/react-native/docs/asyncstorage.html
const UID123_object = {
name: 'Chris',
age: 30,
traits: { hair: 'brown', eyes: 'brown' }
};
const UID123_delta = {
age: 31,
traits: { eyes: 'blue', shoe_size: 10 }
};
waterfall([
(cb) => {
AsyncStorage.setItem('UID123', JSON.stringify(UID123_object))
.then(() => cb(null))
.catch(cb);
},
(cb) => {
AsyncStorage.mergeItem('UID123', JSON.stringify(UID123_delta))
.then(() => cb(null))
.catch(cb);
},
(cb) => {
AsyncStorage.getItem('UID123')
.then((result) => {
cb(null, JSON.parse(result));
})
.catch(cb);
}
], (err, result) => {
assert.equal(err, null);
assert.deepEqual(result, {
'name': 'Chris', 'age': 31, 'traits': {
'shoe_size': 10, 'hair': 'brown', 'eyes': 'blue'
}
});
done();
});
});
});
});

View File

@@ -3,12 +3,13 @@
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*/
import merge from 'lodash/merge';
const mergeLocalStorageItem = (key, value) => {
const oldValue = window.localStorage.getItem(key);
const oldObject = JSON.parse(oldValue);
const newObject = JSON.parse(value);
const nextValue = JSON.stringify({ ...oldObject, ...newObject });
const nextValue = JSON.stringify(merge({}, oldObject, newObject));
window.localStorage.setItem(key, nextValue);
};

View File

@@ -11,7 +11,9 @@ const CSS_RESET =
'html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0)}\n' +
'body{margin:0}\n' +
'button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}\n' +
'input::-webkit-inner-spin-button,input::-webkit-outer-spin-button,input::-webkit-search-cancel-button,input::-webkit-search-decoration {display:none}';
'input::-webkit-inner-spin-button,input::-webkit-outer-spin-button,' +
'input::-webkit-search-cancel-button,input::-webkit-search-decoration,' +
'input::-webkit-search-results-button,input::-webkit-search-results-decoration{display:none}';
const CSS_HELPERS =
// vendor prefix 'display:flex' until React supports fallback values for inline styles

View File

@@ -1,12 +1,10 @@
import keyMirror from 'fbjs/lib/keyMirror';
const ImageResizeMode = keyMirror({
center: null,
contain: null,
cover: null,
none: null,
repeat: null,
stretch: null
});
const ImageResizeMode = {
center: 'center',
contain: 'contain',
cover: 'cover',
none: 'none',
repeat: 'repeat',
stretch: 'stretch'
};
module.exports = ImageResizeMode;

View File

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

View File

@@ -4,7 +4,6 @@ import BaseComponentPropTypes from '../../propTypes/BaseComponentPropTypes';
import createDOMElement from '../../modules/createDOMElement';
import ImageResizeMode from './ImageResizeMode';
import ImageStylePropTypes from './ImageStylePropTypes';
import resolveAssetSource from './resolveAssetSource';
import StyleSheet from '../../apis/StyleSheet';
import StyleSheetPropType from '../../propTypes/StyleSheetPropType';
import View from '../View';
@@ -18,11 +17,24 @@ const STATUS_IDLE = 'IDLE';
const ImageSourcePropType = PropTypes.oneOfType([
PropTypes.shape({
uri: PropTypes.string.isRequired
height: PropTypes.number,
uri: PropTypes.string.isRequired,
width: PropTypes.number
}),
PropTypes.string
]);
const resolveAssetDimensions = (source) => {
if (typeof source === 'object') {
const { height, width } = source;
return { height, width };
}
};
const resolveAssetSource = (source) => {
return ((typeof source === 'object') ? source.uri : source) || null;
};
class Image extends Component {
static displayName = 'Image';
@@ -35,7 +47,7 @@ class Image extends Component {
onLoad: PropTypes.func,
onLoadEnd: PropTypes.func,
onLoadStart: PropTypes.func,
resizeMode: PropTypes.oneOf([ 'center', 'contain', 'cover', 'none', 'repeat', 'stretch' ]),
resizeMode: PropTypes.oneOf(Object.keys(ImageResizeMode)),
source: ImageSourcePropType,
style: StyleSheetPropType(ImageStylePropTypes)
};
@@ -49,9 +61,9 @@ class Image extends Component {
constructor(props, context) {
super(props, context);
this.state = { isLoaded: false };
const uri = resolveAssetSource(props.source);
this._imageState = uri ? STATUS_PENDING : STATUS_IDLE;
this.state = { isLoaded: false };
}
componentDidMount() {
@@ -90,20 +102,26 @@ class Image extends Component {
} = this.props;
const displayImage = resolveAssetSource(!isLoaded ? defaultSource : source);
const imageSizeStyle = resolveAssetDimensions(!isLoaded ? defaultSource : source);
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;
// remove 'resizeMode' style, as it is not supported by View (N.B. styles are frozen in dev)
style = process.env.NODE_ENV !== 'production' ? { ...style } : style;
const style = StyleSheet.flatten([
styles.initial,
imageSizeStyle,
originalStyle,
backgroundImage && { backgroundImage },
resizeModeStyles[resizeMode]
]);
// View doesn't support 'resizeMode' as a style
delete style.resizeMode;
/**
* Image is a non-stretching View. The image is displayed as a background
* image to support `resizeMode`. The HTML image is hidden but used to
* provide the correct responsive image dimensions, and to support the
* image context menu. Child content is rendered into an element absolutely
* positioned over the image.
* The image is displayed as a background image to support `resizeMode`.
* The HTML image is hidden but used to provide the correct responsive
* image dimensions, and to support the image context menu. Child content
* is rendered into an element absolutely positioned over the image.
*/
return (
<View
@@ -111,12 +129,7 @@ class Image extends Component {
accessibilityRole='img'
accessible={accessible}
onLayout={onLayout}
style={[
styles.initial,
style,
backgroundImage && { backgroundImage },
resizeModeStyles[resizeMode]
]}
style={style}
testID={testID}
>
{createDOMElement('img', { src: displayImage, style: styles.img })}
@@ -146,14 +159,18 @@ class Image extends Component {
}
}
_onError = (e) => {
const { onError } = this.props;
const event = { nativeEvent: e };
_onError = () => {
const { onError, source } = this.props;
this._destroyImageLoader();
this._updateImageState(STATUS_ERRORED);
this._onLoadEnd();
if (onError) { onError(event); }
this._updateImageState(STATUS_ERRORED);
if (onError) {
onError({
nativeEvent: {
error: `Failed to load resource ${resolveAssetSource(source)} (404)`
}
});
}
}
_onLoad = (e) => {
@@ -188,7 +205,6 @@ class Image extends Component {
const styles = StyleSheet.create({
initial: {
alignSelf: 'flex-start',
backgroundColor: 'transparent',
backgroundPosition: 'center',
backgroundRepeat: 'no-repeat',

View File

@@ -1,5 +0,0 @@
function resolveAssetSource(source) {
return ((typeof source === 'object') ? source.uri : source) || null;
}
module.exports = resolveAssetSource;

View File

@@ -6,6 +6,8 @@ import ScrollView from '../ScrollView';
import View from '../View';
import React, { Component } from 'react';
const scrollViewProps = Object.keys(ScrollView.propTypes);
class ListView extends Component {
static propTypes = ListViewPropTypes;
@@ -88,14 +90,14 @@ class ListView extends Component {
}
}
const props = pick(ScrollView.propTypes, this.props);
const props = pick(this.props, scrollViewProps);
return React.cloneElement(this.props.renderScrollComponent(props), {
ref: this._setScrollViewRef
}, header, children, footer);
}
_setScrollViewRef(component) {
_setScrollViewRef = (component) => {
this._scrollViewRef = component;
}
}

View File

@@ -171,9 +171,11 @@ const ScrollView = React.createClass({
return React.cloneElement(
refreshControl,
{ style: props.style },
<ScrollViewClass {...props} ref={this._setScrollViewRef} style={styles.base}>
{contentContainer}
</ScrollViewClass>
(
<ScrollViewClass {...props} ref={this._setScrollViewRef} style={styles.base}>
{contentContainer}
</ScrollViewClass>
)
);
}

View File

@@ -55,6 +55,7 @@ class Text extends Component {
const styles = StyleSheet.create({
initial: {
borderWidth: 0,
color: 'inherit',
display: 'inline',
font: 'inherit',

View File

@@ -24,6 +24,9 @@ const TextInputState = {
* If no text field is focused it returns null
*/
currentlyFocusedField(): ?Object {
if (document.activeElement !== this._currentlyFocusedNode) {
this._currentlyFocusedNode = null;
}
return this._currentlyFocusedNode;
},
@@ -33,7 +36,7 @@ const TextInputState = {
* noop if the text field was already focused
*/
focusTextInput(textFieldNode: ?Object) {
if (this._currentlyFocusedNode !== textFieldNode && textFieldNode !== null) {
if (document.activeElement !== textFieldNode && textFieldNode !== null) {
this._currentlyFocusedNode = textFieldNode;
UIManager.focus(textFieldNode);
}
@@ -45,7 +48,7 @@ const TextInputState = {
* noop if it wasn't focused
*/
blurTextInput(textFieldNode: ?Object) {
if (this._currentlyFocusedNode === textFieldNode && textFieldNode !== null) {
if (document.activeElement === textFieldNode && textFieldNode !== null) {
this._currentlyFocusedNode = null;
UIManager.blur(textFieldNode);
}

View File

@@ -22,12 +22,12 @@ const testIfDocumentIsFocused = (message, fn) => {
suite('components/TextInput', () => {
test('prop "autoComplete"', () => {
// off
let input = findNativeInput(shallow(<TextInput />));
assert.equal(input.prop('autoComplete'), undefined);
// on
input = findNativeInput(shallow(<TextInput autoComplete />));
let input = findNativeInput(shallow(<TextInput />));
assert.equal(input.prop('autoComplete'), 'on');
// off
input = findNativeInput(shallow(<TextInput autoComplete='off' />));
assert.equal(input.prop('autoComplete'), 'off');
});
test('prop "autoFocus"', () => {
@@ -179,8 +179,8 @@ suite('components/TextInput', () => {
const input = findNativeInput(mount(<TextInput defaultValue='12345' onSelectionChange={onSelectionChange} />));
input.simulate('select', { target: { selectionStart: 0, selectionEnd: 3 } });
function onSelectionChange(e) {
assert.equal(e.selectionEnd, 3);
assert.equal(e.selectionStart, 0);
assert.equal(e.nativeEvent.selection.end, 3);
assert.equal(e.nativeEvent.selection.start, 0);
done();
}
});

View File

@@ -1,8 +1,8 @@
import applyNativeMethods from '../../modules/applyNativeMethods';
import createDOMElement from '../../modules/createDOMElement';
import findNodeHandle from '../../modules/findNodeHandle';
import omit from 'lodash/omit';
import pick from 'lodash/pick';
import ReactDOM from 'react-dom';
import StyleSheet from '../../apis/StyleSheet';
import Text from '../Text';
import TextareaAutosize from 'react-textarea-autosize';
@@ -14,13 +14,52 @@ import React, { Component, PropTypes } from 'react';
const viewStyleProps = Object.keys(ViewStylePropTypes);
/**
* React Native events differ from W3C events.
*/
const normalizeEventHandler = (handler) => (e) => {
if (handler) {
e.nativeEvent.text = e.target.value;
return handler(e);
}
};
/**
* Determins whether a 'selection' prop differs from a node's existing
* selection state.
*/
const isSelectionStale = (node, selection) => {
if (node && selection) {
const { selectionEnd, selectionStart } = node;
const { start, end } = selection;
return start !== selectionStart || end !== selectionEnd;
}
return false;
};
/**
* Certain input types do no support 'selectSelectionRange' and will throw an
* error.
*/
const setSelection = (node, selection) => {
try {
if (isSelectionStale(node, selection)) {
const { start, end } = selection;
node.setSelectionRange(start, end || start);
}
} catch (e) {}
};
class TextInput extends Component {
static displayName = 'TextInput';
static propTypes = {
...View.propTypes,
autoComplete: PropTypes.bool,
autoCapitalize: PropTypes.oneOf([ 'characters', 'none', 'sentences', 'words' ]),
autoComplete: PropTypes.string,
autoCorrect: PropTypes.bool,
autoFocus: PropTypes.bool,
blurOnSubmit: PropTypes.bool,
clearTextOnFocus: PropTypes.bool,
defaultValue: PropTypes.string,
editable: PropTypes.bool,
@@ -35,17 +74,25 @@ class TextInput extends Component {
onChange: PropTypes.func,
onChangeText: PropTypes.func,
onFocus: PropTypes.func,
onKeyPress: PropTypes.func,
onSelectionChange: PropTypes.func,
placeholder: PropTypes.string,
placeholderTextColor: PropTypes.string,
secureTextEntry: PropTypes.bool,
selectTextOnFocus: PropTypes.bool,
selection: PropTypes.shape({
start: PropTypes.number.isRequired,
end: PropTypes.number
}),
style: Text.propTypes.style,
testID: Text.propTypes.testID,
value: PropTypes.string
};
static defaultProps = {
autoCapitalize: 'sentences',
autoComplete: 'on',
autoCorrect: true,
editable: true,
keyboardType: 'default',
multiline: false,
@@ -60,7 +107,7 @@ class TextInput extends Component {
}
blur() {
TextInputState.blurTextInput(ReactDOM.findDOMNode(this._inputRef));
TextInputState.blurTextInput(findNodeHandle(this._inputRef));
}
clear() {
@@ -68,17 +115,31 @@ class TextInput extends Component {
}
focus() {
TextInputState.focusTextInput(ReactDOM.findDOMNode(this._inputRef));
TextInputState.focusTextInput(findNodeHandle(this._inputRef));
}
isFocused() {
return TextInputState.currentlyFocusedField() === findNodeHandle(this._inputRef);
}
setNativeProps(props) {
UIManager.updateView(this._inputRef, props, this);
}
componentDidMount() {
setSelection(findNodeHandle(this._inputRef), this.props.selection);
}
componentDidUpdate() {
setSelection(findNodeHandle(this._inputRef), this.props.selection);
}
render() {
const {
accessibilityLabel, // eslint-disable-line
autoCapitalize,
autoComplete,
autoCorrect,
autoFocus,
defaultValue,
editable,
@@ -88,7 +149,6 @@ class TextInput extends Component {
multiline,
numberOfLines,
onLayout,
onSelectionChange,
placeholder,
placeholderTextColor,
secureTextEntry,
@@ -131,34 +191,32 @@ class TextInput extends Component {
const rootStyles = pick(flattenedStyle, viewStyleProps);
const textStyles = omit(flattenedStyle, viewStyleProps);
const propsCommon = {
autoComplete: autoComplete && 'on',
const props = {
autoCapitalize,
autoComplete,
autoCorrect: autoCorrect ? 'on' : 'off',
autoFocus,
defaultValue,
maxLength,
onBlur: this._handleBlur,
onChange: this._handleChange,
onFocus: this._handleFocus,
onSelect: onSelectionChange && this._handleSelectionChange,
onBlur: normalizeEventHandler(this._handleBlur),
onChange: normalizeEventHandler(this._handleChange),
onFocus: normalizeEventHandler(this._handleFocus),
onKeyPress: normalizeEventHandler(this._handleKeyPress),
onSelect: normalizeEventHandler(this._handleSelectionChange),
readOnly: !editable,
ref: this._setInputRef,
style: [ styles.input, textStyles, { outline: style.outline } ],
value
};
const propsMultiline = {
...propsCommon,
maxRows: maxNumberOfLines || numberOfLines,
minRows: numberOfLines
};
const propsSingleline = {
...propsCommon,
type
};
if (multiline) {
props.maxRows = maxNumberOfLines || numberOfLines;
props.minRows = numberOfLines;
} else {
props.type = type;
}
const component = multiline ? TextareaAutosize : 'input';
const props = multiline ? propsMultiline : propsSingleline;
const optionalPlaceholder = placeholder && this.state.showPlaceholder && (
<View pointerEvents='none' style={styles.placeholder}>
@@ -191,51 +249,58 @@ class TextInput extends Component {
_handleBlur = (e) => {
const { onBlur } = this.props;
const text = e.target.value;
const { text } = e.nativeEvent;
this.setState({ showPlaceholder: text === '' });
this.blur();
if (onBlur) { onBlur(e); }
}
_handleChange = (e) => {
const { onChange, onChangeText } = this.props;
const text = e.target.value;
const { text } = e.nativeEvent;
this.setState({ showPlaceholder: text === '' });
if (onChange) { onChange(e); }
if (onChangeText) { onChangeText(text); }
if (!this._inputRef) {
// calling `this.props.onChange` or `this.props.onChangeText`
// may clean up the input itself. Exits here.
return;
}
}
_handleClick = (e) => {
this.focus();
if (this.props.editable) {
this.focus();
}
}
_handleFocus = (e) => {
const { clearTextOnFocus, onFocus, selectTextOnFocus } = this.props;
const node = ReactDOM.findDOMNode(this._inputRef);
const text = e.target.value;
const { text } = e.nativeEvent;
const node = findNodeHandle(this._inputRef);
if (onFocus) { onFocus(e); }
if (clearTextOnFocus) { this.clear(); }
if (selectTextOnFocus) { node.select(); }
if (selectTextOnFocus) { node && node.select(); }
this.setState({ showPlaceholder: text === '' });
}
_handleKeyPress = (e) => {
const { blurOnSubmit, multiline, onKeyPress, onSubmitEditing } = this.props;
const blurOnSubmitDefault = !multiline;
const shouldBlurOnSubmit = blurOnSubmit == null ? blurOnSubmitDefault : blurOnSubmit;
if (onKeyPress) { onKeyPress(e); }
if (!e.isDefaultPrevented() && e.which === 13) {
if (onSubmitEditing) { onSubmitEditing(e); }
if (shouldBlurOnSubmit) { this.blur(); }
}
}
_handleSelectionChange = (e) => {
const { onSelectionChange } = this.props;
try {
const { selectionDirection, selectionEnd, selectionStart } = e.target;
const event = {
selectionDirection,
selectionEnd,
selectionStart,
nativeEvent: e.nativeEvent
};
if (onSelectionChange) { onSelectionChange(event); }
} catch (e) {}
const { onSelectionChange, selection = {} } = this.props;
if (onSelectionChange) {
try {
const node = e.target;
if (isSelectionStale(node, selection)) {
const { selectionStart, selectionEnd } = node;
e.nativeEvent.selection = { start: selectionStart, end: selectionEnd };
onSelectionChange(e);
}
} catch (e) {}
}
}
_setInputRef = (component) => {
@@ -243,8 +308,6 @@ class TextInput extends Component {
}
}
applyNativeMethods(TextInput);
const styles = StyleSheet.create({
initial: {
borderColor: 'black'
@@ -255,6 +318,7 @@ const styles = StyleSheet.create({
input: {
appearance: 'none',
backgroundColor: 'transparent',
borderRadius: 0,
borderWidth: 0,
boxSizing: 'border-box',
color: 'inherit',
@@ -266,7 +330,6 @@ const styles = StyleSheet.create({
},
placeholder: {
bottom: 0,
justifyContent: 'center',
left: 0,
position: 'absolute',
right: 0,
@@ -279,4 +342,4 @@ const styles = StyleSheet.create({
}
});
module.exports = TextInput;
module.exports = applyNativeMethods(TextInput);

View File

@@ -66,8 +66,7 @@ class View extends Component {
};
static defaultProps = {
accessible: true,
style: {}
accessible: true
};
static childContextTypes = {
@@ -97,7 +96,7 @@ class View extends Component {
const flattenedStyle = StyleSheet.flatten(style);
const pointerEventsStyle = pointerEvents && { pointerEvents };
// 'View' needs to set 'flexShrink:0' only when there is no 'flex' or 'flexShrink' style provided
const needsFlexReset = flattenedStyle.flex == null && flattenedStyle.flexShrink == null;
const needsFlexReset = !flattenedStyle || (flattenedStyle.flex == null && flattenedStyle.flexShrink == null);
const normalizedEventHandlers = eventHandlerNames.reduce((handlerProps, handlerName) => {
const handler = this.props[handlerName];
@@ -124,10 +123,10 @@ class View extends Component {
_normalizeEventForHandler(handler, handlerName) {
// 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
// events.
const shouldCancelEvent = handlerName.indexOf('onResponder') === 0;
const shouldCancelEvent = handlerName === 'onResponderRelease';
return (e) => {
e.nativeEvent = normalizeNativeEvent(e.nativeEvent);

View File

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