Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(material/badge): insert inline description for non-interactive hosts #27025

Merged
merged 1 commit into from
May 9, 2023

Conversation

jelbourn
Copy link
Member

@jelbourn jelbourn commented May 3, 2023

The badge current applies aria-describedby to surface the developer-provided description. However, assistive technology generally doesn't read these description on non-interactive elements. So, we can take advantage of our handy InteractivityChecker and do something different if the host is not focusable; we add the description inline as the next sibling with .cdk-visually-hidden.

Fixes #26190

@jelbourn jelbourn added Accessibility This issue is related to accessibility (a11y) area: material/badge labels May 3, 2023
@jelbourn jelbourn requested a review from crisbeto May 3, 2023 23:01
@@ -238,15 +254,37 @@ export class MatBadge extends _MatBadgeBase implements OnInit, OnDestroy, CanDis
this._badgeElement.textContent = newContentNormalized;
}

if (!this._isHostInteractive()) {
if (!this._inlineBadgeDescription) {
this._inlineBadgeDescription = this._renderer.createElement('span') as HTMLSpanElement;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need to go through the renderer for this. The badge uses the renderer so view encapsulation works correctly, but this element doesn't need it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed - I just did it since the component was already using it anyway.

this._inlineBadgeDescription.textContent = this.description;

const host = this._elementRef.nativeElement;
host.parentElement?.insertBefore(this._inlineBadgeDescription, host.nextSibling);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the nextSibling here can be undefined.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to append to the badge element

@@ -238,15 +254,37 @@ export class MatBadge extends _MatBadgeBase implements OnInit, OnDestroy, CanDis
this._badgeElement.textContent = newContentNormalized;
}

if (!this._isHostInteractive()) {
Copy link
Member

@crisbeto crisbeto May 4, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fires in ngOnInit and whenever the content changes. Isn't it possible for the element to become non-interactive between those two, e.g. if it's on a button that becomes disabled?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good catch- I took a look at badge usages in Google, and think this is a small enough edge case that we don't need to worry about it. The vast majority of elements with matBadge don't set disabled, and those that do almost all also set matBadgeHidden based on the same condition. I've added a long comment to capture this in the code.

this._inlineBadgeDescription.textContent = this.description;

const host = this._elementRef.nativeElement;
host.parentElement?.insertBefore(this._inlineBadgeDescription, host.nextSibling);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this have to be a sibling of the badge? Maybe we can add the element inside the badge?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed - I had been worried that adding this as a child would potentially insert the description in an awkward place (e.g., breaking up a block of text), but upon further thought, that does more accurately reflect how the badge is applied by the developer.

private _updateInlineDescription() {
// Create the inline description element if it doesn't exist
if (!this._inlineBadgeDescription) {
this._inlineBadgeDescription = document.createElement('span');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should use the document from the DOCUMENT token since it accounts for SSR.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Copy link
Member

@crisbeto crisbeto left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@crisbeto crisbeto added the action: merge The PR is ready for merge by the caretaker label May 5, 2023
@jelbourn jelbourn added the target: patch This PR is targeted for the next patch release label May 5, 2023
The badge current applies `aria-describedby` to surface the
developer-provided description. However, assistive technology generally
doesn't read these description on non-interactive elements. So, we can
take advantage of our handy `InteractivityChecker` and do something
different if the host is not focusable; we add the description inline as
the next sibling with `.cdk-visually-hidden`.

Fixes angular#26190
@angular-robot angular-robot bot merged commit f0a5008 into angular:main May 9, 2023
7 checks passed
angular-robot bot pushed a commit that referenced this pull request May 9, 2023
…sts (#27025)

The badge current applies `aria-describedby` to surface the
developer-provided description. However, assistive technology generally
doesn't read these description on non-interactive elements. So, we can
take advantage of our handy `InteractivityChecker` and do something
different if the host is not focusable; we add the description inline as
the next sibling with `.cdk-visually-hidden`.

Fixes #26190

(cherry picked from commit f0a5008)
@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Jun 9, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Accessibility This issue is related to accessibility (a11y) action: merge The PR is ready for merge by the caretaker area: material/badge target: patch This PR is targeted for the next patch release
2 participants