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
35 changes: 35 additions & 0 deletions src/libs/focusWithDelay.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import {InteractionManager} from 'react-native';

/**
* 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
*/

function createFocusFunction(textInput) {
kosmydel marked this conversation as resolved.
Show resolved Hide resolved
/**
* Focus the text input
* @param {Boolean} [shouldelay=false] Impose delay before focusing the text input
*/
return function focus(shouldelay = false) {
kosmydel marked this conversation as resolved.
Show resolved Hide resolved
// 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 (!shouldelay) {
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 createFocusFunction;
31 changes: 4 additions & 27 deletions src/pages/home/report/ReportActionCompose.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
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 _ from 'underscore';
import lodashGet from 'lodash/get';
import {withOnyx} from 'react-native-onyx';
import createFocusFunction from '../../../libs/focusWithDelay';
kosmydel marked this conversation as resolved.
Show resolved Hide resolved
import styles from '../../../styles/styles';
import themeColors from '../../../styles/themes/default';
import Composer from '../../../components/Composer';
Expand Down Expand Up @@ -173,7 +174,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 = createFocusFunction(this.textInput).bind(this);
this.replaceSelectionWithText = this.replaceSelectionWithText.bind(this);
this.focusComposerOnKeyPress = this.focusComposerOnKeyPress.bind(this);
this.checkComposerVisibility = this.checkComposerVisibility.bind(this);
Expand Down Expand Up @@ -367,6 +368,7 @@ class ReportActionCompose extends React.Component {
setTextInputRef(el) {
ReportActionComposeFocusManager.composerRef.current = el;
this.textInput = el;
this.focus = createFocusFunction(this.textInput).bind(this);
}

/**
Expand Down Expand Up @@ -730,31 +732,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 @@ -35,6 +35,7 @@ import useKeyboardState from '../../../hooks/useKeyboardState';
import useWindowDimensions from '../../../hooks/useWindowDimensions';
import useReportScrollManager from '../../../hooks/useReportScrollManager';
import * as EmojiPickerAction from '../../../libs/actions/EmojiPickerAction';
import focusAfterDelay from '../../../libs/focusWithDelay';
kosmydel marked this conversation as resolved.
Show resolved Hide resolved

const propTypes = {
/** All the data of the action */
Expand Down Expand Up @@ -270,6 +271,11 @@ function ReportActionItemMessageEdit(props) {
[deleteDraft, isKeyboardShown, isSmallScreenWidth, publishDraft],
);

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

return (
<>
<View style={[styles.chatItemMessage, styles.flexRow]}>
Expand Down Expand Up @@ -345,7 +351,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}
nativeID={emojiButtonID}
reportAction={props.action}
Expand Down
Loading