@masterteam/delegations 0.0.55 → 0.0.57

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, catchError, from, finalize, shareReplay, filter, 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,62 @@ 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, from the
57
+ * persisted delegation id — never a token.
58
+ *
59
+ * Pass `delegation` when the row is already in hand; otherwise the state loads
60
+ * the candidates itself, so a host can await this action **before** the
61
+ * authenticated shell mounts and boot straight into delegated mode instead of
62
+ * loading everything twice.
63
+ *
64
+ * Completes quietly when there is nothing to resume or the delegation is no
65
+ * longer startable — the user simply stays themselves.
66
+ */
67
+ class ResumeDelegationSession {
68
+ delegation;
69
+ static type = '[DelegationSession] Resume';
70
+ constructor(delegation = null) {
71
+ this.delegation = delegation;
72
+ }
73
+ }
74
+ /** End the current delegated session. Client-local only (doc 05). */
75
+ class EndDelegationSession {
76
+ reason;
77
+ static type = '[DelegationSession] End';
78
+ constructor(reason = 'Manual') {
79
+ this.reason = reason;
80
+ }
81
+ }
82
+ /** Switch directly from the current session to another delegation. */
83
+ class SwitchDelegationSession {
84
+ delegation;
85
+ static type = '[DelegationSession] Switch';
86
+ constructor(delegation) {
87
+ this.delegation = delegation;
88
+ }
89
+ }
90
+
35
91
  const DEFAULT_CONFIG = {
36
92
  resolveApplicationApiBaseUrl: () => '',
37
93
  resolvePromptNamespace: () => ({
@@ -58,7 +114,7 @@ function withDelegatedRuntime(context = new HttpContext()) {
58
114
  return context.set(DELEGATED_RUNTIME_REQUEST, true);
59
115
  }
60
116
 
61
- const STORAGE_KEY = 'mt.delegation.prompt-receipts.v1';
117
+ const STORAGE_KEY$1 = 'mt.delegation.prompt-receipts.v1';
62
118
  const MAX_RECEIPTS = 100;
63
119
  class DelegationPromptReceiptService {
64
120
  document = inject(DOCUMENT);
@@ -103,7 +159,7 @@ class DelegationPromptReceiptService {
103
159
  }
104
160
  read() {
105
161
  try {
106
- const raw = this.document.defaultView?.localStorage.getItem(STORAGE_KEY);
162
+ const raw = this.document.defaultView?.localStorage.getItem(STORAGE_KEY$1);
107
163
  const parsed = raw ? JSON.parse(raw) : [];
108
164
  return Array.isArray(parsed)
109
165
  ? parsed.filter((item) => typeof item === 'string')
@@ -116,7 +172,7 @@ class DelegationPromptReceiptService {
116
172
  write(receipts) {
117
173
  try {
118
174
  const bounded = [...new Set(receipts)].slice(-MAX_RECEIPTS);
119
- this.document.defaultView?.localStorage.setItem(STORAGE_KEY, JSON.stringify(bounded));
175
+ this.document.defaultView?.localStorage.setItem(STORAGE_KEY$1, JSON.stringify(bounded));
120
176
  }
121
177
  catch {
122
178
  // Storage can be unavailable; the in-memory claim still coalesces dialogs.
@@ -130,42 +186,92 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
130
186
  args: [{ providedIn: 'root' }]
131
187
  }] });
132
188
 
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;
189
+ const STORAGE_KEY = 'mt.delegation.session-resume.v1';
190
+ /**
191
+ * Lets a delegated session survive a page reload **without persisting the
192
+ * token**.
193
+ *
194
+ * Doc 05 is unambiguous: the delegation access token is memory-only, and it
195
+ * stays that way this store keeps a single opaque `delegationId` plus the
196
+ * owning user/app/tenant namespace. On the next boot the runtime calls
197
+ * `POST identity/delegations/{id}/session` again and mints a *fresh* token,
198
+ * which is what doc 09 already prescribes for restarting a session ("use the
199
+ * newly returned token because the scope hash or delegation version may have
200
+ * changed"). The backend re-authorizes from scratch every time, so a delegation
201
+ * that expired, was cancelled, or had its scope revoked while the tab was shut
202
+ * simply fails to resume and the user stays themselves.
203
+ *
204
+ * Never holds a token, a candidate record, a name, or an email.
205
+ */
206
+ class DelegationSessionResumeStore {
207
+ document = inject(DOCUMENT);
208
+ config = inject(DELEGATION_RUNTIME_CONFIG);
209
+ remember(delegationId) {
210
+ this.write({ ns: this.namespace(), delegationId });
143
211
  }
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;
212
+ /**
213
+ * The delegation to resume for the *current* user/app/tenant, or `null`. A
214
+ * marker left by a different actor is dropped rather than honoured, so
215
+ * signing in as someone else on a shared browser never resumes their
216
+ * delegation.
217
+ */
218
+ pending() {
219
+ const marker = this.read();
220
+ if (!marker) {
221
+ return null;
222
+ }
223
+ if (marker.ns !== this.namespace()) {
224
+ this.clear();
225
+ return null;
226
+ }
227
+ return marker.delegationId;
151
228
  }
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;
229
+ clear() {
230
+ try {
231
+ this.storage()?.removeItem(STORAGE_KEY);
232
+ }
233
+ catch {
234
+ // Storage can be unavailable (private mode, blocked cookies).
235
+ }
159
236
  }
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;
237
+ namespace() {
238
+ const { userId, applicationKey, tenantKey } = this.config.resolvePromptNamespace();
239
+ return [userId, applicationKey, tenantKey ?? ''].join('|');
240
+ }
241
+ read() {
242
+ try {
243
+ const raw = this.storage()?.getItem(STORAGE_KEY);
244
+ const parsed = raw ? JSON.parse(raw) : null;
245
+ if (typeof parsed !== 'object' ||
246
+ parsed === null ||
247
+ typeof parsed.ns !== 'string' ||
248
+ typeof parsed.delegationId !== 'number') {
249
+ return null;
250
+ }
251
+ return parsed;
252
+ }
253
+ catch {
254
+ return null;
255
+ }
167
256
  }
257
+ write(marker) {
258
+ try {
259
+ this.storage()?.setItem(STORAGE_KEY, JSON.stringify(marker));
260
+ }
261
+ catch {
262
+ // Storage can be unavailable; delegated mode then simply ends on reload.
263
+ }
264
+ }
265
+ storage() {
266
+ return this.document.defaultView?.localStorage ?? null;
267
+ }
268
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationSessionResumeStore, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
269
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationSessionResumeStore, providedIn: 'root' });
168
270
  }
271
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationSessionResumeStore, decorators: [{
272
+ type: Injectable,
273
+ args: [{ providedIn: 'root' }]
274
+ }] });
169
275
 
170
276
  var DelegationSessionActionKey;
171
277
  (function (DelegationSessionActionKey) {
@@ -255,6 +361,9 @@ let DelegationSessionState = class DelegationSessionState {
255
361
  * or open the availability prompt — after sign-out.
256
362
  */
257
363
  candidatesGeneration = 0;
364
+ resumeStore = inject(DelegationSessionResumeStore);
365
+ /** One resume attempt per page load — a dead delegation must not retry. */
366
+ resumeAttempted = false;
258
367
  constructor() {
259
368
  this.actions$
260
369
  .pipe(ofActionSuccessful(GatewayLoginSuccessShell))
@@ -323,6 +432,9 @@ let DelegationSessionState = class DelegationSessionState {
323
432
  (response.data?.totalCount ?? 0) <= pageSize) {
324
433
  queueMicrotask(() => this.store.dispatch(new EndDelegationSession('ScopeChanged')));
325
434
  }
435
+ if (!active && page === 1) {
436
+ this.tryResume(candidates);
437
+ }
326
438
  return {
327
439
  candidates,
328
440
  candidatesPage: response.data?.page ?? page,
@@ -332,6 +444,62 @@ let DelegationSessionState = class DelegationSessionState {
332
444
  },
333
445
  });
334
446
  }
447
+ /**
448
+ * Fallback resume for hosts that do not await {@link ResumeDelegationSession}
449
+ * before mounting their authenticated shell: pick the session back up as soon
450
+ * as the candidates arrive.
451
+ *
452
+ * Prefer the awaited path — resuming after the shell has already loaded means
453
+ * every actor-scoped read runs twice, once as the real user and again under
454
+ * delegated authority.
455
+ */
456
+ tryResume(candidates) {
457
+ if (this.resumeAttempted || this.resumeStore.pending() == null) {
458
+ return;
459
+ }
460
+ const delegationId = this.resumeStore.pending();
461
+ const row = candidates.find((candidate) => candidate.delegationId === delegationId);
462
+ if (!row) {
463
+ // No longer startable (expired, cancelled, scope revoked) — forget it.
464
+ this.resumeAttempted = true;
465
+ this.resumeStore.clear();
466
+ return;
467
+ }
468
+ queueMicrotask(() => this.store.dispatch(new ResumeDelegationSession(row)).subscribe());
469
+ }
470
+ resume(ctx, { delegation }) {
471
+ const delegationId = this.resumeStore.pending();
472
+ if (ctx.getState().active || delegationId == null) {
473
+ return EMPTY;
474
+ }
475
+ // Claim the attempt up front so the nested candidate load below does not
476
+ // also fire `tryResume` for the same delegation.
477
+ this.resumeAttempted = true;
478
+ const known = delegation?.delegationId === delegationId
479
+ ? delegation
480
+ : ctx
481
+ .getState()
482
+ .candidates.find((candidate) => candidate.delegationId === delegationId);
483
+ const start$ = known
484
+ ? this.startSession(ctx, known, null, 'ResumeSession')
485
+ : this.store.dispatch(new LoadDelegationCandidates()).pipe(switchMap(() => {
486
+ const row = ctx
487
+ .getState()
488
+ .candidates.find((candidate) => candidate.delegationId === delegationId);
489
+ if (!row) {
490
+ // Expired, cancelled, or scope revoked while the tab was closed.
491
+ this.resumeStore.clear();
492
+ return EMPTY;
493
+ }
494
+ return this.startSession(ctx, row, null, 'ResumeSession');
495
+ }));
496
+ // A failed resume is not the caller's problem: drop the marker and let the
497
+ // user carry on as themselves rather than blocking the boot on an error.
498
+ return start$.pipe(catchError(() => {
499
+ this.resumeStore.clear();
500
+ return EMPTY;
501
+ }));
502
+ }
335
503
  start(ctx, { delegation }) {
336
504
  const previous = ctx.getState().active;
337
505
  return this.startSession(ctx, delegation, previous, 'StartSession');
@@ -352,6 +520,9 @@ let DelegationSessionState = class DelegationSessionState {
352
520
  delegationVersion: response.data.delegationVersion,
353
521
  };
354
522
  this.scheduleExpiry(active);
523
+ // Id only — see `DelegationSessionResumeStore`. Never the token.
524
+ this.resumeStore.remember(delegation.delegationId);
525
+ this.resumeAttempted = true;
355
526
  return { active };
356
527
  },
357
528
  }).pipe(switchMap(() => from(Promise.resolve(this.runtime.onContextChanged({
@@ -364,6 +535,10 @@ let DelegationSessionState = class DelegationSessionState {
364
535
  // Client-local only — there is no server end-session endpoint (doc 05).
365
536
  const previous = ctx.getState().active;
366
537
  const clearCandidates = shouldClearCandidates(reason);
538
+ // Whatever the reason, delegated mode is over — a reload must not bring it
539
+ // back. Also blocks a resume that is still queued behind this action.
540
+ this.resumeStore.clear();
541
+ this.resumeAttempted = true;
367
542
  // Logout / user / tenant / app switch must always drop the candidate list,
368
543
  // even when no delegated session was ever started. Leaving it behind kept
369
544
  // the previous actor's delegators in memory across sign-out, which is both
@@ -416,6 +591,9 @@ let DelegationSessionState = class DelegationSessionState {
416
591
  __decorate$1([
417
592
  Action(LoadDelegationCandidates)
418
593
  ], DelegationSessionState.prototype, "loadCandidates", null);
594
+ __decorate$1([
595
+ Action(ResumeDelegationSession)
596
+ ], DelegationSessionState.prototype, "resume", null);
419
597
  __decorate$1([
420
598
  Action(StartDelegationSession)
421
599
  ], DelegationSessionState.prototype, "start", null);
@@ -459,11 +637,12 @@ DelegationSessionState = __decorate$1([
459
637
  ], DelegationSessionState);
460
638
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationSessionState, decorators: [{
461
639
  type: Injectable
462
- }], ctorParameters: () => [], propDecorators: { loadCandidates: [], start: [], end: [], switch: [] } });
640
+ }], ctorParameters: () => [], propDecorators: { loadCandidates: [], resume: [], start: [], end: [], switch: [] } });
463
641
 
464
642
  class DelegationSessionFacade {
465
643
  store = inject(Store);
466
644
  promptReceipts = inject(DelegationPromptReceiptService);
645
+ resumeStore = inject(DelegationSessionResumeStore);
467
646
  endInFlight = null;
468
647
  // ---------------------------------------------------------------------------
469
648
  // Data slices
@@ -478,8 +657,15 @@ class DelegationSessionFacade {
478
657
  // ---------------------------------------------------------------------------
479
658
  /** On-behalf-of (delegator). */
480
659
  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 */ []));
660
+ /**
661
+ * Executed-by (the real signed-in user), known whether or not a session is
662
+ * active: every assigned row carries the current user as its `delegatedUser`,
663
+ * so the account switcher can list "you" alongside the delegators without the
664
+ * package depending on the host's auth state.
665
+ */
666
+ executedBy = computed(() => this.active()?.delegation.delegatedUser ??
667
+ this.candidates()[0]?.delegatedUser ??
668
+ null, ...(ngDevMode ? [{ debugName: "executedBy" }] : /* istanbul ignore next */ []));
483
669
  hasCandidates = computed(() => this.candidates().length > 0, ...(ngDevMode ? [{ debugName: "hasCandidates" }] : /* istanbul ignore next */ []));
484
670
  hasMoreCandidates = computed(() => this.candidates().length < this.candidatesTotalCount(), ...(ngDevMode ? [{ debugName: "hasMoreCandidates" }] : /* istanbul ignore next */ []));
485
671
  isStarting = computed(() => this.loadingActive().includes(DelegationSessionActionKey.StartSession), ...(ngDevMode ? [{ debugName: "isStarting" }] : /* istanbul ignore next */ []));
@@ -500,6 +686,23 @@ class DelegationSessionFacade {
500
686
  claimPromptCandidates(candidates) {
501
687
  return this.promptReceipts.claimUnseen(candidates);
502
688
  }
689
+ /**
690
+ * True when a previous session is waiting to be re-minted after a reload.
691
+ * Synchronous (a `localStorage` read), so a route guard can check it without
692
+ * paying for a round-trip on the overwhelmingly common no-delegation boot.
693
+ */
694
+ hasPendingResume() {
695
+ return this.resumeStore.pending() != null;
696
+ }
697
+ /**
698
+ * Re-mints the delegated session the user reloaded out of, and completes when
699
+ * delegated mode is in force (or when there is nothing to resume). Await this
700
+ * **before** mounting the authenticated shell so its reads run once, already
701
+ * delegated.
702
+ */
703
+ resumeSession() {
704
+ return this.store.dispatch(new ResumeDelegationSession());
705
+ }
503
706
  startSession(delegation) {
504
707
  return this.store.dispatch(new StartDelegationSession(delegation));
505
708
  }
@@ -591,11 +794,11 @@ class StartSessionDialog {
591
794
  this.ref.close(false);
592
795
  }
593
796
  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 });
797
+ 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
798
  }
596
799
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: StartSessionDialog, decorators: [{
597
800
  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" }]
801
+ 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
802
  }], propDecorators: { delegation: [{ type: i0.Input, args: [{ isSignal: true, alias: "delegation", required: true }] }], intent: [{ type: i0.Input, args: [{ isSignal: true, alias: "intent", required: false }] }] } });
