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

[Tooltip] Tooltips aren't displayed on focus on TextFields #14468

Closed
2 tasks done
codeheroics opened this issue Feb 8, 2019 · 11 comments
Closed
2 tasks done

[Tooltip] Tooltips aren't displayed on focus on TextFields #14468

codeheroics opened this issue Feb 8, 2019 · 11 comments
Labels
bug 🐛 Something doesn't work component: tooltip This is the name of the generic UI component, not the React module! good first issue Great for first contributions. Enable to learn the contribution process.

Comments

@codeheroics
Copy link
Contributor

  • This is not a v0.x issue.
  • I have searched the issues of this repository and believe that this is not a duplicate.

Expected Behavior 🤔

Using a Tooltip component with disableHoverListener around a TextField component should display the tooltip on focus

Current Behavior 😯

No tooltip is displayed

Steps to Reproduce 🕹

Link: https://codesandbox.io/s/ooywx0xlq9

  1. Click on the TextField, nothing is displayed.

Testing anywhere the following code sample is enough to reproduce :

<Tooltip title="TestTooltip" disableHoverListener>
  <TextField label="TestTooltip" />
</Tooltip>

Context 🔦

Display hints for user entering their data.

This used to work, and, after trying a few versions, appears to be broken starting v3.1.1

Your Environment 🌎

Tech Version
Material-UI v3.9.2
React 16.6.3
Browser Firefox 66b, Chromium 71

As always, thanks for the great project 🙂

@codeheroics codeheroics changed the title [Tooltip] Tooltips aren't display on focus on TextFields [Tooltip] Tooltips aren't displayed on focus on TextFields Feb 8, 2019
@oliviertassinari
Copy link
Member

@codeheroics Yeah, it's because we make sure the tooltip child still has the focus before opening it. We can work around the problem with:

--- a/packages/material-ui/src/Tooltip/Tooltip.js
+++ b/packages/material-ui/src/Tooltip/Tooltip.js
@@ -71,6 +71,17 @@ export const styles = theme => ({
   },
 });

