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

Commit ca10bd0

Browse files
Splaktarjelbourn
authored andcommitted
fix(autocomplete): Chromevox indicates selected option on focus (#11441)
- implement WAI-ARIA 1.0 recommendation for autocomplete - avoid changing listbox selection or announcing changes when using arrow keys when a selection has been made and the listbox is closed - set aria-setsize and aria-posinset for dropdown options Fixes #10838. Relates to #10970.
1 parent 97e2d00 commit ca10bd0

File tree

2 files changed

+48
-42
lines changed

2 files changed

+48
-42
lines changed

src/components/autocomplete/js/autocompleteController.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -547,15 +547,15 @@ function MdAutocompleteCtrl ($scope, $element, $mdUtil, $mdConstant, $mdTheming,
547547
function keydown (event) {
548548
switch (event.keyCode) {
549549
case $mdConstant.KEY_CODE.DOWN_ARROW:
550-
if (ctrl.loading) return;
550+
if (ctrl.loading || hasSelection()) return;
551551
event.stopPropagation();
552552
event.preventDefault();
553553
ctrl.index = Math.min(ctrl.index + 1, ctrl.matches.length - 1);
554554
updateScroll();
555555
reportMessages(false, ReportType.Selected);
556556
break;
557557
case $mdConstant.KEY_CODE.UP_ARROW:
558-
if (ctrl.loading) return;
558+
if (ctrl.loading || hasSelection()) return;
559559
event.stopPropagation();
560560
event.preventDefault();
561561
ctrl.index = ctrl.index < 0 ? ctrl.matches.length - 1 : Math.max(0, ctrl.index - 1);
@@ -702,16 +702,16 @@ function MdAutocompleteCtrl ($scope, $element, $mdUtil, $mdConstant, $mdTheming,
702702
}
703703

704704
/**
705-
* Determines if the escape keydown should be processed
706-
* @returns {boolean}
705+
* @returns {boolean} if the escape keydown should be processed, return true.
706+
* Otherwise return false.
707707
*/
708708
function shouldProcessEscape() {
709709
return hasEscapeOption('blur') || !ctrl.hidden || ctrl.loading || hasEscapeOption('clear') && $scope.searchText;
710710
}
711711

712712
/**
713-
* Determines if an escape option is set
714-
* @returns {boolean}
713+
* @param {string} option check if this option is set
714+
* @returns {boolean} if the specified escape option is set, return true. Return false otherwise.
715715
*/
716716
function hasEscapeOption(option) {
717717
return !$scope.escapeOptions || $scope.escapeOptions.toLowerCase().indexOf(option) !== -1;

src/components/autocomplete/js/autocompleteDirective.js

Lines changed: 42 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -348,10 +348,16 @@ function MdAutocomplete ($$mdSvgRegistry) {
348348
role="presentation">\
349349
<ul class="md-autocomplete-suggestions"\
350350
ng-class="::menuClass"\
351-
id="ul-{{$mdAutocompleteCtrl.id}}">\
351+
id="ul-{{$mdAutocompleteCtrl.id}}"\
352+
role="listbox">\
352353
<li md-virtual-repeat="item in $mdAutocompleteCtrl.matches"\
353354
ng-class="{ selected: $index === $mdAutocompleteCtrl.index }"\
355+
ng-attr-id="{{$index === $mdAutocompleteCtrl.index ? \'selected_option\' : undefined}}"\
354356
ng-click="$mdAutocompleteCtrl.select($index)"\
357+
role="option"\
358+
aria-setsize="{{$mdAutocompleteCtrl.matches.length}}"\
359+
aria-posinset="{{$index+1}}"\
360+
aria-selected="{{$index === $mdAutocompleteCtrl.index ? true : false}}" \
355361
md-extra-name="$mdAutocompleteCtrl.itemName">\
356362
' + itemTemplate + '\
357363
</li>' + noItemsTemplate + '\
@@ -382,56 +388,56 @@ function MdAutocomplete ($$mdSvgRegistry) {
382388
<md-input-container ng-if="floatingLabel">\
383389
<label>{{floatingLabel}}</label>\
384390
<input type="search"\
385-
' + (tabindex != null ? 'tabindex="' + tabindex + '"' : '') + '\
386-
id="{{ inputId || \'fl-input-\' + $mdAutocompleteCtrl.id }}"\
387-
name="{{inputName}}"\
388-
ng-class="::inputClass"\
389-
autocomplete="off"\
390-
ng-required="$mdAutocompleteCtrl.isRequired"\
391-
ng-readonly="$mdAutocompleteCtrl.isReadonly"\
392-
ng-minlength="inputMinlength"\
393-
ng-maxlength="inputMaxlength"\
394-
ng-disabled="$mdAutocompleteCtrl.isDisabled"\
395-
ng-model="$mdAutocompleteCtrl.scope.searchText"\
396-
ng-model-options="{ allowInvalid: true }"\
397-
ng-keydown="$mdAutocompleteCtrl.keydown($event)"\
398-
ng-blur="$mdAutocompleteCtrl.blur($event)"\
399-
ng-focus="$mdAutocompleteCtrl.focus($event)"\
400-
aria-owns="ul-{{$mdAutocompleteCtrl.id}}"\
401-
aria-label="{{floatingLabel}}"\
402-
aria-autocomplete="list"\
403-
role="combobox"\
404-
aria-haspopup="true"\
405-
aria-activedescendant=""\
406-
aria-expanded="{{!$mdAutocompleteCtrl.hidden}}"/>\
407-
<div md-autocomplete-parent-scope md-autocomplete-replace>' + leftover + '</div>\
408-
</md-input-container>';
409-
} else {
410-
return '\
411-
<input type="search"\
412391
' + (tabindex != null ? 'tabindex="' + tabindex + '"' : '') + '\
413-
id="{{ inputId || \'input-\' + $mdAutocompleteCtrl.id }}"\
392+
id="{{ inputId || \'fl-input-\' + $mdAutocompleteCtrl.id }}"\
414393
name="{{inputName}}"\
415394
ng-class="::inputClass"\
416-
ng-if="!floatingLabel"\
417395
autocomplete="off"\
418396
ng-required="$mdAutocompleteCtrl.isRequired"\
419-
ng-disabled="$mdAutocompleteCtrl.isDisabled"\
420397
ng-readonly="$mdAutocompleteCtrl.isReadonly"\
421398
ng-minlength="inputMinlength"\
422399
ng-maxlength="inputMaxlength"\
400+
ng-disabled="$mdAutocompleteCtrl.isDisabled"\
423401
ng-model="$mdAutocompleteCtrl.scope.searchText"\
402+
ng-model-options="{ allowInvalid: true }"\
424403
ng-keydown="$mdAutocompleteCtrl.keydown($event)"\
425404
ng-blur="$mdAutocompleteCtrl.blur($event)"\
426405
ng-focus="$mdAutocompleteCtrl.focus($event)"\
427-
placeholder="{{placeholder}}"\
428-
aria-owns="ul-{{$mdAutocompleteCtrl.id}}"\
429-
aria-label="{{placeholder}}"\
406+
aria-label="{{floatingLabel}}"\
430407
aria-autocomplete="list"\
431408
role="combobox"\
432409
aria-haspopup="true"\
433-
aria-activedescendant=""\
434-
aria-expanded="{{!$mdAutocompleteCtrl.hidden}}"/>';
410+
aria-expanded="{{!$mdAutocompleteCtrl.hidden}}"\
411+
aria-owns="ul-{{$mdAutocompleteCtrl.id}}"\
412+
ng-attr-aria-activedescendant="{{$mdAutocompleteCtrl.index >= 0 ? \'selected_option\' : undefined}}">\
413+
<div md-autocomplete-parent-scope md-autocomplete-replace>' + leftover + '</div>\
414+
</md-input-container>';
415+
} else {
416+
return '\
417+
<input type="search"\
418+
' + (tabindex != null ? 'tabindex="' + tabindex + '"' : '') + '\
419+
id="{{ inputId || \'input-\' + $mdAutocompleteCtrl.id }}"\
420+
name="{{inputName}}"\
421+
ng-class="::inputClass"\
422+
ng-if="!floatingLabel"\
423+
autocomplete="off"\
424+
ng-required="$mdAutocompleteCtrl.isRequired"\
425+
ng-disabled="$mdAutocompleteCtrl.isDisabled"\
426+
ng-readonly="$mdAutocompleteCtrl.isReadonly"\
427+
ng-minlength="inputMinlength"\
428+
ng-maxlength="inputMaxlength"\
429+
ng-model="$mdAutocompleteCtrl.scope.searchText"\
430+
ng-keydown="$mdAutocompleteCtrl.keydown($event)"\
431+
ng-blur="$mdAutocompleteCtrl.blur($event)"\
432+
ng-focus="$mdAutocompleteCtrl.focus($event)"\
433+
placeholder="{{placeholder}}"\
434+
aria-label="{{placeholder}}"\
435+
aria-autocomplete="list"\
436+
role="combobox"\
437+
aria-haspopup="true"\
438+
aria-expanded="{{!$mdAutocompleteCtrl.hidden}}"\
439+
aria-owns="ul-{{$mdAutocompleteCtrl.id}}"\
440+
ng-attr-aria-activedescendant="{{$mdAutocompleteCtrl.index >= 0 ? \'selected_option\' : undefined}}">';
435441
}
436442
}
437443

0 commit comments

Comments
 (0)