Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
75 changes: 73 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,44 @@
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.setAttribute('text', text);

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

defineCustomElement(Badge);
Expand Down
20 changes: 20 additions & 0 deletions packages/badge/test/badge.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,4 +116,24 @@ 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');
});
});
});
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
69 changes: 65 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,78 @@
<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.setAttribute('text', text);

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

defineCustomElement(Button);
Expand Down
20 changes: 20 additions & 0 deletions packages/button/test/button.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,4 +206,24 @@ describe('vaadin-button', () => {
expect(document.activeElement).to.equal(lastGlobalFocusable);
});
});

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