600
803
 
601
804
  /**
@@ -617,74 +820,25 @@ class DelegationCandidatesPromptDialog {
617
820
  this.ref.close(null);
618
821
  }
619
822
  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 });
823
+ 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
824
  }
622
825
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationCandidatesPromptDialog, decorators: [{
623
826
  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" }]
827
+ 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
828
  }], propDecorators: { candidates: [{ type: i0.Input, args: [{ isSignal: true, alias: "candidates", required: true }] }] } });
626
829
 
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
830
  /**
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.
831
+ * Embeddable "acting as" account switcher (doc 05, 09).
832
+ *
833
+ * Renders one row per identity the user can operate as themselves first, then
834
+ * every delegator who has an active delegation assigned to them — with the
835
+ * current one checked. Picking a row starts, switches, or ends the delegated
836
+ * session; the {@link StartSessionDialog} still takes explicit consent before
837
+ * any session begins, so this list never changes authority on its own.
838
+ *
839
+ * Designed to be dropped inside a host menu (e.g. the user-avatar dropdown) or
840
+ * the topbar popover. Candidate loading + the post-login prompt stay in the
841
+ * always-mounted `TopbarDelegationMenu` controller.
688
842
  *
689
843
  * Emits `closeRequested` after any action so the host overlay can dismiss.
690
844
  */
