From cbfc00addc67221c874b315afecfda359c113627 Mon Sep 17 00:00:00 2001 From: Marco D'Auria Date: Mon, 29 Sep 2025 10:22:18 +0200 Subject: [PATCH 1/2] fix(tooltip): always show if hovered or focused Remove dismissible tooltip functionality and standardize interaction patterns. Tooltips now follow consistent hover/focus behavior as per accessibility guidelines. BREAKING CHANGE: The `triggers` input has been removed to align the behavior with accessibility guidelines. Remove all usages. The tooltip will always be shown if the target element is focused or hovered. --- api-goldens/element-ng/tooltip/index.api.md | 1 - .../e2e/element-examples/si-tooltip.spec.ts | 4 +- ...o-element-examples-chromium-dark-linux.png | 3 + ...-element-examples-chromium-light-linux.png | 3 + .../si-tooltip--si-tooltip--auto.yaml | 26 +++ ...m-element-examples-chromium-dark-linux.png | 4 +- ...-element-examples-chromium-light-linux.png | 4 +- .../si-tooltip--si-tooltip--bottom.yaml | 35 ++- ...d-element-examples-chromium-dark-linux.png | 4 +- ...-element-examples-chromium-light-linux.png | 4 +- .../si-tooltip--si-tooltip--end.yaml | 35 ++- ...t-element-examples-chromium-dark-linux.png | 4 +- ...-element-examples-chromium-light-linux.png | 4 +- .../si-tooltip--si-tooltip--start.yaml | 35 ++- ...p-element-examples-chromium-dark-linux.png | 4 +- ...-element-examples-chromium-light-linux.png | 4 +- .../si-tooltip--si-tooltip--top.yaml | 35 ++- .../tooltip/si-tooltip.directive.spec.ts | 26 +-- .../tooltip/si-tooltip.directive.ts | 52 ++--- src/app/examples/si-tooltip/si-tooltip.html | 211 +++++++++++++----- src/app/examples/si-tooltip/si-tooltip.ts | 32 ++- 21 files changed, 365 insertions(+), 165 deletions(-) create mode 100644 playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--auto-element-examples-chromium-dark-linux.png create mode 100644 playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--auto-element-examples-chromium-light-linux.png create mode 100644 playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--auto.yaml diff --git a/api-goldens/element-ng/tooltip/index.api.md b/api-goldens/element-ng/tooltip/index.api.md index 2c9b9de9f..45bb40baf 100644 --- a/api-goldens/element-ng/tooltip/index.api.md +++ b/api-goldens/element-ng/tooltip/index.api.md @@ -20,7 +20,6 @@ export class SiTooltipDirective implements OnDestroy { readonly placement: i0.InputSignal<"auto" | "top" | "start" | "end" | "bottom">; readonly siTooltip: i0.InputSignal | TranslatableString>; readonly tooltipContext: i0.InputSignal; - readonly triggers: i0.InputSignal<"" | "focus" | undefined>; } // @public (undocumented) diff --git a/playwright/e2e/element-examples/si-tooltip.spec.ts b/playwright/e2e/element-examples/si-tooltip.spec.ts index ad9e9ad22..510fe0d7a 100644 --- a/playwright/e2e/element-examples/si-tooltip.spec.ts +++ b/playwright/e2e/element-examples/si-tooltip.spec.ts @@ -7,11 +7,11 @@ import { expect, test } from '../../support/test-helpers'; test.describe('Tooltip', () => { const example = 'si-tooltip/si-tooltip'; - ['top', 'end', 'start', 'bottom'].forEach(direction => { + ['auto', 'top', 'end', 'start', 'bottom'].forEach(direction => { test(direction, async ({ page, si }) => { await si.visitExample(example); - await page.locator('.btn').getByText(`Tooltip on ${direction}`).dispatchEvent('mouseenter'); + await page.locator(`[placement="${direction}"]`).focus(); await expect(page.locator('.tooltip')).toHaveCount(1); await si.runVisualAndA11yTests(direction); diff --git a/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--auto-element-examples-chromium-dark-linux.png b/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--auto-element-examples-chromium-dark-linux.png new file mode 100644 index 000000000..e4162043d --- /dev/null +++ b/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--auto-element-examples-chromium-dark-linux.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:292c98aec27e865e79f1da3fa19751e4411b9de1780646ef7ce0b4622ddafcf0 +size 17626 diff --git a/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--auto-element-examples-chromium-light-linux.png b/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--auto-element-examples-chromium-light-linux.png new file mode 100644 index 000000000..7a11db87b --- /dev/null +++ b/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--auto-element-examples-chromium-light-linux.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:be11660af2c810e0dc904963e68144920234c29c6c9ce212e88140b54bcb8d6a +size 18746 diff --git a/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--auto.yaml b/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--auto.yaml new file mode 100644 index 000000000..a9fc5900a --- /dev/null +++ b/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--auto.yaml @@ -0,0 +1,26 @@ +- group "File operations": + - button "Open file" + - button "Save" + - button "Download" + - button "Share" +- group "Basic actions": + - button "Edit" + - button "Copy" + - button "Delete" +- group "Template examples": + - button "Template HTML example" + - button "Template example" +- heading "Tooltip placement" [level=4] +- group "Tooltip placement": + - button "Auto placement" + - button "Top" + - button "Bottom" + - button "Start" + - button "End" +- heading "Other examples" [level=4] +- button "A long time ago in a galaxy far, far away..." +- tablist: + - tab + - tab + - tab +- tooltip "Auto placement" \ No newline at end of file diff --git a/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--bottom-element-examples-chromium-dark-linux.png b/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--bottom-element-examples-chromium-dark-linux.png index b6ed986d4..5b99b40a1 100644 --- a/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--bottom-element-examples-chromium-dark-linux.png +++ b/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--bottom-element-examples-chromium-dark-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d609bbe1609bc2c39727231d1cc893a2e96f282e29b069e37185f87e2c7e00f5 -size 13952 +oid sha256:80ecfc2b8fd818d941ced0c82b63f027620c8c476787b30ae76418464ad601ae +size 16258 diff --git a/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--bottom-element-examples-chromium-light-linux.png b/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--bottom-element-examples-chromium-light-linux.png index 31d181389..300879070 100644 --- a/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--bottom-element-examples-chromium-light-linux.png +++ b/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--bottom-element-examples-chromium-light-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f1bf6996e99443b5cd8ea83132e956c88baf8182bd062bbdc4c67b18a92ee9e2 -size 15151 +oid sha256:0db32fe507fa4f26d8de1ffb705abfd19a112b3c388a4b9ea307c88069ba51be +size 17435 diff --git a/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--bottom.yaml b/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--bottom.yaml index 06657adbf..4411aceee 100644 --- a/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--bottom.yaml +++ b/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--bottom.yaml @@ -1,9 +1,26 @@ -- button "Tooltip on top" -- button "Tooltip on end" -- button "Tooltip auto" -- button "Tooltip on start" -- button "Tooltip on bottom" -- button "Dismissible tooltip" -- button "Tooltip template html" -- button "Tooltip template" -- tooltip "Vivamus sagittis lacus vel augue laoreet rutrum faucibus." \ No newline at end of file +- group "File operations": + - button "Open file" + - button "Save" + - button "Download" + - button "Share" +- group "Basic actions": + - button "Edit" + - button "Copy" + - button "Delete" +- group "Template examples": + - button "Template HTML example" + - button "Template example" +- heading "Tooltip placement" [level=4] +- group "Tooltip placement": + - button "Auto placement" + - button "Top" + - button "Bottom" + - button "Start" + - button "End" +- heading "Other examples" [level=4] +- button "A long time ago in a galaxy far, far away..." +- tablist: + - tab + - tab + - tab +- tooltip "Bottom" \ No newline at end of file diff --git a/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--end-element-examples-chromium-dark-linux.png b/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--end-element-examples-chromium-dark-linux.png index 612b6ed99..b93e80a25 100644 --- a/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--end-element-examples-chromium-dark-linux.png +++ b/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--end-element-examples-chromium-dark-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2f62f30fa8d0f044c8b1a592406d63f187a839c913520adb39e4731066711044 -size 15569 +oid sha256:9377d8d4d7e303b61a7bd15c67b8c2a4a825a6074f05bd3abb6799e51b5f9a66 +size 16239 diff --git a/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--end-element-examples-chromium-light-linux.png b/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--end-element-examples-chromium-light-linux.png index a7dc88248..e6a4f08a2 100644 --- a/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--end-element-examples-chromium-light-linux.png +++ b/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--end-element-examples-chromium-light-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:db94ab4401fd95c0616488bfdf8759c393bdba2cdd323b756f33435394bac90a -size 17001 +oid sha256:a27a71478b61774debfe898ddf0b7fb35f2d3af856c7c5cf9cef8a44a4f75f7c +size 17455 diff --git a/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--end.yaml b/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--end.yaml index 06657adbf..746bc23b2 100644 --- a/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--end.yaml +++ b/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--end.yaml @@ -1,9 +1,26 @@ -- button "Tooltip on top" -- button "Tooltip on end" -- button "Tooltip auto" -- button "Tooltip on start" -- button "Tooltip on bottom" -- button "Dismissible tooltip" -- button "Tooltip template html" -- button "Tooltip template" -- tooltip "Vivamus sagittis lacus vel augue laoreet rutrum faucibus." \ No newline at end of file +- group "File operations": + - button "Open file" + - button "Save" + - button "Download" + - button "Share" +- group "Basic actions": + - button "Edit" + - button "Copy" + - button "Delete" +- group "Template examples": + - button "Template HTML example" + - button "Template example" +- heading "Tooltip placement" [level=4] +- group "Tooltip placement": + - button "Auto placement" + - button "Top" + - button "Bottom" + - button "Start" + - button "End" +- heading "Other examples" [level=4] +- button "A long time ago in a galaxy far, far away..." +- tablist: + - tab + - tab + - tab +- tooltip "End" \ No newline at end of file diff --git a/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--start-element-examples-chromium-dark-linux.png b/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--start-element-examples-chromium-dark-linux.png index ed83fa4d6..ded9b02b1 100644 --- a/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--start-element-examples-chromium-dark-linux.png +++ b/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--start-element-examples-chromium-dark-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:936175d1bc86784f34636b37d6262221b756d7664befd85de53548691330351e -size 15352 +oid sha256:1db08b89aafc1cc3933b4e0da43d228fcac19c145178d96f5bf651d0631222fe +size 16300 diff --git a/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--start-element-examples-chromium-light-linux.png b/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--start-element-examples-chromium-light-linux.png index 3f52fd294..157f5488f 100644 --- a/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--start-element-examples-chromium-light-linux.png +++ b/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--start-element-examples-chromium-light-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:02f619b457dabbc31ad6e0e06fd7dc8ba255d6d29a67ea5f9b7c94d35d550f37 -size 16780 +oid sha256:0bd9acb3be1fedfc768d5bcce6fff2c755dd2f0cba31aca6a87f150dd969cd07 +size 17429 diff --git a/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--start.yaml b/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--start.yaml index 06657adbf..8b45881f3 100644 --- a/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--start.yaml +++ b/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--start.yaml @@ -1,9 +1,26 @@ -- button "Tooltip on top" -- button "Tooltip on end" -- button "Tooltip auto" -- button "Tooltip on start" -- button "Tooltip on bottom" -- button "Dismissible tooltip" -- button "Tooltip template html" -- button "Tooltip template" -- tooltip "Vivamus sagittis lacus vel augue laoreet rutrum faucibus." \ No newline at end of file +- group "File operations": + - button "Open file" + - button "Save" + - button "Download" + - button "Share" +- group "Basic actions": + - button "Edit" + - button "Copy" + - button "Delete" +- group "Template examples": + - button "Template HTML example" + - button "Template example" +- heading "Tooltip placement" [level=4] +- group "Tooltip placement": + - button "Auto placement" + - button "Top" + - button "Bottom" + - button "Start" + - button "End" +- heading "Other examples" [level=4] +- button "A long time ago in a galaxy far, far away..." +- tablist: + - tab + - tab + - tab +- tooltip "Start" \ No newline at end of file diff --git a/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--top-element-examples-chromium-dark-linux.png b/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--top-element-examples-chromium-dark-linux.png index 6f51f879d..bc6db7a5d 100644 --- a/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--top-element-examples-chromium-dark-linux.png +++ b/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--top-element-examples-chromium-dark-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1ac5a9c2655e5db73c8b5e39b4bc0721af86929ce15b0b0c4002ce61c322dc4d -size 13721 +oid sha256:fe829efb9553e49220093c7a546b615aefc53e7a5f3eb4805c08724be38d24a6 +size 16512 diff --git a/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--top-element-examples-chromium-light-linux.png b/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--top-element-examples-chromium-light-linux.png index 9d057c6e3..9b50773f0 100644 --- a/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--top-element-examples-chromium-light-linux.png +++ b/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--top-element-examples-chromium-light-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:aaddcd1648514fc812cf4efb89816ead02f12d01ef72dd8c82cdae900a828a32 -size 14893 +oid sha256:85814c3bbb4893720bf9c08f5de2688261e361a3bffc0ae719a0159a9b8bd85b +size 17644 diff --git a/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--top.yaml b/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--top.yaml index 06657adbf..4b6dc3765 100644 --- a/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--top.yaml +++ b/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--top.yaml @@ -1,9 +1,26 @@ -- button "Tooltip on top" -- button "Tooltip on end" -- button "Tooltip auto" -- button "Tooltip on start" -- button "Tooltip on bottom" -- button "Dismissible tooltip" -- button "Tooltip template html" -- button "Tooltip template" -- tooltip "Vivamus sagittis lacus vel augue laoreet rutrum faucibus." \ No newline at end of file +- group "File operations": + - button "Open file" + - button "Save" + - button "Download" + - button "Share" +- group "Basic actions": + - button "Edit" + - button "Copy" + - button "Delete" +- group "Template examples": + - button "Template HTML example" + - button "Template example" +- heading "Tooltip placement" [level=4] +- group "Tooltip placement": + - button "Auto placement" + - button "Top" + - button "Bottom" + - button "Start" + - button "End" +- heading "Other examples" [level=4] +- button "A long time ago in a galaxy far, far away..." +- tablist: + - tab + - tab + - tab +- tooltip "Top" \ No newline at end of file diff --git a/projects/element-ng/tooltip/si-tooltip.directive.spec.ts b/projects/element-ng/tooltip/si-tooltip.directive.spec.ts index f9c55eac4..b51ecc247 100644 --- a/projects/element-ng/tooltip/si-tooltip.directive.spec.ts +++ b/projects/element-ng/tooltip/si-tooltip.directive.spec.ts @@ -15,17 +15,12 @@ describe('SiTooltipDirective', () => { @Component({ imports: [SiTooltipModule], - template: `` }) class TestHostComponent { isDisabled = false; - triggers: '' | 'focus' = ''; } beforeEach(async () => { @@ -48,7 +43,9 @@ describe('SiTooltipDirective', () => { it('should open on focus', () => { button.dispatchEvent(new Event('focus')); - jasmine.clock().tick(500); + // Focus should be immediate (no delay) but still need to tick for setTimeout(0) + jasmine.clock().tick(0); + fixture.detectChanges(); expect(document.querySelector('.tooltip')).toBeTruthy(); expect(document.querySelector('.tooltip')?.innerHTML).toContain('test tooltip'); @@ -70,22 +67,15 @@ describe('SiTooltipDirective', () => { it('should show tooltip on mouse over', () => { button.dispatchEvent(new MouseEvent('mouseenter')); + // hover should have 500ms delay + expect(document.querySelector('.tooltip')).toBeFalsy(); + + jasmine.clock().tick(500); expect(document.querySelector('.tooltip')).toBeTruthy(); button.dispatchEvent(new MouseEvent('mouseleave')); expect(document.querySelector('.tooltip')).toBeFalsy(); }); - - it('should not show tooltip on mouse over with focus trigger', () => { - component.triggers = 'focus'; - fixture.changeDetectorRef.markForCheck(); - fixture.detectChanges(); - - ['mouseenter', 'mouseleave'].forEach(e => { - button.dispatchEvent(new MouseEvent(e)); - expect(document.querySelector('.tooltip')).toBeFalsy(); - }); - }); }); describe('with template', () => { diff --git a/projects/element-ng/tooltip/si-tooltip.directive.ts b/projects/element-ng/tooltip/si-tooltip.directive.ts index ad08f1246..3951a5669 100644 --- a/projects/element-ng/tooltip/si-tooltip.directive.ts +++ b/projects/element-ng/tooltip/si-tooltip.directive.ts @@ -41,11 +41,6 @@ export class SiTooltipDirective implements OnDestroy { */ readonly placement = input('auto'); - /** - * The trigger event on which the tooltip shall be displayed - */ - readonly triggers = input<'' | 'focus'>(); - /** * Allows the tooltip to be disabled * @@ -61,50 +56,57 @@ export class SiTooltipDirective implements OnDestroy { protected describedBy = `__tooltip_${SiTooltipDirective.idCounter++}`; private tooltipRef?: TooltipRef; + private showTimeout?: number; private tooltipService = inject(SiTooltipService); private elementRef = inject(ElementRef); ngOnDestroy(): void { + this.clearShowTimeout(); this.tooltipRef?.destroy(); } - private showTooltip(): void { + private clearShowTimeout(): void { + if (this.showTimeout) { + window.clearTimeout(this.showTimeout); + this.showTimeout = undefined; + } + } + + private showTooltip(immediate = false): void { const siTooltip = this.siTooltip(); if (this.isDisabled() || !siTooltip) { return; } - this.tooltipRef ??= this.tooltipService.createTooltip({ - describedBy: this.describedBy, - element: this.elementRef, - placement: this.placement() - }); - this.tooltipRef.show(this.siTooltip(), this.tooltipContext()); + + this.clearShowTimeout(); + + const delay = immediate ? 0 : 500; + + this.showTimeout = window.setTimeout(() => { + this.tooltipRef ??= this.tooltipService.createTooltip({ + describedBy: this.describedBy, + element: this.elementRef, + placement: this.placement() + }); + this.tooltipRef.show(this.siTooltip(), this.tooltipContext()); + }, delay); } @HostListener('focus') protected focusIn(): void { - this.showTooltip(); + this.showTooltip(true); } @HostListener('mouseenter') protected show(): void { - if (this.triggers() === 'focus') { - return; - } - this.showTooltip(); + this.showTooltip(false); } @HostListener('touchstart') @HostListener('focusout') + @HostListener('mouseleave') protected hide(): void { + this.clearShowTimeout(); this.tooltipRef?.hide(); } - - @HostListener('mouseleave') - protected mouseOut(): void { - if (this.triggers() === 'focus') { - return; - } - this.hide(); - } } diff --git a/src/app/examples/si-tooltip/si-tooltip.html b/src/app/examples/si-tooltip/si-tooltip.html index 0f2c3ec75..e10843482 100644 --- a/src/app/examples/si-tooltip/si-tooltip.html +++ b/src/app/examples/si-tooltip/si-tooltip.html @@ -1,65 +1,154 @@ -
- - - - - - - - - - - +
+
+
+ + + + +
+
+ + + +
+
+ + +
+
- - Hello:
- +
+

