From 4a238f42a3289922653450b53376f32f06ee418e Mon Sep 17 00:00:00 2001 From: Maximilian Koeller Date: Thu, 12 Feb 2026 22:05:43 +0100 Subject: [PATCH] refactor(tooltip): only show tooltip on focus if `:focus-visible` matches Currently, the tooltip is always shown, if the attached element receives focus. This behavior is not desired, as this previously also showed the tooltip if Element was programmatically focussing an element. For instance in the navbar after closing a flyout menu. With the new logic, a tooltip will only be shown on focus if the `:focus-visible` selector matches and thus the site is operated by a keyboard. This commit is labeled as a refactor, as the new behavior was introduced within the same release. --- .../element-examples/navbar-vertical.spec.ts | 26 +++++++++++++++++++ ...d-element-examples-chromium-dark-linux.png | 4 +-- ...-element-examples-chromium-light-linux.png | 4 +-- ...d-element-examples-chromium-dark-linux.png | 4 +-- ...-element-examples-chromium-light-linux.png | 4 +-- .../tooltip/si-tooltip.directive.spec.ts | 2 ++ .../tooltip/si-tooltip.directive.ts | 11 +++++--- 7 files changed, 44 insertions(+), 11 deletions(-) diff --git a/playwright/e2e/element-examples/navbar-vertical.spec.ts b/playwright/e2e/element-examples/navbar-vertical.spec.ts index 8142b1b20..3b862f1c1 100644 --- a/playwright/e2e/element-examples/navbar-vertical.spec.ts +++ b/playwright/e2e/element-examples/navbar-vertical.spec.ts @@ -34,6 +34,32 @@ test.describe('navbar vertical', () => { await si.runVisualAndA11yTests('collapsed-flyout'); }); + test('it should show tooltip only on keyboard interaction', async ({ page, si }) => { + await si.visitExample(example); + await page.getByLabel('collapse', { exact: true }).click(); + await expect(page.getByLabel('expand', { exact: true })).toBeVisible(); + await si.waitForAllAnimationsToComplete(); + const userManagement = page.getByRole('button', { name: 'User management' }); + const tooltip = page.getByRole('tooltip', { name: 'User management' }); + const group = page.getByRole('group', { name: 'User management' }); + + // This checks the tooltip is visible when using the keyboard + await userManagement.focus(); + await userManagement.press('Enter'); + await expect(group.getByRole('link', { name: 'Sub item', exact: true })).toBeFocused(); + await group.press('Escape'); + await expect(tooltip).toBeVisible(); + await expect(userManagement).toBeFocused(); + + // This check the tooltip is not visible whe using the mouse + await userManagement.click(); + await group.hover(); + await expect(tooltip).not.toBeVisible(); + await page.getByRole('main').click(); // outside click to hide flyout + await expect(userManagement).toBeFocused(); + await expect(tooltip).not.toBeVisible(); + }); + test(example + ' mobile collapsed', async ({ page, si }) => { await page.setViewportSize({ width: 570, height: 600 }); await si.visitExample(example, false); diff --git a/playwright/snapshots/navbar-vertical.spec.ts-snapshots/si-navbar-vertical--si-navbar-vertical--collapsed-element-examples-chromium-dark-linux.png b/playwright/snapshots/navbar-vertical.spec.ts-snapshots/si-navbar-vertical--si-navbar-vertical--collapsed-element-examples-chromium-dark-linux.png index cdda7335c..d2c9d9dfb 100644 --- a/playwright/snapshots/navbar-vertical.spec.ts-snapshots/si-navbar-vertical--si-navbar-vertical--collapsed-element-examples-chromium-dark-linux.png +++ b/playwright/snapshots/navbar-vertical.spec.ts-snapshots/si-navbar-vertical--si-navbar-vertical--collapsed-element-examples-chromium-dark-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9815ec309f365caeb737dfca2fd3e50a0d09fa32b1248498dff7ed86e3be06fe -size 18320 +oid sha256:88c6d04c7c387d04774c4a7ceb1fcd74a2087b0f1819d82c235ffb0afb3b48b5 +size 16617 diff --git a/playwright/snapshots/navbar-vertical.spec.ts-snapshots/si-navbar-vertical--si-navbar-vertical--collapsed-element-examples-chromium-light-linux.png b/playwright/snapshots/navbar-vertical.spec.ts-snapshots/si-navbar-vertical--si-navbar-vertical--collapsed-element-examples-chromium-light-linux.png index af66795ba..abe753ad8 100644 --- a/playwright/snapshots/navbar-vertical.spec.ts-snapshots/si-navbar-vertical--si-navbar-vertical--collapsed-element-examples-chromium-light-linux.png +++ b/playwright/snapshots/navbar-vertical.spec.ts-snapshots/si-navbar-vertical--si-navbar-vertical--collapsed-element-examples-chromium-light-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cd7c77f5f95127f0bd6a50fc54e10937fe4c78d5b6136289a0bb6cd673a202d3 -size 18085 +oid sha256:b8c45ee6ec391387ea9a1b65d2a73c43a8143f1a396b963a689d9b8defa68cd9 +size 16280 diff --git a/playwright/snapshots/navbar-vertical.spec.ts-snapshots/si-navbar-vertical--si-navbar-vertical-badges--collapsed-element-examples-chromium-dark-linux.png b/playwright/snapshots/navbar-vertical.spec.ts-snapshots/si-navbar-vertical--si-navbar-vertical-badges--collapsed-element-examples-chromium-dark-linux.png index 1187f52cf..b0d566c0b 100644 --- a/playwright/snapshots/navbar-vertical.spec.ts-snapshots/si-navbar-vertical--si-navbar-vertical-badges--collapsed-element-examples-chromium-dark-linux.png +++ b/playwright/snapshots/navbar-vertical.spec.ts-snapshots/si-navbar-vertical--si-navbar-vertical-badges--collapsed-element-examples-chromium-dark-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:53f770c49b26cd10809c36e374cd666ad9446bef59a16eac664fd4337fa651bf -size 24240 +oid sha256:9489b08cb013f3c542e087d49c9e29f7236c94367c9a7daf12a489e1f8618dcb +size 21770 diff --git a/playwright/snapshots/navbar-vertical.spec.ts-snapshots/si-navbar-vertical--si-navbar-vertical-badges--collapsed-element-examples-chromium-light-linux.png b/playwright/snapshots/navbar-vertical.spec.ts-snapshots/si-navbar-vertical--si-navbar-vertical-badges--collapsed-element-examples-chromium-light-linux.png index df4802e4a..1ba9436c7 100644 --- a/playwright/snapshots/navbar-vertical.spec.ts-snapshots/si-navbar-vertical--si-navbar-vertical-badges--collapsed-element-examples-chromium-light-linux.png +++ b/playwright/snapshots/navbar-vertical.spec.ts-snapshots/si-navbar-vertical--si-navbar-vertical-badges--collapsed-element-examples-chromium-light-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b9f14c9a400b311897f642d310a2625eb9f3fec0fbbf2771175d8a9230311f98 -size 23753 +oid sha256:437cf37a4a7650688419f5333c5182ad81b9374bc7765bb4d66cda848c71fcb4 +size 21171 diff --git a/projects/element-ng/tooltip/si-tooltip.directive.spec.ts b/projects/element-ng/tooltip/si-tooltip.directive.spec.ts index cf30a67e9..ed34f583e 100644 --- a/projects/element-ng/tooltip/si-tooltip.directive.spec.ts +++ b/projects/element-ng/tooltip/si-tooltip.directive.spec.ts @@ -35,6 +35,7 @@ describe('SiTooltipDirective', () => { fixture = TestBed.createComponent(TestHostComponent); component = fixture.componentInstance; button = fixture.nativeElement.querySelector('button') as HTMLButtonElement; + spyOn(button, 'matches').and.returnValue(true); fixture.detectChanges(); }); @@ -112,6 +113,7 @@ describe('SiTooltipDirective', () => { jasmine.clock().install(); fixture = TestBed.createComponent(TestHostComponent); button = fixture.nativeElement.querySelector('button') as HTMLButtonElement; + spyOn(button, 'matches').and.returnValue(true); fixture.detectChanges(); }); diff --git a/projects/element-ng/tooltip/si-tooltip.directive.ts b/projects/element-ng/tooltip/si-tooltip.directive.ts index b114d4349..7071031a2 100644 --- a/projects/element-ng/tooltip/si-tooltip.directive.ts +++ b/projects/element-ng/tooltip/si-tooltip.directive.ts @@ -2,6 +2,7 @@ * Copyright (c) Siemens 2016 - 2026 * SPDX-License-Identifier: MIT */ +import { isPlatformBrowser } from '@angular/common'; import { booleanAttribute, Directive, @@ -9,6 +10,7 @@ import { inject, input, OnDestroy, + PLATFORM_ID, TemplateRef } from '@angular/core'; import { positions } from '@siemens/element-ng/common'; @@ -21,7 +23,7 @@ import { SiTooltipService, TooltipRef } from './si-tooltip.service'; providers: [SiTooltipService], host: { '[attr.aria-describedby]': 'describedBy', - '(focus)': 'focusIn()', + '(focus)': 'focusIn($event)', '(mouseenter)': 'show()', '(touchstart)': 'hide()', '(focusout)': 'hide()', @@ -63,6 +65,7 @@ export class SiTooltipDirective implements OnDestroy { private showTimeout?: ReturnType; private tooltipService = inject(SiTooltipService); private elementRef = inject(ElementRef); + private isBrowser = isPlatformBrowser(inject(PLATFORM_ID)); ngOnDestroy(): void { this.clearShowTimeout(); @@ -98,8 +101,10 @@ export class SiTooltipDirective implements OnDestroy { }, delay); } - protected focusIn(): void { - this.showTooltip(true); + protected focusIn(event: FocusEvent): void { + if (this.isBrowser && (event.target as Element).matches(':focus-visible')) { + this.showTooltip(true); + } } protected show(): void {