@@ -31,9 +31,10 @@ import {
3131 afterNextRender ,
3232 Injector ,
3333 DOCUMENT ,
34+ Renderer2 ,
3435} from '@angular/core' ;
3536import { NgClass } from '@angular/common' ;
36- import { normalizePassiveListenerOptions , Platform } from '@angular/cdk/platform' ;
37+ import { Platform } from '@angular/cdk/platform' ;
3738import { AriaDescriber , FocusMonitor } from '@angular/cdk/a11y' ;
3839import { Directionality } from '@angular/cdk/bidi' ;
3940import {
@@ -167,7 +168,7 @@ export const TOOLTIP_PANEL_CLASS = 'mat-mdc-tooltip-panel';
167168const PANEL_CLASS = 'tooltip-panel' ;
168169
169170/** Options used to bind passive event listeners. */
170- const passiveListenerOptions = normalizePassiveListenerOptions ( { passive : true } ) ;
171+ const passiveListenerOptions = { passive : true } ;
171172
172173// These constants were taken from MDC's `numbers` object. We can't import them from MDC,
173174// because they have some top-level references to `window` which break during SSR.
@@ -200,6 +201,8 @@ export class MatTooltip implements OnDestroy, AfterViewInit {
200201 private _injector = inject ( Injector ) ;
201202 private _viewContainerRef = inject ( ViewContainerRef ) ;
202203 private _mediaMatcher = inject ( MediaMatcher ) ;
204+ private _document = inject ( DOCUMENT ) ;
205+ private _renderer = inject ( Renderer2 ) ;
203206 private _animationsDisabled = _animationsDisabled ( ) ;
204207 private _defaultOptions = inject < MatTooltipDefaultOptions > ( MAT_TOOLTIP_DEFAULT_OPTIONS , {
205208 optional : true ,
@@ -363,8 +366,7 @@ export class MatTooltip implements OnDestroy, AfterViewInit {
363366 }
364367
365368 /** Manually-bound passive event listeners. */
366- private readonly _passiveListeners : ( readonly [ string , EventListenerOrEventListenerObject ] ) [ ] =
367- [ ] ;
369+ private readonly _eventCleanups : ( ( ) => void ) [ ] = [ ] ;
368370
369371 /** Timer started at the last `touchstart` event. */
370372 private _touchstartTimeout : null | ReturnType < typeof setTimeout > = null ;
@@ -438,17 +440,11 @@ export class MatTooltip implements OnDestroy, AfterViewInit {
438440 this . _tooltipInstance = null ;
439441 }
440442
441- // Clean up the event listeners set in the constructor
442- this . _passiveListeners . forEach ( ( [ event , listener ] ) => {
443- nativeElement . removeEventListener ( event , listener , passiveListenerOptions ) ;
444- } ) ;
445- this . _passiveListeners . length = 0 ;
446-
443+ this . _eventCleanups . forEach ( cleanup => cleanup ( ) ) ;
444+ this . _eventCleanups . length = 0 ;
447445 this . _destroyed . next ( ) ;
448446 this . _destroyed . complete ( ) ;
449-
450447 this . _isDestroyed = true ;
451-
452448 this . _ariaDescriber . removeDescription ( nativeElement , this . message , 'tooltip' ) ;
453449 this . _focusMonitor . stopMonitoring ( nativeElement ) ;
454450 }
@@ -783,54 +779,40 @@ export class MatTooltip implements OnDestroy, AfterViewInit {
783779 /** Binds the pointer events to the tooltip trigger. */
784780 private _setupPointerEnterEventsIfNeeded ( ) {
785781 // Optimization: Defer hooking up events if there's no message or the tooltip is disabled.
786- if (
787- this . _disabled ||
788- ! this . message ||
789- ! this . _viewInitialized ||
790- this . _passiveListeners . length
791- ) {
782+ if ( this . _disabled || ! this . message || ! this . _viewInitialized || this . _eventCleanups . length ) {
792783 return ;
793784 }
794785
795786 // The mouse events shouldn't be bound on mobile devices, because they can prevent the
796787 // first tap from firing its click event or can cause the tooltip to open for clicks.
797788 if ( ! this . _isTouchPlatform ( ) ) {
798- this . _passiveListeners . push ( [
799- 'mouseenter' ,
800- event => {
801- this . _setupPointerExitEventsIfNeeded ( ) ;
802- let point = undefined ;
803- if ( ( event as MouseEvent ) . x !== undefined && ( event as MouseEvent ) . y !== undefined ) {
804- point = event as MouseEvent ;
805- }
806- this . show ( undefined , point ) ;
807- } ,
808- ] ) ;
789+ this . _addListener ( 'mouseenter' , ( event : MouseEvent ) => {
790+ this . _setupPointerExitEventsIfNeeded ( ) ;
791+ let point = undefined ;
792+ if ( event . x !== undefined && event . y !== undefined ) {
793+ point = event ;
794+ }
795+ this . show ( undefined , point ) ;
796+ } ) ;
809797 } else if ( this . touchGestures !== 'off' ) {
810798 this . _disableNativeGesturesIfNecessary ( ) ;
799+ this . _addListener ( 'touchstart' , ( event : TouchEvent ) => {
800+ const touch = event . targetTouches ?. [ 0 ] ;
801+ const origin = touch ? { x : touch . clientX , y : touch . clientY } : undefined ;
802+ // Note that it's important that we don't `preventDefault` here,
803+ // because it can prevent click events from firing on the element.
804+ this . _setupPointerExitEventsIfNeeded ( ) ;
805+ if ( this . _touchstartTimeout ) {
806+ clearTimeout ( this . _touchstartTimeout ) ;
807+ }
811808
812- this . _passiveListeners . push ( [
813- 'touchstart' ,
814- event => {
815- const touch = ( event as TouchEvent ) . targetTouches ?. [ 0 ] ;
816- const origin = touch ? { x : touch . clientX , y : touch . clientY } : undefined ;
817- // Note that it's important that we don't `preventDefault` here,
818- // because it can prevent click events from firing on the element.
819- this . _setupPointerExitEventsIfNeeded ( ) ;
820- if ( this . _touchstartTimeout ) {
821- clearTimeout ( this . _touchstartTimeout ) ;
822- }
823-
824- const DEFAULT_LONGPRESS_DELAY = 500 ;
825- this . _touchstartTimeout = setTimeout ( ( ) => {
826- this . _touchstartTimeout = null ;
827- this . show ( undefined , origin ) ;
828- } , this . _defaultOptions ?. touchLongPressShowDelay ?? DEFAULT_LONGPRESS_DELAY ) ;
829- } ,
830- ] ) ;
809+ const DEFAULT_LONGPRESS_DELAY = 500 ;
810+ this . _touchstartTimeout = setTimeout ( ( ) => {
811+ this . _touchstartTimeout = null ;
812+ this . show ( undefined , origin ) ;
813+ } , this . _defaultOptions ?. touchLongPressShowDelay ?? DEFAULT_LONGPRESS_DELAY ) ;
814+ } ) ;
831815 }
832-
833- this . _addListeners ( this . _passiveListeners ) ;
834816 }
835817
836818 private _setupPointerExitEventsIfNeeded ( ) {
@@ -839,20 +821,28 @@ export class MatTooltip implements OnDestroy, AfterViewInit {
839821 }
840822 this . _pointerExitEventsInitialized = true ;
841823
842- const exitListeners : ( readonly [ string , EventListenerOrEventListenerObject ] ) [ ] = [ ] ;
843824 if ( ! this . _isTouchPlatform ( ) ) {
844- exitListeners . push (
845- [
846- 'mouseleave' ,
847- event => {
848- const newTarget = ( event as MouseEvent ) . relatedTarget as Node | null ;
849- if ( ! newTarget || ! this . _overlayRef ?. overlayElement . contains ( newTarget ) ) {
850- this . hide ( ) ;
851- }
852- } ,
853- ] ,
854- [ 'wheel' , event => this . _wheelListener ( event as WheelEvent ) ] ,
855- ) ;
825+ this . _addListener ( 'mouseleave' , ( event : MouseEvent ) => {
826+ const newTarget = event . relatedTarget as Node | null ;
827+ if ( ! newTarget || ! this . _overlayRef ?. overlayElement . contains ( newTarget ) ) {
828+ this . hide ( ) ;
829+ }
830+ } ) ;
831+
832+ this . _addListener ( 'wheel' , ( event : WheelEvent ) => {
833+ if ( this . _isTooltipVisible ( ) ) {
834+ const elementUnderPointer = this . _document . elementFromPoint ( event . clientX , event . clientY ) ;
835+ const element = this . _elementRef . nativeElement ;
836+
837+ // On non-touch devices we depend on the `mouseleave` event to close the tooltip, but it
838+ // won't fire if the user scrolls away using the wheel without moving their cursor. We
839+ // work around it by finding the element under the user's cursor and closing the tooltip
840+ // if it's not the trigger.
841+ if ( elementUnderPointer !== element && ! element . contains ( elementUnderPointer ) ) {
842+ this . hide ( ) ;
843+ }
844+ }
845+ } ) ;
856846 } else if ( this . touchGestures !== 'off' ) {
857847 this . _disableNativeGesturesIfNecessary ( ) ;
858848 const touchendListener = ( ) => {
@@ -862,17 +852,15 @@ export class MatTooltip implements OnDestroy, AfterViewInit {
862852 this . hide ( this . _defaultOptions ?. touchendHideDelay ) ;
863853 } ;
864854
865- exitListeners . push ( [ 'touchend' , touchendListener ] , [ 'touchcancel' , touchendListener ] ) ;
855+ this . _addListener ( 'touchend' , touchendListener ) ;
856+ this . _addListener ( 'touchcancel' , touchendListener ) ;
866857 }
867-
868- this . _addListeners ( exitListeners ) ;
869- this . _passiveListeners . push ( ...exitListeners ) ;
870858 }
871859
872- private _addListeners ( listeners : ( readonly [ string , EventListenerOrEventListenerObject ] ) [ ] ) {
873- listeners . forEach ( ( [ event , listener ] ) => {
874- this . _elementRef . nativeElement . addEventListener ( event , listener , passiveListenerOptions ) ;
875- } ) ;
860+ private _addListener < T extends Event > ( name : string , listener : ( event : T ) => void ) {
861+ this . _eventCleanups . push (
862+ this . _renderer . listen ( this . _elementRef . nativeElement , name , listener , passiveListenerOptions ) ,
863+ ) ;
876864 }
877865
878866 private _isTouchPlatform ( ) : boolean {
@@ -890,24 +878,6 @@ export class MatTooltip implements OnDestroy, AfterViewInit {
890878 ) ;
891879 }
892880
893- /** Listener for the `wheel` event on the element. */
894- private _wheelListener ( event : WheelEvent ) {
895- if ( this . _isTooltipVisible ( ) ) {
896- const elementUnderPointer = this . _injector
897- . get ( DOCUMENT )
898- . elementFromPoint ( event . clientX , event . clientY ) ;
899- const element = this . _elementRef . nativeElement ;
900-
901- // On non-touch devices we depend on the `mouseleave` event to close the tooltip, but it
902- // won't fire if the user scrolls away using the wheel without moving their cursor. We
903- // work around it by finding the element under the user's cursor and closing the tooltip
904- // if it's not the trigger.
905- if ( elementUnderPointer !== element && ! element . contains ( elementUnderPointer ) ) {
906- this . hide ( ) ;
907- }
908- }
909- }
910-
911881 /** Disables the native browser gestures, based on how the tooltip has been configured. */
912882 private _disableNativeGesturesIfNecessary ( ) {
913883 const gestures = this . touchGestures ;
0 commit comments