Skip to content

Commit 0edcfb5

Browse files
committed
fix(tooltip): catch display: none triggers in the legacy visibility fallback
1 parent ab08b3e commit 0edcfb5

2 files changed

Lines changed: 31 additions & 4 deletions

File tree

packages/emcn/src/components/tooltip/tooltip.test.tsx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,27 @@ describe('floating tooltip trigger visibility watcher', () => {
9191
expect(tooltipElement()).toBeNull()
9292
})
9393

94+
it('dismisses the tooltip when the trigger is display: none without a pointer event', () => {
95+
mountTooltip()
96+
hover(trigger())
97+
expect(tooltipElement()).not.toBeNull()
98+
99+
trigger().style.display = 'none'
100+
runWatcherTick()
101+
expect(tooltipElement()).toBeNull()
102+
})
103+
104+
it('dismisses the tooltip when an ancestor becomes display: none', () => {
105+
mountTooltip()
106+
hover(trigger())
107+
expect(tooltipElement()).not.toBeNull()
108+
109+
if (!container) throw new Error('Container did not mount')
110+
container.style.display = 'none'
111+
runWatcherTick()
112+
expect(tooltipElement()).toBeNull()
113+
})
114+
94115
it('dismisses the tooltip when the trigger unmounts from under a static pointer', () => {
95116
mountTooltip()
96117
hover(trigger())

packages/emcn/src/components/tooltip/tooltip.tsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -267,16 +267,22 @@ export function isTextClipped(element: HTMLElement): boolean {
267267

268268
/**
269269
* Whether a tooltip trigger is still visibly rendered. `checkVisibility` (where available) catches
270-
* `display: none` and an inherited `visibility: hidden` anywhere up the tree; the fallback for
271-
* engines without it (Safari < 17.4, jsdom) reads the element's computed `visibility`, which still
272-
* inherits from hidden ancestors.
270+
* `display: none` and an inherited `visibility: hidden` anywhere up the tree. The fallback for
271+
* engines without it (Safari < 17.4, jsdom) reads the element's computed `visibility` — which
272+
* inherits from hidden ancestors — and then walks the ancestor chain for `display: none`, which
273+
* does not inherit. Computed styles, not layout (`getClientRects`/`offsetParent`), on purpose:
274+
* jsdom does no layout, so a layout-based check would misread every trigger as hidden in tests.
273275
*/
274276
function isVisiblyRendered(element: HTMLElement): boolean {
275277
if (!element.isConnected) return false
276278
if (typeof element.checkVisibility === 'function') {
277279
return element.checkVisibility({ checkVisibilityCSS: true, visibilityProperty: true })
278280
}
279-
return getComputedStyle(element).visibility !== 'hidden'
281+
if (getComputedStyle(element).visibility === 'hidden') return false
282+
for (let node: HTMLElement | null = element; node; node = node.parentElement) {
283+
if (getComputedStyle(node).display === 'none') return false
284+
}
285+
return true
280286
}
281287

282288
/** Clamps `value` to the inclusive `[min, max]` range. */

0 commit comments

Comments
 (0)