@masterteam/delegations 0.0.55 → 0.0.56

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.
@@ -2,22 +2,22 @@ import * as i1 from '@angular/common';
2
2
  import { DOCUMENT, CommonModule, Location } from '@angular/common';
3
3
  import * as i0 from '@angular/core';
4
4
  import { InjectionToken, makeEnvironmentProviders, inject, Injectable, signal, computed, input, ChangeDetectionStrategy, Component, output, DestroyRef, viewChild, effect, model, untracked, booleanAttribute, numberAttribute, linkedSignal } from '@angular/core';
5
+ import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
5
6
  import { TranslocoService, TranslocoDirective } from '@jsverse/transloco';
7
+ import { Actions, Store, ofActionSuccessful, ofActionDispatched, Action, Selector, State, select } from '@ngxs/store';
6
8
  import { Avatar } from '@masterteam/components/avatar';
7
9
  import { ModalService } from '@masterteam/components/modal';
10
+ import { ToastService } from '@masterteam/components/toast';
8
11
  import { Icon } from '@masterteam/icons';
9
12
  import { Popover } from 'primeng/popover';
10
- import { Actions, Store, ofActionSuccessful, ofActionDispatched, Action, Selector, State, select } from '@ngxs/store';
11
- import { switchMap, from, EMPTY, finalize, shareReplay, filter, catchError, throwError } from 'rxjs';
13
+ import { EMPTY, switchMap, from, finalize, shareReplay, filter, catchError, throwError } from 'rxjs';
12
14
  import { HttpContextToken, HttpContext, HttpClient, HttpParams, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
13
15
  import { handleApiRequest, REQUEST_CONTEXT, UserSearchFieldConfig, TextareaFieldConfig, DateFieldConfig, RadioButtonFieldConfig, MultiSelectFieldConfig, ToggleFieldConfig, ValidatorConfig } from '@masterteam/components';
14
16
  import { Button } from '@masterteam/components/button';
15
17
  import { ModalRef } from '@masterteam/components/dialog';
16
18
  import { EntityPreview } from '@masterteam/components/entities';
17
- import { ToastService } from '@masterteam/components/toast';
18
19
  import { RouterLink, ActivatedRoute, Router, NavigationEnd, RouterOutlet } from '@angular/router';
19
20
  import { Chip } from '@masterteam/components/chip';
20
- import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
21
21
  import { Page } from '@masterteam/components/page';
22
22
  import { Breadcrumb } from '@masterteam/components/breadcrumb';
23
23
  import { Card } from '@masterteam/components/card';
@@ -32,6 +32,54 @@ import { TextField } from '@masterteam/components/text-field';
32
32
  import * as i4 from 'primeng/skeleton';
33
33
  import { SkeletonModule } from 'primeng/skeleton';
34
34
 
35
+ /** Fetch the active delegations the current user may start (user/activedelegations). */
36
+ class LoadDelegationCandidates {
37
+ page;
38
+ pageSize;
39
+ append;
40
+ static type = '[DelegationSession] Load Candidates';
41
+ constructor(page = 1, pageSize = 25, append = false) {
42
+ this.page = page;
43
+ this.pageSize = pageSize;
44
+ this.append = append;
45
+ }
46
+ }
47
+ /** Start a delegated session for the given assignment row. */
48
+ class StartDelegationSession {
49
+ delegation;
50
+ static type = '[DelegationSession] Start';
51
+ constructor(delegation) {
52
+ this.delegation = delegation;
53
+ }
54
+ }
55
+ /**
56
+ * Re-mint the session the user was in before they reloaded the page. Dispatched
57
+ * by the state itself from the persisted delegation id — never a token.
58
+ */
59
+ class ResumeDelegationSession {
60
+ delegation;
61
+ static type = '[DelegationSession] Resume';
62
+ constructor(delegation) {
63
+ this.delegation = delegation;
64
+ }
65
+ }
66
+ /** End the current delegated session. Client-local only (doc 05). */
67
+ class EndDelegationSession {
68
+ reason;
69
+ static type = '[DelegationSession] End';
70
+ constructor(reason = 'Manual') {
71
+ this.reason = reason;
72
+ }
73
+ }
74
+ /** Switch directly from the current session to another delegation. */
75
+ class SwitchDelegationSession {
76
+ delegation;
77
+ static type = '[DelegationSession] Switch';
78
+ constructor(delegation) {
79
+ this.delegation = delegation;
80
+ }
81
+ }
82
+
35
83
  const DEFAULT_CONFIG = {
36
84
  resolveApplicationApiBaseUrl: () => '',
37
85
  resolvePromptNamespace: () => ({
@@ -58,7 +106,7 @@ function withDelegatedRuntime(context = new HttpContext()) {
58
106
  return context.set(DELEGATED_RUNTIME_REQUEST, true);
59
107
  }
60
108
 
61
- const STORAGE_KEY = 'mt.delegation.prompt-receipts.v1';
109
+ const STORAGE_KEY$1 = 'mt.delegation.prompt-receipts.v1';
62
110
  const MAX_RECEIPTS = 100;
63
111
  class DelegationPromptReceiptService {
64
112
  document = inject(DOCUMENT);
@@ -103,7 +151,7 @@ class DelegationPromptReceiptService {
103
151
  }
104
152
  read() {
105
153
  try {
106
- const raw = this.document.defaultView?.localStorage.getItem(STORAGE_KEY);
154
+ const raw = this.document.defaultView?.localStorage.getItem(STORAGE_KEY$1);
107
155
  const parsed = raw ? JSON.parse(raw) : [];
108
156
  return Array.isArray(parsed)
109
157
  ? parsed.filter((item) => typeof item === 'string')
@@ -116,7 +164,7 @@ class DelegationPromptReceiptService {
116
164
  write(receipts) {
117
165
  try {
118
166
  const bounded = [...new Set(receipts)].slice(-MAX_RECEIPTS);
119
- this.document.defaultView?.localStorage.setItem(STORAGE_KEY, JSON.stringify(bounded));
167
+ this.document.defaultView?.localStorage.setItem(STORAGE_KEY$1, JSON.stringify(bounded));
120
168
  }
121
169
  catch {
122
170
  // Storage can be unavailable; the in-memory claim still coalesces dialogs.
@@ -130,49 +178,99 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
130
178
  args: [{ providedIn: 'root' }]
131
179
  }] });
132
180
 
133
- /** Fetch the active delegations the current user may start (user/activedelegations). */
134
- class LoadDelegationCandidates {
135
- page;
136
- pageSize;
137
- append;
138
- static type = '[DelegationSession] Load Candidates';
139
- constructor(page = 1, pageSize = 25, append = false) {
140
- this.page = page;
141
- this.pageSize = pageSize;
142
- this.append = append;
143
- }
144
- }
145
- /** Start a delegated session for the given assignment row. */
146
- class StartDelegationSession {
147
- delegation;
148
- static type = '[DelegationSession] Start';
149
- constructor(delegation) {
150
- this.delegation = delegation;
151
- }
152
- }
153
- /** End the current delegated session. Client-local only (doc 05). */
154
- class EndDelegationSession {
155
- reason;
156
- static type = '[DelegationSession] End';
157
- constructor(reason = 'Manual') {
158
- this.reason = reason;
159
- }
160
- }
161
- /** Switch directly from the current session to another delegation. */
162
- class SwitchDelegationSession {
163
- delegation;
164
- static type = '[DelegationSession] Switch';
165
- constructor(delegation) {
166
- this.delegation = delegation;
167
- }
168
- }
169
-
170
181
  var DelegationSessionActionKey;
171
182
  (function (DelegationSessionActionKey) {
172
183
  DelegationSessionActionKey["LoadCandidates"] = "loadCandidates";
173
184
  DelegationSessionActionKey["StartSession"] = "startSession";
174
185
  })(DelegationSessionActionKey || (DelegationSessionActionKey = {}));
175
186
 
187
+ const STORAGE_KEY = 'mt.delegation.session-resume.v1';
188
+ /**
189
+ * Lets a delegated session survive a page reload **without persisting the
190
+ * token**.
191
+ *
192
+ * Doc 05 is unambiguous: the delegation access token is memory-only, and it
193
+ * stays that way — this store keeps a single opaque `delegationId` plus the
194
+ * owning user/app/tenant namespace. On the next boot the runtime calls
195
+ * `POST identity/delegations/{id}/session` again and mints a *fresh* token,
196
+ * which is what doc 09 already prescribes for restarting a session ("use the
197
+ * newly returned token because the scope hash or delegation version may have
198
+ * changed"). The backend re-authorizes from scratch every time, so a delegation
199
+ * that expired, was cancelled, or had its scope revoked while the tab was shut
200
+ * simply fails to resume and the user stays themselves.
201
+ *
202
+ * Never holds a token, a candidate record, a name, or an email.
203
+ */
204
+ class DelegationSessionResumeStore {
205
+ document = inject(DOCUMENT);
206
+ config = inject(DELEGATION_RUNTIME_CONFIG);
207
+ remember(delegationId) {
208
+ this.write({ ns: this.namespace(), delegationId });
209
+ }
210
+ /**
211
+ * The delegation to resume for the *current* user/app/tenant, or `null`. A
212
+ * marker left by a different actor is dropped rather than honoured, so
213
+ * signing in as someone else on a shared browser never resumes their
214
+ * delegation.
215
+ */
216
+ pending() {
217
+ const marker = this.read();
218
+ if (!marker) {
219
+ return null;
220
+ }
221
+ if (marker.ns !== this.namespace()) {
222
+ this.clear();
223
+ return null;
224
+ }
225
+ return marker.delegationId;
226
+ }
227
+ clear() {
228
+ try {
229
+ this.storage()?.removeItem(STORAGE_KEY);
230
+ }
231
+ catch {
232
+ // Storage can be unavailable (private mode, blocked cookies).
233
+ }
234
+ }
235
+ namespace() {
236
+ const { userId, applicationKey, tenantKey } = this.config.resolvePromptNamespace();
237
+ return [userId, applicationKey, tenantKey ?? ''].join('|');
238
+ }
239
+ read() {
240
+ try {
241
+ const raw = this.storage()?.getItem(STORAGE_KEY);
242
+ const parsed = raw ? JSON.parse(raw) : null;
243
+ if (typeof parsed !== 'object' ||
244
+ parsed === null ||
245
+ typeof parsed.ns !== 'string' ||
246
+ typeof parsed.delegationId !== 'number') {
247
+ return null;
248
+ }
249
+ return parsed;
250
+ }
251
+ catch {
252
+ return null;
253
+ }
254
+ }
255
+ write(marker) {
256
+ try {
257
+ this.storage()?.setItem(STORAGE_KEY, JSON.stringify(marker));
258
+ }
259
+ catch {
260
+ // Storage can be unavailable; delegated mode then simply ends on reload.
261
+ }
262
+ }
263
+ storage() {
264
+ return this.document.defaultView?.localStorage ?? null;
265
+ }
266
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationSessionResumeStore, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
267
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationSessionResumeStore, providedIn: 'root' });
268
+ }
269
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationSessionResumeStore, decorators: [{
270
+ type: Injectable,
271
+ args: [{ providedIn: 'root' }]
272
+ }] });
273
+
176
274
  /** Package-private: raw delegated credentials never enter NGXS or public models. */
177
275
  class DelegationTokenVault {
178
276
  value = signal(null, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
@@ -255,6 +353,9 @@ let DelegationSessionState = class DelegationSessionState {
255
353
  * or open the availability prompt — after sign-out.
256
354
  */
257
355
  candidatesGeneration = 0;
356
+ resumeStore = inject(DelegationSessionResumeStore);
357
+ /** One resume attempt per page load — a dead delegation must not retry. */
358
+ resumeAttempted = false;
258
359
  constructor() {
259
360
  this.actions$
260
361
  .pipe(ofActionSuccessful(GatewayLoginSuccessShell))
@@ -323,6 +424,9 @@ let DelegationSessionState = class DelegationSessionState {
323
424
  (response.data?.totalCount ?? 0) <= pageSize) {
324
425
  queueMicrotask(() => this.store.dispatch(new EndDelegationSession('ScopeChanged')));
325
426
  }
427
+ if (!active && page === 1) {
428
+ this.tryResume(candidates);
429
+ }
326
430
  return {
327
431
  candidates,
328
432
  candidatesPage: response.data?.page ?? page,
@@ -332,6 +436,39 @@ let DelegationSessionState = class DelegationSessionState {
332
436
  },
333
437
  });
334
438
  }
439
+ /**
440
+ * Re-mints the delegated session the user was in before they reloaded.
441
+ *
442
+ * The token itself was never persisted (doc 05); only the delegation id was,
443
+ * so this is a full `POST .../session` and the backend re-authorizes from
444
+ * scratch. Attempted at most once per page load — a delegation that has since
445
+ * expired or been revoked must not retry on every candidate refresh.
446
+ */
447
+ tryResume(candidates) {
448
+ if (this.resumeAttempted) {
449
+ return;
450
+ }
451
+ const delegationId = this.resumeStore.pending();
452
+ if (delegationId == null) {
453
+ return;
454
+ }
455
+ const row = candidates.find((candidate) => candidate.delegationId === delegationId);
456
+ this.resumeAttempted = true;
457
+ if (!row) {
458
+ // No longer startable (expired, cancelled, scope revoked) — forget it.
459
+ this.resumeStore.clear();
460
+ return;
461
+ }
462
+ queueMicrotask(() => this.store.dispatch(new ResumeDelegationSession(row)).subscribe({
463
+ error: () => this.resumeStore.clear(),
464
+ }));
465
+ }
466
+ resume(ctx, { delegation }) {
467
+ if (ctx.getState().active) {
468
+ return EMPTY;
469
+ }
470
+ return this.startSession(ctx, delegation, null, 'ResumeSession');
471
+ }
335
472
  start(ctx, { delegation }) {
336
473
  const previous = ctx.getState().active;
337
474
  return this.startSession(ctx, delegation, previous, 'StartSession');
@@ -352,6 +489,9 @@ let DelegationSessionState = class DelegationSessionState {
352
489
  delegationVersion: response.data.delegationVersion,
353
490
  };
354
491
  this.scheduleExpiry(active);
492
+ // Id only — see `DelegationSessionResumeStore`. Never the token.
493
+ this.resumeStore.remember(delegation.delegationId);
494
+ this.resumeAttempted = true;
355
495
  return { active };
356
496
  },
357
497
  }).pipe(switchMap(() => from(Promise.resolve(this.runtime.onContextChanged({
@@ -364,6 +504,10 @@ let DelegationSessionState = class DelegationSessionState {
364
504
  // Client-local only — there is no server end-session endpoint (doc 05).
365
505
  const previous = ctx.getState().active;
366
506
  const clearCandidates = shouldClearCandidates(reason);
507
+ // Whatever the reason, delegated mode is over — a reload must not bring it
508
+ // back. Also blocks a resume that is still queued behind this action.
509
+ this.resumeStore.clear();
510
+ this.resumeAttempted = true;
367
511
  // Logout / user / tenant / app switch must always drop the candidate list,
368
512
  // even when no delegated session was ever started. Leaving it behind kept
369
513
  // the previous actor's delegators in memory across sign-out, which is both
@@ -416,6 +560,9 @@ let DelegationSessionState = class DelegationSessionState {
416
560
  __decorate$1([
417
561
  Action(LoadDelegationCandidates)
418
562
  ], DelegationSessionState.prototype, "loadCandidates", null);
563
+ __decorate$1([
564
+ Action(ResumeDelegationSession)
565
+ ], DelegationSessionState.prototype, "resume", null);
419
566
  __decorate$1([
420
567
  Action(StartDelegationSession)
421
568
  ], DelegationSessionState.prototype, "start", null);
@@ -459,7 +606,7 @@ DelegationSessionState = __decorate$1([
459
606
  ], DelegationSessionState);
460
607
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationSessionState, decorators: [{
461
608
  type: Injectable
462
- }], ctorParameters: () => [], propDecorators: { loadCandidates: [], start: [], end: [], switch: [] } });
609
+ }], ctorParameters: () => [], propDecorators: { loadCandidates: [], resume: [], start: [], end: [], switch: [] } });
463
610
 
464
611
  class DelegationSessionFacade {
465
612
  store = inject(Store);
@@ -478,8 +625,15 @@ class DelegationSessionFacade {
478
625
  // ---------------------------------------------------------------------------
479
626
  /** On-behalf-of (delegator). */
480
627
  onBehalfOf = computed(() => this.active()?.delegation.delegator ?? null, ...(ngDevMode ? [{ debugName: "onBehalfOf" }] : /* istanbul ignore next */ []));
481
- /** Executed-by (actual logged-in / delegated user). */
482
- executedBy = computed(() => this.active()?.delegation.delegatedUser ?? null, ...(ngDevMode ? [{ debugName: "executedBy" }] : /* istanbul ignore next */ []));
628
+ /**
629
+ * Executed-by (the real signed-in user), known whether or not a session is
630
+ * active: every assigned row carries the current user as its `delegatedUser`,
631
+ * so the account switcher can list "you" alongside the delegators without the
632
+ * package depending on the host's auth state.
633
+ */
634
+ executedBy = computed(() => this.active()?.delegation.delegatedUser ??
635
+ this.candidates()[0]?.delegatedUser ??
636
+ null, ...(ngDevMode ? [{ debugName: "executedBy" }] : /* istanbul ignore next */ []));
483
637
  hasCandidates = computed(() => this.candidates().length > 0, ...(ngDevMode ? [{ debugName: "hasCandidates" }] : /* istanbul ignore next */ []));
484
638
  hasMoreCandidates = computed(() => this.candidates().length < this.candidatesTotalCount(), ...(ngDevMode ? [{ debugName: "hasMoreCandidates" }] : /* istanbul ignore next */ []));
485
639
  isStarting = computed(() => this.loadingActive().includes(DelegationSessionActionKey.StartSession), ...(ngDevMode ? [{ debugName: "isStarting" }] : /* istanbul ignore next */ []));
@@ -591,11 +745,11 @@ class StartSessionDialog {
591
745
  this.ref.close(false);
592
746
  }
593
747
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: StartSessionDialog, deps: [], target: i0.ɵɵFactoryTarget.Component });
594
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.8", type: StartSessionDialog, isStandalone: true, selector: "mt-start-session-dialog", inputs: { delegation: { classPropertyName: "delegation", publicName: "delegation", isSignal: true, isRequired: true, transformFunction: null }, intent: { classPropertyName: "intent", publicName: "intent", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\n <div\n [class]=\"modal.contentClass + ' flex flex-col gap-4 overflow-y-auto p-5'\"\n >\n <mt-entity-preview\n [data]=\"userEntity(delegation().delegator)\"\n ></mt-entity-preview>\n\n <p class=\"text-sm leading-relaxed text-surface-600\">\n {{ t(bodyKey()) }}\n </p>\n </div>\n\n <div [class]=\"modal.footerClass\">\n <mt-button\n variant=\"outlined\"\n color=\"secondary\"\n [label]=\"t('delegations.common.cancel')\"\n [disabled]=\"isBusy()\"\n (click)=\"cancel()\"\n ></mt-button>\n <mt-button\n color=\"primary\"\n icon=\"user.users-check\"\n [label]=\"t(confirmLabelKey())\"\n [loading]=\"isBusy()\"\n (click)=\"confirm()\"\n ></mt-button>\n </div>\n</ng-container>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs", "ariaLabel"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: EntityPreview, selector: "mt-entity-preview", inputs: ["data", "attachmentShape"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
748
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.8", type: StartSessionDialog, isStandalone: true, selector: "mt-start-session-dialog", inputs: { delegation: { classPropertyName: "delegation", publicName: "delegation", isSignal: true, isRequired: true, transformFunction: null }, intent: { classPropertyName: "intent", publicName: "intent", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\r\n <div\r\n [class]=\"modal.contentClass + ' flex flex-col gap-4 overflow-y-auto p-5'\"\r\n >\r\n <mt-entity-preview\r\n [data]=\"userEntity(delegation().delegator)\"\r\n ></mt-entity-preview>\r\n\r\n <p class=\"text-sm leading-relaxed text-surface-600\">\r\n {{ t(bodyKey()) }}\r\n </p>\r\n </div>\r\n\r\n <div [class]=\"modal.footerClass\">\r\n <mt-button\r\n variant=\"outlined\"\r\n color=\"secondary\"\r\n [label]=\"t('delegations.common.cancel')\"\r\n [disabled]=\"isBusy()\"\r\n (click)=\"cancel()\"\r\n ></mt-button>\r\n <mt-button\r\n color=\"primary\"\r\n icon=\"user.users-check\"\r\n [label]=\"t(confirmLabelKey())\"\r\n [loading]=\"isBusy()\"\r\n (click)=\"confirm()\"\r\n ></mt-button>\r\n </div>\r\n</ng-container>\r\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs", "ariaLabel"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: EntityPreview, selector: "mt-entity-preview", inputs: ["data", "attachmentShape"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
595
749
  }
596
750
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: StartSessionDialog, decorators: [{
597
751
  type: Component,
598
- args: [{ selector: 'mt-start-session-dialog', imports: [CommonModule, Button, EntityPreview, TranslocoDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\n <div\n [class]=\"modal.contentClass + ' flex flex-col gap-4 overflow-y-auto p-5'\"\n >\n <mt-entity-preview\n [data]=\"userEntity(delegation().delegator)\"\n ></mt-entity-preview>\n\n <p class=\"text-sm leading-relaxed text-surface-600\">\n {{ t(bodyKey()) }}\n </p>\n </div>\n\n <div [class]=\"modal.footerClass\">\n <mt-button\n variant=\"outlined\"\n color=\"secondary\"\n [label]=\"t('delegations.common.cancel')\"\n [disabled]=\"isBusy()\"\n (click)=\"cancel()\"\n ></mt-button>\n <mt-button\n color=\"primary\"\n icon=\"user.users-check\"\n [label]=\"t(confirmLabelKey())\"\n [loading]=\"isBusy()\"\n (click)=\"confirm()\"\n ></mt-button>\n </div>\n</ng-container>\n" }]
752
+ args: [{ selector: 'mt-start-session-dialog', imports: [CommonModule, Button, EntityPreview, TranslocoDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\r\n <div\r\n [class]=\"modal.contentClass + ' flex flex-col gap-4 overflow-y-auto p-5'\"\r\n >\r\n <mt-entity-preview\r\n [data]=\"userEntity(delegation().delegator)\"\r\n ></mt-entity-preview>\r\n\r\n <p class=\"text-sm leading-relaxed text-surface-600\">\r\n {{ t(bodyKey()) }}\r\n </p>\r\n </div>\r\n\r\n <div [class]=\"modal.footerClass\">\r\n <mt-button\r\n variant=\"outlined\"\r\n color=\"secondary\"\r\n [label]=\"t('delegations.common.cancel')\"\r\n [disabled]=\"isBusy()\"\r\n (click)=\"cancel()\"\r\n ></mt-button>\r\n <mt-button\r\n color=\"primary\"\r\n icon=\"user.users-check\"\r\n [label]=\"t(confirmLabelKey())\"\r\n [loading]=\"isBusy()\"\r\n (click)=\"confirm()\"\r\n ></mt-button>\r\n </div>\r\n</ng-container>\r\n" }]
599
753
  }], propDecorators: { delegation: [{ type: i0.Input, args: [{ isSignal: true, alias: "delegation", required: true }] }], intent: [{ type: i0.Input, args: [{ isSignal: true, alias: "intent", required: false }] }] } });
600
754
 
601
755
  /**
@@ -617,74 +771,25 @@ class DelegationCandidatesPromptDialog {
617
771
  this.ref.close(null);
618
772
  }
619
773
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationCandidatesPromptDialog, deps: [], target: i0.ɵɵFactoryTarget.Component });
620
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: DelegationCandidatesPromptDialog, isStandalone: true, selector: "mt-delegation-candidates-prompt-dialog", inputs: { candidates: { classPropertyName: "candidates", publicName: "candidates", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\n <div\n [class]=\"modal.contentClass + ' flex flex-col gap-4 overflow-y-auto p-5'\"\n >\n <p class=\"text-sm leading-relaxed text-surface-600\">\n {{ t(\"delegations.session.promptBody\") }}\n </p>\n\n <div class=\"flex flex-col gap-2\">\n @for (cand of candidates(); track cand.delegationId) {\n <button\n type=\"button\"\n class=\"flex w-full cursor-pointer items-center justify-between gap-3 rounded-lg border border-solid border-surface-200 bg-surface-0 px-3 py-2.5 text-start transition-colors hover:border-primary-300 hover:bg-primary-50/50 focus-visible:border-primary-400 focus-visible:outline-none\"\n [attr.aria-label]=\"\n t('delegations.session.promptStartAria', {\n delegatorName: cand.delegator.displayName,\n })\n \"\n (click)=\"select(cand)\"\n >\n <span class=\"flex min-w-0 items-center gap-3\">\n <mt-entity-preview\n [data]=\"userEntity(cand.delegator)\"\n ></mt-entity-preview>\n </span>\n <span\n class=\"inline-flex shrink-0 items-center gap-1.5 text-sm font-medium text-primary\"\n >\n <span class=\"hidden sm:inline\">\n {{ t(\"delegations.action.startSession\") }}\n </span>\n <mt-icon icon=\"arrow.arrow-right\" class=\"text-base\"></mt-icon>\n </span>\n </button>\n }\n </div>\n </div>\n\n <div [class]=\"modal.footerClass\">\n <mt-button\n variant=\"outlined\"\n color=\"secondary\"\n [label]=\"t('delegations.common.close')\"\n (click)=\"close()\"\n ></mt-button>\n </div>\n</ng-container>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs", "ariaLabel"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: EntityPreview, selector: "mt-entity-preview", inputs: ["data", "attachmentShape"] }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
774
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: DelegationCandidatesPromptDialog, isStandalone: true, selector: "mt-delegation-candidates-prompt-dialog", inputs: { candidates: { classPropertyName: "candidates", publicName: "candidates", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\r\n <div\r\n [class]=\"modal.contentClass + ' flex flex-col gap-4 overflow-y-auto p-5'\"\r\n >\r\n <p class=\"text-sm leading-relaxed text-surface-600\">\r\n {{ t(\"delegations.session.promptBody\") }}\r\n </p>\r\n\r\n <div class=\"flex flex-col gap-2\">\r\n @for (cand of candidates(); track cand.delegationId) {\r\n <button\r\n type=\"button\"\r\n class=\"flex w-full cursor-pointer items-center justify-between gap-3 rounded-lg border border-solid border-surface-200 bg-surface-0 px-3 py-2.5 text-start transition-colors hover:border-primary-300 hover:bg-primary-50/50 focus-visible:border-primary-400 focus-visible:outline-none\"\r\n [attr.aria-label]=\"\r\n t('delegations.session.promptStartAria', {\r\n delegatorName: cand.delegator.displayName,\r\n })\r\n \"\r\n (click)=\"select(cand)\"\r\n >\r\n <span class=\"flex min-w-0 items-center gap-3\">\r\n <mt-entity-preview\r\n [data]=\"userEntity(cand.delegator)\"\r\n ></mt-entity-preview>\r\n </span>\r\n <span\r\n class=\"inline-flex shrink-0 items-center gap-1.5 text-sm font-medium text-primary\"\r\n >\r\n <span class=\"hidden sm:inline\">\r\n {{ t(\"delegations.action.startSession\") }}\r\n </span>\r\n <mt-icon icon=\"arrow.arrow-right\" class=\"text-base\"></mt-icon>\r\n </span>\r\n </button>\r\n }\r\n </div>\r\n </div>\r\n\r\n <div [class]=\"modal.footerClass\">\r\n <mt-button\r\n variant=\"outlined\"\r\n color=\"secondary\"\r\n [label]=\"t('delegations.common.close')\"\r\n (click)=\"close()\"\r\n ></mt-button>\r\n </div>\r\n</ng-container>\r\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs", "ariaLabel"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: EntityPreview, selector: "mt-entity-preview", inputs: ["data", "attachmentShape"] }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
621
775
  }
622
776
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationCandidatesPromptDialog, decorators: [{
623
777
  type: Component,
624
- args: [{ selector: 'mt-delegation-candidates-prompt-dialog', imports: [CommonModule, Button, EntityPreview, Icon, TranslocoDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\n <div\n [class]=\"modal.contentClass + ' flex flex-col gap-4 overflow-y-auto p-5'\"\n >\n <p class=\"text-sm leading-relaxed text-surface-600\">\n {{ t(\"delegations.session.promptBody\") }}\n </p>\n\n <div class=\"flex flex-col gap-2\">\n @for (cand of candidates(); track cand.delegationId) {\n <button\n type=\"button\"\n class=\"flex w-full cursor-pointer items-center justify-between gap-3 rounded-lg border border-solid border-surface-200 bg-surface-0 px-3 py-2.5 text-start transition-colors hover:border-primary-300 hover:bg-primary-50/50 focus-visible:border-primary-400 focus-visible:outline-none\"\n [attr.aria-label]=\"\n t('delegations.session.promptStartAria', {\n delegatorName: cand.delegator.displayName,\n })\n \"\n (click)=\"select(cand)\"\n >\n <span class=\"flex min-w-0 items-center gap-3\">\n <mt-entity-preview\n [data]=\"userEntity(cand.delegator)\"\n ></mt-entity-preview>\n </span>\n <span\n class=\"inline-flex shrink-0 items-center gap-1.5 text-sm font-medium text-primary\"\n >\n <span class=\"hidden sm:inline\">\n {{ t(\"delegations.action.startSession\") }}\n </span>\n <mt-icon icon=\"arrow.arrow-right\" class=\"text-base\"></mt-icon>\n </span>\n </button>\n }\n </div>\n </div>\n\n <div [class]=\"modal.footerClass\">\n <mt-button\n variant=\"outlined\"\n color=\"secondary\"\n [label]=\"t('delegations.common.close')\"\n (click)=\"close()\"\n ></mt-button>\n </div>\n</ng-container>\n" }]
778
+ args: [{ selector: 'mt-delegation-candidates-prompt-dialog', imports: [CommonModule, Button, EntityPreview, Icon, TranslocoDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\r\n <div\r\n [class]=\"modal.contentClass + ' flex flex-col gap-4 overflow-y-auto p-5'\"\r\n >\r\n <p class=\"text-sm leading-relaxed text-surface-600\">\r\n {{ t(\"delegations.session.promptBody\") }}\r\n </p>\r\n\r\n <div class=\"flex flex-col gap-2\">\r\n @for (cand of candidates(); track cand.delegationId) {\r\n <button\r\n type=\"button\"\r\n class=\"flex w-full cursor-pointer items-center justify-between gap-3 rounded-lg border border-solid border-surface-200 bg-surface-0 px-3 py-2.5 text-start transition-colors hover:border-primary-300 hover:bg-primary-50/50 focus-visible:border-primary-400 focus-visible:outline-none\"\r\n [attr.aria-label]=\"\r\n t('delegations.session.promptStartAria', {\r\n delegatorName: cand.delegator.displayName,\r\n })\r\n \"\r\n (click)=\"select(cand)\"\r\n >\r\n <span class=\"flex min-w-0 items-center gap-3\">\r\n <mt-entity-preview\r\n [data]=\"userEntity(cand.delegator)\"\r\n ></mt-entity-preview>\r\n </span>\r\n <span\r\n class=\"inline-flex shrink-0 items-center gap-1.5 text-sm font-medium text-primary\"\r\n >\r\n <span class=\"hidden sm:inline\">\r\n {{ t(\"delegations.action.startSession\") }}\r\n </span>\r\n <mt-icon icon=\"arrow.arrow-right\" class=\"text-base\"></mt-icon>\r\n </span>\r\n </button>\r\n }\r\n </div>\r\n </div>\r\n\r\n <div [class]=\"modal.footerClass\">\r\n <mt-button\r\n variant=\"outlined\"\r\n color=\"secondary\"\r\n [label]=\"t('delegations.common.close')\"\r\n (click)=\"close()\"\r\n ></mt-button>\r\n </div>\r\n</ng-container>\r\n" }]
625
779
  }], propDecorators: { candidates: [{ type: i0.Input, args: [{ isSignal: true, alias: "candidates", required: true }] }] } });
626
780
 
627
- const STATUS_VISUAL = {
628
- Active: {
629
- i18nKey: 'delegations.status.active',
630
- styleClass: 'mt-status-chip mt-status-chip--active',
631
- },
632
- Scheduled: {
633
- i18nKey: 'delegations.status.scheduled',
634
- styleClass: 'mt-status-chip mt-status-chip--scheduled',
635
- },
636
- PendingApproval: {
637
- i18nKey: 'delegations.status.pendingApproval',
638
- styleClass: 'mt-status-chip mt-status-chip--pending',
639
- },
640
- InactiveToday: {
641
- i18nKey: 'delegations.status.inactiveToday',
642
- styleClass: 'mt-status-chip mt-status-chip--scheduled',
643
- },
644
- Expired: {
645
- i18nKey: 'delegations.status.expired',
646
- styleClass: 'mt-status-chip mt-status-chip--expired',
647
- },
648
- Rejected: {
649
- i18nKey: 'delegations.status.rejected',
650
- styleClass: 'mt-status-chip mt-status-chip--rejected',
651
- },
652
- Cancelled: {
653
- i18nKey: 'delegations.status.cancelled',
654
- styleClass: 'mt-status-chip mt-status-chip--cancelled',
655
- },
656
- };
657
- class DelegationStatusChip {
658
- status = input.required(...(ngDevMode ? [{ debugName: "status" }] : /* istanbul ignore next */ []));
659
- reasonCode = input(null, ...(ngDevMode ? [{ debugName: "reasonCode" }] : /* istanbul ignore next */ []));
660
- visual = computed(() => STATUS_VISUAL[this.status() === 'Scheduled' && this.reasonCode() === 'InactiveToday'
661
- ? 'InactiveToday'
662
- : this.status()] ?? STATUS_VISUAL.Scheduled, ...(ngDevMode ? [{ debugName: "visual" }] : /* istanbul ignore next */ []));
663
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationStatusChip, deps: [], target: i0.ɵɵFactoryTarget.Component });
664
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.8", type: DelegationStatusChip, isStandalone: true, selector: "mt-delegation-status-chip", inputs: { status: { classPropertyName: "status", publicName: "status", isSignal: true, isRequired: true, transformFunction: null }, reasonCode: { classPropertyName: "reasonCode", publicName: "reasonCode", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
665
- <ng-container *transloco="let t">
666
- @let v = visual();
667
- <mt-chip [label]="t(v.i18nKey)" [styleClass]="v.styleClass"></mt-chip>
668
- </ng-container>
669
- `, isInline: true, styles: [":host{display:inline-flex}:host ::ng-deep .mt-status-chip{font-weight:500;font-size:.75rem;border-radius:9999px;padding-inline:.625rem;padding-block:.125rem}:host ::ng-deep .mt-status-chip--active{background-color:#dcfce7;color:#166534}:host ::ng-deep .mt-status-chip--scheduled{background-color:#dbeafe;color:#1e40af}:host ::ng-deep .mt-status-chip--pending{background-color:#fef9c3;color:#854d0e}:host ::ng-deep .mt-status-chip--expired{background-color:#f3f4f6;color:#4b5563}:host ::ng-deep .mt-status-chip--rejected{background-color:#fee2e2;color:#991b1b}:host ::ng-deep .mt-status-chip--cancelled{background-color:#e5e7eb;color:#374151}:host-context(.dark) ::ng-deep .mt-status-chip--active{background-color:#22c55e33;color:#86efac}:host-context(.dark) ::ng-deep .mt-status-chip--scheduled{background-color:#3b82f633;color:#93c5fd}:host-context(.dark) ::ng-deep .mt-status-chip--pending{background-color:#eab30833;color:#fde047}:host-context(.dark) ::ng-deep .mt-status-chip--expired{background-color:#94a3b833;color:#cbd5e1}:host-context(.dark) ::ng-deep .mt-status-chip--rejected{background-color:#ef444433;color:#fca5a5}:host-context(.dark) ::ng-deep .mt-status-chip--cancelled{background-color:#94a3b826;color:#cbd5e1}\n"], dependencies: [{ kind: "component", type: Chip, selector: "mt-chip", inputs: ["label", "icon", "image", "removable", "removeIcon", "styleClass", "size"], outputs: ["onRemove", "onImageError"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }] });
670
- }
671
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationStatusChip, decorators: [{
672
- type: Component,
673
- args: [{ selector: 'mt-delegation-status-chip', standalone: true, imports: [Chip, TranslocoDirective], template: `
674
- <ng-container *transloco="let t">
675
- @let v = visual();
676
- <mt-chip [label]="t(v.i18nKey)" [styleClass]="v.styleClass"></mt-chip>
677
- </ng-container>
678
- `, styles: [":host{display:inline-flex}:host ::ng-deep .mt-status-chip{font-weight:500;font-size:.75rem;border-radius:9999px;padding-inline:.625rem;padding-block:.125rem}:host ::ng-deep .mt-status-chip--active{background-color:#dcfce7;color:#166534}:host ::ng-deep .mt-status-chip--scheduled{background-color:#dbeafe;color:#1e40af}:host ::ng-deep .mt-status-chip--pending{background-color:#fef9c3;color:#854d0e}:host ::ng-deep .mt-status-chip--expired{background-color:#f3f4f6;color:#4b5563}:host ::ng-deep .mt-status-chip--rejected{background-color:#fee2e2;color:#991b1b}:host ::ng-deep .mt-status-chip--cancelled{background-color:#e5e7eb;color:#374151}:host-context(.dark) ::ng-deep .mt-status-chip--active{background-color:#22c55e33;color:#86efac}:host-context(.dark) ::ng-deep .mt-status-chip--scheduled{background-color:#3b82f633;color:#93c5fd}:host-context(.dark) ::ng-deep .mt-status-chip--pending{background-color:#eab30833;color:#fde047}:host-context(.dark) ::ng-deep .mt-status-chip--expired{background-color:#94a3b833;color:#cbd5e1}:host-context(.dark) ::ng-deep .mt-status-chip--rejected{background-color:#ef444433;color:#fca5a5}:host-context(.dark) ::ng-deep .mt-status-chip--cancelled{background-color:#94a3b826;color:#cbd5e1}\n"] }]
679
- }], propDecorators: { status: [{ type: i0.Input, args: [{ isSignal: true, alias: "status", required: true }] }], reasonCode: [{ type: i0.Input, args: [{ isSignal: true, alias: "reasonCode", required: false }] }] } });
680
-
681
781
  /**
682
- * Embeddable delegation menu content (doc 05, 09): renders the candidate /
683
- * active-session rows and the start / switch / end actions, with no trigger of
684
- * its own. Designed to be dropped inside a host menu (e.g. the user-avatar
685
- * dropdown) or the topbar popover. Reads the shared `DelegationSessionFacade`
686
- * signals; candidate loading + the post-login prompt stay in the always-mounted
687
- * `TopbarDelegationMenu` controller.
782
+ * Embeddable "acting as" account switcher (doc 05, 09).
783
+ *
784
+ * Renders one row per identity the user can operate as themselves first, then
785
+ * every delegator who has an active delegation assigned to them — with the
786
+ * current one checked. Picking a row starts, switches, or ends the delegated
787
+ * session; the {@link StartSessionDialog} still takes explicit consent before
788
+ * any session begins, so this list never changes authority on its own.
789
+ *
790
+ * Designed to be dropped inside a host menu (e.g. the user-avatar dropdown) or
791
+ * the topbar popover. Candidate loading + the post-login prompt stay in the
792
+ * always-mounted `TopbarDelegationMenu` controller.
688
793
  *
689
794
  * Emits `closeRequested` after any action so the host overlay can dismiss.
690
795
  */
@@ -692,25 +797,57 @@ class DelegationMenuPanel {
692
797
  /** Path to the management page, e.g. `/control-panel/delegations` or `/delegations`. */
693
798
  managePath = input('/delegations', ...(ngDevMode ? [{ debugName: "managePath" }] : /* istanbul ignore next */ []));
694
799
  showManageLink = input(true, ...(ngDevMode ? [{ debugName: "showManageLink" }] : /* istanbul ignore next */ []));
800
+ /**
801
+ * Optional richer profile for the "you" row. Falls back to the current user
802
+ * carried on the assigned delegation rows, so the host may omit it.
803
+ */
804
+ currentUser = input(null, ...(ngDevMode ? [{ debugName: "currentUser" }] : /* istanbul ignore next */ []));
695
805
  closeRequested = output();
696
806
  facade = inject(DelegationSessionFacade);
697
807
  modal = inject(ModalService);
698
808
  transloco = inject(TranslocoService);
809
+ toast = inject(ToastService);
699
810
  active = this.facade.active;
700
811
  candidates = this.facade.candidates;
701
812
  hasCandidates = this.facade.hasCandidates;
702
813
  hasMoreCandidates = this.facade.hasMoreCandidates;
703
814
  isLoadingCandidates = this.facade.isLoadingCandidates;
815
+ isBusy = this.facade.isStarting;
704
816
  onBehalfOf = this.facade.onBehalfOf;
705
- executedBy = this.facade.executedBy;
706
- mode = computed(() => {
707
- if (this.active())
708
- return 'active';
709
- if (this.hasCandidates())
710
- return 'candidates';
711
- return 'hidden';
712
- }, ...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
713
- /** Two-letter initials for a delegator avatar (matches the user-menu pattern). */
817
+ /** The real signed-in user — host-supplied when available, derived otherwise. */
818
+ self = computed(() => this.currentUser() ?? this.facade.executedBy(), ...(ngDevMode ? [{ debugName: "self" }] : /* istanbul ignore next */ []));
819
+ /** Nothing to switch between and no session running — render nothing. */
820
+ visible = computed(() => !!this.active() || this.hasCandidates(), ...(ngDevMode ? [{ debugName: "visible" }] : /* istanbul ignore next */ []));
821
+ /** "You", then every delegator. The active identity carries the checkmark. */
822
+ identities = computed(() => {
823
+ const activeSession = this.active();
824
+ const activeId = activeSession?.delegation.delegationId ?? null;
825
+ const self = this.self();
826
+ const candidates = this.candidates();
827
+ // The identity in force is always listed, even if it has fallen off the
828
+ // first page of candidates — a switcher that cannot show who you currently
829
+ // are is worse than one extra row.
830
+ const rows = activeSession && !candidates.some((row) => row.delegationId === activeId)
831
+ ? [activeSession.delegation, ...candidates]
832
+ : candidates;
833
+ return [
834
+ {
835
+ row: null,
836
+ party: self,
837
+ isSelf: true,
838
+ isActive: activeId === null,
839
+ initials: this.initials(self),
840
+ },
841
+ ...rows.map((row) => ({
842
+ row,
843
+ party: row.delegator,
844
+ isSelf: false,
845
+ isActive: row.delegationId === activeId,
846
+ initials: this.initials(row.delegator),
847
+ })),
848
+ ];
849
+ }, ...(ngDevMode ? [{ debugName: "identities" }] : /* istanbul ignore next */ []));
850
+ /** Two-letter initials for an avatar (matches the user-menu pattern). */
714
851
  initials(party) {
715
852
  const name = party?.displayName?.trim() || party?.email?.trim() || '';
716
853
  const parts = name.split(/\s+/).filter(Boolean);
@@ -721,20 +858,24 @@ class DelegationMenuPanel {
721
858
  }
722
859
  return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
723
860
  }
724
- start(row, event) {
725
- event.stopPropagation();
726
- this.closeRequested.emit();
727
- this.openConfirm(row, 'start');
728
- }
729
- switchTo(row, event) {
730
- event.stopPropagation();
731
- this.closeRequested.emit();
732
- this.openConfirm(row, 'switch');
733
- }
734
- endSession(event) {
861
+ select(option, event) {
735
862
  event.stopPropagation();
863
+ if (option.isActive || this.isBusy()) {
864
+ return;
865
+ }
736
866
  this.closeRequested.emit();
737
- this.facade.endSession('Manual').subscribe();
867
+ if (option.isSelf) {
868
+ // Back to your own authority. There is no server call (doc 05) — ending
869
+ // is local — so no confirmation dialog stands between the user and it.
870
+ this.facade.endSession('Manual').subscribe({
871
+ next: () => this.toast.success(this.transloco.translate('delegations.session.ended')),
872
+ });
873
+ return;
874
+ }
875
+ if (!option.row) {
876
+ return;
877
+ }
878
+ this.openConfirm(option.row, this.active() ? 'switch' : 'start');
738
879
  }
739
880
  onManage() {
740
881
  this.closeRequested.emit();
@@ -755,19 +896,12 @@ class DelegationMenuPanel {
755
896
  });
756
897
  }
757
898
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationMenuPanel, deps: [], target: i0.ɵɵFactoryTarget.Component });
758
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: DelegationMenuPanel, isStandalone: true, selector: "mt-delegation-menu-panel", inputs: { managePath: { classPropertyName: "managePath", publicName: "managePath", isSignal: true, isRequired: false, transformFunction: null }, showManageLink: { classPropertyName: "showManageLink", publicName: "showManageLink", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closeRequested: "closeRequested" }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\r\n @if (mode() !== \"hidden\") {\r\n <div class=\"flex w-full flex-col\">\r\n @if (mode() === \"active\" && active(); as session) {\r\n <!-- Active session -->\r\n <div class=\"flex items-center gap-3 border-b border-surface px-4 py-3\">\r\n <mt-avatar\r\n [label]=\"initials(onBehalfOf())\"\r\n shape=\"circle\"\r\n styleClass=\"!size-9 !text-sm !bg-primary-100 !text-primary-700\"\r\n ></mt-avatar>\r\n <div class=\"flex min-w-0 flex-1 flex-col\">\r\n <span\r\n class=\"text-[0.625rem] font-medium uppercase text-surface-500\"\r\n >\r\n {{ t(\"delegations.session.actingOnBehalfOf\") }}\r\n </span>\r\n <span class=\"truncate text-sm font-semibold text-surface-900\">\r\n {{ onBehalfOf()?.displayName }}\r\n </span>\r\n <span class=\"truncate text-xs text-surface-500\">\r\n {{\r\n t(\"delegations.session.executedBy\", {\r\n actualUserName: executedBy()?.displayName,\r\n })\r\n }}\r\n </span>\r\n </div>\r\n <mt-delegation-status-chip\r\n status=\"Active\"\r\n ></mt-delegation-status-chip>\r\n </div>\r\n\r\n <div class=\"flex flex-col p-1.5\">\r\n <button\r\n type=\"button\"\r\n class=\"flex w-full cursor-pointer items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium text-surface-700 hover:bg-surface-100\"\r\n (click)=\"endSession($event)\"\r\n >\r\n <mt-icon icon=\"arrow.arrow-left\" styleClass=\"text-base\"></mt-icon>\r\n <span>{{ t(\"delegations.action.endSession\") }}</span>\r\n </button>\r\n </div>\r\n\r\n @if (candidates().length > 1) {\r\n <div class=\"border-t border-surface p-1.5\">\r\n <div\r\n class=\"px-2 pb-1 text-[0.625rem] font-medium uppercase text-surface-400\"\r\n >\r\n {{ t(\"delegations.session.switchToAnother\") }}\r\n </div>\r\n @for (cand of candidates(); track cand.delegationId) {\r\n @if (cand.delegationId !== session.delegation.delegationId) {\r\n <button\r\n type=\"button\"\r\n class=\"flex w-full cursor-pointer items-center gap-3 rounded-lg px-2 py-2 text-start hover:bg-surface-100\"\r\n [attr.aria-label]=\"\r\n t('delegations.action.switchSession') +\r\n ': ' +\r\n cand.delegator.displayName\r\n \"\r\n (click)=\"switchTo(cand, $event)\"\r\n >\r\n <mt-avatar\r\n [label]=\"initials(cand.delegator)\"\r\n shape=\"circle\"\r\n styleClass=\"!size-8 !text-xs !bg-surface-100 !text-surface-600\"\r\n ></mt-avatar>\r\n <span\r\n class=\"min-w-0 flex-1 truncate text-sm font-medium text-surface-800\"\r\n >\r\n {{ cand.delegator.displayName }}\r\n </span>\r\n <mt-icon\r\n icon=\"arrow.switch-horizontal-01\"\r\n styleClass=\"text-base text-surface-400\"\r\n ></mt-icon>\r\n </button>\r\n }\r\n }\r\n </div>\r\n }\r\n } @else if (mode() === \"candidates\") {\r\n <!-- Candidates available -->\r\n <div class=\"border-b border-surface px-4 py-3\">\r\n <div class=\"text-sm font-semibold text-surface-900\">\r\n {{ t(\"delegations.session.youCanActAs\") }}\r\n </div>\r\n <div class=\"mt-0.5 text-xs text-surface-500\">\r\n {{ t(\"delegations.session.chooseDelegatorHint\") }}\r\n </div>\r\n </div>\r\n <div class=\"flex max-h-72 flex-col overflow-y-auto p-1.5\">\r\n @for (cand of candidates(); track cand.delegationId) {\r\n <button\r\n type=\"button\"\r\n class=\"flex w-full cursor-pointer items-center gap-3 rounded-lg px-2 py-2 text-start hover:bg-surface-100\"\r\n [attr.aria-label]=\"\r\n t('delegations.action.startSession') +\r\n ': ' +\r\n cand.delegator.displayName\r\n \"\r\n (click)=\"start(cand, $event)\"\r\n >\r\n <mt-avatar\r\n [label]=\"initials(cand.delegator)\"\r\n shape=\"circle\"\r\n styleClass=\"!size-8 !text-xs !bg-primary-100 !text-primary-700\"\r\n ></mt-avatar>\r\n <span class=\"flex min-w-0 flex-1 flex-col\">\r\n <span class=\"truncate text-sm font-medium text-surface-800\">\r\n {{ cand.delegator.displayName }}\r\n </span>\r\n @if (cand.delegator.email) {\r\n <span class=\"truncate text-xs text-surface-500\">\r\n {{ cand.delegator.email }}\r\n </span>\r\n }\r\n </span>\r\n <mt-icon\r\n icon=\"arrow.arrow-right\"\r\n styleClass=\"text-base text-surface-400\"\r\n ></mt-icon>\r\n </button>\r\n }\r\n @if (hasMoreCandidates()) {\r\n <button\r\n type=\"button\"\r\n class=\"mx-2 my-1 rounded-lg px-3 py-2 text-sm font-medium text-primary hover:bg-primary-50 disabled:opacity-60\"\r\n [disabled]=\"isLoadingCandidates()\"\r\n (click)=\"loadMore($event)\"\r\n >\r\n {{ t(\"delegations.session.loadMore\") }}\r\n </button>\r\n }\r\n </div>\r\n }\r\n\r\n @if (showManageLink()) {\r\n <div class=\"border-t border-surface p-1.5\">\r\n <a\r\n [routerLink]=\"managePath()\"\r\n (click)=\"onManage()\"\r\n class=\"flex cursor-pointer items-center gap-3 rounded-lg px-3 py-2 text-sm text-surface-700 hover:bg-surface-100\"\r\n >\r\n <mt-icon\r\n icon=\"general.settings-02\"\r\n styleClass=\"text-base\"\r\n ></mt-icon>\r\n <span>{{ t(\"delegations.session.manage\") }}</span>\r\n </a>\r\n </div>\r\n }\r\n </div>\r\n }\r\n</ng-container>\r\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: Avatar, selector: "mt-avatar", inputs: ["label", "icon", "image", "styleClass", "size", "shape", "badge", "badgeSize", "badgeSeverity"], outputs: ["onImageError"] }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: DelegationStatusChip, selector: "mt-delegation-status-chip", inputs: ["status", "reasonCode"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
899
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: DelegationMenuPanel, isStandalone: true, selector: "mt-delegation-menu-panel", inputs: { managePath: { classPropertyName: "managePath", publicName: "managePath", isSignal: true, isRequired: false, transformFunction: null }, showManageLink: { classPropertyName: "showManageLink", publicName: "showManageLink", isSignal: true, isRequired: false, transformFunction: null }, currentUser: { classPropertyName: "currentUser", publicName: "currentUser", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closeRequested: "closeRequested" }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\n @if (visible()) {\n <div class=\"mt-delegation-switcher flex w-full flex-col\">\n <!-- Header: what this list is, and the current mode at a glance -->\n <div class=\"flex flex-col gap-0.5 px-4 pb-2 pt-3\">\n <span\n class=\"text-[0.6875rem] font-semibold uppercase tracking-wide text-surface-400\"\n >\n {{ t(\"delegations.session.actingAs\") }}\n </span>\n @if (active()) {\n <span class=\"text-xs text-surface-500\">\n {{\n t(\"delegations.session.switchBackHint\", {\n actualUserName: self()?.displayName,\n })\n }}\n </span>\n } @else {\n <span class=\"text-xs text-surface-500\">\n {{ t(\"delegations.session.chooseDelegatorHint\") }}\n </span>\n }\n </div>\n\n <!-- Identity list: you first, then everyone who delegated to you -->\n <div\n class=\"mt-delegation-switcher__list flex max-h-80 flex-col gap-0.5 overflow-y-auto px-1.5 pb-1.5\"\n role=\"listbox\"\n [attr.aria-label]=\"t('delegations.session.actingAs')\"\n >\n @for (\n option of identities();\n track option.row?.delegationId ?? \"self\"\n ) {\n <button\n type=\"button\"\n role=\"option\"\n class=\"mt-delegation-switcher__row\"\n [class.mt-delegation-switcher__row--active]=\"option.isActive\"\n [attr.aria-selected]=\"option.isActive\"\n [disabled]=\"isBusy() || option.isActive\"\n [attr.aria-label]=\"\n option.isSelf\n ? t('delegations.session.actAsSelfAria', {\n actualUserName: option.party?.displayName,\n })\n : t('delegations.session.promptStartAria', {\n delegatorName: option.party?.displayName,\n })\n \"\n (click)=\"select(option, $event)\"\n >\n <span class=\"relative shrink-0\">\n <mt-avatar\n [label]=\"option.initials\"\n shape=\"circle\"\n [styleClass]=\"\n option.isActive\n ? '!size-9 !text-xs !bg-primary-100 !text-primary-700'\n : '!size-9 !text-xs !bg-surface-100 !text-surface-600'\n \"\n ></mt-avatar>\n @if (option.isActive && !option.isSelf) {\n <span\n class=\"absolute -bottom-0.5 -end-0.5 flex size-4 items-center justify-center rounded-full bg-primary ring-2 ring-surface-0\"\n aria-hidden=\"true\"\n >\n <mt-icon\n icon=\"user.users-check\"\n styleClass=\"text-[0.55rem] text-white\"\n ></mt-icon>\n </span>\n }\n </span>\n\n <span class=\"flex min-w-0 flex-1 flex-col text-start\">\n <span class=\"flex min-w-0 items-center gap-1.5\">\n <span\n class=\"truncate text-sm font-semibold text-surface-800\"\n [class.text-primary-700]=\"option.isActive\"\n >\n {{ option.party?.displayName }}\n </span>\n @if (option.isSelf) {\n <span class=\"mt-delegation-switcher__badge\">\n {{ t(\"delegations.session.you\") }}\n </span>\n }\n </span>\n <span class=\"truncate text-xs text-surface-500\">\n @if (option.isSelf) {\n {{\n option.party?.email ||\n t(\"delegations.session.ownAccountHint\")\n }}\n } @else {\n {{\n t(\"delegations.session.until\", {\n date: option.row?.endsAtUtc | date: \"mediumDate\",\n })\n }}\n }\n </span>\n </span>\n\n @if (option.isActive) {\n <mt-icon\n icon=\"general.check\"\n styleClass=\"text-base text-primary shrink-0\"\n ></mt-icon>\n } @else {\n <mt-icon\n icon=\"arrow.chevron-right\"\n styleClass=\"mt-delegation-switcher__chevron text-base text-surface-400 shrink-0\"\n ></mt-icon>\n }\n </button>\n }\n\n @if (hasMoreCandidates()) {\n <button\n type=\"button\"\n class=\"mx-1.5 my-1 rounded-lg px-3 py-2 text-sm font-medium text-primary hover:bg-primary-50 disabled:opacity-60\"\n [disabled]=\"isLoadingCandidates()\"\n (click)=\"loadMore($event)\"\n >\n {{ t(\"delegations.session.loadMore\") }}\n </button>\n }\n </div>\n\n @if (showManageLink()) {\n <div class=\"border-t border-surface p-1.5\">\n <a\n [routerLink]=\"managePath()\"\n (click)=\"onManage()\"\n class=\"flex cursor-pointer items-center gap-3 rounded-lg px-3 py-2 text-sm text-surface-700 hover:bg-surface-100\"\n >\n <mt-icon\n icon=\"general.settings-02\"\n styleClass=\"text-base\"\n ></mt-icon>\n <span>{{ t(\"delegations.session.manage\") }}</span>\n </a>\n </div>\n }\n </div>\n }\n</ng-container>\n", styles: [":host{display:block;min-width:0}.mt-delegation-switcher{min-width:15rem}.mt-delegation-switcher__row{display:flex;width:100%;align-items:center;gap:.75rem;padding:.5rem .625rem;border:1px solid transparent;border-radius:.5rem;background:transparent;cursor:pointer;text-align:start;transition:background-color .12s ease-out,border-color .12s ease-out}.mt-delegation-switcher__row:hover:not(:disabled){background-color:var(--p-surface-100)}.mt-delegation-switcher__row:focus-visible{outline:none;border-color:var(--p-primary-400);box-shadow:0 0 0 2px color-mix(in srgb,var(--p-primary-color) 22%,transparent)}.mt-delegation-switcher__row--active,.mt-delegation-switcher__row--active:hover{background-color:var(--p-primary-50);border-color:var(--p-primary-200)}.mt-delegation-switcher__row:disabled{cursor:default}.mt-delegation-switcher__row--active:disabled{opacity:1}.mt-delegation-switcher__row:disabled:not(.mt-delegation-switcher__row--active){opacity:.55}.mt-delegation-switcher__chevron{opacity:0;transition:opacity .12s ease-out}.mt-delegation-switcher__row:hover:not(:disabled) .mt-delegation-switcher__chevron,.mt-delegation-switcher__row:focus-visible .mt-delegation-switcher__chevron{opacity:1}.mt-delegation-switcher__badge{flex-shrink:0;border-radius:999px;background-color:var(--p-surface-200);color:var(--p-surface-600);font-size:.625rem;font-weight:600;line-height:1rem;padding-inline:.375rem;text-transform:uppercase;letter-spacing:.02em}:host-context([dir=\"rtl\"]) .mt-delegation-switcher__chevron{transform:scaleX(-1)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: Avatar, selector: "mt-avatar", inputs: ["label", "icon", "image", "styleClass", "size", "shape", "badge", "badgeSize", "badgeSeverity"], outputs: ["onImageError"] }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "pipe", type: i1.DatePipe, name: "date" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
759
900
  }
760
901
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationMenuPanel, decorators: [{
761
902
  type: Component,
762
- args: [{ selector: 'mt-delegation-menu-panel', standalone: true, imports: [
763
- CommonModule,
764
- Avatar,
765
- Icon,
766
- RouterLink,
767
- TranslocoDirective,
768
- DelegationStatusChip,
769
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\r\n @if (mode() !== \"hidden\") {\r\n <div class=\"flex w-full flex-col\">\r\n @if (mode() === \"active\" && active(); as session) {\r\n <!-- Active session -->\r\n <div class=\"flex items-center gap-3 border-b border-surface px-4 py-3\">\r\n <mt-avatar\r\n [label]=\"initials(onBehalfOf())\"\r\n shape=\"circle\"\r\n styleClass=\"!size-9 !text-sm !bg-primary-100 !text-primary-700\"\r\n ></mt-avatar>\r\n <div class=\"flex min-w-0 flex-1 flex-col\">\r\n <span\r\n class=\"text-[0.625rem] font-medium uppercase text-surface-500\"\r\n >\r\n {{ t(\"delegations.session.actingOnBehalfOf\") }}\r\n </span>\r\n <span class=\"truncate text-sm font-semibold text-surface-900\">\r\n {{ onBehalfOf()?.displayName }}\r\n </span>\r\n <span class=\"truncate text-xs text-surface-500\">\r\n {{\r\n t(\"delegations.session.executedBy\", {\r\n actualUserName: executedBy()?.displayName,\r\n })\r\n }}\r\n </span>\r\n </div>\r\n <mt-delegation-status-chip\r\n status=\"Active\"\r\n ></mt-delegation-status-chip>\r\n </div>\r\n\r\n <div class=\"flex flex-col p-1.5\">\r\n <button\r\n type=\"button\"\r\n class=\"flex w-full cursor-pointer items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium text-surface-700 hover:bg-surface-100\"\r\n (click)=\"endSession($event)\"\r\n >\r\n <mt-icon icon=\"arrow.arrow-left\" styleClass=\"text-base\"></mt-icon>\r\n <span>{{ t(\"delegations.action.endSession\") }}</span>\r\n </button>\r\n </div>\r\n\r\n @if (candidates().length > 1) {\r\n <div class=\"border-t border-surface p-1.5\">\r\n <div\r\n class=\"px-2 pb-1 text-[0.625rem] font-medium uppercase text-surface-400\"\r\n >\r\n {{ t(\"delegations.session.switchToAnother\") }}\r\n </div>\r\n @for (cand of candidates(); track cand.delegationId) {\r\n @if (cand.delegationId !== session.delegation.delegationId) {\r\n <button\r\n type=\"button\"\r\n class=\"flex w-full cursor-pointer items-center gap-3 rounded-lg px-2 py-2 text-start hover:bg-surface-100\"\r\n [attr.aria-label]=\"\r\n t('delegations.action.switchSession') +\r\n ': ' +\r\n cand.delegator.displayName\r\n \"\r\n (click)=\"switchTo(cand, $event)\"\r\n >\r\n <mt-avatar\r\n [label]=\"initials(cand.delegator)\"\r\n shape=\"circle\"\r\n styleClass=\"!size-8 !text-xs !bg-surface-100 !text-surface-600\"\r\n ></mt-avatar>\r\n <span\r\n class=\"min-w-0 flex-1 truncate text-sm font-medium text-surface-800\"\r\n >\r\n {{ cand.delegator.displayName }}\r\n </span>\r\n <mt-icon\r\n icon=\"arrow.switch-horizontal-01\"\r\n styleClass=\"text-base text-surface-400\"\r\n ></mt-icon>\r\n </button>\r\n }\r\n }\r\n </div>\r\n }\r\n } @else if (mode() === \"candidates\") {\r\n <!-- Candidates available -->\r\n <div class=\"border-b border-surface px-4 py-3\">\r\n <div class=\"text-sm font-semibold text-surface-900\">\r\n {{ t(\"delegations.session.youCanActAs\") }}\r\n </div>\r\n <div class=\"mt-0.5 text-xs text-surface-500\">\r\n {{ t(\"delegations.session.chooseDelegatorHint\") }}\r\n </div>\r\n </div>\r\n <div class=\"flex max-h-72 flex-col overflow-y-auto p-1.5\">\r\n @for (cand of candidates(); track cand.delegationId) {\r\n <button\r\n type=\"button\"\r\n class=\"flex w-full cursor-pointer items-center gap-3 rounded-lg px-2 py-2 text-start hover:bg-surface-100\"\r\n [attr.aria-label]=\"\r\n t('delegations.action.startSession') +\r\n ': ' +\r\n cand.delegator.displayName\r\n \"\r\n (click)=\"start(cand, $event)\"\r\n >\r\n <mt-avatar\r\n [label]=\"initials(cand.delegator)\"\r\n shape=\"circle\"\r\n styleClass=\"!size-8 !text-xs !bg-primary-100 !text-primary-700\"\r\n ></mt-avatar>\r\n <span class=\"flex min-w-0 flex-1 flex-col\">\r\n <span class=\"truncate text-sm font-medium text-surface-800\">\r\n {{ cand.delegator.displayName }}\r\n </span>\r\n @if (cand.delegator.email) {\r\n <span class=\"truncate text-xs text-surface-500\">\r\n {{ cand.delegator.email }}\r\n </span>\r\n }\r\n </span>\r\n <mt-icon\r\n icon=\"arrow.arrow-right\"\r\n styleClass=\"text-base text-surface-400\"\r\n ></mt-icon>\r\n </button>\r\n }\r\n @if (hasMoreCandidates()) {\r\n <button\r\n type=\"button\"\r\n class=\"mx-2 my-1 rounded-lg px-3 py-2 text-sm font-medium text-primary hover:bg-primary-50 disabled:opacity-60\"\r\n [disabled]=\"isLoadingCandidates()\"\r\n (click)=\"loadMore($event)\"\r\n >\r\n {{ t(\"delegations.session.loadMore\") }}\r\n </button>\r\n }\r\n </div>\r\n }\r\n\r\n @if (showManageLink()) {\r\n <div class=\"border-t border-surface p-1.5\">\r\n <a\r\n [routerLink]=\"managePath()\"\r\n (click)=\"onManage()\"\r\n class=\"flex cursor-pointer items-center gap-3 rounded-lg px-3 py-2 text-sm text-surface-700 hover:bg-surface-100\"\r\n >\r\n <mt-icon\r\n icon=\"general.settings-02\"\r\n styleClass=\"text-base\"\r\n ></mt-icon>\r\n <span>{{ t(\"delegations.session.manage\") }}</span>\r\n </a>\r\n </div>\r\n }\r\n </div>\r\n }\r\n</ng-container>\r\n" }]
770
- }], propDecorators: { managePath: [{ type: i0.Input, args: [{ isSignal: true, alias: "managePath", required: false }] }], showManageLink: [{ type: i0.Input, args: [{ isSignal: true, alias: "showManageLink", required: false }] }], closeRequested: [{ type: i0.Output, args: ["closeRequested"] }] } });
903
+ args: [{ selector: 'mt-delegation-menu-panel', standalone: true, imports: [CommonModule, Avatar, Icon, RouterLink, TranslocoDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\n @if (visible()) {\n <div class=\"mt-delegation-switcher flex w-full flex-col\">\n <!-- Header: what this list is, and the current mode at a glance -->\n <div class=\"flex flex-col gap-0.5 px-4 pb-2 pt-3\">\n <span\n class=\"text-[0.6875rem] font-semibold uppercase tracking-wide text-surface-400\"\n >\n {{ t(\"delegations.session.actingAs\") }}\n </span>\n @if (active()) {\n <span class=\"text-xs text-surface-500\">\n {{\n t(\"delegations.session.switchBackHint\", {\n actualUserName: self()?.displayName,\n })\n }}\n </span>\n } @else {\n <span class=\"text-xs text-surface-500\">\n {{ t(\"delegations.session.chooseDelegatorHint\") }}\n </span>\n }\n </div>\n\n <!-- Identity list: you first, then everyone who delegated to you -->\n <div\n class=\"mt-delegation-switcher__list flex max-h-80 flex-col gap-0.5 overflow-y-auto px-1.5 pb-1.5\"\n role=\"listbox\"\n [attr.aria-label]=\"t('delegations.session.actingAs')\"\n >\n @for (\n option of identities();\n track option.row?.delegationId ?? \"self\"\n ) {\n <button\n type=\"button\"\n role=\"option\"\n class=\"mt-delegation-switcher__row\"\n [class.mt-delegation-switcher__row--active]=\"option.isActive\"\n [attr.aria-selected]=\"option.isActive\"\n [disabled]=\"isBusy() || option.isActive\"\n [attr.aria-label]=\"\n option.isSelf\n ? t('delegations.session.actAsSelfAria', {\n actualUserName: option.party?.displayName,\n })\n : t('delegations.session.promptStartAria', {\n delegatorName: option.party?.displayName,\n })\n \"\n (click)=\"select(option, $event)\"\n >\n <span class=\"relative shrink-0\">\n <mt-avatar\n [label]=\"option.initials\"\n shape=\"circle\"\n [styleClass]=\"\n option.isActive\n ? '!size-9 !text-xs !bg-primary-100 !text-primary-700'\n : '!size-9 !text-xs !bg-surface-100 !text-surface-600'\n \"\n ></mt-avatar>\n @if (option.isActive && !option.isSelf) {\n <span\n class=\"absolute -bottom-0.5 -end-0.5 flex size-4 items-center justify-center rounded-full bg-primary ring-2 ring-surface-0\"\n aria-hidden=\"true\"\n >\n <mt-icon\n icon=\"user.users-check\"\n styleClass=\"text-[0.55rem] text-white\"\n ></mt-icon>\n </span>\n }\n </span>\n\n <span class=\"flex min-w-0 flex-1 flex-col text-start\">\n <span class=\"flex min-w-0 items-center gap-1.5\">\n <span\n class=\"truncate text-sm font-semibold text-surface-800\"\n [class.text-primary-700]=\"option.isActive\"\n >\n {{ option.party?.displayName }}\n </span>\n @if (option.isSelf) {\n <span class=\"mt-delegation-switcher__badge\">\n {{ t(\"delegations.session.you\") }}\n </span>\n }\n </span>\n <span class=\"truncate text-xs text-surface-500\">\n @if (option.isSelf) {\n {{\n option.party?.email ||\n t(\"delegations.session.ownAccountHint\")\n }}\n } @else {\n {{\n t(\"delegations.session.until\", {\n date: option.row?.endsAtUtc | date: \"mediumDate\",\n })\n }}\n }\n </span>\n </span>\n\n @if (option.isActive) {\n <mt-icon\n icon=\"general.check\"\n styleClass=\"text-base text-primary shrink-0\"\n ></mt-icon>\n } @else {\n <mt-icon\n icon=\"arrow.chevron-right\"\n styleClass=\"mt-delegation-switcher__chevron text-base text-surface-400 shrink-0\"\n ></mt-icon>\n }\n </button>\n }\n\n @if (hasMoreCandidates()) {\n <button\n type=\"button\"\n class=\"mx-1.5 my-1 rounded-lg px-3 py-2 text-sm font-medium text-primary hover:bg-primary-50 disabled:opacity-60\"\n [disabled]=\"isLoadingCandidates()\"\n (click)=\"loadMore($event)\"\n >\n {{ t(\"delegations.session.loadMore\") }}\n </button>\n }\n </div>\n\n @if (showManageLink()) {\n <div class=\"border-t border-surface p-1.5\">\n <a\n [routerLink]=\"managePath()\"\n (click)=\"onManage()\"\n class=\"flex cursor-pointer items-center gap-3 rounded-lg px-3 py-2 text-sm text-surface-700 hover:bg-surface-100\"\n >\n <mt-icon\n icon=\"general.settings-02\"\n styleClass=\"text-base\"\n ></mt-icon>\n <span>{{ t(\"delegations.session.manage\") }}</span>\n </a>\n </div>\n }\n </div>\n }\n</ng-container>\n", styles: [":host{display:block;min-width:0}.mt-delegation-switcher{min-width:15rem}.mt-delegation-switcher__row{display:flex;width:100%;align-items:center;gap:.75rem;padding:.5rem .625rem;border:1px solid transparent;border-radius:.5rem;background:transparent;cursor:pointer;text-align:start;transition:background-color .12s ease-out,border-color .12s ease-out}.mt-delegation-switcher__row:hover:not(:disabled){background-color:var(--p-surface-100)}.mt-delegation-switcher__row:focus-visible{outline:none;border-color:var(--p-primary-400);box-shadow:0 0 0 2px color-mix(in srgb,var(--p-primary-color) 22%,transparent)}.mt-delegation-switcher__row--active,.mt-delegation-switcher__row--active:hover{background-color:var(--p-primary-50);border-color:var(--p-primary-200)}.mt-delegation-switcher__row:disabled{cursor:default}.mt-delegation-switcher__row--active:disabled{opacity:1}.mt-delegation-switcher__row:disabled:not(.mt-delegation-switcher__row--active){opacity:.55}.mt-delegation-switcher__chevron{opacity:0;transition:opacity .12s ease-out}.mt-delegation-switcher__row:hover:not(:disabled) .mt-delegation-switcher__chevron,.mt-delegation-switcher__row:focus-visible .mt-delegation-switcher__chevron{opacity:1}.mt-delegation-switcher__badge{flex-shrink:0;border-radius:999px;background-color:var(--p-surface-200);color:var(--p-surface-600);font-size:.625rem;font-weight:600;line-height:1rem;padding-inline:.375rem;text-transform:uppercase;letter-spacing:.02em}:host-context([dir=\"rtl\"]) .mt-delegation-switcher__chevron{transform:scaleX(-1)}\n"] }]
904
+ }], propDecorators: { managePath: [{ type: i0.Input, args: [{ isSignal: true, alias: "managePath", required: false }] }], showManageLink: [{ type: i0.Input, args: [{ isSignal: true, alias: "showManageLink", required: false }] }], currentUser: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentUser", required: false }] }], closeRequested: [{ type: i0.Output, args: ["closeRequested"] }] } });
771
905
 
772
906
  /**
773
907
  * Topbar surface + controller for the delegation runtime (doc 05, 09).
@@ -795,10 +929,18 @@ class TopbarDelegationMenu {
795
929
  * e.g. embedded in the user-avatar dropdown via {@link DelegationMenuPanel}.
796
930
  */
797
931
  headless = input(false, ...(ngDevMode ? [{ debugName: "headless" }] : /* istanbul ignore next */ []));
932
+ /**
933
+ * Optional richer profile for the "you" row of the switcher. Forwarded to
934
+ * {@link DelegationMenuPanel}; omit it and the current user is derived from
935
+ * the assigned delegation rows.
936
+ */
937
+ currentUser = input(null, ...(ngDevMode ? [{ debugName: "currentUser" }] : /* istanbul ignore next */ []));
798
938
  facade = inject(DelegationSessionFacade);
799
939
  modal = inject(ModalService);
800
940
  transloco = inject(TranslocoService);
801
941
  destroyRef = inject(DestroyRef);
942
+ actions$ = inject(Actions);
943
+ toast = inject(ToastService);
802
944
  popover = viewChild('popover', ...(ngDevMode ? [{ debugName: "popover" }] : /* istanbul ignore next */ []));
803
945
  active = this.facade.active;
804
946
  candidates = this.facade.candidates;
@@ -842,6 +984,19 @@ class TopbarDelegationMenu {
842
984
  this.closePromptFlow();
843
985
  }
844
986
  });
987
+ // A resumed session is the one state change the user did not just ask for
988
+ // — it happens silently on page load. Say so, or "acting as someone else"
989
+ // looks like the app forgot who they are.
990
+ this.actions$
991
+ .pipe(ofActionSuccessful(ResumeDelegationSession), takeUntilDestroyed())
992
+ .subscribe(() => {
993
+ const delegatorName = this.onBehalfOf()?.displayName;
994
+ if (delegatorName) {
995
+ this.toast.info(this.transloco.translate('delegations.session.resumed', {
996
+ delegatorName,
997
+ }));
998
+ }
999
+ });
845
1000
  this.destroyRef.onDestroy(() => {
846
1001
  this.destroyed = true;
847
1002
  this.closePromptFlow();
@@ -964,7 +1119,7 @@ class TopbarDelegationMenu {
964
1119
  }
965
1120
  }
966
1121
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: TopbarDelegationMenu, deps: [], target: i0.ɵɵFactoryTarget.Component });
967
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: TopbarDelegationMenu, isStandalone: true, selector: "mt-topbar-delegation-menu", inputs: { managePath: { classPropertyName: "managePath", publicName: "managePath", isSignal: true, isRequired: false, transformFunction: null }, compact: { classPropertyName: "compact", publicName: "compact", isSignal: true, isRequired: false, transformFunction: null }, showManageLink: { classPropertyName: "showManageLink", publicName: "showManageLink", isSignal: true, isRequired: false, transformFunction: null }, promptOnCandidates: { classPropertyName: "promptOnCandidates", publicName: "promptOnCandidates", isSignal: true, isRequired: false, transformFunction: null }, headless: { classPropertyName: "headless", publicName: "headless", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "popover", first: true, predicate: ["popover"], descendants: true, isSignal: true }], ngImport: i0, template: "<ng-container *transloco=\"let t\">\r\n <!-- Headless = controller only (no topbar UI); the menu is surfaced in the\r\n host user dropdown via <mt-delegation-menu-panel>. -->\r\n @if (!headless() && mode() !== \"hidden\") {\r\n @if (mode() === \"active\") {\r\n <span class=\"sr-only\" role=\"status\" aria-live=\"polite\">\r\n {{\r\n t(\"delegations.session.banner\", {\r\n delegatorName: onBehalfOf()?.displayName,\r\n })\r\n }}.\r\n {{\r\n t(\"delegations.session.executedBy\", {\r\n actualUserName: executedBy()?.displayName,\r\n })\r\n }}\r\n </span>\r\n }\r\n\r\n <div class=\"mt-delegation-trigger flex items-center\">\r\n @if (mode() === \"active\") {\r\n <!-- Active: show the delegator like a user identity, with a switcher caret -->\r\n <button\r\n type=\"button\"\r\n class=\"mt-delegation-trigger__button mt-delegation-trigger__button--active flex min-w-0 max-w-full cursor-pointer items-center gap-2 rounded-full p-1 pe-2 text-current\"\r\n [attr.aria-label]=\"\r\n t('delegations.session.banner', {\r\n delegatorName: onBehalfOf()?.displayName,\r\n }) +\r\n '. ' +\r\n t('delegations.session.executedBy', {\r\n actualUserName: executedBy()?.displayName,\r\n })\r\n \"\r\n [attr.aria-expanded]=\"popoverOpen()\"\r\n aria-haspopup=\"dialog\"\r\n (click)=\"togglePopover($event)\"\r\n >\r\n <span class=\"relative shrink-0\">\r\n <mt-avatar\r\n [label]=\"initials(onBehalfOf())\"\r\n shape=\"circle\"\r\n styleClass=\"!size-7 !text-[0.7rem] !bg-primary-100 !text-primary-700\"\r\n ></mt-avatar>\r\n <span\r\n class=\"absolute -bottom-0.5 -end-0.5 flex size-3.5 items-center justify-center rounded-full bg-emerald-500 ring-2 ring-white\"\r\n aria-hidden=\"true\"\r\n >\r\n <mt-icon\r\n icon=\"user.users-check\"\r\n styleClass=\"text-[0.55rem] text-white\"\r\n ></mt-icon>\r\n </span>\r\n </span>\r\n @if (!compact()) {\r\n <span\r\n class=\"mt-delegation-trigger__label hidden min-w-0 flex-col text-start leading-tight md:flex\"\r\n >\r\n <span class=\"text-[0.625rem] font-medium uppercase opacity-60\">\r\n {{ t(\"delegations.session.actingOnBehalfOf\") }}\r\n </span>\r\n <span class=\"truncate text-sm font-semibold\">\r\n {{ onBehalfOf()?.displayName }}\r\n </span>\r\n </span>\r\n }\r\n <mt-icon\r\n icon=\"arrow.chevron-down\"\r\n styleClass=\"text-sm opacity-70\"\r\n ></mt-icon>\r\n </button>\r\n } @else {\r\n <!-- Candidates: plain icon button matching the other topbar icons -->\r\n <button\r\n type=\"button\"\r\n class=\"mt-delegation-trigger__icon relative flex size-9 cursor-pointer items-center justify-center rounded-full text-current\"\r\n [attr.aria-label]=\"t('delegations.session.availableTitle')\"\r\n [attr.aria-expanded]=\"popoverOpen()\"\r\n aria-haspopup=\"dialog\"\r\n (click)=\"togglePopover($event)\"\r\n >\r\n <mt-icon icon=\"user.users-plus\" styleClass=\"text-xl\"></mt-icon>\r\n @if (candidates().length > 0) {\r\n <span\r\n class=\"absolute -top-0.5 -end-0.5 inline-flex min-w-[1.05rem] items-center justify-center rounded-full bg-primary px-1 text-[0.625rem] font-bold leading-4 text-white ring-2 ring-white\"\r\n >\r\n {{ candidates().length }}\r\n </span>\r\n }\r\n </button>\r\n }\r\n </div>\r\n\r\n <p-popover\r\n #popover\r\n [pt]=\"{ content: { class: 'p-0!' } }\"\r\n (onShow)=\"popoverOpen.set(true)\"\r\n (onHide)=\"popoverOpen.set(false)\"\r\n >\r\n <div class=\"w-72 max-w-[88vw]\">\r\n <mt-delegation-menu-panel\r\n [managePath]=\"managePath()\"\r\n [showManageLink]=\"showManageLink()\"\r\n (closeRequested)=\"closePopover()\"\r\n ></mt-delegation-menu-panel>\r\n </div>\r\n </p-popover>\r\n }\r\n</ng-container>\r\n", styles: [":host{display:inline-flex;align-items:center;min-width:0;max-width:100%}.mt-delegation-trigger{min-width:0;max-width:100%}.mt-delegation-trigger__button,.mt-delegation-trigger__icon{transition:background-color .15s ease-out;border:1px solid transparent}.mt-delegation-trigger__button:hover,.mt-delegation-trigger__icon:hover{background-color:var(--p-surface-100, rgba(0, 0, 0, .05))}.mt-delegation-trigger__button--active{background-color:var(--p-surface-100, rgba(0, 0, 0, .05));border-color:var(--p-surface-200, rgba(0, 0, 0, .08))}.mt-delegation-trigger__button--active:hover{background-color:var(--p-surface-200, rgba(0, 0, 0, .08))}.mt-delegation-trigger__label{max-width:min(12rem,20vw);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}@media(max-width:1280px){.mt-delegation-trigger__label{max-width:8rem}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: Avatar, selector: "mt-avatar", inputs: ["label", "icon", "image", "styleClass", "size", "shape", "badge", "badgeSize", "badgeSeverity"], outputs: ["onImageError"] }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }, { kind: "component", type: Popover, selector: "p-popover", inputs: ["ariaLabel", "ariaLabelledBy", "dismissable", "style", "styleClass", "appendTo", "autoZIndex", "ariaCloseLabel", "baseZIndex", "focusOnShow", "showTransitionOptions", "hideTransitionOptions", "motionOptions"], outputs: ["onShow", "onHide"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: DelegationMenuPanel, selector: "mt-delegation-menu-panel", inputs: ["managePath", "showManageLink"], outputs: ["closeRequested"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1122
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: TopbarDelegationMenu, isStandalone: true, selector: "mt-topbar-delegation-menu", inputs: { managePath: { classPropertyName: "managePath", publicName: "managePath", isSignal: true, isRequired: false, transformFunction: null }, compact: { classPropertyName: "compact", publicName: "compact", isSignal: true, isRequired: false, transformFunction: null }, showManageLink: { classPropertyName: "showManageLink", publicName: "showManageLink", isSignal: true, isRequired: false, transformFunction: null }, promptOnCandidates: { classPropertyName: "promptOnCandidates", publicName: "promptOnCandidates", isSignal: true, isRequired: false, transformFunction: null }, headless: { classPropertyName: "headless", publicName: "headless", isSignal: true, isRequired: false, transformFunction: null }, currentUser: { classPropertyName: "currentUser", publicName: "currentUser", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "popover", first: true, predicate: ["popover"], descendants: true, isSignal: true }], ngImport: i0, template: "<ng-container *transloco=\"let t\">\r\n <!-- Headless = controller only (no topbar UI); the menu is surfaced in the\r\n host user dropdown via <mt-delegation-menu-panel>. -->\r\n @if (!headless() && mode() !== \"hidden\") {\r\n @if (mode() === \"active\") {\r\n <span class=\"sr-only\" role=\"status\" aria-live=\"polite\">\r\n {{\r\n t(\"delegations.session.banner\", {\r\n delegatorName: onBehalfOf()?.displayName,\r\n })\r\n }}.\r\n {{\r\n t(\"delegations.session.executedBy\", {\r\n actualUserName: executedBy()?.displayName,\r\n })\r\n }}\r\n </span>\r\n }\r\n\r\n <div class=\"mt-delegation-trigger flex items-center\">\r\n @if (mode() === \"active\") {\r\n <!-- Active: show the delegator like a user identity, with a switcher caret -->\r\n <button\r\n type=\"button\"\r\n class=\"mt-delegation-trigger__button mt-delegation-trigger__button--active flex min-w-0 max-w-full cursor-pointer items-center gap-2 rounded-full p-1 pe-2 text-current\"\r\n [attr.aria-label]=\"\r\n t('delegations.session.banner', {\r\n delegatorName: onBehalfOf()?.displayName,\r\n }) +\r\n '. ' +\r\n t('delegations.session.executedBy', {\r\n actualUserName: executedBy()?.displayName,\r\n })\r\n \"\r\n [attr.aria-expanded]=\"popoverOpen()\"\r\n aria-haspopup=\"dialog\"\r\n (click)=\"togglePopover($event)\"\r\n >\r\n <span class=\"relative shrink-0\">\r\n <mt-avatar\r\n [label]=\"initials(onBehalfOf())\"\r\n shape=\"circle\"\r\n styleClass=\"!size-7 !text-[0.7rem] !bg-primary-100 !text-primary-700\"\r\n ></mt-avatar>\r\n <span\r\n class=\"absolute -bottom-0.5 -end-0.5 flex size-3.5 items-center justify-center rounded-full bg-emerald-500 ring-2 ring-white\"\r\n aria-hidden=\"true\"\r\n >\r\n <mt-icon\r\n icon=\"user.users-check\"\r\n styleClass=\"text-[0.55rem] text-white\"\r\n ></mt-icon>\r\n </span>\r\n </span>\r\n @if (!compact()) {\r\n <span\r\n class=\"mt-delegation-trigger__label hidden min-w-0 flex-col text-start leading-tight md:flex\"\r\n >\r\n <span class=\"text-[0.625rem] font-medium uppercase opacity-60\">\r\n {{ t(\"delegations.session.actingOnBehalfOf\") }}\r\n </span>\r\n <span class=\"truncate text-sm font-semibold\">\r\n {{ onBehalfOf()?.displayName }}\r\n </span>\r\n </span>\r\n }\r\n <mt-icon\r\n icon=\"arrow.chevron-down\"\r\n styleClass=\"text-sm opacity-70\"\r\n ></mt-icon>\r\n </button>\r\n } @else {\r\n <!-- Candidates: plain icon button matching the other topbar icons -->\r\n <button\r\n type=\"button\"\r\n class=\"mt-delegation-trigger__icon relative flex size-9 cursor-pointer items-center justify-center rounded-full text-current\"\r\n [attr.aria-label]=\"t('delegations.session.availableTitle')\"\r\n [attr.aria-expanded]=\"popoverOpen()\"\r\n aria-haspopup=\"dialog\"\r\n (click)=\"togglePopover($event)\"\r\n >\r\n <mt-icon icon=\"user.users-plus\" styleClass=\"text-xl\"></mt-icon>\r\n @if (candidates().length > 0) {\r\n <span\r\n class=\"absolute -top-0.5 -end-0.5 inline-flex min-w-[1.05rem] items-center justify-center rounded-full bg-primary px-1 text-[0.625rem] font-bold leading-4 text-white ring-2 ring-white\"\r\n >\r\n {{ candidates().length }}\r\n </span>\r\n }\r\n </button>\r\n }\r\n </div>\r\n\r\n <p-popover\r\n #popover\r\n [pt]=\"{ content: { class: 'p-0!' } }\"\r\n (onShow)=\"popoverOpen.set(true)\"\r\n (onHide)=\"popoverOpen.set(false)\"\r\n >\r\n <div class=\"w-80 max-w-[92vw]\">\r\n <mt-delegation-menu-panel\r\n [managePath]=\"managePath()\"\r\n [showManageLink]=\"showManageLink()\"\r\n [currentUser]=\"currentUser()\"\r\n (closeRequested)=\"closePopover()\"\r\n ></mt-delegation-menu-panel>\r\n </div>\r\n </p-popover>\r\n }\r\n</ng-container>\r\n", styles: [":host{display:inline-flex;align-items:center;min-width:0;max-width:100%}.mt-delegation-trigger{min-width:0;max-width:100%}.mt-delegation-trigger__button,.mt-delegation-trigger__icon{transition:background-color .15s ease-out;border:1px solid transparent}.mt-delegation-trigger__button:hover,.mt-delegation-trigger__icon:hover{background-color:var(--p-surface-100, rgba(0, 0, 0, .05))}.mt-delegation-trigger__button--active{background-color:var(--p-surface-100, rgba(0, 0, 0, .05));border-color:var(--p-surface-200, rgba(0, 0, 0, .08))}.mt-delegation-trigger__button--active:hover{background-color:var(--p-surface-200, rgba(0, 0, 0, .08))}.mt-delegation-trigger__label{max-width:min(12rem,20vw);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}@media(max-width:1280px){.mt-delegation-trigger__label{max-width:8rem}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: Avatar, selector: "mt-avatar", inputs: ["label", "icon", "image", "styleClass", "size", "shape", "badge", "badgeSize", "badgeSeverity"], outputs: ["onImageError"] }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }, { kind: "component", type: Popover, selector: "p-popover", inputs: ["ariaLabel", "ariaLabelledBy", "dismissable", "style", "styleClass", "appendTo", "autoZIndex", "ariaCloseLabel", "baseZIndex", "focusOnShow", "showTransitionOptions", "hideTransitionOptions", "motionOptions"], outputs: ["onShow", "onHide"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: DelegationMenuPanel, selector: "mt-delegation-menu-panel", inputs: ["managePath", "showManageLink", "currentUser"], outputs: ["closeRequested"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
968
1123
  }
969
1124
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: TopbarDelegationMenu, decorators: [{
970
1125
  type: Component,
@@ -975,8 +1130,62 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
975
1130
  Popover,
976
1131
  TranslocoDirective,
977
1132
  DelegationMenuPanel,
978
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\r\n <!-- Headless = controller only (no topbar UI); the menu is surfaced in the\r\n host user dropdown via <mt-delegation-menu-panel>. -->\r\n @if (!headless() && mode() !== \"hidden\") {\r\n @if (mode() === \"active\") {\r\n <span class=\"sr-only\" role=\"status\" aria-live=\"polite\">\r\n {{\r\n t(\"delegations.session.banner\", {\r\n delegatorName: onBehalfOf()?.displayName,\r\n })\r\n }}.\r\n {{\r\n t(\"delegations.session.executedBy\", {\r\n actualUserName: executedBy()?.displayName,\r\n })\r\n }}\r\n </span>\r\n }\r\n\r\n <div class=\"mt-delegation-trigger flex items-center\">\r\n @if (mode() === \"active\") {\r\n <!-- Active: show the delegator like a user identity, with a switcher caret -->\r\n <button\r\n type=\"button\"\r\n class=\"mt-delegation-trigger__button mt-delegation-trigger__button--active flex min-w-0 max-w-full cursor-pointer items-center gap-2 rounded-full p-1 pe-2 text-current\"\r\n [attr.aria-label]=\"\r\n t('delegations.session.banner', {\r\n delegatorName: onBehalfOf()?.displayName,\r\n }) +\r\n '. ' +\r\n t('delegations.session.executedBy', {\r\n actualUserName: executedBy()?.displayName,\r\n })\r\n \"\r\n [attr.aria-expanded]=\"popoverOpen()\"\r\n aria-haspopup=\"dialog\"\r\n (click)=\"togglePopover($event)\"\r\n >\r\n <span class=\"relative shrink-0\">\r\n <mt-avatar\r\n [label]=\"initials(onBehalfOf())\"\r\n shape=\"circle\"\r\n styleClass=\"!size-7 !text-[0.7rem] !bg-primary-100 !text-primary-700\"\r\n ></mt-avatar>\r\n <span\r\n class=\"absolute -bottom-0.5 -end-0.5 flex size-3.5 items-center justify-center rounded-full bg-emerald-500 ring-2 ring-white\"\r\n aria-hidden=\"true\"\r\n >\r\n <mt-icon\r\n icon=\"user.users-check\"\r\n styleClass=\"text-[0.55rem] text-white\"\r\n ></mt-icon>\r\n </span>\r\n </span>\r\n @if (!compact()) {\r\n <span\r\n class=\"mt-delegation-trigger__label hidden min-w-0 flex-col text-start leading-tight md:flex\"\r\n >\r\n <span class=\"text-[0.625rem] font-medium uppercase opacity-60\">\r\n {{ t(\"delegations.session.actingOnBehalfOf\") }}\r\n </span>\r\n <span class=\"truncate text-sm font-semibold\">\r\n {{ onBehalfOf()?.displayName }}\r\n </span>\r\n </span>\r\n }\r\n <mt-icon\r\n icon=\"arrow.chevron-down\"\r\n styleClass=\"text-sm opacity-70\"\r\n ></mt-icon>\r\n </button>\r\n } @else {\r\n <!-- Candidates: plain icon button matching the other topbar icons -->\r\n <button\r\n type=\"button\"\r\n class=\"mt-delegation-trigger__icon relative flex size-9 cursor-pointer items-center justify-center rounded-full text-current\"\r\n [attr.aria-label]=\"t('delegations.session.availableTitle')\"\r\n [attr.aria-expanded]=\"popoverOpen()\"\r\n aria-haspopup=\"dialog\"\r\n (click)=\"togglePopover($event)\"\r\n >\r\n <mt-icon icon=\"user.users-plus\" styleClass=\"text-xl\"></mt-icon>\r\n @if (candidates().length > 0) {\r\n <span\r\n class=\"absolute -top-0.5 -end-0.5 inline-flex min-w-[1.05rem] items-center justify-center rounded-full bg-primary px-1 text-[0.625rem] font-bold leading-4 text-white ring-2 ring-white\"\r\n >\r\n {{ candidates().length }}\r\n </span>\r\n }\r\n </button>\r\n }\r\n </div>\r\n\r\n <p-popover\r\n #popover\r\n [pt]=\"{ content: { class: 'p-0!' } }\"\r\n (onShow)=\"popoverOpen.set(true)\"\r\n (onHide)=\"popoverOpen.set(false)\"\r\n >\r\n <div class=\"w-72 max-w-[88vw]\">\r\n <mt-delegation-menu-panel\r\n [managePath]=\"managePath()\"\r\n [showManageLink]=\"showManageLink()\"\r\n (closeRequested)=\"closePopover()\"\r\n ></mt-delegation-menu-panel>\r\n </div>\r\n </p-popover>\r\n }\r\n</ng-container>\r\n", styles: [":host{display:inline-flex;align-items:center;min-width:0;max-width:100%}.mt-delegation-trigger{min-width:0;max-width:100%}.mt-delegation-trigger__button,.mt-delegation-trigger__icon{transition:background-color .15s ease-out;border:1px solid transparent}.mt-delegation-trigger__button:hover,.mt-delegation-trigger__icon:hover{background-color:var(--p-surface-100, rgba(0, 0, 0, .05))}.mt-delegation-trigger__button--active{background-color:var(--p-surface-100, rgba(0, 0, 0, .05));border-color:var(--p-surface-200, rgba(0, 0, 0, .08))}.mt-delegation-trigger__button--active:hover{background-color:var(--p-surface-200, rgba(0, 0, 0, .08))}.mt-delegation-trigger__label{max-width:min(12rem,20vw);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}@media(max-width:1280px){.mt-delegation-trigger__label{max-width:8rem}}\n"] }]
979
- }], ctorParameters: () => [], propDecorators: { managePath: [{ type: i0.Input, args: [{ isSignal: true, alias: "managePath", required: false }] }], compact: [{ type: i0.Input, args: [{ isSignal: true, alias: "compact", required: false }] }], showManageLink: [{ type: i0.Input, args: [{ isSignal: true, alias: "showManageLink", required: false }] }], promptOnCandidates: [{ type: i0.Input, args: [{ isSignal: true, alias: "promptOnCandidates", required: false }] }], headless: [{ type: i0.Input, args: [{ isSignal: true, alias: "headless", required: false }] }], popover: [{ type: i0.ViewChild, args: ['popover', { isSignal: true }] }] } });
1133
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\r\n <!-- Headless = controller only (no topbar UI); the menu is surfaced in the\r\n host user dropdown via <mt-delegation-menu-panel>. -->\r\n @if (!headless() && mode() !== \"hidden\") {\r\n @if (mode() === \"active\") {\r\n <span class=\"sr-only\" role=\"status\" aria-live=\"polite\">\r\n {{\r\n t(\"delegations.session.banner\", {\r\n delegatorName: onBehalfOf()?.displayName,\r\n })\r\n }}.\r\n {{\r\n t(\"delegations.session.executedBy\", {\r\n actualUserName: executedBy()?.displayName,\r\n })\r\n }}\r\n </span>\r\n }\r\n\r\n <div class=\"mt-delegation-trigger flex items-center\">\r\n @if (mode() === \"active\") {\r\n <!-- Active: show the delegator like a user identity, with a switcher caret -->\r\n <button\r\n type=\"button\"\r\n class=\"mt-delegation-trigger__button mt-delegation-trigger__button--active flex min-w-0 max-w-full cursor-pointer items-center gap-2 rounded-full p-1 pe-2 text-current\"\r\n [attr.aria-label]=\"\r\n t('delegations.session.banner', {\r\n delegatorName: onBehalfOf()?.displayName,\r\n }) +\r\n '. ' +\r\n t('delegations.session.executedBy', {\r\n actualUserName: executedBy()?.displayName,\r\n })\r\n \"\r\n [attr.aria-expanded]=\"popoverOpen()\"\r\n aria-haspopup=\"dialog\"\r\n (click)=\"togglePopover($event)\"\r\n >\r\n <span class=\"relative shrink-0\">\r\n <mt-avatar\r\n [label]=\"initials(onBehalfOf())\"\r\n shape=\"circle\"\r\n styleClass=\"!size-7 !text-[0.7rem] !bg-primary-100 !text-primary-700\"\r\n ></mt-avatar>\r\n <span\r\n class=\"absolute -bottom-0.5 -end-0.5 flex size-3.5 items-center justify-center rounded-full bg-emerald-500 ring-2 ring-white\"\r\n aria-hidden=\"true\"\r\n >\r\n <mt-icon\r\n icon=\"user.users-check\"\r\n styleClass=\"text-[0.55rem] text-white\"\r\n ></mt-icon>\r\n </span>\r\n </span>\r\n @if (!compact()) {\r\n <span\r\n class=\"mt-delegation-trigger__label hidden min-w-0 flex-col text-start leading-tight md:flex\"\r\n >\r\n <span class=\"text-[0.625rem] font-medium uppercase opacity-60\">\r\n {{ t(\"delegations.session.actingOnBehalfOf\") }}\r\n </span>\r\n <span class=\"truncate text-sm font-semibold\">\r\n {{ onBehalfOf()?.displayName }}\r\n </span>\r\n </span>\r\n }\r\n <mt-icon\r\n icon=\"arrow.chevron-down\"\r\n styleClass=\"text-sm opacity-70\"\r\n ></mt-icon>\r\n </button>\r\n } @else {\r\n <!-- Candidates: plain icon button matching the other topbar icons -->\r\n <button\r\n type=\"button\"\r\n class=\"mt-delegation-trigger__icon relative flex size-9 cursor-pointer items-center justify-center rounded-full text-current\"\r\n [attr.aria-label]=\"t('delegations.session.availableTitle')\"\r\n [attr.aria-expanded]=\"popoverOpen()\"\r\n aria-haspopup=\"dialog\"\r\n (click)=\"togglePopover($event)\"\r\n >\r\n <mt-icon icon=\"user.users-plus\" styleClass=\"text-xl\"></mt-icon>\r\n @if (candidates().length > 0) {\r\n <span\r\n class=\"absolute -top-0.5 -end-0.5 inline-flex min-w-[1.05rem] items-center justify-center rounded-full bg-primary px-1 text-[0.625rem] font-bold leading-4 text-white ring-2 ring-white\"\r\n >\r\n {{ candidates().length }}\r\n </span>\r\n }\r\n </button>\r\n }\r\n </div>\r\n\r\n <p-popover\r\n #popover\r\n [pt]=\"{ content: { class: 'p-0!' } }\"\r\n (onShow)=\"popoverOpen.set(true)\"\r\n (onHide)=\"popoverOpen.set(false)\"\r\n >\r\n <div class=\"w-80 max-w-[92vw]\">\r\n <mt-delegation-menu-panel\r\n [managePath]=\"managePath()\"\r\n [showManageLink]=\"showManageLink()\"\r\n [currentUser]=\"currentUser()\"\r\n (closeRequested)=\"closePopover()\"\r\n ></mt-delegation-menu-panel>\r\n </div>\r\n </p-popover>\r\n }\r\n</ng-container>\r\n", styles: [":host{display:inline-flex;align-items:center;min-width:0;max-width:100%}.mt-delegation-trigger{min-width:0;max-width:100%}.mt-delegation-trigger__button,.mt-delegation-trigger__icon{transition:background-color .15s ease-out;border:1px solid transparent}.mt-delegation-trigger__button:hover,.mt-delegation-trigger__icon:hover{background-color:var(--p-surface-100, rgba(0, 0, 0, .05))}.mt-delegation-trigger__button--active{background-color:var(--p-surface-100, rgba(0, 0, 0, .05));border-color:var(--p-surface-200, rgba(0, 0, 0, .08))}.mt-delegation-trigger__button--active:hover{background-color:var(--p-surface-200, rgba(0, 0, 0, .08))}.mt-delegation-trigger__label{max-width:min(12rem,20vw);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}@media(max-width:1280px){.mt-delegation-trigger__label{max-width:8rem}}\n"] }]
1134
+ }], ctorParameters: () => [], propDecorators: { managePath: [{ type: i0.Input, args: [{ isSignal: true, alias: "managePath", required: false }] }], compact: [{ type: i0.Input, args: [{ isSignal: true, alias: "compact", required: false }] }], showManageLink: [{ type: i0.Input, args: [{ isSignal: true, alias: "showManageLink", required: false }] }], promptOnCandidates: [{ type: i0.Input, args: [{ isSignal: true, alias: "promptOnCandidates", required: false }] }], headless: [{ type: i0.Input, args: [{ isSignal: true, alias: "headless", required: false }] }], currentUser: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentUser", required: false }] }], popover: [{ type: i0.ViewChild, args: ['popover', { isSignal: true }] }] } });
1135
+
1136
+ const STATUS_VISUAL = {
1137
+ Active: {
1138
+ i18nKey: 'delegations.status.active',
1139
+ styleClass: 'mt-status-chip mt-status-chip--active',
1140
+ },
1141
+ Scheduled: {
1142
+ i18nKey: 'delegations.status.scheduled',
1143
+ styleClass: 'mt-status-chip mt-status-chip--scheduled',
1144
+ },
1145
+ PendingApproval: {
1146
+ i18nKey: 'delegations.status.pendingApproval',
1147
+ styleClass: 'mt-status-chip mt-status-chip--pending',
1148
+ },
1149
+ InactiveToday: {
1150
+ i18nKey: 'delegations.status.inactiveToday',
1151
+ styleClass: 'mt-status-chip mt-status-chip--scheduled',
1152
+ },
1153
+ Expired: {
1154
+ i18nKey: 'delegations.status.expired',
1155
+ styleClass: 'mt-status-chip mt-status-chip--expired',
1156
+ },
1157
+ Rejected: {
1158
+ i18nKey: 'delegations.status.rejected',
1159
+ styleClass: 'mt-status-chip mt-status-chip--rejected',
1160
+ },
1161
+ Cancelled: {
1162
+ i18nKey: 'delegations.status.cancelled',
1163
+ styleClass: 'mt-status-chip mt-status-chip--cancelled',
1164
+ },
1165
+ };
1166
+ class DelegationStatusChip {
1167
+ status = input.required(...(ngDevMode ? [{ debugName: "status" }] : /* istanbul ignore next */ []));
1168
+ reasonCode = input(null, ...(ngDevMode ? [{ debugName: "reasonCode" }] : /* istanbul ignore next */ []));
1169
+ visual = computed(() => STATUS_VISUAL[this.status() === 'Scheduled' && this.reasonCode() === 'InactiveToday'
1170
+ ? 'InactiveToday'
1171
+ : this.status()] ?? STATUS_VISUAL.Scheduled, ...(ngDevMode ? [{ debugName: "visual" }] : /* istanbul ignore next */ []));
1172
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationStatusChip, deps: [], target: i0.ɵɵFactoryTarget.Component });
1173
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.8", type: DelegationStatusChip, isStandalone: true, selector: "mt-delegation-status-chip", inputs: { status: { classPropertyName: "status", publicName: "status", isSignal: true, isRequired: true, transformFunction: null }, reasonCode: { classPropertyName: "reasonCode", publicName: "reasonCode", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
1174
+ <ng-container *transloco="let t">
1175
+ @let v = visual();
1176
+ <mt-chip [label]="t(v.i18nKey)" [styleClass]="v.styleClass"></mt-chip>
1177
+ </ng-container>
1178
+ `, isInline: true, styles: [":host{display:inline-flex}:host ::ng-deep .mt-status-chip{font-weight:500;font-size:.75rem;border-radius:9999px;padding-inline:.625rem;padding-block:.125rem}:host ::ng-deep .mt-status-chip--active{background-color:#dcfce7;color:#166534}:host ::ng-deep .mt-status-chip--scheduled{background-color:#dbeafe;color:#1e40af}:host ::ng-deep .mt-status-chip--pending{background-color:#fef9c3;color:#854d0e}:host ::ng-deep .mt-status-chip--expired{background-color:#f3f4f6;color:#4b5563}:host ::ng-deep .mt-status-chip--rejected{background-color:#fee2e2;color:#991b1b}:host ::ng-deep .mt-status-chip--cancelled{background-color:#e5e7eb;color:#374151}:host-context(.dark) ::ng-deep .mt-status-chip--active{background-color:#22c55e33;color:#86efac}:host-context(.dark) ::ng-deep .mt-status-chip--scheduled{background-color:#3b82f633;color:#93c5fd}:host-context(.dark) ::ng-deep .mt-status-chip--pending{background-color:#eab30833;color:#fde047}:host-context(.dark) ::ng-deep .mt-status-chip--expired{background-color:#94a3b833;color:#cbd5e1}:host-context(.dark) ::ng-deep .mt-status-chip--rejected{background-color:#ef444433;color:#fca5a5}:host-context(.dark) ::ng-deep .mt-status-chip--cancelled{background-color:#94a3b826;color:#cbd5e1}\n"], dependencies: [{ kind: "component", type: Chip, selector: "mt-chip", inputs: ["label", "icon", "image", "removable", "removeIcon", "styleClass", "size"], outputs: ["onRemove", "onImageError"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }] });
1179
+ }
1180
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationStatusChip, decorators: [{
1181
+ type: Component,
1182
+ args: [{ selector: 'mt-delegation-status-chip', standalone: true, imports: [Chip, TranslocoDirective], template: `
1183
+ <ng-container *transloco="let t">
1184
+ @let v = visual();
1185
+ <mt-chip [label]="t(v.i18nKey)" [styleClass]="v.styleClass"></mt-chip>
1186
+ </ng-container>
1187
+ `, styles: [":host{display:inline-flex}:host ::ng-deep .mt-status-chip{font-weight:500;font-size:.75rem;border-radius:9999px;padding-inline:.625rem;padding-block:.125rem}:host ::ng-deep .mt-status-chip--active{background-color:#dcfce7;color:#166534}:host ::ng-deep .mt-status-chip--scheduled{background-color:#dbeafe;color:#1e40af}:host ::ng-deep .mt-status-chip--pending{background-color:#fef9c3;color:#854d0e}:host ::ng-deep .mt-status-chip--expired{background-color:#f3f4f6;color:#4b5563}:host ::ng-deep .mt-status-chip--rejected{background-color:#fee2e2;color:#991b1b}:host ::ng-deep .mt-status-chip--cancelled{background-color:#e5e7eb;color:#374151}:host-context(.dark) ::ng-deep .mt-status-chip--active{background-color:#22c55e33;color:#86efac}:host-context(.dark) ::ng-deep .mt-status-chip--scheduled{background-color:#3b82f633;color:#93c5fd}:host-context(.dark) ::ng-deep .mt-status-chip--pending{background-color:#eab30833;color:#fde047}:host-context(.dark) ::ng-deep .mt-status-chip--expired{background-color:#94a3b833;color:#cbd5e1}:host-context(.dark) ::ng-deep .mt-status-chip--rejected{background-color:#ef444433;color:#fca5a5}:host-context(.dark) ::ng-deep .mt-status-chip--cancelled{background-color:#94a3b826;color:#cbd5e1}\n"] }]
1188
+ }], propDecorators: { status: [{ type: i0.Input, args: [{ isSignal: true, alias: "status", required: true }] }], reasonCode: [{ type: i0.Input, args: [{ isSignal: true, alias: "reasonCode", required: false }] }] } });
980
1189
 