@@ -692,25 +846,57 @@ class DelegationMenuPanel {
692
846
  /** Path to the management page, e.g. `/control-panel/delegations` or `/delegations`. */
693
847
  managePath = input('/delegations', ...(ngDevMode ? [{ debugName: "managePath" }] : /* istanbul ignore next */ []));
694
848
  showManageLink = input(true, ...(ngDevMode ? [{ debugName: "showManageLink" }] : /* istanbul ignore next */ []));
849
+ /**
850
+ * Optional richer profile for the "you" row. Falls back to the current user
851
+ * carried on the assigned delegation rows, so the host may omit it.
852
+ */
853
+ currentUser = input(null, ...(ngDevMode ? [{ debugName: "currentUser" }] : /* istanbul ignore next */ []));
695
854
  closeRequested = output();
696
855
  facade = inject(DelegationSessionFacade);
697
856
  modal = inject(ModalService);
698
857
  transloco = inject(TranslocoService);
858
+ toast = inject(ToastService);
699
859
  active = this.facade.active;
700
860
  candidates = this.facade.candidates;
701
861
  hasCandidates = this.facade.hasCandidates;
702
862
  hasMoreCandidates = this.facade.hasMoreCandidates;
703
863
  isLoadingCandidates = this.facade.isLoadingCandidates;
864
+ isBusy = this.facade.isStarting;
704
865
  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). */
