Skip to content
This repository was archived by the owner on Mar 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 4 additions & 8 deletions packages/core/src/PopOver/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -136,14 +136,10 @@ export const PopOver: FC<PopOverProps> = ({
popOverContainer,
])

useOnScrollEffect(
anchorEl,
onScroll !== undefined
? onScroll
: () => {
/** */
}
)
useOnScrollEffect(anchorEl, () => {
onScroll?.()
position()
})

// Used when resizing the parent anchorEl
useOnResizeParentEffect(anchorEl, position)
Expand Down
18 changes: 17 additions & 1 deletion packages/core/src/Tooltip/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { Typography, TypographyProps } from '../Typography'
import { PopOver, PopOverProps } from '../PopOver'
import { shape, spacing, componentSize } from '../designparams'
import { font } from '../theme'
import { useTouchScrollDistance } from './utils'

/**
* Tooltip
Expand Down Expand Up @@ -252,7 +253,6 @@ export const Tooltip: FC<TooltipProps | ExpandedTooltipProps> = ({
(props.variant === 'expanded' ? props.placement : undefined) ?? 'up-down'
const child = Children.only(children) as ReactElement
const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null)

// State for click
const [visibleByClick, showByClick] = useState(false)
// Delayed state for pointer
Expand All @@ -265,6 +265,8 @@ export const Tooltip: FC<TooltipProps | ExpandedTooltipProps> = ({
// If tooltip should be shown
const visible = visibleByClick || debouncedVisible

const touchScrollDistance = useTouchScrollDistance()

const toggle = useCallback(
(event: PointerEvent) => {
// When using touch instead of mouse, we have to toggle the tooltip
Expand All @@ -278,6 +280,20 @@ export const Tooltip: FC<TooltipProps | ExpandedTooltipProps> = ({
[showByClick]
)

/**
* If the delta for any axis is larger than 150 pixels,
* remove the tooltip from the screen.
*/
useLayoutEffect(() => {
if (!visible) {
return
}
const { x, y } = touchScrollDistance
if (Math.max(Math.abs(x), Math.abs(y)) > 150) {
showByClick(false)
}
}, [touchScrollDistance])

useEffect(() => {
const delayVisible = () => setDebouncedVisible(visibleDelayed)
const delayed = setTimeout(delayVisible, TOOLTIP_DELAY_MS)
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/Tooltip/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './useTouchScrollDistance'
70 changes: 70 additions & 0 deletions packages/core/src/Tooltip/utils/useTouchScrollDistance.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { useState, useEffect, useLayoutEffect } from 'react'

export const useTouchScrollDistance = () => {
const [origin, setOrigin] = useState<TouchList | null>(null)
const [touches, setTouches] = useState<TouchList | null>(null)
/**
* The distance between touch origin and touch current for both
* x-axis and y-axis
*/
const [touchScrollDistance, setTouchScrollDistance] = useState({ x: 0, y: 0 })

useEffect(() => {
const touchStartHandler = (event: TouchEvent) => {
if (origin === null) {
setOrigin(event.touches)
}
}

const touchMoveHandler = (event: TouchEvent) =>
setTouches(event.changedTouches)

const touchEndHandler = (event: TouchEvent) => {
if (event.touches.length === 0) {
setOrigin(null)
setTouches(null)
setTouchScrollDistance({ x: 0, y: 0 })
}
}

const touchCancelHandler = () => {
setOrigin(null)
setTouches(null)
setTouchScrollDistance({ x: 0, y: 0 })
}

document.addEventListener('touchstart', touchStartHandler)
document.addEventListener('touchmove', touchMoveHandler)
document.addEventListener('touchend', touchEndHandler)
document.addEventListener('touchcancel', touchCancelHandler)

return () => {
document.removeEventListener('touchstart', touchStartHandler)
document.removeEventListener('touchmove', touchMoveHandler)
document.removeEventListener('touchend', touchEndHandler)
document.removeEventListener('touchcancel', touchCancelHandler)
}
}, [origin])

/**
* Calculates the distance in pixels between the origin of
* a touch event and position updates to that touch event.
*/
useLayoutEffect(() => {
if (origin === null || touches === null) {
return
}

// User is not scrolling
if (touches.length > 1) {
return
}

const deltaX = touches[0].clientX - origin[0].clientX
const deltaY = touches[0].clientY - origin[0].clientY

setTouchScrollDistance({ x: deltaX, y: deltaY })
}, [origin, touches])

return touchScrollDistance
}
49 changes: 49 additions & 0 deletions packages/ui-tests/src/coreComponents/Tooltip.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,52 @@ context('Tooltip', () => {
})
})
})

context('Tooltip mobile device', () => {
before(() => {
cy.viewport('ipad-2')
cy.visit('http://localhost:9009/#/components/tooltip')
})

const data = {
textDataCy: 'expandedTooltipBottomLeftRightText',
tooltipDataCy: 'expandedTooltipBottomLeftRight',
}

it(`Tooltip ${data.tooltipDataCy} should appear, and should hide`, () => {
// Touch to show tooltip
cy.get(`[data-cy=${data.textDataCy}]`).should('exist')
cy.get(`[data-cy=${data.textDataCy}]`).trigger('pointerdown')
cy.get(`[data-cy=${data.tooltipDataCy}]`)
.should('exist')
.should('be.visible')

// Touch to hide tooltip
cy.get(`[data-cy=${data.textDataCy}]`).trigger('pointerdown')
cy.get(`[data-cy=${data.tooltipDataCy}]`).should('not.exist')
})

it(`Tooltip ${data.tooltipDataCy} should hide when client touch move more than 150 pixels`, () => {
// Touch to show tooltip
cy.get(`[data-cy=${data.textDataCy}]`).trigger('pointerdown')
cy.get(`[data-cy=${data.tooltipDataCy}]`)
.should('exist')
.should('be.visible')

// Touch move 151 pixels and hide tooltip
cy.get(`[data-cy=${data.textDataCy}]`)
.parent()
.trigger('touchstart', {
touches: [{ clientX: 0, clientY: 0, identifier: 0 }],
})

cy.get(`[data-cy=${data.textDataCy}]`)
.parent()
.trigger('touchmove', {
changedTouches: [{ clientX: 151, clientY: 151, identifier: 0 }],
})

// Tooltip is not shown
cy.get(`[data-cy=${data.tooltipDataCy}]`).should('not.exist')
})
})