1- import  { Component ,   ViewEncapsulation ,   ViewChild ,   ElementRef ,   Input ,   NgZone }  from  '@angular/core' ; 
1+ import  { Directive ,   ElementRef ,   Input ,   NgZone ,   AfterViewInit ,   OnDestroy }  from  '@angular/core' ; 
22import  { InteractivityChecker }  from  './interactivity-checker' ; 
33import  { coerceBooleanProperty }  from  '../coercion/boolean-property' ; 
44
@@ -11,48 +11,72 @@ import {coerceBooleanProperty} from '../coercion/boolean-property';
1111 * Things like tabIndex > 0, flex `order`, and shadow roots can cause to two to misalign. 
1212 * This will be replaced with a more intelligent solution before the library is considered stable. 
1313 */ 
14- @Component ( { 
15-   moduleId : module . id , 
16-   selector : 'cdk-focus-trap, focus-trap' , 
17-   templateUrl : 'focus-trap.html' , 
18-   encapsulation : ViewEncapsulation . None , 
14+ @Directive ( { 
15+   selector : 'cdk-focus-trap, focus-trap, [cdk-focus-trap], [focus-trap]' , 
1916} ) 
20- export  class  FocusTrap  { 
21-   @ViewChild ( 'trappedContent' )  trappedContent : ElementRef ; 
17+ export  class  FocusTrap  implements  AfterViewInit ,  OnDestroy  { 
18+   private  _startAnchor : HTMLElement  =  this . _createAnchor ( ) ; 
19+   private  _endAnchor : HTMLElement  =  this . _createAnchor ( ) ; 
2220
2321  /** Whether the focus trap is active. */ 
2422  @Input ( ) 
2523  get  disabled ( ) : boolean  {  return  this . _disabled ;  } 
26-   set  disabled ( val : boolean )  {  this . _disabled  =  coerceBooleanProperty ( val ) ;  } 
24+   set  disabled ( val : boolean )  { 
25+     this . _disabled  =  coerceBooleanProperty ( val ) ; 
26+     this . _startAnchor . tabIndex  =  this . _endAnchor . tabIndex  =  this . _disabled  ? - 1  : 0 ; 
27+   } 
2728  private  _disabled : boolean  =  false ; 
2829
29-   constructor ( private  _checker : InteractivityChecker ,  private  _ngZone : NgZone )  {  } 
30+   constructor ( 
31+     private  _checker : InteractivityChecker , 
32+     private  _ngZone : NgZone , 
33+     private  _elementRef : ElementRef )  {  } 
34+ 
35+   ngAfterViewInit ( )  { 
36+     this . _ngZone . runOutsideAngular ( ( )  =>  { 
37+       this . _elementRef . nativeElement 
38+         . insertAdjacentElement ( 'beforebegin' ,  this . _startAnchor ) 
39+         . addEventListener ( 'focus' ,  ( )  =>  this . focusLastTabbableElement ( ) ) ; 
40+ 
41+       this . _elementRef . nativeElement 
42+         . insertAdjacentElement ( 'afterend' ,  this . _endAnchor ) 
43+         . addEventListener ( 'focus' ,  ( )  =>  this . focusFirstTabbableElement ( ) ) ; 
44+     } ) ; 
45+   } 
46+ 
47+   ngOnDestroy ( )  { 
48+     if  ( this . _startAnchor . parentNode )  { 
49+       this . _startAnchor . parentNode . removeChild ( this . _startAnchor ) ; 
50+     } 
51+ 
52+     if  ( this . _endAnchor . parentNode )  { 
53+       this . _endAnchor . parentNode . removeChild ( this . _endAnchor ) ; 
54+     } 
55+ 
56+     this . _startAnchor  =  this . _endAnchor  =  null ; 
57+   } 
3058
3159  /** 
3260   * Waits for microtask queue to empty, then focuses the first tabbable element within the focus 
3361   * trap region. 
3462   */ 
3563  focusFirstTabbableElementWhenReady ( )  { 
36-     this . _ngZone . onMicrotaskEmpty . first ( ) . subscribe ( ( )  =>  { 
37-       this . focusFirstTabbableElement ( ) ; 
38-     } ) ; 
64+     this . _ngZone . onMicrotaskEmpty . first ( ) . subscribe ( ( )  =>  this . focusFirstTabbableElement ( ) ) ; 
3965  } 
4066
4167  /** 
4268   * Waits for microtask queue to empty, then focuses the last tabbable element within the focus 
4369   * trap region. 
4470   */ 
4571  focusLastTabbableElementWhenReady ( )  { 
46-     this . _ngZone . onMicrotaskEmpty . first ( ) . subscribe ( ( )  =>  { 
47-       this . focusLastTabbableElement ( ) ; 
48-     } ) ; 
72+     this . _ngZone . onMicrotaskEmpty . first ( ) . subscribe ( ( )  =>  this . focusLastTabbableElement ( ) ) ; 
4973  } 
5074
5175  /** 
5276   * Focuses the first tabbable element within the focus trap region. 
5377   */ 
5478  focusFirstTabbableElement ( )  { 
55-     let  rootElement  =  this . trappedContent . nativeElement ; 
79+     let  rootElement  =  this . _elementRef . nativeElement ; 
5680    let  redirectToElement  =  rootElement . querySelector ( '[cdk-focus-start]' )  as  HTMLElement  || 
5781                            this . _getFirstTabbableElement ( rootElement ) ; 
5882
@@ -65,14 +89,13 @@ export class FocusTrap {
6589   * Focuses the last tabbable element within the focus trap region. 
6690   */ 
6791  focusLastTabbableElement ( )  { 
68-     let  rootElement  =  this . trappedContent . nativeElement ; 
69-     let  focusTargets  =  rootElement . querySelectorAll ( '[cdk-focus-end]' ) ; 
92+     let  focusTargets  =  this . _elementRef . nativeElement . querySelectorAll ( '[cdk-focus-end]' ) ; 
7093    let  redirectToElement : HTMLElement  =  null ; 
7194
7295    if  ( focusTargets . length )  { 
7396      redirectToElement  =  focusTargets [ focusTargets . length  -  1 ]  as  HTMLElement ; 
7497    }  else  { 
75-       redirectToElement  =  this . _getLastTabbableElement ( rootElement ) ; 
98+       redirectToElement  =  this . _getLastTabbableElement ( this . _elementRef . nativeElement ) ; 
7699    } 
77100
78101    if  ( redirectToElement )  { 
@@ -114,4 +137,11 @@ export class FocusTrap {
114137
115138    return  null ; 
116139  } 
140+ 
141+   private  _createAnchor ( ) : HTMLElement  { 
142+     let  anchor  =  document . createElement ( 'div' ) ; 
143+     anchor . tabIndex  =  0 ; 
144+     anchor . classList . add ( 'cdk-visually-hidden' ) ; 
145+     return  anchor ; 
146+   } 
117147} 
0 commit comments