import { useAnimation } from '@angular/animations'; import { Directive, OnInit, OnDestroy, Output, ElementRef, Optional, ViewContainerRef, HostListener, Input, EventEmitter } from '@angular/core'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; import { fadeOut } from '../../animations/fade'; import { scaleInCenter } from '../../animations/scale'; import { IgxNavigationService } from '../../core/navigation'; import { IBaseEventArgs } from '../../core/utils'; import { AutoPositionStrategy, HorizontalAlignment, PositionSettings } from '../../services/public_api'; import { IgxToggleActionDirective } from '../toggle/toggle.directive'; import { IgxTooltipComponent } from './tooltip.component'; import { IgxTooltipDirective } from './tooltip.directive'; export interface ITooltipShowEventArgs extends IBaseEventArgs { target: IgxTooltipTargetDirective; tooltip: IgxTooltipDirective; cancel: boolean; } export interface ITooltipHideEventArgs extends IBaseEventArgs { target: IgxTooltipTargetDirective; tooltip: IgxTooltipDirective; cancel: boolean; } /** * **Ignite UI for Angular Tooltip Target** - * [Documentation](https://www.infragistics.com/products/ignite-ui-angular/angular/components/tooltip) * * The Ignite UI for Angular Tooltip Target directive is used to mark an HTML element in the markup as one that has a tooltip. * The tooltip target is used in combination with the Ignite UI for Angular Tooltip by assigning the exported tooltip reference to the * target's selector property. * * Example: * ```html * * Hello there, I am a tooltip! * ``` */ @Directive({ exportAs: 'tooltipTarget', selector: '[igxTooltipTarget]', standalone: true }) export class IgxTooltipTargetDirective extends IgxToggleActionDirective implements OnInit, OnDestroy { /** * Gets/sets the amount of milliseconds that should pass before showing the tooltip. * * ```typescript * // get * let tooltipShowDelay = this.tooltipTarget.showDelay; * ``` * * ```html * * * Hello there, I am a tooltip! * ``` */ @Input('showDelay') public showDelay = 500; /** * Gets/sets the amount of milliseconds that should pass before hiding the tooltip. * * ```typescript * // get * let tooltipHideDelay = this.tooltipTarget.hideDelay; * ``` * * ```html * * * Hello there, I am a tooltip! * ``` */ @Input('hideDelay') public hideDelay = 500; /** * Specifies if the tooltip should not show when hovering its target with the mouse. (defaults to false) * While setting this property to 'true' will disable the user interactions that shows/hides the tooltip, * the developer will still be able to show/hide the tooltip through the API. * * ```typescript * // get * let tooltipDisabledValue = this.tooltipTarget.tooltipDisabled; * ``` * * ```html * * * Hello there, I am a tooltip! * ``` */ @Input('tooltipDisabled') public tooltipDisabled = false; /** * @hidden */ @Input('igxTooltipTarget') public override set target(target: any) { if (target !== null && target !== '') { this._target = target; } } /** * @hidden */ public override get target(): any { if (typeof this._target === 'string') { return this._navigationService.get(this._target); } return this._target; } /** * @hidden */ @Input() public set tooltip(content: any) { if (!this.target && (typeof content === 'string' || content instanceof String)) { const tooltipComponent = this._viewContainerRef.createComponent(IgxTooltipComponent); tooltipComponent.instance.content = content as string; this._target = tooltipComponent.instance.tooltip; } } /** * Gets the respective native element of the directive. * * ```typescript * let tooltipTargetElement = this.tooltipTarget.nativeElement; * ``` */ public get nativeElement() { return this._element.nativeElement; } /** * Indicates if the tooltip that is is associated with this target is currently hidden. * * ```typescript * let tooltipHiddenValue = this.tooltipTarget.tooltipHidden; * ``` */ public get tooltipHidden(): boolean { return !this.target || this.target.collapsed; } /** * Emits an event when the tooltip that is associated with this target starts showing. * This event is fired before the start of the countdown to showing the tooltip. * * ```typescript * tooltipShowing(args: ITooltipShowEventArgs) { * alert("Tooltip started showing!"); * } * ``` * * ```html * * Hello there, I am a tooltip! * ``` */ @Output() public tooltipShow = new EventEmitter(); /** * Emits an event when the tooltip that is associated with this target starts hiding. * This event is fired before the start of the countdown to hiding the tooltip. * * ```typescript * tooltipHiding(args: ITooltipHideEventArgs) { * alert("Tooltip started hiding!"); * } * ``` * * ```html * * Hello there, I am a tooltip! * ``` */ @Output() public tooltipHide = new EventEmitter(); private destroy$ = new Subject(); constructor(private _element: ElementRef, @Optional() private _navigationService: IgxNavigationService, private _viewContainerRef: ViewContainerRef) { super(_element, _navigationService); } /** * @hidden */ @HostListener('click') public override onClick() { if (!this.target.collapsed) { this.target.forceClose(this.mergedOverlaySettings); } } /** * @hidden */ @HostListener('mouseenter') public onMouseEnter() { if (this.tooltipDisabled) { return; } this.checkOutletAndOutsideClick(); const shouldReturn = this.preMouseEnterCheck(); if (shouldReturn) { return; } const showingArgs = { target: this, tooltip: this.target, cancel: false }; this.tooltipShow.emit(showingArgs); if (showingArgs.cancel) { return; } this.target.toBeShown = true; this.target.timeoutId = setTimeout(() => { this.target.open(this.mergedOverlaySettings); // Call open() of IgxTooltipDirective this.target.toBeShown = false; }, this.showDelay); } /** * @hidden */ @HostListener('mouseleave') public onMouseLeave() { if (this.tooltipDisabled) { return; } this.checkOutletAndOutsideClick(); const shouldReturn = this.preMouseLeaveCheck(); if (shouldReturn || this.target.collapsed) { return; } this.target.toBeHidden = true; this.target.timeoutId = setTimeout(() => { this.target.close(); // Call close() of IgxTooltipDirective this.target.toBeHidden = false; }, this.hideDelay); } /** * @hidden */ @HostListener('touchstart') public onTouchStart() { if (this.tooltipDisabled) { return; } this.showTooltip(); } /** * @hidden */ @HostListener('document:touchstart', ['$event']) public onDocumentTouchStart(event) { if (this.tooltipDisabled) { return; } if (this.nativeElement !== event.target && !this.nativeElement.contains(event.target) ) { this.hideTooltip(); } } /** * @hidden */ public override ngOnInit() { super.ngOnInit(); const positionSettings: PositionSettings = { horizontalDirection: HorizontalAlignment.Center, horizontalStartPoint: HorizontalAlignment.Center, openAnimation: useAnimation(scaleInCenter, { params: { duration: '150ms' } }), closeAnimation: useAnimation(fadeOut, { params: { duration: '75ms' } }) }; this._overlayDefaults.positionStrategy = new AutoPositionStrategy(positionSettings); this._overlayDefaults.closeOnOutsideClick = false; this._overlayDefaults.closeOnEscape = true; this.target.closing.pipe(takeUntil(this.destroy$)).subscribe((event) => { const hidingArgs = { target: this, tooltip: this.target, cancel: false }; this.tooltipHide.emit(hidingArgs); if (hidingArgs.cancel) { event.cancel = true; } }); } /** * @hidden */ public ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); } /** * Shows the tooltip by respecting the 'showDelay' property. * * ```typescript * this.tooltipTarget.showTooltip(); * ``` */ public showTooltip() { clearTimeout(this.target.timeoutId); if (!this.target.collapsed) { // if close animation has started finish it, or close the tooltip with no animation this.target.forceClose(this.mergedOverlaySettings); this.target.toBeHidden = false; } const showingArgs = { target: this, tooltip: this.target, cancel: false }; this.tooltipShow.emit(showingArgs); if (showingArgs.cancel) { return; } this.target.toBeShown = true; this.target.timeoutId = setTimeout(() => { this.target.open(this.mergedOverlaySettings); // Call open() of IgxTooltipDirective this.target.toBeShown = false; }, this.showDelay); } /** * Hides the tooltip by respecting the 'hideDelay' property. * * ```typescript * this.tooltipTarget.hideTooltip(); * ``` */ public hideTooltip() { if (this.target.collapsed && this.target.toBeShown) { clearTimeout(this.target.timeoutId); } if (this.target.collapsed || this.target.toBeHidden) { return; } this.target.toBeHidden = true; this.target.timeoutId = setTimeout(() => { this.target.close(); // Call close() of IgxTooltipDirective this.target.toBeHidden = false; }, this.hideDelay); } private checkOutletAndOutsideClick() { if (this.outlet) { this._overlayDefaults.outlet = this.outlet; } } private get mergedOverlaySettings() { return Object.assign({}, this._overlayDefaults, this.overlaySettings); } // Return true if the execution in onMouseEnter should be terminated after this method private preMouseEnterCheck() { // If tooltip is about to be opened if (this.target.toBeShown) { clearTimeout(this.target.timeoutId); this.target.toBeShown = false; } // If Tooltip is opened or about to be hidden if (!this.target.collapsed || this.target.toBeHidden) { clearTimeout(this.target.timeoutId); // if close animation has started finish it, or close the tooltip with no animation this.target.forceClose(this.mergedOverlaySettings); this.target.toBeHidden = false; } return false; } // Return true if the execution in onMouseLeave should be terminated after this method private preMouseLeaveCheck(): boolean { clearTimeout(this.target.timeoutId); // If tooltip is about to be opened if (this.target.toBeShown) { this.target.toBeShown = false; this.target.toBeHidden = false; return true; } return false; } }