@masterteam/delegations 0.0.36 → 0.0.38

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.
@@ -26,8 +26,10 @@ import { Table } from '@masterteam/components/table';
26
26
  import * as i2 from '@angular/forms';
27
27
  import { FormsModule, FormControl, ReactiveFormsModule } from '@angular/forms';
28
28
  import { DynamicForm } from '@masterteam/forms/dynamic-form';
29
+ import * as i3 from '@angular/cdk/scrolling';
30
+ import { ScrollingModule } from '@angular/cdk/scrolling';
29
31
  import { TextField } from '@masterteam/components/text-field';
30
- import * as i3 from 'primeng/skeleton';
32
+ import * as i4 from 'primeng/skeleton';
31
33
  import { SkeletonModule } from 'primeng/skeleton';
32
34
 
33
35
  /** Fetch the active delegations the current user may start (user/activedelegations). */
@@ -641,9 +643,10 @@ class TopbarDelegationMenu {
641
643
  showManageLink = input(true, ...(ngDevMode ? [{ debugName: "showManageLink" }] : /* istanbul ignore next */ []));
642
644
  promptOnCandidates = input(true, ...(ngDevMode ? [{ debugName: "promptOnCandidates" }] : /* istanbul ignore next */ []));
643
645
  /**
644
- * Render no visible trigger/popover but keep loading candidates + the prompt.
645
- * Use when the delegation menu is embedded elsewhere (e.g. the user dropdown
646
- * via {@link DelegationMenuPanel}).
646
+ * Suppress the *candidates* trigger (surface them elsewhere, e.g. the user
647
+ * dropdown via {@link DelegationMenuPanel}) while keeping candidate loading +
648
+ * the post-login prompt. The *active* "Acting on behalf of" pill stays visible
649
+ * even when headless — an active delegated session must always be obvious.
647
650
  */
648
651
  headless = input(false, ...(ngDevMode ? [{ debugName: "headless" }] : /* istanbul ignore next */ []));
649
652
  facade = inject(DelegationSessionFacade);
@@ -723,7 +726,7 @@ class TopbarDelegationMenu {
723
726
  });
724
727
  }
725
728
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: TopbarDelegationMenu, deps: [], target: i0.ɵɵFactoryTarget.Component });
726
- 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\">\n @if (!headless() && mode() !== \"hidden\") {\n @if (mode() === \"active\") {\n <span class=\"sr-only\" role=\"status\" aria-live=\"polite\">\n {{\n t(\"delegations.session.banner\", {\n delegatorName: onBehalfOf()?.displayName,\n })\n }}.\n {{\n t(\"delegations.session.executedBy\", {\n actualUserName: executedBy()?.displayName,\n })\n }}\n </span>\n }\n\n <div class=\"mt-delegation-trigger flex items-center\">\n @if (mode() === \"active\") {\n <!-- Active: show the delegator like a user identity, with a switcher caret -->\n <button\n type=\"button\"\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\"\n [attr.aria-label]=\"\n t('delegations.session.banner', {\n delegatorName: onBehalfOf()?.displayName,\n }) +\n '. ' +\n t('delegations.session.executedBy', {\n actualUserName: executedBy()?.displayName,\n })\n \"\n (click)=\"togglePopover($event)\"\n >\n <span class=\"relative shrink-0\">\n <mt-avatar\n [label]=\"initials(onBehalfOf())\"\n shape=\"circle\"\n styleClass=\"!size-7 !text-[0.7rem] !bg-primary-100 !text-primary-700\"\n ></mt-avatar>\n <span\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\"\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 </span>\n @if (!compact()) {\n <span\n class=\"mt-delegation-trigger__label hidden min-w-0 flex-col text-start leading-tight md:flex\"\n >\n <span class=\"text-[0.625rem] font-medium uppercase opacity-60\">\n {{ t(\"delegations.session.actingOnBehalfOf\") }}\n </span>\n <span class=\"truncate text-sm font-semibold\">\n {{ onBehalfOf()?.displayName }}\n </span>\n </span>\n }\n <mt-icon\n icon=\"arrow.chevron-down\"\n styleClass=\"text-sm opacity-70\"\n ></mt-icon>\n </button>\n } @else {\n <!-- Candidates: plain icon button matching the other topbar icons -->\n <button\n type=\"button\"\n class=\"mt-delegation-trigger__icon relative flex size-9 cursor-pointer items-center justify-center rounded-full text-current\"\n [attr.aria-label]=\"t('delegations.session.availableTitle')\"\n (click)=\"togglePopover($event)\"\n >\n <mt-icon icon=\"user.users-plus\" styleClass=\"text-xl\"></mt-icon>\n @if (candidates().length > 0) {\n <span\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\"\n >\n {{ candidates().length }}\n </span>\n }\n </button>\n }\n </div>\n\n <p-popover #popover [pt]=\"{ content: { class: 'p-0!' } }\">\n <div class=\"w-72 max-w-[88vw]\">\n <mt-delegation-menu-panel\n [managePath]=\"managePath()\"\n [showManageLink]=\"showManageLink()\"\n (closeRequested)=\"closePopover()\"\n ></mt-delegation-menu-panel>\n </div>\n </p-popover>\n }\n</ng-container>\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 });
729
+ 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\">\n <!-- The active \"Acting on behalf of\" pill stays visible even when headless\n (candidates are surfaced elsewhere, e.g. the user dropdown); an active\n delegated session must always be obvious (doc 09 session banner). -->\n @if (mode() === \"active\" || (!headless() && mode() === \"candidates\")) {\n @if (mode() === \"active\") {\n <span class=\"sr-only\" role=\"status\" aria-live=\"polite\">\n {{\n t(\"delegations.session.banner\", {\n delegatorName: onBehalfOf()?.displayName,\n })\n }}.\n {{\n t(\"delegations.session.executedBy\", {\n actualUserName: executedBy()?.displayName,\n })\n }}\n </span>\n }\n\n <div class=\"mt-delegation-trigger flex items-center\">\n @if (mode() === \"active\") {\n <!-- Active: show the delegator like a user identity, with a switcher caret -->\n <button\n type=\"button\"\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\"\n [attr.aria-label]=\"\n t('delegations.session.banner', {\n delegatorName: onBehalfOf()?.displayName,\n }) +\n '. ' +\n t('delegations.session.executedBy', {\n actualUserName: executedBy()?.displayName,\n })\n \"\n (click)=\"togglePopover($event)\"\n >\n <span class=\"relative shrink-0\">\n <mt-avatar\n [label]=\"initials(onBehalfOf())\"\n shape=\"circle\"\n styleClass=\"!size-7 !text-[0.7rem] !bg-primary-100 !text-primary-700\"\n ></mt-avatar>\n <span\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\"\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 </span>\n @if (!compact()) {\n <span\n class=\"mt-delegation-trigger__label hidden min-w-0 flex-col text-start leading-tight md:flex\"\n >\n <span class=\"text-[0.625rem] font-medium uppercase opacity-60\">\n {{ t(\"delegations.session.actingOnBehalfOf\") }}\n </span>\n <span class=\"truncate text-sm font-semibold\">\n {{ onBehalfOf()?.displayName }}\n </span>\n </span>\n }\n <mt-icon\n icon=\"arrow.chevron-down\"\n styleClass=\"text-sm opacity-70\"\n ></mt-icon>\n </button>\n } @else {\n <!-- Candidates: plain icon button matching the other topbar icons -->\n <button\n type=\"button\"\n class=\"mt-delegation-trigger__icon relative flex size-9 cursor-pointer items-center justify-center rounded-full text-current\"\n [attr.aria-label]=\"t('delegations.session.availableTitle')\"\n (click)=\"togglePopover($event)\"\n >\n <mt-icon icon=\"user.users-plus\" styleClass=\"text-xl\"></mt-icon>\n @if (candidates().length > 0) {\n <span\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\"\n >\n {{ candidates().length }}\n </span>\n }\n </button>\n }\n </div>\n\n <p-popover #popover [pt]=\"{ content: { class: 'p-0!' } }\">\n <div class=\"w-72 max-w-[88vw]\">\n <mt-delegation-menu-panel\n [managePath]=\"managePath()\"\n [showManageLink]=\"showManageLink()\"\n (closeRequested)=\"closePopover()\"\n ></mt-delegation-menu-panel>\n </div>\n </p-popover>\n }\n</ng-container>\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 });
727
730
  }
