diff --git a/src/libs/focusAndUpdateMultilineInputRange.js b/src/libs/focusAndUpdateMultilineInputRange.js new file mode 100644 index 000000000000..b5e438899d3d --- /dev/null +++ b/src/libs/focusAndUpdateMultilineInputRange.js @@ -0,0 +1,24 @@ +/** + * Focus a multiline text input and place the cursor at the end of the value (if there is a value in the input). + * + * When a multiline input contains a text value that goes beyond the scroll height, the cursor will be placed + * at the end of the text value, and automatically scroll the input field to this position after the field gains + * focus. This provides a better user experience in cases where the text in the field has to be edited. The auto- + * scroll behaviour works on all platforms except iOS native. + * See https://github.com/Expensify/App/issues/20836 for more details. + * + * @param {Object} input the input element + */ +export default function focusAndUpdateMultilineInputRange(input) { + if (!input) { + return; + } + + input.focus(); + if (input.value && input.setSelectionRange) { + const length = input.value.length; + input.setSelectionRange(length, length); + // eslint-disable-next-line no-param-reassign + input.scrollTop = input.scrollHeight; + } +} diff --git a/src/pages/tasks/NewTaskDescriptionPage.js b/src/pages/tasks/NewTaskDescriptionPage.js index e18615a34ffe..2931b73e74c0 100644 --- a/src/pages/tasks/NewTaskDescriptionPage.js +++ b/src/pages/tasks/NewTaskDescriptionPage.js @@ -1,4 +1,4 @@ -import React, {useEffect, useRef, useState} from 'react'; +import React, {useRef} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import PropTypes from 'prop-types'; @@ -14,6 +14,7 @@ import TextInput from '../../components/TextInput'; import Permissions from '../../libs/Permissions'; import ROUTES from '../../ROUTES'; import * as TaskUtils from '../../libs/actions/Task'; +import focusAndUpdateMultilineInputRange from '../../libs/focusAndUpdateMultilineInputRange'; const propTypes = { /** Beta features list */ @@ -38,17 +39,6 @@ const defaultProps = { function NewTaskDescriptionPage(props) { const inputRef = useRef(null); - // The selection will be used to place the cursor at the end if there is prior text in the text input area - const [selection, setSelection] = useState({start: 0, end: 0}); - - // eslint-disable-next-line rulesdir/prefer-early-return - useEffect(() => { - if (props.task.description) { - const length = props.task.description.length; - setSelection({start: length, end: length}); - } - }, [props.task.description]); - // On submit, we want to call the assignTask function and wait to validate // the response const onSubmit = (values) => { @@ -63,13 +53,7 @@ function NewTaskDescriptionPage(props) { return ( { - if (!inputRef.current) { - return; - } - - inputRef.current.focus(); - }} + onEntryTransitionEnd={() => focusAndUpdateMultilineInputRange(inputRef.current)} > { - setSelection(e.nativeEvent.selection); - }} /> diff --git a/src/pages/tasks/TaskDescriptionPage.js b/src/pages/tasks/TaskDescriptionPage.js index 2791358a912d..aa782aa404b3 100644 --- a/src/pages/tasks/TaskDescriptionPage.js +++ b/src/pages/tasks/TaskDescriptionPage.js @@ -1,4 +1,4 @@ -import React, {useCallback, useEffect, useRef, useState} from 'react'; +import React, {useCallback, useRef} from 'react'; import PropTypes from 'prop-types'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; @@ -12,6 +12,7 @@ import styles from '../../styles/styles'; import compose from '../../libs/compose'; import reportPropTypes from '../reportPropTypes'; import * as TaskUtils from '../../libs/actions/Task'; +import focusAndUpdateMultilineInputRange from '../../libs/focusAndUpdateMultilineInputRange'; const propTypes = { /** Current user session */ @@ -48,21 +49,10 @@ function TaskDescriptionPage(props) { const inputRef = useRef(null); - // Same as NewtaskDescriptionPage, use the selection to place the cursor correctly if there is prior text - const [selection, setSelection] = useState({start: 0, end: 0}); - - // eslint-disable-next-line rulesdir/prefer-early-return - useEffect(() => { - if (props.task.report && props.task.report.description) { - const length = props.task.report.description.length; - setSelection({start: length, end: length}); - } - }, [props.task.report]); - return ( inputRef.current && inputRef.current.focus()} + onEntryTransitionEnd={() => focusAndUpdateMultilineInputRange(inputRef.current)} >
{ - setSelection(e.nativeEvent.selection); - }} />