From f4b2d7a4115814b64ff291e3518fe15f2dfbe390 Mon Sep 17 00:00:00 2001 From: Jörn Zaefferer Date: Tue, 15 May 2012 14:07:35 +0200 Subject: Autocomplete: ARIA live region as extension, adding a messages option. Fixes #7840 - Autocomplete: popup results not read by screen-readers --- ui/jquery.ui.menu.js | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) (limited to 'ui/jquery.ui.menu.js') 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" ); -- cgit v1.2.3 From 0adc6f5e17593d6bd42e6c0586eb85000883dec3 Mon Sep 17 00:00:00 2001 From: Scott González Date: Fri, 18 May 2012 15:34:47 -0400 Subject: Menu: Remove need to pass an event for next(), previous(), focus(). --- ui/jquery.ui.autocomplete.js | 4 ++-- ui/jquery.ui.menu.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'ui/jquery.ui.menu.js') diff --git a/ui/jquery.ui.autocomplete.js b/ui/jquery.ui.autocomplete.js index 1fc01d7a4..8ab48ba03 100644 --- a/ui/jquery.ui.autocomplete.js +++ b/ui/jquery.ui.autocomplete.js @@ -225,7 +225,7 @@ $.widget( "ui.autocomplete", { var item = ui.item.data( "ui-autocomplete-item" ) || ui.item.data( "item.autocomplete" ); if ( false !== this._trigger( "focus", event, { item: item } ) ) { // use value to match what will end up in the input, if it was a key event - if ( /^key/.test(event.originalEvent.type) ) { + if ( event.originalEvent && /^key/.test(event.originalEvent.type) ) { this._value( item.value ); } } else { @@ -468,7 +468,7 @@ $.widget( "ui.autocomplete", { }, this.options.position )); if ( this.options.autoFocus ) { - this.menu.next( new $.Event("mouseover") ); + this.menu.next(); } }, diff --git a/ui/jquery.ui.menu.js b/ui/jquery.ui.menu.js index 36f7e1de4..b3f323cec 100644 --- a/ui/jquery.ui.menu.js +++ b/ui/jquery.ui.menu.js @@ -340,7 +340,7 @@ $.widget( "ui.menu", { // highlight active parent menu item, if any this.active.parent().closest( ".ui-menu-item" ).children( "a:first" ).addClass( "ui-state-active" ); - if ( event.type === "keydown" ) { + if ( event && event.type === "keydown" ) { this._close(); } else { this.timer = this._delay(function() { -- cgit v1.2.3 From f89971a2c378a170f5eb1e9bd6317cf64b52d3e8 Mon Sep 17 00:00:00 2001 From: Jörn Zaefferer Date: Mon, 21 May 2012 16:36:33 +0200 Subject: Menu: Coding standards --- ui/jquery.ui.menu.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'ui/jquery.ui.menu.js') diff --git a/ui/jquery.ui.menu.js b/ui/jquery.ui.menu.js index b3f323cec..8de1eb47a 100644 --- a/ui/jquery.ui.menu.js +++ b/ui/jquery.ui.menu.js @@ -395,8 +395,8 @@ $.widget( "ui.menu", { var position = $.extend( {}, { of: this.active - }, $.type(this.options.position) === "function" ? - this.options.position(this.active) : + }, $.type( this.options.position ) === "function" ? + this.options.position( this.active ) : this.options.position ); -- cgit v1.2.3 From 1e586dcd486f24fda9ed277abc2d94fb8d29aee1 Mon Sep 17 00:00:00 2001 From: Jörn Zaefferer Date: Mon, 21 May 2012 16:42:14 +0200 Subject: Menu: Refactor focus method, extract _scrollIntroView method --- ui/jquery.ui.menu.js | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) (limited to 'ui/jquery.ui.menu.js') diff --git a/ui/jquery.ui.menu.js b/ui/jquery.ui.menu.js index 8de1eb47a..d3c32e3a0 100644 --- a/ui/jquery.ui.menu.js +++ b/ui/jquery.ui.menu.js @@ -311,23 +311,10 @@ $.widget( "ui.menu", { }, focus: function( event, item ) { - var nested, borderTop, paddingTop, offset, scroll, elementHeight, itemHeight, focused; + var nested, focused; this.blur( event, event && event.type === "focus" ); - if ( this._hasScroll() ) { - borderTop = parseFloat( $.css( this.activeMenu[0], "borderTopWidth" ) ) || 0; - paddingTop = parseFloat( $.css( this.activeMenu[0], "paddingTop" ) ) || 0; - offset = item.offset().top - this.activeMenu.offset().top - borderTop - paddingTop; - scroll = this.activeMenu.scrollTop(); - elementHeight = this.activeMenu.height(); - itemHeight = item.height(); - - if ( offset < 0 ) { - this.activeMenu.scrollTop( scroll + offset ); - } else if ( offset + itemHeight > elementHeight ) { - this.activeMenu.scrollTop( scroll + offset - elementHeight + itemHeight ); - } - } + this._scrollIntoView( item ); this.active = item.first(); focused = this.active.children( "a" ).addClass( "ui-state-focus" ); @@ -357,6 +344,24 @@ $.widget( "ui.menu", { this._trigger( "focus", event, { item: item } ); }, + _scrollIntoView: function( item ) { + var borderTop, paddingTop, offset, scroll, elementHeight, itemHeight; + if ( this._hasScroll() ) { + borderTop = parseFloat( $.css( this.activeMenu[0], "borderTopWidth" ) ) || 0; + paddingTop = parseFloat( $.css( this.activeMenu[0], "paddingTop" ) ) || 0; + offset = item.offset().top - this.activeMenu.offset().top - borderTop - paddingTop; + scroll = this.activeMenu.scrollTop(); + elementHeight = this.activeMenu.height(); + itemHeight = item.height(); + + if ( offset < 0 ) { + this.activeMenu.scrollTop( scroll + offset ); + } else if ( offset + itemHeight > elementHeight ) { + this.activeMenu.scrollTop( scroll + offset - elementHeight + itemHeight ); + } + } + }, + blur: function( event, fromFocus ) { if ( !fromFocus ) { clearTimeout( this.timer ); -- cgit v1.2.3 From 9dcd0e0eb824a4000692b6ce6d864056339ac498 Mon Sep 17 00:00:00 2001 From: Jörn Zaefferer Date: Tue, 22 May 2012 16:01:40 +0200 Subject: Menu: Handle SPACE same as ENTER, select items or opening submenus --- tests/unit/menu/menu_events.js | 16 ++++++++++++++-- ui/jquery.ui.menu.js | 22 +++++++++++++++------- 2 files changed, 29 insertions(+), 9 deletions(-) (limited to 'ui/jquery.ui.menu.js') diff --git a/tests/unit/menu/menu_events.js b/tests/unit/menu/menu_events.js index 4cb083240..643da4a26 100644 --- a/tests/unit/menu/menu_events.js +++ b/tests/unit/menu/menu_events.js @@ -208,7 +208,7 @@ test("handle keyboard navigation on menu without scroll and without submenus", f }); asyncTest("handle keyboard navigation on menu without scroll and with submenus", function() { - expect(14); + expect(16); var element = $('#menu2').menu({ select: function(event, ui) { log($(ui.item[0]).text()); @@ -290,11 +290,23 @@ asyncTest("handle keyboard navigation on menu without scroll and with submenus", equal( $("#log").html(), "4,keydown,", "Keydown ESCAPE (close submenu)"); log("keydown",true); - element.simulate( "keydown", { keyCode: $.ui.keyCode.ENTER } ); + element.simulate( "keydown", { keyCode: $.ui.keyCode.SPACE } ); setTimeout( menukeyboard4, 50 ); } function menukeyboard4() { + equal( $("#log").html(), "0,keydown,", "Keydown SPACE (open submenu)"); + + log("keydown",true); + element.simulate( "keydown", { keyCode: $.ui.keyCode.ESCAPE } ); + equal( $("#log").html(), "4,keydown,", "Keydown ESCAPE (close submenu)"); + + log("keydown",true); + element.simulate( "keydown", { keyCode: $.ui.keyCode.ENTER } ); + setTimeout( menukeyboard5, 50 ); + } + + function menukeyboard5() { equal( $("#log").html(), "0,keydown,", "Keydown ENTER (open submenu)"); log("keydown",true); diff --git a/ui/jquery.ui.menu.js b/ui/jquery.ui.menu.js index d3c32e3a0..1bfd76716 100644 --- a/ui/jquery.ui.menu.js +++ b/ui/jquery.ui.menu.js @@ -202,13 +202,11 @@ $.widget( "ui.menu", { event.preventDefault(); break; case $.ui.keyCode.ENTER: - if ( !this.active.is( ".ui-state-disabled" ) ) { - if ( this.active.children( "a[aria-haspopup='true']" ).length ) { - this.expand( event ); - } else { - this.select( event ); - } - } + this._activate( event ); + event.preventDefault(); + break; + case $.ui.keyCode.SPACE: + this._activate( event ); event.preventDefault(); break; case $.ui.keyCode.ESCAPE: @@ -260,6 +258,16 @@ $.widget( "ui.menu", { } }, + _activate: function( event ) { + if ( !this.active.is( ".ui-state-disabled" ) ) { + if ( this.active.children( "a[aria-haspopup='true']" ).length ) { + this.expand( event ); + } else { + this.select( event ); + } + } + }, + refresh: function() { // initialize nested menus var menus, -- cgit v1.2.3