Skip to content

Commit

Permalink
Merge pull request #17138 from tienifr/fix/16098
Browse files Browse the repository at this point in the history
fix/16098: Listen keydown from user to focus again the composer
  • Loading branch information
PauloGasparSv authored Apr 12, 2023
2 parents 34c5155 + c50fb4d commit f496b9f
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 54 deletions.
2 changes: 1 addition & 1 deletion src/components/EmojiPicker/EmojiPickerButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const EmojiPickerButton = (props) => {
StyleUtils.getButtonBackgroundColorStyle(getButtonState(hovered, pressed)),
])}
disabled={props.isDisabled}
onPress={() => EmojiPickerAction.showEmojiPicker(props.onModalHide, props.onEmojiSelected, emojiPopoverAnchor)}
onPress={() => EmojiPickerAction.showEmojiPicker(props.onModalHide, props.onEmojiSelected, emojiPopoverAnchor, undefined, props.onWillShow)}
nativeID={props.nativeID}
>
{({hovered, pressed}) => (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,48 +9,49 @@ import {
import _ from 'underscore';
import lodashGet from 'lodash/get';
import {withOnyx} from 'react-native-onyx';
import styles from '../../../styles/styles';
import themeColors from '../../../styles/themes/default';
import Composer from '../../../components/Composer';
import ONYXKEYS from '../../../ONYXKEYS';
import Icon from '../../../components/Icon';
import * as Expensicons from '../../../components/Icon/Expensicons';
import AttachmentPicker from '../../../components/AttachmentPicker';
import * as Report from '../../../libs/actions/Report';
import ReportTypingIndicator from './ReportTypingIndicator';
import AttachmentModal from '../../../components/AttachmentModal';
import compose from '../../../libs/compose';
import PopoverMenu from '../../../components/PopoverMenu';
import withWindowDimensions, {windowDimensionsPropTypes} from '../../../components/withWindowDimensions';
import withDrawerState from '../../../components/withDrawerState';
import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize';
import willBlurTextInputOnTapOutside from '../../../libs/willBlurTextInputOnTapOutside';
import CONST from '../../../CONST';
import Navigation from '../../../libs/Navigation/Navigation';
import ROUTES from '../../../ROUTES';
import reportActionPropTypes from './reportActionPropTypes';
import * as ReportUtils from '../../../libs/ReportUtils';
import ReportActionComposeFocusManager from '../../../libs/ReportActionComposeFocusManager';
import participantPropTypes from '../../../components/participantPropTypes';
import ParticipantLocalTime from './ParticipantLocalTime';
import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsPropTypes, withCurrentUserPersonalDetailsDefaultProps} from '../../../components/withCurrentUserPersonalDetails';
import {withNetwork, withPersonalDetails} from '../../../components/OnyxProvider';
import * as User from '../../../libs/actions/User';
import Tooltip from '../../../components/Tooltip';
import EmojiPickerButton from '../../../components/EmojiPicker/EmojiPickerButton';
import * as DeviceCapabilities from '../../../libs/DeviceCapabilities';
import toggleReportActionComposeView from '../../../libs/toggleReportActionComposeView';
import OfflineIndicator from '../../../components/OfflineIndicator';
import ExceededCommentLength from '../../../components/ExceededCommentLength';
import withNavigationFocus from '../../../components/withNavigationFocus';
import * as EmojiUtils from '../../../libs/EmojiUtils';
import ReportDropUI from './ReportDropUI';
import DragAndDrop from '../../../components/DragAndDrop';
import reportPropTypes from '../../reportPropTypes';
import EmojiSuggestions from '../../../components/EmojiSuggestions';
import withKeyboardState, {keyboardStatePropTypes} from '../../../components/withKeyboardState';
import ArrowKeyFocusManager from '../../../components/ArrowKeyFocusManager';
import KeyboardShortcut from '../../../libs/KeyboardShortcut';
import styles from '../../../../styles/styles';
import themeColors from '../../../../styles/themes/default';
import Composer from '../../../../components/Composer';
import ONYXKEYS from '../../../../ONYXKEYS';
import Icon from '../../../../components/Icon';
import * as Expensicons from '../../../../components/Icon/Expensicons';
import AttachmentPicker from '../../../../components/AttachmentPicker';
import * as Report from '../../../../libs/actions/Report';
import ReportTypingIndicator from '../ReportTypingIndicator';
import AttachmentModal from '../../../../components/AttachmentModal';
import compose from '../../../../libs/compose';
import PopoverMenu from '../../../../components/PopoverMenu';
import withWindowDimensions, {windowDimensionsPropTypes} from '../../../../components/withWindowDimensions';
import withDrawerState from '../../../../components/withDrawerState';
import withLocalize, {withLocalizePropTypes} from '../../../../components/withLocalize';
import willBlurTextInputOnTapOutside from '../../../../libs/willBlurTextInputOnTapOutside';
import CONST from '../../../../CONST';
import Navigation from '../../../../libs/Navigation/Navigation';
import ROUTES from '../../../../ROUTES';
import reportActionPropTypes from '../reportActionPropTypes';
import * as ReportUtils from '../../../../libs/ReportUtils';
import ReportActionComposeFocusManager from '../../../../libs/ReportActionComposeFocusManager';
import participantPropTypes from '../../../../components/participantPropTypes';
import ParticipantLocalTime from '../ParticipantLocalTime';
import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsPropTypes, withCurrentUserPersonalDetailsDefaultProps} from '../../../../components/withCurrentUserPersonalDetails';
import {withNetwork, withPersonalDetails} from '../../../../components/OnyxProvider';
import * as User from '../../../../libs/actions/User';
import Tooltip from '../../../../components/Tooltip';
import EmojiPickerButton from '../../../../components/EmojiPicker/EmojiPickerButton';
import * as DeviceCapabilities from '../../../../libs/DeviceCapabilities';
import toggleReportActionComposeView from '../../../../libs/toggleReportActionComposeView';
import OfflineIndicator from '../../../../components/OfflineIndicator';
import ExceededCommentLength from '../../../../components/ExceededCommentLength';
import withNavigationFocus from '../../../../components/withNavigationFocus';
import * as EmojiUtils from '../../../../libs/EmojiUtils';
import ReportDropUI from '../ReportDropUI';
import DragAndDrop from '../../../../components/DragAndDrop';
import reportPropTypes from '../../../reportPropTypes';
import EmojiSuggestions from '../../../../components/EmojiSuggestions';
import withKeyboardState, {keyboardStatePropTypes} from '../../../../components/withKeyboardState';
import ArrowKeyFocusManager from '../../../../components/ArrowKeyFocusManager';
import KeyboardShortcut from '../../../../libs/KeyboardShortcut';
import KeyDownAction from './keyDownAction';

const propTypes = {
/** Beta features list */
Expand Down Expand Up @@ -163,6 +164,8 @@ class ReportActionCompose extends React.Component {
this.setIsFullComposerAvailable = this.setIsFullComposerAvailable.bind(this);
this.focus = this.focus.bind(this);
this.addEmojiToTextBox = this.addEmojiToTextBox.bind(this);
this.replaceSelectionWithInput = this.replaceSelectionWithInput.bind(this);
this.keydownListener = this.keydownListener.bind(this);
this.onSelectionChange = this.onSelectionChange.bind(this);
this.isEmojiCode = this.isEmojiCode.bind(this);
this.setTextInputRef = this.setTextInputRef.bind(this);
Expand Down Expand Up @@ -201,10 +204,13 @@ class ReportActionCompose extends React.Component {
isEmojiPickerLarge: false,
composerHeight: 0,
hasExceededMaxCommentLength: false,
isEmojiPickerVisible: false,
};
}

componentDidMount() {
KeyDownAction.listenKeyDown(this.keydownListener);

// This callback is used in the contextMenuActions to manage giving focus back to the compose input.
// TODO: we should clean up this convoluted code and instead move focus management to something like ReportFooter.js or another higher up component
ReportActionComposeFocusManager.onComposerFocus(() => {
Expand Down Expand Up @@ -260,6 +266,9 @@ class ReportActionCompose extends React.Component {
}

componentWillUnmount() {
if (this.keydownListener) {
KeyDownAction.removeListenKeyDown(this.keydownListener);
}
ReportActionComposeFocusManager.clear();

if (this.unsubscribeEscapeKey) {
Expand Down Expand Up @@ -465,23 +474,48 @@ class ReportActionCompose extends React.Component {
return _.size(this.props.reportActions) === 1;
}

keydownListener(e) {
if (this.state.isFocused || this.state.isEmojiPickerVisible || this.props.modal.isVisible || this.props.isSmallScreenWidth) {
return;
}

// if the key pressed is non-character keys like Enter, Shift, ... do not focus
if (e.key.length > 1) {
return;
}

// if we're typing on another input/text area, do not focus
if (e.target.nodeName === 'INPUT' || e.target.nodeName === 'TEXTAREA') {
return;
}

this.focus();
this.replaceSelectionWithInput(e.key);
}

/**
* Callback for the emoji picker to add whatever emoji is chosen into the main input
*
* @param {String} emoji
* @param {String} text
*/
addEmojiToTextBox(emoji) {
const emojiWithSpace = `${emoji} `;
replaceSelectionWithInput(text) {
const newComment = this.comment.slice(0, this.state.selection.start)
+ emojiWithSpace
+ text
+ this.comment.slice(this.state.selection.end, this.comment.length);
this.setState(prevState => ({
selection: {
start: prevState.selection.start + emojiWithSpace.length,
end: prevState.selection.start + emojiWithSpace.length,
start: prevState.selection.start + text.length,
end: prevState.selection.start + text.length,
},
}));
this.updateComment(newComment);
}), this.updateComment(newComment));
}

/**
* Callback for the emoji picker to add whatever emoji is chosen into the main input
*
* @param {String} emoji
*/
addEmojiToTextBox(emoji) {
const emojiWithSpace = `${emoji} `;
this.replaceSelectionWithInput(emojiWithSpace);
}

/**
Expand Down Expand Up @@ -870,8 +904,12 @@ class ReportActionCompose extends React.Component {
{DeviceCapabilities.canUseTouchScreen() && this.props.isMediumScreenWidth ? null : (
<EmojiPickerButton
isDisabled={isBlockedFromConcierge || this.props.disabled}
onModalHide={() => this.focus(true)}
onModalHide={() => {
this.focus(true);
this.setState({isEmojiPickerVisible: false});
}}
onEmojiSelected={this.addEmojiToTextBox}
onWillShow={() => this.setState({isEmojiPickerVisible: true})}
/>
)}
<View
Expand Down
10 changes: 10 additions & 0 deletions src/pages/home/report/ReportActionCompose/keyDownAction.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
function listenKeyDown(callback) {
document.addEventListener('keydown', callback);
}

function removeListenKeyDown(callback) {
document.removeEventListener('keydown', callback);
}

export default {listenKeyDown, removeListenKeyDown};

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
function listenKeyDown() {}
function removeListenKeyDown() {}

export default {removeListenKeyDown, listenKeyDown};

0 comments on commit f496b9f

Please sign in to comment.