From d9395caa63398fd667d5dedea1c5a2d14ebef287 Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Fri, 23 Jun 2023 04:31:53 +0100 Subject: [PATCH 1/2] remove the selection prop from the task description input fields --- src/libs/actions/Task.js | 20 ++++++++++++++++++ src/pages/tasks/NewTaskDescriptionPage.js | 25 ++--------------------- src/pages/tasks/TaskDescriptionPage.js | 19 ++--------------- 3 files changed, 24 insertions(+), 40 deletions(-) diff --git a/src/libs/actions/Task.js b/src/libs/actions/Task.js index 5e27d7db764d..37d37e69e5eb 100644 --- a/src/libs/actions/Task.js +++ b/src/libs/actions/Task.js @@ -672,6 +672,25 @@ function isTaskAssigneeOrTaskOwner(taskReport, sessionAccountID) { return sessionAccountID === getTaskOwnerAccountID(taskReport) || sessionAccountID === getTaskAssigneeAccountID(taskReport); } +/** + * Focus the task description text input and place the cursor at the end of the value (if there is a value in the input). + * + * @param {Object} input the input element + */ +function focusAndUpdateTaskDescriptionInputRange(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; + } +} + export { createTaskAndNavigate, editTaskAndNavigate, @@ -693,4 +712,5 @@ export { dismissModalAndClearOutTaskInfo, getTaskAssigneeAccountID, isTaskAssigneeOrTaskOwner, + focusAndUpdateTaskDescriptionInputRange, }; diff --git a/src/pages/tasks/NewTaskDescriptionPage.js b/src/pages/tasks/NewTaskDescriptionPage.js index e18615a34ffe..771559d8bb53 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'; @@ -38,17 +38,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 +52,7 @@ function NewTaskDescriptionPage(props) { return ( { - if (!inputRef.current) { - return; - } - - inputRef.current.focus(); - }} + onEntryTransitionEnd={() => TaskUtils.focusAndUpdateTaskDescriptionInputRange(inputRef.current)} > { - setSelection(e.nativeEvent.selection); - }} /> diff --git a/src/pages/tasks/TaskDescriptionPage.js b/src/pages/tasks/TaskDescriptionPage.js index 2791358a912d..fced5a5ac306 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'; @@ -48,21 +48,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={() => TaskUtils.focusAndUpdateTaskDescriptionInputRange(inputRef.current)} >
{ - setSelection(e.nativeEvent.selection); - }} />
From 812bbc554b49a27c54e2b6c214812a4a626bde9e Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Tue, 27 Jun 2023 02:43:00 +0100 Subject: [PATCH 2/2] focusAndUpdateMultilineInputRange refactor, add comments to provide context --- src/libs/actions/Task.js | 20 ---------------- src/libs/focusAndUpdateMultilineInputRange.js | 24 +++++++++++++++++++ src/pages/tasks/NewTaskDescriptionPage.js | 3 ++- src/pages/tasks/TaskDescriptionPage.js | 3 ++- 4 files changed, 28 insertions(+), 22 deletions(-) create mode 100644 src/libs/focusAndUpdateMultilineInputRange.js diff --git a/src/libs/actions/Task.js b/src/libs/actions/Task.js index 37d37e69e5eb..5e27d7db764d 100644 --- a/src/libs/actions/Task.js +++ b/src/libs/actions/Task.js @@ -672,25 +672,6 @@ function isTaskAssigneeOrTaskOwner(taskReport, sessionAccountID) { return sessionAccountID === getTaskOwnerAccountID(taskReport) || sessionAccountID === getTaskAssigneeAccountID(taskReport); } -/** - * Focus the task description text input and place the cursor at the end of the value (if there is a value in the input). - * - * @param {Object} input the input element - */ -function focusAndUpdateTaskDescriptionInputRange(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; - } -} - export { createTaskAndNavigate, editTaskAndNavigate, @@ -712,5 +693,4 @@ export { dismissModalAndClearOutTaskInfo, getTaskAssigneeAccountID, isTaskAssigneeOrTaskOwner, - focusAndUpdateTaskDescriptionInputRange, }; 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 771559d8bb53..2931b73e74c0 100644 --- a/src/pages/tasks/NewTaskDescriptionPage.js +++ b/src/pages/tasks/NewTaskDescriptionPage.js @@ -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 */ @@ -52,7 +53,7 @@ function NewTaskDescriptionPage(props) { return ( TaskUtils.focusAndUpdateTaskDescriptionInputRange(inputRef.current)} + onEntryTransitionEnd={() => focusAndUpdateMultilineInputRange(inputRef.current)} > TaskUtils.focusAndUpdateTaskDescriptionInputRange(inputRef.current)} + onEntryTransitionEnd={() => focusAndUpdateMultilineInputRange(inputRef.current)} >