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

prevent focus event when long press in emoji suggestion component #17788

Merged
merged 9 commits into from
Apr 26, 2023
Original file line number Diff line number Diff line change
@@ -1,51 +1,16 @@
import React from 'react';
import {View, Pressable} from 'react-native';
import PropTypes from 'prop-types';
import _ from 'underscore';

// We take FlatList from this package to properly handle the scrolling of EmojiSuggestions in chats since one scroll is nested inside another
import {FlatList} from 'react-native-gesture-handler';
import styles from '../styles/styles';
import * as StyleUtils from '../styles/StyleUtils';
import * as EmojiUtils from '../libs/EmojiUtils';
import Text from './Text';
import CONST from '../CONST';
import getStyledTextArray from '../libs/GetStyledTextArray';

const propTypes = {
/** The index of the highlighted emoji */
highlightedEmojiIndex: PropTypes.number,

/** Array of suggested emoji */
emojis: PropTypes.arrayOf(PropTypes.shape({
/** The emoji code */
code: PropTypes.string,

/** The name of the emoji */
name: PropTypes.string,
})).isRequired,

/** Fired when the user selects an emoji */
onSelect: PropTypes.func.isRequired,

/** Emoji prefix that follows the colon */
prefix: PropTypes.string.isRequired,

/** Show that we can use large emoji picker.
* Depending on available space and whether the input is expanded, we can have a small or large emoji suggester.
* When this value is false, the suggester will have a height of 2.5 items. When this value is true, the height can be up to 5 items. */
isEmojiPickerLarge: PropTypes.bool.isRequired,

/** Show that we should include ReportRecipientLocalTime view height */
shouldIncludeReportRecipientLocalTimeHeight: PropTypes.bool.isRequired,

/** Stores user's preferred skin tone */
preferredSkinToneIndex: PropTypes.number.isRequired,
};

const defaultProps = {
highlightedEmojiIndex: 0,
};
import styles from '../../styles/styles';
import * as StyleUtils from '../../styles/StyleUtils';
import * as EmojiUtils from '../../libs/EmojiUtils';
import Text from '../Text';
import CONST from '../../CONST';
import getStyledTextArray from '../../libs/GetStyledTextArray';
import {propTypes, defaultProps} from './emojiSuggestionsPropTypes';

/**
* @param {Number} numRows
Expand All @@ -71,7 +36,7 @@ const measureHeightOfEmojiRows = (numRows, isEmojiPickerLarge) => {
*/
const keyExtractor = (item, index) => `${item.name}+${index}}`;