Tooltip placement

+
+ + + + + +
+
- -
- +
+

Other examples

+
+ + + +

Portfolio

+
+ +

Performance

+
+ +

Sustainability

+
+
- Hello, I'm a tooltip. - +
+ + Hello, + +
+ +
+
I'm the tooltip template
+
diff --git a/src/app/examples/si-tooltip/si-tooltip.ts b/src/app/examples/si-tooltip/si-tooltip.ts index 36b9d8647..be435066c 100644 --- a/src/app/examples/si-tooltip/si-tooltip.ts +++ b/src/app/examples/si-tooltip/si-tooltip.ts @@ -2,16 +2,36 @@ * Copyright (c) Siemens 2016 - 2025 * SPDX-License-Identifier: MIT */ -import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { afterNextRender, Component, ElementRef, OnDestroy, viewChild } from '@angular/core'; import { SiIconComponent } from '@siemens/element-ng/icon'; +import { SiTabsetComponent, SiTabComponent } from '@siemens/element-ng/tabs'; import { SiTooltipDirective } from '@siemens/element-ng/tooltip'; @Component({ selector: 'app-sample', - imports: [SiIconComponent, SiTooltipDirective], - templateUrl: './si-tooltip.html', - changeDetection: ChangeDetectionStrategy.OnPush + imports: [SiIconComponent, SiTooltipDirective, SiTabsetComponent, SiTabComponent], + templateUrl: './si-tooltip.html' }) -export class SampleComponent { - html = `I am a bold text tooltip.`; +export class SampleComponent implements OnDestroy { + readonly focusButton = viewChild.required>('focusButton'); + private focusTimeout?: ReturnType; + + html = `I'm a microwave`; + + constructor() { + afterNextRender(() => { + const button = this.focusButton(); + if (!button) return; + + this.focusTimeout = setTimeout(() => { + button.nativeElement.focus(); + }); + }); + } + + ngOnDestroy(): void { + if (this.focusTimeout) { + clearTimeout(this.focusTimeout); + } + } } From d6f24bbf4ad82362e595b639e79e94bc2528094f Mon Sep 17 00:00:00 2001 From: Marco D'Auria Date: Fri, 9 Jan 2026 21:42:08 +0100 Subject: [PATCH 2/2] feat(tooltip): add animation --- ...-element-examples-chromium-light-linux.png | 4 +-- ...-element-examples-chromium-light-linux.png | 4 +-- ...-element-examples-chromium-light-linux.png | 4 +-- ...-element-examples-chromium-light-linux.png | 4 +-- ...-element-examples-chromium-light-linux.png | 4 +-- .../tooltip/si-tooltip.component.scss | 29 +++++++++++++++++++ .../tooltip/si-tooltip.component.ts | 7 +++-- .../tooltip/si-tooltip.directive.spec.ts | 1 + .../tooltip/si-tooltip.directive.ts | 19 ++++++------ .../src/styles/bootstrap/_tooltip.scss | 2 +- src/app/examples/si-tooltip/si-tooltip.ts | 17 ++--------- 11 files changed, 58 insertions(+), 37 deletions(-) create mode 100644 projects/element-ng/tooltip/si-tooltip.component.scss diff --git a/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--auto-element-examples-chromium-light-linux.png b/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--auto-element-examples-chromium-light-linux.png index 7a11db87b..8b499e8b0 100644 --- a/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--auto-element-examples-chromium-light-linux.png +++ b/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--auto-element-examples-chromium-light-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:be11660af2c810e0dc904963e68144920234c29c6c9ce212e88140b54bcb8d6a -size 18746 +oid sha256:68049f0225f6cb9585d2f5e5212c550400da7ec3d515e7b554d1f968e1555630 +size 18742 diff --git a/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--bottom-element-examples-chromium-light-linux.png b/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--bottom-element-examples-chromium-light-linux.png index 300879070..163f9bc30 100644 --- a/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--bottom-element-examples-chromium-light-linux.png +++ b/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--bottom-element-examples-chromium-light-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0db32fe507fa4f26d8de1ffb705abfd19a112b3c388a4b9ea307c88069ba51be -size 17435 +oid sha256:90d35000d8321319c4c4fb87b801611cdbf6e65cd41cdee717aa207e1df5c432 +size 17428 diff --git a/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--end-element-examples-chromium-light-linux.png b/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--end-element-examples-chromium-light-linux.png index e6a4f08a2..b990e9741 100644 --- a/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--end-element-examples-chromium-light-linux.png +++ b/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--end-element-examples-chromium-light-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a27a71478b61774debfe898ddf0b7fb35f2d3af856c7c5cf9cef8a44a4f75f7c -size 17455 +oid sha256:4fdd9ffa064f4f77ba40bceffcb0178e110d839f7aa0f1dddef7da59a5d694bc +size 17465 diff --git a/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--start-element-examples-chromium-light-linux.png b/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--start-element-examples-chromium-light-linux.png index 157f5488f..113b7258a 100644 --- a/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--start-element-examples-chromium-light-linux.png +++ b/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--start-element-examples-chromium-light-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0bd9acb3be1fedfc768d5bcce6fff2c755dd2f0cba31aca6a87f150dd969cd07 -size 17429 +oid sha256:371028a63bf9476ea5551aad5a40855a99a32308d02ef8e975bd2dd34e28424b +size 17446 diff --git a/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--top-element-examples-chromium-light-linux.png b/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--top-element-examples-chromium-light-linux.png index 9b50773f0..bd43c7582 100644 --- a/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--top-element-examples-chromium-light-linux.png +++ b/playwright/snapshots/si-tooltip.spec.ts-snapshots/si-tooltip--si-tooltip--top-element-examples-chromium-light-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:85814c3bbb4893720bf9c08f5de2688261e361a3bffc0ae719a0159a9b8bd85b -size 17644 +oid sha256:b42f9fd63061f2391bd10035847be2c71ffa114411bdf1bda238ac7f8b5e23d8 +size 17660 diff --git a/projects/element-ng/tooltip/si-tooltip.component.scss b/projects/element-ng/tooltip/si-tooltip.component.scss new file mode 100644 index 000000000..fd666708c --- /dev/null +++ b/projects/element-ng/tooltip/si-tooltip.component.scss @@ -0,0 +1,29 @@ +@use '@siemens/element-theme/src/styles/variables'; + +$transition-duration: variables.element-transition-duration(0.15s); + +%transition { + transition: + opacity $transition-duration ease-out, + transform $transition-duration ease-out; +} + +:host { + opacity: 1; + transform: scale(1); + @extend %transition; + + &.tooltip-leave { + opacity: 0; + transform: scale(0.8); + } + + .tooltip { + @extend %transition; + + @starting-style { + opacity: 0; + transform: scale(0.8); + } + } +} diff --git a/projects/element-ng/tooltip/si-tooltip.component.ts b/projects/element-ng/tooltip/si-tooltip.component.ts index eae69a5f9..3fcae2d4e 100644 --- a/projects/element-ng/tooltip/si-tooltip.component.ts +++ b/projects/element-ng/tooltip/si-tooltip.component.ts @@ -20,12 +20,15 @@ import { SiTranslatePipe, TranslatableString } from '@siemens/element-translate- @Component({ selector: 'si-tooltip', imports: [NgTemplateOutlet, SiTranslatePipe, NgComponentOutlet], - templateUrl: './si-tooltip.component.html' + templateUrl: './si-tooltip.component.html', + styleUrl: './si-tooltip.component.scss', + host: { + 'animate.leave': 'tooltip-leave' + } }) export class TooltipComponent { /** @defaultValue '' */ readonly tooltip = input | Type>(''); - protected readonly tooltipPositionClass = signal(''); protected readonly arrowPos = signal(undefined); /** @internal */ diff --git a/projects/element-ng/tooltip/si-tooltip.directive.spec.ts b/projects/element-ng/tooltip/si-tooltip.directive.spec.ts index b51ecc247..f6578553a 100644 --- a/projects/element-ng/tooltip/si-tooltip.directive.spec.ts +++ b/projects/element-ng/tooltip/si-tooltip.directive.spec.ts @@ -71,6 +71,7 @@ describe('SiTooltipDirective', () => { expect(document.querySelector('.tooltip')).toBeFalsy(); jasmine.clock().tick(500); + fixture.detectChanges(); expect(document.querySelector('.tooltip')).toBeTruthy(); button.dispatchEvent(new MouseEvent('mouseleave')); diff --git a/projects/element-ng/tooltip/si-tooltip.directive.ts b/projects/element-ng/tooltip/si-tooltip.directive.ts index 3951a5669..252eb179e 100644 --- a/projects/element-ng/tooltip/si-tooltip.directive.ts +++ b/projects/element-ng/tooltip/si-tooltip.directive.ts @@ -6,7 +6,6 @@ import { booleanAttribute, Directive, ElementRef, - HostListener, inject, input, OnDestroy, @@ -21,7 +20,12 @@ import { SiTooltipService, TooltipRef } from './si-tooltip.service'; selector: '[siTooltip]', providers: [SiTooltipService], host: { - '[attr.aria-describedby]': 'describedBy' + '[attr.aria-describedby]': 'describedBy', + '(focus)': 'focusIn()', + '(mouseenter)': 'show()', + '(touchstart)': 'hide()', + '(focusout)': 'hide()', + '(mouseleave)': 'hide()' } }) export class SiTooltipDirective implements OnDestroy { @@ -56,7 +60,7 @@ export class SiTooltipDirective implements OnDestroy { protected describedBy = `__tooltip_${SiTooltipDirective.idCounter++}`; private tooltipRef?: TooltipRef; - private showTimeout?: number; + private showTimeout?: ReturnType; private tooltipService = inject(SiTooltipService); private elementRef = inject(ElementRef); @@ -67,7 +71,7 @@ export class SiTooltipDirective implements OnDestroy { private clearShowTimeout(): void { if (this.showTimeout) { - window.clearTimeout(this.showTimeout); + clearTimeout(this.showTimeout); this.showTimeout = undefined; } } @@ -82,7 +86,7 @@ export class SiTooltipDirective implements OnDestroy { const delay = immediate ? 0 : 500; - this.showTimeout = window.setTimeout(() => { + this.showTimeout = setTimeout(() => { this.tooltipRef ??= this.tooltipService.createTooltip({ describedBy: this.describedBy, element: this.elementRef, @@ -92,19 +96,14 @@ export class SiTooltipDirective implements OnDestroy { }, delay); } - @HostListener('focus') protected focusIn(): void { this.showTooltip(true); } - @HostListener('mouseenter') protected show(): void { this.showTooltip(false); } - @HostListener('touchstart') - @HostListener('focusout') - @HostListener('mouseleave') protected hide(): void { this.clearShowTimeout(); this.tooltipRef?.hide(); diff --git a/projects/element-theme/src/styles/bootstrap/_tooltip.scss b/projects/element-theme/src/styles/bootstrap/_tooltip.scss index dcd3f7998..213ca7544 100644 --- a/projects/element-theme/src/styles/bootstrap/_tooltip.scss +++ b/projects/element-theme/src/styles/bootstrap/_tooltip.scss @@ -13,10 +13,10 @@ font-size: variables.$tooltip-font-size; // Allow breaking very long words so they don't overflow the tooltip's bounds word-wrap: break-word; - opacity: 0; &.show { opacity: variables.$tooltip-opacity; + transform: scale(1); } .tooltip-arrow { diff --git a/src/app/examples/si-tooltip/si-tooltip.ts b/src/app/examples/si-tooltip/si-tooltip.ts index be435066c..a57b98444 100644 --- a/src/app/examples/si-tooltip/si-tooltip.ts +++ b/src/app/examples/si-tooltip/si-tooltip.ts @@ -2,7 +2,7 @@ * Copyright (c) Siemens 2016 - 2025 * SPDX-License-Identifier: MIT */ -import { afterNextRender, Component, ElementRef, OnDestroy, viewChild } from '@angular/core'; +import { afterNextRender, Component, ElementRef, viewChild } from '@angular/core'; import { SiIconComponent } from '@siemens/element-ng/icon'; import { SiTabsetComponent, SiTabComponent } from '@siemens/element-ng/tabs'; import { SiTooltipDirective } from '@siemens/element-ng/tooltip'; @@ -12,26 +12,15 @@ import { SiTooltipDirective } from '@siemens/element-ng/tooltip'; imports: [SiIconComponent, SiTooltipDirective, SiTabsetComponent, SiTabComponent], templateUrl: './si-tooltip.html' }) -export class SampleComponent implements OnDestroy { +export class SampleComponent { readonly focusButton = viewChild.required>('focusButton'); - private focusTimeout?: ReturnType; html = `I'm a microwave`; constructor() { afterNextRender(() => { const button = this.focusButton(); - if (!button) return; - - this.focusTimeout = setTimeout(() => { - button.nativeElement.focus(); - }); + button.nativeElement.focus(); }); } - - ngOnDestroy(): void { - if (this.focusTimeout) { - clearTimeout(this.focusTimeout); - } - } }