Skip to content

Commit b4031cf

Browse files
feat: add tooltip support to vaadin-side-nav-item (#10008) (#10050)
Co-authored-by: Herberts <80950643+HerbertsVaadin@users.noreply.github.com>
1 parent 98247ed commit b4031cf

File tree

4 files changed

+163
-8
lines changed

4 files changed

+163
-8
lines changed

packages/side-nav/src/vaadin-side-nav-item.js

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@
66
import { html, LitElement } from 'lit';
77
import { ifDefined } from 'lit/directives/if-defined.js';
88
import { DisabledMixin } from '@vaadin/a11y-base/src/disabled-mixin.js';
9+
import { screenReaderOnly } from '@vaadin/a11y-base/src/styles/sr-only-styles.js';
910
import { defineCustomElement } from '@vaadin/component-base/src/define.js';
1011
import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
1112
import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js';
13+
import { TooltipController } from '@vaadin/component-base/src/tooltip-controller.js';
1214
import { matchPaths } from '@vaadin/component-base/src/url-utils.js';
1315
import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
1416
import { location } from './location.js';
@@ -171,11 +173,16 @@ class SideNavItem extends SideNavChildrenMixin(DisabledMixin(ElementMixin(Themab
171173
type: Boolean,
172174
value: false,
173175
},
176+
177+
/** @private */
178+
__tooltipText: {
179+
type: String,
180+
},
174181
};
175182
}
176183

177184
static get styles() {
178-
return [sideNavItemBaseStyles];
185+
return [sideNavItemBaseStyles, screenReaderOnly];
179186
}
180187

181188
constructor() {
@@ -243,7 +250,7 @@ class SideNavItem extends SideNavChildrenMixin(DisabledMixin(ElementMixin(Themab
243250
/** @protected */
244251
render() {
245252
return html`
246-
<div part="content" @click="${this._onContentClick}">
253+
<div id="content" part="content" @click="${this._onContentClick}">
247254
<a
248255
id="link"
249256
?disabled="${this.disabled}"
@@ -256,6 +263,7 @@ class SideNavItem extends SideNavChildrenMixin(DisabledMixin(ElementMixin(Themab
256263
>
257264
<slot name="prefix"></slot>
258265
<slot></slot>
266+
<div class="sr-only">${this.__tooltipText}</div>
259267
<slot name="suffix"></slot>
260268
</a>
261269
<button
@@ -271,9 +279,30 @@ class SideNavItem extends SideNavChildrenMixin(DisabledMixin(ElementMixin(Themab
271279
<slot name="children"></slot>
272280
</ul>
273281
<div hidden id="i18n">${this.__effectiveI18n.toggle}</div>
282+
<slot name="tooltip"></slot>
274283
`;
275284
}
276285

286+
/** @protected */
287+
ready() {
288+
super.ready();
289+
290+
this._tooltipController = new TooltipController(this);
291+
this._tooltipController.setTarget(this.$.content);
292+
this._tooltipController.setAriaTarget(null);
293+
this._tooltipController.addEventListener('tooltip-changed', (event) => {
294+
const { node } = event.detail;
295+
if (node) {
296+
const overlay = node._overlayElement;
297+
this.__tooltipText = overlay ? overlay.textContent.trim() : '';
298+
node.setAttribute('aria-hidden', 'true');
299+
} else {
300+
this.__tooltipText = '';
301+
}
302+
});
303+
this.addController(this._tooltipController);
304+
}
305+
277306
/** @private */
278307
_onButtonClick(event) {
279308
// Prevent the event from being handled

packages/side-nav/test/dom/__snapshots__/side-nav-item.test.snap.js

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,10 @@ snapshots["vaadin-side-nav-item item path"] =
176176
/* end snapshot vaadin-side-nav-item item path */
177177

178178
snapshots["vaadin-side-nav-item shadow default"] =
179-
`<div part="content">
179+
`<div
180+
id="content"
181+
part="content"
182+
>
180183
<a
181184
aria-current="false"
182185
id="link"
@@ -187,6 +190,8 @@ snapshots["vaadin-side-nav-item shadow default"] =
187190
</slot>
188191
<slot>
189192
</slot>
193+
<div class="sr-only">
194+
</div>
190195
<slot name="suffix">
191196
</slot>
192197
</a>
@@ -213,11 +218,16 @@ snapshots["vaadin-side-nav-item shadow default"] =
213218
>
214219
Toggle child items
215220
</div>
221+
<slot name="tooltip">
222+
</slot>
216223
`;
217224
/* end snapshot vaadin-side-nav-item shadow default */
218225

219226
snapshots["vaadin-side-nav-item shadow expanded"] =
220-
`<div part="content">
227+
`<div
228+
id="content"
229+
part="content"
230+
>
221231
<a
222232
aria-current="false"
223233
id="link"
@@ -228,6 +238,8 @@ snapshots["vaadin-side-nav-item shadow expanded"] =
228238
</slot>
229239
<slot>
230240
</slot>
241+
<div class="sr-only">
242+
</div>
231243
<slot name="suffix">
232244
</slot>
233245
</a>
@@ -253,11 +265,16 @@ snapshots["vaadin-side-nav-item shadow expanded"] =
253265
>
254266
Toggle child items
255267
</div>
268+
<slot name="tooltip">
269+
</slot>
256270
`;
257271
/* end snapshot vaadin-side-nav-item shadow expanded */
258272

259273
snapshots["vaadin-side-nav-item shadow current"] =
260-
`<div part="content">
274+
`<div
275+
id="content"
276+
part="content"
277+
>
261278
<a
262279
aria-current="page"
263280
href=""
@@ -269,6 +286,8 @@ snapshots["vaadin-side-nav-item shadow current"] =
269286
</slot>
270287
<slot>
271288
</slot>
289+
<div class="sr-only">
290+
</div>
272291
<slot name="suffix">
273292
</slot>
274293
</a>
@@ -294,11 +313,16 @@ snapshots["vaadin-side-nav-item shadow current"] =
294313
>
295314
Toggle child items
296315
</div>
316+
<slot name="tooltip">
317+
</slot>
297318
`;
298319
/* end snapshot vaadin-side-nav-item shadow current */
299320

300321
snapshots["vaadin-side-nav-item shadow path"] =
301-
`<div part="content">
322+
`<div
323+
id="content"
324+
part="content"
325+
>
302326
<a
303327
aria-current="false"
304328
href="path"
@@ -310,6 +334,8 @@ snapshots["vaadin-side-nav-item shadow path"] =
310334
</slot>
311335
<slot>
312336
</slot>
337+
<div class="sr-only">
338+
</div>
313339
<slot name="suffix">
314340
</slot>
315341
</a>
@@ -336,11 +362,16 @@ snapshots["vaadin-side-nav-item shadow path"] =
336362
>
337363
Toggle child items
338364
</div>
365+
<slot name="tooltip">
366+
</slot>
339367
`;
340368
/* end snapshot vaadin-side-nav-item shadow path */
341369

342370
snapshots["vaadin-side-nav-item shadow null path"] =
343-
`<div part="content">
371+
`<div
372+
id="content"
373+
part="content"
374+
>
344375
<a
345376
aria-current="false"
346377
id="link"
@@ -351,6 +382,8 @@ snapshots["vaadin-side-nav-item shadow null path"] =
351382
</slot>
352383
<slot>
353384
</slot>
385+
<div class="sr-only">
386+
</div>
354387
<slot name="suffix">
355388
</slot>
356389
</a>
@@ -377,11 +410,16 @@ snapshots["vaadin-side-nav-item shadow null path"] =
377410
>
378411
Toggle child items
379412
</div>
413+
<slot name="tooltip">
414+
</slot>
380415
`;
381416
/* end snapshot vaadin-side-nav-item shadow null path */
382417

383418
snapshots["vaadin-side-nav-item shadow i18n"] =
384-
`<div part="content">
419+
`<div
420+
id="content"
421+
part="content"
422+
>
385423
<a
386424
aria-current="false"
387425
id="link"
@@ -392,6 +430,8 @@ snapshots["vaadin-side-nav-item shadow i18n"] =
392430
</slot>
393431
<slot>
394432
</slot>
433+
<div class="sr-only">
434+
</div>
395435
<slot name="suffix">
396436
</slot>
397437
</a>
@@ -418,6 +458,8 @@ snapshots["vaadin-side-nav-item shadow i18n"] =
418458
>
419459
Toggle children
420460
</div>
461+
<slot name="tooltip">
462+
</slot>
421463
`;
422464
/* end snapshot vaadin-side-nav-item shadow i18n */
423465

test/integration/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
"@vaadin/popover": "24.9.0-alpha1",
3939
"@vaadin/radio-group": "24.9.0-alpha1",
4040
"@vaadin/select": "24.9.0-alpha1",
41+
"@vaadin/side-nav": "24.9.0-alpha1",
4142
"@vaadin/tabs": "24.9.0-alpha1",
4243
"@vaadin/test-runner-commands": "24.9.0-alpha1",
4344
"@vaadin/testing-helpers": "^1.1.0",
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { expect } from '@vaadin/chai-plugins';
2+
import { fixtureSync, nextRender } from '@vaadin/testing-helpers';
3+
import './not-animated-styles.js';
4+
import '@vaadin/side-nav/src/vaadin-side-nav-item.js';
5+
import { Tooltip } from '@vaadin/tooltip/src/vaadin-tooltip.js';
6+
import { mouseenter, mouseleave } from '@vaadin/tooltip/test/helpers.js';
7+
8+
describe('side-nav-item with tooltip', () => {
9+
let item, tooltip, tooltipOverlay, srOnly;
10+
11+
before(() => {
12+
Tooltip.setDefaultFocusDelay(0);
13+
Tooltip.setDefaultHoverDelay(0);
14+
Tooltip.setDefaultHideDelay(0);
15+
});
16+
17+
beforeEach(async () => {
18+
item = fixtureSync(`
19+
<vaadin-side-nav-item path="/parent">
20+
Parent
21+
<vaadin-tooltip slot="tooltip" text="Tooltip text"></vaadin-tooltip>
22+
<vaadin-side-nav-item path="/parent/child" slot="children">Child</vaadin-side-nav-item>
23+
</vaadin-side-nav-item>
24+
`);
25+
await nextRender();
26+
tooltip = item.querySelector('vaadin-tooltip');
27+
tooltipOverlay = tooltip.shadowRoot.querySelector('vaadin-tooltip-overlay');
28+
srOnly = item.shadowRoot.querySelector('.sr-only');
29+
});
30+
31+
it('should set tooltip target to the item content part', () => {
32+
expect(tooltip.target).to.equal(item.$.content);
33+
});
34+
35+
it('should set tooltip ariaTarget to null', () => {
36+
expect(tooltip.ariaTarget).to.be.null;
37+
});
38+
39+
it('should set aria-hidden: none on the tooltip', () => {
40+
expect(tooltip.getAttribute('aria-hidden')).to.equal('true');
41+
});
42+
43+
it('should toggle tooltip on item content mouseenter', () => {
44+
mouseenter(item.$.content);
45+
expect(tooltipOverlay.opened).to.be.true;
46+
47+
mouseleave(item.$.content);
48+
expect(tooltipOverlay.opened).to.be.false;
49+
});
50+
51+
it('should not show tooltip on child item mouseenter', async () => {
52+
item.click();
53+
const child = item.querySelector('vaadin-side-nav-item');
54+
mouseenter(child.$.content);
55+
await nextRender();
56+
expect(tooltipOverlay.opened).to.be.not.ok;
57+
});
58+
59+
it('should use tooltip text for the sr-only element text content', async () => {
60+
expect(srOnly.textContent).to.equal(tooltip.text);
61+
62+
tooltip.text = 'Other text';
63+
await nextRender();
64+
65+
expect(srOnly.textContent).to.equal('Other text');
66+
});
67+
68+
it('should use tooltip generator for the sr-only element text content', async () => {
69+
expect(srOnly.textContent).to.equal(tooltip.text);
70+
71+
tooltip.generator = () => 'Other text';
72+
await nextRender();
73+
74+
expect(srOnly.textContent).to.equal('Other text');
75+
});
76+
77+
it('should clear the sr-only element content when tooltip is removed', async () => {
78+
tooltip.remove();
79+
await nextRender();
80+
81+
expect(srOnly.textContent).to.equal('');
82+
});
83+
});

0 commit comments

Comments
 (0)