mirror of
https://github.com/zoriya/react-native-web.git
synced 2026-08-15 18:45:06 +00:00
294 lines
7.4 KiB
JavaScript
294 lines
7.4 KiB
JavaScript
import applyNativeMethods from '../../modules/applyNativeMethods';
|
|
import createDOMElement from '../../modules/createDOMElement';
|
|
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';
|
|
import TextInputState from './TextInputState';
|
|
import UIManager from '../../apis/UIManager';
|
|
import View from '../View';
|
|
import ViewStylePropTypes from '../View/ViewStylePropTypes';
|
|
import React, { Component, PropTypes } from 'react';
|
|
|
|
const viewStyleProps = Object.keys(ViewStylePropTypes);
|
|
|
|
class TextInput extends Component {
|
|
static displayName = 'TextInput';
|
|
|
|
static propTypes = {
|
|
...View.propTypes,
|
|
autoCapitalize: PropTypes.oneOf([ 'characters', 'none', 'sentences', 'words' ]),
|
|
autoComplete: PropTypes.bool,
|
|
autoFocus: PropTypes.bool,
|
|
clearTextOnFocus: PropTypes.bool,
|
|
defaultValue: PropTypes.string,
|
|
editable: PropTypes.bool,
|
|
keyboardType: PropTypes.oneOf([
|
|
'default', 'email-address', 'numeric', 'phone-pad', 'search', 'url', 'web-search'
|
|
]),
|
|
maxLength: PropTypes.number,
|
|
maxNumberOfLines: PropTypes.number,
|
|
multiline: PropTypes.bool,
|
|
numberOfLines: PropTypes.number,
|
|
onBlur: PropTypes.func,
|
|
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,
|
|
style: Text.propTypes.style,
|
|
testID: Text.propTypes.testID,
|
|
value: PropTypes.string
|
|
};
|
|
|
|
static defaultProps = {
|
|
autoCapitalize: 'sentences',
|
|
editable: true,
|
|
keyboardType: 'default',
|
|
multiline: false,
|
|
numberOfLines: 2,
|
|
secureTextEntry: false,
|
|
style: {}
|
|
};
|
|
|
|
constructor(props, context) {
|
|
super(props, context);
|
|
this.state = { showPlaceholder: !props.value && !props.defaultValue };
|
|
}
|
|
|
|
blur() {
|
|
TextInputState.blurTextInput(ReactDOM.findDOMNode(this._inputRef));
|
|
}
|
|
|
|
clear() {
|
|
this.setNativeProps({ text: '' });
|
|
}
|
|
|
|
focus() {
|
|
TextInputState.focusTextInput(ReactDOM.findDOMNode(this._inputRef));
|
|
}
|
|
|
|
isFocused() {
|
|
return TextInputState.currentlyFocusedField() === ReactDOM.findDOMNode(this._inputRef);
|
|
}
|
|
|
|
setNativeProps(props) {
|
|
UIManager.updateView(this._inputRef, props, this);
|
|
}
|
|
|
|
render() {
|
|
const {
|
|
accessibilityLabel, // eslint-disable-line
|
|
autoCapitalize,
|
|
autoComplete,
|
|
autoFocus,
|
|
defaultValue,
|
|
editable,
|
|
keyboardType,
|
|
maxLength,
|
|
maxNumberOfLines,
|
|
multiline,
|
|
numberOfLines,
|
|
onKeyPress,
|
|
onLayout,
|
|
onSelectionChange,
|
|
placeholder,
|
|
placeholderTextColor,
|
|
secureTextEntry,
|
|
style,
|
|
testID,
|
|
value
|
|
} = this.props;
|
|
|
|
let type;
|
|
|
|
switch (keyboardType) {
|
|
case 'email-address':
|
|
type = 'email';
|
|
break;
|
|
case 'numeric':
|
|
type = 'number';
|
|
break;
|
|
case 'phone-pad':
|
|
type = 'tel';
|
|
break;
|
|
case 'search':
|
|
case 'web-search':
|
|
type = 'search';
|
|
break;
|
|
case 'url':
|
|
type = 'url';
|
|
break;
|
|
default:
|
|
type = 'text';
|
|
}
|
|
|
|
if (secureTextEntry) {
|
|
type = 'password';
|
|
}
|
|
|
|
// In order to support 'Text' styles on 'TextInput', we split the 'Text'
|
|
// and 'View' styles and apply them to the 'Text' and 'View' components
|
|
// used in the implementation
|
|
const flattenedStyle = StyleSheet.flatten(style);
|
|
const rootStyles = pick(flattenedStyle, viewStyleProps);
|
|
const textStyles = omit(flattenedStyle, viewStyleProps);
|
|
|
|
const propsCommon = {
|
|
autoCapitalize,
|
|
autoComplete: autoComplete && 'on',
|
|
autoFocus,
|
|
defaultValue,
|
|
maxLength,
|
|
onBlur: this._handleBlur,
|
|
onChange: this._handleChange,
|
|
onFocus: this._handleFocus,
|
|
onKeyPress,
|
|
onSelect: onSelectionChange && 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
|
|
};
|
|
|
|
const component = multiline ? TextareaAutosize : 'input';
|
|
const props = multiline ? propsMultiline : propsSingleline;
|
|
|
|
const optionalPlaceholder = placeholder && this.state.showPlaceholder && (
|
|
<View pointerEvents='none' style={styles.placeholder}>
|
|
<Text
|
|
children={placeholder}
|
|
style={[
|
|
styles.placeholderText,
|
|
textStyles,
|
|
placeholderTextColor && { color: placeholderTextColor }
|
|
]}
|
|
/>
|
|
</View>
|
|
);
|
|
|
|
return (
|
|
<View
|
|
accessibilityLabel={accessibilityLabel}
|
|
onClick={this._handleClick}
|
|
onLayout={onLayout}
|
|
style={[ styles.initial, rootStyles ]}
|
|
testID={testID}
|
|
>
|
|
<View style={styles.wrapper}>
|
|
{createDOMElement(component, props)}
|
|
{optionalPlaceholder}
|
|
</View>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
_handleBlur = (e) => {
|
|
const { onBlur } = this.props;
|
|
const text = e.target.value;
|
|
this.setState({ showPlaceholder: text === '' });
|
|
this.blur();
|
|
if (onBlur) { onBlur(e); }
|
|
}
|
|
|
|
_handleChange = (e) => {
|
|
const { onChange, onChangeText } = this.props;
|
|
const text = e.target.value;
|
|
e.nativeEvent.text = text;
|
|
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();
|
|
}
|
|
|
|
_handleFocus = (e) => {
|
|
const { clearTextOnFocus, onFocus, selectTextOnFocus } = this.props;
|
|
const node = ReactDOM.findDOMNode(this._inputRef);
|
|
const text = e.target.value;
|
|
if (onFocus) { onFocus(e); }
|
|
if (clearTextOnFocus) { this.clear(); }
|
|
if (selectTextOnFocus) { node.select(); }
|
|
this.setState({ showPlaceholder: text === '' });
|
|
}
|
|
|
|
_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) {}
|
|
}
|
|
|
|
_setInputRef = (component) => {
|
|
this._inputRef = component;
|
|
}
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
initial: {
|
|
borderColor: 'black'
|
|
},
|
|
wrapper: {
|
|
flex: 1
|
|
},
|
|
input: {
|
|
appearance: 'none',
|
|
backgroundColor: 'transparent',
|
|
borderRadius: 0,
|
|
borderWidth: 0,
|
|
boxSizing: 'border-box',
|
|
color: 'inherit',
|
|
flex: 1,
|
|
font: 'inherit',
|
|
minHeight: '100%', // center small inputs (fix #139)
|
|
padding: 0,
|
|
zIndex: 1
|
|
},
|
|
placeholder: {
|
|
bottom: 0,
|
|
justifyContent: 'center',
|
|
left: 0,
|
|
position: 'absolute',
|
|
right: 0,
|
|
top: 0
|
|
},
|
|
placeholderText: {
|
|
color: 'darkgray',
|
|
overflow: 'hidden',
|
|
whiteSpace: 'pre'
|
|
}
|
|
});
|
|
|
|
module.exports = applyNativeMethods(TextInput);
|