diff --git a/packages/material-ui/src/Tooltip/Tooltip.js b/packages/material-ui/src/Tooltip/Tooltip.js index 92ca464604d828..e461a6a68a083f 100644 --- a/packages/material-ui/src/Tooltip/Tooltip.js +++ b/packages/material-ui/src/Tooltip/Tooltip.js @@ -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: { @@ -240,14 +241,23 @@ const Tooltip = React.forwardRef(function Tooltip(props, ref) { const id = useId(idProp); + const prevUserSelect = React.useRef(); + const stopTouchInteraction = React.useCallback(() => { + 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); @@ -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(() => { + document.body.style.WebkitUserSelect = prevUserSelect.current; handleEnter(event); }, enterTouchDelay); }; diff --git a/packages/material-ui/src/Tooltip/Tooltip.test.js b/packages/material-ui/src/Tooltip/Tooltip.test.js index b757cefab1d17b..83a6c9431c89cd 100644 --- a/packages/material-ui/src/Tooltip/Tooltip.test.js +++ b/packages/material-ui/src/Tooltip/Tooltip.test.js @@ -410,9 +410,7 @@ describe('', () => { title="Hello World" TransitionProps={{ timeout: transitionTimeout }} > - + , ); act(() => { @@ -1083,4 +1081,70 @@ describe('', () => { } }); }); + + describe('user-select state', () => { + let prevWebkitUserSelect; + beforeEach(() => { + prevWebkitUserSelect = document.body.style.WebkitUserSelect; + }); + + afterEach(() => { + document.body.style.WebkitUserSelect = prevWebkitUserSelect; + }); + + it('prevents text-selection during touch-longpress', () => { + const enterTouchDelay = 700; + const enterDelay = 100; + const leaveTouchDelay = 1500; + const transitionTimeout = 10; + const { getByRole } = render( + + + , + ); + document.body.style.WebkitUserSelect = 'revert'; + + fireEvent.touchStart(getByRole('button')); + + expect(document.body.style.WebkitUserSelect).to.equal('none'); + + act(() => { + clock.tick(enterTouchDelay + enterDelay); + }); + expect(document.body.style.WebkitUserSelect.toLowerCase()).to.equal('revert'); + }); + + it('restores user-select when unmounted during longpress', () => { + const enterTouchDelay = 700; + const enterDelay = 100; + const leaveTouchDelay = 1500; + const transitionTimeout = 10; + const { unmount, getByRole } = render( + + + , + ); + + document.body.style.WebkitUserSelect = 'revert'; + // Let updates flush before unmounting + act(() => { + fireEvent.touchStart(getByRole('button')); + }); + unmount(); + + expect(document.body.style.WebkitUserSelect.toLowerCase()).to.equal('revert'); + }); + }); });