flexi-toast
Version:
Lightweight, customizable Angular toast notification component with title, message, icon types, auto-dismiss, manual close, animations, theme and positioning support.
336 lines (321 loc) • 19.2 kB
JavaScript
import * as i0 from '@angular/core';
import { input, linkedSignal, output, HostBinding, ChangeDetectionStrategy, ViewEncapsulation, Component, inject, ApplicationRef, EnvironmentInjector, createComponent, Injectable } from '@angular/core';
class FlexiSwalComponent {
title = input('', /* @ts-ignore */
...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
question = input('', /* @ts-ignore */
...(ngDevMode ? [{ debugName: "question" }] : /* istanbul ignore next */ []));
confirmBtnText = input('Confirm', /* @ts-ignore */
...(ngDevMode ? [{ debugName: "confirmBtnText" }] : /* istanbul ignore next */ []));
cancelBtnText = input('Cancel', /* @ts-ignore */
...(ngDevMode ? [{ debugName: "cancelBtnText" }] : /* istanbul ignore next */ []));
showCloseBtn = input(false, /* @ts-ignore */
...(ngDevMode ? [{ debugName: "showCloseBtn" }] : /* istanbul ignore next */ []));
themeClass = input('light', /* @ts-ignore */
...(ngDevMode ? [{ debugName: "themeClass" }] : /* istanbul ignore next */ []));
contentThemeClass = input('default', /* @ts-ignore */
...(ngDevMode ? [{ debugName: "contentThemeClass" }] : /* istanbul ignore next */ []));
isVisible = input(false, /* @ts-ignore */
...(ngDevMode ? [{ debugName: "isVisible" }] : /* istanbul ignore next */ []));
isVisibleSignal = linkedSignal(() => this.isVisible(), /* @ts-ignore */
...(ngDevMode ? [{ debugName: "isVisibleSignal" }] : /* istanbul ignore next */ []));
confirmed = output();
cancelled = output();
closed = output();
get visible() {
return this.isVisibleSignal();
}
get display() {
return this.isVisibleSignal() ? 'block' : 'none';
}
onConfirm() {
this.confirmed.emit();
this.close();
}
onCancel() {
this.cancelled.emit();
this.close();
}
close() {
this.isVisibleSignal.set(false);
this.closed.emit();
}
show() {
this.isVisibleSignal.set(true);
// Prevent body scroll
document.body.style.overflow = 'hidden';
}
hide() {
this.isVisibleSignal.set(false);
// Restore body scroll
document.body.style.overflow = '';
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: FlexiSwalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.0", type: FlexiSwalComponent, isStandalone: true, selector: "flexi-swal", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, question: { classPropertyName: "question", publicName: "question", isSignal: true, isRequired: false, transformFunction: null }, confirmBtnText: { classPropertyName: "confirmBtnText", publicName: "confirmBtnText", isSignal: true, isRequired: false, transformFunction: null }, cancelBtnText: { classPropertyName: "cancelBtnText", publicName: "cancelBtnText", isSignal: true, isRequired: false, transformFunction: null }, showCloseBtn: { classPropertyName: "showCloseBtn", publicName: "showCloseBtn", isSignal: true, isRequired: false, transformFunction: null }, themeClass: { classPropertyName: "themeClass", publicName: "themeClass", isSignal: true, isRequired: false, transformFunction: null }, contentThemeClass: { classPropertyName: "contentThemeClass", publicName: "contentThemeClass", isSignal: true, isRequired: false, transformFunction: null }, isVisible: { classPropertyName: "isVisible", publicName: "isVisible", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { confirmed: "confirmed", cancelled: "cancelled", closed: "closed" }, host: { properties: { "class.visible": "this.visible", "style.display": "this.display" } }, ngImport: i0, template: `
{
<div class="flexi-swal-container" [attr.data-bs-theme]="themeClass()">
<div class="flexi-swal-content"
[attr.data-bs-theme]="themeClass()">
<!-- Close button (top right) -->
(showCloseBtn()) {
<span class="flexi-swal-close-btn"
(click)="onCancel()">×</span>
}
<!-- Title container -->
<div class="flexi-swal-title-container">
<span>{{ title() }}</span>
<button class="flexi-swal-close-button" (click)="onCancel()">×</button>
</div>
<!-- Question container -->
<div class="flexi-swal-question-container">
<span [innerHtml]="question()"></span>
</div>
<!-- Button container -->
<div class="flexi-swal-button-container">
<button class="flexi-swal-button flexi-swal-button-primary"
(click)="onConfirm()">
{{ confirmBtnText() }}
</button>
<button class="flexi-swal-button"
(click)="onCancel()">
{{ cancelBtnText() }}
</button>
</div>
</div>
</div>
}
`, isInline: true, styles: [":host{position:fixed;top:0;left:0;width:100%;height:100%;z-index:9999;pointer-events:none}:host(.visible){pointer-events:all}.flexi-swal-container{display:flex;position:fixed;top:0;left:0;width:100%;height:100%;background:#00000080;justify-content:center;align-items:center;z-index:9999}.flexi-swal-content{animation:fadeIn .3s ease-in-out}.fade-out{animation:fadeOut .2s ease-in-out forwards}@keyframes fadeIn{0%{opacity:0;transform:scale(.9)}to{opacity:1;transform:scale(1)}}@keyframes fadeOut{0%{opacity:1;transform:scale(1)}to{opacity:0;transform:scale(.9)}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: FlexiSwalComponent, decorators: [{
type: Component,
args: [{ selector: 'flexi-swal', imports: [], encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, template: `
{
<div class="flexi-swal-container" [attr.data-bs-theme]="themeClass()">
<div class="flexi-swal-content"
[attr.data-bs-theme]="themeClass()">
<!-- Close button (top right) -->
(showCloseBtn()) {
<span class="flexi-swal-close-btn"
(click)="onCancel()">×</span>
}
<!-- Title container -->
<div class="flexi-swal-title-container">
<span>{{ title() }}</span>
<button class="flexi-swal-close-button" (click)="onCancel()">×</button>
</div>
<!-- Question container -->
<div class="flexi-swal-question-container">
<span [innerHtml]="question()"></span>
</div>
<!-- Button container -->
<div class="flexi-swal-button-container">
<button class="flexi-swal-button flexi-swal-button-primary"
(click)="onConfirm()">
{{ confirmBtnText() }}
</button>
<button class="flexi-swal-button"
(click)="onCancel()">
{{ cancelBtnText() }}
</button>
</div>
</div>
</div>
}
`, styles: [":host{position:fixed;top:0;left:0;width:100%;height:100%;z-index:9999;pointer-events:none}:host(.visible){pointer-events:all}.flexi-swal-container{display:flex;position:fixed;top:0;left:0;width:100%;height:100%;background:#00000080;justify-content:center;align-items:center;z-index:9999}.flexi-swal-content{animation:fadeIn .3s ease-in-out}.fade-out{animation:fadeOut .2s ease-in-out forwards}@keyframes fadeIn{0%{opacity:0;transform:scale(.9)}to{opacity:1;transform:scale(1)}}@keyframes fadeOut{0%{opacity:1;transform:scale(1)}to{opacity:0;transform:scale(.9)}}\n"] }]
}], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], question: [{ type: i0.Input, args: [{ isSignal: true, alias: "question", required: false }] }], confirmBtnText: [{ type: i0.Input, args: [{ isSignal: true, alias: "confirmBtnText", required: false }] }], cancelBtnText: [{ type: i0.Input, args: [{ isSignal: true, alias: "cancelBtnText", required: false }] }], showCloseBtn: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCloseBtn", required: false }] }], themeClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "themeClass", required: false }] }], contentThemeClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "contentThemeClass", required: false }] }], isVisible: [{ type: i0.Input, args: [{ isSignal: true, alias: "isVisible", required: false }] }], confirmed: [{ type: i0.Output, args: ["confirmed"] }], cancelled: [{ type: i0.Output, args: ["cancelled"] }], closed: [{ type: i0.Output, args: ["closed"] }], visible: [{
type: HostBinding,
args: ['class.visible']
}], display: [{
type: HostBinding,
args: ['style.display']
}] } });
class FlexiToastService {
options = new FlexiToastOptionsModel();
renderer;
appRef = inject(ApplicationRef);
injector = inject(EnvironmentInjector);
constructor(rendererFactory) {
this.renderer = rendererFactory.createRenderer(null, null);
}
showToast(title, text, icon = "success", options) {
if (!options) {
options = this.options;
}
options.icon = icon;
let wrapper = document.querySelector(".flexi-toast-wrapper");
if (!wrapper) {
wrapper = this.renderer.createElement('div');
this.renderer.addClass(wrapper, `flexi-toast-wrapper`);
this.renderer.addClass(wrapper, `flexi-taost-${options.position}`);
const body = this.renderer.selectRootElement('body', true);
this.renderer.appendChild(body, wrapper);
}
else {
wrapper.className = `flexi-toast-wrapper flexi-taost-${options.position}`;
if (options.preventDuplicate) {
const allEl = document.querySelectorAll(".flexi-toast");
if (allEl) {
for (const el of allEl) {
this.renderer.removeChild(wrapper, el);
}
}
}
}
const toast = this.renderer.createElement('div');
this.renderer.addClass(toast, 'flexi-toast');
this.renderer.addClass(toast, `flexi-toast-${options.icon}`);
this.renderer.setAttribute(toast, `data-bs-theme`, options.themeClass);
if (!options.showToastCloseBtn) {
this.renderer.listen(toast, 'click', () => {
this.renderer.removeClass(toast, 'flexi-toast-fade-in');
this.renderer.addClass(toast, 'flexi-toast-fade-out');
setTimeout(() => {
this.renderer.removeChild(wrapper, toast);
}, 500);
});
}
const container = this.renderer.createElement('div');
this.renderer.addClass(container, 'flexi-toast-container');
const container1 = this.renderer.createElement('div');
this.renderer.addClass(container1, 'flexi-toast-container-1');
// SVG ikonu eklemek
const svgIcon = this.renderer.createElement('svg', 'http://www.w3.org/2000/svg');
this.renderer.setAttribute(svgIcon, 'xmlns', 'http://www.w3.org/2000/svg');
this.renderer.setAttribute(svgIcon, 'viewBox', '0 0 512 512');
const path = this.renderer.createElement('path', 'http://www.w3.org/2000/svg');
if (options.icon === "success") {
this.renderer.setAttribute(path, 'd', 'M504 256c0 137-111 248-248 248S8 393 8 256 119 8 256 8s248 111 248 248zM227.3 387.3l184-184c6.2-6.2 6.2-16.4 0-22.6l-22.6-22.6c-6.2-6.2-16.4-6.2-22.6 0L216 308.1l-70.1-70.1c-6.2-6.2-16.4-6.2-22.6 0l-22.6 22.6c-6.2 6.2-6.2 16.4 0 22.6l104 104c6.2 6.2 16.4 6.2 22.6 0z');
}
else if (options.icon === "warning") {
this.renderer.setAttribute(path, 'd', 'M504 256c0 137-111 248-248 248S8 393 8 256C8 119.1 119 8 256 8s248 111.1 248 248zm-248 50c-25.4 0-46 20.6-46 46s20.6 46 46 46 46-20.6 46-46-20.6-46-46-46zm-43.7-165.3l7.4 136c.3 6.4 5.6 11.3 12 11.3h48.5c6.4 0 11.6-5 12-11.3l7.4-136c.4-6.9-5.1-12.7-12-12.7h-63.4c-6.9 0-12.4 5.8-12 12.7z');
}
else if (options.icon === "info") {
this.renderer.setAttribute(path, 'd', 'M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336h24V272H216c-13.3 0-24-10.7-24-24s10.7-24 24-24h48c13.3 0 24 10.7 24 24v88h8c13.3 0 24 10.7 24 24s-10.7 24-24 24H216c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z');
}
else if (options.icon === "error") {
this.renderer.setAttribute(path, 'd', 'M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm121.6 313.1c4.7 4.7 4.7 12.3 0 17L338 377.6c-4.7 4.7-12.3 4.7-17 0L256 312l-65.1 65.6c-4.7 4.7-12.3 4.7-17 0L134.4 338c-4.7-4.7-4.7-12.3 0-17l65.6-65-65.6-65.1c-4.7-4.7-4.7-12.3 0-17l39.6-39.6c4.7-4.7 12.3-4.7 17 0l65 65.7 65.1-65.6c4.7-4.7 12.3-4.7 17 0l39.6 39.6c4.7 4.7 4.7 12.3 0 17L312 256l65.6 65.1z');
}
this.renderer.appendChild(svgIcon, path);
this.renderer.appendChild(container1, svgIcon);
const container2 = this.renderer.createElement('div');
this.renderer.addClass(container2, 'flexi-toast-container-2');
const titleEl = this.renderer.createElement('span');
const titleText = this.renderer.createText(title);
this.renderer.appendChild(titleEl, titleText);
const message = this.renderer.createElement('span');
message.innerHTML = text;
this.renderer.appendChild(container2, titleEl);
this.renderer.appendChild(container2, message);
this.renderer.appendChild(container, container1);
this.renderer.appendChild(container, container2);
if (options.autoClose && options.showProgressBar) {
const progressBar = this.renderer.createElement('div');
this.renderer.addClass(progressBar, 'flexi-toast-progress-bar');
this.renderer.addClass(progressBar, `flex-toast-progress-bar-${options.icon}`);
this.renderer.appendChild(toast, progressBar);
// Add animation to progress bar
this.renderer.setStyle(progressBar, 'transition', `width ${options.timeOut}ms linear`);
this.renderer.setStyle(progressBar, 'width', '100%');
setTimeout(() => {
this.renderer.setStyle(progressBar, 'width', '0%');
}, 10);
}
this.renderer.appendChild(toast, container);
if (options.showToastCloseBtn) {
const closeBtn = this.renderer.createElement('span');
this.renderer.addClass(closeBtn, 'flexi-toast-close-btn');
const closeText = this.renderer.createText('×');
this.renderer.appendChild(closeBtn, closeText);
this.renderer.listen(closeBtn, 'click', () => {
this.renderer.removeClass(toast, 'flexi-toast-fade-in');
this.renderer.addClass(toast, 'flexi-toast-fade-out');
setTimeout(() => {
this.renderer.removeChild(wrapper, toast);
}, 500);
});
this.renderer.appendChild(toast, closeBtn);
}
this.renderer.appendChild(wrapper, toast);
// Timeout to trigger the fade-in animation
setTimeout(() => {
this.renderer.addClass(toast, 'flexi-toast-fade-in');
}, 10);
if (options.autoClose) {
setTimeout(() => {
this.renderer.removeClass(toast, 'flexi-toast-fade-in');
this.renderer.addClass(toast, 'flexi-toast-fade-out');
setTimeout(() => {
this.renderer.removeChild(wrapper, toast);
}, 500);
}, options.timeOut);
}
}
showSwal(title, question, confirmBtnText = this.options.confirmBtnText ?? "Delete", callBack, cancelBtnText = this.options.cancelBtnText ?? "Cancel", cancelCallBack) {
// Create component
const componentRef = createComponent(FlexiSwalComponent, {
environmentInjector: this.injector
});
// Set inputs
componentRef.setInput('title', title);
componentRef.setInput('question', question);
componentRef.setInput('confirmBtnText', confirmBtnText);
componentRef.setInput('cancelBtnText', cancelBtnText);
componentRef.setInput('showCloseBtn', this.options.showSwalCloseBtn);
componentRef.setInput('themeClass', this.options.themeClass);
componentRef.setInput('contentThemeClass', this.options.swalContentThemeClass);
componentRef.setInput('isVisible', true);
// Subscribe to outputs
componentRef.instance.confirmed.subscribe(() => {
callBack();
this.destroySwal(componentRef);
});
componentRef.instance.cancelled.subscribe(() => {
if (cancelCallBack) {
cancelCallBack();
}
this.destroySwal(componentRef);
});
componentRef.instance.closed.subscribe(() => {
this.destroySwal(componentRef);
});
// Append to body
document.body.appendChild(componentRef.location.nativeElement);
// Attach to Angular's change detection
this.appRef.attachView(componentRef.hostView);
componentRef.changeDetectorRef.detectChanges();
return componentRef;
}
destroySwal(componentRef) {
setTimeout(() => {
this.appRef.detachView(componentRef.hostView);
componentRef.destroy();
}, 200);
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: FlexiToastService, deps: [{ token: i0.RendererFactory2 }], target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: FlexiToastService, providedIn: 'root' });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: FlexiToastService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root'
}]
}], ctorParameters: () => [{ type: i0.RendererFactory2 }] });
class FlexiToastOptionsModel {
icon = "success";
autoClose = true;
timeOut = 3000;
showProgressBar = true;
showToastCloseBtn = true;
showSwalCloseBtn = false;
preventDuplicate = false;
position = "bottom-right";
themeClass = "light";
cancelBtnText = "Vazgeç";
confirmBtnText = "Sil";
swalContentThemeClass = "default";
}
/**
* Generated bundle index. Do not edit.
*/
export { FlexiToastOptionsModel, FlexiToastService };
//# sourceMappingURL=flexi-toast.mjs.map