@@ -33,6 +33,7 @@ type FocusState = {
3333 isFocused : boolean ,
3434 isFocusVisible : boolean ,
3535 pointerType : PointerType ,
36+ isEmulatingMouseEvents : boolean ,
3637} ;
3738
3839type FocusProps = {
@@ -66,25 +67,12 @@ const isMac =
6667
6768const targetEventTypes = [ 'focus' , 'blur' ] ;
6869
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' ] ;
8876
8977function isFunction ( obj ) : boolean {
9078 return typeof obj === 'function' ;
@@ -110,21 +98,15 @@ function handleRootPointerEvent(
11098 state : FocusState ,
11199 callback : boolean => void ,
112100) : 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 ;
120102 isGlobalFocusVisible = false ;
121103
122104 // Focus should stop being visible if a pointer is used on the element
123105 // after it was focused using a keyboard.
124106 const focusTarget = state . focusTarget ;
125107 if (
126108 focusTarget !== null &&
127- context . isTargetWithinNode ( event . target , focusTarget ) &&
109+ context . isTargetWithinResponderScope ( focusTarget ) &&
128110 ( type === 'mousedown' || type === 'touchstart' || type === 'pointerdown' )
129111 ) {
130112 callback ( false ) ;
@@ -140,13 +122,6 @@ function handleRootEvent(
140122 const { type} = event ;
141123
142124 switch ( type ) {
143- case 'mousemove' :
144- case 'mousedown' :
145- case 'mouseup' : {
146- state . pointerType = 'mouse' ;
147- handleRootPointerEvent ( event , context , state , callback ) ;
148- break ;
149- }
150125 case 'pointermove' :
151126 case 'pointerdown' :
152127 case 'pointerup' : {
@@ -156,27 +131,45 @@ function handleRootEvent(
156131 handleRootPointerEvent ( event , context , state , callback ) ;
157132 break ;
158133 }
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
159159 case 'touchmove' :
160160 case 'touchstart' :
161161 case 'touchend' : {
162162 state . pointerType = 'touch' ;
163+ state . isEmulatingMouseEvents = true ;
163164 handleRootPointerEvent ( event , context , state , callback ) ;
164165 break ;
165166 }
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 ;
180173 }
181174 break ;
182175 }
@@ -271,6 +264,7 @@ const focusResponderImpl = {
271264 getInitialState ( ) : FocusState {
272265 return {
273266 focusTarget : null ,
267+ isEmulatingMouseEvents : false ,
274268 isFocused : false ,
275269 isFocusVisible : false ,
276270 pointerType : '' ,
@@ -303,6 +297,7 @@ const focusResponderImpl = {
303297 state . isFocusVisible = isGlobalFocusVisible ;
304298 dispatchFocusEvents ( context , props , state ) ;
305299 }
300+ state . isEmulatingMouseEvents = false ;
306301 break ;
307302 }
308303 case 'blur' : {
@@ -311,6 +306,17 @@ const focusResponderImpl = {
311306 state . isFocusVisible = isGlobalFocusVisible ;
312307 state . isFocused = false ;
313308 }
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 ;
314320 break ;
315321 }
316322 }
@@ -322,7 +328,7 @@ const focusResponderImpl = {
322328 state : FocusState ,
323329 ) : void {
324330 handleRootEvent ( event , context , state , isFocusVisible => {
325- if ( state . isFocusVisible !== isFocusVisible ) {
331+ if ( state . isFocused && state . isFocusVisible !== isFocusVisible ) {
326332 state . isFocusVisible = isFocusVisible ;
327333 dispatchFocusVisibleChangeEvent ( context , props , isFocusVisible ) ;
328334 }
@@ -402,6 +408,7 @@ const focusWithinResponderImpl = {
402408 getInitialState ( ) : FocusState {
403409 return {
404410 focusTarget : null ,
411+ isEmulatingMouseEvents : false ,
405412 isFocused : false ,
406413 isFocusVisible : false ,
407414 pointerType : '',
@@ -460,7 +467,7 @@ const focusWithinResponderImpl = {
460467 state : FocusState ,
461468 ) : void {
462469 handleRootEvent ( event , context , state , isFocusVisible => {
463- if ( state . isFocusVisible !== isFocusVisible ) {
470+ if ( state . isFocused && state . isFocusVisible !== isFocusVisible ) {
464471 state . isFocusVisible = isFocusVisible ;
465472 dispatchFocusWithinVisibleChangeEvent (
466473 context ,
0 commit comments