From 8723c9408b75c5c8b838140f2817fa63a2fbb65f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 13 May 2026 08:45:59 +0000 Subject: [PATCH 1/5] Add auto tooltip for button and badge Agent-Logs-Url: https://github.com/vaadin/web-components/sessions/b85857dc-8cb9-4aaa-9268-da95a72b8b1e --- packages/badge/package.json | 1 + packages/badge/src/vaadin-badge.d.ts | 5 ++ packages/badge/src/vaadin-badge.js | 71 +++++++++++++++++++++++++- packages/badge/test/badge.test.ts | 20 ++++++++ packages/button/package.json | 1 + packages/button/src/vaadin-button.d.ts | 5 ++ packages/button/src/vaadin-button.js | 67 ++++++++++++++++++++++-- packages/button/test/button.test.ts | 20 ++++++++ 8 files changed, 184 insertions(+), 6 deletions(-) diff --git a/packages/badge/package.json b/packages/badge/package.json index a6d988d039b..c1215790ab0 100644 --- a/packages/badge/package.json +++ b/packages/badge/package.json @@ -36,6 +36,7 @@ "dependencies": { "@vaadin/a11y-base": "25.2.0-alpha12", "@vaadin/component-base": "25.2.0-alpha12", + "@vaadin/tooltip": "25.2.0-alpha12", "@vaadin/vaadin-themable-mixin": "25.2.0-alpha12", "lit": "^3.0.0" }, diff --git a/packages/badge/src/vaadin-badge.d.ts b/packages/badge/src/vaadin-badge.d.ts index d3462f32e44..242eeebcd66 100644 --- a/packages/badge/src/vaadin-badge.d.ts +++ b/packages/badge/src/vaadin-badge.d.ts @@ -57,6 +57,11 @@ import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mix * See [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation. */ declare class Badge extends ElementMixin(ThemableMixin(HTMLElement)) { + /** + * When enabled, hides the content visually and shows it in a tooltip. + */ + autoTooltip: boolean; + /** * The number to display in the badge. */ diff --git a/packages/badge/src/vaadin-badge.js b/packages/badge/src/vaadin-badge.js index ee174f1c616..a7f44941084 100644 --- a/packages/badge/src/vaadin-badge.js +++ b/packages/badge/src/vaadin-badge.js @@ -3,6 +3,7 @@ * Copyright (c) 2026 - 2026 Vaadin Ltd. * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/ */ +import '@vaadin/tooltip/src/vaadin-tooltip.js'; import { html, LitElement } from 'lit'; import { classMap } from 'lit/directives/class-map.js'; import { screenReaderOnly } from '@vaadin/a11y-base/src/styles/sr-only-styles.js'; @@ -11,6 +12,7 @@ import { isEmptyTextNode } from '@vaadin/component-base/src/dom-utils.js'; import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js'; import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js'; import { SlotObserver } from '@vaadin/component-base/src/slot-observer.js'; +import { TooltipController } from '@vaadin/component-base/src/tooltip-controller.js'; import { LumoInjectionMixin } from '@vaadin/vaadin-themable-mixin/lumo-injection-mixin.js'; import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js'; import { badgeStyles } from './styles/vaadin-badge-base-styles.js'; @@ -93,6 +95,16 @@ class Badge extends ElementMixin(ThemableMixin(PolylitMixin(LumoInjectionMixin(L number: { type: Number, }, + + /** + * When enabled, hides the content visually and shows it in a tooltip. + */ + autoTooltip: { + type: Boolean, + value: false, + reflectToAttribute: true, + sync: true, + }, }; } @@ -108,9 +120,10 @@ class Badge extends ElementMixin(ThemableMixin(PolylitMixin(LumoInjectionMixin(L
${this.number}
-
- +
+
+ `; } @@ -123,6 +136,24 @@ class Badge extends ElementMixin(ThemableMixin(PolylitMixin(LumoInjectionMixin(L } } + /** @protected */ + updated(props) { + super.updated(props); + + if (props.has('autoTooltip')) { + this.__updateAutoTooltip(); + } + } + + /** @protected */ + ready() { + super.ready(); + + this._tooltipController = new TooltipController(this); + this.addController(this._tooltipController); + this.__updateAutoTooltip(); + } + /** @protected */ firstUpdated() { super.firstUpdated(); @@ -137,6 +168,42 @@ class Badge extends ElementMixin(ThemableMixin(PolylitMixin(LumoInjectionMixin(L this.toggleAttribute('has-icon', currentNodes.length > 0); }); } + + /** @private */ + __getContentText() { + const slot = this.shadowRoot.querySelector('slot:not([name])'); + return slot + .assignedNodes({ flatten: true }) + .map((node) => node.textContent) + .join('') + .trim(); + } + + /** @private */ + __hasCustomTooltip() { + return Array.from(this.children).some((node) => node !== this.__autoTooltip && node.slot === 'tooltip'); + } + + /** @private */ + __updateAutoTooltip() { + const text = this.__getContentText(); + + if (!this.autoTooltip || !text || this.__hasCustomTooltip()) { + this.__autoTooltip?.remove(); + return; + } + + if (!this.__autoTooltip) { + this.__autoTooltip = document.createElement('vaadin-tooltip'); + this.__autoTooltip.setAttribute('slot', 'tooltip'); + } + + this.__autoTooltip.setAttribute('text', text); + + if (!this.__autoTooltip.isConnected) { + this.appendChild(this.__autoTooltip); + } + } } defineCustomElement(Badge); diff --git a/packages/badge/test/badge.test.ts b/packages/badge/test/badge.test.ts index 21e3f4f84ec..c441476dea1 100644 --- a/packages/badge/test/badge.test.ts +++ b/packages/badge/test/badge.test.ts @@ -116,4 +116,24 @@ describe('vaadin-badge', () => { expect(badge.hasAttribute('has-icon')).to.be.false; }); }); + + describe('auto-tooltip', () => { + beforeEach(async () => { + badge = fixtureSync('New'); + await nextRender(); + }); + + it('should hide the content visually', () => { + const content = badge.shadowRoot!.querySelector('[part="content"]')!; + + expect(content.classList.contains('sr-only')).to.be.true; + }); + + it('should add a tooltip with content text', () => { + const tooltip = badge.querySelector('vaadin-tooltip[slot="tooltip"]')!; + + expect(tooltip).to.be.ok; + expect(tooltip.getAttribute('text')).to.equal('New'); + }); + }); }); diff --git a/packages/button/package.json b/packages/button/package.json index 9cf7770b79e..487a9c7098d 100644 --- a/packages/button/package.json +++ b/packages/button/package.json @@ -37,6 +37,7 @@ "@open-wc/dedupe-mixin": "^1.3.0", "@vaadin/a11y-base": "25.2.0-alpha12", "@vaadin/component-base": "25.2.0-alpha12", + "@vaadin/tooltip": "25.2.0-alpha12", "@vaadin/vaadin-themable-mixin": "25.2.0-alpha12", "lit": "^3.0.0" }, diff --git a/packages/button/src/vaadin-button.d.ts b/packages/button/src/vaadin-button.d.ts index 530c9bbbd43..ad8136b1233 100644 --- a/packages/button/src/vaadin-button.d.ts +++ b/packages/button/src/vaadin-button.d.ts @@ -54,6 +54,11 @@ import { ButtonMixin } from './vaadin-button-mixin.js'; * See [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation. */ declare class Button extends ButtonMixin(ElementMixin(ThemableMixin(HTMLElement))) { + /** + * When enabled, hides the label visually and shows it in a tooltip. + */ + autoTooltip: boolean; + /** * When disabled, the button is rendered as "dimmed". * diff --git a/packages/button/src/vaadin-button.js b/packages/button/src/vaadin-button.js index d50336e7545..2efcb7e923e 100644 --- a/packages/button/src/vaadin-button.js +++ b/packages/button/src/vaadin-button.js @@ -3,7 +3,10 @@ * Copyright (c) 2017 - 2026 Vaadin Ltd. * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/ */ +import '@vaadin/tooltip/src/vaadin-tooltip.js'; import { html, LitElement } from 'lit'; +import { classMap } from 'lit/directives/class-map.js'; +import { screenReaderOnly } from '@vaadin/a11y-base/src/styles/sr-only-styles.js'; import { defineCustomElement } from '@vaadin/component-base/src/define.js'; import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js'; import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js'; @@ -68,7 +71,7 @@ class Button extends ButtonMixin(ElementMixin(ThemableMixin(PolylitMixin(LumoInj } static get styles() { - return buttonStyles; + return [buttonStyles, screenReaderOnly]; } static get properties() { @@ -95,6 +98,16 @@ class Button extends ButtonMixin(ElementMixin(ThemableMixin(PolylitMixin(LumoInj reflectToAttribute: true, sync: true, }, + + /** + * When enabled, hides the label visually and shows it in a tooltip. + */ + autoTooltip: { + type: Boolean, + value: false, + reflectToAttribute: true, + sync: true, + }, }; } @@ -105,30 +118,76 @@ class Button extends ButtonMixin(ElementMixin(ThemableMixin(PolylitMixin(LumoInj - - + + - +
`; } + /** @protected */ + updated(props) { + super.updated(props); + + if (props.has('autoTooltip')) { + this.__updateAutoTooltip(); + } + } + /** @protected */ ready() { super.ready(); this._tooltipController = new TooltipController(this); this.addController(this._tooltipController); + this.__updateAutoTooltip(); } /** @override */ __shouldAllowFocusWhenDisabled() { return window.Vaadin.featureFlags.accessibleDisabledButtons; } + + /** @private */ + __getLabelText() { + const slot = this.shadowRoot.querySelector('slot:not([name])'); + return slot + .assignedNodes({ flatten: true }) + .map((node) => node.textContent) + .join('') + .trim(); + } + + /** @private */ + __hasCustomTooltip() { + return Array.from(this.children).some((node) => node !== this.__autoTooltip && node.slot === 'tooltip'); + } + + /** @private */ + __updateAutoTooltip() { + const text = this.__getLabelText(); + + if (!this.autoTooltip || !text || this.__hasCustomTooltip()) { + this.__autoTooltip?.remove(); + return; + } + + if (!this.__autoTooltip) { + this.__autoTooltip = document.createElement('vaadin-tooltip'); + this.__autoTooltip.setAttribute('slot', 'tooltip'); + } + + this.__autoTooltip.setAttribute('text', text); + + if (!this.__autoTooltip.isConnected) { + this.appendChild(this.__autoTooltip); + } + } } defineCustomElement(Button); diff --git a/packages/button/test/button.test.ts b/packages/button/test/button.test.ts index 58ce2f0e747..b85dc79801e 100644 --- a/packages/button/test/button.test.ts +++ b/packages/button/test/button.test.ts @@ -206,4 +206,24 @@ describe('vaadin-button', () => { expect(document.activeElement).to.equal(lastGlobalFocusable); }); }); + + describe('auto-tooltip', () => { + beforeEach(async () => { + button = fixtureSync('Press me'); + await nextRender(); + }); + + it('should hide the label visually', () => { + const label = button.shadowRoot!.querySelector('[part="label"]')!; + + expect(label.classList.contains('sr-only')).to.be.true; + }); + + it('should add a tooltip with label text', () => { + const tooltip = button.querySelector('vaadin-tooltip[slot="tooltip"]')!; + + expect(tooltip).to.be.ok; + expect(tooltip.getAttribute('text')).to.equal('Press me'); + }); + }); }); From ea4f927e0530bd94cbd5153e4dd571c0a6bfc252 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 13 May 2026 08:47:34 +0000 Subject: [PATCH 2/5] Document badge tooltip slot Agent-Logs-Url: https://github.com/vaadin/web-components/sessions/b85857dc-8cb9-4aaa-9268-da95a72b8b1e --- packages/badge/src/vaadin-badge.d.ts | 2 ++ packages/badge/src/vaadin-badge.js | 2 ++ 2 files changed, 4 insertions(+) diff --git a/packages/badge/src/vaadin-badge.d.ts b/packages/badge/src/vaadin-badge.d.ts index 242eeebcd66..463933afbdd 100644 --- a/packages/badge/src/vaadin-badge.d.ts +++ b/packages/badge/src/vaadin-badge.d.ts @@ -19,6 +19,7 @@ import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mix * ---------|------------- * (none) | Default slot for the badge text content * `icon` | Slot for an icon to place before the text + * `tooltip` | Slot for a tooltip * * ### Styling * @@ -37,6 +38,7 @@ import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mix * `has-icon` | Set when the badge has content in the icon slot * `has-content` | Set when the badge has content in the default slot * `has-number` | Set when the badge has a number value + * `has-tooltip` | Set when the badge has a slotted tooltip * * The following custom CSS properties are available for styling: * diff --git a/packages/badge/src/vaadin-badge.js b/packages/badge/src/vaadin-badge.js index a7f44941084..ffc7fe45a07 100644 --- a/packages/badge/src/vaadin-badge.js +++ b/packages/badge/src/vaadin-badge.js @@ -30,6 +30,7 @@ import { badgeStyles } from './styles/vaadin-badge-base-styles.js'; * ---------|------------- * (none) | Default slot for the badge text content * `icon` | Slot for an icon to place before the text + * `tooltip` | Slot for a tooltip * * ### Styling * @@ -48,6 +49,7 @@ import { badgeStyles } from './styles/vaadin-badge-base-styles.js'; * `has-icon` | Set when the badge has content in the icon slot * `has-content` | Set when the badge has content in the default slot * `has-number` | Set when the badge has a number value + * `has-tooltip` | Set when the badge has a slotted tooltip * * The following custom CSS properties are available for styling: * From 052128aa740918b2417e57a6519f83fc8271df95 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 13 May 2026 08:51:16 +0000 Subject: [PATCH 3/5] Guard auto tooltip slot lookup Agent-Logs-Url: https://github.com/vaadin/web-components/sessions/b85857dc-8cb9-4aaa-9268-da95a72b8b1e --- packages/badge/src/vaadin-badge.js | 10 ++++++---- packages/button/src/vaadin-button.js | 10 ++++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/packages/badge/src/vaadin-badge.js b/packages/badge/src/vaadin-badge.js index ffc7fe45a07..7b0da0531a7 100644 --- a/packages/badge/src/vaadin-badge.js +++ b/packages/badge/src/vaadin-badge.js @@ -175,10 +175,12 @@ class Badge extends ElementMixin(ThemableMixin(PolylitMixin(LumoInjectionMixin(L __getContentText() { const slot = this.shadowRoot.querySelector('slot:not([name])'); return slot - .assignedNodes({ flatten: true }) - .map((node) => node.textContent) - .join('') - .trim(); + ? slot + .assignedNodes({ flatten: true }) + .map((node) => node.textContent) + .join('') + .trim() + : ''; } /** @private */ diff --git a/packages/button/src/vaadin-button.js b/packages/button/src/vaadin-button.js index 2efcb7e923e..225affe7c50 100644 --- a/packages/button/src/vaadin-button.js +++ b/packages/button/src/vaadin-button.js @@ -157,10 +157,12 @@ class Button extends ButtonMixin(ElementMixin(ThemableMixin(PolylitMixin(LumoInj __getLabelText() { const slot = this.shadowRoot.querySelector('slot:not([name])'); return slot - .assignedNodes({ flatten: true }) - .map((node) => node.textContent) - .join('') - .trim(); + ? slot + .assignedNodes({ flatten: true }) + .map((node) => node.textContent) + .join('') + .trim() + : ''; } /** @private */ From caf0654e3e604b27ec3af9dd3c90e93e7434a0a0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 13 May 2026 10:35:12 +0000 Subject: [PATCH 4/5] Update badge DOM snapshots for tooltip slot Agent-Logs-Url: https://github.com/vaadin/web-components/sessions/868fcc0e-2dac-4420-a00e-f006f272b001 --- .../badge/test/dom/__snapshots__/badge.test.snap.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/badge/test/dom/__snapshots__/badge.test.snap.js b/packages/badge/test/dom/__snapshots__/badge.test.snap.js index 4c501ce9fdb..54c29388318 100644 --- a/packages/badge/test/dom/__snapshots__/badge.test.snap.js +++ b/packages/badge/test/dom/__snapshots__/badge.test.snap.js @@ -31,6 +31,8 @@ snapshots["vaadin-badge shadow default"] = + + `; /* end snapshot vaadin-badge shadow default */ @@ -46,6 +48,8 @@ snapshots["vaadin-badge shadow number"] = + + `; /* end snapshot vaadin-badge shadow number */ @@ -69,6 +73,8 @@ snapshots["vaadin-badge shadow dot"] = + + `; /* end snapshot vaadin-badge shadow dot */ @@ -89,6 +95,8 @@ snapshots["vaadin-badge shadow icon-only"] = + + `; /* end snapshot vaadin-badge shadow icon-only */ @@ -109,6 +117,8 @@ snapshots["vaadin-badge shadow number-only"] = + + `; /* end snapshot vaadin-badge shadow number-only */ From 660900858476335d8303ed54f3dee7ef0864ceb1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 13 May 2026 10:57:48 +0000 Subject: [PATCH 5/5] Prevent auto tooltips from setting aria-describedby Agent-Logs-Url: https://github.com/vaadin/web-components/sessions/73002448-037c-4106-b8ff-668b11b757a5 --- packages/badge/src/vaadin-badge.js | 1 + packages/badge/test/badge.test.ts | 9 +++++++++ packages/button/src/vaadin-button.js | 1 + packages/button/test/button.test.ts | 9 +++++++++ 4 files changed, 20 insertions(+) diff --git a/packages/badge/src/vaadin-badge.js b/packages/badge/src/vaadin-badge.js index 7b0da0531a7..cd812d325e3 100644 --- a/packages/badge/src/vaadin-badge.js +++ b/packages/badge/src/vaadin-badge.js @@ -200,6 +200,7 @@ class Badge extends ElementMixin(ThemableMixin(PolylitMixin(LumoInjectionMixin(L if (!this.__autoTooltip) { this.__autoTooltip = document.createElement('vaadin-tooltip'); this.__autoTooltip.setAttribute('slot', 'tooltip'); + this.__autoTooltip.ariaTarget = null; } this.__autoTooltip.setAttribute('text', text); diff --git a/packages/badge/test/badge.test.ts b/packages/badge/test/badge.test.ts index c441476dea1..20a59b464f4 100644 --- a/packages/badge/test/badge.test.ts +++ b/packages/badge/test/badge.test.ts @@ -135,5 +135,14 @@ describe('vaadin-badge', () => { expect(tooltip).to.be.ok; expect(tooltip.getAttribute('text')).to.equal('New'); }); + + it('should not set aria-describedby', async () => { + const tooltip = badge.querySelector('vaadin-tooltip[slot="tooltip"]')! as any; + await nextUpdate(tooltip); + + expect(badge.hasAttribute('auto-tooltip')).to.be.true; + expect(tooltip.ariaTarget).to.equal(null); + expect(badge.hasAttribute('aria-describedby')).to.be.false; + }); }); }); diff --git a/packages/button/src/vaadin-button.js b/packages/button/src/vaadin-button.js index 225affe7c50..23544e6637c 100644 --- a/packages/button/src/vaadin-button.js +++ b/packages/button/src/vaadin-button.js @@ -182,6 +182,7 @@ class Button extends ButtonMixin(ElementMixin(ThemableMixin(PolylitMixin(LumoInj if (!this.__autoTooltip) { this.__autoTooltip = document.createElement('vaadin-tooltip'); this.__autoTooltip.setAttribute('slot', 'tooltip'); + this.__autoTooltip.ariaTarget = null; } this.__autoTooltip.setAttribute('text', text); diff --git a/packages/button/test/button.test.ts b/packages/button/test/button.test.ts index b85dc79801e..3e90b3f5587 100644 --- a/packages/button/test/button.test.ts +++ b/packages/button/test/button.test.ts @@ -225,5 +225,14 @@ describe('vaadin-button', () => { expect(tooltip).to.be.ok; expect(tooltip.getAttribute('text')).to.equal('Press me'); }); + + it('should not set aria-describedby', async () => { + const tooltip = button.querySelector('vaadin-tooltip[slot="tooltip"]')! as any; + await nextUpdate(tooltip); + + expect(button.hasAttribute('auto-tooltip')).to.be.true; + expect(tooltip.ariaTarget).to.equal(null); + expect(button.hasAttribute('aria-describedby')).to.be.false; + }); }); });