diff --git a/tests/unit/menu/menu.html b/tests/unit/menu/menu.html
index 4970df864..49e33e7b1 100644
--- a/tests/unit/menu/menu.html
+++ b/tests/unit/menu/menu.html
@@ -31,7 +31,7 @@
$("#log").data("lastItem",item);
$('li:eq(' + item + ') a',menu).trigger("click");
}
-
+
@@ -41,10 +41,12 @@
-
+
@@ -66,6 +68,159 @@
Addyston
Adelphi
+
+
+
+
+
+
+
diff --git a/tests/unit/menu/menu_events.js b/tests/unit/menu/menu_events.js
index 2f713acf8..b949c827d 100644
--- a/tests/unit/menu/menu_events.js
+++ b/tests/unit/menu/menu_events.js
@@ -42,4 +42,309 @@ test( "handle blur: click", function() {
$("#remove").remove();
});
+test("handle keyboard navigation on menu without scroll and without submenus", function() {
+ expect(12);
+ var element = $('#menu1').menu({
+ select: function(event, ui) {
+ log($(ui.item[0]).text());
+ },
+ focus: function( event, ui ) {
+ log($(event.target).find(".ui-state-focus").parent().index());
+ }
+ });
+
+ log("keydown",true);
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.DOWN } );
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.DOWN } );
+ equals( $("#log").html(), "1,0,keydown,", "Keydown DOWN");
+
+ log("keydown",true);
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.UP } );
+ equals( $("#log").html(), "0,keydown,", "Keydown UP");
+
+ log("keydown",true);
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.LEFT } );
+ equals( $("#log").html(), "keydown,", "Keydown LEFT (no effect)");
+
+ log("keydown",true);
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.RIGHT } );
+ equals( $("#log").html(), "keydown,", "Keydown RIGHT (no effect)");
+
+ log("keydown",true);
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.PAGE_DOWN } );
+ equals( $("#log").html(), "4,keydown,", "Keydown PAGE_DOWN");
+
+ log("keydown",true);
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.PAGE_DOWN } );
+ equals( $("#log").html(), "keydown,", "Keydown PAGE_DOWN (no effect)");
+
+ log("keydown",true);
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.PAGE_UP } );
+ equals( $("#log").html(), "0,keydown,", "Keydown PAGE_UP");
+
+ log("keydown",true);
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.PAGE_UP } );
+ equals( $("#log").html(), "keydown,", "Keydown PAGE_UP (no effect)");
+
+ log("keydown",true);
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.END } );
+ equals( $("#log").html(), "4,keydown,", "Keydown END");
+
+ log("keydown",true);
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.HOME } );
+ equals( $("#log").html(), "0,keydown,", "Keydown HOME");
+
+ log("keydown",true);
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.ESCAPE } );
+ equals( $("#log").html(), "keydown,", "Keydown ESCAPE (no effect)");
+
+ log("keydown",true);
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.ENTER } );
+ equals( $("#log").html(), "Aberdeen,keydown,", "Keydown ENTER");
+});
+
+asyncTest("handle keyboard navigation on menu without scroll and with submenus", function() {
+ expect(14);
+ var element = $('#menu2').menu({
+ select: function(event, ui) {
+ log($(ui.item[0]).text());
+ },
+ focus: function( event, ui ) {
+ log($(event.target).find(".ui-state-focus").parent().index());
+ }
+ });
+
+ log("keydown",true);
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.DOWN } );
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.DOWN } );
+ equals( $("#log").html(), "1,0,keydown,", "Keydown DOWN");
+
+ log("keydown",true);
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.UP } );
+ equals( $("#log").html(), "0,keydown,", "Keydown UP");
+
+ log("keydown",true);
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.LEFT } );
+ equals( $("#log").html(), "keydown,", "Keydown LEFT (no effect)");
+
+ log("keydown",true);
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.DOWN } );
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.DOWN } );
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.DOWN } );
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.DOWN } );
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.RIGHT } );
+
+ setTimeout( function() {
+ equals( $("#log").html(), "0,4,3,2,1,keydown,", "Keydown RIGHT (open submenu)");
+ }, 50);
+
+ setTimeout( function() {
+ log("keydown",true);
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.LEFT } );
+ equals( $("#log").html(), "4,keydown,", "Keydown LEFT (close submenu)");
+
+ //re-open submenu
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.RIGHT } );
+
+ setTimeout( function() {
+ log("keydown",true);
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.PAGE_DOWN } );
+ equals( $("#log").html(), "2,keydown,", "Keydown PAGE_DOWN");
+
+ log("keydown",true);
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.PAGE_DOWN } );
+ equals( $("#log").html(), "keydown,", "Keydown PAGE_DOWN (no effect)");
+
+ log("keydown",true);
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.PAGE_UP } );
+ equals( $("#log").html(), "0,keydown,", "Keydown PAGE_UP");
+
+ log("keydown",true);
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.PAGE_UP } );
+ equals( $("#log").html(), "keydown,", "Keydown PAGE_UP (no effect)");
+
+ log("keydown",true);
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.END } );
+ equals( $("#log").html(), "2,keydown,", "Keydown END");
+
+ log("keydown",true);
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.HOME } );
+ equals( $("#log").html(), "0,keydown,", "Keydown HOME");
+
+ log("keydown",true);
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.ESCAPE } );
+ equals( $("#log").html(), "4,keydown,", "Keydown ESCAPE (close submenu)");
+
+ log("keydown",true);
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.ENTER } );
+
+ setTimeout( function() {
+ equals( $("#log").html(), "0,keydown,", "Keydown ENTER (open submenu)");
+
+ log("keydown",true);
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.ENTER } );
+ equals( $("#log").html(), "Ada,keydown,", "Keydown ENTER (select item)");
+
+ start();
+ }, 200);
+ }, 150);
+ }, 100);
+
+});
+
+test("handle keyboard navigation on menu with scroll and without submenus", function() {
+ expect(14);
+ var element = $('#menu3').menu({
+ select: function(event, ui) {
+ log($(ui.item[0]).text());
+ },
+ focus: function( event, ui ) {
+ log($(event.target).find(".ui-state-focus").parent().index());
+ }
+ });
+
+ log("keydown",true);
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.DOWN } );
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.DOWN } );
+ equals( $("#log").html(), "1,0,keydown,", "Keydown DOWN");
+
+ log("keydown",true);
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.UP } );
+ equals( $("#log").html(), "0,keydown,", "Keydown UP");
+
+ log("keydown",true);
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.LEFT } );
+ equals( $("#log").html(), "keydown,", "Keydown LEFT (no effect)");
+
+ log("keydown",true);
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.RIGHT } );
+ equals( $("#log").html(), "keydown,", "Keydown RIGHT (no effect)");
+
+ log("keydown",true);
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.PAGE_DOWN } );
+ equals( $("#log").html(), "10,keydown,", "Keydown PAGE_DOWN");
+
+ log("keydown",true);
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.PAGE_DOWN } );
+ equals( $("#log").html(), "20,keydown,", "Keydown PAGE_DOWN");
+
+ log("keydown",true);
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.PAGE_UP } );
+ equals( $("#log").html(), "10,keydown,", "Keydown PAGE_UP");
+
+ log("keydown",true);
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.PAGE_UP } );
+ equals( $("#log").html(), "0,keydown,", "Keydown PAGE_UP");
+
+ log("keydown",true);
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.PAGE_UP } );
+ equals( $("#log").html(), "keydown,", "Keydown PAGE_UP (no effect)");
+
+ log("keydown",true);
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.END } );
+ equals( $("#log").html(), "37,keydown,", "Keydown END");
+
+ log("keydown",true);
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.PAGE_DOWN } );
+ equals( $("#log").html(), "keydown,", "Keydown PAGE_DOWN (no effect)");
+
+ log("keydown",true);
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.HOME } );
+ equals( $("#log").html(), "0,keydown,", "Keydown HOME");
+
+ log("keydown",true);
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.ESCAPE } );
+ equals( $("#log").html(), "keydown,", "Keydown ESCAPE (no effect)");
+
+ log("keydown",true);
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.ENTER } );
+ equals( $("#log").html(), "Aberdeen,keydown,", "Keydown ENTER");
+});
+
+asyncTest("handle keyboard navigation on menu with scroll and with submenus", function() {
+ expect(14);
+ var element = $('#menu4').menu({
+ select: function(event, ui) {
+ log($(ui.item[0]).text());
+ },
+ focus: function( event, ui ) {
+ log($(event.target).find(".ui-state-focus").parent().index());
+ }
+ });
+
+ log("keydown",true);
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.DOWN } );
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.DOWN } );
+ equals( $("#log").html(), "1,0,keydown,", "Keydown DOWN");
+
+ log("keydown",true);
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.UP } );
+ equals( $("#log").html(), "0,keydown,", "Keydown UP");
+
+ log("keydown",true);
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.LEFT } );
+ equals( $("#log").html(), "keydown,", "Keydown LEFT (no effect)");
+
+ log("keydown",true);
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.DOWN } );
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.RIGHT } );
+
+ setTimeout( function() {
+ equals( $("#log").html(), "0,1,keydown,", "Keydown RIGHT (open submenu)");
+ }, 50);
+
+ setTimeout( function() {
+ log("keydown",true);
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.LEFT } );
+ equals( $("#log").html(), "1,keydown,", "Keydown LEFT (close submenu)");
+
+ //re-open submenu
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.RIGHT } );
+
+ setTimeout( function() {
+ log("keydown",true);
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.PAGE_DOWN } );
+ equals( $("#log").html(), "10,keydown,", "Keydown PAGE_DOWN");
+
+ log("keydown",true);
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.PAGE_DOWN } );
+ equals( $("#log").html(), "20,keydown,", "Keydown PAGE_DOWN");
+
+ log("keydown",true);
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.PAGE_UP } );
+ equals( $("#log").html(), "10,keydown,", "Keydown PAGE_UP");
+
+ log("keydown",true);
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.PAGE_UP } );
+ equals( $("#log").html(), "0,keydown,", "Keydown PAGE_UP");
+
+ log("keydown",true);
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.END } );
+ equals( $("#log").html(), "27,keydown,", "Keydown END");
+
+ log("keydown",true);
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.HOME } );
+ equals( $("#log").html(), "0,keydown,", "Keydown HOME");
+
+ log("keydown",true);
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.ESCAPE } );
+ equals( $("#log").html(), "1,keydown,", "Keydown ESCAPE (close submenu)");
+
+ log("keydown",true);
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.ENTER } );
+
+ setTimeout( function() {
+ equals( $("#log").html(), "0,keydown,", "Keydown ENTER (open submenu)");
+
+ log("keydown",true);
+ element.simulate( "keydown", { keyCode: $.ui.keyCode.ENTER } );
+ equals( $("#log").html(), "Aberdeen,keydown,", "Keydown ENTER (select item)");
+
+ start();
+ }, 200);
+ }, 150);
+ }, 100);
+
+});
+
})(jQuery);
diff --git a/ui/jquery.ui.menu.js b/ui/jquery.ui.menu.js
index 61efae7aa..ad7a9c8c1 100644
--- a/ui/jquery.ui.menu.js
+++ b/ui/jquery.ui.menu.js
@@ -89,6 +89,16 @@ $.widget( "ui.menu", {
event.preventDefault();
event.stopImmediatePropagation();
break;
+ case $.ui.keyCode.HOME:
+ self._move( "first", "first", event );
+ event.preventDefault();
+ event.stopImmediatePropagation();
+ break;
+ case $.ui.keyCode.END:
+ self._move( "last", "last", event );
+ event.preventDefault();
+ event.stopImmediatePropagation();
+ break;
case $.ui.keyCode.UP:
self.previous( event );
event.preventDefault();
@@ -252,17 +262,17 @@ $.widget( "ui.menu", {
this.blur( event );
if ( this._hasScroll() ) {
- var borderTop = parseFloat( $.curCSS( this.element[0], "borderTopWidth", true ) ) || 0,
- paddingTop = parseFloat( $.curCSS( this.element[0], "paddingTop", true ) ) || 0,
- offset = item.offset().top - this.element.offset().top - borderTop - paddingTop,
- scroll = this.element.scrollTop(),
- elementHeight = this.element.height(),
+ var borderTop = parseFloat( $.curCSS( this.activeMenu[0], "borderTopWidth", true ) ) || 0,
+ paddingTop = parseFloat( $.curCSS( this.activeMenu[0], "paddingTop", true ) ) || 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.element.scrollTop( scroll + offset );
+ this.activeMenu.scrollTop( scroll + offset );
} else if ( offset + itemHeight > elementHeight ) {
- this.element.scrollTop( scroll + offset - elementHeight + itemHeight );
+ this.activeMenu.scrollTop( scroll + offset - elementHeight + itemHeight );
}
}
@@ -391,11 +401,11 @@ $.widget( "ui.menu", {
},
next: function(event) {
- this._move( "next", ".ui-menu-item", "first", event );
+ this._move( "next", "first", event );
},
previous: function(event) {
- this._move( "prev", ".ui-menu-item", "last", event );
+ this._move( "prev", "last", event );
},
first: function() {
@@ -406,25 +416,36 @@ $.widget( "ui.menu", {
return this.active && !this.active.nextAll( ".ui-menu-item" ).length;
},
- _move: function( direction, edge, filter, event ) {
+ _move: function( direction, filter, event ) {
if ( !this.active ) {
- this.focus( event, this.activeMenu.children( edge )[ filter ]() );
+ this.focus( event, this.activeMenu.children( ".ui-menu-item" )[ filter ]() );
return;
}
- var next = this.active[ direction + "All" ]( ".ui-menu-item" ).eq( 0 );
+
+ var next;
+ if ( direction === "first" || direction === "last" ) {
+ next = this.active[ direction === "first" ? "prevAll" : "nextAll" ]( ".ui-menu-item" ).eq( -1 );
+ } else {
+ next = this.active[ direction + "All" ]( ".ui-menu-item" ).eq( 0 );
+ }
+
if ( next.length ) {
this.focus( event, next );
} else {
- this.focus( event, this.activeMenu.children( edge )[ filter ]() );
+ this.focus( event, this.activeMenu.children( ".ui-menu-item" )[ filter ]() );
}
},
nextPage: function( event ) {
if ( this._hasScroll() ) {
- if ( !this.active || this.last() ) {
+ if ( !this.active ) {
this.focus( event, this.activeMenu.children( ".ui-menu-item" ).first() );
return;
}
+ if ( this.last() ) {
+ return;
+ }
+
var base = this.active.offset().top,
height = this.element.height(),
result;
@@ -436,14 +457,17 @@ $.widget( "ui.menu", {
this.focus( event, result );
} else {
this.focus( event, this.activeMenu.children( ".ui-menu-item" )
- [ !this.active || this.last() ? "first" : "last" ]() );
+ [ !this.active ? "first" : "last" ]() );
}
},
previousPage: function( event ) {
if ( this._hasScroll() ) {
- if ( !this.active || this.first() ) {
- this.focus( event, this.activeMenu.children( ".ui-menu-item" ).last() );
+ if ( !this.active ) {
+ this.focus( event, this.activeMenu.children( ".ui-menu-item" ).first() );
+ return;
+ }
+ if ( this.first() ) {
return;
}
@@ -457,8 +481,7 @@ $.widget( "ui.menu", {
this.focus( event, result );
} else {
- this.focus( event, this.activeMenu.children( ".ui-menu-item" )
- [ !this.active || this.first() ? ":last" : ":first" ]() );
+ this.focus( event, this.activeMenu.children( ".ui-menu-item" ).first() );
}
},