Skip to content

Commit

Permalink
Merge pull request Expensify#23258 from kosmydel/@swm/edit-mode-focus
Browse files Browse the repository at this point in the history
  • Loading branch information
mountiny authored Aug 11, 2023
2 parents dd0d98f + 4d721e0 commit cceb862
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 28 deletions.
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);
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);
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);
Expand Down Expand Up @@ -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);
}

/**
Expand Down Expand Up @@ -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.
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 @@ -346,7 +352,10 @@ function ReportActionItemMessageEdit(props) {
<View style={styles.editChatItemEmojiWrapper}>
<EmojiPickerButton
isDisabled={props.shouldDisableEmojiPicker}
onModalHide={() => InteractionManager.runAfterInteractions(() => textInputRef.current.focus())}
onModalHide={() => {
setIsFocused(true);
focus(true);
}}
onEmojiSelected={addEmojiToTextBox}
nativeID={emojiButtonID}
reportAction={props.action}
Expand Down

0 comments on commit cceb862

Please sign in to comment.