[fix] TextInput placeholder layout and focus

Fix the layout of placeholder text and shift focus to the DOM input when
`TextInput` is clicked or pressed.

Thanks to @tuckerconnelly and @Dremora.

Fix #138
Fix #119
Close #137
This commit is contained in:
Nicolas Gallagher
2016-06-28 15:04:38 -07:00
parent 3da05c48b0
commit 65a9317756
3 changed files with 76 additions and 66 deletions

View File

@@ -90,7 +90,7 @@ export default class App extends React.Component {
/> />
<TextInput secureTextEntry /> <TextInput secureTextEntry />
<TextInput defaultValue='read only' editable={false} /> <TextInput defaultValue='read only' editable={false} />
<TextInput keyboardType='email-address' placeholder='you@domain.com' placeholderTextColor='red' /> <TextInput style={{ flex:1, height: 60, padding: 20 }} keyboardType='email-address' placeholder='you@domain.com' placeholderTextColor='red' />
<TextInput keyboardType='numeric' /> <TextInput keyboardType='numeric' />
<TextInput keyboardType='phone-pad' /> <TextInput keyboardType='phone-pad' />
<TextInput defaultValue='https://delete-me' keyboardType='url' placeholder='https://www.some-website.com' selectTextOnFocus /> <TextInput defaultValue='https://delete-me' keyboardType='url' placeholder='https://www.some-website.com' selectTextOnFocus />

View File

@@ -180,17 +180,17 @@ suite('components/TextInput', () => {
test('prop "placeholder"', () => { test('prop "placeholder"', () => {
const placeholder = 'placeholder' const placeholder = 'placeholder'
const result = findShallowPlaceholder(utils.shallowRender(<TextInput placeholder={placeholder} />)) const result = findShallowPlaceholder(utils.shallowRender(<TextInput placeholder={placeholder} />))
assert.equal(result.props.children, placeholder) assert.equal(result.props.children.props.children, placeholder)
}) })
test('prop "placeholderTextColor"', () => { test('prop "placeholderTextColor"', () => {
const placeholder = 'placeholder' const placeholder = 'placeholder'
let result = findShallowPlaceholder(utils.shallowRender(<TextInput placeholder={placeholder} />)) let result = findShallowPlaceholder(utils.shallowRender(<TextInput placeholder={placeholder} />))
assert.equal(StyleSheet.flatten(result.props.style).color, 'darkgray') assert.equal(StyleSheet.flatten(result.props.children.props.style).color, 'darkgray')
result = findShallowPlaceholder(utils.shallowRender(<TextInput placeholder={placeholder} placeholderTextColor='red' />)) result = findShallowPlaceholder(utils.shallowRender(<TextInput placeholder={placeholder} placeholderTextColor='red' />))
assert.equal(StyleSheet.flatten(result.props.style).color, 'red') assert.equal(StyleSheet.flatten(result.props.children.props.style).color, 'red')
}) })
test('prop "secureTextEntry"', () => { test('prop "secureTextEntry"', () => {

View File

@@ -66,50 +66,6 @@ class TextInput extends Component {
this.refs.input.setNativeProps(props) this.refs.input.setNativeProps(props)
} }
_onBlur = (e) => {
const { onBlur } = this.props
const text = e.target.value
this.setState({ showPlaceholder: text === '' })
this.blur()
if (onBlur) onBlur(e)
}
_onChange = (e) => {
const { onChange, onChangeText } = this.props
const text = e.target.value
this.setState({ showPlaceholder: text === '' })
if (onChange) onChange(e)
if (onChangeText) onChangeText(text)
if (!this.refs.input) {
// calling `this.props.onChange` or `this.props.onChangeText`
// may clean up the input itself. Exits here.
return
}
}
_onFocus = (e) => {
const { clearTextOnFocus, onFocus, selectTextOnFocus } = this.props
const node = ReactDOM.findDOMNode(this.refs.input)
const text = e.target.value
this.focus()
if (onFocus) onFocus(e)
if (clearTextOnFocus) this.clear()
if (selectTextOnFocus) node.select()
this.setState({ showPlaceholder: text === '' })
}
_onSelectionChange = (e) => {
const { onSelectionChange } = this.props
const { selectionDirection, selectionEnd, selectionStart } = e.target
const event = {
selectionDirection,
selectionEnd,
selectionStart,
nativeEvent: e.nativeEvent
}
if (onSelectionChange) onSelectionChange(event)
}
render() { render() {
const { const {
/* eslint-disable react/prop-types */ /* eslint-disable react/prop-types */
@@ -163,10 +119,10 @@ class TextInput extends Component {
autoFocus, autoFocus,
defaultValue, defaultValue,
maxLength, maxLength,
onBlur: this._onBlur, onBlur: this._handleBlur,
onChange: this._onChange, onChange: this._handleChange,
onFocus: this._onFocus, onFocus: this._handleFocus,
onSelect: onSelectionChange && this._onSelectionChange, onSelect: onSelectionChange && this._handleSelectionChange,
readOnly: !editable, readOnly: !editable,
style: { ...styles.input, outline: style.outline }, style: { ...styles.input, outline: style.outline },
value value
@@ -190,25 +146,76 @@ class TextInput extends Component {
return ( return (
<View <View
accessibilityLabel={accessibilityLabel} accessibilityLabel={accessibilityLabel}
style={[ onClick={this._handleClick}
styles.initial, style={[ styles.initial, style ]}
style
]}
testID={testID} testID={testID}
> >
<View style={styles.wrapper}> <View style={styles.wrapper}>
{createNativeComponent({ ...props, ref: 'input' })} {createNativeComponent({ ...props, ref: 'input' })}
{placeholder && this.state.showPlaceholder && <Text {placeholder && this.state.showPlaceholder && (
pointerEvents='none' <View pointerEvents='none' style={styles.placeholder}>
style={[ <Text
styles.placeholder, children={placeholder}
placeholderTextColor && { color: placeholderTextColor } style={[
]} styles.placeholderText,
>{placeholder}</Text>} placeholderTextColor && { color: placeholderTextColor }
]}
/>
</View>
)}
</View> </View>
</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
this.setState({ showPlaceholder: text === '' })
if (onChange) onChange(e)
if (onChangeText) onChangeText(text)
if (!this.refs.input) {
// 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.refs.input)
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) {}
}
} }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
@@ -232,12 +239,15 @@ const styles = StyleSheet.create({
}, },
placeholder: { placeholder: {
bottom: 0, bottom: 0,
color: 'darkgray', justifyContent: 'center',
left: 0, left: 0,
overflow: 'hidden',
position: 'absolute', position: 'absolute',
right: 0, right: 0,
top: 0, top: 0
},
placeholderText: {
color: 'darkgray',
overflow: 'hidden',
whiteSpace: 'pre' whiteSpace: 'pre'
} }
}) })