@@ -22,8 +22,8 @@ import {
2222import { isPlatformServer } from '@angular/common' ;
2323import { takeUntilDestroyed } from '@angular/core/rxjs-interop' ;
2424import type { Instance } from 'tippy.js' ;
25- import { fromEvent , merge , Observable , Subject } from 'rxjs' ;
26- import { filter , map , switchMap , takeUntil } from 'rxjs/operators' ;
25+ import { merge , Observable , Subject } from 'rxjs' ;
26+ import { switchMap , takeUntil } from 'rxjs/operators' ;
2727import {
2828 Content ,
2929 isComponent ,
@@ -70,6 +70,9 @@ const defaultTriggerTarget: TippyProps['triggerTarget'] = null;
7070const defaultZIndex : TippyProps [ 'zIndex' ] = 9999 ;
7171const defaultAnimation : TippyProps [ 'animation' ] = 'fade' ;
7272
73+ // Available since Angular 20.
74+ declare const ngServerMode : boolean ;
75+
7376@Directive ( {
7477 // eslint-disable-next-line @angular-eslint/directive-selector
7578 selector : '[tp]' ,
@@ -224,7 +227,10 @@ export class TippyDirective implements OnChanges, AfterViewInit, OnInit {
224227 }
225228
226229 private destroyRef = inject ( DestroyRef ) ;
227- private isServer = isPlatformServer ( inject ( PLATFORM_ID ) ) ;
230+ private isServer =
231+ // Drop `isPlatformServer` once `ngServeMode` is available during compilation.
232+ ( typeof ngServerMode !== 'undefined' && ngServerMode ) ||
233+ isPlatformServer ( inject ( PLATFORM_ID ) ) ;
228234 private tippyFactory = inject ( TippyFactory ) ;
229235 private destroyed = false ;
230236 private created = false ;
@@ -523,35 +529,52 @@ export class TippyDirective implements OnChanges, AfterViewInit, OnInit {
523529 }
524530
525531 protected handleContextMenu ( ) {
526- fromEvent < MouseEvent > ( this . host ( ) , 'contextmenu' )
527- . pipe ( takeUntilDestroyed ( this . destroyRef ) )
528- . subscribe ( ( event : MouseEvent ) => {
529- event . preventDefault ( ) ;
530-
531- this . instance . setProps ( {
532- getReferenceClientRect : ( ) =>
533- ( {
534- width : 0 ,
535- height : 0 ,
536- top : event . clientY ,
537- bottom : event . clientY ,
538- left : event . clientX ,
539- right : event . clientX ,
540- } as DOMRectReadOnly ) ,
541- } ) ;
542-
543- this . instance . show ( ) ;
532+ const host = this . host ( ) ;
533+ const onContextMenu = ( event : MouseEvent ) => {
534+ event . preventDefault ( ) ;
535+
536+ this . instance . setProps ( {
537+ getReferenceClientRect : ( ) =>
538+ ( {
539+ width : 0 ,
540+ height : 0 ,
541+ top : event . clientY ,
542+ bottom : event . clientY ,
543+ left : event . clientX ,
544+ right : event . clientX ,
545+ } as DOMRectReadOnly ) ,
544546 } ) ;
547+
548+ this . instance . show ( ) ;
549+ } ;
550+
551+ host . addEventListener ( 'contextmenu' , onContextMenu ) ;
552+ this . destroyRef . onDestroy ( ( ) =>
553+ host . removeEventListener ( 'contextmenu' , onContextMenu )
554+ ) ;
545555 }
546556
547557 protected handleEscapeButton ( ) : void {
548- fromEvent < KeyboardEvent > ( document . body , 'keydown' )
549- . pipe (
550- filter ( ( { code } : KeyboardEvent ) => code === 'Escape' ) ,
551- takeUntil ( this . visibleInternal . pipe ( filter ( ( v ) => ! v ) ) ) ,
552- takeUntilDestroyed ( this . destroyRef )
553- )
554- . subscribe ( ( ) => this . hide ( ) ) ;
558+ const onKeydown = ( event : KeyboardEvent ) => {
559+ if ( event . code === 'Escape' ) {
560+ this . hide ( ) ;
561+ }
562+ } ;
563+
564+ document . body . addEventListener ( 'keydown' , onKeydown ) ;
565+
566+ // Remove listener when `visibleInternal` becomes false.
567+ const visibleSubscription = this . visibleInternal . subscribe ( ( v ) => {
568+ if ( ! v ) {
569+ document . body . removeEventListener ( 'keydown' , onKeydown ) ;
570+ visibleSubscription . unsubscribe ( ) ;
571+ }
572+ } ) ;
573+
574+ this . destroyRef . onDestroy ( ( ) => {
575+ document . body . removeEventListener ( 'keydown' , onKeydown ) ;
576+ visibleSubscription . unsubscribe ( ) ;
577+ } ) ;
555578 }
556579
557580 protected checkOverflow ( isElementOverflow : boolean ) {
@@ -607,16 +630,15 @@ export class TippyDirective implements OnChanges, AfterViewInit, OnInit {
607630 this . contentChanged . pipe (
608631 // We need to wait for the content to be rendered before we can check if it's overflowing.
609632 switchMap ( ( ) => {
610- return new Observable ( ( subscriber ) => {
633+ return new Observable < boolean > ( ( subscriber ) => {
611634 const id = window . requestAnimationFrame ( ( ) => {
612- subscriber . next ( ) ;
635+ subscriber . next ( isElementOverflow ( host ) ) ;
613636 subscriber . complete ( ) ;
614637 } ) ;
615638
616639 return ( ) => cancelAnimationFrame ( id ) ;
617640 } ) ;
618- } ) ,
619- map ( ( ) => isElementOverflow ( host ) )
641+ } )
620642 )
621643 ) ;
622644 }
0 commit comments