Created
March 31, 2018 15:28
-
-
Save amcdnl/c2d986dbd7f7836a383c378599e721bc to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Directive, ViewContainerRef, ElementRef, Input, TemplateRef, OnInit, OnDestroy, NgZone } from '@angular/core'; | |
import { Subject } from 'rxjs/Subject'; | |
import { Overlay, OverlayRef, OriginConnectionPosition, OverlayConnectionPosition } from '@angular/cdk/overlay'; | |
import { fromEvent } from 'rxjs/observable/fromEvent'; | |
import { takeUntil } from 'rxjs/operators'; | |
import { TemplatePortal, ComponentPortal } from '@angular/cdk/portal'; | |
import { PopoverComponent } from './popover.component'; | |
import { Subscription } from 'rxjs/Subscription'; | |
@Directive({ | |
selector: '[dfPopover]' | |
}) | |
export class PopoverTriggerDirective implements OnInit, OnDestroy { | |
@Input('popoverTrigger') trigger: 'hover' = 'hover'; | |
@Input('popoverDelay') delay = 100; | |
@Input('popoverContent') content: string; | |
@Input('popoverTemplate') template: TemplateRef<any>; | |
@Input('popoverTemplateContext') context: any; | |
@Input('popoverMargin') margin: number; | |
@Input('popoverPosition') position: 'left' | 'right' | 'above' | 'below' | 'before' | 'after' = 'above'; | |
private _mouseLeaveSubscription: Subscription; | |
private _contextSubscription: Subscription; | |
private _destroy$: Subject<void>; | |
private _overlayRef: OverlayRef; | |
private _timeout: any; | |
constructor( | |
private _overlay: Overlay, | |
private _elementRef: ElementRef, | |
private _ngZone: NgZone | |
) {} | |
ngOnInit() { | |
this.hookupTriggers(); | |
} | |
ngOnDestroy() { | |
this.hide(0); | |
if (this._destroy$) { | |
this._destroy$.next(); | |
this._destroy$.complete(); | |
} | |
} | |
hookupTriggers() { | |
this._destroy$ = new Subject(); | |
if (this.trigger === 'hover') { | |
fromEvent(this._elementRef.nativeElement, 'mouseenter') | |
.pipe(takeUntil(this._destroy$)) | |
.subscribe(() => this.show()); | |
} | |
} | |
show() { | |
if (!this.content && !this.template) { | |
throw new Error('Template or content is not defined.'); | |
} | |
if (this.trigger === 'hover') { | |
this._mouseLeaveSubscription = | |
fromEvent(this._elementRef.nativeElement, 'mouseleave') | |
.subscribe(() => this.hide()); | |
} | |
// if a user does a right click, hide the popover | |
this._contextSubscription = | |
fromEvent(this._elementRef.nativeElement, 'contextmenu') | |
.subscribe(() => this.hide(0)); | |
clearTimeout(this._timeout); | |
this._timeout = setTimeout(() => { | |
// if the overlay is already opened, don't reopen it | |
if (this._overlayRef) return; | |
const originPosition = this.getOriginPosition(); | |
const overlayPosition = this.getOverlayPosition(); | |
const positionStrategy = this._overlay | |
.position() | |
.flexibleConnectedTo(this._elementRef) | |
.withFlexibleWidth(false) | |
.withFlexibleHeight(false) | |
.withViewportMargin(this.margin) | |
.withPositions([{ ...originPosition, ...overlayPosition }]); | |
this._overlayRef = this._overlay.create({ positionStrategy }); | |
const componentPortal = new ComponentPortal(PopoverComponent); | |
const componentRef = this._overlayRef.attach(componentPortal); | |
// virtual scroll causing popovers to mess up sometimes | |
this._ngZone.run(() => { | |
componentRef.instance.content = this.content; | |
componentRef.instance.template = this.template; | |
componentRef.instance.context = this.context; | |
}); | |
}, this.delay); | |
} | |
getOriginPosition(): OriginConnectionPosition { | |
if (this.position === 'above' || this.position === 'below') { | |
return { | |
originX: 'center', | |
originY: this.position === 'above' ? 'top' : 'bottom' | |
}; | |
} else if (this.position === 'before' || this.position === 'left') { | |
return { originX: 'start', originY: 'center' }; | |
} else if (this.position === 'after' || this.position === 'right') { | |
return { originX: 'end', originY: 'center' }; | |
} | |
} | |
getOverlayPosition(): OverlayConnectionPosition { | |
if (this.position === 'above') { | |
return { overlayX: 'center', overlayY: 'bottom' }; | |
} else if (this.position === 'below') { | |
return { overlayX: 'center', overlayY: 'top' }; | |
} else if (this.position === 'before' || this.position === 'left') { | |
return { overlayX: 'end', overlayY: 'center' }; | |
} else if (this.position === 'after' || this.position === 'right') { | |
return { overlayX: 'start', overlayY: 'center' }; | |
} | |
} | |
hide(delay = this.delay) { | |
clearTimeout(this._timeout); | |
this._timeout = setTimeout(() => { | |
if (this._overlayRef) { | |
this._overlayRef.detach(); | |
this._overlayRef.dispose(); | |
this._overlayRef = undefined; | |
if (this._mouseLeaveSubscription) { | |
this._mouseLeaveSubscription.unsubscribe(); | |
} | |
if (this._contextSubscription) { | |
this._contextSubscription.unsubscribe(); | |
} | |
} | |
}, delay); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Component, ChangeDetectionStrategy, Input, TemplateRef, ElementRef } from '@angular/core'; | |
@Component({ | |
selector: 'df-popover', | |
template: ` | |
<div class="popover"> | |
<span *ngIf="content" [innerHTML]="content"></span> | |
<ng-template | |
*ngIf="template" | |
[ngTemplateOutlet]="template" | |
[ngTemplateOutletContext]="{ context: context }"> | |
</ng-template> | |
</div> | |
`, | |
host: { | |
'class': 'mat-elevation-z2' | |
}, | |
styleUrls: ['./popover.component.scss'], | |
changeDetection: ChangeDetectionStrategy.OnPush | |
}) | |
export class PopoverComponent { | |
@Input() content: string; | |
@Input() template: TemplateRef<any>; | |
@Input() context: any; | |
constructor(public elementRef: ElementRef) {} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment