227 lines
6.3 KiB
TypeScript
227 lines
6.3 KiB
TypeScript
/* Angular Imports */
|
|
import { ComponentType, Overlay, OriginConnectionPosition, OverlayConnectionPosition, ConnectionPositionPair } from '@angular/cdk/overlay';
|
|
import { ComponentPortal, TemplatePortal } from '@angular/cdk/portal';
|
|
import { Injectable, InjectionToken, Injector, TemplateRef, ElementRef } from '@angular/core';
|
|
|
|
/* Custom Imports */
|
|
import { PopoverConfig } from './popover-config';
|
|
import { PopoverRef } from './popover-ref';
|
|
import { PopoverComponent } from './popover.component';
|
|
|
|
/**
|
|
* Injection token that can be used to access the data that was passed in to a popover.
|
|
*
|
|
*/
|
|
export const POPOVER_DATA = new InjectionToken('popover.data');
|
|
|
|
const defaultConfig: PopoverConfig = {
|
|
backdropClass: '',
|
|
disableClose: false,
|
|
panelClass: '',
|
|
arrowOffset: 30,
|
|
arrowSize: 20
|
|
};
|
|
|
|
/**
|
|
* Service to open modal and manage popovers.
|
|
*/
|
|
@Injectable({
|
|
providedIn: 'root'
|
|
})
|
|
export class PopoverService {
|
|
/**
|
|
* @param {Injector} injector Injector.
|
|
* @param {overlay} overlay Overlay.
|
|
*/
|
|
constructor(private overlay: Overlay, private injector: Injector) { }
|
|
|
|
/**
|
|
* Opens the popover at target element
|
|
* @param {ComponentType<any> | TemplateRef<any>} componentOrTemplate ComponentType<any> | TemplateRef<any>.
|
|
* @param {ElementRef | HTMLElement} target ElementRef | HTMLElement.
|
|
* @param {string} position String.
|
|
* @param {Partial<PopoverConfig> = {}} config Partial<PopoverConfig> = {}.
|
|
*/
|
|
open<D = any>(componentOrTemplate: ComponentType<any> | TemplateRef<any>,
|
|
target: ElementRef | HTMLElement, position: string,
|
|
backdrop: boolean,
|
|
config: Partial<PopoverConfig> = {}): PopoverRef<D> {
|
|
const popoverConfig: PopoverConfig = Object.assign({}, defaultConfig, config);
|
|
|
|
const arrowSize = popoverConfig.arrowSize;
|
|
const arrowOffset = popoverConfig.arrowOffset;
|
|
const panelOffset = arrowSize / 2;
|
|
let showbackdrop: boolean;
|
|
let positions: ConnectionPositionPair[];
|
|
// preferred positions, in order of priority
|
|
if (position === 'bottom') {
|
|
positions = [
|
|
// bottom center
|
|
{
|
|
overlayX: 'center',
|
|
overlayY: 'top',
|
|
originX: 'center',
|
|
originY: 'bottom',
|
|
panelClass: ['top', 'center'],
|
|
offsetY: panelOffset
|
|
},
|
|
// bottom left
|
|
{
|
|
overlayX: 'start',
|
|
overlayY: 'top',
|
|
originX: 'center',
|
|
originY: 'bottom',
|
|
panelClass: ['top', 'left'],
|
|
offsetX: -1 * arrowOffset,
|
|
offsetY: panelOffset
|
|
},
|
|
// bottom right
|
|
{
|
|
overlayX: 'end',
|
|
overlayY: 'top',
|
|
originX: 'center',
|
|
originY: 'bottom',
|
|
panelClass: ['top', 'right'],
|
|
offsetX: arrowOffset,
|
|
offsetY: panelOffset
|
|
}
|
|
];
|
|
} else if (position === 'top') {
|
|
positions = [
|
|
// top center
|
|
{
|
|
overlayX: 'center',
|
|
overlayY: 'bottom',
|
|
originX: 'center',
|
|
originY: 'top',
|
|
panelClass: ['bottom', 'center'],
|
|
offsetY: -1 * panelOffset
|
|
},
|
|
// top left
|
|
{
|
|
overlayX: 'start',
|
|
overlayY: 'bottom',
|
|
originX: 'center',
|
|
originY: 'top',
|
|
panelClass: ['bottom', 'left'],
|
|
offsetX: -1 * arrowOffset,
|
|
offsetY: -1 * panelOffset
|
|
},
|
|
// top right
|
|
{
|
|
overlayX: 'end',
|
|
overlayY: 'bottom',
|
|
originX: 'center',
|
|
originY: 'top',
|
|
panelClass: ['bottom', 'right'],
|
|
offsetX: arrowOffset,
|
|
offsetY: -1 * panelOffset
|
|
}
|
|
];
|
|
} else if (position === 'right') {
|
|
positions = [
|
|
// right
|
|
{
|
|
originX : 'end',
|
|
originY : 'center',
|
|
overlayX: 'start',
|
|
overlayY: 'center',
|
|
panelClass: ['center', 'left'],
|
|
offsetX: 0.5 * arrowOffset,
|
|
offsetY: -1 * panelOffset
|
|
},
|
|
];
|
|
} else if (position === 'left') {
|
|
positions = [
|
|
// left
|
|
{
|
|
originX : 'start',
|
|
originY : 'center',
|
|
overlayX: 'end',
|
|
overlayY: 'center',
|
|
panelClass: ['center', 'right'],
|
|
offsetX: -0.5 * arrowOffset,
|
|
offsetY: -1 * panelOffset
|
|
},
|
|
];
|
|
}
|
|
if (backdrop === true) {
|
|
showbackdrop = true;
|
|
} else {
|
|
showbackdrop = false;
|
|
}
|
|
const positionStrategy = this.overlay
|
|
.position()
|
|
.flexibleConnectedTo(target)
|
|
.withPush(false)
|
|
.withFlexibleDimensions(false)
|
|
.withPositions(positions);
|
|
|
|
const overlayRef = this.overlay.create({
|
|
hasBackdrop: showbackdrop,
|
|
backdropClass: config.backdropClass,
|
|
panelClass: config.panelClass,
|
|
positionStrategy,
|
|
scrollStrategy: this.overlay.scrollStrategies.reposition()
|
|
});
|
|
|
|
const popoverRef = new PopoverRef(overlayRef, positionStrategy, popoverConfig);
|
|
|
|
const popover = overlayRef.attach(new ComponentPortal(
|
|
PopoverComponent,
|
|
null,
|
|
Injector.create({
|
|
parent: this.injector,
|
|
providers: [
|
|
{ provide: PopoverRef, useValue: popoverRef }
|
|
]
|
|
})
|
|
// new PortalInjector(
|
|
// this.injector,
|
|
// new WeakMap<any, any>([
|
|
// [PopoverRef, popoverRef]
|
|
// ])
|
|
// )
|
|
)).instance;
|
|
|
|
if (componentOrTemplate instanceof TemplateRef) {
|
|
// rendering a provided template dynamically
|
|
popover.attachTemplatePortal(
|
|
new TemplatePortal(
|
|
componentOrTemplate,
|
|
null,
|
|
{
|
|
$implicit: config.data,
|
|
popover: popoverRef
|
|
}
|
|
)
|
|
);
|
|
} else {
|
|
// rendering a provided component dynamically
|
|
popover.attachComponentPortal(
|
|
new ComponentPortal(
|
|
componentOrTemplate,
|
|
null,
|
|
Injector.create({
|
|
parent: this.injector,
|
|
providers: [
|
|
{ provide: POPOVER_DATA, useValue: config.data },
|
|
{ provide: PopoverRef, useValue: popoverRef },
|
|
]
|
|
})
|
|
// new PortalInjector(
|
|
// this.injector,
|
|
// new WeakMap<any, any>([
|
|
// [POPOVER_DATA, config.data],
|
|
// [PopoverRef, popoverRef]
|
|
// ])
|
|
// )
|
|
)
|
|
);
|
|
|
|
}
|
|
|
|
return popoverRef;
|
|
}
|
|
}
|