Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/badge/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down
7 changes: 7 additions & 0 deletions packages/badge/src/vaadin-badge.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand All @@ -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:
*
Expand All @@ -57,6 +59,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.
*/
Expand Down
76 changes: 74 additions & 2 deletions packages/badge/src/vaadin-badge.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -11,6 +12,7 @@
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';
Expand All @@ -28,6 +30,7 @@
* ---------|-------------
* (none) | Default slot for the badge text content
* `icon` | Slot for an icon to place before the text
* `tooltip` | Slot for a tooltip
*
* ### Styling
*
Expand All @@ -46,6 +49,7 @@
* `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:
*
Expand Down Expand Up @@ -93,6 +97,16 @@
number: {
type: Number,
},

/**
* When enabled, hides the content visually and shows it in a tooltip.
*/
autoTooltip: {
type: Boolean,
value: false,
reflectToAttribute: true,
sync: true,
},
};
}

Expand All @@ -108,9 +122,10 @@
<slot name="icon"></slot>
</div>
<div part="number" class="${classMap({ 'sr-only': iconOnly || dot })}">${this.number}</div>
<div part="content" class="${classMap({ 'sr-only': numberOnly || iconOnly || dot })}">
<slot></slot>
<div part="content" class="${classMap({ 'sr-only': numberOnly || iconOnly || dot || this.autoTooltip })}">
<slot @slotchange="${this.__updateAutoTooltip}"></slot>
</div>
<slot name="tooltip" @slotchange="${this.__updateAutoTooltip}"></slot>
`;
}

Expand All @@ -123,6 +138,24 @@
}
}

/** @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();
Expand All @@ -137,6 +170,45 @@
this.toggleAttribute('has-icon', currentNodes.length > 0);
});
}

/** @private */
__getContentText() {
const slot = this.shadowRoot.querySelector('slot:not([name])');
return slot
? 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');

Check warning on line 188 in packages/badge/src/vaadin-badge.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this "!==" check; it will always be true. Did you mean to use "!="?

See more on https://sonarcloud.io/project/issues?id=vaadin_web-components&issues=AZ4g2XBVJQwQJWo_eerQ&open=AZ4g2XBVJQwQJWo_eerQ&pullRequest=11747
}

/** @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.ariaTarget = null;
}

this.__autoTooltip.setAttribute('text', text);

if (!this.__autoTooltip.isConnected) {
this.appendChild(this.__autoTooltip);
}
}
}

defineCustomElement(Badge);
Expand Down
29 changes: 29 additions & 0 deletions packages/badge/test/badge.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,4 +116,33 @@ describe('vaadin-badge', () => {
expect(badge.hasAttribute('has-icon')).to.be.false;
});
});

describe('auto-tooltip', () => {
beforeEach(async () => {
badge = fixtureSync('<vaadin-badge auto-tooltip>New</vaadin-badge>');
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');
});

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;
});
});
});
10 changes: 10 additions & 0 deletions packages/badge/test/dom/__snapshots__/badge.test.snap.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ snapshots["vaadin-badge shadow default"] =
<slot>
</slot>
</div>
<slot name="tooltip">
</slot>
`;
/* end snapshot vaadin-badge shadow default */

Expand All @@ -46,6 +48,8 @@ snapshots["vaadin-badge shadow number"] =
<slot>
</slot>
</div>
<slot name="tooltip">
</slot>
`;
/* end snapshot vaadin-badge shadow number */

Expand All @@ -69,6 +73,8 @@ snapshots["vaadin-badge shadow dot"] =
<slot>
</slot>
</div>
<slot name="tooltip">
</slot>
`;
/* end snapshot vaadin-badge shadow dot */

Expand All @@ -89,6 +95,8 @@ snapshots["vaadin-badge shadow icon-only"] =
<slot>
</slot>
</div>
<slot name="tooltip">
</slot>
`;
/* end snapshot vaadin-badge shadow icon-only */

Expand All @@ -109,6 +117,8 @@ snapshots["vaadin-badge shadow number-only"] =
<slot>
</slot>
</div>
<slot name="tooltip">
</slot>
`;
/* end snapshot vaadin-badge shadow number-only */

1 change: 1 addition & 0 deletions packages/button/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down
5 changes: 5 additions & 0 deletions packages/button/src/vaadin-button.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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".
*
Expand Down
70 changes: 66 additions & 4 deletions packages/button/src/vaadin-button.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -68,7 +71,7 @@
}

static get styles() {
return buttonStyles;
return [buttonStyles, screenReaderOnly];
}

static get properties() {
Expand All @@ -95,6 +98,16 @@
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,
},
};
}

Expand All @@ -105,30 +118,79 @@
<span part="prefix" aria-hidden="true">
<slot name="prefix"></slot>
</span>
<span part="label">
<slot></slot>
<span part="label" class="${classMap({ 'sr-only': this.autoTooltip })}">
<slot @slotchange="${this.__updateAutoTooltip}"></slot>
</span>
<span part="suffix" aria-hidden="true">
<slot name="suffix"></slot>
</span>

<slot name="tooltip"></slot>
<slot name="tooltip" @slotchange="${this.__updateAutoTooltip}"></slot>
</div>
`;
}

/** @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
? 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');

Check warning on line 170 in packages/button/src/vaadin-button.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this "!==" check; it will always be true. Did you mean to use "!="?

See more on https://sonarcloud.io/project/issues?id=vaadin_web-components&issues=AZ4g2XG0JQwQJWo_eerR&open=AZ4g2XG0JQwQJWo_eerR&pullRequest=11747
}

/** @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.ariaTarget = null;
}

this.__autoTooltip.setAttribute('text', text);

if (!this.__autoTooltip.isConnected) {
this.appendChild(this.__autoTooltip);
}
}
}

defineCustomElement(Button);
Expand Down
Loading
Loading