13
13
* jquery.ui.position.js
14
14
*/
15
15
( function ( $ ) {
16
-
16
+
17
17
var idIncrement = 0 ;
18
18
19
19
$ . widget ( "ui.popup" , {
@@ -28,34 +28,34 @@ $.widget( "ui.popup", {
28
28
if ( ! this . options . trigger ) {
29
29
this . options . trigger = this . element . prev ( ) ;
30
30
}
31
-
31
+
32
32
if ( ! this . element . attr ( "id" ) ) {
33
33
this . element . attr ( "id" , "ui-popup-" + idIncrement ++ ) ;
34
34
this . generatedId = true ;
35
35
}
36
-
36
+
37
37
if ( ! this . element . attr ( "role" ) ) {
38
38
// TODO alternatives to tooltip are dialog and menu, all three aren't generic popups
39
- this . element . attr ( "role" , "tooltip " ) ;
39
+ this . element . attr ( "role" , "dialog " ) ;
40
40
this . generatedRole = true ;
41
41
}
42
-
42
+
43
43
this . options . trigger
44
44
. attr ( "aria-haspopup" , true )
45
45
. attr ( "aria-owns" , this . element . attr ( "id" ) ) ;
46
-
46
+
47
47
this . element
48
- . addClass ( "ui-popup" )
48
+ . addClass ( "ui-popup" )
49
49
this . close ( ) ;
50
50
51
51
this . _bind ( this . options . trigger , {
52
52
keydown : function ( event ) {
53
- // prevent space-to-open to scroll the page, only hapens for anchor ui.button
54
- if ( this . options . trigger . is ( "a:ui-button" ) && event . keyCode == $ . ui . keyCode . SPACE ) {
55
- event . preventDefault ( )
53
+ // prevent space-to-open to scroll the page, only happens for anchor ui.button
54
+ if ( this . options . trigger . is ( "a:ui-button" ) && event . keyCode == $ . ui . keyCode . SPACE ) {
55
+ event . preventDefault ( ) ;
56
56
}
57
57
// TODO handle SPACE to open popup? only when not handled by ui.button
58
- if ( event . keyCode == $ . ui . keyCode . SPACE && this . options . trigger . is ( "a:not(:ui-button)" ) ) {
58
+ if ( event . keyCode == $ . ui . keyCode . SPACE && this . options . trigger . is ( "a:not(:ui-button)" ) ) {
59
59
this . options . trigger . trigger ( "click" , event ) ;
60
60
}
61
61
// translate keydown to click
@@ -79,60 +79,83 @@ $.widget( "ui.popup", {
79
79
} , 1 ) ;
80
80
}
81
81
} ) ;
82
-
83
- this . _bind ( this . element , {
84
- // TODO use focusout so that element itself doesn't need to be focussable
85
- blur : function ( event ) {
82
+
83
+ if ( ! this . element . is ( ":ui-menu" ) ) {
84
+ //default use case, wrap tab order in popup
85
+ this . _bind ( { keydown : function ( event ) {
86
+ if ( event . keyCode !== $ . ui . keyCode . TAB ) {
87
+ return ;
88
+ }
89
+ var tabbables = $ ( ":tabbable" , this . element ) ,
90
+ first = tabbables . first ( ) ,
91
+ last = tabbables . last ( ) ;
92
+ if ( event . target === last [ 0 ] && ! event . shiftKey ) {
93
+ first . focus ( 1 ) ;
94
+ event . preventDefault ( ) ;
95
+ } else if ( event . target === first [ 0 ] && event . shiftKey ) {
96
+ last . focus ( 1 ) ;
97
+ event . preventDefault ( ) ;
98
+ }
99
+ }
100
+ } ) ;
101
+ }
102
+
103
+ this . _bind ( {
104
+ focusout : function ( event ) {
86
105
var that = this ;
87
106
// use a timer to allow click to clear it and letting that
88
107
// handle the closing instead of opening again
89
108
that . closeTimer = setTimeout ( function ( ) {
90
109
that . close ( event ) ;
91
110
} , 100 ) ;
111
+ } ,
112
+ focusin : function ( event ) {
113
+ clearTimeout ( this . closeTimer ) ;
92
114
}
93
115
} ) ;
94
116
95
117
this . _bind ( {
96
- // TODO only triggerd on element if it can receive focus
118
+ // TODO only triggered on element if it can receive focus
97
119
// bind to document instead?
98
120
// either element itself or a child should be focusable
99
121
keyup : function ( event ) {
100
- if ( event . keyCode == $ . ui . keyCode . ESCAPE && this . element . is ( ":visible" ) ) {
122
+ if ( event . keyCode == $ . ui . keyCode . ESCAPE && this . element . is ( ":visible" ) ) {
101
123
this . close ( event ) ;
102
124
// TODO move this to close()? would allow menu.select to call popup.close, and get focus back to trigger
103
125
this . options . trigger . focus ( ) ;
104
126
}
105
127
}
106
128
} ) ;
107
-
129
+
108
130
this . _bind ( document , {
109
131
click : function ( event ) {
110
- if ( this . isOpen && ! $ ( event . target ) . closest ( ".ui-popup" ) . length ) {
132
+ if ( this . isOpen && ! $ ( event . target ) . closest ( ".ui-popup" ) . length ) {
111
133
this . close ( event ) ;
112
134
}
113
135
}
114
136
} )
115
137
} ,
116
-
138
+
117
139
_destroy : function ( ) {
118
140
this . element
119
141
. show ( )
120
142
. removeClass ( "ui-popup" )
121
143
. removeAttr ( "aria-hidden" )
122
- . removeAttr ( "aria-expanded" ) ;
144
+ . removeAttr ( "aria-expanded" )
145
+ . unbind ( "keypress.ui-popup" ) ;
123
146
124
147
this . options . trigger
125
148
. removeAttr ( "aria-haspopup" )
126
149
. removeAttr ( "aria-owns" ) ;
127
-
150
+
128
151
if ( this . generatedId ) {
129
152
this . element . removeAttr ( "id" ) ;
130
153
}
131
154
if ( this . generatedRole ) {
132
155
this . element . removeAttr ( "role" ) ;
133
156
}
134
157
} ,
135
-
158
+
136
159
open : function ( event ) {
137
160
var position = $ . extend ( { } , {
138
161
of : this . options . trigger
@@ -142,17 +165,28 @@ $.widget( "ui.popup", {
142
165
. show ( )
143
166
. attr ( "aria-hidden" , false )
144
167
. attr ( "aria-expanded" , true )
145
- . position ( position )
146
- // TODO find a focussable child, otherwise put focus on element, add tabIndex=0 if not focussable
147
- . focus ( ) ;
168
+ . position ( position ) ;
148
169
149
- if ( this . element . is ( ":ui-menu" ) ) {
150
- this . element . menu ( "focus" , event , this . element . children ( "li" ) . first ( ) ) ;
170
+ if ( this . element . is ( ":ui-menu" ) ) { //popup is a menu
171
+ this . element . menu ( "focus" , event , this . element . children ( "li" ) . first ( ) ) ;
172
+ this . element . focus ( ) ;
173
+ } else {
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
+ this . removeTabIndex = false ;
178
+ if ( ! tabbables . length ) {
179
+ if ( ! this . element . is ( ":tabbable" ) ) {
180
+ this . element . attr ( "tabindex" , "0" ) ;
181
+ this . removeTabIndex = true ;
182
+ }
183
+ tabbables = tabbables . add ( this . element [ 0 ] ) ;
184
+ }
185
+ tabbables . first ( ) . focus ( 1 ) ;
151
186
}
152
187
153
188
// take trigger out of tab order to allow shift-tab to skip trigger
154
- this . options . trigger . attr ( "tabindex" , - 1 ) ;
155
-
189
+ this . options . trigger . attr ( "tabindex" , - 1 ) ;
156
190
this . isOpen = true ;
157
191
this . _trigger ( "open" , event ) ;
158
192
} ,
@@ -163,13 +197,13 @@ $.widget( "ui.popup", {
163
197
. attr ( "aria-hidden" , true )
164
198
. attr ( "aria-expanded" , false ) ;
165
199
166
- this . options . trigger . attr ( "tabindex" , 0 ) ;
167
-
200
+ this . options . trigger . attr ( "tabindex" , 0 ) ;
201
+ if ( this . removeTabIndex ) {
202
+ this . element . removeAttr ( "tabindex" ) ;
203
+ }
168
204
this . isOpen = false ;
169
205
this . _trigger ( "close" , event ) ;
170
206
}
171
-
172
-
173
207
} ) ;
174
208
175
209
} ( jQuery ) ) ;
0 commit comments