Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix focus after picking an emoji in edit mode #23258

Merged
merged 13 commits into from
Aug 11, 2023
40 changes: 40 additions & 0 deletions src/libs/focusWithDelay/focusWithDelay.js
Original file line number Diff line number Diff line change
@@ -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;
7 changes: 7 additions & 0 deletions src/libs/focusWithDelay/index.js
Original file line number Diff line number Diff line change
@@ -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);
6 changes: 6 additions & 0 deletions src/libs/focusWithDelay/index.native.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import focusWithDelay from './focusWithDelay';

/**
* We enable the delay on native to display the keyboard correctly
*/
export default focusWithDelay(false);
kosmydel marked this conversation as resolved.
Show resolved Hide resolved
31 changes: 4 additions & 27 deletions src/pages/home/report/ReportActionCompose.js
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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);
kosmydel marked this conversation as resolved.
Show resolved Hide resolved
this.focus = focusWithDelay(this.textInput).bind(this);
kosmydel marked this conversation as resolved.
Show resolved Hide resolved
this.replaceSelectionWithText = this.replaceSelectionWithText.bind(this);
this.focusComposerOnKeyPress = this.focusComposerOnKeyPress.bind(this);
this.checkComposerVisibility = this.checkComposerVisibility.bind(this);
Expand Down Expand Up @@ -379,6 +380,7 @@ class ReportActionCompose extends React.Component {
if (_.isFunction(this.props.animatedRef)) {
this.props.animatedRef(el);
}
this.focus = focusWithDelay(this.textInput).bind(this);
}

/**
Expand Down Expand Up @@ -742,31 +744,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.
Expand Down
11 changes: 10 additions & 1 deletion src/pages/home/report/ReportActionItemMessageEdit.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -271,6 +272,11 @@ function ReportActionItemMessageEdit(props) {
[deleteDraft, isKeyboardShown, isSmallScreenWidth, publishDraft],
);

/**
* Focus the composer text input
*/
const focus = focusWithDelay(textInputRef.current);

return (
<>
<View style={[styles.chatItemMessage, styles.flexRow]}>
Expand Down Expand Up @@ -341,7 +347,10 @@ function ReportActionItemMessageEdit(props) {
<View style={styles.editChatItemEmojiWrapper}>
<EmojiPickerButton
isDisabled={props.shouldDisableEmojiPicker}
onModalHide={() => InteractionManager.runAfterInteractions(() => textInputRef.current.focus())}
onModalHide={() => {
setIsFocused(true);
kosmydel marked this conversation as resolved.
Show resolved Hide resolved
focus(true);
}}
onEmojiSelected={addEmojiToTextBox}
reportAction={props.action}
nativeID={emojiButtonID}
Expand Down