866
+ /** The real signed-in user — host-supplied when available, derived otherwise. */
867
+ self = computed(() => this.currentUser() ?? this.facade.executedBy(), ...(ngDevMode ? [{ debugName: "self" }] : /* istanbul ignore next */ []));
868
+ /** Nothing to switch between and no session running — render nothing. */
869
+ visible = computed(() => !!this.active() || this.hasCandidates(), ...(ngDevMode ? [{ debugName: "visible" }] : /* istanbul ignore next */ []));
870
+ /** "You", then every delegator. The active identity carries the checkmark. */
871
+ identities = computed(() => {
872
+ const activeSession = this.active();
873
+ const activeId = activeSession?.delegation.delegationId ?? null;
874
+ const self = this.self();
875
+ const candidates = this.candidates();
876
+ // The identity in force is always listed, even if it has fallen off the
877
+ // first page of candidates — a switcher that cannot show who you currently
878
+ // are is worse than one extra row.
879
+ const rows = activeSession && !candidates.some((row) => row.delegationId === activeId)
880
+ ? [activeSession.delegation, ...candidates]
881
+ : candidates;
882
+ return [
883
+ {
884
+ row: null,
885
+ party: self,
886
+ isSelf: true,
887
+ isActive: activeId === null,
888
+ initials: this.initials(self),
889
+ },
890
+ ...rows.map((row) => ({
891
+ row,
892
+ party: row.delegator,
893
+ isSelf: false,
894
+ isActive: row.delegationId === activeId,
895
+ initials: this.initials(row.delegator),
896
+ })),
897
+ ];
898
+ }, ...(ngDevMode ? [{ debugName: "identities" }] : /* istanbul ignore next */ []));
899
+ /** Two-letter initials for an avatar (matches the user-menu pattern). */
714
900
  initials(party) {
715
901
  const name = party?.displayName?.trim() || party?.email?.trim() || '';
716
902
  const parts = name.split(/\s+/).filter(Boolean);
@@ -721,20 +907,24 @@ class DelegationMenuPanel {
721
907
  }
722
908
  return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
723
909
  }
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) {
910
+ select(option, event) {
735
911
  event.stopPropagation();
912
+ if (option.isActive || this.isBusy()) {
913
+ return;
914
+ }
736
915
  this.closeRequested.emit();
737
- this.facade.endSession('Manual').subscribe();
916
+ if (option.isSelf) {
917
+ // Back to your own authority. There is no server call (doc 05) — ending
918
+ // is local — so no confirmation dialog stands between the user and it.
919
+ this.facade.endSession('Manual').subscribe({
920
+ next: () => this.toast.success(this.transloco.translate('delegations.session.ended')),
921
+ });
922
+ return;
923
+ }
924
+ if (!option.row) {
925
+ return;
926
+ }
927
+ this.openConfirm(option.row, this.active() ? 'switch' : 'start');
738
928
  }
739
929
  onManage() {
740
930
  this.closeRequested.emit();
@@ -755,19 +945,12 @@ class DelegationMenuPanel {
755
945
  });
756
946
  }
