@@ -25,6 +25,10 @@ import '../../src/browser/style/hover-service.css';
25
25
26
26
export type HoverPosition = 'left' | 'right' | 'top' | 'bottom' ;
27
27
28
+ // Threshold, in milliseconds, over which a mouse movement is not considered
29
+ // quick enough as to be ignored
30
+ const quickMouseThresholdMillis = 200 ;
31
+
28
32
export namespace HoverPosition {
29
33
export function invertIfNecessary ( position : HoverPosition , target : DOMRect , host : DOMRect , totalWidth : number , totalHeight : number ) : HoverPosition {
30
34
if ( position === 'left' ) {
@@ -91,31 +95,36 @@ export class HoverService {
91
95
}
92
96
return this . _hoverHost ;
93
97
}
98
+
99
+ // Pending presentation of the hover pop-up
94
100
protected pendingTimeout : Disposable | undefined ;
95
- protected pendingHoverCancellation : Disposable | undefined ;
101
+ // Pending cancellation of presentation of the hover pop-up or
102
+ // dismissal of the hover pop-up currently presented
103
+ protected pendingHoverCancel : Disposable | undefined ;
96
104
protected hoverTarget : HTMLElement | undefined ;
97
105
protected lastHidHover = Date . now ( ) ;
98
106
protected readonly disposeOnHide = new DisposableCollection ( ) ;
99
107
100
108
requestHover ( request : HoverRequest ) : void {
101
109
if ( request . target !== this . hoverTarget ) {
110
+ this . pendingHoverCancel ?. dispose ( ) ;
102
111
this . cancelHover ( ) ;
103
112
const hoverDelay = this . getHoverDelay ( ) ;
104
113
this . pendingTimeout = disposableTimeout ( ( ) => this . renderHover ( request ) , hoverDelay ) ;
105
114
if ( hoverDelay > 0 ) {
106
- this . cancelHoverOnMouseOut ( request ) ;
115
+ this . cancelPendingHoverOnMouseOut ( request ) ;
107
116
}
108
117
}
109
118
}
110
119
111
120
protected getHoverDelay ( ) : number {
112
- return Date . now ( ) - this . lastHidHover < 200
121
+ return Date . now ( ) - this . lastHidHover < quickMouseThresholdMillis
113
122
? 0
114
123
: this . preferences . get ( 'workbench.hover.delay' , isOSX ? 1500 : 500 ) ;
115
124
}
116
125
117
126
protected async renderHover ( request : HoverRequest ) : Promise < void > {
118
- this . cancelPendingHoverCancellation ( ) ;
127
+ this . pendingHoverCancel ?. dispose ( ) ;
119
128
120
129
const host = this . hoverHost ;
121
130
let firstChild : HTMLElement | undefined ;
@@ -201,36 +210,41 @@ export class HoverService {
201
210
return position ;
202
211
}
203
212
204
- /** Cancel the pending cancellation of hover. */
205
- protected cancelPendingHoverCancellation ( ) : void {
206
- this . pendingHoverCancellation ?. dispose ( ) ;
207
- this . pendingHoverCancellation = undefined ;
208
- }
209
-
210
213
protected listenForMouseOut ( ) : void {
211
214
const handleMouseMove = ( e : MouseEvent ) => {
212
215
if ( e . target instanceof Node && ! this . hoverHost . contains ( e . target ) && ! this . hoverTarget ?. contains ( e . target ) ) {
213
- this . cancelHover ( ) ;
216
+ this . pendingHoverCancel ?. dispose ( ) ;
217
+ this . pendingHoverCancel = disposableTimeout ( ( ) => {
218
+ if ( ! this . hoverHost . matches ( ':hover' ) ) {
219
+ this . cancelHover ( ) ;
220
+ }
221
+ } , quickMouseThresholdMillis ) ;
222
+ this . disposeOnHide . push ( this . pendingHoverCancel ) ;
214
223
}
215
224
} ;
216
225
document . addEventListener ( 'mousemove' , handleMouseMove ) ;
217
226
this . disposeOnHide . push ( { dispose : ( ) => document . removeEventListener ( 'mousemove' , handleMouseMove ) } ) ;
218
227
}
219
228
220
- protected cancelHoverOnMouseOut ( request : HoverRequest ) : void {
221
- this . cancelPendingHoverCancellation ( ) ;
229
+ protected cancelPendingHoverOnMouseOut ( request : HoverRequest ) : void {
230
+ this . pendingHoverCancel ?. dispose ( ) ;
222
231
const target = request . target ;
223
232
const handleMouseLeave = ( ) : void => {
224
- this . cancelHover ( ) ;
225
- this . cancelPendingHoverCancellation ( ) ;
233
+ // Cancel the pending hover if it hasn't yet been presented
234
+ if ( ! this . hoverHost . isConnected ) {
235
+ this . cancelHover ( ) ;
236
+ }
237
+ this . pendingHoverCancel ?. dispose ( ) ;
226
238
} ;
227
239
target . addEventListener ( 'mouseleave' , handleMouseLeave ) ;
228
- this . pendingHoverCancellation = this . disposeOnHide . push (
229
- Disposable . create ( ( ) => target . removeEventListener ( 'mouseleave' , handleMouseLeave ) ) ) ;
240
+ this . pendingHoverCancel = Disposable . create (
241
+ ( ) => target . removeEventListener ( 'mouseleave' , handleMouseLeave ) ) ;
242
+ this . disposeOnHide . push ( this . pendingHoverCancel ) ;
230
243
}
231
244
232
245
cancelHover ( ) : void {
233
246
this . pendingTimeout ?. dispose ( ) ;
247
+ this . pendingHoverCancel ?. dispose ( ) ;
234
248
this . unRenderHover ( ) ;
235
249
this . disposeOnHide . dispose ( ) ;
236
250
this . hoverTarget = undefined ;
0 commit comments