Skip to content

Commit 0e5a2e1

Browse files
committed
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
1 parent ce5539f commit 0e5a2e1

File tree

2 files changed

+58
-23
lines changed

2 files changed

+58
-23
lines changed

tests/unit/dialog/dialog_core.js

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ test("widget method", function() {
4040
});
4141

4242
asyncTest( "focus tabbable", function() {
43-
expect( 5 );
43+
expect( 6 );
4444
var element,
4545
options = {
4646
buttons: [{
@@ -50,6 +50,12 @@ asyncTest( "focus tabbable", function() {
5050
};
5151

5252
function checkFocus( markup, options, testFn, next ) {
53+
54+
// Support: IE8
55+
// For some reason the focus doesn't get set properly if we don't
56+
// focus the body first.
57+
$( "body" ).focus();
58+
5359
element = $( markup ).dialog( options );
5460
setTimeout(function() {
5561
testFn();
@@ -59,43 +65,57 @@ asyncTest( "focus tabbable", function() {
5965
}
6066

6167
function step1() {
68+
element = $( "<div><input><input></div>" ).dialog( options );
69+
setTimeout(function() {
70+
var input = element.find( "input:last" ).focus().blur();
71+
element.dialog( "instance" )._focusTabbable();
72+
setTimeout(function() {
73+
equal( document.activeElement, input[ 0 ],
74+
"1. an element that was focused previously." );
75+
element.remove();
76+
setTimeout( step2 );
77+
});
78+
});
79+
}
80+
81+
function step2() {
6282
checkFocus( "<div><input><input autofocus></div>", options, function() {
6383
equal( document.activeElement, element.find( "input" )[ 1 ],
64-
"1. first element inside the dialog matching [autofocus]" );
65-
}, step2 );
84+
"2. first element inside the dialog matching [autofocus]" );
85+
}, step3 );
6686
}
6787

68-
function step2() {
88+
function step3() {
6989
checkFocus( "<div><input><input></div>", options, function() {
7090
equal( document.activeElement, element.find( "input" )[ 0 ],
71-
"2. tabbable element inside the content element" );
72-
}, step3 );
91+
"3. tabbable element inside the content element" );
92+
}, step4 );
7393
}
7494

75-
function step3() {
95+
function step4() {
7696
checkFocus( "<div>text</div>", options, function() {
7797
equal( document.activeElement,
7898
element.dialog( "widget" ).find( ".ui-dialog-buttonpane button" )[ 0 ],
79-
"3. tabbable element inside the buttonpane" );
80-
}, step4 );
99+
"4. tabbable element inside the buttonpane" );
100+
}, step5 );
81101
}
82102

83-
function step4() {
103+
function step5() {
84104
checkFocus( "<div>text</div>", {}, function() {
85105
equal( document.activeElement,
86106
element.dialog( "widget" ).find( ".ui-dialog-titlebar .ui-dialog-titlebar-close" )[ 0 ],
87-
"4. the close button" );
88-
}, step5 );
107+
"5. the close button" );
108+
}, step6 );
89109
}
90110

91-
function step5() {
111+
function step6() {
92112
element = $( "<div>text</div>" ).dialog({
93113
autoOpen: false
94114
});
95115
element.dialog( "widget" ).find( ".ui-dialog-titlebar-close" ).hide();
96116
element.dialog( "open" );
97117
setTimeout(function() {
98-
equal( document.activeElement, element.parent()[ 0 ], "5. the dialog itself" );
118+
equal( document.activeElement, element.parent()[ 0 ], "6. the dialog itself" );
99119
element.remove();
100120
start();
101121
});

ui/jquery.ui.dialog.js

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,8 @@ $.widget( "ui.dialog", {
118118
}
119119

120120
this._isOpen = false;
121+
122+
this._trackFocus();
121123
},
122124

123125
_init: function() {
@@ -178,6 +180,7 @@ $.widget( "ui.dialog", {
178180
}
179181

180182
this._isOpen = false;
183+
this._focusedElement = null;
181184
this._destroyOverlay();
182185

183186
if ( !this.opener.filter(":focusable").focus().length ) {
@@ -256,20 +259,24 @@ $.widget( "ui.dialog", {
256259

257260
_focusTabbable: function() {
258261
// Set focus to the first match:
259-
// 1. First element inside the dialog matching [autofocus]
260-
// 2. Tabbable element inside the content element
261-
// 3. Tabbable element inside the buttonpane
262-
// 4. The close button
263-
// 5. The dialog itself
264-
var hasFocus = this.element.find("[autofocus]");
262+
// 1. An element that was focused previously
263+
// 2. First element inside the dialog matching [autofocus]
264+
// 3. Tabbable element inside the content element
265+
// 4. Tabbable element inside the buttonpane
266+
// 5. The close button
267+
// 6. The dialog itself
268+
var hasFocus = this._focusedElement;
269+
if ( !hasFocus ) {
270+
hasFocus = this.element.find( "[autofocus]" );
271+
}
265272
if ( !hasFocus.length ) {
266-
hasFocus = this.element.find(":tabbable");
273+
hasFocus = this.element.find( ":tabbable" );
267274
}
268275
if ( !hasFocus.length ) {
269-
hasFocus = this.uiDialogButtonPane.find(":tabbable");
276+
hasFocus = this.uiDialogButtonPane.find( ":tabbable" );
270277
}
271278
if ( !hasFocus.length ) {
272-
hasFocus = this.uiDialogTitlebarClose.filter(":tabbable");
279+
hasFocus = this.uiDialogTitlebarClose.filter( ":tabbable" );
273280
}
274281
if ( !hasFocus.length ) {
275282
hasFocus = this.uiDialog;
@@ -552,6 +559,14 @@ $.widget( "ui.dialog", {
552559
.css( "position", position );
553560
},
554561

562+
_trackFocus: function() {
563+
this._on( this.widget(), {
564+
"focusin": function( event ) {
565+
this._focusedElement = $( event.target );
566+
}
567+
});
568+
},
569+
555570
_minHeight: function() {
556571
var options = this.options;
557572

0 commit comments

Comments
 (0)