757
947
  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 });
948
+ 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
949
  }
760
950
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationMenuPanel, decorators: [{
761
951
  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"] }] } });
952
+ 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"] }]
953
+ }], 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
954
 
772
955
  /**
773
956
  * Topbar surface + controller for the delegation runtime (doc 05, 09).
@@ -795,10 +978,18 @@ class TopbarDelegationMenu {
795
978
  * e.g. embedded in the user-avatar dropdown via {@link DelegationMenuPanel}.
796
979
  */
797
980
  headless = input(false, ...(ngDevMode ? [{ debugName: "headless" }] : /* istanbul ignore next */ []));
981
+ /**
982
+ * Optional richer profile for the "you" row of the switcher. Forwarded to
983
+ * {@link DelegationMenuPanel}; omit it and the current user is derived from
984
+ * the assigned delegation rows.
985
+ */
986
+ currentUser = input(null, ...(ngDevMode ? [{ debugName: "currentUser" }] : /* istanbul ignore next */ []));
798
987
  facade = inject(DelegationSessionFacade);
799
988
  modal = inject(ModalService);
800
989
  transloco = inject(TranslocoService);
801
990
  destroyRef = inject(DestroyRef);
991
+ actions$ = inject(Actions);
992
+ toast = inject(ToastService);
802
993
  popover = viewChild('popover', ...(ngDevMode ? [{ debugName: "popover" }] : /* istanbul ignore next */ []));
