UNPKG

nzrm-ng

Version:

<p align="center"> <img src="docs/angular.gif" alt="Angular" width="100" height="100"/> </p>

718 lines (709 loc) 178 kB
import * as i0 from '@angular/core'; import { HostListener, Input, Directive, NgModule, Component, Injectable, Inject, ViewEncapsulation, EventEmitter, Output, PLATFORM_ID, inject, forwardRef, ContentChild, ViewChild } from '@angular/core'; import * as i2 from '@angular/common'; import { CommonModule, DOCUMENT, isPlatformBrowser } from '@angular/common'; import { trigger, state, transition, style, animate } from '@angular/animations'; import { BehaviorSubject, Subject, Subscription, firstValueFrom, takeUntil } from 'rxjs'; import { NG_VALUE_ACCESSOR } from '@angular/forms'; import { v4 } from 'uuid'; class TooltipDirective { el; renderer; tooltipText = ''; showOnlyIf = true; sticky = false; severity = 'normal'; position = 'top'; showDelay = 100; hideDelay = 100; maxWidth = '250px'; tooltipElement = null; isVisible = false; showTimeoutId = null; hideTimeoutId = null; constructor(el, renderer) { this.el = el; this.renderer = renderer; } ngOnInit() { this.initializeTooltip(); if (this.sticky && this.showOnlyIf) { setTimeout(() => this.showTooltip(), 0); } } initializeTooltip() { if (this.tooltipElement) { this.renderer.removeChild(document.body, this.tooltipElement); } this.tooltipElement = this.renderer.createElement('div'); this.renderer.addClass(this.tooltipElement, 'n-tooltip'); this.renderer.addClass(this.tooltipElement, `n-tooltip-${this.position}`); if (this.severity !== 'normal') { this.renderer.addClass(this.tooltipElement, `n-tooltip-${this.severity}`); } const contentWrapper = this.renderer.createElement('div'); this.renderer.addClass(contentWrapper, 'n-tooltip-content'); if (this.severity !== 'normal') { const iconElement = this.renderer.createElement('span'); this.renderer.addClass(iconElement, 'n-tooltip-icon'); this.renderer.addClass(iconElement, 'pi'); switch (this.severity) { case 'info': this.renderer.addClass(iconElement, 'pi-info-circle'); break; case 'success': this.renderer.addClass(iconElement, 'pi-check-circle'); break; case 'warn': this.renderer.addClass(iconElement, 'pi-exclamation-triangle'); break; case 'error': this.renderer.addClass(iconElement, 'pi-times-circle'); break; } this.renderer.appendChild(contentWrapper, iconElement); } const textElement = this.renderer.createElement('span'); this.renderer.addClass(textElement, 'n-tooltip-text'); const textNode = this.renderer.createText(this.tooltipText); this.renderer.appendChild(textElement, textNode); this.renderer.appendChild(contentWrapper, textElement); this.renderer.appendChild(this.tooltipElement, contentWrapper); this.renderer.setStyle(this.tooltipElement, 'max-width', this.maxWidth); this.renderer.setStyle(this.tooltipElement, 'position', 'absolute'); this.renderer.setStyle(this.tooltipElement, 'z-index', '9999'); this.renderer.setStyle(this.tooltipElement, 'display', 'none'); this.renderer.setStyle(this.tooltipElement, 'opacity', '0'); this.renderer.appendChild(document.body, this.tooltipElement); } onMouseEnter() { if (!this.showOnlyIf) return; this.clearTimeouts(); this.showTimeoutId = setTimeout(() => { this.showTooltip(); }, this.showDelay); } onFocus() { if (!this.showOnlyIf) return; this.clearTimeouts(); this.showTimeoutId = setTimeout(() => { this.showTooltip(); }, this.showDelay); } onMouseLeave() { if (this.tooltipElement && !this.sticky) { this.clearTimeouts(); this.hideTimeoutId = setTimeout(() => { this.hideTooltip(); }, this.hideDelay); } } onBlur() { if (this.tooltipElement && !this.sticky) { this.clearTimeouts(); this.hideTimeoutId = setTimeout(() => { this.hideTooltip(); }, this.hideDelay); } } onClick() { if (this.sticky && this.showOnlyIf) { if (this.isVisible) { this.hideTooltip(); } else { this.showTooltip(); } } } onScroll() { if (this.tooltipElement && this.showOnlyIf && this.isVisible) { this.updateTooltipPosition(); } } onResize() { if (this.tooltipElement && this.showOnlyIf && this.isVisible) { this.updateTooltipPosition(); } } clearTimeouts() { if (this.showTimeoutId) { clearTimeout(this.showTimeoutId); this.showTimeoutId = null; } if (this.hideTimeoutId) { clearTimeout(this.hideTimeoutId); this.hideTimeoutId = null; } } showTooltip() { if (!this.tooltipElement) return; this.renderer.setStyle(this.tooltipElement, 'display', 'block'); this.updateTooltipPosition(); void this.tooltipElement.offsetHeight; this.renderer.setStyle(this.tooltipElement, 'opacity', '1'); this.isVisible = true; } hideTooltip() { if (!this.tooltipElement) return; this.renderer.setStyle(this.tooltipElement, 'opacity', '0'); // Wait for the transition to complete setTimeout(() => { if (this.tooltipElement && !this.isVisible) { this.renderer.setStyle(this.tooltipElement, 'display', 'none'); } }, 300); this.isVisible = false; } updateTooltipPosition() { if (!this.tooltipElement) return; const hostRect = this.el.nativeElement.getBoundingClientRect(); let tooltipRect = this.tooltipElement.getBoundingClientRect(); if (tooltipRect.width === 0 || tooltipRect.height === 0) { const currentDisplay = this.tooltipElement.style.display; const currentOpacity = this.tooltipElement.style.opacity; this.renderer.setStyle(this.tooltipElement, 'display', 'block'); this.renderer.setStyle(this.tooltipElement, 'opacity', '0'); tooltipRect = this.tooltipElement.getBoundingClientRect(); if (!this.isVisible) { this.renderer.setStyle(this.tooltipElement, 'display', currentDisplay); this.renderer.setStyle(this.tooltipElement, 'opacity', currentOpacity); } } let top = 0, left = 0; switch (this.position) { case 'top': top = hostRect.top - tooltipRect.height - 8; left = hostRect.left + (hostRect.width / 2) - (tooltipRect.width / 2); break; case 'right': top = hostRect.top + (hostRect.height / 2) - (tooltipRect.height / 2); left = hostRect.right + 8; break; case 'bottom': top = hostRect.bottom + 8; left = hostRect.left + (hostRect.width / 2) - (tooltipRect.width / 2); break; case 'left': top = hostRect.top + (hostRect.height / 2) - (tooltipRect.height / 2); left = hostRect.left - tooltipRect.width - 8; break; } this.renderer.setStyle(this.tooltipElement, 'top', `${top + window.pageYOffset}px`); this.renderer.setStyle(this.tooltipElement, 'left', `${left + window.pageXOffset}px`); } ngOnDestroy() { this.clearTimeouts(); if (this.tooltipElement) { this.renderer.removeChild(document.body, this.tooltipElement); this.tooltipElement = null; } } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: TooltipDirective, deps: [{ token: i0.ElementRef }, { token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.1.3", type: TooltipDirective, isStandalone: true, selector: "[nTooltip]", inputs: { tooltipText: "tooltipText", showOnlyIf: "showOnlyIf", sticky: "sticky", severity: "severity", position: "position", showDelay: "showDelay", hideDelay: "hideDelay", maxWidth: "maxWidth" }, host: { listeners: { "mouseenter": "onMouseEnter()", "focus": "onFocus()", "mouseleave": "onMouseLeave()", "blur": "onBlur()", "click": "onClick()", "window:scroll": "onScroll()", "window:resize": "onResize()" } }, ngImport: i0 }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: TooltipDirective, decorators: [{ type: Directive, args: [{ selector: '[nTooltip]', standalone: true, }] }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.Renderer2 }], propDecorators: { tooltipText: [{ type: Input }], showOnlyIf: [{ type: Input }], sticky: [{ type: Input }], severity: [{ type: Input }], position: [{ type: Input }], showDelay: [{ type: Input }], hideDelay: [{ type: Input }], maxWidth: [{ type: Input }], onMouseEnter: [{ type: HostListener, args: ['mouseenter'] }], onFocus: [{ type: HostListener, args: ['focus'] }], onMouseLeave: [{ type: HostListener, args: ['mouseleave'] }], onBlur: [{ type: HostListener, args: ['blur'] }], onClick: [{ type: HostListener, args: ['click'] }], onScroll: [{ type: HostListener, args: ['window:scroll'] }], onResize: [{ type: HostListener, args: ['window:resize'] }] } }); class TooltipModule { static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: TooltipModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.1.3", ngImport: i0, type: TooltipModule, imports: [CommonModule, TooltipDirective], exports: [TooltipDirective] }); static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: TooltipModule, imports: [CommonModule] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: TooltipModule, decorators: [{ type: NgModule, args: [{ declarations: [], imports: [ CommonModule, TooltipDirective ], exports: [ TooltipDirective ] }] }] }); class NotificationComponent { message = null; visible = false; animationState = 'void'; progressWidth = 100; showProgressBar = true; timeout; progressInterval; ngOnInit() { if (this.message) { this.show(); } } show() { this.visible = true; this.animationState = 'visible'; this.startProgressBar(); } hide() { this.animationState = 'hidden'; this.clearTimers(); setTimeout(() => { this.visible = false; }, 200); } getIconClass() { switch (this.message?.severity) { case 'success': return 'pi pi-check-circle'; case 'info': return 'pi pi-info-circle'; case 'warn': return 'pi pi-exclamation-triangle'; case 'error': return 'pi pi-times-circle'; default: return 'pi pi-info-circle'; } } startProgressBar() { this.progressWidth = 100; const duration = this.message?.timeout || 5000; clearInterval(this.progressInterval); this.progressInterval = setInterval(() => { this.progressWidth -= 0.5; if (this.progressWidth <= 0) { this.hide(); } }, duration / 200); this.timeout = setTimeout(() => { this.hide(); }, duration); } clearTimers() { clearTimeout(this.timeout); clearInterval(this.progressInterval); } ngOnDestroy() { this.clearTimers(); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: NotificationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.3", type: NotificationComponent, isStandalone: true, selector: "app-notification", inputs: { message: "message" }, ngImport: i0, template: "<div *ngIf=\"visible\" class=\"notification-container\" [ngClass]=\"'notification-' + message?.severity\"\n [@notificationAnimation]=\"animationState\">\n <div class=\"notification-icon\">\n <i class=\"notification-icon-symbol\" [ngClass]=\"getIconClass()\"></i>\n </div>\n <div class=\"notification-content\">\n <div class=\"notification-summary\">{{ message?.summary }}</div>\n <div class=\"notification-detail\">{{ message?.detail }}</div>\n </div>\n <button class=\"notification-close\" (click)=\"hide()\">\u00D7</button>\n <div *ngIf=\"showProgressBar\" class=\"notification-progress\" [style.width.%]=\"progressWidth\"></div>\n</div>", styles: ["@import\"https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap\";body{background-color:var(--bg-primary);color:var(--text-primary);font-family:Segoe UI,Roboto,Helvetica Neue,sans-serif;margin:0;padding:0;min-height:100vh;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;scrollbar-color:var(--bg-tertiary) var(--bg-secondary);scrollbar-width:thin}:root{--bg-primary: #1e1e2e;--bg-secondary: #282838;--bg-tertiary: #313142;--text-primary: #e4e4e7;--text-secondary: #a1a1aa;--text-muted: #71717a;--border-color: #3f3f51;--brand-primary: #7b68ee;--brand-secondary: #5a4fcf;--success-color: #4ade80;--info-color: #60a5fa;--warn-color: #fbbf24;--error-color: #f87171;--tooltip-bg-color: rgba(30, 30, 46, .95);--tooltip-text-color: #e4e4e7;--tooltip-shadow: 0 4px 20px rgba(0, 0, 0, .4);--tooltip-border: 1px solid #3f3f51;--shadow-sm: 0 2px 8px rgba(0, 0, 0, .3);--shadow-md: 0 8px 24px rgba(0, 0, 0, .4);--shadow-lg: 0 12px 32px rgba(0, 0, 0, .5)}.light-theme{--bg-primary: #ffffff;--bg-secondary: #f5f5f7;--bg-tertiary: #e9e9ec;--text-primary: #1a1a2e;--text-secondary: #4a4a5a;--text-muted: #71717a;--border-color: #e2e2e7;--brand-primary: #5a4fcf;--brand-secondary: #7b68ee;--success-color: #4CAF50;--info-color: #2196F3;--warn-color: #FF9800;--error-color: #F44336;--tooltip-bg-color: rgba(40, 40, 60, .95);--tooltip-text-color: #ffffff;--tooltip-border: none;--shadow-sm: 0 2px 8px rgba(0, 0, 0, .1);--shadow-md: 0 8px 24px rgba(0, 0, 0, .12);--shadow-lg: 0 12px 32px rgba(0, 0, 0, .15)}*{transition:background-color .3s ease,color .3s ease,border-color .3s ease,box-shadow .3s ease}body[dir=rtl]{direction:rtl;text-align:right}body[dir=rtl] .notification-summary,body[dir=rtl] .notification-detail{text-align:right!important}.n-tooltip{position:absolute;background-color:var(--tooltip-bg-color, rgba(40, 40, 60, .95));color:var(--tooltip-text-color, #ffffff);border-radius:6px;font-size:14px;font-weight:500;letter-spacing:.2px;z-index:1000;opacity:0;transform:scale(.95);transition:opacity .2s ease,transform .2s ease;box-shadow:var(--shadow-md, 0 8px 24px rgba(0, 0, 0, .4));-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);pointer-events:none;max-width:250px}.n-tooltip:after{content:\"\";position:absolute;display:block;width:0;height:0}.n-tooltip.n-tooltip-visible{opacity:1;transform:scale(1)}.n-tooltip .n-tooltip-content{padding:10px 14px;display:flex;align-items:center;gap:8px}.n-tooltip .n-tooltip-text{line-height:1.4}.n-tooltip .n-tooltip-icon{display:inline-flex;font-size:16px}.n-tooltip-top{transform-origin:bottom center}.n-tooltip-top:after{border-left:6px solid transparent;border-right:6px solid transparent;border-top:6px solid var(--tooltip-bg-color, rgba(40, 40, 60, .95));bottom:-6px;left:50%;transform:translate(-50%)}.n-tooltip-right{transform-origin:left center}.n-tooltip-right:after{border-top:6px solid transparent;border-bottom:6px solid transparent;border-right:6px solid var(--tooltip-bg-color, rgba(40, 40, 60, .95));left:-6px;top:50%;transform:translateY(-50%)}.n-tooltip-bottom{transform-origin:top center}.n-tooltip-bottom:after{border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid var(--tooltip-bg-color, rgba(40, 40, 60, .95));top:-6px;left:50%;transform:translate(-50%)}.n-tooltip-left{transform-origin:right center}.n-tooltip-left:after{border-top:6px solid transparent;border-bottom:6px solid transparent;border-left:6px solid var(--tooltip-bg-color, rgba(40, 40, 60, .95));right:-6px;top:50%;transform:translateY(-50%)}.n-tooltip-info{--tooltip-bg-color: rgba(33, 150, 243, .95)}.n-tooltip-info:after{border-top-color:#2196f3f2}.n-tooltip-info.n-tooltip-right:after{border-top-color:transparent;border-right-color:#2196f3f2}.n-tooltip-info.n-tooltip-bottom:after{border-top-color:transparent;border-bottom-color:#2196f3f2}.n-tooltip-info.n-tooltip-left:after{border-top-color:transparent;border-left-color:#2196f3f2}.n-tooltip-info .n-tooltip-icon{color:#fffffff2}.n-tooltip-success{--tooltip-bg-color: rgba(76, 175, 80, .95)}.n-tooltip-success:after{border-top-color:#4caf50f2}.n-tooltip-success.n-tooltip-right:after{border-top-color:transparent;border-right-color:#4caf50f2}.n-tooltip-success.n-tooltip-bottom:after{border-top-color:transparent;border-bottom-color:#4caf50f2}.n-tooltip-success.n-tooltip-left:after{border-top-color:transparent;border-left-color:#4caf50f2}.n-tooltip-success .n-tooltip-icon{color:#fffffff2}.n-tooltip-warn{--tooltip-bg-color: rgba(255, 152, 0, .95)}.n-tooltip-warn:after{border-top-color:#ff9800f2}.n-tooltip-warn.n-tooltip-right:after{border-top-color:transparent;border-right-color:#ff9800f2}.n-tooltip-warn.n-tooltip-bottom:after{border-top-color:transparent;border-bottom-color:#ff9800f2}.n-tooltip-warn.n-tooltip-left:after{border-top-color:transparent;border-left-color:#ff9800f2}.n-tooltip-warn .n-tooltip-icon{color:#fffffff2}.n-tooltip-error{--tooltip-bg-color: rgba(244, 67, 54, .95)}.n-tooltip-error:after{border-top-color:#f44336f2}.n-tooltip-error.n-tooltip-right:after{border-top-color:transparent;border-right-color:#f44336f2}.n-tooltip-error.n-tooltip-bottom:after{border-top-color:transparent;border-bottom-color:#f44336f2}.n-tooltip-error.n-tooltip-left:after{border-top-color:transparent;border-left-color:#f44336f2}.n-tooltip-error .n-tooltip-icon{color:#fffffff2}.notification-container{display:flex;align-items:flex-start;background:var(--bg-secondary);border-radius:12px;box-shadow:var(--shadow-md);padding:1.25rem;margin-bottom:1rem;width:340px;position:relative;overflow:hidden;border:1px solid var(--border-color);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px)}.notification-icon{display:flex;align-items:center;justify-content:center;width:40px;height:40px;border-radius:50%;margin-right:1rem;flex-shrink:0}.notification-icon-symbol{font-size:1.25rem}.notification-content{flex:1;padding-right:1.5rem}.notification-summary{font-weight:600;font-size:1rem;margin-bottom:.25rem;color:var(--text-primary);letter-spacing:.2px}.notification-detail{font-size:.9rem;color:var(--text-secondary);line-height:1.5}.notification-close{position:absolute;top:.75rem;right:.75rem;background:transparent;border:none;font-size:1.2rem;cursor:pointer;opacity:.6;transition:all .2s ease;color:var(--text-secondary);width:24px;height:24px;display:flex;align-items:center;justify-content:center;border-radius:4px}.notification-close:hover{opacity:1;background-color:#ffffff1a;color:var(--text-primary)}.notification-progress{position:absolute;bottom:0;left:0;height:3px;transition:width linear}.notification-success{color:var(--text-primary)}.notification-success .notification-icon{background-color:#4ade8026;color:var(--success-color)}.notification-success .notification-progress{background-color:var(--success-color)}.notification-info{color:var(--text-primary)}.notification-info .notification-icon{background-color:#60a5fa26;color:var(--info-color)}.notification-info .notification-progress{background-color:var(--info-color)}.notification-warn{color:var(--text-primary)}.notification-warn .notification-icon{background-color:#fbbf2426;color:var(--warn-color)}.notification-warn .notification-progress{background-color:var(--warn-color)}.notification-error{color:var(--text-primary)}.notification-error .notification-icon{background-color:#f8717126;color:var(--error-color)}.notification-error .notification-progress{background-color:var(--error-color)}app-notification{position:fixed;top:20px;right:20px;z-index:1200;display:block}.fade-in{animation:fadeIn .3s ease forwards}.fade-out{animation:fadeOut .2s ease forwards}@keyframes fadeIn{0%{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}@keyframes fadeOut{0%{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(-10px)}}@keyframes shimmer{0%{transform:translate(-100%)}to{transform:translate(100%)}}.light-theme{--bg-primary: #ffffff;--bg-secondary: #f5f5f7;--bg-tertiary: #e9e9ec;--text-primary: #1a1a2e;--text-secondary: #4a4a5a;--text-muted: #71717a;--border-color: #e2e2e7;--tooltip-bg-color: rgba(40, 40, 60, .95);--tooltip-text-color: #ffffff;--tooltip-border: none;--shadow-sm: 0 2px 8px rgba(0, 0, 0, .1);--shadow-md: 0 8px 24px rgba(0, 0, 0, .12);--shadow-lg: 0 12px 32px rgba(0, 0, 0, .15)}.light-theme .n-tooltip{--tooltip-text-color: #ffffff;--shadow-md: 0 8px 24px rgba(0, 0, 0, .12)}.light-theme .notification-container{background:#fff;border:1px solid var(--border-color)}.light-theme .notification-summary{color:var(--text-primary)}.light-theme .notification-detail,.light-theme .notification-close{color:var(--text-secondary)}.light-theme .notification-close:hover{background-color:#0000000d;color:var(--text-primary)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }], animations: [ trigger('notificationAnimation', [ state('void', style({ transform: 'translateX(100%)', opacity: 0 })), state('visible', style({ transform: 'translateX(0)', opacity: 1 })), state('hidden', style({ transform: 'translateX(100%)', opacity: 0 })), transition('void => visible', animate('300ms ease-out')), transition('visible => hidden', animate('200ms ease-in')) ]) ] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: NotificationComponent, decorators: [{ type: Component, args: [{ selector: 'app-notification', imports: [ CommonModule ], animations: [ trigger('notificationAnimation', [ state('void', style({ transform: 'translateX(100%)', opacity: 0 })), state('visible', style({ transform: 'translateX(0)', opacity: 1 })), state('hidden', style({ transform: 'translateX(100%)', opacity: 0 })), transition('void => visible', animate('300ms ease-out')), transition('visible => hidden', animate('200ms ease-in')) ]) ], template: "<div *ngIf=\"visible\" class=\"notification-container\" [ngClass]=\"'notification-' + message?.severity\"\n [@notificationAnimation]=\"animationState\">\n <div class=\"notification-icon\">\n <i class=\"notification-icon-symbol\" [ngClass]=\"getIconClass()\"></i>\n </div>\n <div class=\"notification-content\">\n <div class=\"notification-summary\">{{ message?.summary }}</div>\n <div class=\"notification-detail\">{{ message?.detail }}</div>\n </div>\n <button class=\"notification-close\" (click)=\"hide()\">\u00D7</button>\n <div *ngIf=\"showProgressBar\" class=\"notification-progress\" [style.width.%]=\"progressWidth\"></div>\n</div>", styles: ["@import\"https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap\";body{background-color:var(--bg-primary);color:var(--text-primary);font-family:Segoe UI,Roboto,Helvetica Neue,sans-serif;margin:0;padding:0;min-height:100vh;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;scrollbar-color:var(--bg-tertiary) var(--bg-secondary);scrollbar-width:thin}:root{--bg-primary: #1e1e2e;--bg-secondary: #282838;--bg-tertiary: #313142;--text-primary: #e4e4e7;--text-secondary: #a1a1aa;--text-muted: #71717a;--border-color: #3f3f51;--brand-primary: #7b68ee;--brand-secondary: #5a4fcf;--success-color: #4ade80;--info-color: #60a5fa;--warn-color: #fbbf24;--error-color: #f87171;--tooltip-bg-color: rgba(30, 30, 46, .95);--tooltip-text-color: #e4e4e7;--tooltip-shadow: 0 4px 20px rgba(0, 0, 0, .4);--tooltip-border: 1px solid #3f3f51;--shadow-sm: 0 2px 8px rgba(0, 0, 0, .3);--shadow-md: 0 8px 24px rgba(0, 0, 0, .4);--shadow-lg: 0 12px 32px rgba(0, 0, 0, .5)}.light-theme{--bg-primary: #ffffff;--bg-secondary: #f5f5f7;--bg-tertiary: #e9e9ec;--text-primary: #1a1a2e;--text-secondary: #4a4a5a;--text-muted: #71717a;--border-color: #e2e2e7;--brand-primary: #5a4fcf;--brand-secondary: #7b68ee;--success-color: #4CAF50;--info-color: #2196F3;--warn-color: #FF9800;--error-color: #F44336;--tooltip-bg-color: rgba(40, 40, 60, .95);--tooltip-text-color: #ffffff;--tooltip-border: none;--shadow-sm: 0 2px 8px rgba(0, 0, 0, .1);--shadow-md: 0 8px 24px rgba(0, 0, 0, .12);--shadow-lg: 0 12px 32px rgba(0, 0, 0, .15)}*{transition:background-color .3s ease,color .3s ease,border-color .3s ease,box-shadow .3s ease}body[dir=rtl]{direction:rtl;text-align:right}body[dir=rtl] .notification-summary,body[dir=rtl] .notification-detail{text-align:right!important}.n-tooltip{position:absolute;background-color:var(--tooltip-bg-color, rgba(40, 40, 60, .95));color:var(--tooltip-text-color, #ffffff);border-radius:6px;font-size:14px;font-weight:500;letter-spacing:.2px;z-index:1000;opacity:0;transform:scale(.95);transition:opacity .2s ease,transform .2s ease;box-shadow:var(--shadow-md, 0 8px 24px rgba(0, 0, 0, .4));-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);pointer-events:none;max-width:250px}.n-tooltip:after{content:\"\";position:absolute;display:block;width:0;height:0}.n-tooltip.n-tooltip-visible{opacity:1;transform:scale(1)}.n-tooltip .n-tooltip-content{padding:10px 14px;display:flex;align-items:center;gap:8px}.n-tooltip .n-tooltip-text{line-height:1.4}.n-tooltip .n-tooltip-icon{display:inline-flex;font-size:16px}.n-tooltip-top{transform-origin:bottom center}.n-tooltip-top:after{border-left:6px solid transparent;border-right:6px solid transparent;border-top:6px solid var(--tooltip-bg-color, rgba(40, 40, 60, .95));bottom:-6px;left:50%;transform:translate(-50%)}.n-tooltip-right{transform-origin:left center}.n-tooltip-right:after{border-top:6px solid transparent;border-bottom:6px solid transparent;border-right:6px solid var(--tooltip-bg-color, rgba(40, 40, 60, .95));left:-6px;top:50%;transform:translateY(-50%)}.n-tooltip-bottom{transform-origin:top center}.n-tooltip-bottom:after{border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid var(--tooltip-bg-color, rgba(40, 40, 60, .95));top:-6px;left:50%;transform:translate(-50%)}.n-tooltip-left{transform-origin:right center}.n-tooltip-left:after{border-top:6px solid transparent;border-bottom:6px solid transparent;border-left:6px solid var(--tooltip-bg-color, rgba(40, 40, 60, .95));right:-6px;top:50%;transform:translateY(-50%)}.n-tooltip-info{--tooltip-bg-color: rgba(33, 150, 243, .95)}.n-tooltip-info:after{border-top-color:#2196f3f2}.n-tooltip-info.n-tooltip-right:after{border-top-color:transparent;border-right-color:#2196f3f2}.n-tooltip-info.n-tooltip-bottom:after{border-top-color:transparent;border-bottom-color:#2196f3f2}.n-tooltip-info.n-tooltip-left:after{border-top-color:transparent;border-left-color:#2196f3f2}.n-tooltip-info .n-tooltip-icon{color:#fffffff2}.n-tooltip-success{--tooltip-bg-color: rgba(76, 175, 80, .95)}.n-tooltip-success:after{border-top-color:#4caf50f2}.n-tooltip-success.n-tooltip-right:after{border-top-color:transparent;border-right-color:#4caf50f2}.n-tooltip-success.n-tooltip-bottom:after{border-top-color:transparent;border-bottom-color:#4caf50f2}.n-tooltip-success.n-tooltip-left:after{border-top-color:transparent;border-left-color:#4caf50f2}.n-tooltip-success .n-tooltip-icon{color:#fffffff2}.n-tooltip-warn{--tooltip-bg-color: rgba(255, 152, 0, .95)}.n-tooltip-warn:after{border-top-color:#ff9800f2}.n-tooltip-warn.n-tooltip-right:after{border-top-color:transparent;border-right-color:#ff9800f2}.n-tooltip-warn.n-tooltip-bottom:after{border-top-color:transparent;border-bottom-color:#ff9800f2}.n-tooltip-warn.n-tooltip-left:after{border-top-color:transparent;border-left-color:#ff9800f2}.n-tooltip-warn .n-tooltip-icon{color:#fffffff2}.n-tooltip-error{--tooltip-bg-color: rgba(244, 67, 54, .95)}.n-tooltip-error:after{border-top-color:#f44336f2}.n-tooltip-error.n-tooltip-right:after{border-top-color:transparent;border-right-color:#f44336f2}.n-tooltip-error.n-tooltip-bottom:after{border-top-color:transparent;border-bottom-color:#f44336f2}.n-tooltip-error.n-tooltip-left:after{border-top-color:transparent;border-left-color:#f44336f2}.n-tooltip-error .n-tooltip-icon{color:#fffffff2}.notification-container{display:flex;align-items:flex-start;background:var(--bg-secondary);border-radius:12px;box-shadow:var(--shadow-md);padding:1.25rem;margin-bottom:1rem;width:340px;position:relative;overflow:hidden;border:1px solid var(--border-color);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px)}.notification-icon{display:flex;align-items:center;justify-content:center;width:40px;height:40px;border-radius:50%;margin-right:1rem;flex-shrink:0}.notification-icon-symbol{font-size:1.25rem}.notification-content{flex:1;padding-right:1.5rem}.notification-summary{font-weight:600;font-size:1rem;margin-bottom:.25rem;color:var(--text-primary);letter-spacing:.2px}.notification-detail{font-size:.9rem;color:var(--text-secondary);line-height:1.5}.notification-close{position:absolute;top:.75rem;right:.75rem;background:transparent;border:none;font-size:1.2rem;cursor:pointer;opacity:.6;transition:all .2s ease;color:var(--text-secondary);width:24px;height:24px;display:flex;align-items:center;justify-content:center;border-radius:4px}.notification-close:hover{opacity:1;background-color:#ffffff1a;color:var(--text-primary)}.notification-progress{position:absolute;bottom:0;left:0;height:3px;transition:width linear}.notification-success{color:var(--text-primary)}.notification-success .notification-icon{background-color:#4ade8026;color:var(--success-color)}.notification-success .notification-progress{background-color:var(--success-color)}.notification-info{color:var(--text-primary)}.notification-info .notification-icon{background-color:#60a5fa26;color:var(--info-color)}.notification-info .notification-progress{background-color:var(--info-color)}.notification-warn{color:var(--text-primary)}.notification-warn .notification-icon{background-color:#fbbf2426;color:var(--warn-color)}.notification-warn .notification-progress{background-color:var(--warn-color)}.notification-error{color:var(--text-primary)}.notification-error .notification-icon{background-color:#f8717126;color:var(--error-color)}.notification-error .notification-progress{background-color:var(--error-color)}app-notification{position:fixed;top:20px;right:20px;z-index:1200;display:block}.fade-in{animation:fadeIn .3s ease forwards}.fade-out{animation:fadeOut .2s ease forwards}@keyframes fadeIn{0%{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}@keyframes fadeOut{0%{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(-10px)}}@keyframes shimmer{0%{transform:translate(-100%)}to{transform:translate(100%)}}.light-theme{--bg-primary: #ffffff;--bg-secondary: #f5f5f7;--bg-tertiary: #e9e9ec;--text-primary: #1a1a2e;--text-secondary: #4a4a5a;--text-muted: #71717a;--border-color: #e2e2e7;--tooltip-bg-color: rgba(40, 40, 60, .95);--tooltip-text-color: #ffffff;--tooltip-border: none;--shadow-sm: 0 2px 8px rgba(0, 0, 0, .1);--shadow-md: 0 8px 24px rgba(0, 0, 0, .12);--shadow-lg: 0 12px 32px rgba(0, 0, 0, .15)}.light-theme .n-tooltip{--tooltip-text-color: #ffffff;--shadow-md: 0 8px 24px rgba(0, 0, 0, .12)}.light-theme .notification-container{background:#fff;border:1px solid var(--border-color)}.light-theme .notification-summary{color:var(--text-primary)}.light-theme .notification-detail,.light-theme .notification-close{color:var(--text-secondary)}.light-theme .notification-close:hover{background-color:#0000000d;color:var(--text-primary)}\n"] }] }], propDecorators: { message: [{ type: Input }] } }); class NotificationService { appRef; componentFactoryResolver; injector; notificationRefs = []; constructor(appRef, componentFactoryResolver, injector) { this.appRef = appRef; this.componentFactoryResolver = componentFactoryResolver; this.injector = injector; } show(message) { const componentRef = this.componentFactoryResolver .resolveComponentFactory(NotificationComponent) .create(this.injector); componentRef.instance.message = message; this.appRef.attachView(componentRef.hostView); const domElement = componentRef.hostView.rootNodes[0]; document.body.appendChild(domElement); this.notificationRefs.push(componentRef); componentRef.instance.animationState = 'visible'; const originalHide = componentRef.instance.hide; componentRef.instance.hide = () => { originalHide.call(componentRef.instance); setTimeout(() => { this.removeNotification(componentRef); }, 300); }; } success(summary, detail, timeout = 5000) { this.show({ severity: 'success', summary, detail, timeout }); } info(summary, detail, timeout = 5000) { this.show({ severity: 'info', summary, detail, timeout }); } warn(summary, detail, timeout = 5000) { this.show({ severity: 'warn', summary, detail, timeout }); } error(summary, detail, timeout = 5000) { this.show({ severity: 'error', summary, detail, timeout }); } removeNotification(ref) { const index = this.notificationRefs.indexOf(ref); if (index !== -1) { this.appRef.detachView(ref.hostView); ref.destroy(); this.notificationRefs.splice(index, 1); } } clear() { this.notificationRefs.forEach(ref => { this.appRef.detachView(ref.hostView); ref.destroy(); }); this.notificationRefs = []; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: NotificationService, deps: [{ token: i0.ApplicationRef }, { token: i0.ComponentFactoryResolver }, { token: i0.Injector }], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: NotificationService, providedIn: 'root' }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: NotificationService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: () => [{ type: i0.ApplicationRef }, { type: i0.ComponentFactoryResolver }, { type: i0.Injector }] }); class ThemeService { renderer; _isDarkTheme = new BehaviorSubject(true); isDarkTheme$ = this._isDarkTheme.asObservable(); constructor(rendererFactory) { this.renderer = rendererFactory.createRenderer(null, null); const savedTheme = localStorage.getItem('isDarkTheme'); const initialTheme = savedTheme !== null ? savedTheme === 'true' : window.matchMedia('(prefers-color-scheme: dark)').matches; this.setTheme(initialTheme); } get isDarkTheme() { return this._isDarkTheme.value; } setTheme(isDark) { this._isDarkTheme.next(isDark); localStorage.setItem('isDarkTheme', String(isDark)); if (isDark) { this.renderer.addClass(document.documentElement, 'dark-theme'); this.renderer.removeClass(document.documentElement, 'light-theme'); } else { this.renderer.addClass(document.documentElement, 'light-theme'); this.renderer.removeClass(document.documentElement, 'dark-theme'); } } toggleTheme() { this.setTheme(!this.isDarkTheme); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: ThemeService, deps: [{ token: i0.RendererFactory2 }], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: ThemeService, providedIn: 'root' }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: ThemeService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: () => [{ type: i0.RendererFactory2 }] }); class ThemeToggleComponent { themeService; size = 'medium'; constructor(themeService) { this.themeService = themeService; } get isDarkTheme() { return this.themeService.isDarkTheme; } toggleTheme() { this.themeService.toggleTheme(); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: ThemeToggleComponent, deps: [{ token: ThemeService }], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.3", type: ThemeToggleComponent, isStandalone: true, selector: "app-theme-toggle", inputs: { size: "size" }, ngImport: i0, template: ` <button class="theme-toggle" [class.theme-toggle-small]="size === 'small'" [class.theme-toggle-large]="size === 'large'" (click)="toggleTheme()" [attr.aria-label]="'Toggle ' + (isDarkTheme ? 'light' : 'dark') + ' mode'" > <div class="theme-toggle-icon"> <svg *ngIf="isDarkTheme" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <circle cx="12" cy="12" r="5"></circle> <line x1="12" y1="1" x2="12" y2="3"></line> <line x1="12" y1="21" x2="12" y2="23"></line> <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line> <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line> <line x1="1" y1="12" x2="3" y2="12"></line> <line x1="21" y1="12" x2="23" y2="12"></line> <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line> <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line> </svg> <svg *ngIf="!isDarkTheme" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path> </svg> </div> </button> `, isInline: true, styles: [".theme-toggle{background-color:var(--bg-tertiary);color:var(--text-primary);border:1px solid var(--border-color);border-radius:50%;cursor:pointer;width:40px;height:40px;display:flex;align-items:center;justify-content:center;transition:all .2s ease;padding:0;box-shadow:var(--shadow-sm)}.theme-toggle:hover{box-shadow:var(--shadow-md)}.theme-toggle:active{transform:translateY(0)}.theme-toggle-icon{width:20px;height:20px;display:flex;align-items:center;justify-content:center}.theme-toggle-small{width:32px;height:32px}.theme-toggle-small .theme-toggle-icon{width:16px;height:16px}.theme-toggle-large{width:48px;height:48px}.theme-toggle-large .theme-toggle-icon{width:24px;height:24px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: ThemeToggleComponent, decorators: [{ type: Component, args: [{ selector: 'app-theme-toggle', standalone: true, imports: [CommonModule], template: ` <button class="theme-toggle" [class.theme-toggle-small]="size === 'small'" [class.theme-toggle-large]="size === 'large'" (click)="toggleTheme()" [attr.aria-label]="'Toggle ' + (isDarkTheme ? 'light' : 'dark') + ' mode'" > <div class="theme-toggle-icon"> <svg *ngIf="isDarkTheme" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <circle cx="12" cy="12" r="5"></circle> <line x1="12" y1="1" x2="12" y2="3"></line> <line x1="12" y1="21" x2="12" y2="23"></line> <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line> <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line> <line x1="1" y1="12" x2="3" y2="12"></line> <line x1="21" y1="12" x2="23" y2="12"></line> <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line> <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line> </svg> <svg *ngIf="!isDarkTheme" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path> </svg> </div> </button> `, styles: [".theme-toggle{background-color:var(--bg-tertiary);color:var(--text-primary);border:1px solid var(--border-color);border-radius:50%;cursor:pointer;width:40px;height:40px;display:flex;align-items:center;justify-content:center;transition:all .2s ease;padding:0;box-shadow:var(--shadow-sm)}.theme-toggle:hover{box-shadow:var(--shadow-md)}.theme-toggle:active{transform:translateY(0)}.theme-toggle-icon{width:20px;height:20px;display:flex;align-items:center;justify-content:center}.theme-toggle-small{width:32px;height:32px}.theme-toggle-small .theme-toggle-icon{width:16px;height:16px}.theme-toggle-large{width:48px;height:48px}.theme-toggle-large .theme-toggle-icon{width:24px;height:24px}\n"] }] }], ctorParameters: () => [{ type: ThemeService }], propDecorators: { size: [{ type: Input }] } }); class ThemeModule { static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: ThemeModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.1.3", ngImport: i0, type: ThemeModule, imports: [CommonModule, ThemeToggleComponent], exports: [ThemeToggleComponent] }); static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: ThemeModule, providers: [ ThemeService ], imports: [CommonModule, ThemeToggleComponent] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: ThemeModule, decorators: [{ type: NgModule, args: [{ imports: [ CommonModule, ThemeToggleComponent ], exports: [ ThemeToggleComponent ], providers: [ ThemeService ] }] }] }); class ConfirmationDialogService { dialogSubject = new Subject(); resultSubject = new Subject(); currentDialog = null; dialog$ = this.dialogSubject.asObservable(); confirm(config) { const completeConfig = { confirmText: "Confirm", cancelText: "Cancel", severity: "info", showCancel: true, ...config, }; this.dialogSubject.next(completeConfig); this.currentDialog = this.resultSubject.asObservable(); return this.currentDialog; } success(title, message, options) { return this.confirm({ title, message, severity: "success", ...options, }); } info(title, message, options) { return this.confirm({ title, message, severity: "info", ...options, }); } warn(title, message, options) { return this.confirm({ title, message, severity: "warn", ...options, }); } error(title, message, options) { return this.confirm({ title, message, severity: "error", ...options, }); } close(result) { this.resultSubject.next(result); this.dialogSubject.next(null); this.currentDialog = null; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: ConfirmationDialogService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: ConfirmationDialogService, providedIn: "root" }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: ConfirmationDialogService, decorators: [{ type: Injectable, args: [{ providedIn: "root", }] }] }); class ConfirmationDialogComponent { dialogService; cdr; document; visible = false; config = null; subscription = new Subscription(); animationInProgress = false; constructor(dialogService, cdr, document) { this.dialogService = dialogService; this.cdr = cdr; this.document = document; } ngOnInit() { this.subscription.add(this.dialogService.dialog$.subscribe((config) => { this.config = config; this.visible = !!config; if (this.visible) { this.disableBodyScroll(); } else { this.enableBodyScroll(); } this.cdr.detectChanges(); })); } ngOnDestroy() { this.enableBodyScroll(); this.subscription.unsubscribe(); } confirm() { if (this.animationInProgress) return; this.dialogService.close({ confirmed: true }); } cancel() { if (this.animationInProgress) return; this.dialogService.close({ confirmed: false }); } onEscapeKey() { if (this.visible && !this.animationInProgress) { this.cancel(); } } getIconClass(severity = "info") { switch (severity) { case "success": return "pi pi-check-circle"; case "warn": return "pi pi-exclamation-triangle"; case "error": return "pi pi-times-circle"; case "info": default: return "pi pi-info-circle"; } } stopPropagation(event) { event.stopPropagation(); } onAnimationStart(event) { this.animationInProgress = true; } onAnimationDone(event) { this.animationInProgress = false; } disableBodyScroll() { const scrollbarWidth = window.innerWidth - this.document.documentElement.clientWidth; this.document.body.style.overflow = "hidden"; this.document.body.style.paddingRight = `${scrollbarWidth}px`; } enableBodyScroll() { this.document.body.style.overflow = ""; this.document.body.style.paddingRight = ""; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: ConfirmationDialogComponent, deps: [{ token: ConfirmationDialogService }, { to