Skip to content
This repository was archived by the owner on Sep 5, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 163 additions & 5 deletions src/components/autocomplete/autocomplete.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1660,8 +1660,170 @@ describe('<md-autocomplete>', function() {

});

describe('accessibility', function() {
describe('Accessibility', function() {
var $timeout = null;

beforeEach(inject(function ($injector) {
$timeout = $injector.get('$timeout');
}));

it('should add the placeholder as the input\'s aria-label', function() {
var template =
'<md-autocomplete' +
' md-selected-item="selectedItem"' +
' md-search-text="searchText"' +
' md-items="item in match(searchText)"' +
' md-item-text="item.display"' +
' placeholder="placeholder">' +
' <span md-highlight-text="searchText">{{item.display}}</span>' +
'</md-autocomplete>';
var scope = createScope();
var element = compile(template, scope);
var input = element.find('input');

// Flush the initial autocomplete timeout to gather the elements.
$timeout.flush();

expect(input.attr('aria-label')).toBe('placeholder');
});

it('should add the input-aria-label as the input\'s aria-label', function() {
var template =
'<md-autocomplete' +
' md-selected-item="selectedItem"' +
' md-search-text="searchText"' +
' md-items="item in match(searchText)"' +
' md-item-text="item.display"' +
' placeholder="placeholder"' +
' input-aria-label="TestLabel">' +
'</md-autocomplete>';
var scope = createScope();
var element = compile(template, scope);
var input = element.find('input');

// Flush the initial autocomplete timeout to gather the elements.
$timeout.flush();

expect(input.attr('aria-label')).toBe('TestLabel');
});

it('should add the input-aria-labelledby to the input', function() {
var template =
'<label id="test-label">Test Label</label>' +
'<md-autocomplete' +
' md-selected-item="selectedItem"' +
' md-search-text="searchText"' +
' md-items="item in match(searchText)"' +
' md-item-text="item.display"' +
' placeholder="placeholder"' +
' input-aria-labelledby="test-label">' +
'</md-autocomplete>';
var scope = createScope();
var element = compile(template, scope);
var input = element.find('input');

// Flush the initial autocomplete timeout to gather the elements.
$timeout.flush();

expect(input.attr('aria-label')).not.toExist();
expect(input.attr('aria-labelledby')).toBe('test-label');
});

it('should add the input-aria-describedby to the input', function() {
var template =
'<md-autocomplete' +
' md-selected-item="selectedItem"' +
' md-search-text="searchText"' +
' md-items="item in match(searchText)"' +
' md-item-text="item.display"' +
' placeholder="placeholder"' +
' input-aria-describedby="test-desc">' +
'</md-autocomplete>' +
'<div id="test-desc">Test Description</div>';
var scope = createScope();
var element = compile(template, scope);
var input = element.find('input');

// Flush the initial autocomplete timeout to gather the elements.
$timeout.flush();

expect(input.attr('aria-describedby')).toBe('test-desc');
});

it('should not break an aria-label on the autocomplete when using input-aria-label or aria-describedby', function() {
var template =
'<md-autocomplete' +
' md-selected-item="selectedItem"' +
' md-search-text="searchText"' +
' md-items="item in match(searchText)"' +
' md-item-text="item.display"' +
' placeholder="placeholder"' +
' aria-label="TestAriaLabel"' +
' input-aria-label="TestLabel"' +
' input-aria-describedby="test-desc">' +
'</md-autocomplete>' +
'<div id="test-desc">Test Description</div>';
var scope = createScope();
var element = compile(template, scope);
var autocomplete = element[0];
var input = element.find('input');

// Flush the initial autocomplete timeout to gather the elements.
$timeout.flush();

expect(input.attr('aria-label')).toBe('TestLabel');
expect(input.attr('aria-describedby')).toBe('test-desc');
expect(autocomplete.getAttribute('aria-label')).toBe('TestAriaLabel');
});

it('should not break an aria-label on the autocomplete', function() {
var template =
'<md-autocomplete' +
' md-selected-item="selectedItem"' +
' md-search-text="searchText"' +
' md-items="item in match(searchText)"' +
' md-item-text="item.display"' +
' placeholder="placeholder"' +
' aria-label="TestAriaLabel">' +
'</md-autocomplete>';
var scope = createScope();
var element = compile(template, scope);
var input = element.find('input');

// Flush the initial autocomplete timeout to gather the elements.
$timeout.flush();

expect(input.attr('aria-label')).toBe('placeholder');
expect(element.attr('aria-label')).toBe('TestAriaLabel');
});

it('should not break an aria-label on the autocomplete when using input-aria-labelledby', function() {
var template =
'<label id="test-label">Test Label</label>' +
'<md-autocomplete' +
' md-selected-item="selectedItem"' +
' md-search-text="searchText"' +
' md-items="item in match(searchText)"' +
' md-item-text="item.display"' +
' placeholder="placeholder"' +
' aria-label="TestAriaLabel"' +
' input-aria-labelledby="test-label">' +
'</md-autocomplete>';
var scope = createScope();
var element = compile(template, scope);
var autocomplete = element[1];
var input = element.find('input');

// Flush the initial autocomplete timeout to gather the elements.
$timeout.flush();

expect(input.attr('aria-label')).not.toExist();
expect(input.attr('aria-labelledby')).toBe('test-label');
expect(autocomplete.getAttribute('aria-label')).toBe('TestAriaLabel');
});
});

describe('Accessibility Announcements', function() {
var $mdLiveAnnouncer, $timeout, $mdConstant = null;
var liveEl, scope, element, ctrl = null;

Expand Down Expand Up @@ -1691,7 +1853,6 @@ describe('<md-autocomplete>', function() {
}));

it('should announce count on dropdown open', function() {

ctrl.focus();
waitForVirtualRepeat();

Expand All @@ -1701,7 +1862,6 @@ describe('<md-autocomplete>', function() {
});

it('should announce count and selection on dropdown open', function() {

// Manually enable md-autoselect for the autocomplete.
ctrl.index = 0;

Expand All @@ -1715,7 +1875,6 @@ describe('<md-autocomplete>', function() {
});

it('should announce the selection when using the arrow keys', function() {

ctrl.focus();
waitForVirtualRepeat();

Expand Down Expand Up @@ -1769,7 +1928,6 @@ describe('<md-autocomplete>', function() {
});

it('should announce the count when matches change', function() {

ctrl.focus();
waitForVirtualRepeat();

Expand Down
6 changes: 4 additions & 2 deletions src/components/autocomplete/demoBasicUsage/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<p id="autocompleteDescription">
Use <code>md-autocomplete</code> to search for matches from local or remote data sources.
</p>
<label id="favoriteStateLabel">Favorite State</label>
<md-autocomplete
ng-disabled="ctrl.isDisabled"
md-no-cache="ctrl.noCache"
Expand All @@ -14,8 +15,9 @@
md-items="item in ctrl.querySearch(ctrl.searchText)"
md-item-text="item.display"
md-min-length="0"
placeholder="What is your favorite US state?"
aria-describedby="autocompleteDescription autocompleteDetailedDescription">
placeholder="Ex. Alaska"
input-aria-labelledby="favoriteStateLabel"
input-aria-describedby="autocompleteDetailedDescription">
<md-item-template>
<span md-highlight-text="ctrl.searchText" md-highlight-flags="^i">{{item.display}}</span>
</md-item-template>
Expand Down
1 change: 1 addition & 0 deletions src/components/autocomplete/demoCustomTemplate/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
md-items="item in ctrl.querySearch(ctrl.searchText)"
md-item-text="item.name"
md-min-length="0"
input-aria-label="Current Repository"
placeholder="Pick an Angular repository"
md-menu-class="autocomplete-custom-template"
md-menu-container-class="custom-container">
Expand Down
2 changes: 1 addition & 1 deletion src/components/autocomplete/demoFloatingLabel/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
md-item-text="item.display"
md-require-match=""
md-floating-label="Favorite state"
aria-describedby="favoriteStateDescription">
input-aria-describedby="favoriteStateDescription">
<md-item-template>
<span md-highlight-text="ctrl.searchText">{{item.display}}</span>
</md-item-template>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,17 @@ <h2>Autocomplete Dialog Example</h2>
<md-dialog-content ng-cloak>
<div class="md-dialog-content">
<form ng-submit="$event.preventDefault()">
<p>Use <code>md-autocomplete</code> to search for matches from local or remote data sources.</p>
<p>
Use <code>md-autocomplete</code> to search for matches from local or remote data sources.
</p>
<md-autocomplete
md-selected-item="ctrl.selectedItem"
md-search-text="ctrl.searchText"
md-items="item in ctrl.querySearch(ctrl.searchText)"
md-item-text="item.display"
md-min-length="0"
placeholder="What is your favorite US state?"
input-aria-label="Favorite State"
md-autofocus="">
<md-item-template>
<span md-highlight-text="ctrl.searchText" md-highlight-flags="^i">{{item.display}}</span>
Expand Down
11 changes: 11 additions & 0 deletions src/components/autocomplete/js/autocompleteController.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,17 @@ function MdAutocompleteCtrl ($scope, $element, $mdUtil, $mdConstant, $mdTheming,
if ($scope.inputAriaDescribedBy) {
elements.input.setAttribute('aria-describedby', $scope.inputAriaDescribedBy);
}
if (!$scope.floatingLabel) {
if ($scope.inputAriaLabel) {
elements.input.setAttribute('aria-label', $scope.inputAriaLabel);
} else if ($scope.inputAriaLabelledBy) {
elements.input.setAttribute('aria-labelledby', $scope.inputAriaLabelledBy);
} else if ($scope.placeholder) {
// If no aria-label or aria-labelledby references are defined, then just label using the
// placeholder.
elements.input.setAttribute('aria-label', $scope.placeholder);
}
}
});
}

Expand Down
45 changes: 31 additions & 14 deletions src/components/autocomplete/js/autocompleteDirective.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ angular
* no matches were found. You can do this by wrapping your template in `md-item-template` and
* adding a tag for `md-not-found`. An example of this is shown below.
*
* To reset the displayed value you must clear both values for `md-search-text` and `md-selected-item`.
* To reset the displayed value you must clear both values for `md-search-text` and
* `md-selected-item`.
*
* ### Validation
*
Expand Down Expand Up @@ -96,11 +97,14 @@ angular
* make suggestions.
* @param {number=} md-delay Specifies the amount of time (in milliseconds) to wait before looking
* for results.
* @param {boolean=} md-clear-button Whether the clear button for the autocomplete input should show up or not.
* @param {boolean=} md-autofocus If true, the autocomplete will be automatically focused when a `$mdDialog`,
* `$mdBottomsheet` or `$mdSidenav`, which contains the autocomplete, is opening. <br/><br/>
* @param {boolean=} md-clear-button Whether the clear button for the autocomplete input should show
* up or not.
* @param {boolean=} md-autofocus If true, the autocomplete will be automatically focused when a
* `$mdDialog`, `$mdBottomsheet` or `$mdSidenav`, which contains the autocomplete, is opening.
* <br/><br/>
* Also the autocomplete will immediately focus the input element.
* @param {boolean=} md-no-asterisk When present, asterisk will not be appended to the floating label.
* @param {boolean=} md-no-asterisk When present, asterisk will not be appended to the floating
* label.
* @param {boolean=} md-autoselect If set to true, the first item will be automatically selected
* in the dropdown upon open.
* @param {string=} md-input-name The name attribute given to the input element to be used with
Expand All @@ -111,7 +115,7 @@ angular
* @param {string=} md-input-class This will be applied to the input for styling. This attribute
* is only valid when a `md-floating-label` is defined.
* @param {string=} md-floating-label This will add a floating label to autocomplete and wrap it in
* `md-input-container`
* `md-input-container`.
* @param {string=} md-select-on-focus When present the inputs text will be automatically selected
* on focus.
* @param {string=} md-input-id An ID to be added to the input element.
Expand All @@ -135,6 +139,15 @@ angular
* content of these elements at the end of announcing that the autocomplete has been selected
* and describing its current state. The descriptive elements do not need to be visible on the
* page.
* @param {string=} input-aria-labelledby A space-separated list of element IDs. The ideal use case
* is that this would contain the ID of a `<label>` element that is associated with this
* autocomplete. This will only have affect when `md-floating-label` is not defined.<br><br>
* For `<label id="state">US State</label>`, you would set this to
* `input-aria-labelledby="state"`.
* @param {string=} input-aria-label A label that will be applied to the autocomplete's input.
* This will be announced by screen readers before the placeholder.
* This will only have affect when `md-floating-label` is not defined. If you define both
* `input-aria-label` and `input-aria-labelledby`, then `input-aria-label` will take precedence.
* @param {string=} md-selected-message Attribute to specify the text that the screen reader will
* announce after a value is selected. Default is: "selected". If `Alaska` is selected in the
* options panel, it will read "Alaska selected". You will want to override this when your app
Expand Down Expand Up @@ -179,12 +192,12 @@ angular
* ### Clear button for the input
* By default, the clear button is displayed when there is input. This aligns with the spec's
* [Search Pattern](https://material.io/archive/guidelines/patterns/search.html#search-in-app-search).
* In floating label mode, when `md-floating-label="My Label"` is applied, the clear button is not displayed
* by default (see the spec's
* In floating label mode, when `md-floating-label="My Label"` is applied, the clear button is not
* displayed by default (see the spec's
* [Autocomplete Text Field](https://material.io/archive/guidelines/components/text-fields.html#text-fields-layout)).
*
* Nevertheless, developers are able to explicitly toggle the clear button for all autocomplete components
* with `md-clear-button`.
* Nevertheless, developers are able to explicitly toggle the clear button for all autocomplete
* components with `md-clear-button`.
*
* <hljs lang="html">
* <md-autocomplete ... md-clear-button="true"></md-autocomplete>
Expand Down Expand Up @@ -223,7 +236,8 @@ angular
* input validation for the field.
*
* ### Asynchronous Results
* The autocomplete items expression also supports promises, which will resolve with the query results.
* The autocomplete items expression also supports promises, which will resolve with the query
* results.
*
* <hljs lang="js">
* function AppController($scope, $http) {
Expand Down Expand Up @@ -265,6 +279,8 @@ function MdAutocomplete ($$mdSvgRegistry) {
itemText: '&mdItemText',
placeholder: '@placeholder',
inputAriaDescribedBy: '@?inputAriaDescribedby',
inputAriaLabelledBy: '@?inputAriaLabelledby',
inputAriaLabel: '@?inputAriaLabel',
noCache: '=?mdNoCache',
requireMatch: '=?mdRequireMatch',
selectOnMatch: '=?mdSelectOnMatch',
Expand Down Expand Up @@ -303,8 +319,8 @@ function MdAutocomplete ($$mdSvgRegistry) {
// be added to the element in the template function.
ctrl.hasNotFound = !!element.attr('md-has-not-found');

// By default the inset autocomplete should show the clear button when not explicitly overwritten
// or in floating label mode.
// By default the inset autocomplete should show the clear button when not explicitly
// overwritten or in floating label mode.
if (!angular.isDefined(attrs.mdClearButton) && !scope.floatingLabel) {
scope.clearButton = true;
}
Expand Down Expand Up @@ -370,7 +386,8 @@ function MdAutocomplete ($$mdSvgRegistry) {
var templateTag = element.find('md-item-template').detach(),
html = templateTag.length ? templateTag.html() : element.html();
if (!templateTag.length) element.empty();
return '<md-autocomplete-parent-scope md-autocomplete-replace>' + html + '</md-autocomplete-parent-scope>';
return '<md-autocomplete-parent-scope md-autocomplete-replace>' + html +
'</md-autocomplete-parent-scope>';
}

function getNoItemsTemplate() {
Expand Down