728
731
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: TopbarDelegationMenu, decorators: [{
729
732
  type: Component,
@@ -734,7 +737,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
734
737
  Popover,
735
738
  TranslocoDirective,
736
739
  DelegationMenuPanel,
737
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\n @if (!headless() && mode() !== \"hidden\") {\n @if (mode() === \"active\") {\n <span class=\"sr-only\" role=\"status\" aria-live=\"polite\">\n {{\n t(\"delegations.session.banner\", {\n delegatorName: onBehalfOf()?.displayName,\n })\n }}.\n {{\n t(\"delegations.session.executedBy\", {\n actualUserName: executedBy()?.displayName,\n })\n }}\n </span>\n }\n\n <div class=\"mt-delegation-trigger flex items-center\">\n @if (mode() === \"active\") {\n <!-- Active: show the delegator like a user identity, with a switcher caret -->\n <button\n type=\"button\"\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\"\n [attr.aria-label]=\"\n t('delegations.session.banner', {\n delegatorName: onBehalfOf()?.displayName,\n }) +\n '. ' +\n t('delegations.session.executedBy', {\n actualUserName: executedBy()?.displayName,\n })\n \"\n (click)=\"togglePopover($event)\"\n >\n <span class=\"relative shrink-0\">\n <mt-avatar\n [label]=\"initials(onBehalfOf())\"\n shape=\"circle\"\n styleClass=\"!size-7 !text-[0.7rem] !bg-primary-100 !text-primary-700\"\n ></mt-avatar>\n <span\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\"\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 </span>\n @if (!compact()) {\n <span\n class=\"mt-delegation-trigger__label hidden min-w-0 flex-col text-start leading-tight md:flex\"\n >\n <span class=\"text-[0.625rem] font-medium uppercase opacity-60\">\n {{ t(\"delegations.session.actingOnBehalfOf\") }}\n </span>\n <span class=\"truncate text-sm font-semibold\">\n {{ onBehalfOf()?.displayName }}\n </span>\n </span>\n }\n <mt-icon\n icon=\"arrow.chevron-down\"\n styleClass=\"text-sm opacity-70\"\n ></mt-icon>\n </button>\n } @else {\n <!-- Candidates: plain icon button matching the other topbar icons -->\n <button\n type=\"button\"\n class=\"mt-delegation-trigger__icon relative flex size-9 cursor-pointer items-center justify-center rounded-full text-current\"\n [attr.aria-label]=\"t('delegations.session.availableTitle')\"\n (click)=\"togglePopover($event)\"\n >\n <mt-icon icon=\"user.users-plus\" styleClass=\"text-xl\"></mt-icon>\n @if (candidates().length > 0) {\n <span\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\"\n >\n {{ candidates().length }}\n </span>\n }\n </button>\n }\n </div>\n\n <p-popover #popover [pt]=\"{ content: { class: 'p-0!' } }\">\n <div class=\"w-72 max-w-[88vw]\">\n <mt-delegation-menu-panel\n [managePath]=\"managePath()\"\n [showManageLink]=\"showManageLink()\"\n (closeRequested)=\"closePopover()\"\n ></mt-delegation-menu-panel>\n </div>\n </p-popover>\n }\n</ng-container>\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"] }]
740
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\n <!-- The active \"Acting on behalf of\" pill stays visible even when headless\n (candidates are surfaced elsewhere, e.g. the user dropdown); an active\n delegated session must always be obvious (doc 09 session banner). -->\n @if (mode() === \"active\" || (!headless() && mode() === \"candidates\")) {\n @if (mode() === \"active\") {\n <span class=\"sr-only\" role=\"status\" aria-live=\"polite\">\n {{\n t(\"delegations.session.banner\", {\n delegatorName: onBehalfOf()?.displayName,\n })\n }}.\n {{\n t(\"delegations.session.executedBy\", {\n actualUserName: executedBy()?.displayName,\n })\n }}\n </span>\n }\n\n <div class=\"mt-delegation-trigger flex items-center\">\n @if (mode() === \"active\") {\n <!-- Active: show the delegator like a user identity, with a switcher caret -->\n <button\n type=\"button\"\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\"\n [attr.aria-label]=\"\n t('delegations.session.banner', {\n delegatorName: onBehalfOf()?.displayName,\n }) +\n '. ' +\n t('delegations.session.executedBy', {\n actualUserName: executedBy()?.displayName,\n })\n \"\n (click)=\"togglePopover($event)\"\n >\n <span class=\"relative shrink-0\">\n <mt-avatar\n [label]=\"initials(onBehalfOf())\"\n shape=\"circle\"\n styleClass=\"!size-7 !text-[0.7rem] !bg-primary-100 !text-primary-700\"\n ></mt-avatar>\n <span\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\"\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 </span>\n @if (!compact()) {\n <span\n class=\"mt-delegation-trigger__label hidden min-w-0 flex-col text-start leading-tight md:flex\"\n >\n <span class=\"text-[0.625rem] font-medium uppercase opacity-60\">\n {{ t(\"delegations.session.actingOnBehalfOf\") }}\n </span>\n <span class=\"truncate text-sm font-semibold\">\n {{ onBehalfOf()?.displayName }}\n </span>\n </span>\n }\n <mt-icon\n icon=\"arrow.chevron-down\"\n styleClass=\"text-sm opacity-70\"\n ></mt-icon>\n </button>\n } @else {\n <!-- Candidates: plain icon button matching the other topbar icons -->\n <button\n type=\"button\"\n class=\"mt-delegation-trigger__icon relative flex size-9 cursor-pointer items-center justify-center rounded-full text-current\"\n [attr.aria-label]=\"t('delegations.session.availableTitle')\"\n (click)=\"togglePopover($event)\"\n >\n <mt-icon icon=\"user.users-plus\" styleClass=\"text-xl\"></mt-icon>\n @if (candidates().length > 0) {\n <span\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\"\n >\n {{ candidates().length }}\n </span>\n }\n </button>\n }\n </div>\n\n <p-popover #popover [pt]=\"{ content: { class: 'p-0!' } }\">\n <div class=\"w-72 max-w-[88vw]\">\n <mt-delegation-menu-panel\n [managePath]=\"managePath()\"\n [showManageLink]=\"showManageLink()\"\n (closeRequested)=\"closePopover()\"\n ></mt-delegation-menu-panel>\n </div>\n </p-popover>\n }\n</ng-container>\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"] }]
738
741
  }], 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 }] }] } });
739
742
 
740
743
  // ---------------------------------------------------------------------------
@@ -1996,6 +1999,22 @@ class ScopePicker {
1996
1999
  /** Stable operation-node keys for the current scope grants. */
1997
2000
  selectedKeys = computed(() => new Set(this.scope().grants.map(delegationOperationNodeKey)), ...(ngDevMode ? [{ debugName: "selectedKeys" }] : /* istanbul ignore next */ []));
1998
2001
  selectedCount = computed(() => this.selectedKeys().size, ...(ngDevMode ? [{ debugName: "selectedCount" }] : /* istanbul ignore next */ []));
2002
+ /** Every selectable operation key (for the "select all" master toggle). */
2003
+ allLeafKeys = computed(() => Array.from(this.grantIndex().keys()), ...(ngDevMode ? [{ debugName: "allLeafKeys" }] : /* istanbul ignore next */ []));
2004
+ selectAllState = computed(() => {
2005
+ const all = this.allLeafKeys();
2006
+ if (!all.length)
2007
+ return 'unchecked';
2008
+ const selected = this.selectedKeys();
2009
+ let count = 0;
2010
+ for (const key of all) {
2011
+ if (selected.has(key))
2012
+ count++;
2013
+ }
2014
+ if (count === 0)
2015
+ return 'unchecked';
2016
+ return count === all.length ? 'checked' : 'indeterminate';
2017
+ }, ...(ngDevMode ? [{ debugName: "selectAllState" }] : /* istanbul ignore next */ []));
1999
2018
  searchTerm = signal('', ...(ngDevMode ? [{ debugName: "searchTerm" }] : /* istanbul ignore next */ []));
2000
2019
  expandedKeys = signal(new Set(), ...(ngDevMode ? [{ debugName: "expandedKeys" }] : /* istanbul ignore next */ []));
2001
2020
  filteredTree = computed(() => {
@@ -2020,6 +2039,28 @@ class ScopePicker {
2020
2039
  .map(prune)
2021
2040
  .filter((node) => !!node);
2022
2041
  }, ...(ngDevMode ? [{ debugName: "filteredTree" }] : /* istanbul ignore next */ []));
2042
+ /**
2043
+ * Flattened list of currently-visible rows (respecting expand state + search)
2044
+ * for CDK virtual scrolling — only the on-screen rows render, so large
2045
+ * permission catalogs stay fast. Recomputes on tree/expand/search changes,
2046
+ * NOT on selection toggles.
2047
+ */
2048
+ visibleNodes = computed(() => {
2049
+ const out = [];
2050
+ const searching = !!this.searchTerm().trim();
2051
+ const expanded = this.expandedKeys();
2052
+ const walk = (nodes, depth) => {
2053
+ for (const node of nodes) {
2054
+ out.push({ node, depth });
2055
+ const open = searching || expanded.has(node.key);
2056
+ if (open && node.children?.length) {
2057
+ walk(node.children, depth + 1);
2058
+ }
2059
+ }
2060
+ };
2061
+ walk(this.filteredTree(), 0);
2062
+ return out;
2063
+ }, ...(ngDevMode ? [{ debugName: "visibleNodes" }] : /* istanbul ignore next */ []));
2023
2064
  // --- App accessibility ----------------------------------------------------
2024
2065
  appAccessibilities = computed(() => this.options()?.appAccessibility ?? [], ...(ngDevMode ? [{ debugName: "appAccessibilities" }] : /* istanbul ignore next */ []));
2025
2066
  selectedAccessibilityKeys = signal(new Set(), ...(ngDevMode ? [{ debugName: "selectedAccessibilityKeys" }] : /* istanbul ignore next */ []));
@@ -2041,25 +2082,33 @@ class ScopePicker {
2041
2082
  return;
2042
2083
  }
2043
2084
  if (lastDelegatorUserId !== delegator) {
2085
+ // Only reset when the user actually switches between two real
2086
+ // delegators. The initial population (undefined -> id, e.g. edit
2087
+ // prefill or self-service current user arriving) must NOT wipe the
2088
+ // freshly-bound scope.
2089
+ const switchedDelegator = !!lastDelegatorUserId && !!delegator;
2044
2090
  lastDelegatorUserId = delegator;
2045
- this.scope.set(EMPTY_SCOPE$1);
2046
- this.selectedAccessibilityKeys.set(new Set());
2047
- this.searchTerm.set('');
2048
- this.activeScopeTab.set('permissions');
2049
- this.facade.clearScopePreview();
2091
+ if (switchedDelegator) {
2092
+ untracked(() => {
2093
+ this.scope.set(EMPTY_SCOPE$1);
2094
+ this.selectedAccessibilityKeys.set(new Set());
2095
+ this.searchTerm.set('');
2096
+ this.activeScopeTab.set('permissions');
2097
+ this.facade.clearScopePreview();
2098
+ });
2099
+ }
2050
2100
  }
2051
2101
  });
2052
- // Default-expand Template + Level nodes whenever options (re)load.
2102
+ // Default-expand Template nodes whenever options (re)load (levels collapse
2103
+ // so large catalogs open compact; search auto-expands matches).
2053
2104
  effect(() => {
2054
2105
  const model = this.treeModel();
2055
2106
  const keys = new Set();
2056
- const walk = (node) => {
2057
- if (node.kind === 'template' || node.kind === 'level') {
2107
+ for (const node of model) {
2108
+ if (node.kind === 'template') {
2058
2109
  keys.add(node.key);
2059
2110
  }
2060
- (node.children ?? []).forEach(walk);
2061
- };
2062
- model.forEach(walk);
2111
+ }
2063
2112
  untracked(() => this.expandedKeys.set(keys));
2064
2113
  });
2065
2114
  // Debounced scope preview.
@@ -2189,6 +2238,21 @@ class ScopePicker {
2189
2238
  }
2190
2239
  this.scope.update((scope) => ({ ...scope, grants: [] }));
2191
2240
  }
2241
+ /** Master "select all" toggle in the tree header. */
2242
+ toggleAll() {
2243
+ if (this.readonly()) {
2244
+ return;
2245
+ }
2246
+ if (this.selectAllState() === 'checked') {
2247
+ this.clearSelection();
2248
+ }
2249
+ else {
2250
+ this.applySelection(new Set(this.allLeafKeys()));
2251
+ }
2252
+ }
2253
+ trackVisible(_index, item) {
2254
+ return item.node.key;
2255
+ }
2192
2256
  // --- App accessibility ----------------------------------------------------
