From 9bdfc68aa2667e70b7d0f4214b43b8cd76526d20 Mon Sep 17 00:00:00 2001 From: Will Martin Date: Fri, 6 Feb 2026 10:43:52 -0500 Subject: [PATCH] [CL-1034] tooltip should only show on focus-visible (#18767) --- .../src/tooltip/tooltip.directive.ts | 18 ++++++++++++++++-- libs/components/src/tooltip/tooltip.spec.ts | 13 +++++++++++-- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/libs/components/src/tooltip/tooltip.directive.ts b/libs/components/src/tooltip/tooltip.directive.ts index 419b503c911..a50a4d07e26 100644 --- a/libs/components/src/tooltip/tooltip.directive.ts +++ b/libs/components/src/tooltip/tooltip.directive.ts @@ -28,8 +28,8 @@ export const TOOLTIP_DELAY_MS = 800; host: { "(mouseenter)": "showTooltip()", "(mouseleave)": "hideTooltip()", - "(focus)": "showTooltip()", - "(blur)": "hideTooltip()", + "(focusin)": "onFocusIn($event)", + "(focusout)": "onFocusOut()", "[attr.aria-describedby]": "resolvedDescribedByIds()", }, }) @@ -125,6 +125,20 @@ export class TooltipDirective implements OnInit, OnDestroy { this.destroyTooltip(); }; + /** + * Show tooltip on focus-visible (keyboard navigation) but not on regular focus (mouse click). + */ + protected onFocusIn(event: FocusEvent) { + const target = event.target as HTMLElement; + if (target.matches(":focus-visible")) { + this.showTooltip(); + } + } + + protected onFocusOut() { + this.hideTooltip(); + } + protected readonly resolvedDescribedByIds = computed(() => { if (this.addTooltipToDescribedby()) { if (this.currentDescribedByIds) { diff --git a/libs/components/src/tooltip/tooltip.spec.ts b/libs/components/src/tooltip/tooltip.spec.ts index b3ec710a294..0d73db2d015 100644 --- a/libs/components/src/tooltip/tooltip.spec.ts +++ b/libs/components/src/tooltip/tooltip.spec.ts @@ -103,13 +103,22 @@ describe("TooltipDirective (visibility only)", () => { expect(isVisible()).toBe(true); })); - it("sets isVisible to true on focus", fakeAsync(() => { + it("sets isVisible to true on focus-visible", fakeAsync(() => { const button: HTMLButtonElement = fixture.debugElement.query(By.css("button")).nativeElement; const directive = getDirective(); const isVisible = (directive as unknown as { isVisible: () => boolean }).isVisible; - button.dispatchEvent(new Event("focus")); + // Mock matches to return true for :focus-visible (simulates keyboard navigation) + const originalMatches = button.matches.bind(button); + button.matches = jest.fn((selector: string) => { + if (selector === ":focus-visible") { + return true; + } + return originalMatches(selector); + }); + + button.dispatchEvent(new FocusEvent("focusin", { bubbles: true })); tick(TOOLTIP_DELAY_MS); expect(isVisible()).toBe(true); }));