nzrm-ng
Version:
<p align="center"> <img src="docs/angular.gif" alt="Angular" width="100" height="100"/> </p>
1 lines • 218 kB
Source Map (JSON)
{"version":3,"file":"nzrm-ng.mjs","sources":["../../../projects/nzrm-ng/src/lib/components/tooltip/tooltip.directive.ts","../../../projects/nzrm-ng/src/lib/components/tooltip/tooltip.module.ts","../../../projects/nzrm-ng/src/lib/components/notification/notification.component.ts","../../../projects/nzrm-ng/src/lib/components/notification/notification.component.html","../../../projects/nzrm-ng/src/lib/components/notification/notification.service.ts","../../../projects/nzrm-ng/src/lib/services/theme.service.ts","../../../projects/nzrm-ng/src/lib/components/theme-toggle/theme-toggle.component.ts","../../../projects/nzrm-ng/src/lib/components/theme-toggle/theme-toggle.module.ts","../../../projects/nzrm-ng/src/lib/components/confirmation-dialog/confirmation-dialog.service.ts","../../../projects/nzrm-ng/src/lib/components/confirmation-dialog/confirmation-dialog.component.ts","../../../projects/nzrm-ng/src/lib/components/confirmation-dialog/confirmation-dialog.component.html","../../../projects/nzrm-ng/src/lib/components/confirmation-dialog/confirmation-dialog.directive.ts","../../../projects/nzrm-ng/src/lib/components/message-dialog/message-dialog.service.ts","../../../projects/nzrm-ng/src/lib/components/message-dialog/message-dialog.component.ts","../../../projects/nzrm-ng/src/lib/components/message-dialog/message-dialog.component.html","../../../projects/nzrm-ng/src/lib/components/button/button.directive.ts","../../../projects/nzrm-ng/src/lib/components/button/button.module.ts","../../../projects/nzrm-ng/src/lib/components/input-text/input-text.directive.ts","../../../projects/nzrm-ng/src/lib/components/input-text/input-text.module.ts","../../../projects/nzrm-ng/src/lib/components/drop-down/drop-down.directive.ts","../../../projects/nzrm-ng/src/lib/components/drop-down/drop-down.service.ts","../../../projects/nzrm-ng/src/lib/components/drop-down/drop-down.component.ts","../../../projects/nzrm-ng/src/lib/components/drop-down/drop-down.component.html","../../../projects/nzrm-ng/src/lib/components/drop-down/drop-down.module.ts","../../../projects/nzrm-ng/src/lib/components/skeleton/skeleton.directive.ts","../../../projects/nzrm-ng/src/lib/components/skeleton/skeleton.module.ts","../../../projects/nzrm-ng/src/lib/styles/styles.module.ts","../../../projects/nzrm-ng/src/public-api.ts","../../../projects/nzrm-ng/src/nzrm-ng.ts"],"sourcesContent":["import { Directive, ElementRef, HostListener, Input, OnInit, OnDestroy, Renderer2 } from '@angular/core';\n\nexport type TooltipSeverity = 'info' | 'success' | 'error' | 'warn' | 'normal';\nexport type TooltipPosition = 'top' | 'right' | 'bottom' | 'left';\n\n@Directive({\n selector: '[nTooltip]',\n standalone: true,\n})\nexport class TooltipDirective implements OnInit, OnDestroy {\n @Input() tooltipText = '';\n @Input() showOnlyIf = true;\n @Input() sticky = false;\n @Input() severity: TooltipSeverity = 'normal';\n @Input() position: TooltipPosition = 'top';\n @Input() showDelay = 100;\n @Input() hideDelay = 100;\n @Input() maxWidth = '250px';\n\n private tooltipElement: HTMLElement | null = null;\n private isVisible = false;\n private showTimeoutId: any = null;\n private hideTimeoutId: any = null;\n\n constructor(\n private el: ElementRef,\n private renderer: Renderer2,\n ) { }\n\n ngOnInit() {\n this.initializeTooltip();\n\n if (this.sticky && this.showOnlyIf) {\n setTimeout(() => this.showTooltip(), 0);\n }\n }\n\n private initializeTooltip(): void {\n if (this.tooltipElement) {\n this.renderer.removeChild(document.body, this.tooltipElement);\n }\n\n this.tooltipElement = this.renderer.createElement('div');\n this.renderer.addClass(this.tooltipElement, 'n-tooltip');\n this.renderer.addClass(this.tooltipElement, `n-tooltip-${this.position}`);\n\n if (this.severity !== 'normal') {\n this.renderer.addClass(this.tooltipElement, `n-tooltip-${this.severity}`);\n }\n\n const contentWrapper = this.renderer.createElement('div');\n this.renderer.addClass(contentWrapper, 'n-tooltip-content');\n\n if (this.severity !== 'normal') {\n const iconElement = this.renderer.createElement('span');\n this.renderer.addClass(iconElement, 'n-tooltip-icon');\n\n this.renderer.addClass(iconElement, 'pi');\n\n switch (this.severity) {\n case 'info':\n this.renderer.addClass(iconElement, 'pi-info-circle');\n break;\n case 'success':\n this.renderer.addClass(iconElement, 'pi-check-circle');\n break;\n case 'warn':\n this.renderer.addClass(iconElement, 'pi-exclamation-triangle');\n break;\n case 'error':\n this.renderer.addClass(iconElement, 'pi-times-circle');\n break;\n }\n\n this.renderer.appendChild(contentWrapper, iconElement);\n }\n\n const textElement = this.renderer.createElement('span');\n this.renderer.addClass(textElement, 'n-tooltip-text');\n const textNode = this.renderer.createText(this.tooltipText);\n this.renderer.appendChild(textElement, textNode);\n this.renderer.appendChild(contentWrapper, textElement);\n\n this.renderer.appendChild(this.tooltipElement, contentWrapper);\n\n this.renderer.setStyle(this.tooltipElement, 'max-width', this.maxWidth);\n\n this.renderer.setStyle(this.tooltipElement, 'position', 'absolute');\n this.renderer.setStyle(this.tooltipElement, 'z-index', '9999');\n this.renderer.setStyle(this.tooltipElement, 'display', 'none');\n this.renderer.setStyle(this.tooltipElement, 'opacity', '0');\n\n this.renderer.appendChild(document.body, this.tooltipElement);\n }\n\n @HostListener('mouseenter')\n onMouseEnter(): void {\n if (!this.showOnlyIf) return;\n this.clearTimeouts();\n\n this.showTimeoutId = setTimeout(() => {\n this.showTooltip();\n }, this.showDelay);\n }\n\n @HostListener('focus')\n onFocus(): void {\n if (!this.showOnlyIf) return;\n this.clearTimeouts();\n\n this.showTimeoutId = setTimeout(() => {\n this.showTooltip();\n }, this.showDelay);\n }\n\n @HostListener('mouseleave')\n onMouseLeave(): void {\n if (this.tooltipElement && !this.sticky) {\n this.clearTimeouts();\n\n this.hideTimeoutId = setTimeout(() => {\n this.hideTooltip();\n }, this.hideDelay);\n }\n }\n\n @HostListener('blur')\n onBlur(): void {\n if (this.tooltipElement && !this.sticky) {\n this.clearTimeouts();\n\n this.hideTimeoutId = setTimeout(() => {\n this.hideTooltip();\n }, this.hideDelay);\n }\n }\n\n @HostListener('click')\n onClick(): void {\n if (this.sticky && this.showOnlyIf) {\n if (this.isVisible) {\n this.hideTooltip();\n } else {\n this.showTooltip();\n }\n }\n }\n\n @HostListener('window:scroll')\n onScroll(): void {\n if (this.tooltipElement && this.showOnlyIf && this.isVisible) {\n this.updateTooltipPosition();\n }\n }\n\n @HostListener('window:resize')\n onResize(): void {\n if (this.tooltipElement && this.showOnlyIf && this.isVisible) {\n this.updateTooltipPosition();\n }\n }\n\n private clearTimeouts(): void {\n if (this.showTimeoutId) {\n clearTimeout(this.showTimeoutId);\n this.showTimeoutId = null;\n }\n\n if (this.hideTimeoutId) {\n clearTimeout(this.hideTimeoutId);\n this.hideTimeoutId = null;\n }\n }\n\n private showTooltip(): void {\n if (!this.tooltipElement) return;\n\n this.renderer.setStyle(this.tooltipElement, 'display', 'block');\n\n this.updateTooltipPosition();\n\n void this.tooltipElement.offsetHeight;\n\n this.renderer.setStyle(this.tooltipElement, 'opacity', '1');\n this.isVisible = true;\n }\n\n private hideTooltip(): void {\n if (!this.tooltipElement) return;\n\n this.renderer.setStyle(this.tooltipElement, 'opacity', '0');\n\n // Wait for the transition to complete\n setTimeout(() => {\n if (this.tooltipElement && !this.isVisible) {\n this.renderer.setStyle(this.tooltipElement, 'display', 'none');\n }\n }, 300);\n\n this.isVisible = false;\n }\n\n private updateTooltipPosition(): void {\n if (!this.tooltipElement) return;\n\n const hostRect = this.el.nativeElement.getBoundingClientRect();\n let tooltipRect = this.tooltipElement.getBoundingClientRect();\n\n if (tooltipRect.width === 0 || tooltipRect.height === 0) {\n const currentDisplay = this.tooltipElement.style.display;\n const currentOpacity = this.tooltipElement.style.opacity;\n\n this.renderer.setStyle(this.tooltipElement, 'display', 'block');\n this.renderer.setStyle(this.tooltipElement, 'opacity', '0');\n\n tooltipRect = this.tooltipElement.getBoundingClientRect();\n\n if (!this.isVisible) {\n this.renderer.setStyle(this.tooltipElement, 'display', currentDisplay);\n this.renderer.setStyle(this.tooltipElement, 'opacity', currentOpacity);\n }\n }\n\n let top = 0, left = 0;\n\n switch (this.position) {\n case 'top':\n top = hostRect.top - tooltipRect.height - 8;\n left = hostRect.left + (hostRect.width / 2) - (tooltipRect.width / 2);\n break;\n case 'right':\n top = hostRect.top + (hostRect.height / 2) - (tooltipRect.height / 2);\n left = hostRect.right + 8;\n break;\n case 'bottom':\n top = hostRect.bottom + 8;\n left = hostRect.left + (hostRect.width / 2) - (tooltipRect.width / 2);\n break;\n case 'left':\n top = hostRect.top + (hostRect.height / 2) - (tooltipRect.height / 2);\n left = hostRect.left - tooltipRect.width - 8;\n break;\n }\n\n this.renderer.setStyle(this.tooltipElement, 'top', `${top + window.pageYOffset}px`);\n this.renderer.setStyle(this.tooltipElement, 'left', `${left + window.pageXOffset}px`);\n }\n\n ngOnDestroy(): void {\n this.clearTimeouts();\n\n if (this.tooltipElement) {\n this.renderer.removeChild(document.body, this.tooltipElement);\n this.tooltipElement = null;\n }\n }\n}","import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { TooltipDirective } from './tooltip.directive';\n\n\n\n@NgModule({\n declarations: [],\n imports: [\n CommonModule,\n TooltipDirective\n ],\n exports: [\n TooltipDirective\n ]\n})\nexport class TooltipModule { }\n","import { trigger, state, style, transition, animate } from '@angular/animations';\nimport { CommonModule } from '@angular/common';\nimport { Component, Input } from '@angular/core';\n\nexport interface NotificationMessage {\n severity: 'success' | 'info' | 'warn' | 'error';\n summary: string;\n detail: string;\n timeout?: number;\n}\n\n@Component({\n selector: 'app-notification',\n imports: [\n CommonModule\n ],\n templateUrl: './notification.component.html',\n styleUrl: './notification.component.scss',\n animations: [\n trigger('notificationAnimation', [\n state('void', style({\n transform: 'translateX(100%)',\n opacity: 0\n })),\n state('visible', style({\n transform: 'translateX(0)',\n opacity: 1\n })),\n state('hidden', style({\n transform: 'translateX(100%)',\n opacity: 0\n })),\n transition('void => visible', animate('300ms ease-out')),\n transition('visible => hidden', animate('200ms ease-in'))\n ])\n ]\n})\nexport class NotificationComponent {\n @Input() message: NotificationMessage | null = null;\n\n visible = false;\n animationState = 'void';\n progressWidth = 100;\n showProgressBar = true;\n\n private timeout: any;\n private progressInterval: any;\n\n ngOnInit(): void {\n if (this.message) {\n this.show();\n }\n }\n\n show(): void {\n this.visible = true;\n this.animationState = 'visible';\n this.startProgressBar();\n }\n\n hide(): void {\n this.animationState = 'hidden';\n this.clearTimers();\n\n setTimeout(() => {\n this.visible = false;\n }, 200);\n }\n\n getIconClass(): string {\n switch (this.message?.severity) {\n case 'success': return 'pi pi-check-circle';\n case 'info': return 'pi pi-info-circle';\n case 'warn': return 'pi pi-exclamation-triangle';\n case 'error': return 'pi pi-times-circle';\n default: return 'pi pi-info-circle';\n }\n }\n\n private startProgressBar(): void {\n this.progressWidth = 100;\n const duration = this.message?.timeout || 5000;\n\n clearInterval(this.progressInterval);\n this.progressInterval = setInterval(() => {\n this.progressWidth -= 0.5;\n if (this.progressWidth <= 0) {\n this.hide();\n }\n }, duration / 200);\n\n this.timeout = setTimeout(() => {\n this.hide();\n }, duration);\n }\n\n private clearTimers(): void {\n clearTimeout(this.timeout);\n clearInterval(this.progressInterval);\n }\n\n ngOnDestroy(): void {\n this.clearTimers();\n }\n}","<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()\">×</button>\n <div *ngIf=\"showProgressBar\" class=\"notification-progress\" [style.width.%]=\"progressWidth\"></div>\n</div>","import { Injectable, ApplicationRef, ComponentFactoryResolver, ComponentRef, Injector, EmbeddedViewRef } from '@angular/core';\nimport { NotificationComponent, NotificationMessage } from './notification.component';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class NotificationService {\n private notificationRefs: ComponentRef<NotificationComponent>[] = [];\n\n constructor(\n private appRef: ApplicationRef,\n private componentFactoryResolver: ComponentFactoryResolver,\n private injector: Injector\n ) { }\n\n show(message: NotificationMessage): void {\n const componentRef = this.componentFactoryResolver\n .resolveComponentFactory(NotificationComponent)\n .create(this.injector);\n\n componentRef.instance.message = message;\n\n this.appRef.attachView(componentRef.hostView);\n\n const domElement = (componentRef.hostView as EmbeddedViewRef<any>).rootNodes[0];\n\n document.body.appendChild(domElement);\n\n this.notificationRefs.push(componentRef);\n\n componentRef.instance.animationState = 'visible';\n const originalHide = componentRef.instance.hide;\n componentRef.instance.hide = () => {\n originalHide.call(componentRef.instance);\n\n setTimeout(() => {\n this.removeNotification(componentRef);\n }, 300);\n };\n }\n\n success(summary: string, detail: string, timeout = 5000): void {\n this.show({ severity: 'success', summary, detail, timeout });\n }\n\n info(summary: string, detail: string, timeout = 5000): void {\n this.show({ severity: 'info', summary, detail, timeout });\n }\n\n warn(summary: string, detail: string, timeout = 5000): void {\n this.show({ severity: 'warn', summary, detail, timeout });\n }\n\n error(summary: string, detail: string, timeout = 5000): void {\n this.show({ severity: 'error', summary, detail, timeout });\n }\n\n private removeNotification(ref: ComponentRef<NotificationComponent>): void {\n const index = this.notificationRefs.indexOf(ref);\n if (index !== -1) {\n this.appRef.detachView(ref.hostView);\n ref.destroy();\n this.notificationRefs.splice(index, 1);\n }\n }\n\n clear(): void {\n this.notificationRefs.forEach(ref => {\n this.appRef.detachView(ref.hostView);\n ref.destroy();\n });\n this.notificationRefs = [];\n }\n}","import { Injectable, Renderer2, RendererFactory2 } from '@angular/core';\nimport { BehaviorSubject } from 'rxjs';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class ThemeService {\n private renderer: Renderer2;\n private _isDarkTheme = new BehaviorSubject<boolean>(true);\n\n public isDarkTheme$ = this._isDarkTheme.asObservable();\n\n constructor(rendererFactory: RendererFactory2) {\n this.renderer = rendererFactory.createRenderer(null, null);\n\n const savedTheme = localStorage.getItem('isDarkTheme');\n const initialTheme = savedTheme !== null ? savedTheme === 'true' :\n window.matchMedia('(prefers-color-scheme: dark)').matches;\n\n this.setTheme(initialTheme);\n }\n\n public get isDarkTheme(): boolean {\n return this._isDarkTheme.value;\n }\n\n public setTheme(isDark: boolean): void {\n this._isDarkTheme.next(isDark);\n localStorage.setItem('isDarkTheme', String(isDark));\n\n if (isDark) {\n this.renderer.addClass(document.documentElement, 'dark-theme');\n this.renderer.removeClass(document.documentElement, 'light-theme');\n } else {\n this.renderer.addClass(document.documentElement, 'light-theme');\n this.renderer.removeClass(document.documentElement, 'dark-theme');\n }\n }\n\n public toggleTheme(): void {\n this.setTheme(!this.isDarkTheme);\n }\n}","import { Component, Input } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { ThemeService } from '../../services/theme.service';\n\n@Component({\n selector: 'app-theme-toggle',\n standalone: true,\n imports: [CommonModule],\n template: `\n <button \n class=\"theme-toggle\"\n [class.theme-toggle-small]=\"size === 'small'\"\n [class.theme-toggle-large]=\"size === 'large'\"\n (click)=\"toggleTheme()\" \n [attr.aria-label]=\"'Toggle ' + (isDarkTheme ? 'light' : 'dark') + ' mode'\"\n >\n <div class=\"theme-toggle-icon\">\n <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\">\n <circle cx=\"12\" cy=\"12\" r=\"5\"></circle>\n <line x1=\"12\" y1=\"1\" x2=\"12\" y2=\"3\"></line>\n <line x1=\"12\" y1=\"21\" x2=\"12\" y2=\"23\"></line>\n <line x1=\"4.22\" y1=\"4.22\" x2=\"5.64\" y2=\"5.64\"></line>\n <line x1=\"18.36\" y1=\"18.36\" x2=\"19.78\" y2=\"19.78\"></line>\n <line x1=\"1\" y1=\"12\" x2=\"3\" y2=\"12\"></line>\n <line x1=\"21\" y1=\"12\" x2=\"23\" y2=\"12\"></line>\n <line x1=\"4.22\" y1=\"19.78\" x2=\"5.64\" y2=\"18.36\"></line>\n <line x1=\"18.36\" y1=\"5.64\" x2=\"19.78\" y2=\"4.22\"></line>\n </svg>\n <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\">\n <path d=\"M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z\"></path>\n </svg>\n </div>\n </button>\n `,\n styles: [`\n .theme-toggle {\n background-color: var(--bg-tertiary);\n color: var(--text-primary);\n border: 1px solid var(--border-color);\n border-radius: 50%;\n cursor: pointer;\n width: 40px;\n height: 40px;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: all 0.2s ease;\n padding: 0;\n box-shadow: var(--shadow-sm);\n }\n \n .theme-toggle:hover {\n box-shadow: var(--shadow-md);\n }\n \n .theme-toggle:active {\n transform: translateY(0);\n }\n \n .theme-toggle-icon {\n width: 20px;\n height: 20px;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n \n .theme-toggle-small {\n width: 32px;\n height: 32px;\n }\n \n .theme-toggle-small .theme-toggle-icon {\n width: 16px;\n height: 16px;\n }\n \n .theme-toggle-large {\n width: 48px;\n height: 48px;\n }\n \n .theme-toggle-large .theme-toggle-icon {\n width: 24px;\n height: 24px;\n }\n `]\n})\nexport class ThemeToggleComponent {\n @Input() size: 'small' | 'medium' | 'large' = 'medium';\n\n constructor(private themeService: ThemeService) { }\n\n get isDarkTheme(): boolean {\n return this.themeService.isDarkTheme;\n }\n\n toggleTheme(): void {\n this.themeService.toggleTheme();\n }\n}","import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { ThemeService } from '../../services/theme.service';\nimport { ThemeToggleComponent } from './theme-toggle.component';\n\n@NgModule({\n imports: [\n CommonModule,\n ThemeToggleComponent\n ],\n exports: [\n ThemeToggleComponent\n ],\n providers: [\n ThemeService\n ]\n})\nexport class ThemeModule { }","import { Injectable } from \"@angular/core\"\nimport { Observable, Subject } from \"rxjs\"\nimport { ConfirmationDialogConfig, ConfirmationDialogResult } from \"./confirmation-dialog.model\"\n\n@Injectable({\n providedIn: \"root\",\n})\nexport class ConfirmationDialogService {\n private dialogSubject = new Subject<ConfirmationDialogConfig | null>()\n private resultSubject = new Subject<ConfirmationDialogResult>()\n private currentDialog: Observable<ConfirmationDialogResult> | null = null\n public dialog$ = this.dialogSubject.asObservable()\n\n confirm(config: ConfirmationDialogConfig): Observable<ConfirmationDialogResult> {\n const completeConfig: ConfirmationDialogConfig = {\n confirmText: \"Confirm\",\n cancelText: \"Cancel\",\n severity: \"info\",\n showCancel: true,\n ...config,\n }\n\n this.dialogSubject.next(completeConfig)\n this.currentDialog = this.resultSubject.asObservable()\n return this.currentDialog\n }\n\n success(\n title: string,\n message: string,\n options?: Partial<ConfirmationDialogConfig>,\n ): Observable<ConfirmationDialogResult> {\n return this.confirm({\n title,\n message,\n severity: \"success\",\n ...options,\n })\n }\n\n info(\n title: string,\n message: string,\n options?: Partial<ConfirmationDialogConfig>,\n ): Observable<ConfirmationDialogResult> {\n return this.confirm({\n title,\n message,\n severity: \"info\",\n ...options,\n })\n }\n\n warn(\n title: string,\n message: string,\n options?: Partial<ConfirmationDialogConfig>,\n ): Observable<ConfirmationDialogResult> {\n return this.confirm({\n title,\n message,\n severity: \"warn\",\n ...options,\n })\n }\n\n error(\n title: string,\n message: string,\n options?: Partial<ConfirmationDialogConfig>,\n ): Observable<ConfirmationDialogResult> {\n return this.confirm({\n title,\n message,\n severity: \"error\",\n ...options,\n })\n }\n\n close(result: ConfirmationDialogResult): void {\n this.resultSubject.next(result)\n this.dialogSubject.next(null)\n this.currentDialog = null\n }\n}\n\n","import {\n Component,\n OnInit,\n OnDestroy,\n HostListener,\n ChangeDetectorRef,\n ViewEncapsulation,\n Inject,\n} from \"@angular/core\"\nimport { CommonModule } from \"@angular/common\"\nimport { Subscription } from \"rxjs\"\nimport { trigger, transition, style, animate, AnimationEvent } from \"@angular/animations\"\nimport { ConfirmationDialogService } from \"./confirmation-dialog.service\"\nimport { ConfirmationDialogConfig, DialogSeverity } from \"./confirmation-dialog.model\"\nimport { DOCUMENT } from \"@angular/common\"\n\n@Component({\n selector: \"n-confirmation-dialog\",\n standalone: true,\n imports: [CommonModule],\n templateUrl: \"./confirmation-dialog.component.html\",\n styleUrls: [\"./confirmation-dialog.component.scss\"],\n encapsulation: ViewEncapsulation.None,\n animations: [\n trigger(\"dialogAnimation\", [\n transition(\":enter\", [\n style({ opacity: 0, transform: \"scale(0.95) translateY(10px)\" }),\n animate(\"250ms cubic-bezier(0.16, 1, 0.3, 1)\", style({ opacity: 1, transform: \"scale(1) translateY(0)\" })),\n ]),\n transition(\":leave\", [\n animate(\n \"180ms cubic-bezier(0.16, 1, 0.3, 1)\",\n style({ opacity: 0, transform: \"scale(0.95) translateY(10px)\" }),\n ),\n ]),\n ]),\n trigger(\"overlayAnimation\", [\n transition(\":enter\", [style({ opacity: 0 }), animate(\"200ms ease-out\", style({ opacity: 1 }))]),\n transition(\":leave\", [animate(\"150ms ease-in\", style({ opacity: 0 }))]),\n ]),\n ],\n})\nexport class ConfirmationDialogComponent implements OnInit, OnDestroy {\n visible = false\n config: ConfirmationDialogConfig | null = null\n private subscription: Subscription = new Subscription()\n private animationInProgress = false;\n\n constructor(\n private dialogService: ConfirmationDialogService,\n private cdr: ChangeDetectorRef,\n @Inject(DOCUMENT) private document: Document\n ) { }\n\n ngOnInit(): void {\n this.subscription.add(\n this.dialogService.dialog$.subscribe((config) => {\n this.config = config\n this.visible = !!config\n\n if (this.visible) {\n this.disableBodyScroll()\n } else {\n this.enableBodyScroll()\n }\n\n this.cdr.detectChanges()\n }),\n )\n }\n\n ngOnDestroy(): void {\n this.enableBodyScroll()\n this.subscription.unsubscribe()\n }\n\n confirm(): void {\n if (this.animationInProgress) return\n this.dialogService.close({ confirmed: true })\n }\n\n cancel(): void {\n if (this.animationInProgress) return\n this.dialogService.close({ confirmed: false })\n }\n\n @HostListener(\"document:keydown.escape\")\n onEscapeKey(): void {\n if (this.visible && !this.animationInProgress) {\n this.cancel()\n }\n }\n\n getIconClass(severity: DialogSeverity = \"info\"): string {\n switch (severity) {\n case \"success\":\n return \"pi pi-check-circle\"\n case \"warn\":\n return \"pi pi-exclamation-triangle\"\n case \"error\":\n return \"pi pi-times-circle\"\n case \"info\":\n default:\n return \"pi pi-info-circle\"\n }\n }\n\n stopPropagation(event: MouseEvent): void {\n event.stopPropagation()\n }\n\n onAnimationStart(event: AnimationEvent): void {\n this.animationInProgress = true\n }\n\n onAnimationDone(event: AnimationEvent): void {\n this.animationInProgress = false\n }\n\n private disableBodyScroll(): void {\n const scrollbarWidth = window.innerWidth - this.document.documentElement.clientWidth\n this.document.body.style.overflow = \"hidden\"\n this.document.body.style.paddingRight = `${scrollbarWidth}px`\n }\n\n private enableBodyScroll(): void {\n this.document.body.style.overflow = \"\"\n this.document.body.style.paddingRight = \"\"\n }\n}\n\n","<div *ngIf=\"visible\" class=\"dialog-overlay\" (click)=\"cancel()\" [@overlayAnimation]>\n <div class=\"dialog-container\" (click)=\"stopPropagation($event)\" [@dialogAnimation]>\n <div class=\"dialog-header\" [ngClass]=\"'dialog-' + (config?.severity || 'info')\">\n <div class=\"dialog-icon\">\n <i [class]=\"getIconClass(config?.severity)\"></i>\n </div>\n <h2 class=\"dialog-title\">{{ config?.title }}</h2>\n <button class=\"dialog-close\" (click)=\"cancel()\" aria-label=\"Close\">\n <i class=\"pi pi-times\"></i>\n </button>\n </div>\n\n <div class=\"dialog-content\">\n <p class=\"dialog-message\">{{ config?.message }}</p>\n </div>\n\n <div class=\"dialog-actions\">\n <ng-container *ngIf=\"config?.showCancel !== false\">\n <button class=\"dialog-button dialog-button-cancel\" (click)=\"cancel()\">\n {{ config?.cancelText || 'Cancel' }}\n </button>\n </ng-container>\n <button class=\"dialog-button dialog-button-confirm\" [ngClass]=\"'dialog-button-' + (config?.severity || 'info')\"\n (click)=\"confirm()\">\n {{ config?.confirmText || 'Confirm' }}\n </button>\n </div>\n </div>\n</div>","import { Directive, Input, Output, EventEmitter, HostListener } from \"@angular/core\"\nimport { ConfirmationDialogService } from \"./confirmation-dialog.service\"\nimport { ConfirmationDialogConfig, DialogSeverity } from \"./confirmation-dialog.model\"\nimport { firstValueFrom } from \"rxjs\"\n\n@Directive({\n selector: \"[nConfirmation]\",\n standalone: true,\n})\nexport class ConfirmationDirective {\n @Input() confirmTitle = \"Confirm Action\"\n @Input() confirmMessage = \"Are you sure you want to proceed?\"\n @Input() confirmText = \"Confirm\"\n @Input() cancelText = \"Cancel\"\n @Input() confirmSeverity: DialogSeverity = \"warn\"\n @Input() showCancel = true\n @Input() disabled = false\n\n @Output() confirmed = new EventEmitter<void>()\n @Output() cancelled = new EventEmitter<void>()\n @Output() dialogOpened = new EventEmitter<void>()\n\n constructor(private dialogService: ConfirmationDialogService) { }\n\n @HostListener(\"click\", [\"$event\"])\n async onClick(event: Event): Promise<void> {\n if (this.disabled) {\n return\n }\n\n event.preventDefault()\n event.stopPropagation()\n\n const config: ConfirmationDialogConfig = {\n title: this.confirmTitle,\n message: this.confirmMessage,\n confirmText: this.confirmText,\n cancelText: this.cancelText,\n severity: this.confirmSeverity,\n showCancel: this.showCancel,\n }\n\n try {\n this.dialogOpened.emit()\n const result = await firstValueFrom(this.dialogService.confirm(config))\n\n if (result.confirmed) {\n this.confirmed.emit()\n } else {\n this.cancelled.emit()\n }\n } catch (error) {\n this.cancelled.emit()\n console.error(\"Dialog error:\", error)\n }\n }\n}\n\n","import { Injectable, Inject } from \"@angular/core\"\nimport { DOCUMENT } from \"@angular/common\"\nimport { Observable, Subject } from \"rxjs\"\nimport { MessageDialogConfig } from \"./message-dialog.model\"\n\n@Injectable({\n providedIn: \"root\",\n})\nexport class MessageDialogService {\n private dialogSubject = new Subject<MessageDialogConfig | null>()\n private closeSubject = new Subject<void>()\n\n public dialog$ = this.dialogSubject.asObservable()\n public close$ = this.closeSubject.asObservable()\n\n private activeTimeoutId: number | null = null;\n\n constructor(@Inject(DOCUMENT) private document: Document) { }\n\n show(config: MessageDialogConfig): Observable<void> {\n\n if (this.activeTimeoutId !== null) {\n window.clearTimeout(this.activeTimeoutId)\n this.activeTimeoutId = null\n }\n\n const completeConfig: MessageDialogConfig = {\n showCloseButton: true,\n closeOnEscape: true,\n closeOnBackdropClick: true,\n closable: true,\n duration: 0,\n severity: \"info\",\n ...config,\n }\n\n this.dialogSubject.next(completeConfig)\n\n if (completeConfig.duration && completeConfig.duration > 0) {\n this.activeTimeoutId = window.setTimeout(() => {\n this.close()\n }, completeConfig.duration)\n }\n\n return this.close$\n }\n\n success(title: string, message: string, options?: Partial<MessageDialogConfig>): Observable<void> {\n return this.show({\n title,\n message,\n severity: \"success\",\n ...options,\n })\n }\n\n info(title: string, message: string, options?: Partial<MessageDialogConfig>): Observable<void> {\n return this.show({\n title,\n message,\n severity: \"info\",\n ...options,\n })\n }\n\n warn(title: string, message: string, options?: Partial<MessageDialogConfig>): Observable<void> {\n return this.show({\n title,\n message,\n severity: \"warn\",\n ...options,\n })\n }\n\n error(title: string, message: string, options?: Partial<MessageDialogConfig>): Observable<void> {\n return this.show({\n title,\n message,\n severity: \"error\",\n ...options,\n })\n }\n\n close(): void {\n if (this.activeTimeoutId !== null) {\n window.clearTimeout(this.activeTimeoutId)\n this.activeTimeoutId = null\n }\n\n this.dialogSubject.next(null)\n this.closeSubject.next()\n }\n}","import {\n Component,\n OnInit,\n OnDestroy,\n HostListener,\n ChangeDetectorRef,\n ViewEncapsulation,\n Inject,\n} from \"@angular/core\"\nimport { CommonModule } from \"@angular/common\"\nimport { Subscription } from \"rxjs\"\nimport { trigger, transition, style, animate, type AnimationEvent } from \"@angular/animations\"\nimport { DOCUMENT } from \"@angular/common\"\nimport { MessageDialogConfig, MessageSeverity } from \"./message-dialog.model\"\nimport { MessageDialogService } from \"./message-dialog.service\"\n@Component({\n selector: \"n-message-dialog\",\n standalone: true,\n imports: [CommonModule],\n templateUrl: \"./message-dialog.component.html\",\n styleUrls: [\"./message-dialog.component.scss\"],\n encapsulation: ViewEncapsulation.None,\n animations: [\n trigger(\"dialogAnimation\", [\n transition(\":enter\", [\n style({ opacity: 0, transform: \"scale(0.95) translateY(10px)\" }),\n animate(\"250ms cubic-bezier(0.16, 1, 0.3, 1)\", style({ opacity: 1, transform: \"scale(1) translateY(0)\" })),\n ]),\n transition(\":leave\", [\n animate(\n \"180ms cubic-bezier(0.16, 1, 0.3, 1)\",\n style({ opacity: 0, transform: \"scale(0.95) translateY(10px)\" }),\n ),\n ]),\n ]),\n trigger(\"overlayAnimation\", [\n transition(\":enter\", [style({ opacity: 0 }), animate(\"200ms ease-out\", style({ opacity: 1 }))]),\n transition(\":leave\", [animate(\"150ms ease-in\", style({ opacity: 0 }))]),\n ]),\n ],\n})\nexport class MessageDialogComponent implements OnInit, OnDestroy {\n visible = false\n config: MessageDialogConfig | null = null\n private subscription: Subscription = new Subscription()\n private animationInProgress = false;\n\n constructor(\n private dialogService: MessageDialogService,\n private cdr: ChangeDetectorRef,\n @Inject(DOCUMENT) private document: Document\n ) { }\n\n ngOnInit(): void {\n this.subscription.add(\n this.dialogService.dialog$.subscribe((config) => {\n this.config = config\n this.visible = !!config\n\n if (this.visible) {\n this.disableBodyScroll()\n } else {\n this.enableBodyScroll()\n }\n\n this.cdr.detectChanges()\n }),\n )\n }\n\n ngOnDestroy(): void {\n this.enableBodyScroll()\n this.subscription.unsubscribe()\n }\n\n close(): void {\n if (this.animationInProgress || !this.config?.closable) return\n this.dialogService.close()\n }\n\n @HostListener(\"document:keydown.escape\")\n onEscapeKey(): void {\n if (this.visible && !this.animationInProgress && this.config?.closeOnEscape && this.config?.closable) {\n this.close()\n }\n }\n\n onBackdropClick(): void {\n if (this.config?.closeOnBackdropClick && this.config?.closable) {\n this.close()\n }\n }\n\n getIconClass(severity: MessageSeverity = \"info\"): string {\n switch (severity) {\n case \"success\":\n return \"pi pi-check-circle\"\n case \"warn\":\n return \"pi pi-exclamation-triangle\"\n case \"error\":\n return \"pi pi-times-circle\"\n case \"info\":\n default:\n return \"pi pi-info-circle\"\n }\n }\n\n stopPropagation(event: MouseEvent): void {\n event.stopPropagation()\n }\n\n onAnimationStart(event: AnimationEvent): void {\n this.animationInProgress = true\n }\n\n onAnimationDone(event: AnimationEvent): void {\n this.animationInProgress = false\n }\n\n private disableBodyScroll(): void {\n const scrollbarWidth = window.innerWidth - this.document.documentElement.clientWidth\n this.document.body.style.overflow = \"hidden\"\n this.document.body.style.paddingRight = `${scrollbarWidth}px`\n }\n\n private enableBodyScroll(): void {\n this.document.body.style.overflow = \"\"\n this.document.body.style.paddingRight = \"\"\n }\n}","<div *ngIf=\"visible\" class=\"message-dialog-overlay\" (click)=\"onBackdropClick()\" [@overlayAnimation]>\n <div class=\"message-dialog-container\" (click)=\"stopPropagation($event)\" [@dialogAnimation]\n (@dialogAnimation.start)=\"onAnimationStart($event)\" (@dialogAnimation.done)=\"onAnimationDone($event)\">\n\n <div class=\"message-dialog-header\" [ngClass]=\"'message-dialog-' + (config?.severity || 'info')\">\n <div class=\"message-dialog-icon\">\n <i [class]=\"getIconClass(config?.severity)\"></i>\n </div>\n <h2 class=\"message-dialog-title\">{{ config?.title }}</h2>\n <button *ngIf=\"config?.showCloseButton && config?.closable\" class=\"message-dialog-close\" (click)=\"close()\"\n aria-label=\"Close\">\n <i class=\"pi pi-times\"></i>\n </button>\n </div>\n\n <div class=\"message-dialog-content\">\n <p class=\"message-dialog-message\">{{ config?.message }}</p>\n </div>\n </div>\n</div>","import { isPlatformBrowser } from '@angular/common';\nimport { Directive, ElementRef, HostListener, Input, OnInit, OnChanges, SimpleChanges, Renderer2, Inject, PLATFORM_ID } from '@angular/core';\n\ntype ButtonSeverity = 'primary' | 'secondary' | 'success' | 'info' | 'warn' | 'error';\ntype ButtonSize = 'sm' | 'md' | 'lg';\n\n@Directive({\n selector: '[nButton]',\n standalone: true,\n})\nexport class ButtonDirective implements OnInit, OnChanges {\n private isBrowser: boolean;\n\n @Input() severity: ButtonSeverity = 'primary';\n @Input() size: ButtonSize = 'md';\n @Input() label: string = '';\n @Input() outlined: boolean = false;\n @Input() rounded: boolean = false;\n @Input()\n get disabled(): boolean {\n return this._disabled;\n }\n set disabled(value: boolean) {\n this._disabled = value;\n this.updateDisabledState();\n }\n private _disabled: boolean = false;\n @Input() icon: string = '';\n @Input() iconPos: 'left' | 'right' = 'left';\n @Input()\n set loading(value: boolean) {\n if (value) {\n if (!this.originalLabel) {\n this.originalLabel = this.label;\n }\n } else {\n }\n this._loading = value;\n this.updateDisabledState();\n }\n get loading(): boolean {\n return this._loading;\n }\n private _loading: boolean = false;\n @Input() ripple: boolean = true;\n\n private loadingSpinner: HTMLElement | null = null;\n private initialMinWidth: string | null = null;\n private originalLabel: string | null = null;\n\n @HostListener('click', ['$event'])\n onClick(event: MouseEvent): void {\n if (!this.isBrowser || this.disabled || !this.ripple) return;\n\n this.createRippleEffect(event);\n }\n\n constructor(\n @Inject(PLATFORM_ID) platformId: Object,\n private el: ElementRef,\n private renderer: Renderer2\n ) {\n this.isBrowser = isPlatformBrowser(platformId)\n }\n\n ngOnInit(): void {\n if (this.isBrowser) {\n this.applyBaseStyles();\n this.applySeverityStyles();\n this.applySizeStyles();\n\n this.initialMinWidth = this.getMinWidth();\n\n if (this.outlined) {\n this.applyOutlinedStyles();\n }\n\n if (this.rounded) {\n this.applyRoundedStyles();\n }\n\n const isIconOnly = this.icon && !this.label;\n\n if (isIconOnly) {\n this.applyIconOnlyStyles();\n }\n\n this.addButtonContent();\n\n if (this.loading) {\n this.applyLoadingState();\n }\n\n this.addRippleStyles();\n\n const observer = new MutationObserver((mutations) => {\n mutations.forEach((mutation) => {\n if (mutation.attributeName === 'disabled') {\n const nativeDisabled = this.el.nativeElement.hasAttribute('disabled');\n if (nativeDisabled !== this._disabled) {\n this._disabled = nativeDisabled;\n this.updateDisabledState();\n }\n }\n });\n });\n\n observer.observe(this.el.nativeElement, {\n attributes: true,\n attributeFilter: ['disabled']\n });\n }\n }\n\n ngOnChanges(changes: SimpleChanges): void {\n if (this.isBrowser) {\n if (changes['loading']) {\n if (changes['loading'].currentValue) {\n this.applyLoadingState();\n } else {\n this.removeLoadingState();\n }\n }\n if (changes['disabled']) {\n this.updateDisabledState();\n }\n }\n }\n\n private createRippleEffect(event: MouseEvent): void {\n if (!this.isBrowser) return;\n\n const ripple = this.renderer.createElement('span');\n this.renderer.addClass(ripple, 'n-button-ripple');\n\n const rect = this.el.nativeElement.getBoundingClientRect();\n\n const size = Math.max(rect.width, rect.height) * 2;\n\n this.renderer.setStyle(ripple, 'width', `${size}px`);\n this.renderer.setStyle(ripple, 'height', `${size}px`);\n\n const x = event.clientX - rect.left - (size / 2);\n const y = event.clientY - rect.top - (size / 2);\n this.renderer.setStyle(ripple, 'left', `${x}px`);\n this.renderer.setStyle(ripple, 'top', `${y}px`);\n\n this.renderer.appendChild(this.el.nativeElement, ripple);\n\n setTimeout(() => {\n if (this.el.nativeElement.contains(ripple)) {\n this.renderer.removeChild(this.el.nativeElement, ripple);\n }\n }, 600);\n }\n\n private addRippleStyles(): void {\n if (!this.isBrowser) return;\n\n if (!document.getElementById('n-button-ripple-style')) {\n const style = this.renderer.createElement('style');\n style.id = 'n-button-ripple-style';\n const css = `\n .n-button-ripple {\n position: absolute;\n border-radius: 50%;\n background-color: rgba(255, 255, 255, 0.4);\n transform: scale(0);\n animation: n-button-ripple-animation 0.6s ease-out;\n pointer-events: none;\n }\n \n @keyframes n-button-ripple-animation {\n to {\n transform: scale(2);\n opacity: 0;\n }\n }\n `;\n this.renderer.appendChild(style, this.renderer.createText(css));\n this.renderer.appendChild(document.head, style);\n }\n }\n\n private applyBaseStyles(): void {\n const styles = {\n 'display': 'inline-flex',\n 'align-items': 'center',\n 'justify-content': 'center',\n 'font-family': 'Inter, sans-serif',\n 'font-weight': '500',\n 'border': 'none',\n 'cursor': 'pointer',\n 'transition': 'background-color 0.2s ease, color 0.2s ease, box-shadow 0.2s ease, width 0.2s ease, padding 0.2s ease, min-width 0.2s ease',\n 'position': 'relative',\n 'overflow': 'hidden',\n 'box-shadow': 'var(--shadow-sm)',\n 'text-transform': 'none',\n 'letter-spacing': '0.3px',\n 'border-radius': '6px',\n 'min-width': this.getMinWidth(),\n };\n\n Object.entries(styles).forEach(([property, value]) => {\n this.renderer.setStyle(this.el.nativeElement, property, value);\n });\n }\n\n private getMinWidth(): string {\n switch (this.size) {\n case 'sm': return '80px';\n case 'lg': return '120px';\n case 'md':\n default: return '100px';\n }\n }\n\n private applySeverityStyles(): void {\n let bgColor: string;\n let textColor: string;\n let hoverBgColor: string;\n\n switch (this.severity) {\n case 'primary':\n bgColor = 'var(--brand-primary)';\n textColor = '#ffffff';\n hoverBgColor = 'var(--brand-secondary)';\n break;\n case 'secondary':\n bgColor = 'var(--bg-tertiary)';\n textColor = 'var(--text-primary)';\n hoverBgColor = 'var(--bg-secondary)';\n break;\n case 'success':\n bgColor = 'var(--success-color)';\n textColor = '#ffffff';\n hoverBgColor = 'color-mix(in srgb, var(--success-color), #000 10%)';\n break;\n case 'info':\n bgColor = 'var(--info-color)';\n textColor = '#ffffff';\n hoverBgColor = 'color-mix(in srgb, var(--info-color), #000 10%)';\n break;\n case 'warn':\n bgColor = 'var(--warn-color)';\n textColor = '#ffffff';\n hoverBgColor = 'color-mix(in srgb, var(--warn-color), #000 10%)';\n break;\n case 'error':\n bgColor = 'var(--error-color)';\n textColor = '#ffffff';\n hoverBgColor = 'color-mix(in srgb, var(--error-color), #000 10%)';\n break;\n default:\n bgColor = 'var(--brand-primary)';\n textColor = '#ffffff';\n hoverBgColor = 'var(--brand-secondary)';\n }\n\n this.renderer.setStyle(this.el.nativeElement, 'background-color', bgColor);\n this.renderer.setStyle(this.el.nativeElement, 'color', textColor);\n\n this.renderer.listen(this.el.nativeElement, 'mouseenter', () => {\n if (!this.disabled) {\n this.renderer.setStyle(this.el.nativeElement, 'background-color', hoverBgColor);\n this.renderer.setStyle(this.el.nativeElement, 'box-shadow', 'var(--shadow-md)');\n }\n });\n\n this.renderer.listen(this.el.nativeElement, 'mouseleave', () => {\n if (!this.disabled) {\n this.renderer.setStyle(this.el.nativeElement, 'background-color', bgColor);\n this.renderer.setStyle(this.el.nativeElement, 'box-shadow', 'var(--shadow-sm)');\n }\n });\n }\n\n private applySizeStyles(): void {\n let padding: string;\n let fontSize: string;\n let height: string;\n\n switch (this.size) {\n case 'sm':\n padding = '0.5rem 0.75rem';\n fontSize = '0.75rem';\n height = '32px';\n break;\n case 'lg':\n padding = '0.75rem 1.5rem';\n fontSize = '1rem';\n height = '48px';\n break;\n case 'md':\n default:\n padding = '0.625rem 1.25rem';\n fontSize = '0.875rem';\n height = '40px';\n }\n\n this.renderer.setStyle(this.el.nativeElement, 'padding', padding);\n this.renderer.setStyle(this.el.nativeElement, 'font-size', fontSize);\n this.renderer.setStyle(this.el.nativeElement, 'height', height);\n }\n\n private applyOutlinedStyles(): void {\n let borderColor: strin