10
10
import type {
11
11
ReactDOMResponderEvent ,
12
12
ReactDOMResponderContext ,
13
+ PointerType ,
13
14
} from 'shared/ReactDOMTypes' ;
14
15
import type { ReactEventResponderListener } from 'shared/ReactTypes' ;
15
16
@@ -29,15 +30,14 @@ type HoverState = {
29
30
hoverTarget : null | Element | Document ,
30
31
isActiveHovered : boolean ,
31
32
isHovered : boolean ,
32
- isTouched : boolean ,
33
- hoverStartTimeout : null | number ,
34
- hoverEndTimeout : null | number ,
35
- ignoreEmulatedMouseEvents : boolean ,
33
+ isTouched ?: boolean ,
34
+ ignoreEmulatedMouseEvents ?: boolean ,
36
35
} ;
37
36
38
37
type HoverEventType = 'hoverstart' | 'hoverend' | 'hoverchange' | 'hovermove' ;
39
38
40
39
type HoverEvent = { |
40
+ pointerType : PointerType ,
41
41
target : Element | Document ,
42
42
type : HoverEventType ,
43
43
timeStamp : number ,
@@ -51,17 +51,8 @@ type HoverEvent = {|
51
51
y : null | number ,
52
52
| } ;
53
53
54
- const targetEventTypes = [
55
- 'pointerover' ,
56
- 'pointermove' ,
57
- 'pointerout' ,
58
- 'pointercancel' ,
59
- ] ;
60
-
61
- // If PointerEvents is not supported (e.g., Safari), also listen to touch and mouse events.
62
- if ( typeof window !== 'undefined' && window . PointerEvent === undefined ) {
63
- targetEventTypes . push ( 'touchstart' , 'mouseover' , 'mousemove' , 'mouseout' ) ;
64
- }
54
+ const hasPointerEvents =
55
+ typeof window !== 'undefined' && window . PointerEvent != null ;
65
56
66
57
function isFunction ( obj ) : boolean {
67
58
return typeof obj === 'function' ;
@@ -79,13 +70,16 @@ function createHoverEvent(
79
70
let pageY = null ;
80
71
let screenX = null ;
81
72
let screenY = null ;
73
+ let pointerType = '' ;
82
74
83
75
if ( event ) {
84
76
const nativeEvent = ( event . nativeEvent : any ) ;
77
+ pointerType = event . pointerType ;
85
78
( { clientX, clientY, pageX, pageY, screenX, screenY} = nativeEvent ) ;
86
79
}
87
80
88
81
return {
82
+ pointerType,
89
83
target,
90
84
type,
91
85
timeStamp : context . getTimeStamp ( ) ,
@@ -131,11 +125,6 @@ function dispatchHoverStartEvents(
131
125
132
126
state . isHovered = true ;
133
127
134
- if ( state . hoverEndTimeout !== null ) {
135
- context . clearTimeout ( state . hoverEndTimeout ) ;
136
- state . hoverEndTimeout = null ;
137
- }
138
-
139
128
if ( ! state . isActiveHovered ) {
140
129
state . isActiveHovered = true ;
141
130
const onHoverStart = props . onHoverStart ;
@@ -152,6 +141,20 @@ function dispatchHoverStartEvents(
152
141
}
153
142
}
154
143
144
+ function dispatchHoverMoveEvent ( event , context , props , state ) {
145
+ const target = state . hoverTarget ;
146
+ const onHoverMove = props . onHoverMove ;
147
+ if ( isFunction ( onHoverMove ) ) {
148
+ const syntheticEvent = createHoverEvent (
149
+ event ,
150
+ context ,
151
+ 'hovermove' ,
152
+ ( ( target : any ) : Element | Document ) ,
153
+ ) ;
154
+ context . dispatchEvent ( syntheticEvent , onHoverMove , UserBlockingEvent ) ;
155
+ }
156
+ }
157
+
155
158
function dispatchHoverEndEvents (
156
159
event : null | ReactDOMResponderEvent ,
157
160
context : ReactDOMResponderContext ,
@@ -170,11 +173,6 @@ function dispatchHoverEndEvents(
170
173
171
174
state . isHovered = false ;
172
175
173
- if ( state . hoverStartTimeout !== null ) {
174
- context . clearTimeout ( state . hoverStartTimeout ) ;
175
- state . hoverStartTimeout = null ;
176
- }
177
-
178
176
if ( state . isActiveHovered ) {
179
177
state . isActiveHovered = false ;
180
178
const onHoverEnd = props . onHoverEnd ;
@@ -189,7 +187,6 @@ function dispatchHoverEndEvents(
189
187
}
190
188
dispatchHoverChangeEvent ( event , context , props , state ) ;
191
189
state . hoverTarget = null ;
192
- state . ignoreEmulatedMouseEvents = false ;
193
190
state . isTouched = false ;
194
191
}
195
192
}
@@ -204,24 +201,17 @@ function unmountResponder(
204
201
}
205
202
}
206
203
207
- function isEmulatedMouseEvent ( event , state ) {
208
- const { type} = event ;
209
- return (
210
- state . ignoreEmulatedMouseEvents &&
211
- ( type === 'mousemove' || type === 'mouseover' || type === 'mouseout' )
212
- ) ;
213
- }
214
-
215
204
const hoverResponderImpl = {
216
- targetEventTypes,
205
+ targetEventTypes : [
206
+ 'pointerover' ,
207
+ 'pointermove' ,
208
+ 'pointerout' ,
209
+ 'pointercancel' ,
210
+ ] ,
217
211
getInitialState ( ) {
218
212
return {
219
213
isActiveHovered : false ,
220
214
isHovered : false ,
221
- isTouched : false ,
222
- hoverStartTimeout : null ,
223
- hoverEndTimeout : null ,
224
- ignoreEmulatedMouseEvents : false ,
225
215
} ;
226
216
} ,
227
217
allowMultipleHostChildren : false ,
@@ -237,95 +227,120 @@ const hoverResponderImpl = {
237
227
if ( props . disabled ) {
238
228
if ( state . isHovered ) {
239
229
dispatchHoverEndEvents ( event , context , props , state ) ;
240
- state . ignoreEmulatedMouseEvents = false ;
241
- }
242
- if ( state . isTouched ) {
243
- state . isTouched = false ;
244
230
}
245
231
return ;
246
232
}
247
233
248
234
switch ( type ) {
249
235
// START
250
- case 'pointerover' :
251
- case 'mouseover' :
252
- case 'touchstart' : {
253
- if ( ! state . isHovered ) {
254
- // Prevent hover events for touch
255
- if ( state . isTouched || pointerType === 'touch' ) {
256
- state . isTouched = true ;
257
- return ;
258
- }
259
-
260
- // Prevent hover events for emulated events
261
- if ( isEmulatedMouseEvent ( event , state ) ) {
262
- return ;
263
- }
236
+ case 'pointerover' : {
237
+ if ( ! state . isHovered && pointerType !== 'touch' ) {
264
238
state . hoverTarget = event . responderTarget ;
265
- state . ignoreEmulatedMouseEvents = true ;
266
239
dispatchHoverStartEvents ( event , context , props , state ) ;
267
240
}
268
- return ;
241
+ break ;
269
242
}
270
243
271
244
// MOVE
272
- case 'pointermove' :
273
- case 'mousemove' : {
274
- if ( state . isHovered && ! isEmulatedMouseEvent ( event , state ) ) {
275
- const onHoverMove = props . onHoverMove ;
276
- if ( state . hoverTarget !== null && isFunction ( onHoverMove ) ) {
277
- const syntheticEvent = createHoverEvent (
278
- event ,
279
- context ,
280
- 'hovermove' ,
281
- state . hoverTarget ,
282
- ) ;
283
- context . dispatchEvent (
284
- syntheticEvent ,
285
- onHoverMove ,
286
- UserBlockingEvent ,
287
- ) ;
288
- }
245
+ case 'pointermove' : {
246
+ if ( state . isHovered && state . hoverTarget !== null ) {
247
+ dispatchHoverMoveEvent ( event , context , props , state ) ;
289
248
}
290
- return ;
249
+ break ;
291
250
}
292
251
293
252
// END
294
253
case 'pointerout' :
295
- case 'pointercancel' :
296
- case 'mouseout' :
297
- case 'touchcancel' :
298
- case 'touchend' : {
254
+ case 'pointercancel' : {
299
255
if ( state . isHovered ) {
300
256
dispatchHoverEndEvents ( event , context , props , state ) ;
301
- state . ignoreEmulatedMouseEvents = false ;
302
257
}
303
- if ( state . isTouched ) {
304
- state . isTouched = false ;
305
- }
306
- return ;
258
+ break ;
307
259
}
308
260
}
309
261
} ,
310
- onUnmount (
311
- context : ReactDOMResponderContext ,
312
- props : HoverProps ,
313
- state : HoverState ,
314
- ) {
315
- unmountResponder ( context , props , state ) ;
262
+ onUnmount: unmountResponder ,
263
+ onOwnershipChange : unmountResponder ,
264
+ } ;
265
+
266
+ const hoverResponderFallbackImpl = {
267
+ targetEventTypes : [ 'mouseover' , 'mousemove' , 'mouseout' , 'touchstart' ] ,
268
+ getInitialState ( ) {
269
+ return {
270
+ isActiveHovered : false ,
271
+ isHovered : false ,
272
+ isTouched : false ,
273
+ ignoreEmulatedMouseEvents : false ,
274
+ } ;
316
275
} ,
317
- onOwnershipChange (
276
+ allowMultipleHostChildren : false ,
277
+ allowEventHooks : true ,
278
+ onEvent (
279
+ event : ReactDOMResponderEvent ,
318
280
context : ReactDOMResponderContext ,
319
281
props : HoverProps ,
320
282
state : HoverState ,
321
- ) {
322
- unmountResponder ( context , props , state ) ;
283
+ ) : void {
284
+ const { type} = event ;
285
+
286
+ if ( props . disabled ) {
287
+ if ( state . isHovered ) {
288
+ dispatchHoverEndEvents ( event , context , props , state ) ;
289
+ state . ignoreEmulatedMouseEvents = false ;
290
+ }
291
+ state . isTouched = false ;
292
+ return ;
293
+ }
294
+
295
+ switch ( type ) {
296
+ // START
297
+ case 'mouseover' : {
298
+ if ( ! state . isHovered && ! state . ignoreEmulatedMouseEvents ) {
299
+ state . hoverTarget = event . responderTarget ;
300
+ dispatchHoverStartEvents ( event , context , props , state ) ;
301
+ }
302
+ break ;
303
+ }
304
+
305
+ // MOVE
306
+ case 'mousemove' : {
307
+ if (
308
+ state . isHovered &&
309
+ state . hoverTarget !== null &&
310
+ ! state . ignoreEmulatedMouseEvents
311
+ ) {
312
+ dispatchHoverMoveEvent ( event , context , props , state ) ;
313
+ } else if ( ! state . isHovered && type === 'mousemove' ) {
314
+ state . ignoreEmulatedMouseEvents = false ;
315
+ state . isTouched = false ;
316
+ }
317
+ break ;
318
+ }
319
+
320
+ // END
321
+ case 'mouseout' : {
322
+ if ( state . isHovered ) {
323
+ dispatchHoverEndEvents ( event , context , props , state ) ;
324
+ }
325
+ break ;
326
+ }
327
+
328
+ case 'touchstart' : {
329
+ if ( ! state . isHovered ) {
330
+ state . isTouched = true ;
331
+ state . ignoreEmulatedMouseEvents = true ;
332
+ }
333
+ break ;
334
+ }
335
+ }
323
336
} ,
337
+ onUnmount: unmountResponder ,
338
+ onOwnershipChange : unmountResponder ,
324
339
} ;
325
340
326
341
export const HoverResponder = React . unstable_createResponder (
327
342
'Hover' ,
328
- hoverResponderImpl ,
343
+ hasPointerEvents ? hoverResponderImpl : hoverResponderFallbackImpl ,
329
344
) ;
330
345
331
346
export function useHoverResponder (
0 commit comments