Skip to content
This repository was archived by the owner on Sep 5, 2024. It is now read-only.

Commit 37f1535

Browse files
committed
fix(icon): providing empty alt or aria-label attributes do not hide them from a11y
- change behavior around how `aria-label` and `aria-hidden` is applied to match documentation - clarify documentation around `alt` and `aria-label` behavior - fix/improve Closure types - remove unused variable and out of date comments - replace blacklist with block-list in comments Fixes #10721
1 parent 3d98b6e commit 37f1535

File tree

4 files changed

+53
-50
lines changed

4 files changed

+53
-50
lines changed

src/components/icon/demoFontIconsWithClassnames/index.html

+9-9
Original file line numberDiff line numberDiff line change
@@ -8,28 +8,28 @@
88
<md-icon md-font-icon="{{ font.name }}"
99
ng-style="{color: !font.theme && font.color, 'font-size': it.size + 'px', height: it.size + 'px'}"
1010
ng-class="font.theme"
11-
aria-label="{{ font.name + it.size }}"
12-
class="step "></md-icon>
11+
aria-label="{{ font.name + '-' + it.size }}"
12+
class="step"></md-icon>
1313
</div>
1414
<div class="preview-scale">
15-
<span class="step" style="padding-left:{{ it.padding }}px">{{ it.size }}px</span>
15+
<span class="step" style="{{ 'padding-left: ' + it.padding + 'px'}}">{{ it.size }}px</span>
1616
</div>
1717
</div>
1818
</div>
1919

2020
<!-- For this demo, include the font-faces needed by mdIcon above -->
2121
<style>
2222
@font-face {
23-
font-family:"icomoon";
24-
src:url("https://cdn.rawgit.com/angular/material/master/docs/app/fonts/icomoon.eot");
25-
font-weight:normal;
26-
font-style:normal;
23+
font-family: "icomoon";
24+
src: url("https://cdn.rawgit.com/angular/material/master/docs/app/fonts/icomoon.eot");
25+
font-weight: normal;
26+
font-style: normal;
2727
}
2828

2929
@font-face {
3030
font-family: 'icomoon';
31-
src:url('https://cdn.rawgit.com/angular/material/master/docs/app/fonts/icomoon.eot?-cmq1um');
32-
src:url('https://cdn.rawgit.com/angular/material/master/docs/app/fonts/icomoon.eot?#iefix-cmq1um') format('embedded-opentype'),
31+
src: url('https://cdn.rawgit.com/angular/material/master/docs/app/fonts/icomoon.eot?-cmq1um');
32+
src: url('https://cdn.rawgit.com/angular/material/master/docs/app/fonts/icomoon.eot?#iefix-cmq1um') format('embedded-opentype'),
3333
url('https://cdn.rawgit.com/angular/material/master/docs/app/fonts/icomoon.woff?-cmq1um') format('woff'),
3434
url('https://cdn.rawgit.com/angular/material/master/docs/app/fonts/icomoon.ttf?-cmq1um') format('truetype'),
3535
url('https://cdn.rawgit.com/angular/material/master/docs/app/fonts/icomoon.svg?-cmq1um#icomoon') format('svg');

src/components/icon/demoFontIconsWithClassnames/script.js

-3
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,6 @@ angular
2121
];
2222

