Skip to content

Commit de09cbe

Browse files
committed
fix(CPopover, CTooltip): prevent removing the popper style before the transition ends
1 parent 4e7daab commit de09cbe

File tree

6 files changed

+93
-7
lines changed

6 files changed

+93
-7
lines changed

packages/coreui-react/src/components/popover/CPopover.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { Transition } from 'react-transition-group'
77
import { usePopper } from '../../hooks'
88
import { fallbackPlacementsPropType, triggerPropType } from '../../props'
99
import type { Placements, Triggers } from '../../types'
10-
import { getRTLPlacement } from '../../utils'
10+
import { getRTLPlacement, getTransitionDurationFromElement } from '../../utils'
1111

1212
export interface CPopoverProps extends Omit<HTMLAttributes<HTMLDivElement>, 'title' | 'content'> {
1313
/**
@@ -164,7 +164,7 @@ export const CPopover: FC<CPopoverProps> = ({
164164
onExit={onHide}
165165
timeout={{
166166
enter: 0,
167-
exit: 200,
167+
exit: popoverRef.current ? getTransitionDurationFromElement(popoverRef.current) + 50 : 200,
168168
}}
169169
unmountOnExit
170170
>

packages/coreui-react/src/components/tooltip/CTooltip.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { Transition } from 'react-transition-group'
77
import { usePopper } from '../../hooks'
88
import { fallbackPlacementsPropType, triggerPropType } from '../../props'
99
import type { Placements, Triggers } from '../../types'
10-
import { getRTLPlacement } from '../../utils'
10+
import { getRTLPlacement, getTransitionDurationFromElement } from '../../utils'
1111

1212
export interface CTooltipProps extends Omit<HTMLAttributes<HTMLDivElement>, 'content'> {
1313
/**
@@ -158,7 +158,7 @@ export const CTooltip: FC<CTooltipProps> = ({
158158
onExit={onHide}
159159
timeout={{
160160
enter: 0,
161-
exit: 200,
161+
exit: tooltipRef.current ? getTransitionDurationFromElement(tooltipRef.current) + 50 : 200,
162162
}}
163163
unmountOnExit
164164
>

packages/coreui-react/src/hooks/usePopper.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { useRef } from 'react'
22
import { createPopper } from '@popperjs/core'
33
import type { Instance, Options } from '@popperjs/core'
44

5+
import { executeAfterTransition } from '../utils'
6+
57
interface UsePopperOutput {
68
popper: Instance | undefined
79
initPopper: (reference: HTMLElement, popper: HTMLElement, options: Partial<Options>) => void
@@ -10,14 +12,20 @@ interface UsePopperOutput {
1012

1113
export const usePopper = (): UsePopperOutput => {
1214
const _popper = useRef<Instance>()
15+
const el = useRef<HTMLElement>()
1316

1417
const initPopper = (reference: HTMLElement, popper: HTMLElement, options: Partial<Options>) => {
1518
_popper.current = createPopper(reference, popper, options)
19+
el.current = popper
1620
}
1721

1822
const destroyPopper = () => {
19-
if (_popper.current) {
20-
_popper.current.destroy()
23+
const popperInstance = _popper.current
24+
25+
if (popperInstance && el.current) {
26+
executeAfterTransition(() => {
27+
popperInstance.destroy()
28+
}, el.current)
2129
}
2230

2331
_popper.current = undefined
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import getTransitionDurationFromElement from './getTransitionDurationFromElement'
2+
3+
const execute = (callback: () => void) => {
4+
if (typeof callback === 'function') {
5+
callback()
6+
}
7+
}
8+
9+
const triggerTransitionEnd = (element: HTMLElement) => {
10+
element.dispatchEvent(new Event('transitionend'))
11+
}
12+
13+
const executeAfterTransition = (
14+
callback: () => void,
15+
transitionElement: HTMLElement,
16+
waitForTransition = true,
17+
) => {
18+
if (!waitForTransition) {
19+
execute(callback)
20+
return
21+
}
22+
23+
const durationPadding = 5
24+
const emulatedDuration = getTransitionDurationFromElement(transitionElement) + durationPadding
25+
26+
let called = false
27+
28+
const handler = ({ target }: { target: any }) => {
29+
if (target !== transitionElement) {
30+
return
31+
}
32+
33+
called = true
34+
transitionElement.removeEventListener('transitionend', handler)
35+
execute(callback)
36+
}
37+
38+
transitionElement.addEventListener('transitionend', handler)
39+
setTimeout(() => {
40+
if (!called) {
41+
triggerTransitionEnd(transitionElement)
42+
}
43+
}, emulatedDuration)
44+
}
45+
46+
export default executeAfterTransition
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
const getTransitionDurationFromElement = (element: HTMLElement) => {
2+
if (!element) {
3+
return 0
4+
}
5+
6+
// Get transition-duration of the element
7+
let { transitionDuration, transitionDelay } = window.getComputedStyle(element)
8+
9+
const floatTransitionDuration = Number.parseFloat(transitionDuration)
10+
const floatTransitionDelay = Number.parseFloat(transitionDelay)
11+
12+
// Return 0 if element or transition duration is not found
13+
if (!floatTransitionDuration && !floatTransitionDelay) {
14+
return 0
15+
}
16+
17+
// If multiple durations are defined, take the first
18+
transitionDuration = transitionDuration.split(',')[0]
19+
transitionDelay = transitionDelay.split(',')[0]
20+
21+
return (Number.parseFloat(transitionDuration) + Number.parseFloat(transitionDelay)) * 1000
22+
}
23+
24+
export default getTransitionDurationFromElement
Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
1+
import executeAfterTransition from './executeAfterTransition'
12
import getRTLPlacement from './getRTLPlacement'
3+
import getTransitionDurationFromElement from './getTransitionDurationFromElement'
24
import isInViewport from './isInViewport'
35
import isRTL from './isRTL'
46

5-
export { getRTLPlacement, isInViewport, isRTL }
7+
export {
8+
executeAfterTransition,
9+
getRTLPlacement,
10+
getTransitionDurationFromElement,
11+
isInViewport,
12+
isRTL,
13+
}

0 commit comments

Comments
 (0)