803
994
  active = this.facade.active;
804
995
  candidates = this.facade.candidates;
@@ -842,6 +1033,19 @@ class TopbarDelegationMenu {
842
1033
  this.closePromptFlow();
843
1034
  }
844
1035
  });
1036
+ // A resumed session is the one state change the user did not just ask for
1037
+ // — it happens silently on page load. Say so, or "acting as someone else"
1038
+ // looks like the app forgot who they are.
1039
+ this.actions$
1040
+ .pipe(ofActionSuccessful(ResumeDelegationSession), takeUntilDestroyed())
1041
+ .subscribe(() => {
1042
+ const delegatorName = this.onBehalfOf()?.displayName;
1043
+ if (delegatorName) {
1044
+ this.toast.info(this.transloco.translate('delegations.session.resumed', {
1045
+ delegatorName,
1046
+ }));
1047
+ }
1048
+ });
845
1049
  this.destroyRef.onDestroy(() => {
846
1050
  this.destroyed = true;
847
1051
  this.closePromptFlow();
@@ -964,7 +1168,7 @@ class TopbarDelegationMenu {
964
1168
  }
965
1169
  }
966
1170
  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 });
1171
+ 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
1172
  }
969
1173
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: TopbarDelegationMenu, decorators: [{
970
1174
  type: Component,
@@ -975,8 +1179,62 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
975
1179
  Popover,
976
1180
  TranslocoDirective,
977
1181
  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 }] }] } });
