Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 54 additions & 32 deletions projects/ngneat/helipopper/src/lib/tippy.directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ import {
import { isPlatformServer } from '@angular/common';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import type { Instance } from 'tippy.js';
import { fromEvent, merge, Observable, Subject } from 'rxjs';
import { filter, map, switchMap, takeUntil } from 'rxjs/operators';
import { merge, Observable, Subject } from 'rxjs';
import { switchMap, takeUntil } from 'rxjs/operators';
import {
Content,
isComponent,
Expand Down Expand Up @@ -70,6 +70,9 @@ const defaultTriggerTarget: TippyProps['triggerTarget'] = null;
const defaultZIndex: TippyProps['zIndex'] = 9999;
const defaultAnimation: TippyProps['animation'] = 'fade';

// Available since Angular 20.
declare const ngServerMode: boolean;

@Directive({
// eslint-disable-next-line @angular-eslint/directive-selector
selector: '[tp]',
Expand Down Expand Up @@ -224,7 +227,10 @@ export class TippyDirective implements OnChanges, AfterViewInit, OnInit {
}

private destroyRef = inject(DestroyRef);
private isServer = isPlatformServer(inject(PLATFORM_ID));
private isServer =
// Drop `isPlatformServer` once `ngServeMode` is available during compilation.
(typeof ngServerMode !== 'undefined' && ngServerMode) ||
isPlatformServer(inject(PLATFORM_ID));
private tippyFactory = inject(TippyFactory);
private destroyed = false;
private created = false;
Expand Down Expand Up @@ -523,35 +529,52 @@ export class TippyDirective implements OnChanges, AfterViewInit, OnInit {
}

protected handleContextMenu() {
fromEvent<MouseEvent>(this.host(), 'contextmenu')
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((event: MouseEvent) => {
event.preventDefault();

this.instance.setProps({
getReferenceClientRect: () =>
({
width: 0,
height: 0,
top: event.clientY,
bottom: event.clientY,
left: event.clientX,
right: event.clientX,
} as DOMRectReadOnly),
});

this.instance.show();
const host = this.host();
const onContextMenu = (event: MouseEvent) => {
event.preventDefault();

this.instance.setProps({
getReferenceClientRect: () =>
({
width: 0,
height: 0,
top: event.clientY,
bottom: event.clientY,
left: event.clientX,
right: event.clientX,
} as DOMRectReadOnly),
});

this.instance.show();
};

host.addEventListener('contextmenu', onContextMenu);
this.destroyRef.onDestroy(() =>
host.removeEventListener('contextmenu', onContextMenu)
);
}

protected handleEscapeButton(): void {
fromEvent<KeyboardEvent>(document.body, 'keydown')
.pipe(
filter(({ code }: KeyboardEvent) => code === 'Escape'),
takeUntil(this.visibleInternal.pipe(filter((v) => !v))),
takeUntilDestroyed(this.destroyRef)
)
.subscribe(() => this.hide());
const onKeydown = (event: KeyboardEvent) => {
if (event.code === 'Escape') {
this.hide();
}
};

document.body.addEventListener('keydown', onKeydown);

// Remove listener when `visibleInternal` becomes false.
const visibleSubscription = this.visibleInternal.subscribe((v) => {
if (!v) {
document.body.removeEventListener('keydown', onKeydown);
visibleSubscription.unsubscribe();
}
});

this.destroyRef.onDestroy(() => {
document.body.removeEventListener('keydown', onKeydown);
visibleSubscription.unsubscribe();
});
}

protected checkOverflow(isElementOverflow: boolean) {
Expand Down Expand Up @@ -607,16 +630,15 @@ export class TippyDirective implements OnChanges, AfterViewInit, OnInit {
this.contentChanged.pipe(
// We need to wait for the content to be rendered before we can check if it's overflowing.
switchMap(() => {
return new Observable((subscriber) => {
return new Observable<boolean>((subscriber) => {
const id = window.requestAnimationFrame(() => {
subscriber.next();
subscriber.next(isElementOverflow(host));
subscriber.complete();
});

return () => cancelAnimationFrame(id);
});
}),
map(() => isElementOverflow(host))
})
)
);
}
Expand Down