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 && ( ); return ( {createDOMElement(component, props)} {optionalPlaceholder} ); } _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);