diff --git a/src/components/Hoverable/hoverablePropTypes.js b/src/components/Hoverable/hoverablePropTypes.js index 07b8a8741efb..c17fa804b601 100644 --- a/src/components/Hoverable/hoverablePropTypes.js +++ b/src/components/Hoverable/hoverablePropTypes.js @@ -19,9 +19,6 @@ const propTypes = { /** Function that executes when the mouse leaves the children. */ onHoverOut: PropTypes.func, - - // If the mouse clicks outside, should we dismiss hover? - resetsOnClickOutside: PropTypes.bool, }; const defaultProps = { @@ -29,7 +26,6 @@ const defaultProps = { containerStyles: [], onHoverIn: () => {}, onHoverOut: () => {}, - resetsOnClickOutside: false, }; export { diff --git a/src/components/Hoverable/index.js b/src/components/Hoverable/index.js index f06ed5602744..ef4bc2bf532d 100644 --- a/src/components/Hoverable/index.js +++ b/src/components/Hoverable/index.js @@ -72,10 +72,6 @@ class Hoverable extends Component { if (!this.state.isHovered) { return; } - if (this.props.resetsOnClickOutside) { - this.setIsHovered(false); - return; - } if (this.wrapperView && !this.wrapperView.contains(event.target)) { this.setIsHovered(false); } @@ -93,8 +89,24 @@ class Hoverable extends Component { ref(el); } }, - onMouseEnter: () => this.setIsHovered(true), - onMouseLeave: () => this.setIsHovered(false), + onMouseEnter: (el) => { + this.setIsHovered(true); + + // Call the original onMouseEnter, if any + const {onMouseEnter} = this.props.children; + if (_.isFunction(onMouseEnter)) { + onMouseEnter(el); + } + }, + onMouseLeave: (el) => { + this.setIsHovered(false); + + // Call the original onMouseLeave, if any + const {onMouseLeave} = this.props.children; + if (_.isFunction(onMouseLeave)) { + onMouseLeave(el); + } + }, }); } return ( diff --git a/src/components/Modal/BaseModal.js b/src/components/Modal/BaseModal.js index 323df19c7720..580b8f9abc47 100644 --- a/src/components/Modal/BaseModal.js +++ b/src/components/Modal/BaseModal.js @@ -8,6 +8,7 @@ import * as StyleUtils from '../../styles/StyleUtils'; import themeColors from '../../styles/themes/default'; import {propTypes as modalPropTypes, defaultProps as modalDefaultProps} from './modalPropTypes'; import * as Modal from '../../libs/actions/Modal'; +import DomUtils from '../../libs/DomUtils'; import getModalStyles from '../../styles/getModalStyles'; import variables from '../../styles/variables'; @@ -90,6 +91,7 @@ class BaseModal extends PureComponent { // Note: Escape key on web/desktop will trigger onBackButtonPress callback // eslint-disable-next-line react/jsx-props-no-multi-spaces onBackButtonPress={this.props.onClose} + onModalWillShow={DomUtils.blurActiveElement} onModalShow={() => { if (this.props.shouldSetModalVisibility) { Modal.setModalVisibility(true); diff --git a/src/components/Tooltip/index.js b/src/components/Tooltip/index.js index 8d3a345492ac..890c249e075b 100644 --- a/src/components/Tooltip/index.js +++ b/src/components/Tooltip/index.js @@ -7,6 +7,7 @@ import withWindowDimensions from '../withWindowDimensions'; import {propTypes, defaultProps} from './tooltipPropTypes'; import TooltipSense from './TooltipSense'; import makeCancellablePromise from '../../libs/MakeCancellablePromise'; +import * as Browser from '../../libs/Browser'; class Tooltip extends PureComponent { constructor(props) { @@ -63,7 +64,7 @@ class Tooltip extends PureComponent { getWrapperPosition() { return new Promise(((resolve) => { // Make sure the wrapper is mounted before attempting to measure it. - if (this.wrapperView) { + if (this.wrapperView && _.isFunction(this.wrapperView.measureInWindow)) { this.wrapperView.measureInWindow((x, y, width, height) => resolve({ x, y, width, height, })); @@ -79,6 +80,12 @@ class Tooltip extends PureComponent { * Display the tooltip in an animation. */ showTooltip() { + // On mWeb we do not show Tooltips as there are no way to hide them besides blurring. + // That's due to that fact that on mWeb there is no MouseLeave events. + if (Browser.isMobile()) { + return; + } + if (!this.state.isRendered) { this.setState({isRendered: true}); } @@ -148,6 +155,8 @@ class Tooltip extends PureComponent { this.wrapperView = el} style={this.props.containerStyles} + onBlur={this.hideTooltip} + focusable > {this.props.children} @@ -156,15 +165,24 @@ class Tooltip extends PureComponent { if (this.props.absolute && React.isValidElement(this.props.children)) { child = React.cloneElement(React.Children.only(this.props.children), { ref: (el) => { - // Keep your own reference this.wrapperView = el; // Call the original ref, if any const {ref} = this.props.children; - if (typeof ref === 'function') { + if (_.isFunction(ref)) { ref(el); } }, + onBlur: (el) => { + this.hideTooltip(); + + // Call the original onBlur, if any + const {onBlur} = this.props.children; + if (_.isFunction(onBlur)) { + onBlur(el); + } + }, + focusable: true, }); } return ( @@ -189,7 +207,6 @@ class Tooltip extends PureComponent { containerStyles={this.props.containerStyles} onHoverIn={this.showTooltip} onHoverOut={this.hideTooltip} - resetsOnClickOutside > {child} diff --git a/src/libs/DomUtils/index.js b/src/libs/DomUtils/index.js new file mode 100644 index 000000000000..4e58ea3a08fb --- /dev/null +++ b/src/libs/DomUtils/index.js @@ -0,0 +1,7 @@ +function blurActiveElement() { + document.activeElement.blur(); +} + +export default { + blurActiveElement, +}; diff --git a/src/libs/DomUtils/index.native.js b/src/libs/DomUtils/index.native.js new file mode 100644 index 000000000000..0e796bc40b54 --- /dev/null +++ b/src/libs/DomUtils/index.native.js @@ -0,0 +1,5 @@ +function blurActiveElement() {} + +export default { + blurActiveElement, +}; diff --git a/src/libs/Navigation/NavigationRoot.js b/src/libs/Navigation/NavigationRoot.js index 7dea3a743bf3..0e0958e8a393 100644 --- a/src/libs/Navigation/NavigationRoot.js +++ b/src/libs/Navigation/NavigationRoot.js @@ -8,6 +8,7 @@ import AppNavigator from './AppNavigator'; import FullScreenLoadingIndicator from '../../components/FullscreenLoadingIndicator'; import themeColors from '../../styles/themes/default'; import styles from '../../styles/styles'; +import DomUtils from '../DomUtils'; import Log from '../Log'; // https://reactnavigation.org/docs/themes @@ -45,6 +46,12 @@ function parseAndLogRoute(state) { Log.info('Navigating to route', false, {path: currentPath}); } + // Clicking a button that does navigation will stay active even if it's out of view + // and it's tooltip will stay visible. + // We blur the element manually to fix that (especially for Safari). + // More info: https://github.com/Expensify/App/issues/13146 + DomUtils.blurActiveElement(); + Navigation.setIsNavigationReady(); }