Skip to content

Commit d89437f

Browse files
author
Steven G. Harms
committed
menubar: Repair TAB order bugs
I believe this fixes tab order. Regrettably I cannot get the unit test (included in this commit) which verifies the function of TAB by use of `simulate` functioning. The test under investigation is: "TAB order should be sane mirroring dialog's test " in `menubar_events.js`. I'm including it in the PR in hopes that additional review will present the flaw in my implementation. I've tried mirroring the use in dialog's test suite with no success. Curiously my use of `simulate` _seems_ right insofar as my test using it to trigger RIGHT and LEFT cursor key events _is_ behaving as expected.
1 parent d163b23 commit d89437f

File tree

3 files changed

+94
-16
lines changed

3 files changed

+94
-16
lines changed

tests/unit/menubar/menubar_core.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,18 @@ test( "accessibility", function () {
1919
ok( !element.attr( "aria-activedescendant" ), "aria-activedescendant not set" );
2020
});
2121

22+
test( "Cursor keys should move the focus", function() {
23+
expect( 3 );
24+
25+
var element = $( "#bar1" ).menubar(),
26+
firstMenuItem = $( "#bar1 .ui-menubar-item .ui-button:first" );
27+
28+
firstMenuItem[ 0 ].focus();
29+
equal( document.activeElement, firstMenuItem[0], "Focus set on first menuItem" );
30+
$( firstMenuItem ).simulate( "keydown", { keyCode: $.ui.keyCode.RIGHT } );
31+
ok( !firstMenuItem.hasClass( "ui-state-focus" ), "RIGHT should move focus off of focused item" );
32+
$( document.activeElement ).simulate( "keydown", { keyCode: $.ui.keyCode.LEFT } );
33+
equal( document.activeElement, firstMenuItem[0], "LEFT should return focus first menuItem" );
34+
} );
35+
2236
})( jQuery );

tests/unit/menubar/menubar_events.js

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,70 @@ test ( "_findNextFocusableTarget should find one and only one item", function()
5050
expect(2);
5151

5252
var element = $("#bar1").menubar(),
53-
menubarWidget = element.data("ui-menubar"),
54-
firstMenuItem = $("#bar1 .ui-menubar-item").eq(0),
55-
expectedFocusableTarget = $("#bar1 .ui-menubar-item .ui-widget").eq(0),
53+
menubarWidget = element.data( "ui-menubar" ),
54+
firstMenuItem = $( "#bar1 .ui-menubar-item" ).eq( 0 ),
55+
expectedFocusableTarget = $("#bar1 .ui-menubar-item .ui-widget").eq( 0 ),
5656
result = menubarWidget._findNextFocusableTarget( firstMenuItem );
5757

5858
equal( expectedFocusableTarget[0], result[0], "_findNextFocusableTarget should return the focusable element underneath the menuItem" );
5959
equal( 1, result.length, "One and only one item should be returned." );
6060
});
6161

62+
asyncTest( "TAB order should be sane mirroring dialog's test", function() {
63+
expect( 3 );
64+
65+
var element = $( "#bar1" ).menubar(),
66+
firstMenuItem = $( "#bar1 .ui-menubar-item .ui-button:first" );
67+
68+
function checkTab() {
69+
setTimeout( start );
70+
ok( !firstMenuItem.hasClass( "ui-state-focus" ), "The manually focused item should no longer have focus after TAB" );
71+
//setTimeout( start );
72+
}
73+
74+
firstMenuItem[ 0 ].focus();
75+
ok( $( firstMenuItem ).hasClass( "ui-state-focus" ), "Should have focus class" );
76+
77+
setTimeout(function() {
78+
equal( document.activeElement, firstMenuItem[0], "Focus set on first menuItem" );
79+
$( document.activeElement ).simulate( "keydown", { keyCode: $.ui.keyCode.TAB } );
80+
setTimeout( checkTab );
81+
})
82+
83+
} );
84+
85+
asyncTest( "TAB order should be sane", function() {
86+
expect( 3 );
87+
88+
89+
var element = $( "#bar1" ).menubar(),
90+
debugDelay = 0,
91+
firstMenuItem = $( "#bar1 .ui-menubar-item .ui-button:first" );
92+
93+
/* Make the qunit fixture visible if we're debugging this test*/
94+
if ( debugDelay ) {
95+
$('<link rel="stylesheet" href="../../../themes/base/jquery.ui.all.css" />').appendTo("head");
96+
$( "#qunit-fixture" ).css({ right: "300px", top: "300px", left:0 });
97+
}
98+
99+
setTimeout(function(){
100+
firstMenuItem[ 0 ].focus();
101+
102+
function postFocus(){
103+
ok( !firstMenuItem.hasClass( "ui-state-focus" ), "The manually focused item should no longer have focus after TAB" );
104+
setTimeout( start );
105+
};
106+
107+
setTimeout(function() {
108+
ok( firstMenuItem.hasClass( "ui-state-focus" ), "Should have focus class" );
109+
equal( document.activeElement, firstMenuItem, "Focus set on first menuItem" );
110+
$( document.activeElement ).simulate( "keydown", { keyCode: $.ui.keyCode.TAB } );
111+
setTimeout( postFocus );
112+
});
113+
114+
}, debugDelay );
115+
116+
} );
117+
118+
62119
})( jQuery );

