mesauth-angular 1.12.0 → 1.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -10,7 +10,7 @@ import { DomSanitizer } from '@angular/platform-browser';
|
|
|
10
10
|
import { DatePipe } from '@angular/common';
|
|
11
11
|
|
|
12
12
|
/** Current installed package version — keep in sync with package.json. */
|
|
13
|
-
const PACKAGE_VERSION = '1.
|
|
13
|
+
const PACKAGE_VERSION = '1.15.0';
|
|
14
14
|
/**
|
|
15
15
|
* Provides server-driven UI configuration loaded from the hosted manifest.
|
|
16
16
|
* Components read `labels()` and `features()` signals instead of hardcoded strings.
|
|
@@ -422,6 +422,7 @@ class ThemeService {
|
|
|
422
422
|
/** Observable kept for backward compatibility */
|
|
423
423
|
currentTheme$ = toObservable(this._currentTheme);
|
|
424
424
|
observer = null;
|
|
425
|
+
_settedTheme = signal(null, ...(ngDevMode ? [{ debugName: "_settedTheme" }] : /* istanbul ignore next */ []));
|
|
425
426
|
constructor() {
|
|
426
427
|
this.detectTheme();
|
|
427
428
|
this.startWatching();
|
|
@@ -430,6 +431,11 @@ class ThemeService {
|
|
|
430
431
|
this.stopWatching();
|
|
431
432
|
}
|
|
432
433
|
detectTheme() {
|
|
434
|
+
const fixed = this._settedTheme();
|
|
435
|
+
if (fixed !== null) {
|
|
436
|
+
this._currentTheme.set(fixed);
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
433
439
|
const html = document.documentElement;
|
|
434
440
|
const isDark = html.classList.contains('dark') ||
|
|
435
441
|
html.getAttribute('data-theme') === 'dark' ||
|
|
@@ -460,6 +466,10 @@ class ThemeService {
|
|
|
460
466
|
setTheme(theme) {
|
|
461
467
|
this._currentTheme.set(theme);
|
|
462
468
|
}
|
|
469
|
+
setFixTheme(theme) {
|
|
470
|
+
this._settedTheme.set(theme);
|
|
471
|
+
this._currentTheme.set(theme);
|
|
472
|
+
}
|
|
463
473
|
refreshTheme() {
|
|
464
474
|
this.detectTheme();
|
|
465
475
|
}
|
|
@@ -547,6 +557,7 @@ class UserProfileComponent {
|
|
|
547
557
|
avatarShape = computed(() => this.currentUser()?.avatarShape ?? this.inputAvatarShape(), ...(ngDevMode ? [{ debugName: "avatarShape" }] : /* istanbul ignore next */ []));
|
|
548
558
|
avatarFrame = computed(() => this.currentUser()?.avatarFrame ?? null, ...(ngDevMode ? [{ debugName: "avatarFrame" }] : /* istanbul ignore next */ []));
|
|
549
559
|
avatarRatio = computed(() => this.currentUser()?.avatarRatio ?? 'ar-11', ...(ngDevMode ? [{ debugName: "avatarRatio" }] : /* istanbul ignore next */ []));
|
|
560
|
+
givenStyle = computed(() => this.currentUser()?.givenColor || 'indigo', ...(ngDevMode ? [{ debugName: "givenStyle" }] : /* istanbul ignore next */ []));
|
|
550
561
|
authService = inject(MesAuthService);
|
|
551
562
|
router = inject(Router);
|
|
552
563
|
themeService = inject(ThemeService);
|
|
@@ -647,9 +658,20 @@ class UserProfileComponent {
|
|
|
647
658
|
onViewProfile() {
|
|
648
659
|
const config = this.authService.getConfig();
|
|
649
660
|
const baseUrl = config?.userBaseUrl || '';
|
|
650
|
-
window.location.href
|
|
661
|
+
const currentUrl = window.location.href;
|
|
662
|
+
this.openInNewTabIfSameOrigin(currentUrl, `${baseUrl}/profile`);
|
|
651
663
|
this.dropdownOpen.set(false);
|
|
652
664
|
}
|
|
665
|
+
openInNewTabIfSameOrigin(currentUrl, destinationUrl) {
|
|
666
|
+
// Check if current page URL starts with the destination URL
|
|
667
|
+
if (!destinationUrl.startsWith(currentUrl)) {
|
|
668
|
+
window.open(destinationUrl, "_blank", "noopener,noreferrer");
|
|
669
|
+
}
|
|
670
|
+
else {
|
|
671
|
+
// Optional: redirect in same tab
|
|
672
|
+
window.location.href = destinationUrl;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
653
675
|
onLogout() {
|
|
654
676
|
this.authService.logout().subscribe({
|
|
655
677
|
next: () => {
|
|
@@ -670,11 +692,11 @@ class UserProfileComponent {
|
|
|
670
692
|
this.notificationClick.emit();
|
|
671
693
|
}
|
|
672
694
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: UserProfileComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
673
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: UserProfileComponent, isStandalone: true, selector: "ma-user-profile", inputs: { inputAvatarShape: { classPropertyName: "inputAvatarShape", publicName: "inputAvatarShape", isSignal: true, isRequired: false, transformFunction: null }, showBell: { classPropertyName: "showBell", publicName: "showBell", isSignal: true, isRequired: false, transformFunction: null }, showApproval: { classPropertyName: "showApproval", publicName: "showApproval", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { notificationClick: "notificationClick", approvalClick: "approvalClick" }, host: { listeners: { "document:click": "onDocumentClick($event)" }, properties: { "class": "this.themeClass" } }, ngImport: i0, template: "<div class=\"user-profile-container\">\n @if (!currentUser()) {\n <!-- Not logged in -->\n <button class=\"login-btn\" (click)=\"onLogin()\">Login</button>\n } @else {\n <!-- Logged in -->\n <div class=\"user-header\">\n <!-- Notification Bell -->\n @if (showBell()) {\n <button class=\"notification-btn\" [class.has-unread]=\"unreadCount() > 0\" (click)=\"onNotificationClick()\" title=\"Notifications\" aria-label=\"Notifications\">\n <svg class=\"bell-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9\"/>\n <path d=\"M13.73 21a2 2 0 0 1-3.46 0\"/>\n </svg>\n @if (unreadCount() > 0) {\n <span class=\"badge\">{{ unreadCount() > 99 ? '99+' : unreadCount() }}</span>\n }\n </button>\n }\n \n\n <!-- Approval Button -->\n @if (showApproval()) {\n <button class=\"notification-btn\" [class.has-unread]=\"pendingApprovalCount() > 0\" (click)=\"onApprovalClick()\" title=\"Approvals\" aria-label=\"Approvals\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M9 11l3 3L22 4\"/>\n <path d=\"M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11\"/>\n </svg>\n @if (pendingApprovalCount() > 0) {\n <span class=\"badge\">{{ pendingApprovalCount() > 99 ? '99+' : pendingApprovalCount() }}</span>\n }\n </button>\n } \n\n <!-- User Avatar + Dropdown -->\n <div class=\"user-menu-wrapper\">\n <button class=\"user-menu-btn\" (click)=\"toggleDropdown()\" [attr.aria-label]=\"'User menu for ' + (currentUser().fullName || currentUser().userName)\" aria-haspopup=\"true\" [attr.aria-expanded]=\"dropdownOpen()\">\n <ma-avatar\n [src]=\"getAvatarUrl(currentUser())\"\n [alt]=\"currentUser().fullName || currentUser().userName || ''\"\n [initials]=\"getLastNameInitial(currentUser())\"\n [size]=\"navAvatarSize()\"\n [shape]=\"avatarShape()\"\n [ratio]=\"avatarRatio()\"\n [frame]=\"avatarFrame()\"\n [ring]=\"true\"\n [ringActive]=\"dropdownOpen()\">\n </ma-avatar>\n </button>\n\n @if (dropdownOpen()) {\n <div class=\"mes-dropdown-menu\">\n <!-- User info header -->\n <div class=\"mes-dropdown-header\">\n <div class=\"dropdown-avatar-wrap\">\n <ma-avatar\n [src]=\"getAvatarUrl(currentUser())\"\n [alt]=\"currentUser().fullName || currentUser().userName || ''\"\n [initials]=\"getLastNameInitial(currentUser())\"\n [size]=\"dropAvatarSize()\"\n [shape]=\"avatarShape()\"\n [ratio]=\"avatarRatio()\"\n [frame]=\"avatarFrame()\">\n </ma-avatar>\n </div>\n <div class=\"dropdown-info-col\">\n <div class=\"dropdown-user-info\">\n <span class=\"dropdown-user-name\">{{ currentUser().fullName || currentUser().userName }}</span>\n @if (currentUser().position || currentUser().department) {\n <span class=\"dropdown-user-sub\">{{ currentUser().position || currentUser().department }}</span>\n }\n </div>\n <div class=\"dropdown-user-actions\">\n <button class=\"icon-action profile-link\" (click)=\"onViewProfile()\" title=\"View Profile\" aria-label=\"View Profile\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2\"/><circle cx=\"12\" cy=\"7\" r=\"4\"/>\n </svg>\n </button>\n <button class=\"icon-action logout-item\" (click)=\"onLogout()\" title=\"Logout\" aria-label=\"Logout\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4\"/><polyline points=\"16 17 21 12 16 7\"/><line x1=\"21\" y1=\"12\" x2=\"9\" y2=\"12\"/>\n </svg>\n </button>\n </div>\n </div>\n </div>\n\n <div class=\"mes-dropdown-items\">\n <ng-content></ng-content>\n </div>\n </div>\n }\n </div>\n </div>\n }\n</div>\n", styles: [".user-profile-container{display:flex;align-items:center;gap:4px}.login-btn{padding:7px 18px;background-color:var(--primary-color);color:#fff;border:none;border-radius:8px;cursor:pointer;font-weight:500;font-size:13px;letter-spacing:.2px;transition:background-color .2s,transform .15s}.login-btn:hover{background-color:var(--primary-hover);transform:translateY(-1px)}.user-header{display:flex;align-items:center;gap:4px}.notification-btn{position:relative;background:none;border:none;cursor:pointer;padding:8px;border-radius:10px;color:var(--text-secondary);display:flex;align-items:center;justify-content:center;transition:color .2s,background-color .2s}.notification-btn:hover{background-color:var(--primary-light);color:var(--primary-color)}.notification-btn.has-unread{color:var(--primary-color)}.bell-icon{display:block;transition:transform .35s cubic-bezier(.34,1.56,.64,1)}.notification-btn:hover .bell-icon{transform:rotate(-20deg) scale(1.15)}.badge{position:absolute;top:2px;right:2px;background-color:var(--error-color);color:#fff;border-radius:10px;min-width:17px;height:17px;padding:0 4px;display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:700;line-height:1;box-shadow:0 0 0 2px var(--bg-primary);animation:badge-pop .25s cubic-bezier(.34,1.56,.64,1)}@keyframes badge-pop{0%{transform:scale(0)}to{transform:scale(1)}}.user-menu-wrapper{position:relative}.user-menu-btn{background:none;border:none;cursor:pointer;padding:4px;display:flex;align-items:center;justify-content:center;transition:transform .2s}.user-menu-btn:hover{transform:scale(1.06)}.mes-dropdown-menu{position:absolute;top:calc(100% + 10px);right:0;background:var(--bg-primary);border:1px solid var(--border-color);border-radius:14px;box-shadow:0 8px 32px var(--shadow-lg),0 2px 8px var(--shadow);min-width:220px;z-index:1000;overflow:hidden;animation:dropdown-in .16s cubic-bezier(.16,1,.3,1)}@keyframes dropdown-in{0%{opacity:0;transform:translateY(-8px) scale(.96)}to{opacity:1;transform:translateY(0) scale(1)}}.mes-dropdown-header{display:flex;align-items:center;gap:12px;padding:16px;background:var(--bg-secondary)}.dropdown-avatar-wrap{flex-shrink:0}.dropdown-user-info{display:flex;flex-direction:column;gap:3px;min-width:0}.dropdown-user-name{font-weight:600;font-size:14px;color:var(--text-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.dropdown-user-sub{font-size:11px;color:var(--primary-color);font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.mes-dropdown-divider{height:1px;background:var(--border-color)}.dropdown-info-col{display:flex;flex-direction:column;gap:6px;min-width:0;flex:1}.dropdown-user-actions{display:flex;align-items:center;justify-content:flex-end;gap:4px}.icon-action{display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;border:none;background:none;border-radius:8px;cursor:pointer;color:var(--text-secondary);transition:background-color .15s,color .15s,transform .15s}.icon-action:hover{transform:translateY(-1px)}.icon-action.profile-link{color:var(--primary-color)}.icon-action.profile-link:hover{background-color:var(--primary-light)}.icon-action.logout-item{color:var(--error-color)}.icon-action.logout-item:hover{background-color:var(--error-light)}.mes-dropdown-items:not(:empty){border-top:1px solid var(--border-color)}\n"], dependencies: [{ kind: "component", type: MaAvatarComponent, selector: "ma-avatar", inputs: ["src", "alt", "initials", "size", "shape", "frame", "ratio", "scale", "ring", "ringActive"] }] });
|
|
695
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: UserProfileComponent, isStandalone: true, selector: "ma-user-profile", inputs: { inputAvatarShape: { classPropertyName: "inputAvatarShape", publicName: "inputAvatarShape", isSignal: true, isRequired: false, transformFunction: null }, showBell: { classPropertyName: "showBell", publicName: "showBell", isSignal: true, isRequired: false, transformFunction: null }, showApproval: { classPropertyName: "showApproval", publicName: "showApproval", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { notificationClick: "notificationClick", approvalClick: "approvalClick" }, host: { listeners: { "document:click": "onDocumentClick($event)" }, properties: { "class": "this.themeClass" } }, ngImport: i0, template: "<div class=\"user-profile-container\">\n @if (!currentUser()) {\n <!-- Not logged in -->\n <button class=\"login-btn\" (click)=\"onLogin()\">Login</button>\n } @else {\n <!-- Logged in -->\n <div class=\"user-header\">\n <!-- Notification Bell -->\n @if (showBell()) {\n <button class=\"notification-btn\" [class.has-unread]=\"unreadCount() > 0\" (click)=\"onNotificationClick()\" title=\"Notifications\" aria-label=\"Notifications\">\n <svg class=\"bell-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9\"/>\n <path d=\"M13.73 21a2 2 0 0 1-3.46 0\"/>\n </svg>\n @if (unreadCount() > 0) {\n <span class=\"badge\">{{ unreadCount() > 99 ? '99+' : unreadCount() }}</span>\n }\n </button>\n }\n \n\n <!-- Approval Button -->\n @if (showApproval()) {\n <button class=\"notification-btn\" [class.has-unread]=\"pendingApprovalCount() > 0\" (click)=\"onApprovalClick()\" title=\"Approvals\" aria-label=\"Approvals\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M9 11l3 3L22 4\"/>\n <path d=\"M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11\"/>\n </svg>\n @if (pendingApprovalCount() > 0) {\n <span class=\"badge\">{{ pendingApprovalCount() > 99 ? '99+' : pendingApprovalCount() }}</span>\n }\n </button>\n } \n\n <!-- User Avatar + Dropdown -->\n <div class=\"user-menu-wrapper\">\n <button class=\"user-menu-btn\" (click)=\"toggleDropdown()\" [attr.aria-label]=\"'User menu for ' + (currentUser().fullName || currentUser().userName)\" aria-haspopup=\"true\" [attr.aria-expanded]=\"dropdownOpen()\">\n <ma-avatar\n [src]=\"getAvatarUrl(currentUser())\"\n [alt]=\"currentUser().fullName || currentUser().userName || ''\"\n [initials]=\"getLastNameInitial(currentUser())\"\n [size]=\"navAvatarSize()\"\n [shape]=\"avatarShape()\"\n [ratio]=\"avatarRatio()\"\n [frame]=\"avatarFrame()\" \n [ring]=\"true\"\n [ringActive]=\"dropdownOpen()\" /> \n </button>\n\n @if (dropdownOpen()) {\n <div class=\"mes-dropdown-menu\">\n <!-- User info header -->\n <div class=\"mes-dropdown-header\">\n <div class=\"dropdown-avatar-wrap\">\n <ma-avatar\n [src]=\"getAvatarUrl(currentUser())\"\n [alt]=\"currentUser().fullName || currentUser().userName || ''\"\n [initials]=\"getLastNameInitial(currentUser())\"\n [size]=\"dropAvatarSize()\"\n [shape]=\"avatarShape()\"\n [ratio]=\"avatarRatio()\"\n [frame]=\"avatarFrame()\"\n [scale]=\"1.5\" /> \n </div>\n <div class=\"dropdown-info-col\">\n <div class=\"dropdown-user-info\">\n <span class=\"dropdown-user-name\">{{ currentUser().fullName || currentUser().userName }}</span> \n <span class=\"dropdown-user-sub\">\n @if (currentUser().position || currentUser().department) {\n {{ currentUser().position || currentUser().department }} \n }\n @if (currentUser().givenTitle) {\n <span class=\"given-title-badge given-title\"\n [class]=\"'given-color-' + givenStyle()\">{{ currentUser().givenTitle }}</span>\n }\n </span>\n </div>\n <div class=\"dropdown-user-actions\">\n <button class=\"icon-action profile-link\" (click)=\"onViewProfile()\" title=\"View Profile\" aria-label=\"View Profile\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2\"/><circle cx=\"12\" cy=\"7\" r=\"4\"/>\n </svg>\n </button>\n <button class=\"icon-action logout-item\" (click)=\"onLogout()\" title=\"Logout\" aria-label=\"Logout\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4\"/><polyline points=\"16 17 21 12 16 7\"/><line x1=\"21\" y1=\"12\" x2=\"9\" y2=\"12\"/>\n </svg>\n </button>\n </div>\n </div>\n </div>\n\n <div class=\"mes-dropdown-items\">\n <ng-content></ng-content>\n </div>\n </div>\n }\n </div>\n </div>\n }\n</div>\n", styles: [".user-profile-container{display:flex;align-items:center;gap:4px}.login-btn{padding:7px 18px;background-color:var(--primary-color);color:#fff;border:none;border-radius:8px;cursor:pointer;font-weight:500;font-size:13px;letter-spacing:.2px;transition:background-color .2s,transform .15s}.login-btn:hover{background-color:var(--primary-hover);transform:translateY(-1px)}.user-header{display:flex;align-items:center;gap:4px}.notification-btn{position:relative;background:none;border:none;cursor:pointer;padding:8px;border-radius:10px;color:var(--text-secondary);display:flex;align-items:center;justify-content:center;transition:color .2s,background-color .2s}.notification-btn:hover{background-color:var(--primary-light);color:var(--primary-color)}.notification-btn.has-unread{color:var(--primary-color)}.bell-icon{display:block;transition:transform .35s cubic-bezier(.34,1.56,.64,1)}.notification-btn:hover .bell-icon{transform:rotate(-20deg) scale(1.15)}.badge{position:absolute;top:2px;right:2px;background-color:var(--error-color);color:#fff;border-radius:10px;min-width:17px;height:17px;padding:0 4px;display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:700;line-height:1;box-shadow:0 0 0 2px var(--bg-primary);animation:badge-pop .25s cubic-bezier(.34,1.56,.64,1)}@keyframes badge-pop{0%{transform:scale(0)}to{transform:scale(1)}}.user-menu-wrapper{position:relative}.user-menu-btn{background:none;border:none;cursor:pointer;padding:4px;display:flex;align-items:center;justify-content:center;transition:transform .2s}.user-menu-btn:hover{transform:scale(1.06)}.mes-dropdown-menu{position:absolute;top:calc(100% + 10px);right:0;background:var(--bg-primary);border:1px solid var(--border-color);border-radius:14px;box-shadow:0 8px 32px var(--shadow-lg),0 2px 8px var(--shadow);min-width:220px;z-index:1000;overflow:hidden;animation:dropdown-in .16s cubic-bezier(.16,1,.3,1)}@keyframes dropdown-in{0%{opacity:0;transform:translateY(-8px) scale(.96)}to{opacity:1;transform:translateY(0) scale(1)}}.mes-dropdown-header{display:flex;align-items:center;gap:12px;padding:16px;background:var(--bg-secondary)}.dropdown-avatar-wrap{flex-shrink:0}.mes-dropdown-header .dropdown-avatar-wrap{margin-right:5px}.dropdown-user-info{display:flex;flex-direction:column;gap:3px;min-width:0}.dropdown-user-name{font-weight:600;font-size:14px;color:var(--text-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-bottom:5px}.dropdown-user-sub{font-size:11px;color:var(--primary-color);font-weight:500;white-space:nowrap;text-overflow:ellipsis;margin-bottom:12px}.given-title{float:inline-end}.mes-dropdown-divider{height:1px;background:var(--border-color)}.dropdown-info-col{display:flex;flex-direction:column;gap:6px;min-width:0;flex:1}.dropdown-user-actions{display:flex;align-items:center;justify-content:flex-end;gap:4px}.icon-action{display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;border:none;background:none;border-radius:8px;cursor:pointer;color:var(--text-secondary);transition:background-color .15s,color .15s,transform .15s}.icon-action:hover{transform:translateY(-1px)}.icon-action.profile-link{color:var(--primary-color)}.icon-action.profile-link:hover{background-color:var(--primary-light)}.icon-action.logout-item{color:var(--error-color)}.icon-action.logout-item:hover{background-color:var(--error-light)}.mes-dropdown-items:not(:empty){border-top:1px solid var(--border-color)}\n"], dependencies: [{ kind: "component", type: MaAvatarComponent, selector: "ma-avatar", inputs: ["src", "alt", "initials", "size", "shape", "frame", "ratio", "scale", "ring", "ringActive"] }] });
|
|
674
696
|
}
|
|
675
697
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: UserProfileComponent, decorators: [{
|
|
676
698
|
type: Component,
|
|
677
|
-
args: [{ selector: 'ma-user-profile', imports: [MaAvatarComponent], template: "<div class=\"user-profile-container\">\n @if (!currentUser()) {\n <!-- Not logged in -->\n <button class=\"login-btn\" (click)=\"onLogin()\">Login</button>\n } @else {\n <!-- Logged in -->\n <div class=\"user-header\">\n <!-- Notification Bell -->\n @if (showBell()) {\n <button class=\"notification-btn\" [class.has-unread]=\"unreadCount() > 0\" (click)=\"onNotificationClick()\" title=\"Notifications\" aria-label=\"Notifications\">\n <svg class=\"bell-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9\"/>\n <path d=\"M13.73 21a2 2 0 0 1-3.46 0\"/>\n </svg>\n @if (unreadCount() > 0) {\n <span class=\"badge\">{{ unreadCount() > 99 ? '99+' : unreadCount() }}</span>\n }\n </button>\n }\n \n\n <!-- Approval Button -->\n @if (showApproval()) {\n <button class=\"notification-btn\" [class.has-unread]=\"pendingApprovalCount() > 0\" (click)=\"onApprovalClick()\" title=\"Approvals\" aria-label=\"Approvals\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M9 11l3 3L22 4\"/>\n <path d=\"M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11\"/>\n </svg>\n @if (pendingApprovalCount() > 0) {\n <span class=\"badge\">{{ pendingApprovalCount() > 99 ? '99+' : pendingApprovalCount() }}</span>\n }\n </button>\n } \n\n <!-- User Avatar + Dropdown -->\n <div class=\"user-menu-wrapper\">\n <button class=\"user-menu-btn\" (click)=\"toggleDropdown()\" [attr.aria-label]=\"'User menu for ' + (currentUser().fullName || currentUser().userName)\" aria-haspopup=\"true\" [attr.aria-expanded]=\"dropdownOpen()\">\n <ma-avatar\n [src]=\"getAvatarUrl(currentUser())\"\n [alt]=\"currentUser().fullName || currentUser().userName || ''\"\n [initials]=\"getLastNameInitial(currentUser())\"\n [size]=\"navAvatarSize()\"\n [shape]=\"avatarShape()\"\n [ratio]=\"avatarRatio()\"\n [frame]=\"avatarFrame()\"\n [ring]=\"true\"\n [ringActive]=\"dropdownOpen()\"
|
|
699
|
+
args: [{ selector: 'ma-user-profile', imports: [MaAvatarComponent], template: "<div class=\"user-profile-container\">\n @if (!currentUser()) {\n <!-- Not logged in -->\n <button class=\"login-btn\" (click)=\"onLogin()\">Login</button>\n } @else {\n <!-- Logged in -->\n <div class=\"user-header\">\n <!-- Notification Bell -->\n @if (showBell()) {\n <button class=\"notification-btn\" [class.has-unread]=\"unreadCount() > 0\" (click)=\"onNotificationClick()\" title=\"Notifications\" aria-label=\"Notifications\">\n <svg class=\"bell-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9\"/>\n <path d=\"M13.73 21a2 2 0 0 1-3.46 0\"/>\n </svg>\n @if (unreadCount() > 0) {\n <span class=\"badge\">{{ unreadCount() > 99 ? '99+' : unreadCount() }}</span>\n }\n </button>\n }\n \n\n <!-- Approval Button -->\n @if (showApproval()) {\n <button class=\"notification-btn\" [class.has-unread]=\"pendingApprovalCount() > 0\" (click)=\"onApprovalClick()\" title=\"Approvals\" aria-label=\"Approvals\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M9 11l3 3L22 4\"/>\n <path d=\"M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11\"/>\n </svg>\n @if (pendingApprovalCount() > 0) {\n <span class=\"badge\">{{ pendingApprovalCount() > 99 ? '99+' : pendingApprovalCount() }}</span>\n }\n </button>\n } \n\n <!-- User Avatar + Dropdown -->\n <div class=\"user-menu-wrapper\">\n <button class=\"user-menu-btn\" (click)=\"toggleDropdown()\" [attr.aria-label]=\"'User menu for ' + (currentUser().fullName || currentUser().userName)\" aria-haspopup=\"true\" [attr.aria-expanded]=\"dropdownOpen()\">\n <ma-avatar\n [src]=\"getAvatarUrl(currentUser())\"\n [alt]=\"currentUser().fullName || currentUser().userName || ''\"\n [initials]=\"getLastNameInitial(currentUser())\"\n [size]=\"navAvatarSize()\"\n [shape]=\"avatarShape()\"\n [ratio]=\"avatarRatio()\"\n [frame]=\"avatarFrame()\" \n [ring]=\"true\"\n [ringActive]=\"dropdownOpen()\" /> \n </button>\n\n @if (dropdownOpen()) {\n <div class=\"mes-dropdown-menu\">\n <!-- User info header -->\n <div class=\"mes-dropdown-header\">\n <div class=\"dropdown-avatar-wrap\">\n <ma-avatar\n [src]=\"getAvatarUrl(currentUser())\"\n [alt]=\"currentUser().fullName || currentUser().userName || ''\"\n [initials]=\"getLastNameInitial(currentUser())\"\n [size]=\"dropAvatarSize()\"\n [shape]=\"avatarShape()\"\n [ratio]=\"avatarRatio()\"\n [frame]=\"avatarFrame()\"\n [scale]=\"1.5\" /> \n </div>\n <div class=\"dropdown-info-col\">\n <div class=\"dropdown-user-info\">\n <span class=\"dropdown-user-name\">{{ currentUser().fullName || currentUser().userName }}</span> \n <span class=\"dropdown-user-sub\">\n @if (currentUser().position || currentUser().department) {\n {{ currentUser().position || currentUser().department }} \n }\n @if (currentUser().givenTitle) {\n <span class=\"given-title-badge given-title\"\n [class]=\"'given-color-' + givenStyle()\">{{ currentUser().givenTitle }}</span>\n }\n </span>\n </div>\n <div class=\"dropdown-user-actions\">\n <button class=\"icon-action profile-link\" (click)=\"onViewProfile()\" title=\"View Profile\" aria-label=\"View Profile\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2\"/><circle cx=\"12\" cy=\"7\" r=\"4\"/>\n </svg>\n </button>\n <button class=\"icon-action logout-item\" (click)=\"onLogout()\" title=\"Logout\" aria-label=\"Logout\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4\"/><polyline points=\"16 17 21 12 16 7\"/><line x1=\"21\" y1=\"12\" x2=\"9\" y2=\"12\"/>\n </svg>\n </button>\n </div>\n </div>\n </div>\n\n <div class=\"mes-dropdown-items\">\n <ng-content></ng-content>\n </div>\n </div>\n }\n </div>\n </div>\n }\n</div>\n", styles: [".user-profile-container{display:flex;align-items:center;gap:4px}.login-btn{padding:7px 18px;background-color:var(--primary-color);color:#fff;border:none;border-radius:8px;cursor:pointer;font-weight:500;font-size:13px;letter-spacing:.2px;transition:background-color .2s,transform .15s}.login-btn:hover{background-color:var(--primary-hover);transform:translateY(-1px)}.user-header{display:flex;align-items:center;gap:4px}.notification-btn{position:relative;background:none;border:none;cursor:pointer;padding:8px;border-radius:10px;color:var(--text-secondary);display:flex;align-items:center;justify-content:center;transition:color .2s,background-color .2s}.notification-btn:hover{background-color:var(--primary-light);color:var(--primary-color)}.notification-btn.has-unread{color:var(--primary-color)}.bell-icon{display:block;transition:transform .35s cubic-bezier(.34,1.56,.64,1)}.notification-btn:hover .bell-icon{transform:rotate(-20deg) scale(1.15)}.badge{position:absolute;top:2px;right:2px;background-color:var(--error-color);color:#fff;border-radius:10px;min-width:17px;height:17px;padding:0 4px;display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:700;line-height:1;box-shadow:0 0 0 2px var(--bg-primary);animation:badge-pop .25s cubic-bezier(.34,1.56,.64,1)}@keyframes badge-pop{0%{transform:scale(0)}to{transform:scale(1)}}.user-menu-wrapper{position:relative}.user-menu-btn{background:none;border:none;cursor:pointer;padding:4px;display:flex;align-items:center;justify-content:center;transition:transform .2s}.user-menu-btn:hover{transform:scale(1.06)}.mes-dropdown-menu{position:absolute;top:calc(100% + 10px);right:0;background:var(--bg-primary);border:1px solid var(--border-color);border-radius:14px;box-shadow:0 8px 32px var(--shadow-lg),0 2px 8px var(--shadow);min-width:220px;z-index:1000;overflow:hidden;animation:dropdown-in .16s cubic-bezier(.16,1,.3,1)}@keyframes dropdown-in{0%{opacity:0;transform:translateY(-8px) scale(.96)}to{opacity:1;transform:translateY(0) scale(1)}}.mes-dropdown-header{display:flex;align-items:center;gap:12px;padding:16px;background:var(--bg-secondary)}.dropdown-avatar-wrap{flex-shrink:0}.mes-dropdown-header .dropdown-avatar-wrap{margin-right:5px}.dropdown-user-info{display:flex;flex-direction:column;gap:3px;min-width:0}.dropdown-user-name{font-weight:600;font-size:14px;color:var(--text-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-bottom:5px}.dropdown-user-sub{font-size:11px;color:var(--primary-color);font-weight:500;white-space:nowrap;text-overflow:ellipsis;margin-bottom:12px}.given-title{float:inline-end}.mes-dropdown-divider{height:1px;background:var(--border-color)}.dropdown-info-col{display:flex;flex-direction:column;gap:6px;min-width:0;flex:1}.dropdown-user-actions{display:flex;align-items:center;justify-content:flex-end;gap:4px}.icon-action{display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;border:none;background:none;border-radius:8px;cursor:pointer;color:var(--text-secondary);transition:background-color .15s,color .15s,transform .15s}.icon-action:hover{transform:translateY(-1px)}.icon-action.profile-link{color:var(--primary-color)}.icon-action.profile-link:hover{background-color:var(--primary-light)}.icon-action.logout-item{color:var(--error-color)}.icon-action.logout-item:hover{background-color:var(--error-light)}.mes-dropdown-items:not(:empty){border-top:1px solid var(--border-color)}\n"] }]
|
|
678
700
|
}], ctorParameters: () => [], propDecorators: { inputAvatarShape: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputAvatarShape", required: false }] }], showBell: [{ type: i0.Input, args: [{ isSignal: true, alias: "showBell", required: false }] }], showApproval: [{ type: i0.Input, args: [{ isSignal: true, alias: "showApproval", required: false }] }], notificationClick: [{ type: i0.Output, args: ["notificationClick"] }], approvalClick: [{ type: i0.Output, args: ["approvalClick"] }], themeClass: [{
|
|
679
701
|
type: HostBinding,
|
|
680
702
|
args: ['class']
|
|
@@ -1174,6 +1196,16 @@ class MaUserComponent {
|
|
|
1174
1196
|
avatarShape = input('rounded', ...(ngDevMode ? [{ debugName: "avatarShape" }] : /* istanbul ignore next */ []));
|
|
1175
1197
|
showBell = input(true, ...(ngDevMode ? [{ debugName: "showBell" }] : /* istanbul ignore next */ []));
|
|
1176
1198
|
showApproval = input(true, ...(ngDevMode ? [{ debugName: "showApproval" }] : /* istanbul ignore next */ []));
|
|
1199
|
+
theme = input(null, ...(ngDevMode ? [{ debugName: "theme" }] : /* istanbul ignore next */ []));
|
|
1200
|
+
themeService = inject(ThemeService);
|
|
1201
|
+
constructor() {
|
|
1202
|
+
effect(() => {
|
|
1203
|
+
const t = this.theme();
|
|
1204
|
+
if (t) {
|
|
1205
|
+
this.themeService.setFixTheme(t);
|
|
1206
|
+
}
|
|
1207
|
+
});
|
|
1208
|
+
}
|
|
1177
1209
|
ngAfterViewInit() {
|
|
1178
1210
|
if (this.userProfile) {
|
|
1179
1211
|
this.userProfile.loadUnreadCount();
|
|
@@ -1191,18 +1223,18 @@ class MaUserComponent {
|
|
|
1191
1223
|
}
|
|
1192
1224
|
}
|
|
1193
1225
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: MaUserComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1194
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.9", type: MaUserComponent, isStandalone: true, selector: "ma-user", inputs: { avatarShape: { classPropertyName: "avatarShape", publicName: "avatarShape", isSignal: true, isRequired: false, transformFunction: null }, showBell: { classPropertyName: "showBell", publicName: "showBell", isSignal: true, isRequired: false, transformFunction: null }, showApproval: { classPropertyName: "showApproval", publicName: "showApproval", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "userProfile", first: true, predicate: UserProfileComponent, descendants: true }, { propertyName: "approvalPanel", first: true, predicate: MaApprovalPanelComponent, descendants: true }], ngImport: i0, template: "<ma-toast-container></ma-toast-container>\n<div class=\"user-header\">\n <ma-user-profile\n (notificationClick)=\"notificationPanel.open()\"\n (approvalClick)=\"approvalPanel.open()\"\n [inputAvatarShape]=\"avatarShape()\"\n [showBell]=\"showBell()\"\n [showApproval]=\"showApproval()\">\n <ng-content></ng-content>\n </ma-user-profile>\n</div>\n<ma-notification-panel #notificationPanel (notificationRead)=\"onNotificationRead()\"></ma-notification-panel>\n<ma-approval-panel #approvalPanel (approvalActioned)=\"onApprovalActioned()\"></ma-approval-panel>\n", styles: [".user-header{display:flex;justify-content:flex-end}\n"], dependencies: [{ kind: "component", type: ToastContainerComponent, selector: "ma-toast-container" }, { kind: "component", type: UserProfileComponent, selector: "ma-user-profile", inputs: ["inputAvatarShape", "showBell", "showApproval"], outputs: ["notificationClick", "approvalClick"] }, { kind: "component", type: NotificationPanelComponent, selector: "ma-notification-panel", outputs: ["notificationRead"] }, { kind: "component", type: MaApprovalPanelComponent, selector: "ma-approval-panel", outputs: ["approvalActioned"] }] });
|
|
1226
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.9", type: MaUserComponent, isStandalone: true, selector: "ma-user", inputs: { avatarShape: { classPropertyName: "avatarShape", publicName: "avatarShape", isSignal: true, isRequired: false, transformFunction: null }, showBell: { classPropertyName: "showBell", publicName: "showBell", isSignal: true, isRequired: false, transformFunction: null }, showApproval: { classPropertyName: "showApproval", publicName: "showApproval", isSignal: true, isRequired: false, transformFunction: null }, theme: { classPropertyName: "theme", publicName: "theme", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "userProfile", first: true, predicate: UserProfileComponent, descendants: true }, { propertyName: "approvalPanel", first: true, predicate: MaApprovalPanelComponent, descendants: true }], ngImport: i0, template: "<ma-toast-container></ma-toast-container>\n<div class=\"user-header\">\n <ma-user-profile\n (notificationClick)=\"notificationPanel.open()\"\n (approvalClick)=\"approvalPanel.open()\"\n [inputAvatarShape]=\"avatarShape()\"\n [showBell]=\"showBell()\"\n [showApproval]=\"showApproval()\">\n <ng-content></ng-content>\n </ma-user-profile>\n</div>\n<ma-notification-panel #notificationPanel (notificationRead)=\"onNotificationRead()\"></ma-notification-panel>\n<ma-approval-panel #approvalPanel (approvalActioned)=\"onApprovalActioned()\"></ma-approval-panel>\n", styles: [".user-header{display:flex;justify-content:flex-end}\n"], dependencies: [{ kind: "component", type: ToastContainerComponent, selector: "ma-toast-container" }, { kind: "component", type: UserProfileComponent, selector: "ma-user-profile", inputs: ["inputAvatarShape", "showBell", "showApproval"], outputs: ["notificationClick", "approvalClick"] }, { kind: "component", type: NotificationPanelComponent, selector: "ma-notification-panel", outputs: ["notificationRead"] }, { kind: "component", type: MaApprovalPanelComponent, selector: "ma-approval-panel", outputs: ["approvalActioned"] }] });
|
|
1195
1227
|
}
|
|
1196
1228
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: MaUserComponent, decorators: [{
|
|
1197
1229
|
type: Component,
|
|
1198
1230
|
args: [{ selector: 'ma-user', imports: [ToastContainerComponent, UserProfileComponent, NotificationPanelComponent, MaApprovalPanelComponent], template: "<ma-toast-container></ma-toast-container>\n<div class=\"user-header\">\n <ma-user-profile\n (notificationClick)=\"notificationPanel.open()\"\n (approvalClick)=\"approvalPanel.open()\"\n [inputAvatarShape]=\"avatarShape()\"\n [showBell]=\"showBell()\"\n [showApproval]=\"showApproval()\">\n <ng-content></ng-content>\n </ma-user-profile>\n</div>\n<ma-notification-panel #notificationPanel (notificationRead)=\"onNotificationRead()\"></ma-notification-panel>\n<ma-approval-panel #approvalPanel (approvalActioned)=\"onApprovalActioned()\"></ma-approval-panel>\n", styles: [".user-header{display:flex;justify-content:flex-end}\n"] }]
|
|
1199
|
-
}], propDecorators: { userProfile: [{
|
|
1231
|
+
}], ctorParameters: () => [], propDecorators: { userProfile: [{
|
|
1200
1232
|
type: ViewChild,
|
|
1201
1233
|
args: [UserProfileComponent]
|
|
1202
1234
|
}], approvalPanel: [{
|
|
1203
1235
|
type: ViewChild,
|
|
1204
1236
|
args: [MaApprovalPanelComponent]
|
|
1205
|
-
}], avatarShape: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarShape", required: false }] }], showBell: [{ type: i0.Input, args: [{ isSignal: true, alias: "showBell", required: false }] }], showApproval: [{ type: i0.Input, args: [{ isSignal: true, alias: "showApproval", required: false }] }] } });
|
|
1237
|
+
}], avatarShape: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarShape", required: false }] }], showBell: [{ type: i0.Input, args: [{ isSignal: true, alias: "showBell", required: false }] }], showApproval: [{ type: i0.Input, args: [{ isSignal: true, alias: "showApproval", required: false }] }], theme: [{ type: i0.Input, args: [{ isSignal: true, alias: "theme", required: false }] }] } });
|
|
1206
1238
|
|
|
1207
1239
|
class NotificationBadgeComponent {
|
|
1208
1240
|
notificationClick = output();
|