1182
+ ], 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"] }]
1183
+ }], 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 }] }] } });
1184
+
1185
+ const STATUS_VISUAL = {
1186
+ Active: {
1187
+ i18nKey: 'delegations.status.active',
1188
+ styleClass: 'mt-status-chip mt-status-chip--active',
1189
+ },
1190
+ Scheduled: {
1191
+ i18nKey: 'delegations.status.scheduled',
1192
+ styleClass: 'mt-status-chip mt-status-chip--scheduled',
1193
+ },
1194
+ PendingApproval: {
1195
+ i18nKey: 'delegations.status.pendingApproval',
1196
+ styleClass: 'mt-status-chip mt-status-chip--pending',
1197
+ },
1198
+ InactiveToday: {
1199
+ i18nKey: 'delegations.status.inactiveToday',
1200
+ styleClass: 'mt-status-chip mt-status-chip--scheduled',
1201
+ },
1202
+ Expired: {
1203
+ i18nKey: 'delegations.status.expired',
1204
+ styleClass: 'mt-status-chip mt-status-chip--expired',
1205
+ },
1206
+ Rejected: {
1207
+ i18nKey: 'delegations.status.rejected',
1208
+ styleClass: 'mt-status-chip mt-status-chip--rejected',
1209
+ },
1210
+ Cancelled: {
1211
+ i18nKey: 'delegations.status.cancelled',
1212
+ styleClass: 'mt-status-chip mt-status-chip--cancelled',
1213
+ },
1214
+ };
1215
+ class DelegationStatusChip {
1216
+ status = input.required(...(ngDevMode ? [{ debugName: "status" }] : /* istanbul ignore next */ []));
1217
+ reasonCode = input(null, ...(ngDevMode ? [{ debugName: "reasonCode" }] : /* istanbul ignore next */ []));
1218
+ visual = computed(() => STATUS_VISUAL[this.status() === 'Scheduled' && this.reasonCode() === 'InactiveToday'
1219
+ ? 'InactiveToday'
1220
+ : this.status()] ?? STATUS_VISUAL.Scheduled, ...(ngDevMode ? [{ debugName: "visual" }] : /* istanbul ignore next */ []));
1221
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationStatusChip, deps: [], target: i0.ɵɵFactoryTarget.Component });
1222
+ 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: `
1223
+ <ng-container *transloco="let t">
1224
+ @let v = visual();
1225
+ <mt-chip [label]="t(v.i18nKey)" [styleClass]="v.styleClass"></mt-chip>
1226
+ </ng-container>
1227
+ `, 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"] }] });
1228
+ }
1229
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationStatusChip, decorators: [{
1230
+ type: Component,
1231
+ args: [{ selector: 'mt-delegation-status-chip', standalone: true, imports: [Chip, TranslocoDirective], template: `
1232
+ <ng-container *transloco="let t">
1233
+ @let v = visual();
1234
+ <mt-chip [label]="t(v.i18nKey)" [styleClass]="v.styleClass"></mt-chip>
1235
+ </ng-container>
1236
+ `, 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"] }]
1237
+ }], propDecorators: { status: [{ type: i0.Input, args: [{ isSignal: true, alias: "status", required: true }] }], reasonCode: [{ type: i0.Input, args: [{ isSignal: true, alias: "reasonCode", required: false }] }] } });
980
1238
 
981
1239
  // ---------------------------------------------------------------------------
982
1240
  // Lists / detail
@@ -1729,11 +1987,11 @@ class RejectDelegationDialog {
1729
1987
  this.ref.close(false);
1730
1988
  }
1731
1989
  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 });
1990
+ 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
1991
  }
1734
1992
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: RejectDelegationDialog, decorators: [{
1735
1993
  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" }]
1994
+ 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
1995
  }], propDecorators: { delegation: [{ type: i0.Input, args: [{ isSignal: true, alias: "delegation", required: true }] }] } });
1738
1996
 
1739
1997
  class Delegations {
@@ -4146,5 +4404,5 @@ const appDelegationInterceptor = (req, next) => {
4146
4404
  * Generated bundle index. Do not edit.
4147
4405
  */
4148
4406
 
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 };
4407
+ 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
4408
  //# sourceMappingURL=masterteam-delegations.mjs.map