@@ -3,7 +3,10 @@ import {Observable} from 'rxjs/Observable';
3
3
import { Subject } from 'rxjs/Subject' ;
4
4
5
5
6
- export type FocusOrigin = 'mouse' | 'keyboard' | 'program' ;
6
+ const TOUCH_BUFFER_MS = 650 ;
7
+
8
+
9
+ export type FocusOrigin = 'touch' | 'mouse' | 'keyboard' | 'program' ;
7
10
8
11
9
12
/** Monitors mouse and keyboard events to determine the cause of focus events. */
@@ -18,14 +21,59 @@ export class FocusOriginMonitor {
18
21
/** Whether the window has just been focused. */
19
22
private _windowFocused = false ;
20
23
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
+
21
55
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 ) ;
29
77
30
78
// Make a note of when the window regains focus, so we can restore the origin info for the
31
79
// focused element.
@@ -45,6 +93,10 @@ export class FocusOriginMonitor {
45
93
46
94
/** Focuses the element via the specified focus origin. */
47
95
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
+
48
100
this . _setOriginForCurrentEventQueue ( origin ) ;
49
101
renderer . invokeElementMethod ( element , 'focus' ) ;
50
102
}
@@ -57,6 +109,10 @@ export class FocusOriginMonitor {
57
109
58
110
/** Handles focus events on a registered element. */
59
111
private _onFocus ( element : Element , renderer : Renderer , subject : Subject < FocusOrigin > ) {
112
+ if ( this . _touchActive ) {
113
+ this . _origin = 'touch' ;
114
+ }
115
+
60
116
// If we couldn't detect a cause for the focus event, it's due to one of two reasons:
61
117
// 1) The window has just regained focus, in which case we want to restore the focused state of
62
118
// the element from before the window blurred.
@@ -71,6 +127,7 @@ export class FocusOriginMonitor {
71
127
}
72
128
73
129
renderer . setElementClass ( element , 'cdk-focused' , true ) ;
130
+ renderer . setElementClass ( element , 'cdk-touch-focused' , this . _origin == 'touch' ) ;
74
131
renderer . setElementClass ( element , 'cdk-keyboard-focused' , this . _origin == 'keyboard' ) ;
75
132
renderer . setElementClass ( element , 'cdk-mouse-focused' , this . _origin == 'mouse' ) ;
76
133
renderer . setElementClass ( element , 'cdk-program-focused' , this . _origin == 'program' ) ;
@@ -83,6 +140,7 @@ export class FocusOriginMonitor {
83
140
/** Handles blur events on a registered element. */
84
141
private _onBlur ( element : Element , renderer : Renderer , subject : Subject < FocusOrigin > ) {
85
142
renderer . setElementClass ( element , 'cdk-focused' , false ) ;
143
+ renderer . setElementClass ( element , 'cdk-touch-focused' , false ) ;
86
144
renderer . setElementClass ( element , 'cdk-keyboard-focused' , false ) ;
87
145
renderer . setElementClass ( element , 'cdk-mouse-focused' , false ) ;
88
146
renderer . setElementClass ( element , 'cdk-program-focused' , false ) ;
0 commit comments