@@ -26,13 +26,16 @@ type FocusState = {
2626 focusTarget : null | Element | Document ,
2727 isFocused : boolean ,
2828 isLocalFocusVisible : boolean ,
29+ pointerType : PointerType ,
2930} ;
3031
32+ type PointerType = '' | 'mouse' | 'keyboard' | 'pen' | 'touch' ;
3133type FocusEventType = 'focus' | 'blur' | 'focuschange' | 'focusvisiblechange' ;
3234
3335type FocusEvent = { |
3436 target : Element | Document ,
3537 type : FocusEventType ,
38+ pointerType : PointerType ,
3639 timeStamp : number ,
3740| } ;
3841
@@ -45,25 +48,33 @@ const rootEventTypes = [
4548 'keydown' ,
4649 'keypress' ,
4750 'keyup' ,
48- 'mousemove' ,
49- 'mousedown' ,
50- 'mouseup' ,
5151 'pointermove' ,
5252 'pointerdown' ,
5353 'pointerup' ,
54- 'touchmove' ,
55- 'touchstart' ,
56- 'touchend' ,
5754] ;
5855
56+ // If PointerEvents is not supported (e.g., Safari), also listen to touch and mouse events.
57+ if ( typeof window !== 'undefined' && window . PointerEvent === undefined ) {
58+ rootEventTypes . push (
59+ 'mousemove' ,
60+ 'mousedown' ,
61+ 'mouseup' ,
62+ 'touchmove' ,
63+ 'touchstart' ,
64+ 'touchend' ,
65+ ) ;
66+ }
67+
5968function createFocusEvent (
6069 context : ReactResponderContext ,
6170 type : FocusEventType ,
6271 target : Element | Document ,
72+ pointerType : PointerType ,
6373) : FocusEvent {
6474 return {
6575 target,
6676 type,
77+ pointerType,
6778 timeStamp : context . getTimeStamp ( ) ,
6879 } ;
6980}
@@ -73,16 +84,27 @@ function dispatchFocusInEvents(
7384 props : FocusProps ,
7485 state : FocusState ,
7586) {
87+ const pointerType = state . pointerType ;
7688 const target = ( ( state . focusTarget : any ) : Element | Document ) ;
7789 if ( props . onFocus ) {
78- const syntheticEvent = createFocusEvent ( context , 'focus' , target ) ;
90+ const syntheticEvent = createFocusEvent (
91+ context ,
92+ 'focus' ,
93+ target ,
94+ pointerType ,
95+ ) ;
7996 context . dispatchEvent ( syntheticEvent , props . onFocus , { discrete : true } ) ;
8097 }
8198 if ( props . onFocusChange ) {
8299 const listener = ( ) => {
83100 props . onFocusChange ( true ) ;
84101 } ;
85- const syntheticEvent = createFocusEvent ( context , 'focuschange' , target ) ;
102+ const syntheticEvent = createFocusEvent (
103+ context ,
104+ 'focuschange' ,
105+ target ,
106+ pointerType ,
107+ ) ;
86108 context . dispatchEvent ( syntheticEvent , listener , { discrete : true } ) ;
87109 }
88110 if ( props . onFocusVisibleChange && state . isLocalFocusVisible ) {
@@ -93,6 +115,7 @@ function dispatchFocusInEvents(
93115 context ,
94116 'focusvisiblechange' ,
95117 target ,
118+ pointerType ,
96119 ) ;
97120 context . dispatchEvent ( syntheticEvent , listener , { discrete : true } ) ;
98121 }
@@ -103,16 +126,27 @@ function dispatchFocusOutEvents(
103126 props : FocusProps ,
104127 state : FocusState ,
105128) {
129+ const pointerType = state . pointerType ;
106130 const target = ( ( state . focusTarget : any ) : Element | Document ) ;
107131 if ( props . onBlur ) {
108- const syntheticEvent = createFocusEvent ( context , 'blur' , target ) ;
132+ const syntheticEvent = createFocusEvent (
133+ context ,
134+ 'blur' ,
135+ target ,
136+ pointerType ,
137+ ) ;
109138 context . dispatchEvent ( syntheticEvent , props . onBlur , { discrete : true } ) ;
110139 }
111140 if ( props . onFocusChange ) {
112141 const listener = ( ) => {
113142 props . onFocusChange ( false ) ;
114143 } ;
115- const syntheticEvent = createFocusEvent ( context , 'focuschange' , target ) ;
144+ const syntheticEvent = createFocusEvent (
145+ context ,
146+ 'focuschange' ,
147+ target ,
148+ pointerType ,
149+ ) ;
116150 context . dispatchEvent ( syntheticEvent , listener , { discrete : true } ) ;
117151 }
118152 dispatchFocusVisibleOutEvent ( context , props , state ) ;
@@ -123,6 +157,7 @@ function dispatchFocusVisibleOutEvent(
123157 props : FocusProps ,
124158 state : FocusState ,
125159) {
160+ const pointerType = state . pointerType ;
126161 const target = ( ( state . focusTarget : any ) : Element | Document ) ;
127162 if ( props . onFocusVisibleChange && state . isLocalFocusVisible ) {
128163 const listener = ( ) => {
@@ -132,6 +167,7 @@ function dispatchFocusVisibleOutEvent(
132167 context ,
133168 'focusvisiblechange' ,
134169 target ,
170+ pointerType ,
135171 ) ;
136172 context . dispatchEvent ( syntheticEvent , listener , { discrete : true } ) ;
137173 state . isLocalFocusVisible = false ;
@@ -148,6 +184,31 @@ function unmountResponder(
148184 }
149185}
150186
187+ function handleRootPointerEvent (
188+ event : ReactResponderEvent ,
189+ context : ReactResponderContext ,
190+ props : FocusProps ,
191+ state : FocusState ,
192+ ) : void {
193+ const { type, target} = event ;
194+ // Ignore a Safari quirks where 'mousemove' is dispatched on the 'html'
195+ // element when the window blurs.
196+ if ( type === 'mousemove' && target . nodeName === 'HTML' ) {
197+ return ;
198+ }
199+
200+ isGlobalFocusVisible = false ;
201+
202+ // Focus should stop being visible if a pointer is used on the element
203+ // after it was focused using a keyboard.
204+ if (
205+ state . focusTarget === context . getEventCurrentTarget ( event ) &&
206+ ( type === 'mousedown' || type === 'touchstart' || type === 'pointerdown' )
207+ ) {
208+ dispatchFocusVisibleOutEvent ( context , props , state ) ;
209+ }
210+ }
211+
151212let isGlobalFocusVisible = true ;
152213
153214const FocusResponder = {
@@ -158,6 +219,7 @@ const FocusResponder = {
158219 focusTarget : null ,
159220 isFocused : false ,
160221 isLocalFocusVisible : false ,
222+ pointerType : '' ,
161223 } ;
162224 } ,
163225 stopLocalPropagation : true ,
@@ -208,36 +270,30 @@ const FocusResponder = {
208270 props : FocusProps ,
209271 state : FocusState ,
210272 ) : void {
211- const { type , target } = event ;
273+ const { type} = event ;
212274
213275 switch ( type ) {
214276 case 'mousemove' :
215277 case 'mousedown' :
216- case 'mouseup ':
278+ case 'mouseup' : {
279+ state . pointerType = 'mouse' ;
280+ handleRootPointerEvent ( event , context , props , state ) ;
281+ break ;
282+ }
217283 case 'pointermove' :
218284 case 'pointerdown' :
219- case 'pointerup ':
285+ case 'pointerup' : {
286+ // $FlowFixMe: Flow doesn't know about PointerEvents
287+ const nativeEvent = ( ( event . nativeEvent : any ) : PointerEvent ) ;
288+ state . pointerType = nativeEvent . pointerType ;
289+ handleRootPointerEvent ( event , context , props , state ) ;
290+ break ;
291+ }
220292 case 'touchmove' :
221293 case 'touchstart' :
222294 case 'touchend' : {
223- // Ignore a Safari quirks where 'mousemove' is dispatched on the 'html'
224- // element when the window blurs.
225- if ( type === 'mousemove' && target . nodeName === 'HTML' ) {
226- return ;
227- }
228-
229- isGlobalFocusVisible = false ;
230-
231- // Focus should stop being visible if a pointer is used on the element
232- // after it was focused using a keyboard.
233- if (
234- state . focusTarget === context . getEventCurrentTarget ( event ) &&
235- ( type === 'mousedown' ||
236- type === 'touchstart' ||
237- type === 'pointerdown' )
238- ) {
239- dispatchFocusVisibleOutEvent ( context , props , state ) ;
240- }
295+ state . pointerType = 'touch' ;
296+ handleRootPointerEvent ( event , context , props , state ) ;
241297 break ;
242298 }
243299
@@ -249,6 +305,7 @@ const FocusResponder = {
249305 nativeEvent . key === 'Tab' &&
250306 ! ( nativeEvent . metaKey || nativeEvent . altKey || nativeEvent . ctrlKey )
251307 ) {
308+ state . pointerType = 'keyboard' ;
252309 isGlobalFocusVisible = true ;
253310 }
254311 break ;
0 commit comments