@@ -196,6 +196,13 @@ angular.module('ui.bootstrap.modal', [])
196
196
NOW_CLOSING_EVENT : 'modal.stack.now-closing'
197
197
} ;
198
198
199
+ //Modal focus behavior
200
+ var focusableElementList ;
201
+ var focusIndex = 0 ;
202
+ var tababbleSelector = 'a[href], area[href], input:not([disabled]), ' +
203
+ 'button:not([disabled]),select:not([disabled]), textarea:not([disabled]), ' +
204
+ 'iframe, object, embed, *[tabindex], *[contenteditable=true]' ;
205
+
199
206
function backdropIndex ( ) {
200
207
var topBackdropIndex = - 1 ;
201
208
var opened = openedWindows . keys ( ) ;
@@ -207,7 +214,7 @@ angular.module('ui.bootstrap.modal', [])
207
214
return topBackdropIndex ;
208
215
}
209
216
210
- $rootScope . $watch ( backdropIndex , function ( newBackdropIndex ) {
217
+ $rootScope . $watch ( backdropIndex , function ( newBackdropIndex ) {
211
218
if ( backdropScope ) {
212
219
backdropScope . index = newBackdropIndex ;
213
220
}
@@ -281,15 +288,35 @@ angular.module('ui.bootstrap.modal', [])
281
288
}
282
289
283
290
$document . bind ( 'keydown' , function ( evt ) {
284
- var modal ;
291
+ var modal = openedWindows . top ( ) ;
292
+ if ( modal && modal . value . keyboard ) {
293
+ switch ( evt . which ) {
294
+ case 27 : {
295
+ evt . preventDefault ( ) ;
296
+ $rootScope . $apply ( function ( ) {
297
+ $modalStack . dismiss ( modal . key , 'escape key press' ) ;
298
+ } ) ;
299
+ break ;
300
+ }
301
+ case 9 : {
302
+ $modalStack . loadFocusElementList ( modal ) ;
303
+ var focusChanged = false ;
304
+ if ( evt . shiftKey ) {
305
+ if ( $modalStack . isFocusInFirstItem ( evt ) ) {
306
+ focusChanged = $modalStack . focusLastFocusableElement ( ) ;
307
+ }
308
+ } else {
309
+ if ( $modalStack . isFocusInLastItem ( evt ) ) {
310
+ focusChanged = $modalStack . focusFirstFocusableElement ( ) ;
311
+ }
312
+ }
285
313
286
- if ( evt . which === 27 ) {
287
- modal = openedWindows . top ( ) ;
288
- if ( modal && modal . value . keyboard ) {
289
- evt . preventDefault ( ) ;
290
- $rootScope . $apply ( function ( ) {
291
- $modalStack . dismiss ( modal . key , 'escape key press' ) ;
292
- } ) ;
314
+ if ( focusChanged ) {
315
+ evt . preventDefault ( ) ;
316
+ evt . stopPropagation ( ) ;
317
+ }
318
+ break ;
319
+ }
293
320
}
294
321
}
295
322
} ) ;
@@ -338,6 +365,7 @@ angular.module('ui.bootstrap.modal', [])
338
365
openedWindows . top ( ) . value . modalOpener = modalOpener ;
339
366
body . append ( modalDomEl ) ;
340
367
body . addClass ( OPENED_MODAL_CLASS ) ;
368
+ $modalStack . clearFocusListCache ( ) ;
341
369
} ;
342
370
343
371
function broadcastClosing ( modalWindow , resultOrReason , closing ) {
@@ -382,6 +410,51 @@ angular.module('ui.bootstrap.modal', [])
382
410
}
383
411
} ;
384
412
413
+ $modalStack . focusFirstFocusableElement = function ( ) {
414
+ if ( focusableElementList . length > 0 ) {
415
+ focusableElementList [ 0 ] . focus ( ) ;
416
+ return true ;
417
+ }
418
+ return false ;
419
+ } ;
420
+ $modalStack . focusLastFocusableElement = function ( ) {
421
+ if ( focusableElementList . length > 0 ) {
422
+ focusableElementList [ focusableElementList . length - 1 ] . focus ( ) ;
423
+ return true ;
424
+ }
425
+ return false ;
426
+ } ;
427
+
428
+ $modalStack . isFocusInFirstItem = function ( evt ) {
429
+ if ( focusableElementList . length > 0 ) {
430
+ return ( evt . target || evt . srcElement ) == focusableElementList [ 0 ] ;
431
+ }
432
+ return false ;
433
+ } ;
434
+
435
+ $modalStack . isFocusInLastItem = function ( evt ) {
436
+ if ( focusableElementList . length > 0 ) {
437
+ return ( evt . target || evt . srcElement ) == focusableElementList [ focusableElementList . length - 1 ] ;
438
+ }
439
+ return false ;
440
+ } ;
441
+
442
+ $modalStack . clearFocusListCache = function ( ) {
443
+ focusableElementList = [ ] ;
444
+ focusIndex = 0 ;
445
+ } ;
446
+
447
+ $modalStack . loadFocusElementList = function ( modalWindow ) {
448
+ if ( focusableElementList === undefined || ! focusableElementList . length0 ) {
449
+ if ( modalWindow ) {
450
+ var modalDomE1 = modalWindow . value . modalDomEl ;
451
+ if ( modalDomE1 && modalDomE1 . length ) {
452
+ focusableElementList = modalDomE1 [ 0 ] . querySelectorAll ( tababbleSelector ) ;
453
+ }
454
+ }
455
+ }
456
+ } ;
457
+
385
458
return $modalStack ;
386
459
} ] )
387
460
0 commit comments