Fix TextInput onSelectionChange logic

This commit is contained in:
Nicolas Gallagher
2020-05-18 12:08:06 -07:00
parent fc78cb06fd
commit c2d4fd6d77
5 changed files with 148 additions and 119 deletions
@@ -209,6 +209,12 @@ called with `{ nativeEvent: { selection: { start, end } } }`.
</Story>
</Preview>
<Preview withSource='none'>
<Story name="onSelectionChangeControlled">
<Stories.onSelectionChangeControlled />
</Story>
</Preview>
### onSubmitEditing
Callback that is called when the keyboard's submit button is pressed. When
@@ -2,106 +2,27 @@
* @noflow
*/
import React from 'react';
import React, { useState } from 'react';
import { View, Text, TextInput } from 'react-native';
import { styles } from '../helpers';
import { Text, TextInput, View } from 'react-native';
type SelectionExampleState = {
selection: {
start: number,
end?: number
},
value: string
};
function OnSelectionChange() {
const [text, setText] = useState('');
const [selection, setSelection] = useState({ start: 0, end: 0 });
class OnSelectionChangeExample 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 });
};
onChangeText = value => {
this.setState({ value });
};
getRandomPosition() {
const length = this.state.value.length;
return Math.round(Math.random() * length);
}
select = (start, end) => () => {
this._textInput.focus();
this.setState({ selection: { start, end } });
};
selectRandom = () => {
const positions = [this.getRandomPosition(), this.getRandomPosition()].sort();
this.select(...positions)();
};
placeAt = position => () => {
this.select(position, position)();
};
placeAtRandom = () => {
this.placeAt(this.getRandomPosition())();
};
setRef = textInput => {
this._textInput = textInput;
};
render() {
const length = this.state.value.length;
return (
<View>
<TextInput
multiline={this.props.multiline}
onChangeText={this.onChangeText}
onSelectionChange={this.onSelectionChange}
ref={this.setRef}
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(0)}>Place at Start (0, 0)</Text>
<Text onPress={this.placeAt(length)}>
Place at End ({length}, {length})
</Text>
<Text onPress={this.placeAtRandom}>Place at Random</Text>
<Text onPress={this.select(0, length)}>Select All</Text>
<Text onPress={this.selectRandom}>Select Random</Text>
</View>
</View>
);
}
return (
<View>
<TextInput
onChangeText={setText}
onSelectionChange={event => {
setSelection(event.nativeEvent.selection);
}}
style={styles.textinput}
value={text}
/>
<Text>{JSON.stringify(selection)}</Text>
</View>
);
}
const TextInputOnSelectionChangeExample = () => (
<View>
<OnSelectionChangeExample style={styles.textinput} value="text selection can be changed" />
<OnSelectionChangeExample
multiline
style={styles.multiline}
value={'multiline text selection\ncan also be changed'}
/>
</View>
);
export default function OnSelectionChange() {
return <TextInputOnSelectionChangeExample />;
}
export default OnSelectionChange;
@@ -0,0 +1,107 @@
/**
* @noflow
*/
import React from 'react';
import { styles } from '../helpers';
import { Text, TextInput, View } from 'react-native';
type SelectionExampleState = {
selection: {
start: number,
end?: number
},
value: string
};
class OnSelectionChangeExample 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 });
};
onChangeText = value => {
this.setState({ value });
};
getRandomPosition() {
const length = this.state.value.length;
return Math.round(Math.random() * length);
}
select = (start, end) => () => {
this._textInput.focus();
this.setState({ selection: { start, end } });
};
selectRandom = () => {
const positions = [this.getRandomPosition(), this.getRandomPosition()].sort();
this.select(...positions)();
};
placeAt = position => () => {
this.select(position, position)();
};
placeAtRandom = () => {
this.placeAt(this.getRandomPosition())();
};
setRef = textInput => {
this._textInput = textInput;
};
render() {
const length = this.state.value.length;
return (
<View>
<TextInput
multiline={this.props.multiline}
onChangeText={this.onChangeText}
onSelectionChange={this.onSelectionChange}
ref={this.setRef}
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(0)}>Place at Start (0, 0)</Text>
<Text onPress={this.placeAt(length)}>
Place at End ({length}, {length})
</Text>
<Text onPress={this.placeAtRandom}>Place at Random</Text>
<Text onPress={this.select(0, length)}>Select All</Text>
<Text onPress={this.selectRandom}>Select Random</Text>
</View>
</View>
);
}
}
const TextInputOnSelectionChangeExample = () => (
<View>
<OnSelectionChangeExample style={styles.textinput} value="text selection can be changed" />
<OnSelectionChangeExample
multiline
style={styles.multiline}
value={'multiline text selection\ncan also be changed'}
/>
</View>
);
export default function OnSelectionChange() {
return <TextInputOnSelectionChangeExample />;
}
@@ -9,6 +9,7 @@ export { default as maxLength } from './MaxLength';
export { default as multiline } from './Multiline';
export { default as numberOfLines } from './NumberOfLines';
export { default as onSelectionChange } from './OnSelectionChange';
export { default as onSelectionChangeControlled } from './OnSelectionChangeControlled';
export { default as placeholder } from './Placeholder';
export { default as placeholderTextColor } from './PlaceholderTextColor';
export { default as secureTextEntry } from './SecureTextEntry';
+15 -21
View File
@@ -22,19 +22,14 @@ import useResponderEvents from '../../hooks/useResponderEvents';
import StyleSheet from '../StyleSheet';
import TextInputState from '../../modules/TextInputState';
const emptyObject = {};
/**
* Determines whether a 'selection' prop differs from a node's existing
* selection state.
*/
const isSelectionStale = (node, selection) => {
if (node != null && selection != null && selection.start != null) {
const { selectionEnd, selectionStart } = node;
const { start, end } = selection;
return start !== selectionStart || end !== selectionEnd;
}
return false;
const { selectionEnd, selectionStart } = node;
const { start, end } = selection;
return start !== selectionStart || end !== selectionEnd;
};
/**
@@ -42,7 +37,7 @@ const isSelectionStale = (node, selection) => {
* error.
*/
const setSelection = (node, selection) => {
if (node != null && selection != null && isSelectionStale(node, selection)) {
if (isSelectionStale(node, selection)) {
const { start, end } = selection;
try {
node.setSelectionRange(start, end || start);
@@ -155,7 +150,7 @@ const TextInput = forwardRef<TextInputProps, *>((props, forwardedRef) => {
placeholderTextColor,
returnKeyType,
secureTextEntry = false,
selection = emptyObject,
selection,
selectTextOnFocus,
spellCheck
} = props;
@@ -251,7 +246,6 @@ const TextInput = forwardRef<TextInputProps, *>((props, forwardedRef) => {
if (onChangeText) {
onChangeText(text);
}
handleSelectionChange(e);
}
function handleFocus(e) {
@@ -312,22 +306,22 @@ const TextInput = forwardRef<TextInputProps, *>((props, forwardedRef) => {
if (onSelectionChange) {
try {
const node = e.target;
if (isSelectionStale(node, selection)) {
const { selectionStart, selectionEnd } = node;
e.nativeEvent.selection = {
start: selectionStart,
end: selectionEnd
};
e.nativeEvent.text = e.target.value;
onSelectionChange(e);
}
const { selectionStart, selectionEnd } = node;
e.nativeEvent.selection = {
start: selectionStart,
end: selectionEnd
};
e.nativeEvent.text = e.target.value;
onSelectionChange(e);
} catch (e) {}
}
}
useLayoutEffect(() => {
const node = hostRef.current;
setSelection(node, selection);
if (node != null && selection != null) {
setSelection(node, selection);
}
if (document.activeElement === node) {
TextInputState._currentlyFocusedNode = node;
}