@@ -3,7 +3,10 @@ import {Observable} from 'rxjs/Observable';
33import { Subject } from 'rxjs/Subject' ;
44
55
6- export type FocusOrigin = 'mouse' | 'keyboard' | 'program' ;
6+ const TOUCH_BUFFER_MS = 650 ;
7+
8+
9+ export type FocusOrigin = 'touch' | 'mouse' | 'keyboard' | 'program' ;
710
811
912/** Monitors mouse and keyboard events to determine the cause of focus events. */
@@ -18,14 +21,59 @@ export class FocusOriginMonitor {
1821 /** Whether the window has just been focused. */
1922 private _windowFocused = false ;
2023
24+ /** Whether the current sequence of events was kicked off by a touch event. */
25+ private get _touchActive ( ) { return this . _touchActiveBacking ; }
26+ private set _touchActive ( value : boolean ) {
27+ this . _touchActiveBacking = value ;
28+
29+ if ( this . _touchActiveBacking ) {
30+ // Register a listener to unset the backing variable after a focus followed by a blur.
31+ // This is necessary to avoid accidentally counting a programmatic focus that occurs on a
32+ // touchstart event from being counted as a touch focus.
33+ document . addEventListener ( 'focus' , this . _touchFocusListener , true ) ;
34+ } else {
35+ // Remove the focus and blur listeners so they don't interfere in the future.
36+ document . removeEventListener ( 'focus' , this . _touchFocusListener , true ) ;
37+ document . removeEventListener ( 'blur' , this . _touchBlurListener , true ) ;
38+ }
39+ }
40+ private _touchActiveBacking = false ;
41+
42+ /** A focus listener that adds _touchBlurListener listener and then uninstalls itself. */
43+ private _touchFocusListener = ( ) => {
44+ document . addEventListener ( 'blur' , this . _touchBlurListener , true ) ;
45+ document . removeEventListener ( 'focus' , this . _touchFocusListener , true ) ;
46+ } ;
47+
48+ /** Blur listener that unsets the _touchActiveBacking and origin and then uninstalls itself. */
49+ private _touchBlurListener = ( ) => {
50+ this . _touchActiveBacking = false ;
51+ this . _origin = null ;
52+ document . removeEventListener ( 'blur' , this . _touchBlurListener , true ) ;
53+ } ;
54+
2155 constructor ( ) {
22- // Listen to keydown and mousedown in the capture phase so we can detect them even if the user
23- // stops propagation.
24- // TODO(mmalerba): Figure out how to handle touchstart
25- document . addEventListener (
26- 'keydown' , ( ) => this . _setOriginForCurrentEventQueue ( 'keyboard' ) , true ) ;
27- document . addEventListener (
28- 'mousedown' , ( ) => this . _setOriginForCurrentEventQueue ( 'mouse' ) , true ) ;
56+ // Note: we listen to events in the capture phase so we can detect them even if the user stops
57+ // propagation.
58+
59+ // On keydown record the origin and cancel any touch event that may be in progress.
60+ document . addEventListener ( 'keydown' , ( ) => {
61+ this . _touchActive = false ;
62+ this . _setOriginForCurrentEventQueue ( 'keyboard' ) ;
63+ } , true ) ;
64+
65+ // On mousedown record the origin, but do not cancel the touch event. A mousedown can happen as
66+ // a result of a touch event.
67+ document . addEventListener ( 'mousedown' ,
68+ ( ) => this . _setOriginForCurrentEventQueue ( 'mouse' ) , true ) ;
69+
70+ // When the touchstart event fires the focus event is not yet in the event queue. This means we
71+ // can't rely on the trick used above (setting timeout of 0ms). Instead we wait 650ms to see if
72+ // a focus happens.
73+ document . addEventListener ( 'touchstart' , ( ) => {
74+ this . _touchActive = true ;
75+ setTimeout ( ( ) => this . _touchActive = false , TOUCH_BUFFER_MS ) ;
76+ } , true ) ;
2977
3078 // Make a note of when the window regains focus, so we can restore the origin info for the
3179 // focused element.
@@ -45,6 +93,10 @@ export class FocusOriginMonitor {
4593
4694 /** Focuses the element via the specified focus origin. */
4795 focusVia ( element : Node , renderer : Renderer , origin : FocusOrigin ) {
96+ // This method may have been called as a result of a touchstart event, so we need to clear
97+ // _touchActive to prevent it from interfering.
98+ this . _touchActive = false ;
99+
48100 this . _setOriginForCurrentEventQueue ( origin ) ;
49101 renderer . invokeElementMethod ( element , 'focus' ) ;
50102 }
@@ -57,6 +109,10 @@ export class FocusOriginMonitor {
57109
58110 /** Handles focus events on a registered element. */
59111 private _onFocus ( element : Element , renderer : Renderer , subject : Subject < FocusOrigin > ) {
112+ if ( this . _touchActive ) {
113+ this . _origin = 'touch' ;
114+ }
115+
60116 // If we couldn't detect a cause for the focus event, it's due to one of two reasons:
61117 // 1) The window has just regained focus, in which case we want to restore the focused state of
62118 // the element from before the window blurred.
@@ -71,6 +127,7 @@ export class FocusOriginMonitor {
71127 }
72128
73129 renderer . setElementClass ( element , 'cdk-focused' , true ) ;
130+ renderer . setElementClass ( element , 'cdk-touch-focused' , this . _origin == 'touch' ) ;
74131 renderer . setElementClass ( element , 'cdk-keyboard-focused' , this . _origin == 'keyboard' ) ;
75132 renderer . setElementClass ( element , 'cdk-mouse-focused' , this . _origin == 'mouse' ) ;
76133 renderer . setElementClass ( element , 'cdk-program-focused' , this . _origin == 'program' ) ;
@@ -83,6 +140,7 @@ export class FocusOriginMonitor {
83140 /** Handles blur events on a registered element. */
84141 private _onBlur ( element : Element , renderer : Renderer , subject : Subject < FocusOrigin > ) {
85142 renderer . setElementClass ( element , 'cdk-focused' , false ) ;
143+ renderer . setElementClass ( element , 'cdk-touch-focused' , false ) ;
86144 renderer . setElementClass ( element , 'cdk-keyboard-focused' , false ) ;
87145 renderer . setElementClass ( element , 'cdk-mouse-focused' , false ) ;
88146 renderer . setElementClass ( element , 'cdk-program-focused' , false ) ;
0 commit comments