ap-popup
Version:
Flexible Angular popup/notification library
268 lines (263 loc) • 17.2 kB
JavaScript
import * as i0 from '@angular/core';
import { EventEmitter, HostBinding, Output, Input, Component, Inject, Injectable } from '@angular/core';
import { Subject, timer, takeUntil, take } from 'rxjs';
import { NgIf, NgClass, NgTemplateOutlet, DOCUMENT } from '@angular/common';
class ApPopupComponent {
elementRef;
text;
template;
templateContext = {};
theme = 'default';
animation = 'cubic';
animationDuration = 900;
animationHideDuration = 900;
timeout = 3000;
parentSelector = null;
closeByMouseUp = true;
customClass;
customWrapperClass;
zIndex;
remove = new EventEmitter();
hideAnimation;
distanceToPageTop = 0;
id;
cubicBezierFunctionIn = 'cubic-bezier(0, 1.82, 0.46, 0.84)';
cubicBezierFunctionOut = 'cubic-bezier(0.42, -0.65, 0.27, 0.93)';
_close$ = new Subject();
get getHostTransition() {
let timingFunction = 'linear';
if (this.animation === 'cubic') {
timingFunction = this.cubicBezierFunctionIn;
}
return `all ${timingFunction} ${this.animationDuration}ms`;
}
get getZIndex() {
return this.zIndex?.toString() || '1';
}
get getCursor() {
return this.closeByMouseUp ? 'pointer' : 'default';
}
constructor(elementRef) {
this.elementRef = elementRef;
}
ngOnInit() {
if (!this.timeout)
return;
timer(this.timeout)
.pipe(takeUntil(this._close$))
.subscribe(() => {
this._startHideAnimation();
});
}
ngOnDestroy() {
this._close$.complete();
}
onMouseUp() {
if (!this.closeByMouseUp)
return;
this.close();
}
close() {
this.zIndex = 999;
this._close$.next();
this._startHideAnimation();
}
updateDistanceTop() {
this.distanceToPageTop = this._getDistanceFromBottomToPageTop(this.elementRef.nativeElement);
}
_startHideAnimation() {
this.updateDistanceTop();
this.hideAnimation = `${this.animation}-out`;
this.remove.emit();
}
_getDistanceFromBottomToPageTop(element) {
return element.getBoundingClientRect().bottom;
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ApPopupComponent, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: ApPopupComponent, isStandalone: true, selector: "lib-ap-popup", inputs: { text: "text", template: "template", templateContext: "templateContext", theme: "theme", animation: "animation", animationDuration: "animationDuration", animationHideDuration: "animationHideDuration", timeout: "timeout", parentSelector: "parentSelector", closeByMouseUp: "closeByMouseUp", customClass: "customClass", customWrapperClass: "customWrapperClass", zIndex: "zIndex" }, outputs: { remove: "remove" }, host: { properties: { "style.transition": "this.getHostTransition", "style.zIndex": "this.getZIndex", "style.cursor": "this.getCursor" } }, ngImport: i0, template: "<div\r\n class=\"ap-popup\"\r\n [ngClass]=\"[animation, hideAnimation ? hideAnimation : '', customClass ? customClass : '']\"\r\n [style]=\"\r\n '--animationDuration:' +\r\n animationDuration +\r\n 'ms;' +\r\n '--cubicFunctionIn:' +\r\n cubicBezierFunctionIn +\r\n ';' +\r\n '--cubicFunctionOut:' +\r\n cubicBezierFunctionOut +\r\n ';' +\r\n '--distanceToPageTop:' +\r\n distanceToPageTop +\r\n 'px;'\r\n \"\r\n (mouseup)=\"onMouseUp()\"\r\n>\r\n <div *ngIf=\"text || template\" class=\"ap-popup__content\" [ngClass]=\"'ap-popup__content-' + theme\">\r\n <ng-container\r\n *ngIf=\"template\"\r\n [ngTemplateOutlet]=\"template\"\r\n [ngTemplateOutletContext]=\"templateContext\"\r\n ></ng-container>\r\n\r\n <span *ngIf=\"text && !template\" [innerHtml]=\"text\"></span>\r\n </div>\r\n</div>\r\n", styles: [":host{--ap-popup-max-width: 540px;max-width:var(--ap-popup-max-width);display:flex;width:fit-content;position:absolute;top:0;left:0;right:0;margin-inline:auto;justify-content:center;user-select:none;-webkit-user-select:none;-webkit-tap-highlight-color:transparent}.ap-popup{width:auto;max-width:fit-content}.ap-popup__content{display:flex;align-items:center;column-gap:8px;padding:12px 16px;border-radius:16px;background:#fff;box-shadow:0 4px 12px #04040414;width:fit-content;font-size:16px;font-weight:400;line-height:24px}.ap-popup__content-warning,.ap-popup__content-error{background:#fff3d1;box-shadow:0 4px 12px #8f701614}.ap-popup__content-success{background:#e9f1df;box-shadow:0 4px 12px #4a862e14}.ap-popup__content-light{background:#000;box-shadow:0 8px 28px #00000014}.slide{animation:slideInFromRight var(--animationDuration) forwards ease-in}.slide-out{animation:slideOutToRight var(--animationDuration) forwards ease-out}.cubic{animation:slideInFromTop var(--animationDuration) forwards var(--cubicFunctionIn)}.cubic-out{animation:slideOutToTop var(--animationDuration) forwards var(--cubicFunctionOut)}.fade{animation:fadeIn var(--animationDuration) forwards linear}.fade-out{animation:fadeOut var(--animationDuration) forwards linear}@keyframes slideInFromTop{0%{transform:translateY(calc(-200% - var(--distanceToPageTop)))}to{transform:translateY(0)}}@keyframes slideOutToTop{0%{transform:translateY(0)}to{transform:translateY(calc(-200% - var(--distanceToPageTop)))}}@keyframes slideInFromRight{0%{transform:translate(110%)}to{transform:translate(0)}}@keyframes slideOutToRight{0%{transform:translate(0)}to{transform:translate(110%)}}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}@keyframes fadeOut{0%{opacity:1}to{opacity:0}}\n"], dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ApPopupComponent, decorators: [{
type: Component,
args: [{ selector: 'lib-ap-popup', imports: [NgIf, NgClass, NgTemplateOutlet], template: "<div\r\n class=\"ap-popup\"\r\n [ngClass]=\"[animation, hideAnimation ? hideAnimation : '', customClass ? customClass : '']\"\r\n [style]=\"\r\n '--animationDuration:' +\r\n animationDuration +\r\n 'ms;' +\r\n '--cubicFunctionIn:' +\r\n cubicBezierFunctionIn +\r\n ';' +\r\n '--cubicFunctionOut:' +\r\n cubicBezierFunctionOut +\r\n ';' +\r\n '--distanceToPageTop:' +\r\n distanceToPageTop +\r\n 'px;'\r\n \"\r\n (mouseup)=\"onMouseUp()\"\r\n>\r\n <div *ngIf=\"text || template\" class=\"ap-popup__content\" [ngClass]=\"'ap-popup__content-' + theme\">\r\n <ng-container\r\n *ngIf=\"template\"\r\n [ngTemplateOutlet]=\"template\"\r\n [ngTemplateOutletContext]=\"templateContext\"\r\n ></ng-container>\r\n\r\n <span *ngIf=\"text && !template\" [innerHtml]=\"text\"></span>\r\n </div>\r\n</div>\r\n", styles: [":host{--ap-popup-max-width: 540px;max-width:var(--ap-popup-max-width);display:flex;width:fit-content;position:absolute;top:0;left:0;right:0;margin-inline:auto;justify-content:center;user-select:none;-webkit-user-select:none;-webkit-tap-highlight-color:transparent}.ap-popup{width:auto;max-width:fit-content}.ap-popup__content{display:flex;align-items:center;column-gap:8px;padding:12px 16px;border-radius:16px;background:#fff;box-shadow:0 4px 12px #04040414;width:fit-content;font-size:16px;font-weight:400;line-height:24px}.ap-popup__content-warning,.ap-popup__content-error{background:#fff3d1;box-shadow:0 4px 12px #8f701614}.ap-popup__content-success{background:#e9f1df;box-shadow:0 4px 12px #4a862e14}.ap-popup__content-light{background:#000;box-shadow:0 8px 28px #00000014}.slide{animation:slideInFromRight var(--animationDuration) forwards ease-in}.slide-out{animation:slideOutToRight var(--animationDuration) forwards ease-out}.cubic{animation:slideInFromTop var(--animationDuration) forwards var(--cubicFunctionIn)}.cubic-out{animation:slideOutToTop var(--animationDuration) forwards var(--cubicFunctionOut)}.fade{animation:fadeIn var(--animationDuration) forwards linear}.fade-out{animation:fadeOut var(--animationDuration) forwards linear}@keyframes slideInFromTop{0%{transform:translateY(calc(-200% - var(--distanceToPageTop)))}to{transform:translateY(0)}}@keyframes slideOutToTop{0%{transform:translateY(0)}to{transform:translateY(calc(-200% - var(--distanceToPageTop)))}}@keyframes slideInFromRight{0%{transform:translate(110%)}to{transform:translate(0)}}@keyframes slideOutToRight{0%{transform:translate(0)}to{transform:translate(110%)}}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}@keyframes fadeOut{0%{opacity:1}to{opacity:0}}\n"] }]
}], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { text: [{
type: Input
}], template: [{
type: Input
}], templateContext: [{
type: Input
}], theme: [{
type: Input
}], animation: [{
type: Input
}], animationDuration: [{
type: Input
}], animationHideDuration: [{
type: Input
}], timeout: [{
type: Input
}], parentSelector: [{
type: Input
}], closeByMouseUp: [{
type: Input
}], customClass: [{
type: Input
}], customWrapperClass: [{
type: Input
}], zIndex: [{
type: Input
}], remove: [{
type: Output
}], getHostTransition: [{
type: HostBinding,
args: ['style.transition']
}], getZIndex: [{
type: HostBinding,
args: ['style.zIndex']
}], getCursor: [{
type: HostBinding,
args: ['style.cursor']
}] } });
class ApPopupService {
_componentFactoryResolver;
_appRef;
_injector;
_document;
_zIndexCounter = 1000;
_wrapper = null;
_renderer;
_popups = [];
_removingPopupIds = new Set();
_customWrapperClass;
constructor(rendererFactory, _componentFactoryResolver, _appRef, _injector, _document) {
this._componentFactoryResolver = _componentFactoryResolver;
this._appRef = _appRef;
this._injector = _injector;
this._document = _document;
this._renderer = rendererFactory.createRenderer(null, null);
}
openPopup(data) {
if (!this._popups.length) {
if (this._wrapper) {
this._renderer.removeChild(this._document.body, this._wrapper);
}
this._wrapper = null;
}
if (!this._wrapper) {
this._wrapper = this._renderer.createElement('div');
this._renderer.addClass(this._wrapper, 'popups-wrapper');
this._customWrapperClass = data.customWrapperClass;
if (this._customWrapperClass) {
this._renderer.addClass(this._wrapper, this._customWrapperClass);
}
let parent = this._document.body;
if (data.parentSelector) {
const parentRef = this._document.querySelector(data.parentSelector);
if (parentRef) {
parent = parentRef;
}
}
this._renderer.appendChild(parent, this._wrapper);
}
const componentFactory = this._componentFactoryResolver.resolveComponentFactory(ApPopupComponent);
const componentRef = componentFactory.create(this._injector);
componentRef.instance.text = data.text;
componentRef.instance.template = data.template;
componentRef.instance.customClass = data.customClass;
componentRef.instance.customWrapperClass = data.customWrapperClass;
componentRef.instance.zIndex = ++this._zIndexCounter;
componentRef.instance.id = this._zIndexCounter;
componentRef.instance.theme = data.theme || componentRef.instance.theme;
componentRef.instance.templateContext =
data.templateContext || componentRef.instance.templateContext;
componentRef.instance.animation =
data.animation || componentRef.instance.animation;
componentRef.instance.timeout =
data.timeout === null
? null
: data.timeout || componentRef.instance.timeout;
componentRef.instance.parentSelector =
data.parentSelector || componentRef.instance.parentSelector;
componentRef.instance.animationDuration =
data.animationDuration || componentRef.instance.animationDuration;
componentRef.instance.closeByMouseUp =
data.closeByMouseUp === undefined
? componentRef.instance.closeByMouseUp
: data.closeByMouseUp;
componentRef.instance.remove.pipe(take(1)).subscribe(() => {
this.removePopup(componentRef.instance);
});
this._appRef.attachView(componentRef.hostView);
const domElem = componentRef.hostView.rootNodes[0];
this._renderer.appendChild(this._wrapper, domElem);
const animatedPopups = this._popups.filter((p) => !p.hideAnimation);
animatedPopups.forEach((popup, index) => {
const previousPopupsHeight = animatedPopups
.filter((_, i) => i < index)
.reduce((acc, p) => {
return acc + p.elementRef.nativeElement.clientHeight;
}, 0);
setTimeout(() => {
this._renderer.setStyle(popup.elementRef.nativeElement, 'transform', `translateY(calc(${previousPopupsHeight +
componentRef.instance.elementRef.nativeElement.clientHeight}px + ${12 * (index + 1)}px))`);
}, 0);
popup.updateDistanceTop();
});
this._popups.unshift(componentRef.instance);
return componentRef.instance;
}
removePopup(popup) {
const index = this._popups.findIndex((p) => p.id === popup.id);
if (index < 0)
return;
this._removingPopupIds.add(popup.id);
timer(popup.animationDuration / 4)
.pipe(take(1))
.subscribe(() => {
const newList = this._popups.filter((p) => !this._removingPopupIds.has(p.id));
for (let i = index; i < newList.length; i++) {
const currentPopup = newList[i];
const previousPopupsHeight = newList
.filter((_, pi) => pi < i)
.reduce((acc, p) => {
return acc + p.elementRef.nativeElement.clientHeight;
}, 0);
this._renderer.setStyle(currentPopup.elementRef.nativeElement, 'transform', `translateY(calc(${previousPopupsHeight}px + ${12 * i}px))`);
}
this._popups = this._popups.filter((p) => p.id !== popup.id);
});
timer(popup.animationDuration)
.pipe(take(1))
.subscribe(() => {
this._renderer.removeChild(parent, popup.elementRef.nativeElement);
this._removingPopupIds.delete(popup.id);
if (!this._popups.length &&
this._wrapper &&
!this._removingPopupIds.size) {
this._renderer.removeChild(this._document.body, this._wrapper);
this._wrapper = null;
}
});
}
clearAll() {
if (this._wrapper) {
this._renderer.removeChild(this._document.body, this._wrapper);
}
this._popups.length = 0;
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ApPopupService, deps: [{ token: i0.RendererFactory2 }, { token: i0.ComponentFactoryResolver }, { token: i0.ApplicationRef }, { token: i0.Injector }, { token: DOCUMENT }], target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ApPopupService, providedIn: 'root' });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ApPopupService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root',
}]
}], ctorParameters: () => [{ type: i0.RendererFactory2 }, { type: i0.ComponentFactoryResolver }, { type: i0.ApplicationRef }, { type: i0.Injector }, { type: Document, decorators: [{
type: Inject,
args: [DOCUMENT]
}] }] });
/**
* Generated bundle index. Do not edit.
*/
export { ApPopupComponent, ApPopupService };
//# sourceMappingURL=ap-popup.mjs.map