mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
fix(a11y): don't set aria description if it's the same as the node's aria-label (#15250)
As per a recent discussion, reworks the `AriaDescriber` not to set the `aria-describedby`, if the message will be exactly the same as an existing `aria-label` on the element. This avoids the same text being read out twice. Fixes #15048.
This commit is contained in:
committed by
Jeremy Elbourn
parent
66d38fc596
commit
1bc8bc4b91
@@ -132,6 +132,13 @@ describe('AriaDescriber', () => {
|
|||||||
// Use `querySelectorAll` with an attribute since `getElementById` will stop at the first match.
|
// Use `querySelectorAll` with an attribute since `getElementById` will stop at the first match.
|
||||||
expect(document.querySelectorAll(`[id='${MESSAGES_CONTAINER_ID}']`).length).toBe(1);
|
expect(document.querySelectorAll(`[id='${MESSAGES_CONTAINER_ID}']`).length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should not describe messages that match up with the aria-label of the element', () => {
|
||||||
|
component.element1.setAttribute('aria-label', 'Hello');
|
||||||
|
ariaDescriber.describe(component.element1, 'Hello');
|
||||||
|
ariaDescriber.describe(component.element1, 'Hi');
|
||||||
|
expectMessages(['Hi']);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function getMessagesContainer() {
|
function getMessagesContainer() {
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ export class AriaDescriber implements OnDestroy {
|
|||||||
|
|
||||||
/** Removes the host element's aria-describedby reference to the message element. */
|
/** Removes the host element's aria-describedby reference to the message element. */
|
||||||
removeDescription(hostElement: Element, message: string) {
|
removeDescription(hostElement: Element, message: string) {
|
||||||
if (!this._canBeDescribed(hostElement, message)) {
|
if (!this._isElementNode(hostElement)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,10 +218,22 @@ export class AriaDescriber implements OnDestroy {
|
|||||||
|
|
||||||
/** Determines whether a message can be described on a particular element. */
|
/** Determines whether a message can be described on a particular element. */
|
||||||
private _canBeDescribed(element: Element, message: string): boolean {
|
private _canBeDescribed(element: Element, message: string): boolean {
|
||||||
return element.nodeType === this._document.ELEMENT_NODE && message != null &&
|
if (!this._isElementNode(element)) {
|
||||||
!!`${message}`.trim();
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const trimmedMessage = message == null ? '' : `${message}`.trim();
|
||||||
|
const ariaLabel = element.getAttribute('aria-label');
|
||||||
|
|
||||||
|
// We shouldn't set descriptions if they're exactly the same as the `aria-label` of the element,
|
||||||
|
// because screen readers will end up reading out the same text twice in a row.
|
||||||
|
return trimmedMessage ? (!ariaLabel || ariaLabel.trim() !== trimmedMessage) : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Checks whether a node is an Element node. */
|
||||||
|
private _isElementNode(element: Node): element is Element {
|
||||||
|
return element.nodeType === this._document.ELEMENT_NODE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user