@@ -145,6 +145,7 @@ export class MatTooltip implements OnDestroy, AfterViewInit {
145
145
private _tooltipClass : string | string [ ] | Set < string > | { [ key : string ] : any } ;
146
146
private _scrollStrategy : ( ) => ScrollStrategy ;
147
147
private _viewInitialized = false ;
148
+ private _pointerExitEventsInitialized = false ;
148
149
149
150
/** Allows the user to define the position of the tooltip relative to the parent element */
150
151
@Input ( 'matTooltipPosition' )
@@ -175,7 +176,7 @@ export class MatTooltip implements OnDestroy, AfterViewInit {
175
176
if ( this . _disabled ) {
176
177
this . hide ( 0 ) ;
177
178
} else {
178
- this . _setupPointerEvents ( ) ;
179
+ this . _setupPointerEnterEventsIfNeeded ( ) ;
179
180
}
180
181
}
181
182
@@ -215,7 +216,7 @@ export class MatTooltip implements OnDestroy, AfterViewInit {
215
216
if ( ! this . _message && this . _isTooltipVisible ( ) ) {
216
217
this . hide ( 0 ) ;
217
218
} else {
218
- this . _setupPointerEvents ( ) ;
219
+ this . _setupPointerEnterEventsIfNeeded ( ) ;
219
220
this . _updateTooltipMessage ( ) ;
220
221
this . _ngZone . runOutsideAngular ( ( ) => {
221
222
// The `AriaDescriber` has some functionality that avoids adding a description if it's the
@@ -241,7 +242,7 @@ export class MatTooltip implements OnDestroy, AfterViewInit {
241
242
}
242
243
243
244
/** Manually-bound passive event listeners. */
244
- private _passiveListeners = new Map < string , EventListenerOrEventListenerObject > ( ) ;
245
+ private readonly _passiveListeners : Array < readonly [ string , EventListenerOrEventListenerObject ] > = [ ] ;
245
246
246
247
/** Timer started at the last `touchstart` event. */
247
248
private _touchstartTimeout : number ;
@@ -283,7 +284,7 @@ export class MatTooltip implements OnDestroy, AfterViewInit {
283
284
ngAfterViewInit ( ) {
284
285
// This needs to happen after view init so the initial values for all inputs have been set.
285
286
this . _viewInitialized = true ;
286
- this . _setupPointerEvents ( ) ;
287
+ this . _setupPointerEnterEventsIfNeeded ( ) ;
287
288
288
289
this . _focusMonitor . monitor ( this . _elementRef )
289
290
. pipe ( takeUntil ( this . _destroyed ) )
@@ -312,10 +313,10 @@ export class MatTooltip implements OnDestroy, AfterViewInit {
312
313
313
314
// Clean up the event listeners set in the constructor
314
315
nativeElement . removeEventListener ( 'keydown' , this . _handleKeydown ) ;
315
- this . _passiveListeners . forEach ( ( listener , event ) => {
316
+ this . _passiveListeners . forEach ( ( [ event , listener ] ) => {
316
317
nativeElement . removeEventListener ( event , listener , passiveListenerOptions ) ;
317
318
} ) ;
318
- this . _passiveListeners . clear ( ) ;
319
+ this . _passiveListeners . length = 0 ;
319
320
320
321
this . _destroyed . next ( ) ;
321
322
this . _destroyed . complete ( ) ;
@@ -549,49 +550,77 @@ export class MatTooltip implements OnDestroy, AfterViewInit {
549
550
}
550
551
551
552
/** Binds the pointer events to the tooltip trigger. */
552
- private _setupPointerEvents ( ) {
553
+ private _setupPointerEnterEventsIfNeeded ( ) {
553
554
// Optimization: Defer hooking up events if there's no message or the tooltip is disabled.
554
555
if ( this . _disabled || ! this . message || ! this . _viewInitialized ||
555
- this . _passiveListeners . size ) {
556
+ this . _passiveListeners . length ) {
556
557
return ;
557
558
}
558
559
559
560
// The mouse events shouldn't be bound on mobile devices, because they can prevent the
560
561
// first tap from firing its click event or can cause the tooltip to open for clicks.
561
562
if ( ! this . _platform . IOS && ! this . _platform . ANDROID ) {
562
563
this . _passiveListeners
563
- . set ( 'mouseenter' , ( ) => this . show ( ) )
564
- . set ( 'mouseleave' , ( ) => this . hide ( ) ) ;
564
+ . push ( [ 'mouseenter' , ( ) => {
565
+ this . _setupPointerExitEventsIfNeeded ( ) ;
566
+ this . show ( ) ;
567
+ } ] ) ;
568
+ } else if ( this . touchGestures !== 'off' ) {
569
+ this . _disableNativeGesturesIfNecessary ( ) ;
570
+
571
+ this . _passiveListeners
572
+ . push ( [ 'touchstart' , ( ) => {
573
+ // Note that it's important that we don't `preventDefault` here,
574
+ // because it can prevent click events from firing on the element.
575
+ this . _setupPointerExitEventsIfNeeded ( ) ;
576
+ clearTimeout ( this . _touchstartTimeout ) ;
577
+ this . _touchstartTimeout = setTimeout ( ( ) => this . show ( ) , LONGPRESS_DELAY ) ;
578
+ } ] ) ;
579
+ }
580
+
581
+ this . _hookupListeners ( this . _passiveListeners ) ;
582
+ }
583
+
584
+ private _setupPointerExitEventsIfNeeded ( ) {
585
+ if ( this . _pointerExitEventsInitialized ) {
586
+ return ;
587
+ }
588
+ this . _pointerExitEventsInitialized = true ;
589
+
590
+ const exitListeners : Array < readonly [ string , EventListenerOrEventListenerObject ] > = [ ] ;
591
+ if ( ! this . _platform . IOS && ! this . _platform . ANDROID ) {
592
+ exitListeners . push ( [ 'mouseleave' , ( ) => this . hide ( ) ] ) ;
565
593
} else if ( this . touchGestures !== 'off' ) {
566
594
this . _disableNativeGesturesIfNecessary ( ) ;
567
595
const touchendListener = ( ) => {
568
596
clearTimeout ( this . _touchstartTimeout ) ;
569
597
this . hide ( this . _defaultOptions . touchendHideDelay ) ;
570
598
} ;
571
599
572
- this . _passiveListeners
573
- . set ( 'touchend' , touchendListener )
574
- . set ( 'touchcancel' , touchendListener )
575
- . set ( 'touchstart' , ( ) => {
576
- // Note that it's important that we don't `preventDefault` here,
577
- // because it can prevent click events from firing on the element.
578
- clearTimeout ( this . _touchstartTimeout ) ;
579
- this . _touchstartTimeout = setTimeout ( ( ) => this . show ( ) , LONGPRESS_DELAY ) ;
580
- } ) ;
600
+ exitListeners . push (
601
+ [ 'touchend' , touchendListener ] ,
602
+ [ 'touchcancel' , touchendListener ] ,
603
+ ) ;
581
604
}
582
605
583
- this . _passiveListeners . forEach ( ( listener , event ) => {
606
+ this . _hookupListeners ( exitListeners ) ;
607
+ this . _passiveListeners . push ( ...exitListeners ) ;
608
+ }
609
+
610
+ private _hookupListeners ( listeners : ReadonlyArray < readonly [ string , EventListenerOrEventListenerObject ] > ) {
611
+ listeners . forEach ( ( [ event , listener ] ) => {
584
612
this . _elementRef . nativeElement . addEventListener ( event , listener , passiveListenerOptions ) ;
585
613
} ) ;
586
614
}
587
615
588
616
/** Disables the native browser gestures, based on how the tooltip has been configured. */
589
617
private _disableNativeGesturesIfNecessary ( ) {
590
- const element = this . _elementRef . nativeElement ;
591
- const style = element . style ;
592
618
const gestures = this . touchGestures ;
593
619
594
620
if ( gestures !== 'off' ) {
621
+ const element = this . _elementRef . nativeElement ;
622
+ const style = element . style ;
623
+
595
624
// If gestures are set to `auto`, we don't disable text selection on inputs and
596
625
// textareas, because it prevents the user from typing into them on iOS Safari.
597
626
if ( gestures === 'on' || ( element . nodeName !== 'INPUT' && element . nodeName !== 'TEXTAREA' ) ) {
0 commit comments