2193
2257
  toggleAccessibility(key) {
2194
2258
  if (this.readonly()) {
@@ -2247,13 +2311,14 @@ class ScopePicker {
2247
2311
  return this.activeLang() ?? this.transloco.getActiveLang();
2248
2312
  }
2249
2313
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: ScopePicker, deps: [], target: i0.ɵɵFactoryTarget.Component });
2250
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: ScopePicker, isStandalone: true, selector: "mt-scope-picker", inputs: { scope: { classPropertyName: "scope", publicName: "scope", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, adminMode: { classPropertyName: "adminMode", publicName: "adminMode", isSignal: true, isRequired: false, transformFunction: null }, delegatorUserId: { classPropertyName: "delegatorUserId", publicName: "delegatorUserId", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { scope: "scopeChange" }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\n <!-- Recursive node template -->\n <ng-template #nodeTpl let-node let-depth=\"depth\">\n @let state = nodeState(node);\n @let branch = hasChildren(node);\n <div\n class=\"group flex items-center gap-2 rounded-lg py-1.5 pe-2 transition-colors hover:bg-surface-50\"\n [style.padding-inline-start.rem]=\"0.25 + depth * 1.25\"\n >\n @if (branch) {\n <button\n type=\"button\"\n class=\"flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-md text-surface-400 hover:bg-surface-100 hover:text-surface-600\"\n [attr.aria-expanded]=\"isExpanded(node)\"\n [attr.aria-label]=\"nodeLabel(node)\"\n (click)=\"toggleExpand(node); $event.stopPropagation()\"\n >\n <mt-icon\n [icon]=\"isExpanded(node) ? 'arrow.chevron-down' : 'arrow.chevron-right'\"\n styleClass=\"text-base\"\n ></mt-icon>\n </button>\n } @else {\n <span class=\"size-6 shrink-0\"></span>\n }\n\n <button\n type=\"button\"\n role=\"checkbox\"\n [attr.aria-checked]=\"\n state === 'indeterminate' ? 'mixed' : state === 'checked'\n \"\n [attr.aria-label]=\"nodeLabel(node)\"\n class=\"flex size-[18px] shrink-0 items-center justify-center rounded-[5px] border transition-colors\"\n [class.cursor-pointer]=\"!readonly()\"\n [class.border-primary-500]=\"state !== 'unchecked'\"\n [class.bg-primary-500]=\"state !== 'unchecked'\"\n [class.text-white]=\"state !== 'unchecked'\"\n [class.border-surface-300]=\"state === 'unchecked'\"\n [class.bg-surface-0]=\"state === 'unchecked'\"\n [disabled]=\"readonly()\"\n (click)=\"toggle(node); $event.stopPropagation()\"\n >\n @if (state === \"checked\") {\n <svg\n viewBox=\"0 0 16 16\"\n class=\"size-3\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M3.5 8.5L6.5 11.5L12.5 4.5\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n } @else if (state === \"indeterminate\") {\n <span class=\"h-[2px] w-2.5 rounded-full bg-white\"></span>\n }\n </button>\n\n <button\n type=\"button\"\n class=\"flex min-w-0 flex-1 cursor-pointer items-center gap-2 text-start\"\n (click)=\"onRowClick(node)\"\n >\n <span\n class=\"truncate text-sm\"\n [class.font-semibold]=\"node.kind === 'template'\"\n [class.text-surface-900]=\"node.kind === 'template'\"\n [class.font-medium]=\"node.kind === 'level' || node.kind === 'module'\"\n [class.text-surface-800]=\"node.kind === 'level' || node.kind === 'module'\"\n [class.text-surface-600]=\"node.kind === 'operation'\"\n >\n {{ nodeLabel(node) }}\n </span>\n @if (node.kind === \"operation\" && node.isHighRisk) {\n <span\n class=\"shrink-0 rounded-full bg-amber-100 px-2 py-0.5 text-[0.625rem] font-medium text-amber-700\"\n >\n {{ t(\"delegations.scope.highRisk\") }}\n </span>\n }\n </button>\n </div>\n\n @if (branch && isExpanded(node)) {\n @for (child of node.children; track trackNode($index, child)) {\n <ng-container\n *ngTemplateOutlet=\"nodeTpl; context: { $implicit: child, depth: depth + 1 }\"\n ></ng-container>\n }\n }\n </ng-template>\n\n <div class=\"flex flex-col gap-3\">\n @if (isLoadingOptions()) {\n <mt-card [paddingless]=\"true\">\n <div class=\"flex flex-col gap-3 p-4\">\n <p-skeleton height=\"2.5rem\"></p-skeleton>\n @for (item of [0, 1, 2, 3, 4, 5]; track $index) {\n <p-skeleton height=\"2rem\"></p-skeleton>\n }\n </div>\n </mt-card>\n } @else {\n @if (errorOptions(); as errorMessage) {\n <div\n class=\"rounded-2xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700\"\n >\n {{ errorMessage }}\n </div>\n }\n\n @if (!hasOptions() && !errorOptions()) {\n <mt-card [paddingless]=\"true\">\n <div\n class=\"flex min-h-72 flex-col items-center justify-center gap-4 px-6 py-8 text-center\"\n >\n <div\n class=\"flex size-16 items-center justify-center rounded-3xl bg-surface-50 text-primary\"\n >\n <svg\n viewBox=\"0 0 64 64\"\n class=\"size-9\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <rect\n x=\"10\"\n y=\"12\"\n width=\"44\"\n height=\"38\"\n rx=\"12\"\n class=\"fill-primary/10 stroke-primary/35\"\n stroke-width=\"2\"\n />\n <path\n d=\"M21 27H43\"\n class=\"stroke-primary\"\n stroke-width=\"3\"\n stroke-linecap=\"round\"\n />\n <path\n d=\"M21 35H37\"\n class=\"stroke-primary/70\"\n stroke-width=\"3\"\n stroke-linecap=\"round\"\n />\n </svg>\n </div>\n <div class=\"space-y-1\">\n <p class=\"text-base font-medium text-surface-900\">\n {{ t(\"delegations.scope.permissionsTitle\") }}\n </p>\n <p class=\"text-sm text-surface-500\">\n {{ t(\"delegations.scope.noGrantableOptions\") }}\n </p>\n </div>\n </div>\n </mt-card>\n }\n\n @if (hasOptions()) {\n <mt-card [paddingless]=\"true\" class=\"overflow-hidden\">\n <!-- Inner view switch: Permissions / Pages & accessibility -->\n @if (hasAccessibility()) {\n <div class=\"border-b border-surface px-3 pt-2.5 pb-0\">\n <mt-tabs\n mode=\"underline\"\n [(active)]=\"activeScopeTab\"\n [options]=\"[\n {\n value: 'permissions',\n label: t('delegations.scope.permissionsTab'),\n badge: selectedCount() || null,\n },\n {\n value: 'accessibility',\n label: t('delegations.scope.accessibilityTitle'),\n badge: selectedAccessibilityCount() || null,\n },\n ]\"\n fluid\n ></mt-tabs>\n </div>\n }\n\n @if (activeScopeTab() === \"permissions\" || !hasAccessibility()) {\n <!-- Toolbar: table-style search + clear -->\n <div class=\"flex items-center gap-2 border-b border-surface px-3 py-2\">\n <div class=\"min-w-0 flex-1\">\n <mt-text-field\n [ngModel]=\"searchTerm()\"\n (ngModelChange)=\"searchTerm.set($event)\"\n icon=\"general.search-lg\"\n [placeholder]=\"t('delegations.scope.searchPlaceholder')\"\n ></mt-text-field>\n </div>\n @if (selectedCount() > 0) {\n <span class=\"shrink-0 text-xs font-medium text-surface-500\">\n {{ selectedCount() }} {{ t(\"delegations.scope.selected\") }}\n </span>\n <mt-button\n variant=\"text\"\n size=\"small\"\n [label]=\"t('delegations.scope.clear')\"\n [disabled]=\"readonly()\"\n (click)=\"clearSelection()\"\n ></mt-button>\n }\n </div>\n\n <!-- Permission tree -->\n <div class=\"max-h-[22rem] overflow-y-auto p-1.5\">\n @for (node of filteredTree(); track trackNode($index, node)) {\n <ng-container\n *ngTemplateOutlet=\"nodeTpl; context: { $implicit: node, depth: 0 }\"\n ></ng-container>\n } @empty {\n <p class=\"px-3 py-8 text-center text-sm text-surface-500\">\n {{ t(\"components.table.no-data-found\") }}\n </p>\n }\n </div>\n } @else {\n <!-- App accessibility -->\n <div class=\"flex flex-col gap-3 p-4\">\n <p class=\"text-xs text-surface-500\">\n {{ t(\"delegations.scope.accessibilityHint\") }}\n </p>\n <div class=\"flex flex-wrap gap-2\">\n @for (item of appAccessibilities(); track item.accessibilityKey) {\n @let selected = isAccessibilitySelected(item.accessibilityKey);\n <button\n type=\"button\"\n class=\"inline-flex cursor-pointer items-center gap-1.5 rounded-full border px-3 py-1.5 text-xs font-medium transition-colors\"\n [class.border-primary-400]=\"selected\"\n [class.bg-primary-50]=\"selected\"\n [class.text-primary-700]=\"selected\"\n [class.border-surface-200]=\"!selected\"\n [class.text-surface-600]=\"!selected\"\n [class.hover:border-primary-300]=\"!readonly()\"\n [disabled]=\"readonly()\"\n [attr.aria-pressed]=\"selected\"\n (click)=\"toggleAccessibility(item.accessibilityKey)\"\n >\n <span\n class=\"inline-flex size-1.5 rounded-full\"\n [class.bg-primary-500]=\"selected\"\n [class.bg-surface-300]=\"!selected\"\n ></span>\n {{ accessibilityLabel(item) }}\n </button>\n }\n </div>\n </div>\n }\n </mt-card>\n }\n\n <!-- Compact live scope preview -->\n <div\n class=\"flex flex-col gap-1.5 rounded-lg border border-surface bg-surface-50 px-3 py-2\"\n >\n <div class=\"flex items-center gap-2 text-xs\">\n <span class=\"shrink-0 font-semibold text-surface-700\">\n {{ t(\"delegations.scope.previewTitle\") }}:\n </span>\n @if (isPreviewing()) {\n <p-skeleton width=\"8rem\" height=\"0.7rem\"></p-skeleton>\n } @else if (preview(); as p) {\n @if (p.isValid) {\n <span class=\"min-w-0 flex-1 truncate text-surface-900\">\n {{\n getPreviewSummary(p.summary) ||\n t(\"delegations.column.scopeSummary\")\n }}\n </span>\n } @else {\n <span class=\"font-medium text-red-700\">\n {{ t(\"delegations.scope.previewInvalid\") }}\n </span>\n }\n } @else {\n <span class=\"text-surface-500\">\n {{ t(\"delegations.scope.noSelection\") }}\n </span>\n }\n </div>\n @if (preview(); as p) {\n @if (p.warnings.length > 0) {\n <ul class=\"list-inside list-disc space-y-0.5 text-xs text-amber-700\">\n @for (w of p.warnings; track $index) {\n <li>{{ w.message }}</li>\n }\n </ul>\n }\n @if (p.deniedItems.length > 0) {\n <ul class=\"list-inside list-disc space-y-0.5 text-xs text-red-700\">\n @for (d of p.deniedItems; track $index) {\n <li>\n {{ formatDeniedTarget(d.targetKey) }} /\n {{ formatDeniedOperation(d.operationKey) }} -\n {{ d.reasonCode }}\n </li>\n }\n </ul>\n }\n }\n </div>\n }\n </div>\n</ng-container>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { 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"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: Card, selector: "mt-card", inputs: ["class", "title", "paddingless"] }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }, { kind: "component", type: Tabs, selector: "mt-tabs", inputs: ["options", "optionLabel", "optionValue", "active", "mode", "moreLabel", "defaultIcon", "size", "fluid", "disabled", "searchThreshold"], outputs: ["activeChange", "onChange"] }, { kind: "component", type: TextField, selector: "mt-text-field", inputs: ["field", "hint", "label", "placeholder", "class", "type", "readonly", "pInputs", "required", "maxLength", "icon", "iconPosition"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i3.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2314
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: ScopePicker, isStandalone: true, selector: "mt-scope-picker", inputs: { scope: { classPropertyName: "scope", publicName: "scope", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, adminMode: { classPropertyName: "adminMode", publicName: "adminMode", isSignal: true, isRequired: false, transformFunction: null }, delegatorUserId: { classPropertyName: "delegatorUserId", publicName: "delegatorUserId", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { scope: "scopeChange" }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\n <!-- Reusable tri-state checkbox box -->\n <ng-template #checkbox let-state=\"state\">\n <span\n class=\"flex size-[18px] shrink-0 items-center justify-center rounded-[5px] border transition-colors\"\n [class.border-primary-500]=\"state !== 'unchecked'\"\n [class.bg-primary-500]=\"state !== 'unchecked'\"\n [class.text-white]=\"state !== 'unchecked'\"\n [class.border-surface-300]=\"state === 'unchecked'\"\n [class.bg-surface-0]=\"state === 'unchecked'\"\n >\n @if (state === \"checked\") {\n <svg\n viewBox=\"0 0 16 16\"\n class=\"size-3\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M3.5 8.5L6.5 11.5L12.5 4.5\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n } @else if (state === \"indeterminate\") {\n <span class=\"h-[2px] w-2.5 rounded-full bg-white\"></span>\n }\n </span>\n </ng-template>\n\n <div class=\"flex flex-col gap-3\">\n @if (isLoadingOptions()) {\n <mt-card [paddingless]=\"true\">\n <div class=\"flex flex-col gap-3 p-4\">\n <p-skeleton height=\"2.5rem\"></p-skeleton>\n @for (item of [0, 1, 2, 3, 4, 5]; track $index) {\n <p-skeleton height=\"2rem\"></p-skeleton>\n }\n </div>\n </mt-card>\n } @else {\n @if (errorOptions(); as errorMessage) {\n <div\n class=\"rounded-2xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700\"\n >\n {{ errorMessage }}\n </div>\n }\n\n @if (!hasOptions() && !errorOptions()) {\n <mt-card [paddingless]=\"true\">\n <div\n class=\"flex min-h-72 flex-col items-center justify-center gap-4 px-6 py-8 text-center\"\n >\n <div\n class=\"flex size-16 items-center justify-center rounded-3xl bg-surface-50 text-primary\"\n >\n <svg\n viewBox=\"0 0 64 64\"\n class=\"size-9\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <rect\n x=\"10\"\n y=\"12\"\n width=\"44\"\n height=\"38\"\n rx=\"12\"\n class=\"fill-primary/10 stroke-primary/35\"\n stroke-width=\"2\"\n />\n <path\n d=\"M21 27H43\"\n class=\"stroke-primary\"\n stroke-width=\"3\"\n stroke-linecap=\"round\"\n />\n <path\n d=\"M21 35H37\"\n class=\"stroke-primary/70\"\n stroke-width=\"3\"\n stroke-linecap=\"round\"\n />\n </svg>\n </div>\n <div class=\"space-y-1\">\n <p class=\"text-base font-medium text-surface-900\">\n {{ t(\"delegations.scope.permissionsTitle\") }}\n </p>\n <p class=\"text-sm text-surface-500\">\n {{ t(\"delegations.scope.noGrantableOptions\") }}\n </p>\n </div>\n </div>\n </mt-card>\n }\n\n @if (hasOptions()) {\n <mt-card [paddingless]=\"true\" class=\"overflow-hidden\">\n <!-- Inner view switch: Permissions / Pages & accessibility -->\n @if (hasAccessibility()) {\n <div class=\"border-b border-surface px-3 pt-2.5 pb-0\">\n <mt-tabs\n mode=\"underline\"\n [(active)]=\"activeScopeTab\"\n [options]=\"[\n {\n value: 'permissions',\n label: t('delegations.scope.permissionsTab'),\n badge: selectedCount() || null,\n },\n {\n value: 'accessibility',\n label: t('delegations.scope.accessibilityTitle'),\n badge: selectedAccessibilityCount() || null,\n },\n ]\"\n fluid\n ></mt-tabs>\n </div>\n }\n\n @if (activeScopeTab() === \"permissions\" || !hasAccessibility()) {\n <!-- Toolbar: table-style search + clear -->\n <div class=\"flex items-center gap-2 border-b border-surface px-3 py-2\">\n <div class=\"min-w-0 flex-1\">\n <mt-text-field\n [ngModel]=\"searchTerm()\"\n (ngModelChange)=\"searchTerm.set($event)\"\n icon=\"general.search-lg\"\n [placeholder]=\"t('delegations.scope.searchPlaceholder')\"\n ></mt-text-field>\n </div>\n @if (selectedCount() > 0) {\n <span class=\"shrink-0 text-xs font-medium text-surface-500\">\n {{ selectedCount() }} {{ t(\"delegations.scope.selected\") }}\n </span>\n <mt-button\n variant=\"text\"\n size=\"small\"\n [label]=\"t('delegations.scope.clear')\"\n [disabled]=\"readonly()\"\n (click)=\"clearSelection()\"\n ></mt-button>\n }\n </div>\n\n <!-- Select-all header -->\n <button\n type=\"button\"\n class=\"flex w-full cursor-pointer items-center gap-2 border-b border-surface px-3 py-2 text-start hover:bg-surface-50\"\n role=\"checkbox\"\n [attr.aria-checked]=\"\n selectAllState() === 'indeterminate'\n ? 'mixed'\n : selectAllState() === 'checked'\n \"\n [disabled]=\"readonly()\"\n (click)=\"toggleAll()\"\n >\n <ng-container\n *ngTemplateOutlet=\"checkbox; context: { state: selectAllState() }\"\n ></ng-container>\n <span class=\"text-xs font-semibold uppercase tracking-wide text-surface-500\">\n {{ t(\"delegations.scope.selectAll\") }}\n </span>\n </button>\n\n <!-- Virtualized permission tree -->\n @if (visibleNodes().length > 0) {\n <cdk-virtual-scroll-viewport\n itemSize=\"40\"\n class=\"block h-[20rem]\"\n >\n <div\n *cdkVirtualFor=\"\n let item of visibleNodes();\n trackBy: trackVisible\n \"\n class=\"group flex h-10 items-center gap-2 pe-2 transition-colors hover:bg-surface-50\"\n [style.padding-inline-start.rem]=\"0.5 + item.depth * 1.25\"\n >\n @if (hasChildren(item.node)) {\n <button\n type=\"button\"\n class=\"flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-md text-surface-400 hover:bg-surface-100 hover:text-surface-600\"\n [attr.aria-expanded]=\"isExpanded(item.node)\"\n [attr.aria-label]=\"nodeLabel(item.node)\"\n (click)=\"toggleExpand(item.node); $event.stopPropagation()\"\n >\n <mt-icon\n [icon]=\"\n isExpanded(item.node)\n ? 'arrow.chevron-down'\n : 'arrow.chevron-right'\n \"\n styleClass=\"text-base\"\n ></mt-icon>\n </button>\n } @else {\n <span class=\"size-6 shrink-0\"></span>\n }\n\n <button\n type=\"button\"\n role=\"checkbox\"\n [attr.aria-checked]=\"\n nodeState(item.node) === 'indeterminate'\n ? 'mixed'\n : nodeState(item.node) === 'checked'\n \"\n [attr.aria-label]=\"nodeLabel(item.node)\"\n class=\"shrink-0\"\n [class.cursor-pointer]=\"!readonly()\"\n [disabled]=\"readonly()\"\n (click)=\"toggle(item.node); $event.stopPropagation()\"\n >\n <ng-container\n *ngTemplateOutlet=\"\n checkbox;\n context: { state: nodeState(item.node) }\n \"\n ></ng-container>\n </button>\n\n <button\n type=\"button\"\n class=\"flex min-w-0 flex-1 cursor-pointer items-center gap-2 text-start\"\n (click)=\"onRowClick(item.node)\"\n >\n <span\n class=\"truncate text-sm\"\n [class.font-semibold]=\"item.node.kind === 'template'\"\n [class.text-surface-900]=\"item.node.kind === 'template'\"\n [class.font-medium]=\"\n item.node.kind === 'level' || item.node.kind === 'module'\n \"\n [class.text-surface-800]=\"\n item.node.kind === 'level' || item.node.kind === 'module'\n \"\n [class.text-surface-600]=\"item.node.kind === 'operation'\"\n >\n {{ nodeLabel(item.node) }}\n </span>\n @if (item.node.kind === \"operation\" && item.node.isHighRisk) {\n <span\n class=\"shrink-0 rounded-full bg-amber-100 px-2 py-0.5 text-[0.625rem] font-medium text-amber-700\"\n >\n {{ t(\"delegations.scope.highRisk\") }}\n </span>\n }\n </button>\n </div>\n </cdk-virtual-scroll-viewport>\n } @else {\n <p class=\"px-3 py-8 text-center text-sm text-surface-500\">\n {{ t(\"components.table.no-data-found\") }}\n </p>\n }\n } @else {\n <!-- App accessibility -->\n <div class=\"flex flex-col gap-3 p-4\">\n <p class=\"text-xs text-surface-500\">\n {{ t(\"delegations.scope.accessibilityHint\") }}\n </p>\n <div class=\"flex flex-wrap gap-2\">\n @for (item of appAccessibilities(); track item.accessibilityKey) {\n @let selected = isAccessibilitySelected(item.accessibilityKey);\n <button\n type=\"button\"\n class=\"inline-flex cursor-pointer items-center gap-1.5 rounded-full border px-3 py-1.5 text-xs font-medium transition-colors\"\n [class.border-primary-400]=\"selected\"\n [class.bg-primary-50]=\"selected\"\n [class.text-primary-700]=\"selected\"\n [class.border-surface-200]=\"!selected\"\n [class.text-surface-600]=\"!selected\"\n [class.hover:border-primary-300]=\"!readonly()\"\n [disabled]=\"readonly()\"\n [attr.aria-pressed]=\"selected\"\n (click)=\"toggleAccessibility(item.accessibilityKey)\"\n >\n <span\n class=\"inline-flex size-1.5 rounded-full\"\n [class.bg-primary-500]=\"selected\"\n [class.bg-surface-300]=\"!selected\"\n ></span>\n {{ accessibilityLabel(item) }}\n </button>\n }\n </div>\n </div>\n }\n </mt-card>\n }\n\n <!-- Compact live scope preview -->\n <div\n class=\"flex flex-col gap-1.5 rounded-lg border border-surface bg-surface-50 px-3 py-2\"\n >\n <div class=\"flex items-center gap-2 text-xs\">\n <span class=\"shrink-0 font-semibold text-surface-700\">\n {{ t(\"delegations.scope.previewTitle\") }}:\n </span>\n @if (isPreviewing()) {\n <p-skeleton width=\"8rem\" height=\"0.7rem\"></p-skeleton>\n } @else if (preview(); as p) {\n @if (p.isValid) {\n <span class=\"min-w-0 flex-1 truncate text-surface-900\">\n {{\n getPreviewSummary(p.summary) ||\n t(\"delegations.column.scopeSummary\")\n }}\n </span>\n } @else {\n <span class=\"font-medium text-red-700\">\n {{ t(\"delegations.scope.previewInvalid\") }}\n </span>\n }\n } @else {\n <span class=\"text-surface-500\">\n {{ t(\"delegations.scope.noSelection\") }}\n </span>\n }\n </div>\n @if (preview(); as p) {\n @if (p.warnings.length > 0) {\n <ul class=\"list-inside list-disc space-y-0.5 text-xs text-amber-700\">\n @for (w of p.warnings; track $index) {\n <li>{{ w.message }}</li>\n }\n </ul>\n }\n @if (p.deniedItems.length > 0) {\n <ul class=\"list-inside list-disc space-y-0.5 text-xs text-red-700\">\n @for (d of p.deniedItems; track $index) {\n <li>\n {{ formatDeniedTarget(d.targetKey) }} /\n {{ formatDeniedOperation(d.operationKey) }} -\n {{ d.reasonCode }}\n </li>\n }\n </ul>\n }\n }\n </div>\n }\n </div>\n</ng-container>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ScrollingModule }, { kind: "directive", type: i3.CdkFixedSizeVirtualScroll, selector: "cdk-virtual-scroll-viewport[itemSize]", inputs: ["itemSize", "minBufferPx", "maxBufferPx"] }, { kind: "directive", type: i3.CdkVirtualForOf, selector: "[cdkVirtualFor][cdkVirtualForOf]", inputs: ["cdkVirtualForOf", "cdkVirtualForTrackBy", "cdkVirtualForTemplate", "cdkVirtualForTemplateCacheSize"] }, { kind: "component", type: i3.CdkVirtualScrollViewport, selector: "cdk-virtual-scroll-viewport", inputs: ["orientation", "appendOnly"], outputs: ["scrolledIndexChange"] }, { 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"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: Card, selector: "mt-card", inputs: ["class", "title", "paddingless"] }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }, { kind: "component", type: Tabs, selector: "mt-tabs", inputs: ["options", "optionLabel", "optionValue", "active", "mode", "moreLabel", "defaultIcon", "size", "fluid", "disabled", "searchThreshold"], outputs: ["activeChange", "onChange"] }, { kind: "component", type: TextField, selector: "mt-text-field", inputs: ["field", "hint", "label", "placeholder", "class", "type", "readonly", "pInputs", "required", "maxLength", "icon", "iconPosition"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i4.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2251
2315
  }
2252
2316
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: ScopePicker, decorators: [{
2253
2317
  type: Component,
2254
2318
  args: [{ selector: 'mt-scope-picker', imports: [
2255
2319
  CommonModule,
2256
2320
  FormsModule,
2321
+ ScrollingModule,
2257
2322
  Button,
2258
2323
  Card,
2259
2324
  Icon,
@@ -2261,7 +2326,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
2261
2326
  TextField,
2262
2327
  SkeletonModule,
2263
2328
  TranslocoDirective,
2264
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\n <!-- Recursive node template -->\n <ng-template #nodeTpl let-node let-depth=\"depth\">\n @let state = nodeState(node);\n @let branch = hasChildren(node);\n <div\n class=\"group flex items-center gap-2 rounded-lg py-1.5 pe-2 transition-colors hover:bg-surface-50\"\n [style.padding-inline-start.rem]=\"0.25 + depth * 1.25\"\n >\n @if (branch) {\n <button\n type=\"button\"\n class=\"flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-md text-surface-400 hover:bg-surface-100 hover:text-surface-600\"\n [attr.aria-expanded]=\"isExpanded(node)\"\n [attr.aria-label]=\"nodeLabel(node)\"\n (click)=\"toggleExpand(node); $event.stopPropagation()\"\n >\n <mt-icon\n [icon]=\"isExpanded(node) ? 'arrow.chevron-down' : 'arrow.chevron-right'\"\n styleClass=\"text-base\"\n ></mt-icon>\n </button>\n } @else {\n <span class=\"size-6 shrink-0\"></span>\n }\n\n <button\n type=\"button\"\n role=\"checkbox\"\n [attr.aria-checked]=\"\n state === 'indeterminate' ? 'mixed' : state === 'checked'\n \"\n [attr.aria-label]=\"nodeLabel(node)\"\n class=\"flex size-[18px] shrink-0 items-center justify-center rounded-[5px] border transition-colors\"\n [class.cursor-pointer]=\"!readonly()\"\n [class.border-primary-500]=\"state !== 'unchecked'\"\n [class.bg-primary-500]=\"state !== 'unchecked'\"\n [class.text-white]=\"state !== 'unchecked'\"\n [class.border-surface-300]=\"state === 'unchecked'\"\n [class.bg-surface-0]=\"state === 'unchecked'\"\n [disabled]=\"readonly()\"\n (click)=\"toggle(node); $event.stopPropagation()\"\n >\n @if (state === \"checked\") {\n <svg\n viewBox=\"0 0 16 16\"\n class=\"size-3\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M3.5 8.5L6.5 11.5L12.5 4.5\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n } @else if (state === \"indeterminate\") {\n <span class=\"h-[2px] w-2.5 rounded-full bg-white\"></span>\n }\n </button>\n\n <button\n type=\"button\"\n class=\"flex min-w-0 flex-1 cursor-pointer items-center gap-2 text-start\"\n (click)=\"onRowClick(node)\"\n >\n <span\n class=\"truncate text-sm\"\n [class.font-semibold]=\"node.kind === 'template'\"\n [class.text-surface-900]=\"node.kind === 'template'\"\n [class.font-medium]=\"node.kind === 'level' || node.kind === 'module'\"\n [class.text-surface-800]=\"node.kind === 'level' || node.kind === 'module'\"\n [class.text-surface-600]=\"node.kind === 'operation'\"\n >\n {{ nodeLabel(node) }}\n </span>\n @if (node.kind === \"operation\" && node.isHighRisk) {\n <span\n class=\"shrink-0 rounded-full bg-amber-100 px-2 py-0.5 text-[0.625rem] font-medium text-amber-700\"\n >\n {{ t(\"delegations.scope.highRisk\") }}\n </span>\n }\n </button>\n </div>\n\n @if (branch && isExpanded(node)) {\n @for (child of node.children; track trackNode($index, child)) {\n <ng-container\n *ngTemplateOutlet=\"nodeTpl; context: { $implicit: child, depth: depth + 1 }\"\n ></ng-container>\n }\n }\n </ng-template>\n\n <div class=\"flex flex-col gap-3\">\n @if (isLoadingOptions()) {\n <mt-card [paddingless]=\"true\">\n <div class=\"flex flex-col gap-3 p-4\">\n <p-skeleton height=\"2.5rem\"></p-skeleton>\n @for (item of [0, 1, 2, 3, 4, 5]; track $index) {\n <p-skeleton height=\"2rem\"></p-skeleton>\n }\n </div>\n </mt-card>\n } @else {\n @if (errorOptions(); as errorMessage) {\n <div\n class=\"rounded-2xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700\"\n >\n {{ errorMessage }}\n </div>\n }\n\n @if (!hasOptions() && !errorOptions()) {\n <mt-card [paddingless]=\"true\">\n <div\n class=\"flex min-h-72 flex-col items-center justify-center gap-4 px-6 py-8 text-center\"\n >\n <div\n class=\"flex size-16 items-center justify-center rounded-3xl bg-surface-50 text-primary\"\n >\n <svg\n viewBox=\"0 0 64 64\"\n class=\"size-9\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <rect\n x=\"10\"\n y=\"12\"\n width=\"44\"\n height=\"38\"\n rx=\"12\"\n class=\"fill-primary/10 stroke-primary/35\"\n stroke-width=\"2\"\n />\n <path\n d=\"M21 27H43\"\n class=\"stroke-primary\"\n stroke-width=\"3\"\n stroke-linecap=\"round\"\n />\n <path\n d=\"M21 35H37\"\n class=\"stroke-primary/70\"\n stroke-width=\"3\"\n stroke-linecap=\"round\"\n />\n </svg>\n </div>\n <div class=\"space-y-1\">\n <p class=\"text-base font-medium text-surface-900\">\n {{ t(\"delegations.scope.permissionsTitle\") }}\n </p>\n <p class=\"text-sm text-surface-500\">\n {{ t(\"delegations.scope.noGrantableOptions\") }}\n </p>\n </div>\n </div>\n </mt-card>\n }\n\n @if (hasOptions()) {\n <mt-card [paddingless]=\"true\" class=\"overflow-hidden\">\n <!-- Inner view switch: Permissions / Pages & accessibility -->\n @if (hasAccessibility()) {\n <div class=\"border-b border-surface px-3 pt-2.5 pb-0\">\n <mt-tabs\n mode=\"underline\"\n [(active)]=\"activeScopeTab\"\n [options]=\"[\n {\n value: 'permissions',\n label: t('delegations.scope.permissionsTab'),\n badge: selectedCount() || null,\n },\n {\n value: 'accessibility',\n label: t('delegations.scope.accessibilityTitle'),\n badge: selectedAccessibilityCount() || null,\n },\n ]\"\n fluid\n ></mt-tabs>\n </div>\n }\n\n @if (activeScopeTab() === \"permissions\" || !hasAccessibility()) {\n <!-- Toolbar: table-style search + clear -->\n <div class=\"flex items-center gap-2 border-b border-surface px-3 py-2\">\n <div class=\"min-w-0 flex-1\">\n <mt-text-field\n [ngModel]=\"searchTerm()\"\n (ngModelChange)=\"searchTerm.set($event)\"\n icon=\"general.search-lg\"\n [placeholder]=\"t('delegations.scope.searchPlaceholder')\"\n ></mt-text-field>\n </div>\n @if (selectedCount() > 0) {\n <span class=\"shrink-0 text-xs font-medium text-surface-500\">\n {{ selectedCount() }} {{ t(\"delegations.scope.selected\") }}\n </span>\n <mt-button\n variant=\"text\"\n size=\"small\"\n [label]=\"t('delegations.scope.clear')\"\n [disabled]=\"readonly()\"\n (click)=\"clearSelection()\"\n ></mt-button>\n }\n </div>\n\n <!-- Permission tree -->\n <div class=\"max-h-[22rem] overflow-y-auto p-1.5\">\n @for (node of filteredTree(); track trackNode($index, node)) {\n <ng-container\n *ngTemplateOutlet=\"nodeTpl; context: { $implicit: node, depth: 0 }\"\n ></ng-container>\n } @empty {\n <p class=\"px-3 py-8 text-center text-sm text-surface-500\">\n {{ t(\"components.table.no-data-found\") }}\n </p>\n }\n </div>\n } @else {\n <!-- App accessibility -->\n <div class=\"flex flex-col gap-3 p-4\">\n <p class=\"text-xs text-surface-500\">\n {{ t(\"delegations.scope.accessibilityHint\") }}\n </p>\n <div class=\"flex flex-wrap gap-2\">\n @for (item of appAccessibilities(); track item.accessibilityKey) {\n @let selected = isAccessibilitySelected(item.accessibilityKey);\n <button\n type=\"button\"\n class=\"inline-flex cursor-pointer items-center gap-1.5 rounded-full border px-3 py-1.5 text-xs font-medium transition-colors\"\n [class.border-primary-400]=\"selected\"\n [class.bg-primary-50]=\"selected\"\n [class.text-primary-700]=\"selected\"\n [class.border-surface-200]=\"!selected\"\n [class.text-surface-600]=\"!selected\"\n [class.hover:border-primary-300]=\"!readonly()\"\n [disabled]=\"readonly()\"\n [attr.aria-pressed]=\"selected\"\n (click)=\"toggleAccessibility(item.accessibilityKey)\"\n >\n <span\n class=\"inline-flex size-1.5 rounded-full\"\n [class.bg-primary-500]=\"selected\"\n [class.bg-surface-300]=\"!selected\"\n ></span>\n {{ accessibilityLabel(item) }}\n </button>\n }\n </div>\n </div>\n }\n </mt-card>\n }\n\n <!-- Compact live scope preview -->\n <div\n class=\"flex flex-col gap-1.5 rounded-lg border border-surface bg-surface-50 px-3 py-2\"\n >\n <div class=\"flex items-center gap-2 text-xs\">\n <span class=\"shrink-0 font-semibold text-surface-700\">\n {{ t(\"delegations.scope.previewTitle\") }}:\n </span>\n @if (isPreviewing()) {\n <p-skeleton width=\"8rem\" height=\"0.7rem\"></p-skeleton>\n } @else if (preview(); as p) {\n @if (p.isValid) {\n <span class=\"min-w-0 flex-1 truncate text-surface-900\">\n {{\n getPreviewSummary(p.summary) ||\n t(\"delegations.column.scopeSummary\")\n }}\n </span>\n } @else {\n <span class=\"font-medium text-red-700\">\n {{ t(\"delegations.scope.previewInvalid\") }}\n </span>\n }\n } @else {\n <span class=\"text-surface-500\">\n {{ t(\"delegations.scope.noSelection\") }}\n </span>\n }\n </div>\n @if (preview(); as p) {\n @if (p.warnings.length > 0) {\n <ul class=\"list-inside list-disc space-y-0.5 text-xs text-amber-700\">\n @for (w of p.warnings; track $index) {\n <li>{{ w.message }}</li>\n }\n </ul>\n }\n @if (p.deniedItems.length > 0) {\n <ul class=\"list-inside list-disc space-y-0.5 text-xs text-red-700\">\n @for (d of p.deniedItems; track $index) {\n <li>\n {{ formatDeniedTarget(d.targetKey) }} /\n {{ formatDeniedOperation(d.operationKey) }} -\n {{ d.reasonCode }}\n </li>\n }\n </ul>\n }\n }\n </div>\n }\n </div>\n</ng-container>\n" }]
2329
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\n <!-- Reusable tri-state checkbox box -->\n <ng-template #checkbox let-state=\"state\">\n <span\n class=\"flex size-[18px] shrink-0 items-center justify-center rounded-[5px] border transition-colors\"\n [class.border-primary-500]=\"state !== 'unchecked'\"\n [class.bg-primary-500]=\"state !== 'unchecked'\"\n [class.text-white]=\"state !== 'unchecked'\"\n [class.border-surface-300]=\"state === 'unchecked'\"\n [class.bg-surface-0]=\"state === 'unchecked'\"\n >\n @if (state === \"checked\") {\n <svg\n viewBox=\"0 0 16 16\"\n class=\"size-3\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M3.5 8.5L6.5 11.5L12.5 4.5\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n } @else if (state === \"indeterminate\") {\n <span class=\"h-[2px] w-2.5 rounded-full bg-white\"></span>\n }\n </span>\n </ng-template>\n\n <div class=\"flex flex-col gap-3\">\n @if (isLoadingOptions()) {\n <mt-card [paddingless]=\"true\">\n <div class=\"flex flex-col gap-3 p-4\">\n <p-skeleton height=\"2.5rem\"></p-skeleton>\n @for (item of [0, 1, 2, 3, 4, 5]; track $index) {\n <p-skeleton height=\"2rem\"></p-skeleton>\n }\n </div>\n </mt-card>\n } @else {\n @if (errorOptions(); as errorMessage) {\n <div\n class=\"rounded-2xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700\"\n >\n {{ errorMessage }}\n </div>\n }\n\n @if (!hasOptions() && !errorOptions()) {\n <mt-card [paddingless]=\"true\">\n <div\n class=\"flex min-h-72 flex-col items-center justify-center gap-4 px-6 py-8 text-center\"\n >\n <div\n class=\"flex size-16 items-center justify-center rounded-3xl bg-surface-50 text-primary\"\n >\n <svg\n viewBox=\"0 0 64 64\"\n class=\"size-9\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <rect\n x=\"10\"\n y=\"12\"\n width=\"44\"\n height=\"38\"\n rx=\"12\"\n class=\"fill-primary/10 stroke-primary/35\"\n stroke-width=\"2\"\n />\n <path\n d=\"M21 27H43\"\n class=\"stroke-primary\"\n stroke-width=\"3\"\n stroke-linecap=\"round\"\n />\n <path\n d=\"M21 35H37\"\n class=\"stroke-primary/70\"\n stroke-width=\"3\"\n stroke-linecap=\"round\"\n />\n </svg>\n </div>\n <div class=\"space-y-1\">\n <p class=\"text-base font-medium text-surface-900\">\n {{ t(\"delegations.scope.permissionsTitle\") }}\n </p>\n <p class=\"text-sm text-surface-500\">\n {{ t(\"delegations.scope.noGrantableOptions\") }}\n </p>\n </div>\n </div>\n </mt-card>\n }\n\n @if (hasOptions()) {\n <mt-card [paddingless]=\"true\" class=\"overflow-hidden\">\n <!-- Inner view switch: Permissions / Pages & accessibility -->\n @if (hasAccessibility()) {\n <div class=\"border-b border-surface px-3 pt-2.5 pb-0\">\n <mt-tabs\n mode=\"underline\"\n [(active)]=\"activeScopeTab\"\n [options]=\"[\n {\n value: 'permissions',\n label: t('delegations.scope.permissionsTab'),\n badge: selectedCount() || null,\n },\n {\n value: 'accessibility',\n label: t('delegations.scope.accessibilityTitle'),\n badge: selectedAccessibilityCount() || null,\n },\n ]\"\n fluid\n ></mt-tabs>\n </div>\n }\n\n @if (activeScopeTab() === \"permissions\" || !hasAccessibility()) {\n <!-- Toolbar: table-style search + clear -->\n <div class=\"flex items-center gap-2 border-b border-surface px-3 py-2\">\n <div class=\"min-w-0 flex-1\">\n <mt-text-field\n [ngModel]=\"searchTerm()\"\n (ngModelChange)=\"searchTerm.set($event)\"\n icon=\"general.search-lg\"\n [placeholder]=\"t('delegations.scope.searchPlaceholder')\"\n ></mt-text-field>\n </div>\n @if (selectedCount() > 0) {\n <span class=\"shrink-0 text-xs font-medium text-surface-500\">\n {{ selectedCount() }} {{ t(\"delegations.scope.selected\") }}\n </span>\n <mt-button\n variant=\"text\"\n size=\"small\"\n [label]=\"t('delegations.scope.clear')\"\n [disabled]=\"readonly()\"\n (click)=\"clearSelection()\"\n ></mt-button>\n }\n </div>\n\n <!-- Select-all header -->\n <button\n type=\"button\"\n class=\"flex w-full cursor-pointer items-center gap-2 border-b border-surface px-3 py-2 text-start hover:bg-surface-50\"\n role=\"checkbox\"\n [attr.aria-checked]=\"\n selectAllState() === 'indeterminate'\n ? 'mixed'\n : selectAllState() === 'checked'\n \"\n [disabled]=\"readonly()\"\n (click)=\"toggleAll()\"\n >\n <ng-container\n *ngTemplateOutlet=\"checkbox; context: { state: selectAllState() }\"\n ></ng-container>\n <span class=\"text-xs font-semibold uppercase tracking-wide text-surface-500\">\n {{ t(\"delegations.scope.selectAll\") }}\n </span>\n </button>\n\n <!-- Virtualized permission tree -->\n @if (visibleNodes().length > 0) {\n <cdk-virtual-scroll-viewport\n itemSize=\"40\"\n class=\"block h-[20rem]\"\n >\n <div\n *cdkVirtualFor=\"\n let item of visibleNodes();\n trackBy: trackVisible\n \"\n class=\"group flex h-10 items-center gap-2 pe-2 transition-colors hover:bg-surface-50\"\n [style.padding-inline-start.rem]=\"0.5 + item.depth * 1.25\"\n >\n @if (hasChildren(item.node)) {\n <button\n type=\"button\"\n class=\"flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-md text-surface-400 hover:bg-surface-100 hover:text-surface-600\"\n [attr.aria-expanded]=\"isExpanded(item.node)\"\n [attr.aria-label]=\"nodeLabel(item.node)\"\n (click)=\"toggleExpand(item.node); $event.stopPropagation()\"\n >\n <mt-icon\n [icon]=\"\n isExpanded(item.node)\n ? 'arrow.chevron-down'\n : 'arrow.chevron-right'\n \"\n styleClass=\"text-base\"\n ></mt-icon>\n </button>\n } @else {\n <span class=\"size-6 shrink-0\"></span>\n }\n\n <button\n type=\"button\"\n role=\"checkbox\"\n [attr.aria-checked]=\"\n nodeState(item.node) === 'indeterminate'\n ? 'mixed'\n : nodeState(item.node) === 'checked'\n \"\n [attr.aria-label]=\"nodeLabel(item.node)\"\n class=\"shrink-0\"\n [class.cursor-pointer]=\"!readonly()\"\n [disabled]=\"readonly()\"\n (click)=\"toggle(item.node); $event.stopPropagation()\"\n >\n <ng-container\n *ngTemplateOutlet=\"\n checkbox;\n context: { state: nodeState(item.node) }\n \"\n ></ng-container>\n </button>\n\n <button\n type=\"button\"\n class=\"flex min-w-0 flex-1 cursor-pointer items-center gap-2 text-start\"\n (click)=\"onRowClick(item.node)\"\n >\n <span\n class=\"truncate text-sm\"\n [class.font-semibold]=\"item.node.kind === 'template'\"\n [class.text-surface-900]=\"item.node.kind === 'template'\"\n [class.font-medium]=\"\n item.node.kind === 'level' || item.node.kind === 'module'\n \"\n [class.text-surface-800]=\"\n item.node.kind === 'level' || item.node.kind === 'module'\n \"\n [class.text-surface-600]=\"item.node.kind === 'operation'\"\n >\n {{ nodeLabel(item.node) }}\n </span>\n @if (item.node.kind === \"operation\" && item.node.isHighRisk) {\n <span\n class=\"shrink-0 rounded-full bg-amber-100 px-2 py-0.5 text-[0.625rem] font-medium text-amber-700\"\n >\n {{ t(\"delegations.scope.highRisk\") }}\n </span>\n }\n </button>\n </div>\n </cdk-virtual-scroll-viewport>\n } @else {\n <p class=\"px-3 py-8 text-center text-sm text-surface-500\">\n {{ t(\"components.table.no-data-found\") }}\n </p>\n }\n } @else {\n <!-- App accessibility -->\n <div class=\"flex flex-col gap-3 p-4\">\n <p class=\"text-xs text-surface-500\">\n {{ t(\"delegations.scope.accessibilityHint\") }}\n </p>\n <div class=\"flex flex-wrap gap-2\">\n @for (item of appAccessibilities(); track item.accessibilityKey) {\n @let selected = isAccessibilitySelected(item.accessibilityKey);\n <button\n type=\"button\"\n class=\"inline-flex cursor-pointer items-center gap-1.5 rounded-full border px-3 py-1.5 text-xs font-medium transition-colors\"\n [class.border-primary-400]=\"selected\"\n [class.bg-primary-50]=\"selected\"\n [class.text-primary-700]=\"selected\"\n [class.border-surface-200]=\"!selected\"\n [class.text-surface-600]=\"!selected\"\n [class.hover:border-primary-300]=\"!readonly()\"\n [disabled]=\"readonly()\"\n [attr.aria-pressed]=\"selected\"\n (click)=\"toggleAccessibility(item.accessibilityKey)\"\n >\n <span\n class=\"inline-flex size-1.5 rounded-full\"\n [class.bg-primary-500]=\"selected\"\n [class.bg-surface-300]=\"!selected\"\n ></span>\n {{ accessibilityLabel(item) }}\n </button>\n }\n </div>\n </div>\n }\n </mt-card>\n }\n\n <!-- Compact live scope preview -->\n <div\n class=\"flex flex-col gap-1.5 rounded-lg border border-surface bg-surface-50 px-3 py-2\"\n >\n <div class=\"flex items-center gap-2 text-xs\">\n <span class=\"shrink-0 font-semibold text-surface-700\">\n {{ t(\"delegations.scope.previewTitle\") }}:\n </span>\n @if (isPreviewing()) {\n <p-skeleton width=\"8rem\" height=\"0.7rem\"></p-skeleton>\n } @else if (preview(); as p) {\n @if (p.isValid) {\n <span class=\"min-w-0 flex-1 truncate text-surface-900\">\n {{\n getPreviewSummary(p.summary) ||\n t(\"delegations.column.scopeSummary\")\n }}\n </span>\n } @else {\n <span class=\"font-medium text-red-700\">\n {{ t(\"delegations.scope.previewInvalid\") }}\n </span>\n }\n } @else {\n <span class=\"text-surface-500\">\n {{ t(\"delegations.scope.noSelection\") }}\n </span>\n }\n </div>\n @if (preview(); as p) {\n @if (p.warnings.length > 0) {\n <ul class=\"list-inside list-disc space-y-0.5 text-xs text-amber-700\">\n @for (w of p.warnings; track $index) {\n <li>{{ w.message }}</li>\n }\n </ul>\n }\n @if (p.deniedItems.length > 0) {\n <ul class=\"list-inside list-disc space-y-0.5 text-xs text-red-700\">\n @for (d of p.deniedItems; track $index) {\n <li>\n {{ formatDeniedTarget(d.targetKey) }} /\n {{ formatDeniedOperation(d.operationKey) }} -\n {{ d.reasonCode }}\n </li>\n }\n </ul>\n }\n }\n </div>\n }\n </div>\n</ng-container>\n" }]
2265
2330
  }], ctorParameters: () => [], propDecorators: { scope: [{ type: i0.Input, args: [{ isSignal: true, alias: "scope", required: false }] }, { type: i0.Output, args: ["scopeChange"] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], adminMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "adminMode", required: false }] }], delegatorUserId: [{ type: i0.Input, args: [{ isSignal: true, alias: "delegatorUserId", required: false }] }] } });
2266
2331
 
2267
2332
  const EMPTY_SCOPE = { grants: [], metadata: {} };
@@ -2308,6 +2373,23 @@ function getCurrentDelegatorFromLocalStorage() {
2308
2373
  return null;
2309
2374
  }
2310
2375
  }
2376
+ /**
2377
+ * Backend requires ISO 8601 **UTC** timestamps ending with `Z`
2378
+ * (Masterteam.Shared `ISO8601DateTimeConverter`). The date field can emit a
2379
+ * `Date` or an offset string like `2026-06-01T13:47:00+00:00`; normalize both
2380
+ * to the same instant rendered with `Z` (otherwise the command fails to bind →
2381
+ * "command field is required").
2382
+ */
2383
+ function toUtcIso(value) {
2384
+ if (value instanceof Date) {
2385
+ return Number.isNaN(value.getTime()) ? undefined : value.toISOString();
2386
+ }
2387
+ if (typeof value === 'string' && value.trim()) {
2388
+ const parsed = new Date(value);
2389
+ return Number.isNaN(parsed.getTime()) ? value : parsed.toISOString();
2390
+ }
2391
+ return undefined;
2392
+ }
2311
2393
  function extractUserId(value) {
2312
2394
  if (typeof value === 'string') {
2313
2395
  return value.trim() || undefined;
@@ -2489,34 +2571,47 @@ class DelegationForm {
2489
2571
  },
2490
2572
  ],
2491
2573
  }), ...(ngDevMode ? [{ debugName: "formConfig" }] : /* istanbul ignore next */ []));
