diff options
author | Jörn Zaefferer <joern.zaefferer@gmail.com> | 2012-05-15 14:07:35 +0200 |
---|---|---|
committer | Jörn Zaefferer <joern.zaefferer@gmail.com> | 2012-05-16 11:55:12 +0200 |
commit | f4b2d7a4115814b64ff291e3518fe15f2dfbe390 (patch) | |
tree | 15cf5f2edc99e614b32007e7dca2af3fcb78a1cf /ui | |
parent | c0f6b0ccdf69c705a03e30778ae318e0cd8a0625 (diff) | |
download | jquery-ui-f4b2d7a4115814b64ff291e3518fe15f2dfbe390.tar.gz jquery-ui-f4b2d7a4115814b64ff291e3518fe15f2dfbe390.zip |
Autocomplete: ARIA live region as extension, adding a messages option. Fixes #7840 - Autocomplete: popup results not read by screen-readers
Diffstat (limited to 'ui')
-rw-r--r-- | ui/jquery.ui.autocomplete.js | 48 | ||||
-rw-r--r-- | ui/jquery.ui.menu.js | 26 |
2 files changed, 58 insertions, 16 deletions
diff --git a/ui/jquery.ui.autocomplete.js b/ui/jquery.ui.autocomplete.js index fab9691a3..fa15bc278 100644 --- a/ui/jquery.ui.autocomplete.js +++ b/ui/jquery.ui.autocomplete.js @@ -60,13 +60,7 @@ $.widget( "ui.autocomplete", { this.element .addClass( "ui-autocomplete-input" ) - .attr( "autocomplete", "off" ) - // TODO verify these actually work as intended - .attr({ - role: "textbox", - "aria-autocomplete": "list", - "aria-haspopup": "true" - }); + .attr( "autocomplete", "off" ); this._bind({ keydown: function( event ) { @@ -188,7 +182,9 @@ $.widget( "ui.autocomplete", { .appendTo( this.document.find( this.options.appendTo || "body" )[0] ) .menu({ // custom key handling for now - input: $() + input: $(), + // disable ARIA support, the live region takes care of that + role: null }) .zIndex( this.element.zIndex() + 1 ) .hide() @@ -532,4 +528,40 @@ $.extend( $.ui.autocomplete, { } }); + +// live region extension, adding a `messages` option +$.widget( "ui.autocomplete", $.ui.autocomplete, { + options: { + messages: { + noResults: "No search results.", + results: function(amount) { + return amount + ( amount > 1 ? " results are" : " result is" ) + " available, use up and down arrow keys to navigate."; + } + } + }, + _create: function() { + this._super(); + this.liveRegion = $( "<span>", { + role: "status", + "aria-live": "polite" + }) + .addClass( "ui-helper-hidden-accessible" ) + .insertAfter( this.element ); + }, + __response: function( content ) { + var message; + this._superApply( arguments ); + if ( this.options.disabled || this.cancelSearch) { + return; + } + if ( content && content.length ) { + message = this.options.messages.results( content.length ); + } else { + message = this.options.messages.noResults; + } + this.liveRegion.text( message ); + } +}); + + }( jQuery )); diff --git a/ui/jquery.ui.menu.js b/ui/jquery.ui.menu.js index 7704521fb..36f7e1de4 100644 --- a/ui/jquery.ui.menu.js +++ b/ui/jquery.ui.menu.js @@ -26,6 +26,7 @@ $.widget( "ui.menu", { my: "left top", at: "right top" }, + role: "menu", // callbacks blur: null, @@ -42,7 +43,7 @@ $.widget( "ui.menu", { .addClass( "ui-menu ui-widget ui-widget-content ui-corner-all" ) .attr({ id: this.menuId, - role: "menu", + role: this.options.role, tabIndex: 0 }) // need to catch all clicks on disabled menu @@ -267,7 +268,7 @@ $.widget( "ui.menu", { .addClass( "ui-menu ui-widget ui-widget-content ui-corner-all" ) .hide() .attr({ - role: "menu", + role: this.options.role, "aria-hidden": "true", "aria-expanded": "false" }); @@ -281,7 +282,7 @@ $.widget( "ui.menu", { .children( "a" ) .addClass( "ui-corner-all" ) .attr( "tabIndex", -1 ) - .attr( "role", "menuitem" ) + .attr( "role", this._itemRole() ) .attr( "id", function( i ) { return menuId + "-" + i; }); @@ -302,8 +303,15 @@ $.widget( "ui.menu", { }); }, + _itemRole: function() { + return { + menu: "menuitem", + listbox: "option" + }[ this.options.role ]; + }, + focus: function( event, item ) { - var nested, borderTop, paddingTop, offset, scroll, elementHeight, itemHeight; + var nested, borderTop, paddingTop, offset, scroll, elementHeight, itemHeight, focused; this.blur( event, event && event.type === "focus" ); if ( this._hasScroll() ) { @@ -322,10 +330,12 @@ $.widget( "ui.menu", { } this.active = item.first(); - this.element.attr( "aria-activedescendant", - this.active.children( "a" ) - .addClass( "ui-state-focus" ) - .attr( "id" ) ); + focused = this.active.children( "a" ).addClass( "ui-state-focus" ); + // only update aria-activedescendant if there's a role + // otherwise we assume focus is managed elsewhere + if ( this.options.role ) { + this.element.attr( "aria-activedescendant", focused.attr( "id" ) ); + } // highlight active parent menu item, if any this.active.parent().closest( ".ui-menu-item" ).children( "a:first" ).addClass( "ui-state-active" ); |