+function isDescendant(parent, child) {
+  let node = child;
+  while (node != null) {
+    if (node === parent) {
+      return true;
+    }
+    node = node.parentNode;
+  }
+  return false;
+}
+
 class Tooltip extends React.Component {
   ignoreNonTouchEvents = false;

@@ -125,12 +136,14 @@ class Tooltip extends React.Component {
   };

   handleFocus = event => {
     event.persist();
     // The autoFocus of React might trigger the event before the componentDidMount.
     // We need to account for this eventuality.
     this.focusTimer = setTimeout(() => {
       // We need to make sure the focus hasn't moved since the event was triggered.
-      if (this.childrenRef === document.activeElement) {
+      if (isDescendant(this.childrenRef, document.activeElement)) {
         this.handleEnter(event);
       }
     });

@eps1lon What do you think?

@oliviertassinari oliviertassinari added bug 🐛 Something doesn't work good first issue Great for first contributions. Enable to learn the contribution process. component: tooltip This is the name of the generic UI component, not the React module! labels Feb 8, 2019
@eps1lon
Copy link
Member

eps1lon commented Feb 8, 2019

Looks good to me. Could you explain why we can only trigger handleEnter after cDM? From a naive standpoint it looks like we could just remove this hole logic. And forward handleFocus to handleEnter.

@oliviertassinari
Copy link
Member

The active element on the page can quickly move without triggering the correct focus / blur events. The settimeout is a simple hack to dodge the autofocus problem. I think that you can find the history with a git blame.

@eps1lon
Copy link
Member

eps1lon commented Feb 8, 2019

The active element on the page can quickly move without triggering the correct focus / blur events. The settimeout is a simple hack to dodge the autofocus problem. I think that you can find the history with a git blame.

Can't reproduce the original issue described in #12372 with previous mui versions. Maybe this was a bug in react? Otherwise we should apply this behavior to every component that handles focus.

I'm fairly certain this was caused by facebook/react#7769. Maybe add a warning if we receive focus before ref?

@oliviertassinari
Copy link
Member

oliviertassinari commented Feb 10, 2019

Note for self, isDescendant() can be replaced with the native .contains() API.

Can't reproduce the original issue described in #12372

@eps1lon Try that out:

import React from 'react';
import Input from '@material-ui/core/Input';
import Tooltip from '@material-ui/core/Tooltip';

const Index = () => {
  const [open, setOpen] = React.useState(false);
  return (
    <React.Fragment>
      <button onClick={() => setOpen(true)}>hello</button>
      {open ? (
        <Tooltip title="Title">
          <Input value="value" autoFocus />
        </Tooltip>
      ) : null}
    </React.Fragment>
  );
};

export default Index;

I'm fairly certain this was caused by facebook/react#7769. Maybe add a warning if we receive focus before ref?

Yes, you are right! I propose the following fix:

--- a/packages/material-ui/src/Tooltip/Tooltip.js
+++ b/packages/material-ui/src/Tooltip/Tooltip.js
@@ -125,15 +125,14 @@ class Tooltip extends React.Component {
   };

   handleFocus = event => {
-    event.persist();
+    // Workaround for https://github.com/facebook/react/issues/7769
     // The autoFocus of React might trigger the event before the componentDidMount.
     // We need to account for this eventuality.
-    this.focusTimer = setTimeout(() => {
-      // We need to make sure the focus hasn't moved since the event was triggered.
-      if (this.childrenRef === document.activeElement) {
-        this.handleEnter(event);
-      }
-    });
+    if (!this.childrenRef) {
+      this.childrenRef = event.currentTarget;
+    }
+
+    this.handleEnter(event);

     const childrenProps = this.props.children.props;
     if (childrenProps.onFocus) {
diff --git a/packages/material-ui/src/Tooltip/Tooltip.test.js b/packages/material-ui/src/Tooltip/Tooltip.test.js
index 58a5adcd0..936d679fb 100644
--- a/packages/material-ui/src/Tooltip/Tooltip.test.js
+++ b/packages/material-ui/src/Tooltip/Tooltip.test.js
@@ -1,10 +1,12 @@
 import React from 'react';
 import { assert } from 'chai';
+import PropTypes from 'prop-types';
 import { spy, useFakeTimers } from 'sinon';
 import consoleErrorMock from 'test/utils/consoleErrorMock';
 import { createShallow, createMount, getClasses, unwrap } from '@material-ui/core/test-utils';
 import Popper from '../Popper';
 import Tooltip from './Tooltip';
+import Input from '../Input';
 import createMuiTheme from '../styles/createMuiTheme';

 function persist() {}
@@ -12,7 +14,7 @@ function persist() {}
 const TooltipNaked = unwrap(Tooltip);
 const theme = createMuiTheme();

describe('<Tooltip />', () => {
   let shallow;
   let mount;
   let classes;
@@ -170,6 +172,28 @@ describe('<Tooltip />', () => {
     it('should mount without any issue', () => {
       mount(<Tooltip {...defaultProps} open />);
     });
+
+    it('should handle autoFocus + onFocus forwarding', () => {
+      const AutoFocus = props => (
+        <div>
+          {props.open ? (
+            <Tooltip title="Title">
+              <Input value="value" autoFocus />
+            </Tooltip>
+          ) : null}
+        </div>
+      );
+      AutoFocus.propTypes = {
+        open: PropTypes.bool,
+      };
+
+      const wrapper = mount(<AutoFocus />);
+      wrapper.setProps({ open: true });
+      assert.strictEqual(wrapper.find(Popper).props().open, false);
+      clock.tick(0);
+      wrapper.update();
+      assert.strictEqual(wrapper.find(Popper).props().open, true);
+    });
   });

   describe('prop: delay', () => {

It should solve #14468, #12371 and #12853 with less code.

@oliviertassinari
Copy link
Member

@eps1lon What do you think about it? @codeheroics Do you want to submit a pull-request? :)

@eps1lon
Copy link
Member

eps1lon commented Feb 10, 2019

Your example is working fine for me. Not sure that this issue can be reproduced deterministically.

Your fix looks fine to me though. I think we have something similar in place somewhere else. It is at least more predictable than using 0 delay timeouts.

@oliviertassinari
Copy link
Member

oliviertassinari commented Feb 10, 2019

@eps1lon Here you are: https://3ml4nm811.codesandbox.io/.

@eps1lon
Copy link
Member

eps1lon commented Feb 10, 2019

Interesting. 1.4.1 has issues, 1.4.0 not. If I open it in its own window and incrementally go from 1.4.0 to 1.4.2 I have no error. It will not focus until I switch tab though.

All of that is saying to me that either codesandbox is not equipped for this kind of debugging or we have triggered an edge case. Nevertheless the event.currentTarget fix may work. Still scary use the DOM imperatively before refs.

@codeheroics
Copy link
Contributor Author

@oliviertassinari Sure! I'll test and send it right away, though I feel kind of guilty since you've done all the work and written all the code already 😅

@oliviertassinari
Copy link
Member

@codeheroics Peer review is important :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug 🐛 Something doesn't work component: tooltip This is the name of the generic UI component, not the React module! good first issue Great for first contributions. Enable to learn the contribution process.
Projects
None yet
Development

No branches or pull requests

3 participants