ui/jquery.ui.menubar.js

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ $.widget( "ui.menubar", {
4343

4444
// Keep track of open submenus
4545
this.openSubmenus = 0;
46+
47+
// Scorched earth: NOTHING can be tabbed to
48+
this.menuItems.find("*").slice(1).attr("tabindex", -1);
4649
},
4750

4851
_initializeMenubarsBoundElement: function() {
@@ -65,6 +68,7 @@ $.widget( "ui.menubar", {
6568
}
6669
},
6770
focusin: function() {
71+
this._disableTabIndexOnFirstMenuItem();
6872
clearTimeout( menubar.closeTimer );
6973
},
7074
focusout: function( event ) {
@@ -105,7 +109,7 @@ $.widget( "ui.menubar", {
105109
isLastElement = ( index === ( collectionLength - 1 ) );
106110

107111
if ( isFirstElement ) {
108-
$menuItem.data( "prevMenuItem", $( this.menuItems[collectionLength - 1]) );
112+
$menuItem.data( "prevMenuItem", $( this.menuItems[collectionLength - 1]) );
109113
$menuItem.data( "nextMenuItem", $( this.menuItems[index+1]) );
110114
} else if ( isLastElement ) {
111115
$menuItem.data( "nextMenuItem", $( this.menuItems[0]) );
@@ -171,6 +175,7 @@ $.widget( "ui.menubar", {
171175

172176
this._on( subMenus, {
173177
keydown: function( event ) {
178+
$(event.target).attr("tabIndex", 1);
174179
var parentButton,
175180
menu = $( this );
176181
if ( menu.is(":hidden") ) {
@@ -192,6 +197,7 @@ $.widget( "ui.menubar", {
192197
}
193198

194199
event.preventDefault();
200+
$(event.target).attr("tabIndex", -1);
195201
break;
196202
case $.ui.keyCode.RIGHT:
197203
this.next( event );
@@ -206,15 +212,8 @@ $.widget( "ui.menubar", {
206212
},
207213

208214
_initializeItem: function( $anItem, menubar ) {
209-
//only the first item is eligible to receive the focus
210215
var menuItemHasSubMenu = $anItem.data("parentMenuItem").data("hasSubMenu");
211216

212-
// Only the first item is tab-able
213-
if ( menubar.items.length === 1 ) {
214-
$anItem.attr( "tabindex", 1 );
215-
} else {
216-
$anItem.attr( "tabIndex", -1 );
217-
}
218217

219218
this._focusable( this.items );
220219
this._hoverable( this.items );
@@ -239,10 +238,12 @@ $.widget( "ui.menubar", {
239238

240239
__applyMouseAndKeyboardBehaviorForMenuItem: function( $anItem, menubar ) {
241240
menubar._on( $anItem, {
242-
focus: function(){
241+
focus: function(){
242+
$anItem.attr("tabIndex", 1);
243243
$anItem.addClass("ui-state-focus");
244244
},
245245
focusout: function(){
246+
$anItem.attr("tabIndex", -1);
246247
$anItem.removeClass("ui-state-focus");
247248
}
248249
} );
@@ -287,7 +288,6 @@ $.widget( "ui.menubar", {
287288
this._open( event, menu );
288289
}
289290
};
290-
291291
menubar._on( input, {
292292
click: mouseBehaviorCallback,
293293
focus: mouseBehaviorCallback,
@@ -312,6 +312,9 @@ $.widget( "ui.menubar", {
312312
this.next( event );
313313
event.preventDefault();
314314
break;
315+
case $.ui.keyCode.TAB:
316+
event.stopPropagation();
317+
break;
315318
}
316319
};
317320

@@ -439,7 +442,7 @@ $.widget( "ui.menubar", {
439442
}
440443

441444
// set tabIndex -1 to have the button skipped on shift-tab when menu is open (it gets focus)
442-
button = menuItem.addClass("ui-state-active").attr( "tabIndex", -1 );
445+
button = menuItem.addClass("ui-state-active");
443446

444447
this.active = menu
445448
.show()
@@ -501,7 +504,7 @@ $.widget( "ui.menubar", {
501504
this._submenuless_open( event, nextMenuItem );
502505
}
503506
} else {
504-
closestMenuItem.find(".ui-button").attr( "tabindex", -1 );
507+
closestMenuItem.find(".ui-button");
505508
focusableTarget.focus();
506509
}
507510
},
@@ -539,8 +542,12 @@ $.widget( "ui.menubar", {
539542
menu.parent(".ui-menubar-item").removeClass("ui-state-active");
540543
},
541544

545+
_disableTabIndexOnFirstMenuItem: function() {
546+
this.items[0].attr( "tabIndex", -1 );
547+
},
548+
542549
_reenableTabIndexOnFirstMenuItem: function() {
543-
$(this.menuItems[0]).find(".ui-widget").attr( "tabindex", 1 );
550+
this.items[0].attr( "tabIndex", 1 );
544551
}
545552

546553
});

0 commit comments

Comments
 (0)