diff --git a/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.js b/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.js
index dec809e1266b..a1abfb0326f3 100644
--- a/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.js
+++ b/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.js
@@ -1,9 +1,30 @@
import React from 'react';
import {Pressable} from 'react-native';
-import * as anchorForAttachmentsOnlyPropTypes from './anchorForAttachmentsOnlyPropTypes';
+import PropTypes from 'prop-types';
+import {
+ propTypes as anchorForAttachmentsOnlyPropTypes,
+ defaultProps as anchorForAttachmentsOnlyDefaultProps,
+} from './anchorForAttachmentsOnlyPropTypes';
import AttachmentView from '../AttachmentView';
import fileDownload from '../../libs/fileDownload';
import addEncryptedAuthTokenToURL from '../../libs/addEncryptedAuthTokenToURL';
+import {ShowContextMenuContext, showContextMenuForReport} from '../ShowContextMenuContext';
+
+const propTypes = {
+ /** Press in handler for the link */
+ onPressIn: PropTypes.func,
+
+ /** Press out handler for the link */
+ onPressOut: PropTypes.func,
+
+ ...anchorForAttachmentsOnlyPropTypes,
+};
+
+const defaultProps = {
+ onPressIn: undefined,
+ onPressOut: undefined,
+ ...anchorForAttachmentsOnlyDefaultProps,
+};
class BaseAnchorForAttachmentsOnly extends React.Component {
constructor(props) {
@@ -30,27 +51,45 @@ class BaseAnchorForAttachmentsOnly extends React.Component {
const source = addEncryptedAuthTokenToURL(this.props.source);
return (
- {
- if (this.state.isDownloading) {
- return;
- }
- this.processDownload(source, this.props.displayName);
- }}
- >
-
-
+
+ {({
+ anchor,
+ reportID,
+ action,
+ checkIfContextMenuActive,
+ }) => (
+ {
+ if (this.state.isDownloading) {
+ return;
+ }
+ this.processDownload(source, this.props.displayName);
+ }}
+ onPressIn={this.props.onPressIn}
+ onPressOut={this.props.onPressOut}
+ onLongPress={event => showContextMenuForReport(
+ event,
+ anchor,
+ reportID,
+ action,
+ checkIfContextMenuActive,
+ )}
+ >
+
+
+ )}
+
);
}
}
-BaseAnchorForAttachmentsOnly.propTypes = anchorForAttachmentsOnlyPropTypes.propTypes;
-BaseAnchorForAttachmentsOnly.defaultProps = anchorForAttachmentsOnlyPropTypes.defaultProps;
+BaseAnchorForAttachmentsOnly.propTypes = propTypes;
+BaseAnchorForAttachmentsOnly.defaultProps = defaultProps;
export default BaseAnchorForAttachmentsOnly;
diff --git a/src/components/AnchorForAttachmentsOnly/index.js b/src/components/AnchorForAttachmentsOnly/index.js
index a71d65e969cd..9a0fb4be1237 100644
--- a/src/components/AnchorForAttachmentsOnly/index.js
+++ b/src/components/AnchorForAttachmentsOnly/index.js
@@ -1,9 +1,17 @@
import React from 'react';
import * as anchorForAttachmentsOnlyPropTypes from './anchorForAttachmentsOnlyPropTypes';
import BaseAnchorForAttachmentsOnly from './BaseAnchorForAttachmentsOnly';
+import * as DeviceCapabilities from '../../libs/DeviceCapabilities';
+import ControlSelection from '../../libs/ControlSelection';
-// eslint-disable-next-line react/jsx-props-no-spreading
-const AnchorForAttachmentsOnly = props => ;
+const AnchorForAttachmentsOnly = props => (
+ DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()}
+ onPressOut={() => ControlSelection.unblock()}
+ />
+);
AnchorForAttachmentsOnly.propTypes = anchorForAttachmentsOnlyPropTypes.propTypes;
AnchorForAttachmentsOnly.defaultProps = anchorForAttachmentsOnlyPropTypes.defaultProps;
diff --git a/src/components/AnchorForCommentsOnly/BaseAnchorForCommentsOnly.js b/src/components/AnchorForCommentsOnly/BaseAnchorForCommentsOnly.js
index e48e68e329f5..18d3fbf25960 100644
--- a/src/components/AnchorForCommentsOnly/BaseAnchorForCommentsOnly.js
+++ b/src/components/AnchorForCommentsOnly/BaseAnchorForCommentsOnly.js
@@ -1,6 +1,7 @@
import _ from 'underscore';
import React from 'react';
import {StyleSheet} from 'react-native';
+import PropTypes from 'prop-types';
import lodashGet from 'lodash/get';
import Str from 'expensify-common/lib/str';
import Text from '../Text';
@@ -11,13 +12,28 @@ import Tooltip from '../Tooltip';
import * as DeviceCapabilities from '../../libs/DeviceCapabilities';
import styles from '../../styles/styles';
import withWindowDimensions, {windowDimensionsPropTypes} from '../withWindowDimensions';
-import {propTypes as anchorForCommentsOnlyPropTypes, defaultProps} from './anchorForCommentsOnlyPropTypes';
+import {
+ propTypes as anchorForCommentsOnlyPropTypes,
+ defaultProps as anchorForCommentsOnlyDefaultProps,
+} from './anchorForCommentsOnlyPropTypes';
const propTypes = {
+ /** Press in handler for the link */
+ onPressIn: PropTypes.func,
+
+ /** Press out handler for the link */
+ onPressOut: PropTypes.func,
+
...anchorForCommentsOnlyPropTypes,
...windowDimensionsPropTypes,
};
+const defaultProps = {
+ onPressIn: undefined,
+ onPressOut: undefined,
+ ...anchorForCommentsOnlyDefaultProps,
+};
+
/*
* This is a default anchor component for regular links.
*/
@@ -45,6 +61,9 @@ const BaseAnchorForCommentsOnly = (props) => {
);
}
}
+ onPress={linkProps.onPress}
+ onPressIn={props.onPressIn}
+ onPressOut={props.onPressOut}
>
{
rel: props.rel,
target: props.target,
}}
- // eslint-disable-next-line react/jsx-props-no-spreading
- {...linkProps}
+ href={linkProps.href}
// eslint-disable-next-line react/jsx-props-no-spreading
{...rest}
>
diff --git a/src/components/AnchorForCommentsOnly/index.js b/src/components/AnchorForCommentsOnly/index.js
index 1526e78007fe..d25b819cddba 100644
--- a/src/components/AnchorForCommentsOnly/index.js
+++ b/src/components/AnchorForCommentsOnly/index.js
@@ -1,9 +1,18 @@
import React from 'react';
import * as anchorForCommentsOnlyPropTypes from './anchorForCommentsOnlyPropTypes';
import BaseAnchorForCommentsOnly from './BaseAnchorForCommentsOnly';
+import * as DeviceCapabilities from '../../libs/DeviceCapabilities';
+import ControlSelection from '../../libs/ControlSelection';
+
+const AnchorForCommentsOnly = props => (
+ DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()}
+ onPressOut={() => ControlSelection.unblock()}
+ />
+);
-// eslint-disable-next-line react/jsx-props-no-spreading
-const AnchorForCommentsOnly = props => ;
AnchorForCommentsOnly.propTypes = anchorForCommentsOnlyPropTypes.propTypes;
AnchorForCommentsOnly.defaultProps = anchorForCommentsOnlyPropTypes.defaultProps;
AnchorForCommentsOnly.displayName = 'AnchorForCommentsOnly';
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js
index f3e9009a72ca..cef91ba9a9e2 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js
@@ -6,6 +6,7 @@ import styles from '../../../styles/styles';
import ThumbnailImage from '../../ThumbnailImage';
import PressableWithoutFocus from '../../PressableWithoutFocus';
import CONST from '../../../CONST';
+import {ShowContextMenuContext, showContextMenuForReport} from '../../ShowContextMenuContext';
const ImageRenderer = (props) => {
const htmlAttribs = props.tnode.attributes;
@@ -57,27 +58,37 @@ const ImageRenderer = (props) => {
imageHeight={imageHeight}
/>
) : (
-
- {({show}) => (
-
+ {({
+ anchor,
+ reportID,
+ action,
+ checkIfContextMenuActive,
+ }) => (
+
-
-
+ {({show}) => (
+ showContextMenuForReport(event, anchor, reportID, action, checkIfContextMenuActive)}
+ >
+
+
+ )}
+
)}
-
+
);
};
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer/BasePreRenderer.js b/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer/BasePreRenderer.js
index 5edafb4dc04d..25ea94e0ae6b 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer/BasePreRenderer.js
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer/BasePreRenderer.js
@@ -1,28 +1,59 @@
import React, {forwardRef} from 'react';
import {ScrollView} from 'react-native-gesture-handler';
-import {View} from 'react-native';
+import {Pressable} from 'react-native';
+import PropTypes from 'prop-types';
import _ from 'underscore';
import htmlRendererPropTypes from '../htmlRendererPropTypes';
import withLocalize from '../../../withLocalize';
+import {ShowContextMenuContext, showContextMenuForReport} from '../../../ShowContextMenuContext';
+
+const propTypes = {
+ /** Press in handler for the code block */
+ onPressIn: PropTypes.func,
+
+ /** Press out handler for the code block */
+ onPressOut: PropTypes.func,
+
+ ...htmlRendererPropTypes,
+};
+
+const defaultProps = {
+ onPressIn: undefined,
+ onPressOut: undefined,
+};
const BasePreRenderer = forwardRef((props, ref) => {
const TDefaultRenderer = props.TDefaultRenderer;
- const defaultRendererProps = _.omit(props, ['TDefaultRenderer']);
+ const defaultRendererProps = _.omit(props, ['TDefaultRenderer', 'onPressIn', 'onPressOut', 'onLongPress']);
return (
- true}>
- {/* eslint-disable-next-line react/jsx-props-no-spreading */}
-
-
+
+ {({
+ anchor,
+ reportID,
+ action,
+ checkIfContextMenuActive,
+ }) => (
+ showContextMenuForReport(event, anchor, reportID, action, checkIfContextMenuActive)}
+ >
+ {/* eslint-disable-next-line react/jsx-props-no-spreading */}
+
+
+ )}
+
);
});
BasePreRenderer.displayName = 'BasePreRenderer';
-BasePreRenderer.propTypes = htmlRendererPropTypes;
+BasePreRenderer.propTypes = propTypes;
+BasePreRenderer.defaultProps = defaultProps;
export default withLocalize(BasePreRenderer);
diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer/index.js b/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer/index.js
index 59c05e73ed97..9e158ede05f0 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer/index.js
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer/index.js
@@ -3,6 +3,8 @@ import _ from 'underscore';
import withLocalize from '../../../withLocalize';
import htmlRendererPropTypes from '../htmlRendererPropTypes';
import BasePreRenderer from './BasePreRenderer';
+import * as DeviceCapabilities from '../../../../libs/DeviceCapabilities';
+import ControlSelection from '../../../../libs/ControlSelection';
class PreRenderer extends React.Component {
constructor(props) {
@@ -58,6 +60,8 @@ class PreRenderer extends React.Component {
// eslint-disable-next-line react/jsx-props-no-spreading
{...this.props}
ref={el => this.ref = el}
+ onPressIn={() => DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()}
+ onPressOut={() => ControlSelection.unblock()}
/>
);
}
diff --git a/src/components/PressableWithSecondaryInteraction/index.js b/src/components/PressableWithSecondaryInteraction/index.js
index d9e1629d33d6..7b971e726867 100644
--- a/src/components/PressableWithSecondaryInteraction/index.js
+++ b/src/components/PressableWithSecondaryInteraction/index.js
@@ -1,7 +1,6 @@
import _ from 'underscore';
import React, {Component} from 'react';
import {Pressable} from 'react-native';
-import {LongPressGestureHandler, State} from 'react-native-gesture-handler';
import * as pressableWithSecondaryInteractionPropTypes from './pressableWithSecondaryInteractionPropTypes';
import styles from '../../styles/styles';
import * as DeviceCapabilities from '../../libs/DeviceCapabilities';
@@ -12,7 +11,6 @@ import * as DeviceCapabilities from '../../libs/DeviceCapabilities';
class PressableWithSecondaryInteraction extends Component {
constructor(props) {
super(props);
- this.callSecondaryInteractionWithMappedEvent = this.callSecondaryInteractionWithMappedEvent.bind(this);
this.executeSecondaryInteractionOnContextMenu = this.executeSecondaryInteractionOnContextMenu.bind(this);
}
@@ -27,27 +25,6 @@ class PressableWithSecondaryInteraction extends Component {
this.pressableRef.removeEventListener('contextmenu', this.executeSecondaryInteractionOnContextMenu);
}
- /**
- * @param {Object} e
- */
- callSecondaryInteractionWithMappedEvent(e) {
- if ((e.nativeEvent.state !== State.ACTIVE) || DeviceCapabilities.hasHoverSupport()) {
- return;
- }
-
- // Map gesture event to normal Responder event
- const {
- absoluteX, absoluteY, locationX, locationY,
- } = e.nativeEvent;
- const mapEvent = {
- ...e,
- nativeEvent: {
- ...e.nativeEvent, pageX: absoluteX, pageY: absoluteY, x: locationX, y: locationY,
- },
- };
- this.props.onSecondaryInteraction(mapEvent);
- }
-
/**
* @param {contextmenu} e - A right-click MouseEvent.
* https://developer.mozilla.org/en-US/docs/Web/API/Element/contextmenu_event
@@ -65,19 +42,23 @@ class PressableWithSecondaryInteraction extends Component {
// On Web, Text does not support LongPress events thus manage inline mode with styling instead of using Text.
return (
-
- this.pressableRef = el}
- // eslint-disable-next-line react/jsx-props-no-spreading
- {...defaultPressableProps}
- >
- {this.props.children}
-
-
+ {
+ if (DeviceCapabilities.hasHoverSupport()) {
+ return;
+ }
+ this.props.onSecondaryInteraction(e);
+ }}
+ onPressOut={this.props.onPressOut}
+ onPress={this.props.onPress}
+ ref={el => this.pressableRef = el}
+ // eslint-disable-next-line react/jsx-props-no-spreading
+ {...defaultPressableProps}
+ >
+ {this.props.children}
+
);
}
}
diff --git a/src/components/PressableWithSecondaryInteraction/index.native.js b/src/components/PressableWithSecondaryInteraction/index.native.js
index 3b6edfc92198..f186146b4134 100644
--- a/src/components/PressableWithSecondaryInteraction/index.native.js
+++ b/src/components/PressableWithSecondaryInteraction/index.native.js
@@ -1,7 +1,6 @@
import _ from 'underscore';
import React, {forwardRef} from 'react';
import {Pressable} from 'react-native';
-import {LongPressGestureHandler, State} from 'react-native-gesture-handler';
import * as pressableWithSecondaryInteractionPropTypes from './pressableWithSecondaryInteractionPropTypes';
import Text from '../Text';
import HapticFeedback from '../../libs/HapticFeedback';
@@ -16,40 +15,21 @@ const PressableWithSecondaryInteraction = (props) => {
// Use Text node for inline mode to prevent content overflow.
const Node = props.inline ? Text : Pressable;
return (
- {
- if (e.nativeEvent.state !== State.ACTIVE) {
- return;
- }
-
- // Map gesture event to normal Responder event
- const {
- absoluteX, absoluteY, locationX, locationY,
- } = e.nativeEvent;
- const mapEvent = {
- ...e,
- nativeEvent: {
- ...e.nativeEvent, pageX: absoluteX, pageY: absoluteY, x: locationX, y: locationY,
- },
- };
-
+ {
e.preventDefault();
HapticFeedback.trigger();
- props.onSecondaryInteraction(mapEvent);
+ props.onSecondaryInteraction(e);
}}
- >
-
- {props.children}
-
-
-
+ {...(_.omit(props, 'onLongPress'))}
+ >
+ {props.children}
+
);
};
diff --git a/src/components/PressableWithoutFocus.js b/src/components/PressableWithoutFocus.js
index 30668608496f..10ea16c71d06 100644
--- a/src/components/PressableWithoutFocus.js
+++ b/src/components/PressableWithoutFocus.js
@@ -12,6 +12,9 @@ const propTypes = {
/** Callback for onPress event */
onPress: PropTypes.func.isRequired,
+ /** Callback for onLongPress event */
+ onLongPress: PropTypes.func,
+
/** Styles that should be passed to touchable container */
// eslint-disable-next-line react/forbid-prop-types
styles: PropTypes.arrayOf(PropTypes.object),
@@ -19,6 +22,7 @@ const propTypes = {
const defaultProps = {
styles: [],
+ onLongPress: undefined,
};
/**
@@ -41,7 +45,12 @@ class PressableWithoutFocus extends React.Component {
render() {
return (
- this.pressableRef = el} style={this.props.styles}>
+ this.pressableRef = el}
+ style={this.props.styles}
+ >
{this.props.children}
);
diff --git a/src/components/ReportActionItem/IOUAction.js b/src/components/ReportActionItem/IOUAction.js
index 8896b4a41b58..ba78f8cd9cb8 100644
--- a/src/components/ReportActionItem/IOUAction.js
+++ b/src/components/ReportActionItem/IOUAction.js
@@ -25,6 +25,12 @@ const propTypes = {
/** Is this IOUACTION the most recent? */
isMostRecentIOUReportAction: PropTypes.bool.isRequired,
+ /** Popover context menu anchor, used for showing context menu */
+ contextMenuAnchor: PropTypes.shape({current: PropTypes.elementType}),
+
+ /** Callback for updating context menu active state, used for showing context menu */
+ checkIfContextMenuActive: PropTypes.func,
+
/* Onyx Props */
/** chatReport associated with iouReport */
chatReport: PropTypes.shape({
@@ -48,6 +54,8 @@ const propTypes = {
};
const defaultProps = {
+ contextMenuAnchor: undefined,
+ checkIfContextMenuActive: () => {},
chatReport: {
participants: [],
},
@@ -79,13 +87,19 @@ const IOUAction = (props) => {
<>
{shouldShowIOUPreview && (
{},
+ action: undefined,
+ contextMenuAnchor: undefined,
+ checkIfContextMenuActive: () => {},
containerStyles: [],
walletTerms: {},
pendingAction: null,
@@ -129,8 +145,30 @@ const IOUPreview = (props) => {
{style: 'currency', currency: props.iouReport.currency},
) : '';
const avatarTooltip = [Str.removeSMSDomain(managerEmail), Str.removeSMSDomain(ownerEmail)];
+
+ const showContextMenu = (event) => {
+ // Use action and shouldHidePayButton props to check if we are in IOUDetailsModal,
+ // if it's true, do nothing when user long press, otherwise show context menu.
+ if (!props.action && props.shouldHidePayButton) {
+ return;
+ }
+
+ showContextMenuForReport(
+ event,
+ props.contextMenuAnchor,
+ props.chatReportID,
+ props.action,
+ props.checkIfContextMenuActive,
+ );
+ };
+
return (
-
+ DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()}
+ onPressOut={() => ControlSelection.unblock()}
+ onLongPress={showContextMenu}
+ >
{