@@ -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
@@ -205,17 +206,15 @@ export class MatTooltip implements OnDestroy, AfterViewInit {
205
206
@Input ( 'matTooltip' )
206
207
get message ( ) { return this . _message ; }
207
208
set message ( value : string ) {
208
- if ( this . _message ) {
209
- this . _ariaDescriber . removeDescription ( this . _elementRef . nativeElement , this . _message ) ;
210
- }
209
+ this . _ariaDescriber . removeDescription ( this . _elementRef . nativeElement , this . _message ) ;
211
210
212
211
// If the message is not a string (e.g. number), convert it to a string and trim it.
213
212
this . _message = value != null ? `${ value } ` . trim ( ) : '' ;
214
213
215
214
if ( ! this . _message && this . _isTooltipVisible ( ) ) {
216
215
this . hide ( 0 ) ;
217
216
} else {
218
- this . _setupPointerEvents ( ) ;
217
+ this . _setupPointerEnterEventsIfNeeded ( ) ;
219
218
this . _updateTooltipMessage ( ) ;
220
219
this . _ngZone . runOutsideAngular ( ( ) => {
221
220
// The `AriaDescriber` has some functionality that avoids adding a description if it's the
@@ -241,7 +240,8 @@ export class MatTooltip implements OnDestroy, AfterViewInit {
241
240
}
242
241
243
242
/** Manually-bound passive event listeners. */
244
- private _passiveListeners = new Map < string , EventListenerOrEventListenerObject > ( ) ;
243
+ private readonly _passiveListeners :
244
+ ( readonly [ string , EventListenerOrEventListenerObject ] ) [ ] = [ ] ;
245
245
246
246
/** Timer started at the last `touchstart` event. */
247
247
private _touchstartTimeout : number ;
@@ -283,7 +283,7 @@ export class MatTooltip implements OnDestroy, AfterViewInit {
283
283
ngAfterViewInit ( ) {
284
284
// This needs to happen after view init so the initial values for all inputs have been set.
285
285
this . _viewInitialized = true ;
286
- this . _setupPointerEvents ( ) ;
286
+ this . _setupPointerEnterEventsIfNeeded ( ) ;
287
287
288
288
this . _focusMonitor . monitor ( this . _elementRef )
289
289
. pipe ( takeUntil ( this . _destroyed ) )
@@ -312,10 +312,10 @@ export class MatTooltip implements OnDestroy, AfterViewInit {
312
312
313
313
// Clean up the event listeners set in the constructor
314
314
nativeElement . removeEventListener ( 'keydown' , this . _handleKeydown ) ;
315
- this . _passiveListeners . forEach ( ( listener , event ) => {
315
+ this . _passiveListeners . forEach ( ( [ event , listener ] ) => {
316
316
nativeElement . removeEventListener ( event , listener , passiveListenerOptions ) ;
317
317
} ) ;
318
- this . _passiveListeners . clear ( ) ;
318
+ this . _passiveListeners . length = 0 ;
319
319
320
320
this . _destroyed . next ( ) ;
321
321
this . _destroyed . complete ( ) ;
@@ -549,49 +549,82 @@ export class MatTooltip implements OnDestroy, AfterViewInit {
549
549
}
550
550
551
551
/** Binds the pointer events to the tooltip trigger. */
552
- private _setupPointerEvents ( ) {
552
+ private _setupPointerEnterEventsIfNeeded ( ) {
553
553
// Optimization: Defer hooking up events if there's no message or the tooltip is disabled.
554
554
if ( this . _disabled || ! this . message || ! this . _viewInitialized ||
555
- this . _passiveListeners . size ) {
555
+ this . _passiveListeners . length ) {
556
556
return ;
557
557
}
558
558
559
559
// The mouse events shouldn't be bound on mobile devices, because they can prevent the
560
560
// first tap from firing its click event or can cause the tooltip to open for clicks.
561
- if ( ! this . _platform . IOS && ! this . _platform . ANDROID ) {
561
+ if ( this . _platformSupportsMouseEvents ( ) ) {
562
562
this . _passiveListeners
563
- . set ( 'mouseenter' , ( ) => this . show ( ) )
564
- . set ( 'mouseleave' , ( ) => this . hide ( ) ) ;
563
+ . push ( [ 'mouseenter' , ( ) => {
564
+ this . _setupPointerExitEventsIfNeeded ( ) ;
565
+ this . show ( ) ;
566
+ } ] ) ;
567
+ } else if ( this . touchGestures !== 'off' ) {
568
+ this . _disableNativeGesturesIfNecessary ( ) ;
569
+
570
+ this . _passiveListeners
571
+ . push ( [ 'touchstart' , ( ) => {
572
+ // Note that it's important that we don't `preventDefault` here,
573
+ // because it can prevent click events from firing on the element.
574
+ this . _setupPointerExitEventsIfNeeded ( ) ;
575
+ clearTimeout ( this . _touchstartTimeout ) ;
576
+ this . _touchstartTimeout = setTimeout ( ( ) => this . show ( ) , LONGPRESS_DELAY ) ;
577
+ } ] ) ;
578
+ }
579
+
580
+ this . _addListeners ( this . _passiveListeners ) ;
581
+ }
582
+
583
+ private _setupPointerExitEventsIfNeeded ( ) {
584
+ if ( this . _pointerExitEventsInitialized ) {
585
+ return ;
586
+ }
587
+ this . _pointerExitEventsInitialized = true ;
588
+
589
+ const exitListeners : ( readonly [ string , EventListenerOrEventListenerObject ] ) [ ] = [ ] ;
590
+ if ( this . _platformSupportsMouseEvents ( ) ) {
591
+ exitListeners . push ( [ 'mouseleave' , ( ) => this . hide ( ) ] ) ;
565
592
} else if ( this . touchGestures !== 'off' ) {
566
593
this . _disableNativeGesturesIfNecessary ( ) ;
567
594
const touchendListener = ( ) => {
568
595
clearTimeout ( this . _touchstartTimeout ) ;
569
596
this . hide ( this . _defaultOptions . touchendHideDelay ) ;
570
597
} ;
571
598
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
- } ) ;
599
+ exitListeners . push (
600
+ [ 'touchend' , touchendListener ] ,
601
+ [ 'touchcancel' , touchendListener ] ,
602
+ ) ;
581
603
}
582
604
583
- this . _passiveListeners . forEach ( ( listener , event ) => {
605
+ this . _addListeners ( exitListeners ) ;
606
+ this . _passiveListeners . push ( ...exitListeners ) ;
607
+ }
608
+
609
+ private _addListeners (
610
+ 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
616
+ private _platformSupportsMouseEvents ( ) {
617
+ return ! this . _platform . IOS && ! this . _platform . ANDROID ;
618
+ }
619
+
588
620
/** Disables the native browser gestures, based on how the tooltip has been configured. */
589
621
private _disableNativeGesturesIfNecessary ( ) {
590
- const element = this . _elementRef . nativeElement ;
591
- const style = element . style ;
592
622
const gestures = this . touchGestures ;
593
623
594
624
if ( gestures !== 'off' ) {
625
+ const element = this . _elementRef . nativeElement ;
626
+ const style = element . style ;
627
+
595
628
// If gestures are set to `auto`, we don't disable text selection on inputs and
596
629
// textareas, because it prevents the user from typing into them on iOS Safari.
597
630
if ( gestures === 'on' || ( element . nodeName !== 'INPUT' && element . nodeName !== 'TEXTAREA' ) ) {
0 commit comments