diff --git a/src/libs/focusWithDelay/focusWithDelay.js b/src/libs/focusWithDelay/focusWithDelay.js new file mode 100644 index 000000000000..143d5dd12430 --- /dev/null +++ b/src/libs/focusWithDelay/focusWithDelay.js @@ -0,0 +1,40 @@ +import {InteractionManager} from 'react-native'; + +/** + * Creates a function that can be used to focus a text input + * @param {Boolean} disableDelay whether to force focus without a delay (on web and desktop) + * @returns {Function} a focusWithDelay function + */ +function focusWithDelay(disableDelay = false) { + /** + * Create a function that focuses a text input. + * @param {Object} textInput the text input to focus + * @returns {Function} a function that focuses the text input with a configurable delay + */ + return (textInput) => + /** + * Focus the text input + * @param {Boolean} [shouldDelay=false] Impose delay before focusing the text input + */ + (shouldDelay = false) => { + // There could be other animations running while we trigger manual focus. + // This prevents focus from making those animations janky. + InteractionManager.runAfterInteractions(() => { + if (!textInput) { + return; + } + + if (disableDelay || !shouldDelay) { + textInput.focus(); + } else { + // Keyboard is not opened after Emoji Picker is closed + // SetTimeout is used as a workaround + // https://github.com/react-native-modal/react-native-modal/issues/114 + // We carefully choose a delay. 100ms is found enough for keyboard to open. + setTimeout(() => textInput.focus(), 100); + } + }); + }; +} + +export default focusWithDelay; diff --git a/src/libs/focusWithDelay/index.js b/src/libs/focusWithDelay/index.js new file mode 100644 index 000000000000..faeb43147c5c --- /dev/null +++ b/src/libs/focusWithDelay/index.js @@ -0,0 +1,7 @@ +import focusWithDelay from './focusWithDelay'; + +/** + * We pass true to disable the delay on the web because it doesn't require + * using the workaround (explained in the focusWithDelay.js file). + */ +export default focusWithDelay(true); diff --git a/src/libs/focusWithDelay/index.native.js b/src/libs/focusWithDelay/index.native.js new file mode 100644 index 000000000000..27fb19fe1570 --- /dev/null +++ b/src/libs/focusWithDelay/index.native.js @@ -0,0 +1,6 @@ +import focusWithDelay from './focusWithDelay'; + +/** + * We enable the delay on native to display the keyboard correctly + */ +export default focusWithDelay(false); diff --git a/src/pages/home/report/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose.js index 123d804409a4..ef783a208ef8 100644 --- a/src/pages/home/report/ReportActionCompose.js +++ b/src/pages/home/report/ReportActionCompose.js @@ -1,11 +1,12 @@ import React from 'react'; import PropTypes from 'prop-types'; -import {View, InteractionManager, LayoutAnimation, NativeModules, findNodeHandle} from 'react-native'; +import {View, LayoutAnimation, NativeModules, findNodeHandle} from 'react-native'; import {runOnJS} from 'react-native-reanimated'; import {Gesture, GestureDetector} from 'react-native-gesture-handler'; import _ from 'underscore'; import lodashGet from 'lodash/get'; import {withOnyx} from 'react-native-onyx'; +import focusWithDelay from '../../../libs/focusWithDelay'; import styles from '../../../styles/styles'; import themeColors from '../../../styles/themes/default'; import Composer from '../../../components/Composer'; @@ -177,7 +178,7 @@ class ReportActionCompose extends React.Component { this.submitForm = this.submitForm.bind(this); this.setIsFocused = this.setIsFocused.bind(this); this.setIsFullComposerAvailable = this.setIsFullComposerAvailable.bind(this); - this.focus = this.focus.bind(this); + this.focus = focusWithDelay(this.textInput).bind(this); this.replaceSelectionWithText = this.replaceSelectionWithText.bind(this); this.focusComposerOnKeyPress = this.focusComposerOnKeyPress.bind(this); this.checkComposerVisibility = this.checkComposerVisibility.bind(this); @@ -381,6 +382,7 @@ class ReportActionCompose extends React.Component { if (_.isFunction(this.props.animatedRef)) { this.props.animatedRef(el); } + this.focus = focusWithDelay(this.textInput).bind(this); } /** @@ -741,31 +743,6 @@ class ReportActionCompose extends React.Component { this.replaceSelectionWithText(e.key, false); } - /** - * Focus the composer text input - * @param {Boolean} [shouldelay=false] Impose delay before focusing the composer - * @memberof ReportActionCompose - */ - focus(shouldelay = false) { - // There could be other animations running while we trigger manual focus. - // This prevents focus from making those animations janky. - InteractionManager.runAfterInteractions(() => { - if (!this.textInput) { - return; - } - - if (!shouldelay) { - this.textInput.focus(); - } else { - // Keyboard is not opened after Emoji Picker is closed - // SetTimeout is used as a workaround - // https://github.com/react-native-modal/react-native-modal/issues/114 - // We carefully choose a delay. 100ms is found enough for keyboard to open. - setTimeout(() => this.textInput.focus(), 100); - } - }); - } - /** * Save our report comment in Onyx. We debounce this method in the constructor so that it's not called too often * to update Onyx and re-render this component. diff --git a/src/pages/home/report/ReportActionItemMessageEdit.js b/src/pages/home/report/ReportActionItemMessageEdit.js index 54c5fec4533e..cb03d12b4caf 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.js +++ b/src/pages/home/report/ReportActionItemMessageEdit.js @@ -36,6 +36,7 @@ import useKeyboardState from '../../../hooks/useKeyboardState'; import useWindowDimensions from '../../../hooks/useWindowDimensions'; import useReportScrollManager from '../../../hooks/useReportScrollManager'; import * as EmojiPickerAction from '../../../libs/actions/EmojiPickerAction'; +import focusWithDelay from '../../../libs/focusWithDelay'; const propTypes = { /** All the data of the action */ @@ -271,6 +272,11 @@ function ReportActionItemMessageEdit(props) { [deleteDraft, isKeyboardShown, isSmallScreenWidth, publishDraft], ); + /** + * Focus the composer text input + */ + const focus = focusWithDelay(textInputRef.current); + return ( <> @@ -346,7 +352,10 @@ function ReportActionItemMessageEdit(props) { InteractionManager.runAfterInteractions(() => textInputRef.current.focus())} + onModalHide={() => { + setIsFocused(true); + focus(true); + }} onEmojiSelected={addEmojiToTextBox} nativeID={emojiButtonID} reportAction={props.action}