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] Long press select text on iOS #23466

Merged
merged 14 commits into from
Nov 12, 2020
24 changes: 20 additions & 4 deletions packages/material-ui/src/Tooltip/Tooltip.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,14 @@ export const styles = (theme) => ({
padding: '4px 8px',
fontSize: theme.typography.pxToRem(11),
maxWidth: 300,
margin: 2,
wordWrap: 'break-word',
fontWeight: theme.typography.fontWeightMedium,
},
/* Styles applied to the tooltip (label wrapper) element if `arrow={true}`. */
tooltipArrow: {
position: 'relative',
margin: '0',
margin: 0,
},
/* Styles applied to the arrow element. */
arrow: {
Expand Down Expand Up @@ -195,6 +196,7 @@ const Tooltip = React.forwardRef(function Tooltip(props, ref) {
const [childNode, setChildNode] = React.useState();
const [arrowRef, setArrowRef] = React.useState(null);
const ignoreNonTouchEvents = React.useRef(false);
const prevUserSelect = React.useRef();

const disableInteractive = disableInteractiveProp || followCursor;

Expand Down Expand Up @@ -240,14 +242,22 @@ const Tooltip = React.forwardRef(function Tooltip(props, ref) {

const id = useId(idProp);

const stopTouchInteraction = useEventCallback(() => {
if (prevUserSelect.current !== undefined) {
document.body.style.WebkitUserSelect = prevUserSelect.current;
prevUserSelect.current = undefined;
}
clearTimeout(touchTimer.current);
});

React.useEffect(() => {
return () => {
clearTimeout(closeTimer.current);
clearTimeout(enterTimer.current);
clearTimeout(leaveTimer.current);
clearTimeout(touchTimer.current);
stopTouchInteraction();
};
}, []);
}, [stopTouchInteraction]);

const handleOpen = (event) => {
clearTimeout(hystersisTimer);
Expand Down Expand Up @@ -369,9 +379,15 @@ const Tooltip = React.forwardRef(function Tooltip(props, ref) {
detectTouchStart(event);
clearTimeout(leaveTimer.current);
clearTimeout(closeTimer.current);
clearTimeout(touchTimer.current);
stopTouchInteraction();
event.persist();

prevUserSelect.current = document.body.style.WebkitUserSelect;
// Prevent iOS text selection on long-tap.
document.body.style.WebkitUserSelect = 'none';

touchTimer.current = setTimeout(() => {
oliviertassinari marked this conversation as resolved.
Show resolved Hide resolved
document.body.style.WebkitUserSelect = prevUserSelect.current;
handleEnter(event);
}, enterTouchDelay);
};
Expand Down
38 changes: 35 additions & 3 deletions packages/material-ui/src/Tooltip/Tooltip.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,12 @@ describe('<Tooltip />', () => {
});

describe('touch screen', () => {
const isJSDOM = navigator.userAgent === 'node.js';

afterEach(() => {
document.body.style.WebkitUserSelect = undefined;
});

it('should not respond to quick events', () => {
const { getByRole, queryByRole } = render(
<Tooltip title="Hello World">
Expand All @@ -375,14 +381,14 @@ describe('<Tooltip />', () => {
title="Hello World"
TransitionProps={{ timeout: transitionTimeout }}
>
<button id="testChild" type="submit">
Hello World
</button>
<button type="submit">Hello World</button>
</Tooltip>,
);
act(() => {
fireEvent.touchStart(getByRole('button'));
expect(document.body.style.WebkitUserSelect).to.equal('none');
clock.tick(enterTouchDelay + enterDelay);
expect(document.body.style.WebkitUserSelect).to.equal(isJSDOM ? undefined : '');
});

expect(getByRole('tooltip')).toBeVisible();
Expand All @@ -399,6 +405,32 @@ describe('<Tooltip />', () => {
expect(queryByRole('tooltip')).to.equal(null);
});

it('should restore the user-select state correctly', () => {
const enterTouchDelay = 700;
const enterDelay = 100;
const leaveTouchDelay = 1500;
const transitionTimeout = 10;
const { getByRole } = render(
<Tooltip
enterTouchDelay={enterTouchDelay}
enterDelay={enterDelay}
leaveTouchDelay={leaveTouchDelay}
title="Hello World"
TransitionProps={{ timeout: transitionTimeout }}
>
<button type="submit">Hello World</button>
</Tooltip>,
);

document.body.style.WebkitUserSelect = 'revert';
act(() => {
fireEvent.touchStart(getByRole('button'));
expect(document.body.style.WebkitUserSelect).to.equal('none');
clock.tick(enterTouchDelay + enterDelay);
expect(document.body.style.WebkitUserSelect.toLowerCase()).to.equal('revert');
});
});

it('should not open if disableTouchListener', () => {
const { getByRole, queryByRole } = render(
<Tooltip title="Hello World" disableTouchListener>
Expand Down