2323
$scope.fonts = [].concat(iconData);
24-
25-
26-
2724
})
2825
.config(function($mdThemingProvider){
2926
// Update the theme colors to use themes on font-icons

src/components/icon/js/iconDirective.js

+37-31
Original file line numberDiff line numberDiff line change
@@ -105,22 +105,25 @@ angular
105105
* icon and to change the icon size. These settings only affect the downloaded icons.
106106
*
107107
* @param {string} md-font-icon String name of CSS icon associated with the font-face will be used
108-
* to render the icon. Requires the fonts and the named CSS styles to be preloaded.
108+
* to render the icon. Requires the fonts and the named CSS styles to be preloaded.
109109
* @param {string} md-font-set CSS style name associated with the font library; which will be assigned as
110-
* the class for the font-icon ligature. This value may also be an alias that is used to lookup the classname;
111-
* internally use `$mdIconProvider.fontSet(<alias>)` to determine the style name.
110+
* the class for the font-icon ligature. This value may also be an alias that is used to lookup the classname;
111+
* internally use `$mdIconProvider.fontSet(<alias>)` to determine the style name.
112112
* @param {string} md-svg-src String URL (or expression) used to load, cache, and display an
113-
* external SVG.
113+
* external SVG.
114114
* @param {string} md-svg-icon md-svg-icon String name used for lookup of the icon from the internal cache;
115-
* interpolated strings or expressions may also be used. Specific set names can be used with
116-
* the syntax `<set name>:<icon name>`.<br/><br/>
117-
* To use icon sets, developers are required to pre-register the sets using the `$mdIconProvider` service.
118-
* @param {string=} aria-label Labels icon for accessibility. If an empty string is provided, icon
119-
* will be hidden from accessibility layer with `aria-hidden="true"`. If there's no aria-label on the icon
120-
* nor a label on the parent element, a warning will be logged to the console.
121-
* @param {string=} alt Labels icon for accessibility. If an empty string is provided, icon
122-
* will be hidden from accessibility layer with `aria-hidden="true"`. If there's no alt on the icon
123-
* nor a label on the parent element, a warning will be logged to the console.
115+
* interpolated strings or expressions may also be used. Specific set names can be used with
116+
* the syntax `<set name>:<icon name>`.<br/><br/>
117+
* To use icon sets, developers are required to pre-register the sets using the `$mdIconProvider` service.
118+
* @param {string=} aria-label Labels the icon for accessibility. If an empty string is provided,
119+
* the icon will be hidden from the accessibility layer with `aria-hidden="true"`. If there is no
120+
* `aria-label` attribute on the icon, we check the following, in order: the `alt` attribute, the
121+
* `aria-label` from the parent element, the icon's `md-font-icon` or `md-svg-icon` string, and the
122+
* text content inside `<md-icon></md-icon>`. If none of these have any text, the icon is hidden
123+
* from the accessibility layer with `aria-hidden="true"`.
124+
* @param {string=} alt Labels the icon for accessibility. If an empty string is provided and the
125+
* icon has no `aria-label`, then the icon will be hidden from accessibility layer with
126+
* `aria-hidden="true"`.
124127
*
125128
* @usage
126129
* When using SVGs:
@@ -198,7 +201,10 @@ function mdIconDirective($mdIcon, $mdTheming, $mdAria, $sce) {
198201

199202
/**
200203
* Directive postLink
201-
* Supports embedded SVGs, font-icons, & external SVGs
204+
* Supports embedded SVGs, font-icons, & external SVGs.
205+
* @param {IScope} scope
206+
* @param {JQLite} element
207+
* @param {IAttributes} attr
202208
*/
203209
function postLink(scope, element, attr) {
204210
$mdTheming(element);
@@ -210,40 +216,40 @@ function mdIconDirective($mdIcon, $mdTheming, $mdAria, $sce) {
210216
attr.$observe('mdFontIcon', fontIconChanged);
211217
attr.$observe('mdFontSet', fontIconChanged);
212218

213-
// Keep track of the content of the svg src so we can compare against it later to see if the
214-
// attribute is static (and thus safe).
215-
var originalSvgSrc = element[0].getAttribute(attr.$attr.mdSvgSrc);
216-
217-
// If using a font-icon, then the textual name of the icon itself
218-
// provides the aria-label.
219-
220-
var attrName = attr.$normalize(attr.$attr.mdSvgIcon || attr.$attr.mdSvgSrc || '');
221-
222219
/* Provide a default accessibility role of img */
223220
if (!attr.role) {
224221
$mdAria.expect(element, 'role', 'img');
225222
/* manually update attr variable */
226223
attr.role = 'img';
227224
}
228225

226+
// If the aria-label is explicitly set to the empty string, then hide this element from the
227+
// accessibility layer.
228+
if (element[0].hasAttribute('aria-label') && attr.ariaLabel === '') {
229+
element.attr('aria-hidden', true);
230+
}
231+
229232
/* Don't process ARIA if already valid */
230233
if (attr.role === "img" && !attr.ariaHidden && !$mdAria.hasAriaLabel(element)) {
231-
var iconName;
232-
if (attr.alt) {
233-
/* Use alt text by default if available */
234+
// If the developer signals to hide this icon from the accessibility layer, do so.
235+
if (element[0].hasAttribute('alt') && attr.alt === '') {
236+
element.attr('aria-hidden', true);
237+
} else if (attr.alt) {
238+
/* Use the alt text for the aria-label by default, if available. */
234239
$mdAria.expect(element, 'aria-label', attr.alt);
235240
} else if ($mdAria.parentHasAriaLabel(element, 2)) {
236-
/* Parent has ARIA so we will assume it will describe the image */
241+
/* Parent has ARIA so we will assume it will describe the icon. */
237242
$mdAria.expect(element, 'aria-hidden', 'true');
238-
} else if (iconName = (attr.mdFontIcon || attr.mdSvgIcon || element.text())) {
239-
/* Use icon name as aria-label */
240-
$mdAria.expect(element, 'aria-label', iconName);
243+
} else if (attr.mdFontIcon || attr.mdSvgIcon || element.text()) {
244+
/* Use icon name or node's text content as the aria-label */
245+
$mdAria.expect(element, 'aria-label', attr.mdFontIcon || attr.mdSvgIcon || element.text());
241246
} else {
242-
/* No label found */
247+
/* No label found, hide this icon from the accessibility layer */
243248
$mdAria.expect(element, 'aria-hidden', 'true');
244249
}
245250
}
246251

252+
var attrName = attr.$normalize(attr.$attr.mdSvgIcon || attr.$attr.mdSvgSrc || '');
247253
if (attrName) {
248254
// Use either pre-configured SVG or URL source, respectively.
249255
attr.$observe(attrName, function(attrVal) {

src/core/services/aria/aria.js

+7-7
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,9 @@ function MdAriaService($$rAF, $log, $window, $interpolate) {
7373

7474
/**
7575
* Check if expected attribute has been specified on the target element or child
76-
* @param element
77-
* @param attrName
78-
* @param {optional} defaultValue What to set the attr to if no value is found
76+
* @param {string|JQLite} element
77+
* @param {string} attrName
78+
* @param {string=} defaultValue What to set the attr to if no value is found
7979
*/
8080
function expect(element, attrName, defaultValue) {
8181

@@ -192,8 +192,8 @@ function MdAriaService($$rAF, $log, $window, $interpolate) {
192192

193193
/**
194194
* Check if expected element's parent has aria label attribute and has valid role and tagName
195-
* @param element
196-
* @param {optional} level Number of levels deep search should be performed
195+
* @param {string|JQLite|Node & ParentNode} element
196+
* @param {number=} level Number of levels deep search should be performed
197197
*/
198198
function parentHasAriaLabel(element, level) {
199199
level = level || 1;
@@ -214,7 +214,7 @@ function MdAriaService($$rAF, $log, $window, $interpolate) {
214214
if (!hasAriaLabel(parentNode)) {
215215
return false;
216216
}
217-
/* Perform role blacklist check */
217+
/* Perform role block-list check */
218218
if (parentNode.hasAttribute('role')) {
219219
switch (parentNode.getAttribute('role').toLowerCase()) {
220220
case 'command':
@@ -236,7 +236,7 @@ function MdAriaService($$rAF, $log, $window, $interpolate) {
236236
return false;
237237
}
238238
}
239-
/* Perform tagName blacklist check */
239+
/* Perform tagName block-list check */
240240
switch (parentNode.tagName.toLowerCase()) {
241241
case 'abbr':
242242
case 'acronym':

0 commit comments

Comments
 (0)