const EmojiSuggestions = (props) => {
const BaseEmojiSuggestions = (props) => {
/**
* Render a suggestion menu item component.
* @param {Object} params.item
Expand All @@ -91,6 +56,7 @@ const EmojiSuggestions = (props) => {
)}
onMouseDown={e => e.preventDefault()}
onPress={() => props.onSelect(index)}
onLongPress={() => {}}
>
<View style={styles.emojiSuggestionContainer}>
<Text style={styles.emojiSuggestionsEmoji}>{EmojiUtils.getEmojiCodeWithSkinColor(item, props.preferredSkinToneIndex)}</Text>
Expand All @@ -115,6 +81,7 @@ const EmojiSuggestions = (props) => {

return (
<View
ref={props.forwardedRef}
style={[
styles.emojiSuggestionsContainer,
StyleUtils.getEmojiSuggestionContainerStyle(
Expand All @@ -135,8 +102,11 @@ const EmojiSuggestions = (props) => {
);
};

EmojiSuggestions.propTypes = propTypes;
EmojiSuggestions.defaultProps = defaultProps;
EmojiSuggestions.displayName = 'EmojiSuggestions';
BaseEmojiSuggestions.propTypes = propTypes;
BaseEmojiSuggestions.defaultProps = defaultProps;
BaseEmojiSuggestions.displayName = 'BaseEmojiSuggestions';

export default EmojiSuggestions;
export default React.forwardRef((props, ref) => (
// eslint-disable-next-line react/jsx-props-no-spreading
<BaseEmojiSuggestions {...props} forwardedRef={ref} />
));
41 changes: 41 additions & 0 deletions src/components/EmojiSuggestions/emojiSuggestionsPropTypes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import PropTypes from 'prop-types';

const propTypes = {
/** The index of the highlighted emoji */
highlightedEmojiIndex: PropTypes.number,

/** Array of suggested emoji */
emojis: PropTypes.arrayOf(PropTypes.shape({
/** The emoji code */
code: PropTypes.string,

/** The name of the emoji */
name: PropTypes.string,
})).isRequired,

/** Fired when the user selects an emoji */
onSelect: PropTypes.func.isRequired,

/** Emoji prefix that follows the colon */
prefix: PropTypes.string.isRequired,

/** Show that we can use large emoji picker.
* Depending on available space and whether the input is expanded, we can have a small or large emoji suggester.
* When this value is false, the suggester will have a height of 2.5 items. When this value is true, the height can be up to 5 items. */
isEmojiPickerLarge: PropTypes.bool.isRequired,

/** Show that we should include ReportRecipientLocalTime view height */
shouldIncludeReportRecipientLocalTimeHeight: PropTypes.bool.isRequired,

/** Stores user's preferred skin tone */
preferredSkinToneIndex: PropTypes.number.isRequired,

/** A ref to forward to the suggestion container */
forwardedRef: PropTypes.object,
};

const defaultProps = {
highlightedEmojiIndex: 0,
};

export {propTypes, defaultProps};
32 changes: 32 additions & 0 deletions src/components/EmojiSuggestions/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React from 'react';
tgolen marked this conversation as resolved.
Show resolved Hide resolved
import BaseEmojiSuggestions from './BaseEmojiSuggestions';
import * as DeviceCapabilities from '../../libs/DeviceCapabilities';
import {propTypes, defaultProps} from './emojiSuggestionsPropTypes';

const EmojiSuggestions = (props) => {
const containerRef = React.useRef(null);
React.useEffect(() => {
const container = containerRef.current;
if (!container) {
return;
}
container.onpointerdown = (e) => {
tgolen marked this conversation as resolved.
Show resolved Hide resolved
if (DeviceCapabilities.hasHoverSupport()) {
Copy link
Member

@rushatgabhane rushatgabhane Apr 26, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NAB: I'm curious - Does this mean this bug will occur on mWeb devices that support a stylus??

function hasHoverSupport() {
    return !window.matchMedia('(hover: none)').matches;
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The purpose here is that for desktop web platforms that support hover, we don't need to prevent its default behavior, otherwise the emoji will be placed in the main composer immediately after the long-press is released.

return;
}
e.preventDefault();
};
return () => container.onpointerdown = null;
}, []);

return (
// eslint-disable-next-line react/jsx-props-no-spreading
<BaseEmojiSuggestions {...props} ref={containerRef} />
);
};

EmojiSuggestions.propTypes = propTypes;
EmojiSuggestions.defaultProps = defaultProps;
EmojiSuggestions.displayName = 'EmojiSuggestions';

export default EmojiSuggestions;
14 changes: 14 additions & 0 deletions src/components/EmojiSuggestions/index.native.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React from 'react';
import BaseEmojiSuggestions from './BaseEmojiSuggestions';
import {propTypes, defaultProps} from './emojiSuggestionsPropTypes';

const EmojiSuggestions = props => (
// eslint-disable-next-line react/jsx-props-no-spreading
<BaseEmojiSuggestions {...props} />
);

EmojiSuggestions.propTypes = propTypes;
EmojiSuggestions.defaultProps = defaultProps;
EmojiSuggestions.displayName = 'EmojiSuggestions';

export default EmojiSuggestions;