981
1190
  // ---------------------------------------------------------------------------
982
1191
  // Lists / detail
@@ -1729,11 +1938,11 @@ class RejectDelegationDialog {
1729
1938
  this.ref.close(false);
1730
1939
  }
1731
1940
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: RejectDelegationDialog, deps: [], target: i0.ɵɵFactoryTarget.Component });
1732
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: RejectDelegationDialog, isStandalone: true, selector: "mt-reject-delegation-dialog", inputs: { delegation: { classPropertyName: "delegation", publicName: "delegation", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\n <div\n [class]=\"modal.contentClass + ' flex flex-col gap-4 overflow-y-auto p-5'\"\n >\n <div class=\"flex flex-col gap-1\">\n <div class=\"text-sm text-surface-700\">\n {{ t(\"delegations.confirm.reject\") }}\n </div>\n <mt-entity-preview\n [data]=\"userEntity(delegation().delegator)\"\n ></mt-entity-preview>\n </div>\n\n <div class=\"flex flex-col gap-1\">\n <label\n class=\"text-sm font-medium text-surface-800\"\n for=\"mt-reject-reason\"\n >\n {{ t(\"delegations.form.rejectionReason\") }}\n <span class=\"text-red-600\">*</span>\n </label>\n <textarea\n id=\"mt-reject-reason\"\n rows=\"3\"\n class=\"w-full rounded-md border border-surface-300 px-3 py-2 text-sm focus:border-primary focus:outline-none\"\n [class.border-red-400]=\"submitted() && !isValid()\"\n [value]=\"reason()\"\n (input)=\"onReasonInput($event)\"\n [attr.aria-invalid]=\"submitted() && !isValid()\"\n [placeholder]=\"t('delegations.form.rejectionReason')\"\n ></textarea>\n @if (submitted() && !isValid()) {\n <span class=\"text-xs text-red-600\">\n {{ t(\"delegations.form.rejectReasonRequired\") }}\n </span>\n }\n </div>\n </div>\n\n <div [class]=\"modal.footerClass\">\n <mt-button\n variant=\"outlined\"\n color=\"secondary\"\n [label]=\"t('delegations.common.cancel')\"\n [disabled]=\"isBusy()\"\n (click)=\"cancel()\"\n ></mt-button>\n <mt-button\n color=\"danger\"\n icon=\"general.x-close\"\n [label]=\"t('delegations.action.reject')\"\n [loading]=\"isBusy()\"\n [disabled]=\"!isValid()\"\n (click)=\"confirm()\"\n ></mt-button>\n </div>\n</ng-container>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs", "ariaLabel"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: EntityPreview, selector: "mt-entity-preview", inputs: ["data", "attachmentShape"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1941
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: RejectDelegationDialog, isStandalone: true, selector: "mt-reject-delegation-dialog", inputs: { delegation: { classPropertyName: "delegation", publicName: "delegation", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\r\n <div\r\n [class]=\"modal.contentClass + ' flex flex-col gap-4 overflow-y-auto p-5'\"\r\n >\r\n <div class=\"flex flex-col gap-1\">\r\n <div class=\"text-sm text-surface-700\">\r\n {{ t(\"delegations.confirm.reject\") }}\r\n </div>\r\n <mt-entity-preview\r\n [data]=\"userEntity(delegation().delegator)\"\r\n ></mt-entity-preview>\r\n </div>\r\n\r\n <div class=\"flex flex-col gap-1\">\r\n <label\r\n class=\"text-sm font-medium text-surface-800\"\r\n for=\"mt-reject-reason\"\r\n >\r\n {{ t(\"delegations.form.rejectionReason\") }}\r\n <span class=\"text-red-600\">*</span>\r\n </label>\r\n <textarea\r\n id=\"mt-reject-reason\"\r\n rows=\"3\"\r\n class=\"w-full rounded-md border border-surface-300 px-3 py-2 text-sm focus:border-primary focus:outline-none\"\r\n [class.border-red-400]=\"submitted() && !isValid()\"\r\n [value]=\"reason()\"\r\n (input)=\"onReasonInput($event)\"\r\n [attr.aria-invalid]=\"submitted() && !isValid()\"\r\n [placeholder]=\"t('delegations.form.rejectionReason')\"\r\n ></textarea>\r\n @if (submitted() && !isValid()) {\r\n <span class=\"text-xs text-red-600\">\r\n {{ t(\"delegations.form.rejectReasonRequired\") }}\r\n </span>\r\n }\r\n </div>\r\n </div>\r\n\r\n <div [class]=\"modal.footerClass\">\r\n <mt-button\r\n variant=\"outlined\"\r\n color=\"secondary\"\r\n [label]=\"t('delegations.common.cancel')\"\r\n [disabled]=\"isBusy()\"\r\n (click)=\"cancel()\"\r\n ></mt-button>\r\n <mt-button\r\n color=\"danger\"\r\n icon=\"general.x-close\"\r\n [label]=\"t('delegations.action.reject')\"\r\n [loading]=\"isBusy()\"\r\n [disabled]=\"!isValid()\"\r\n (click)=\"confirm()\"\r\n ></mt-button>\r\n </div>\r\n</ng-container>\r\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs", "ariaLabel"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: EntityPreview, selector: "mt-entity-preview", inputs: ["data", "attachmentShape"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1733
1942
  }
1734
1943
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: RejectDelegationDialog, decorators: [{
1735
1944
  type: Component,
1736
- args: [{ selector: 'mt-reject-delegation-dialog', imports: [CommonModule, Button, EntityPreview, TranslocoDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\n <div\n [class]=\"modal.contentClass + ' flex flex-col gap-4 overflow-y-auto p-5'\"\n >\n <div class=\"flex flex-col gap-1\">\n <div class=\"text-sm text-surface-700\">\n {{ t(\"delegations.confirm.reject\") }}\n </div>\n <mt-entity-preview\n [data]=\"userEntity(delegation().delegator)\"\n ></mt-entity-preview>\n </div>\n\n <div class=\"flex flex-col gap-1\">\n <label\n class=\"text-sm font-medium text-surface-800\"\n for=\"mt-reject-reason\"\n >\n {{ t(\"delegations.form.rejectionReason\") }}\n <span class=\"text-red-600\">*</span>\n </label>\n <textarea\n id=\"mt-reject-reason\"\n rows=\"3\"\n class=\"w-full rounded-md border border-surface-300 px-3 py-2 text-sm focus:border-primary focus:outline-none\"\n [class.border-red-400]=\"submitted() && !isValid()\"\n [value]=\"reason()\"\n (input)=\"onReasonInput($event)\"\n [attr.aria-invalid]=\"submitted() && !isValid()\"\n [placeholder]=\"t('delegations.form.rejectionReason')\"\n ></textarea>\n @if (submitted() && !isValid()) {\n <span class=\"text-xs text-red-600\">\n {{ t(\"delegations.form.rejectReasonRequired\") }}\n </span>\n }\n </div>\n </div>\n\n <div [class]=\"modal.footerClass\">\n <mt-button\n variant=\"outlined\"\n color=\"secondary\"\n [label]=\"t('delegations.common.cancel')\"\n [disabled]=\"isBusy()\"\n (click)=\"cancel()\"\n ></mt-button>\n <mt-button\n color=\"danger\"\n icon=\"general.x-close\"\n [label]=\"t('delegations.action.reject')\"\n [loading]=\"isBusy()\"\n [disabled]=\"!isValid()\"\n (click)=\"confirm()\"\n ></mt-button>\n </div>\n</ng-container>\n" }]
1945
+ args: [{ selector: 'mt-reject-delegation-dialog', imports: [CommonModule, Button, EntityPreview, TranslocoDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\r\n <div\r\n [class]=\"modal.contentClass + ' flex flex-col gap-4 overflow-y-auto p-5'\"\r\n >\r\n <div class=\"flex flex-col gap-1\">\r\n <div class=\"text-sm text-surface-700\">\r\n {{ t(\"delegations.confirm.reject\") }}\r\n </div>\r\n <mt-entity-preview\r\n [data]=\"userEntity(delegation().delegator)\"\r\n ></mt-entity-preview>\r\n </div>\r\n\r\n <div class=\"flex flex-col gap-1\">\r\n <label\r\n class=\"text-sm font-medium text-surface-800\"\r\n for=\"mt-reject-reason\"\r\n >\r\n {{ t(\"delegations.form.rejectionReason\") }}\r\n <span class=\"text-red-600\">*</span>\r\n </label>\r\n <textarea\r\n id=\"mt-reject-reason\"\r\n rows=\"3\"\r\n class=\"w-full rounded-md border border-surface-300 px-3 py-2 text-sm focus:border-primary focus:outline-none\"\r\n [class.border-red-400]=\"submitted() && !isValid()\"\r\n [value]=\"reason()\"\r\n (input)=\"onReasonInput($event)\"\r\n [attr.aria-invalid]=\"submitted() && !isValid()\"\r\n [placeholder]=\"t('delegations.form.rejectionReason')\"\r\n ></textarea>\r\n @if (submitted() && !isValid()) {\r\n <span class=\"text-xs text-red-600\">\r\n {{ t(\"delegations.form.rejectReasonRequired\") }}\r\n </span>\r\n }\r\n </div>\r\n </div>\r\n\r\n <div [class]=\"modal.footerClass\">\r\n <mt-button\r\n variant=\"outlined\"\r\n color=\"secondary\"\r\n [label]=\"t('delegations.common.cancel')\"\r\n [disabled]=\"isBusy()\"\r\n (click)=\"cancel()\"\r\n ></mt-button>\r\n <mt-button\r\n color=\"danger\"\r\n icon=\"general.x-close\"\r\n [label]=\"t('delegations.action.reject')\"\r\n [loading]=\"isBusy()\"\r\n [disabled]=\"!isValid()\"\r\n (click)=\"confirm()\"\r\n ></mt-button>\r\n </div>\r\n</ng-container>\r\n" }]
1737
1946
  }], propDecorators: { delegation: [{ type: i0.Input, args: [{ isSignal: true, alias: "delegation", required: true }] }] } });
1738
1947
 
1739
1948
  class Delegations {
@@ -4146,5 +4355,5 @@ const appDelegationInterceptor = (req, next) => {
4146
4355
  * Generated bundle index. Do not edit.
4147
4356
  */
4148
4357
 
4149
- export { ApproveDelegation, CancelDelegation, ClearDelegationDetail, ClearScopePreview, CreateDelegationLegacy, CreateDelegationV2, DELEGATED_RUNTIME_REQUEST, DELEGATION_RUNTIME_CONFIG, DelegationDetailDrawer, DelegationForm, DelegationMenuPanel, DelegationSessionActionKey, DelegationSessionFacade, DelegationSessionState, DelegationStatusChip, Delegations, DelegationsActionKey, DelegationsFacade, DelegationsList, DelegationsState, EndDelegationSession, GetActiveAssignedDelegations, GetAdminDelegations, GetApprovalDelegations, GetAssignedDelegations, GetDelegationDetail, GetMyDelegations, GetScopeOptions, LoadDelegationCandidates, PreviewScope, RejectDelegation, RejectDelegationDialog, ScopePicker, StartDelegationSession, StartSessionDialog, SwitchDelegationSession, TopbarDelegationMenu, UpdateDelegationLegacy, UpdateDelegationV2, appDelegationInterceptor, provideDelegationRuntime, withDelegatedRuntime };
4358
+ export { ApproveDelegation, CancelDelegation, ClearDelegationDetail, ClearScopePreview, CreateDelegationLegacy, CreateDelegationV2, DELEGATED_RUNTIME_REQUEST, DELEGATION_RUNTIME_CONFIG, DelegationDetailDrawer, DelegationForm, DelegationMenuPanel, DelegationSessionActionKey, DelegationSessionFacade, DelegationSessionResumeStore, DelegationSessionState, DelegationStatusChip, Delegations, DelegationsActionKey, DelegationsFacade, DelegationsList, DelegationsState, EndDelegationSession, GetActiveAssignedDelegations, GetAdminDelegations, GetApprovalDelegations, GetAssignedDelegations, GetDelegationDetail, GetMyDelegations, GetScopeOptions, LoadDelegationCandidates, PreviewScope, RejectDelegation, RejectDelegationDialog, ResumeDelegationSession, ScopePicker, StartDelegationSession, StartSessionDialog, SwitchDelegationSession, TopbarDelegationMenu, UpdateDelegationLegacy, UpdateDelegationV2, appDelegationInterceptor, provideDelegationRuntime, withDelegatedRuntime };
4150
4359
  //# sourceMappingURL=masterteam-delegations.mjs.map