Skip to content

Commit 496709a

Browse files
Copilottudorpopams
andauthored
fix(react-tooltip): dismiss tooltip when document becomes hidden (#36023)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: tudorpopams <97875118+tudorpopams@users.noreply.github.com>
1 parent 2b6113f commit 496709a

3 files changed

Lines changed: 42 additions & 1 deletion

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "patch",
3+
"comment": "fix: dismiss Tooltip when the document becomes hidden (e.g. tab backgrounded or app switched on mobile)",
4+
"packageName": "@fluentui/react-tooltip",
5+
"email": "198982749+Copilot@users.noreply.github.com",
6+
"dependentChangeType": "patch"
7+
}

packages/react-components/react-tooltip/library/src/components/Tooltip/Tooltip.test.tsx

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { Tooltip } from './Tooltip';
33
import { isConformant } from '../../testing/isConformant';
44
import type { IsConformantOptions } from '@fluentui/react-conformance';
55
import type { RenderResult } from '@testing-library/react';
6-
import { render } from '@testing-library/react';
6+
import { act, fireEvent, render } from '@testing-library/react';
77
import { resetIdsForTests } from '@fluentui/react-utilities';
88

99
// testing-library's queryByRole function doesn't look inside portals
@@ -148,4 +148,26 @@ describe('Tooltip', () => {
148148
expect(target.getAttribute('aria-description')).toBe(null);
149149
expect(target.getAttribute('aria-describedby')).toBe('test-describedby');
150150
});
151+
152+
it('hides the tooltip when the document becomes hidden (e.g. tab backgrounded on mobile)', () => {
153+
const onVisibleChange = jest.fn();
154+
const result = render(
155+
<Tooltip content="Tooltip content" relationship="label" visible onVisibleChange={onVisibleChange}>
156+
<button />
157+
</Tooltip>,
158+
);
159+
160+
// Tooltip starts visible
161+
expect(queryByRoleTooltip(result)).not.toBeNull();
162+
163+
// Simulate the tab being backgrounded / app switched on mobile
164+
const visibilityStateSpy = jest.spyOn(document, 'visibilityState', 'get').mockReturnValue('hidden');
165+
act(() => {
166+
fireEvent(document, new Event('visibilitychange'));
167+
});
168+
169+
expect(onVisibleChange).toHaveBeenCalledWith(undefined, expect.objectContaining({ visible: false }));
170+
171+
visibilityStateSpy.mockRestore();
172+
});
151173
});

packages/react-components/react-tooltip/library/src/components/Tooltip/useTooltipBase.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,12 +149,24 @@ export const useTooltipBase_unstable = (props: TooltipBaseProps): TooltipBaseSta
149149
capture: true,
150150
});
151151

152+
// Dismiss the tooltip when the document becomes hidden (e.g. tab backgrounded,
153+
// app switched on mobile). The original trigger (hover/tap/focus) is no longer
154+
// active in this case, so persisting the tooltip is a stale UI state.
155+
const onDocumentVisibilityChange = () => {
156+
if (targetDocument?.visibilityState === 'hidden') {
157+
thisTooltip.hide();
158+
}
159+
};
160+
161+
targetDocument?.addEventListener('visibilitychange', onDocumentVisibilityChange);
162+
152163
return () => {
153164
if (context.visibleTooltip === thisTooltip) {
154165
context.visibleTooltip = undefined;
155166
}
156167

157168
targetDocument?.removeEventListener('keydown', onDocumentKeyDown, { capture: true });
169+
targetDocument?.removeEventListener('visibilitychange', onDocumentVisibilityChange);
158170
};
159171
}
160172
}, [context, targetDocument, visible, setVisible]);

0 commit comments

Comments
 (0)