@@ -35,7 +35,7 @@ $.widget( "ui.popup", {
35
35
36
36
if ( ! this . element . attr ( "role" ) ) {
37
37
// TODO alternatives to tooltip are dialog and menu, all three aren't generic popups
38
- this . element . attr ( "role" , "tooltip " ) ;
38
+ this . element . attr ( "role" , "dialog " ) ;
39
39
this . generatedRole = true ;
40
40
}
41
41
@@ -80,19 +80,22 @@ $.widget( "ui.popup", {
80
80
} ) ;
81
81
82
82
this . _bind ( this . element , {
83
- // TODO use focusout so that element itself doesn't need to be focussable
84
- blur : function ( event ) {
83
+ focusout : function ( event ) {
85
84
var that = this ;
86
85
// use a timer to allow click to clear it and letting that
87
86
// handle the closing instead of opening again
88
87
that . closeTimer = setTimeout ( function ( ) {
89
88
that . close ( event ) ;
90
89
} , 100 ) ;
91
- }
90
+ } ,
91
+ focusin : function ( event ) {
92
+ var that = this ;
93
+ clearTimeout ( that . closeTimer ) ;
94
+ }
92
95
} ) ;
93
96
94
97
this . _bind ( {
95
- // TODO only triggerd on element if it can receive focus
98
+ // TODO only triggered on element if it can receive focus
96
99
// bind to document instead?
97
100
// either element itself or a child should be focusable
98
101
keyup : function ( event ) {
@@ -141,14 +144,43 @@ $.widget( "ui.popup", {
141
144
. show ( )
142
145
. attr ( "aria-hidden" , false )
143
146
. attr ( "aria-expanded" , true )
144
- . position ( position )
145
- // TODO find a focussable child, otherwise put focus on element, add tabIndex=0 if not focussable
146
- . focus ( ) ;
147
-
148
- if ( this . element . is ( ":ui-menu" ) ) {
147
+ . position ( position ) ;
148
+
149
+ if ( this . element . is ( ":ui-menu" ) ) { //popup is a menu
150
+ this . element . focus ( ) ;
149
151
this . element . menu ( "focus" , event , this . element . children ( "li" ) . first ( ) ) ;
152
+ this . element . focus ( ) ;
150
153
}
151
-
154
+ // TODO add other special use cases that differ from the default dialog style keyboard mechanism
155
+ else {
156
+ //default use case, popup could be anything (e.g. a form)
157
+ this . element
158
+ . bind ( "keypress.ui-popup" , function ( event ) {
159
+ if ( event . keyCode !== $ . ui . keyCode . TAB ) {
160
+ return ;
161
+ }
162
+ var tabbables = $ ( ":tabbable" , this ) ,
163
+ first = tabbables . filter ( ":first" ) ,
164
+ last = tabbables . filter ( ":last" ) ;
165
+ if ( event . target === last [ 0 ] && ! event . shiftKey ) {
166
+ first . focus ( 1 ) ;
167
+ return false ;
168
+ } else if ( event . target === first [ 0 ] && event . shiftKey ) {
169
+ last . focus ( 1 ) ;
170
+ return false ;
171
+ }
172
+ } ) ;
173
+
174
+ // set focus to the first tabbable element in the popup container
175
+ // if there are no tabbable elements, set focus on the popup itself
176
+ var tabbables = this . element . find ( ":tabbable" ) ;
177
+ if ( ! tabbables . length ) {
178
+ this . element . attr ( "tabindex" , "0" ) ;
179
+ tabbables . add ( this . element ) ;
180
+ }
181
+ tabbables . eq ( 0 ) . focus ( 1 ) ;
182
+ }
183
+
152
184
// take trigger out of tab order to allow shift-tab to skip trigger
153
185
this . options . trigger . attr ( "tabindex" , - 1 ) ;
154
186
@@ -160,7 +192,8 @@ $.widget( "ui.popup", {
160
192
this . element
161
193
. hide ( )
162
194
. attr ( "aria-hidden" , true )
163
- . attr ( "aria-expanded" , false ) ;
195
+ . attr ( "aria-expanded" , false )
196
+ . unbind ( "keypress.ui-popup" ) ;
164
197
165
198
this . options . trigger . attr ( "tabindex" , 0 ) ;
166
199
0 commit comments