Skip to content

Commit 1ae1abf

Browse files
authored
Merge pull request #3339 from kev1n77/fmy/bugfix
fix(ui): keep an interactive tooltip preview open while it scrolls
2 parents 3e39661 + 5071a8a commit 1ae1abf

2 files changed

Lines changed: 28 additions & 1 deletion

File tree

‎design-system/packages/ui/src/components/Tooltip/Tooltip.tsx‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,13 @@ export function Tooltip({
263263
// Capture nested, non-bubbling scrolls during both delayed and visible states.
264264
// Dormant tooltips do not need a document-level scroll subscription.
265265
if (view && !scrollCleanupRef.current) {
266-
const dismissOnScroll = () => hideCurrentRef.current();
266+
const dismissOnScroll = (event: Event) => {
267+
// Scrolling the tooltip's own scroll container (a wheel over the
268+
// preview or a scrollbar drag inside it) is not a moving anchor.
269+
const surface = tooltipRef.current;
270+
if (surface && event.target instanceof Node && surface.contains(event.target)) return;
271+
hideCurrentRef.current();
272+
};
267273
view.addEventListener("scroll", dismissOnScroll, { capture: true, passive: true });
268274
view.visualViewport?.addEventListener("scroll", dismissOnScroll, { passive: true });
269275
scrollCleanupRef.current = () => {

‎src/web-ui/src/shared/ui/Tooltip.test.tsx‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,27 @@ describe('shared tooltip visibility', () => {
114114
expect(popup()).toBeNull();
115115
});
116116

117+
it('keeps an interactive preview open while its own scroll container scrolls', () => {
118+
render(
119+
<Tooltip interactive content={<p data-testid="preview">Sub-agent details</p>}>
120+
<button>Details</button>
121+
</Tooltip>,
122+
);
123+
const button = host.querySelector('button')!;
124+
enter(button); advance(); advance(1);
125+
const card = popup()!;
126+
expect(card.textContent).toBe('Sub-agent details');
127+
// The tooltip body is its own scroll container: wheel scrolling or a
128+
// scrollbar drag inside the preview must not dismiss the preview.
129+
act(() => card.dispatchEvent(new Event('scroll')));
130+
expect(popup()).toBe(card);
131+
act(() => card.querySelector('[data-openbitfun-part="content"]')!.dispatchEvent(new Event('scroll')));
132+
expect(popup()).toBe(card);
133+
// A scroll owned by the surface behind the trigger still dismisses it.
134+
act(() => host.dispatchEvent(new Event('scroll')));
135+
expect(popup()).toBeNull();
136+
});
137+
117138
it('dismisses pinned cards on visual viewport scrolling', () => {
118139
const viewport = new EventTarget();
119140
vi.stubGlobal('visualViewport', viewport);

0 commit comments

Comments
 (0)