2574
+ // Prefill (edit) and default-init (create) must each happen exactly once, so
2575
+ // later signal changes (detail re-emits, field edits) never wipe the user's
2576
+ // in-progress scope selection or field values.
2577
+ editPrefilled = false;
2578
+ createInitialized = false;
2492
2579
  constructor() {
2493
2580
  effect(() => {
2494
2581
  const editing = this.delegationForEdit();
2495
2582
  const d = this.detail();
2496
- if (editing && d && d.row.delegationId === editing.delegationId) {
2497
- this.delegationFormControl.patchValue({
2498
- delegateFrom: this.mapPartyToUserValue(d.row.delegator),
2499
- delegateTo: d.row.delegatedUser,
2500
- description: '',
2501
- delegateFromDateTime: d.row.startsAtUtc,
2502
- delegateToDateTime: d.row.endsAtUtc,
2503
- delegationDaysType: d.row.dayRuleMode,
2504
- specificDays: d.row.specificDays ?? [],
2505
- requiresApproval: d.row.approval?.requiresApproval ?? false,
2506
- });
2507
- this.scope.set({
2508
- grants: d.scope?.grants ?? [],
2509
- metadata: d.scope?.metadata ?? {},
2510
- });
2511
- }
2512
- else if (!editing) {
2513
- this.delegationFormControl.reset({
2514
- delegateFrom: null,
2515
- delegationDaysType: 'FullRange',
2516
- requiresApproval: true,
2517
- });
2518
- this.scope.set(EMPTY_SCOPE);
2519
- }
2583
+ untracked(() => {
2584
+ if (editing && d && d.row.delegationId === editing.delegationId) {
2585
+ if (this.editPrefilled)
2586
+ return;
2587
+ this.editPrefilled = true;
2588
+ this.delegationFormControl.patchValue({
2589
+ delegateFrom: this.mapPartyToUserValue(d.row.delegator),
2590
+ delegateTo: d.row.delegatedUser,
2591
+ description: '',
2592
+ delegateFromDateTime: d.row.startsAtUtc,
2593
+ delegateToDateTime: d.row.endsAtUtc,
2594
+ delegationDaysType: d.row.dayRuleMode,
2595
+ specificDays: d.row.specificDays ?? [],
2596
+ requiresApproval: d.row.approval?.requiresApproval ?? false,
2597
+ });
2598
+ this.scope.set({
2599
+ grants: d.scope?.grants ?? [],
2600
+ metadata: d.scope?.metadata ?? {},
2601
+ });
2602
+ }
2603
+ else if (!editing) {
2604
+ if (this.createInitialized)
2605
+ return;
2606
+ this.createInitialized = true;
2607
+ this.delegationFormControl.reset({
2608
+ delegateFrom: null,
2609
+ delegationDaysType: 'FullRange',
2610
+ requiresApproval: true,
2611
+ });
2612
+ this.scope.set(EMPTY_SCOPE);
2613
+ }
2614
+ });
2520
2615
  });
2521
2616
  effect(() => {
2522
2617
  const currentDelegator = this.currentDelegator();
@@ -2553,8 +2648,8 @@ class DelegationForm {
2553
2648
  const delegationDaysType = (value?.delegationDaysType ?? 'FullRange');
2554
2649
  const specificDays = delegationDaysType === 'SpecificDays' ? (value?.specificDays ?? []) : [];
2555
2650
  const description = value?.description;
2556
- const delegateFromDateTime = value?.delegateFromDateTime;
2557
- const delegateToDateTime = value?.delegateToDateTime;
2651
+ const delegateFromDateTime = toUtcIso(value?.delegateFromDateTime) ?? '';
2652
+ const delegateToDateTime = toUtcIso(value?.delegateToDateTime) ?? '';
2558
2653
  const requiresApproval = !!value?.requiresApproval;
2559
2654
  const delegatedUserId = extractUserId(value?.delegateTo) ?? '';
2560
2655
  const selectedDelegatorId = this.selectedDelegatorId();
@@ -2707,7 +2802,7 @@ class DelegationDetailDrawer {
2707
2802
  return toDelegationUserEntity(party, { label });
2708
2803
  }
2709
2804
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationDetailDrawer, deps: [], target: i0.ɵɵFactoryTarget.Component });
2710
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: DelegationDetailDrawer, isStandalone: true, selector: "mt-delegation-detail-drawer", inputs: { delegationId: { classPropertyName: "delegationId", publicName: "delegationId", isSignal: true, isRequired: true, transformFunction: null }, adminMode: { classPropertyName: "adminMode", publicName: "adminMode", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\r\n <div class=\"flex flex-col h-full\">\r\n <!-- Tabs -->\r\n <div class=\"border-b border-surface-200 px-4 pt-2\">\r\n <mt-tabs\r\n mode=\"underline\"\r\n [(active)]=\"activeTab\"\r\n [options]=\"tabOptions()\"\r\n fluid\r\n ></mt-tabs>\r\n </div>\r\n\r\n <!-- Body -->\r\n <div class=\"flex-1 overflow-y-auto p-4\">\r\n @if (isLoading() && !detail()) {\r\n <p-skeleton height=\"2rem\" class=\"mb-3\"></p-skeleton>\r\n <p-skeleton height=\"6rem\" class=\"mb-3\"></p-skeleton>\r\n <p-skeleton height=\"6rem\"></p-skeleton>\r\n } @else if (detail(); as d) {\r\n @if (activeTab() === \"overview\") {\r\n <div class=\"flex flex-col gap-4\">\r\n <div class=\"flex items-center justify-between gap-3\">\r\n <div class=\"flex items-center gap-3 min-w-0\">\r\n <mt-entity-preview\r\n [data]=\"\r\n userEntity(\r\n d.row.delegator,\r\n t('delegations.column.delegatorName')\r\n )\r\n \"\r\n ></mt-entity-preview>\r\n </div>\r\n <mt-delegation-status-chip\r\n [status]=\"d.status.effectiveStatus\"\r\n ></mt-delegation-status-chip>\r\n </div>\r\n\r\n <div class=\"grid grid-cols-2 gap-4\">\r\n <div class=\"flex flex-col\">\r\n <mt-entity-preview\r\n [data]=\"\r\n userEntity(\r\n d.row.delegatedUser,\r\n t('delegations.column.delegatedTo')\r\n )\r\n \"\r\n ></mt-entity-preview>\r\n </div>\r\n <div class=\"flex flex-col\">\r\n <span class=\"text-xs text-surface-500\">\r\n {{ t(\"delegations.column.approval\") }}\r\n </span>\r\n <span class=\"text-sm text-surface-900\">{{ approvalLabel() }}</span>\r\n </div>\r\n <div class=\"flex flex-col\">\r\n <span class=\"text-xs text-surface-500\">\r\n {{ t(\"delegations.column.startDate\") }}\r\n </span>\r\n <span class=\"text-sm text-surface-900\">\r\n {{ d.row.startsAtUtc | date: \"medium\" }}\r\n </span>\r\n </div>\r\n <div class=\"flex flex-col\">\r\n <span class=\"text-xs text-surface-500\">\r\n {{ t(\"delegations.column.endDate\") }}\r\n </span>\r\n <span class=\"text-sm text-surface-900\">\r\n {{ d.row.endsAtUtc | date: \"medium\" }}\r\n </span>\r\n </div>\r\n <div class=\"flex flex-col\">\r\n <span class=\"text-xs text-surface-500\">\r\n {{ t(\"delegations.form.timeZone\") }}\r\n </span>\r\n <span class=\"text-sm text-surface-900\">{{\r\n d.row.timeZoneId\r\n }}</span>\r\n </div>\r\n <div class=\"flex flex-col\">\r\n <span class=\"text-xs text-surface-500\">\r\n {{ t(\"delegations.column.days\") }}\r\n </span>\r\n <span class=\"text-sm text-surface-900\">\r\n @if (d.row.dayRuleMode === \"FullRange\") {\r\n {{ t(\"delegations.form.fullRange\") }}\r\n } @else {\r\n {{ formatSpecificDays(d.row.specificDays) }}\r\n }\r\n </span>\r\n </div>\r\n </div>\r\n\r\n @if (d.row.cancellation?.cancellationReason) {\r\n <div\r\n class=\"rounded-md border border-surface-200 bg-surface-50 p-3 text-sm text-surface-800\"\r\n >\r\n <div class=\"font-medium\">\r\n {{ t(\"delegations.form.cancellationReason\") }}\r\n </div>\r\n <div>{{ d.row.cancellation.cancellationReason }}</div>\r\n </div>\r\n }\r\n </div>\r\n } @else {\r\n <!-- Scope tab -->\r\n <div class=\"flex flex-col gap-3\">\r\n <div class=\"text-sm text-surface-900\">\r\n {{\r\n formatScopeSummary(d.row.scopeSummary) ||\r\n t(\"delegations.column.scopeSummary\")\r\n }}\r\n </div>\r\n <div class=\"flex flex-col gap-2\">\r\n @for (grant of d.scope.grants; track $index) {\r\n <div\r\n class=\"border border-surface-200 rounded-md px-3 py-2 flex flex-col gap-1\"\r\n >\r\n <div class=\"flex items-center justify-between gap-2\">\r\n <span class=\"text-sm font-medium text-surface-900\">\r\n {{ getScopeTargetLabel(grant.target) }}\r\n </span>\r\n <span class=\"text-xs text-surface-500\">\r\n {{ grant.target.targetType }}\r\n </span>\r\n </div>\r\n <span\r\n class=\"text-xs px-2 py-0.5 rounded-md bg-primary-50 text-primary w-fit\"\r\n >\r\n {{ getScopeActionLabel(grant.action) }}\r\n </span>\r\n </div>\r\n }\r\n @if (d.scope.grants.length === 0) {\r\n <p class=\"text-xs text-surface-500\">\r\n {{ t(\"delegations.scope.noSelection\") }}\r\n </p>\r\n }\r\n </div>\r\n </div>\r\n }\r\n }\r\n </div>\r\n\r\n <!-- Footer -->\r\n <div\r\n class=\"border-t border-surface-200 px-4 py-3 flex items-center justify-end\"\r\n >\r\n <mt-button\r\n [label]=\"t('delegations.common.cancel')\"\r\n variant=\"outlined\"\r\n (click)=\"ref.close()\"\r\n ></mt-button>\r\n </div>\r\n </div>\r\n</ng-container>\r\n", styles: [":host{display:block;height:100%}\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"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: EntityPreview, selector: "mt-entity-preview", inputs: ["data", "attachmentShape"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i3.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }, { kind: "component", type: Tabs, selector: "mt-tabs", inputs: ["options", "optionLabel", "optionValue", "active", "mode", "moreLabel", "defaultIcon", "size", "fluid", "disabled", "searchThreshold"], outputs: ["activeChange", "onChange"] }, { 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"] }, { kind: "pipe", type: i1.DatePipe, name: "date" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2805
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: DelegationDetailDrawer, isStandalone: true, selector: "mt-delegation-detail-drawer", inputs: { delegationId: { classPropertyName: "delegationId", publicName: "delegationId", isSignal: true, isRequired: true, transformFunction: null }, adminMode: { classPropertyName: "adminMode", publicName: "adminMode", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\r\n <div class=\"flex flex-col h-full\">\r\n <!-- Tabs -->\r\n <div class=\"border-b border-surface-200 px-4 pt-2\">\r\n <mt-tabs\r\n mode=\"underline\"\r\n [(active)]=\"activeTab\"\r\n [options]=\"tabOptions()\"\r\n fluid\r\n ></mt-tabs>\r\n </div>\r\n\r\n <!-- Body -->\r\n <div class=\"flex-1 overflow-y-auto p-4\">\r\n @if (isLoading() && !detail()) {\r\n <p-skeleton height=\"2rem\" class=\"mb-3\"></p-skeleton>\r\n <p-skeleton height=\"6rem\" class=\"mb-3\"></p-skeleton>\r\n <p-skeleton height=\"6rem\"></p-skeleton>\r\n } @else if (detail(); as d) {\r\n @if (activeTab() === \"overview\") {\r\n <div class=\"flex flex-col gap-4\">\r\n <div class=\"flex items-center justify-between gap-3\">\r\n <div class=\"flex items-center gap-3 min-w-0\">\r\n <mt-entity-preview\r\n [data]=\"\r\n userEntity(\r\n d.row.delegator,\r\n t('delegations.column.delegatorName')\r\n )\r\n \"\r\n ></mt-entity-preview>\r\n </div>\r\n <mt-delegation-status-chip\r\n [status]=\"d.status.effectiveStatus\"\r\n ></mt-delegation-status-chip>\r\n </div>\r\n\r\n <div class=\"grid grid-cols-2 gap-4\">\r\n <div class=\"flex flex-col\">\r\n <mt-entity-preview\r\n [data]=\"\r\n userEntity(\r\n d.row.delegatedUser,\r\n t('delegations.column.delegatedTo')\r\n )\r\n \"\r\n ></mt-entity-preview>\r\n </div>\r\n <div class=\"flex flex-col\">\r\n <span class=\"text-xs text-surface-500\">\r\n {{ t(\"delegations.column.approval\") }}\r\n </span>\r\n <span class=\"text-sm text-surface-900\">{{ approvalLabel() }}</span>\r\n </div>\r\n <div class=\"flex flex-col\">\r\n <span class=\"text-xs text-surface-500\">\r\n {{ t(\"delegations.column.startDate\") }}\r\n </span>\r\n <span class=\"text-sm text-surface-900\">\r\n {{ d.row.startsAtUtc | date: \"medium\" }}\r\n </span>\r\n </div>\r\n <div class=\"flex flex-col\">\r\n <span class=\"text-xs text-surface-500\">\r\n {{ t(\"delegations.column.endDate\") }}\r\n </span>\r\n <span class=\"text-sm text-surface-900\">\r\n {{ d.row.endsAtUtc | date: \"medium\" }}\r\n </span>\r\n </div>\r\n <div class=\"flex flex-col\">\r\n <span class=\"text-xs text-surface-500\">\r\n {{ t(\"delegations.form.timeZone\") }}\r\n </span>\r\n <span class=\"text-sm text-surface-900\">{{\r\n d.row.timeZoneId\r\n }}</span>\r\n </div>\r\n <div class=\"flex flex-col\">\r\n <span class=\"text-xs text-surface-500\">\r\n {{ t(\"delegations.column.days\") }}\r\n </span>\r\n <span class=\"text-sm text-surface-900\">\r\n @if (d.row.dayRuleMode === \"FullRange\") {\r\n {{ t(\"delegations.form.fullRange\") }}\r\n } @else {\r\n {{ formatSpecificDays(d.row.specificDays) }}\r\n }\r\n </span>\r\n </div>\r\n </div>\r\n\r\n @if (d.row.cancellation?.cancellationReason) {\r\n <div\r\n class=\"rounded-md border border-surface-200 bg-surface-50 p-3 text-sm text-surface-800\"\r\n >\r\n <div class=\"font-medium\">\r\n {{ t(\"delegations.form.cancellationReason\") }}\r\n </div>\r\n <div>{{ d.row.cancellation.cancellationReason }}</div>\r\n </div>\r\n }\r\n </div>\r\n } @else {\r\n <!-- Scope tab -->\r\n <div class=\"flex flex-col gap-3\">\r\n <div class=\"text-sm text-surface-900\">\r\n {{\r\n formatScopeSummary(d.row.scopeSummary) ||\r\n t(\"delegations.column.scopeSummary\")\r\n }}\r\n </div>\r\n <div class=\"flex flex-col gap-2\">\r\n @for (grant of d.scope.grants; track $index) {\r\n <div\r\n class=\"border border-surface-200 rounded-md px-3 py-2 flex flex-col gap-1\"\r\n >\r\n <div class=\"flex items-center justify-between gap-2\">\r\n <span class=\"text-sm font-medium text-surface-900\">\r\n {{ getScopeTargetLabel(grant.target) }}\r\n </span>\r\n <span class=\"text-xs text-surface-500\">\r\n {{ grant.target.targetType }}\r\n </span>\r\n </div>\r\n <span\r\n class=\"text-xs px-2 py-0.5 rounded-md bg-primary-50 text-primary w-fit\"\r\n >\r\n {{ getScopeActionLabel(grant.action) }}\r\n </span>\r\n </div>\r\n }\r\n @if (d.scope.grants.length === 0) {\r\n <p class=\"text-xs text-surface-500\">\r\n {{ t(\"delegations.scope.noSelection\") }}\r\n </p>\r\n }\r\n </div>\r\n </div>\r\n }\r\n }\r\n </div>\r\n\r\n <!-- Footer -->\r\n <div\r\n class=\"border-t border-surface-200 px-4 py-3 flex items-center justify-end\"\r\n >\r\n <mt-button\r\n [label]=\"t('delegations.common.cancel')\"\r\n variant=\"outlined\"\r\n (click)=\"ref.close()\"\r\n ></mt-button>\r\n </div>\r\n </div>\r\n</ng-container>\r\n", styles: [":host{display:block;height:100%}\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"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: EntityPreview, selector: "mt-entity-preview", inputs: ["data", "attachmentShape"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i4.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }, { kind: "component", type: Tabs, selector: "mt-tabs", inputs: ["options", "optionLabel", "optionValue", "active", "mode", "moreLabel", "defaultIcon", "size", "fluid", "disabled", "searchThreshold"], outputs: ["activeChange", "onChange"] }, { 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"] }, { kind: "pipe", type: i1.DatePipe, name: "date" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2711
2806
  }
2712
2807
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationDetailDrawer, decorators: [{
2713
2808
  type: Component,