Repository: mui/material-ui (TypeScript) — Feature

Issue:
[Tooltip] Apply enterDelay on Component base
Tooltips shouldn't open immediately when another tooltip is already opened. Each component instance should respect its own delays. 

Though there is no official description about the behavior, material uses the proposed behavior for its tooltips: see
https://material.io/components/tooltips/#specs

<!-- Checked checkbox should look like this: [x] -->

- [x] The issue is present in the latest release.
- [x] I have searched the [issues](https://github.com/mui-org/material-ui/issues) of this repository and believe that this is not a duplicate.

## Current Behavior 😯

Currently when one Tooltip is already opened and we hover above another one, the tooltip opens directly without respecting the defined enterDelay. 

## Expected Behavior 🤔

Tooltips should respect their own enter delays.

## Steps to Reproduce 🕹

https://codesandbox.io/s/material-demo-3fxbm?fontsize=14&hidenavigation=1&theme=dark

Steps:

1. Hover over one item and wait till the Tooltip opens
2. Hover over the other items

## Context 🔦

I'm trying to render a DataGrid with a sort of Link Column, which should render a tooltip for each cell displaying the actual href. 

## Your Environment 🌎

| Tech        | Version |
| ----------- | ------- |
| Material-UI | v4.9.3  |
| React       |     16.12.0  |
| Browser     |     chrome 80   |
| TypeScript  |     3.7.5    |




Discussion:

Resolving patch:
diff --git a/docs/pages/api/tooltip.md b/docs/pages/api/tooltip.md
--- a/docs/pages/api/tooltip.md
+++ b/docs/pages/api/tooltip.md
@@ -30,7 +30,8 @@ You can learn more about the difference by [reading this guide](/guides/minimizi
 | <span class="prop-name">disableFocusListener</span> | <span class="prop-type">bool</span> | <span class="prop-default">false</span> | Do not respond to focus events. |
 | <span class="prop-name">disableHoverListener</span> | <span class="prop-type">bool</span> | <span class="prop-default">false</span> | Do not respond to hover events. |
 | <span class="prop-name">disableTouchListener</span> | <span class="prop-type">bool</span> | <span class="prop-default">false</span> | Do not respond to long press touch events. |
-| <span class="prop-name">enterDelay</span> | <span class="prop-type">number</span> | <span class="prop-default">0</span> | The number of milliseconds to wait before showing the tooltip. This prop won't impact the enter touch delay (`enterTouchDelay`). |
+| <span class="prop-name">enterDelay</span> | <span class="prop-type">number</span> | <span class="prop-default">200</span> | The number of milliseconds to wait before showing the tooltip. This prop won't impact the enter touch delay (`enterTouchDelay`). |
+| <span class="prop-name">enterNextDelay</span> | <span class="prop-type">number</span> | <span class="prop-default">0</span> | The number of milliseconds to wait before showing the tooltip when one was already recently opened. |
 | <span class="prop-name">enterTouchDelay</span> | <span class="prop-type">number</span> | <span class="prop-default">700</span> | The number of milliseconds a user must touch the element before showing the tooltip. |
 | <span class="prop-name">id</span> | <span class="prop-type">string</span> |  | This prop is used to help implement the accessibility logic. If you don't provide this prop. It falls back to a randomly generated id. |
 | <span class="prop-name">interactive</span> | <span class="prop-type">bool</span> | <span class="prop-default">false</span> | Makes a tooltip interactive, i.e. will not close when the user hovers over the tooltip before the `leaveDelay` is expired. |
diff --git a/packages/material-ui/src/Tooltip/Tooltip.d.ts b/packages/material-ui/src/Tooltip/Tooltip.d.ts
--- a/packages/material-ui/src/Tooltip/Tooltip.d.ts
+++ b/packages/material-ui/src/Tooltip/Tooltip.d.ts
@@ -11,6 +11,7 @@ export interface TooltipProps
   disableHoverListener?: boolean;
   disableTouchListener?: boolean;
   enterDelay?: number;
+  enterNextDelay?: number;
   enterTouchDelay?: number;
   id?: string;
   interactive?: boolean;
diff --git a/packages/material-ui/src/Tooltip/Tooltip.js b/packages/material-ui/src/Tooltip/Tooltip.js
--- a/packages/material-ui/src/Tooltip/Tooltip.js
+++ b/packages/material-ui/src/Tooltip/Tooltip.js
@@ -183,7 +183,8 @@ const Tooltip = React.forwardRef(function Tooltip(props, ref) {
     disableFocusListener = false,
     disableHoverListener = false,
     disableTouchListener = false,
-    enterDelay = 0,
+    enterDelay = 200,
+    enterNextDelay = 0,
     enterTouchDelay = 700,
     id: idProp,
     interactive = false,
@@ -303,11 +304,14 @@ const Tooltip = React.forwardRef(function Tooltip(props, ref) {
 
     clearTimeout(enterTimer.current);
     clearTimeout(leaveTimer.current);
-    if (enterDelay && !hystersisOpen) {
+    if (enterDelay || (hystersisOpen && enterNextDelay)) {
       event.persist();
-      enterTimer.current = setTimeout(() => {
-        handleOpen(event);
-      }, enterDelay);
+      enterTimer.current = setTimeout(
+        () => {
+          handleOpen(event);
+        },
+        hystersisOpen ? enterNextDelay : enterDelay,
+      );
     } else {
       handleOpen(event);
     }
@@ -345,9 +349,7 @@ const Tooltip = React.forwardRef(function Tooltip(props, ref) {
     clearTimeout(hystersisTimer);
     hystersisTimer = setTimeout(() => {
       hystersisOpen = false;
-    }, 500);
-    // Use 500 ms per https://github.com/reach/reach-ui/blob/3b5319027d763a3082880be887d7a29aee7d3afc/packages/tooltip/src/index.js#L214
-
+    }, 800 + leaveDelay);
     setOpenState(false);
 
     if (onClose) {
@@ -569,6 +571,10 @@ Tooltip.propTypes = {
    * This prop won't impact the enter touch delay (`enterTouchDelay`).
    */
   enterDelay: PropTypes.number,
+  /**
+   * The number of milliseconds to wait before showing the tooltip when one was already recently opened.
+   */
+  enterNextDelay: PropTypes.number,
   /**
    * The number of milliseconds a user must touch the element before showing the tooltip.
    */


Test patch:
diff --git a/packages/material-ui/src/Tooltip/Tooltip.test.js b/packages/material-ui/src/Tooltip/Tooltip.test.js
--- a/packages/material-ui/src/Tooltip/Tooltip.test.js
+++ b/packages/material-ui/src/Tooltip/Tooltip.test.js
@@ -36,6 +36,7 @@ describe('<Tooltip />', () => {
   const render = createClientRender({ strict: false });
   let clock;
   const defaultProps = {
+    enterDelay: 0,
     children: (
       <button id="testChild" type="submit">
         Hello World
@@ -186,7 +187,7 @@ describe('<Tooltip />', () => {
       const AutoFocus = props => (
         <div>
           {props.open ? (
-            <Tooltip title="Title">
+            <Tooltip {...defaultProps} title="Title">
               <Input value="value" autoFocus />
             </Tooltip>
           ) : null}
@@ -222,6 +223,7 @@ describe('<Tooltip />', () => {
         <Tooltip
           {...defaultProps}
           enterDelay={111}
+          enterNextDelay={30}
           leaveDelay={5}
           TransitionProps={{ timeout: 6 }}
         />,
@@ -237,7 +239,9 @@ describe('<Tooltip />', () => {
       expect(document.body.querySelectorAll('[role="tooltip"]').length).to.equal(0);
 
       focusVisible(children);
-      // Bypass `enterDelay` wait, instant display.
+      // Bypass `enterDelay` wait, use `enterNextDelay`.
+      expect(document.body.querySelectorAll('[role="tooltip"]').length).to.equal(0);
+      clock.tick(30);
       expect(document.body.querySelectorAll('[role="tooltip"]').length).to.equal(1);
     });
 
@@ -351,7 +355,7 @@ describe('<Tooltip />', () => {
   describe('prop: interactive', () => {
     it('should keep the overlay open if the popper element is hovered', () => {
       const wrapper = mount(
-        <Tooltip title="Hello World" interactive leaveDelay={111}>
+        <Tooltip {...defaultProps} title="Hello World" interactive leaveDelay={111}>
           <button id="testChild" type="submit">
             Hello World
           </button>