Skip to content

Commit 8a979ba

Browse files
committed
fix(material/badge): move inline description element to be next sibling
move inline description element to be next sibling where `matBadge` was applied instead of being within the non interactive element and invisible to screen readers
1 parent 04dfc52 commit 8a979ba

File tree

2 files changed

+34
-5
lines changed

2 files changed

+34
-5
lines changed

src/material/badge/badge.spec.ts

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -243,16 +243,16 @@ describe('MatBadge', () => {
243243
badgeHostNativeElement = badgeHostDebugElement.nativeElement;
244244
});
245245

246-
it('should insert the description inline after the host', () => {
246+
it('should insert description as next sibling for host', () => {
247247
testComponent.description.set('Extra info');
248248
fixture.detectChanges();
249249

250-
const inlineDescription = badgeHostNativeElement.querySelector('.cdk-visually-hidden')!;
250+
const inlineDescription = badgeHostNativeElement.nextSibling!;
251251
expect(inlineDescription)
252252
.withContext('A visually hidden description element should exist')
253253
.toBeDefined();
254254
expect(inlineDescription.textContent)
255-
.withContext('The badge host next sibling should contain its description')
255+
.withContext('The badge next sibling should contain its description')
256256
.toBe('Extra info');
257257

258258
testComponent.description.set('Different info');
@@ -263,14 +263,34 @@ describe('MatBadge', () => {
263263
.toBe('Different info');
264264
});
265265

266-
it('should not apply aria-describedby for non-interactive hosts', () => {
266+
it('should not apply aria-describedby for non-interactive host', () => {
267267
testComponent.description.set('Extra info');
268268
fixture.detectChanges();
269269

270270
expect(badgeHostNativeElement.hasAttribute('aria-description'))
271271
.withContext('Non-interactive hosts should not have aria-describedby')
272272
.toBeFalse();
273273
});
274+
275+
it('should not insert description as next sibling if description is not provided', () => {
276+
fixture.detectChanges();
277+
278+
expect(badgeHostNativeElement.nextSibling).toBeFalsy();
279+
});
280+
281+
it('should not create multiple description elements if description changes', () => {
282+
testComponent.description.set('one');
283+
fixture.detectChanges();
284+
285+
let siblings = fixture.nativeElement.querySelectorAll('.cdk-visually-hidden');
286+
expect(siblings.length).toBe(1);
287+
288+
testComponent.description.set('two');
289+
fixture.detectChanges();
290+
291+
siblings = fixture.nativeElement.querySelectorAll('.cdk-visually-hidden');
292+
expect(siblings.length).toBe(1);
293+
});
274294
});
275295
});
276296

src/material/badge/badge.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,14 +296,23 @@ export class MatBadge implements OnInit, OnDestroy {
296296
}
297297

298298
private _updateInlineDescription() {
299+
// We do not need to need empty description element for non interactive elements.
300+
if (!this.description) return;
301+
299302
// Create the inline description element if it doesn't exist
300303
if (!this._inlineBadgeDescription) {
301304
this._inlineBadgeDescription = this._document.createElement('span');
302305
this._inlineBadgeDescription.classList.add('cdk-visually-hidden');
303306
}
304307

305308
this._inlineBadgeDescription.textContent = this.description;
306-
this._badgeElement?.appendChild(this._inlineBadgeDescription);
309+
// We want to add the inline description element right after the not interactive element that
310+
// badge is on therefore we can't use appendChild here as they will keep getting stacked in
311+
// last.
312+
this._elementRef.nativeElement.parentNode?.insertBefore(
313+
this._inlineBadgeDescription,
314+
this._elementRef.nativeElement.nextSibling,
315+
);
307316
}
308317

309318
private _removeInlineDescription() {

0 commit comments

Comments
 (0)