@@ -33,6 +33,7 @@ type FocusState = {
33
33
isFocused : boolean ,
34
34
isFocusVisible : boolean ,
35
35
pointerType : PointerType ,
36
+ isEmulatingMouseEvents : boolean ,
36
37
} ;
37
38
38
39
type FocusProps = {
@@ -66,25 +67,12 @@ const isMac =
66
67
67
68
const targetEventTypes = [ 'focus' , 'blur' ] ;
68
69
69
- const rootEventTypes = [
70
- 'keydown' ,
71
- 'keyup' ,
72
- 'pointermove' ,
73
- 'pointerdown' ,
74
- 'pointerup' ,
75
- ] ;
76
-
77
- // If PointerEvents is not supported (e.g., Safari), also listen to touch and mouse events.
78
- if ( typeof window !== 'undefined' && window . PointerEvent === undefined ) {
79
- rootEventTypes . push (
80
- 'mousemove' ,
81
- 'mousedown' ,
82
- 'mouseup' ,
83
- 'touchmove' ,
84
- 'touchstart' ,
85
- 'touchend' ,
86
- ) ;
87
- }
70
+ const hasPointerEvents =
71
+ typeof window !== 'undefined' && window . PointerEvent != null ;
72
+
73
+ const rootEventTypes = hasPointerEvents
74
+ ? [ 'keydown' , 'keyup' , 'pointermove' , 'pointerdown' , 'pointerup' ]
75
+ : [ 'keydown' , 'keyup' , 'mousedown' , 'touchmove' , 'touchstart' , 'touchend' ] ;
88
76
89
77
function isFunction ( obj ) : boolean {
90
78
return typeof obj === 'function' ;
@@ -110,21 +98,15 @@ function handleRootPointerEvent(
110
98
state : FocusState ,
111
99
callback : boolean => void ,
112
100
) : void {
113
- const { type, target} = event ;
114
- // Ignore a Safari quirks where 'mousemove' is dispatched on the 'html'
115
- // element when the window blurs.
116
- if ( type === 'mousemove' && target . nodeName === 'HTML' ) {
117
- return ;
118
- }
119
-
101
+ const { type} = event ;
120
102
isGlobalFocusVisible = false ;
121
103
122
104
// Focus should stop being visible if a pointer is used on the element
123
105
// after it was focused using a keyboard.
124
106
const focusTarget = state . focusTarget ;
125
107
if (
126
108
focusTarget !== null &&
127
- context . isTargetWithinNode ( event . target , focusTarget ) &&
109
+ context . isTargetWithinResponderScope ( focusTarget ) &&
128
110
( type === 'mousedown' || type === 'touchstart' || type === 'pointerdown' )
129
111
) {
130
112
callback ( false ) ;
@@ -140,13 +122,6 @@ function handleRootEvent(
140
122
const { type} = event ;
141
123
142
124
switch ( type ) {
143
- case 'mousemove' :
144
- case 'mousedown' :
145
- case 'mouseup' : {
146
- state . pointerType = 'mouse' ;
147
- handleRootPointerEvent ( event , context , state , callback ) ;
148
- break ;
149
- }
150
125
case 'pointermove' :
151
126
case 'pointerdown' :
152
127
case 'pointerup' : {
@@ -156,27 +131,45 @@ function handleRootEvent(
156
131
handleRootPointerEvent ( event , context , state , callback ) ;
157
132
break ;
158
133
}
134
+
135
+ case 'keydown' :
136
+ case 'keyup' : {
137
+ const nativeEvent = event . nativeEvent ;
138
+ const focusTarget = state . focusTarget ;
139
+ const { key, metaKey, altKey, ctrlKey} = ( nativeEvent : any ) ;
140
+ const validKey =
141
+ key === 'Enter' ||
142
+ key === ' ' ||
143
+ ( key === 'Tab' && ! ( metaKey || ( ! isMac && altKey ) || ctrlKey ) ) ;
144
+
145
+ if ( validKey ) {
146
+ state . pointerType = 'keyboard' ;
147
+ isGlobalFocusVisible = true ;
148
+ if (
149
+ focusTarget !== null &&
150
+ context . isTargetWithinResponderScope ( focusTarget )
151
+ ) {
152
+ callback ( true ) ;
153
+ }
154
+ }
155
+ break ;
156
+ }
157
+
158
+ // fallbacks for no PointerEvent support
159
159
case 'touchmove' :
160
160
case 'touchstart' :
161
161
case 'touchend' : {
162
162
state . pointerType = 'touch' ;
163
+ state . isEmulatingMouseEvents = true ;
163
164
handleRootPointerEvent ( event , context , state , callback ) ;
164
165
break ;
165
166
}
166
-
167
- case 'keydown' :
168
- case 'keyup' : {
169
- const nativeEvent = event . nativeEvent ;
170
- if (
171
- nativeEvent . key === 'Tab' &&
172
- ! (
173
- nativeEvent . metaKey ||
174
- ( ! isMac && nativeEvent . altKey ) ||
175
- nativeEvent . ctrlKey
176
- )
177
- ) {
178
- state . pointerType = 'keyboard' ;
179
- isGlobalFocusVisible = true ;
167
+ case 'mousedown' : {
168
+ if ( ! state . isEmulatingMouseEvents ) {
169
+ state . pointerType = 'mouse' ;
170
+ handleRootPointerEvent ( event , context , state , callback ) ;
171
+ } else {
172
+ state . isEmulatingMouseEvents = false ;
180
173
}
181
174
break ;
182
175
}
@@ -271,6 +264,7 @@ const focusResponderImpl = {
271
264
getInitialState ( ) : FocusState {
272
265
return {
273
266
focusTarget : null ,
267
+ isEmulatingMouseEvents : false ,
274
268
isFocused : false ,
275
269
isFocusVisible : false ,
276
270
pointerType : '' ,
@@ -303,6 +297,7 @@ const focusResponderImpl = {
303
297
state . isFocusVisible = isGlobalFocusVisible ;
304
298
dispatchFocusEvents ( context , props , state ) ;
305
299
}
300
+ state . isEmulatingMouseEvents = false ;
306
301
break ;
307
302
}
308
303
case 'blur' : {
@@ -311,6 +306,17 @@ const focusResponderImpl = {
311
306
state . isFocusVisible = isGlobalFocusVisible ;
312
307
state . isFocused = false ;
313
308
}
309
+ // This covers situations where focus is lost to another document in
310
+ // the same window (e.g., iframes). Any action that restores focus to
311
+ // the document (e.g., touch or click) first causes 'focus' to be
312
+ // dispatched, which means the 'pointerType' we provide is stale
313
+ // (it reflects the *previous* pointer). We cannot determine the
314
+ // 'pointerType' in this case, so a blur with no
315
+ // relatedTarget is used as a signal to reset the 'pointerType'.
316
+ if ( event . nativeEvent . relatedTarget == null ) {
317
+ state . pointerType = '' ;
318
+ }
319
+ state . isEmulatingMouseEvents = false ;
314
320
break ;
315
321
}
316
322
}
@@ -322,7 +328,7 @@ const focusResponderImpl = {
322
328
state : FocusState ,
323
329
) : void {
324
330
handleRootEvent ( event , context , state , isFocusVisible => {
325
- if ( state . isFocusVisible !== isFocusVisible ) {
331
+ if ( state . isFocused && state . isFocusVisible !== isFocusVisible ) {
326
332
state . isFocusVisible = isFocusVisible ;
327
333
dispatchFocusVisibleChangeEvent ( context , props , isFocusVisible ) ;
328
334
}
@@ -402,6 +408,7 @@ const focusWithinResponderImpl = {
402
408
getInitialState ( ) : FocusState {
403
409
return {
404
410
focusTarget : null ,
411
+ isEmulatingMouseEvents : false ,
405
412
isFocused : false ,
406
413
isFocusVisible : false ,
407
414
pointerType : '',
@@ -460,7 +467,7 @@ const focusWithinResponderImpl = {
460
467
state : FocusState ,
461
468
) : void {
462
469
handleRootEvent ( event , context , state , isFocusVisible => {
463
- if ( state . isFocusVisible !== isFocusVisible ) {
470
+ if ( state . isFocused && state . isFocusVisible !== isFocusVisible ) {
464
471
state . isFocusVisible = isFocusVisible ;
465
472
dispatchFocusWithinVisibleChangeEvent (
466
473
context ,
0 commit comments