mirror of
https://github.com/zhigang1992/react-native-web.git
synced 2026-03-30 17:34:05 +08:00
Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3b661d8d6d | ||
|
|
22d20706e3 | ||
|
|
0b2813b186 | ||
|
|
b248de552d | ||
|
|
2b826dc7f4 | ||
|
|
b46acd4f50 | ||
|
|
5a03cb25cb | ||
|
|
44e60d12e3 | ||
|
|
fc60f8d332 | ||
|
|
2a65ca6fc0 | ||
|
|
9db3bd7e67 | ||
|
|
1963e9109a | ||
|
|
14072c7471 | ||
|
|
0af6dc00fc | ||
|
|
c9d401f09a | ||
|
|
8aeeed0ef7 | ||
|
|
f5d0f73b4f | ||
|
|
ee7d367062 | ||
|
|
dbd607ce47 | ||
|
|
373cb38ca9 | ||
|
|
4576b42365 | ||
|
|
5a5707855b | ||
|
|
0c76cc5d80 | ||
|
|
d64df129b2 | ||
|
|
763c5444ce |
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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())
|
||||
});
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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("https://google.com/favicon.ico")') > -1;
|
||||
assert.equal(hasBackgroundImage, true);
|
||||
image.unmount();
|
||||
done();
|
||||
}
|
||||
});
|
||||
@@ -74,6 +92,7 @@ suite('components/Image', () => {
|
||||
assert.ok(true);
|
||||
const hasBackgroundImage = (image.html()).indexOf('url("https://google.com/favicon.ico")') > -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();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
function resolveAssetSource(source) {
|
||||
return ((typeof source === 'object') ? source.uri : source) || null;
|
||||
}
|
||||
|
||||
module.exports = resolveAssetSource;
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -55,6 +55,7 @@ class Text extends Component {
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
initial: {
|
||||
borderWidth: 0,
|
||||
color: 'inherit',
|
||||
display: 'inline',
|
||||
font: 'inherit',
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user