@engie-group/fluid-design-system-angular
Version:
Fluid Design System Angular
188 lines (166 loc) • 5.92 kB
text/typescript
import { ConnectedPosition, Overlay, OverlayPositionBuilder, OverlayRef, ScrollDispatcher } from '@angular/cdk/overlay';
import { ComponentPortal } from '@angular/cdk/portal';
import { DOCUMENT } from '@angular/common';
import {
ComponentRef,
Directive,
ElementRef,
HostListener,
Inject,
Input,
NgZone,
OnDestroy,
OnInit,
TemplateRef
} from '@angular/core';
import { fromEvent, merge, Subject, takeUntil } from 'rxjs';
import { TooltipComponent } from '../tooltip.component';
import { TooltipOptions } from '../tooltip.model';
export class TooltipDirective implements OnInit, OnDestroy {
private unsubscribe: Subject<void> = new Subject<void>();
private _tooltipOptions: TooltipOptions;
private overlayRef: OverlayRef;
private tooltipRef: ComponentRef<TooltipComponent>;
private intersectionObserver: IntersectionObserver;
set tooltipOptions(value: TooltipOptions) {
this._tooltipOptions = value;
this.setTooltipValues();
const scrollableAncestors = this.scrollDispatcher.getAncestorScrollContainers(this.el);
const positionStrategy = this.overlayPositionBuilder
.flexibleConnectedTo(this.el)
.withPositions([this.getPositionOptions()])
.withScrollableContainers(scrollableAncestors);
this.overlayRef?.updatePositionStrategy(positionStrategy);
}
get tooltipOptions(): TooltipOptions {
return this._tooltipOptions;
}
tooltipCustomContent: TemplateRef<any>;
constructor(
private el: ElementRef,
private overlayPositionBuilder: OverlayPositionBuilder,
private overlay: Overlay,
private scrollDispatcher: ScrollDispatcher,
private doc: Document,
private zone: NgZone
) {}
ngOnInit(): void {
const scrollableAncestors = this.scrollDispatcher.getAncestorScrollContainers(this.el);
const positionStrategy = this.overlayPositionBuilder
.flexibleConnectedTo(this.el)
.withPositions([this.getPositionOptions()])
.withScrollableContainers(scrollableAncestors);
this.overlayRef = this.overlay.create({
positionStrategy,
scrollStrategy: this.overlay.scrollStrategies.reposition()
});
}
ngOnDestroy() {
this.overlayRef?.dispose();
this.unsubscribe.next();
this.unsubscribe.complete();
}
getPositionOptions(): ConnectedPosition {
const defaultPosition: ConnectedPosition = {
originX: 'center',
originY: 'top',
overlayX: 'center',
overlayY: 'bottom'
};
switch (this.tooltipOptions?.placement) {
case 'bottom':
return {
originX: 'center',
originY: 'bottom',
overlayX: 'center',
overlayY: 'top'
};
case 'top':
return defaultPosition;
case 'left':
return {
originX: 'start',
originY: 'center',
overlayX: 'end',
overlayY: 'center'
};
case 'right':
return {
originX: 'end',
originY: 'center',
overlayX: 'start',
overlayY: 'center'
};
default:
return defaultPosition;
}
}
show() {
if (this.tooltipRef) {
return;
}
this.tooltipRef = this.overlayRef.attach(new ComponentPortal(TooltipComponent));
merge(
fromEvent(this.tooltipRef?.location?.nativeElement, 'mouseleave'),
fromEvent(this.tooltipRef?.location?.nativeElement, 'focusout')
)
.pipe(takeUntil(this.unsubscribe))
.subscribe((event) => {
this.hide(event);
});
this.zone.runOutsideAngular(() => {
this.intersectionObserver = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
const isElementVisible = entry.isIntersecting;
if (!isElementVisible) {
this.zone.run(() => {
this.hide();
});
}
});
});
this.intersectionObserver.observe(this.el?.nativeElement);
});
this.setTooltipValues();
this.el?.nativeElement?.firstElementChild?.setAttribute('aria-describedby', this.tooltipOptions.tooltipId);
}
hide(event?) {
const focusedElement = this.doc?.activeElement;
const isFocusedElement = focusedElement && this.el.nativeElement.contains(focusedElement);
const newTarget = (event as MouseEvent)?.relatedTarget as Node | null;
const isNextTargetTooltip = newTarget && this.overlayRef?.overlayElement?.contains(newTarget);
const isNextTargetElement = newTarget && this.el?.nativeElement?.contains(newTarget);
if (!newTarget || (!isNextTargetTooltip && !isNextTargetElement && !isFocusedElement)) {
this.overlayRef.detach();
this.tooltipRef = null;
this.unsubscribe.next();
this.intersectionObserver.disconnect();
this.el?.nativeElement?.firstElementChild?.removeAttribute('aria-describedby');
}
}
setTooltipValues() {
const tooltipComponent = this.tooltipRef?.instance;
if (!tooltipComponent) {
return;
}
tooltipComponent.label = this.tooltipCustomContent ? null : this.tooltipOptions?.label;
tooltipComponent.isInverse = this.tooltipOptions?.isInverse;
tooltipComponent.hasArrow = this.tooltipOptions?.hasArrow ?? true;
tooltipComponent.tooltipId = this.tooltipOptions?.tooltipId;
tooltipComponent.arrowPlacement = this.tooltipOptions?.arrowPlacement ?? 'center';
tooltipComponent.placement = this.tooltipOptions?.placement ?? 'top';
tooltipComponent.isStandalone = this.tooltipOptions?.isStandalone ?? true;
tooltipComponent.isAnimated = this.tooltipOptions?.isAnimated ?? true;
tooltipComponent.contentTemplateRef = this.tooltipCustomContent;
}
}