Dialog: Restore focus to the previously focused element when window regains focus. Fixes #9101 - Dialog: Track last focused element instead of always focusing the first tabbable element

This commit is contained in:
Jörn Zaefferer 2013-10-02 17:27:43 +02:00
parent ce5539f368
commit 0e5a2e126a
2 changed files with 61 additions and 26 deletions

View File

@ -40,7 +40,7 @@ test("widget method", function() {
}); });
asyncTest( "focus tabbable", function() { asyncTest( "focus tabbable", function() {
expect( 5 ); expect( 6 );
var element, var element,
options = { options = {
buttons: [{ buttons: [{
@ -50,6 +50,12 @@ asyncTest( "focus tabbable", function() {
}; };
function checkFocus( markup, options, testFn, next ) { function checkFocus( markup, options, testFn, next ) {
// Support: IE8
// For some reason the focus doesn't get set properly if we don't
// focus the body first.
$( "body" ).focus();
element = $( markup ).dialog( options ); element = $( markup ).dialog( options );
setTimeout(function() { setTimeout(function() {
testFn(); testFn();
@ -59,43 +65,57 @@ asyncTest( "focus tabbable", function() {
} }
function step1() { function step1() {
checkFocus( "<div><input><input autofocus></div>", options, function() { element = $( "<div><input><input></div>" ).dialog( options );
equal( document.activeElement, element.find( "input" )[ 1 ], setTimeout(function() {
"1. first element inside the dialog matching [autofocus]" ); var input = element.find( "input:last" ).focus().blur();
}, step2 ); element.dialog( "instance" )._focusTabbable();
setTimeout(function() {
equal( document.activeElement, input[ 0 ],
"1. an element that was focused previously." );
element.remove();
setTimeout( step2 );
});
});
} }
function step2() { function step2() {
checkFocus( "<div><input><input></div>", options, function() { checkFocus( "<div><input><input autofocus></div>", options, function() {
equal( document.activeElement, element.find( "input" )[ 0 ], equal( document.activeElement, element.find( "input" )[ 1 ],
"2. tabbable element inside the content element" ); "2. first element inside the dialog matching [autofocus]" );
}, step3 ); }, step3 );
} }
function step3() { function step3() {
checkFocus( "<div>text</div>", options, function() { checkFocus( "<div><input><input></div>", options, function() {
equal( document.activeElement, equal( document.activeElement, element.find( "input" )[ 0 ],
element.dialog( "widget" ).find( ".ui-dialog-buttonpane button" )[ 0 ], "3. tabbable element inside the content element" );
"3. tabbable element inside the buttonpane" );
}, step4 ); }, step4 );
} }
function step4() { function step4() {
checkFocus( "<div>text</div>", {}, function() { checkFocus( "<div>text</div>", options, function() {
equal( document.activeElement, equal( document.activeElement,
element.dialog( "widget" ).find( ".ui-dialog-titlebar .ui-dialog-titlebar-close" )[ 0 ], element.dialog( "widget" ).find( ".ui-dialog-buttonpane button" )[ 0 ],
"4. the close button" ); "4. tabbable element inside the buttonpane" );
}, step5 ); }, step5 );
} }
function step5() { function step5() {
checkFocus( "<div>text</div>", {}, function() {
equal( document.activeElement,
element.dialog( "widget" ).find( ".ui-dialog-titlebar .ui-dialog-titlebar-close" )[ 0 ],
"5. the close button" );
}, step6 );
}
function step6() {
element = $( "<div>text</div>" ).dialog({ element = $( "<div>text</div>" ).dialog({
autoOpen: false autoOpen: false
}); });
element.dialog( "widget" ).find( ".ui-dialog-titlebar-close" ).hide(); element.dialog( "widget" ).find( ".ui-dialog-titlebar-close" ).hide();
element.dialog( "open" ); element.dialog( "open" );
setTimeout(function() { setTimeout(function() {
equal( document.activeElement, element.parent()[ 0 ], "5. the dialog itself" ); equal( document.activeElement, element.parent()[ 0 ], "6. the dialog itself" );
element.remove(); element.remove();
start(); start();
}); });

View File

@ -118,6 +118,8 @@ $.widget( "ui.dialog", {
} }
this._isOpen = false; this._isOpen = false;
this._trackFocus();
}, },
_init: function() { _init: function() {
@ -178,6 +180,7 @@ $.widget( "ui.dialog", {
} }
this._isOpen = false; this._isOpen = false;
this._focusedElement = null;
this._destroyOverlay(); this._destroyOverlay();
if ( !this.opener.filter(":focusable").focus().length ) { if ( !this.opener.filter(":focusable").focus().length ) {
@ -256,20 +259,24 @@ $.widget( "ui.dialog", {
_focusTabbable: function() { _focusTabbable: function() {
// Set focus to the first match: // Set focus to the first match:
// 1. First element inside the dialog matching [autofocus] // 1. An element that was focused previously
// 2. Tabbable element inside the content element // 2. First element inside the dialog matching [autofocus]
// 3. Tabbable element inside the buttonpane // 3. Tabbable element inside the content element
// 4. The close button // 4. Tabbable element inside the buttonpane
// 5. The dialog itself // 5. The close button
var hasFocus = this.element.find("[autofocus]"); // 6. The dialog itself
if ( !hasFocus.length ) { var hasFocus = this._focusedElement;
hasFocus = this.element.find(":tabbable"); if ( !hasFocus ) {
hasFocus = this.element.find( "[autofocus]" );
} }
if ( !hasFocus.length ) { if ( !hasFocus.length ) {
hasFocus = this.uiDialogButtonPane.find(":tabbable"); hasFocus = this.element.find( ":tabbable" );
} }
if ( !hasFocus.length ) { if ( !hasFocus.length ) {
hasFocus = this.uiDialogTitlebarClose.filter(":tabbable"); hasFocus = this.uiDialogButtonPane.find( ":tabbable" );
}
if ( !hasFocus.length ) {
hasFocus = this.uiDialogTitlebarClose.filter( ":tabbable" );
} }
if ( !hasFocus.length ) { if ( !hasFocus.length ) {
hasFocus = this.uiDialog; hasFocus = this.uiDialog;
@ -552,6 +559,14 @@ $.widget( "ui.dialog", {
.css( "position", position ); .css( "position", position );
}, },
_trackFocus: function() {
this._on( this.widget(), {
"focusin": function( event ) {
this._focusedElement = $( event.target );
}
});
},
_minHeight: function() { _minHeight: function() {
var options = this.options; var options = this.options;