mesauth-angular 1.5.5 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,12 +4,23 @@ Angular helper library to connect to a backend API and SignalR hub to surface th
4
4
 
5
5
  ## Changelog
6
6
 
7
+ ### v1.6.0 (2026-03-28) - **Approval Module Enhancements**
8
+ - **`[templateId]` locks routing UI**: When bound, the routing mode toggle and template dropdown are hidden — the template name is shown as read-only. Role-based steps auto-load candidate pickers from `GET /approval/roles/preview`; the requester selects one user per step before submitting. Pass as number binding: `[templateId]="6"`.
9
+ - **`(approvalSubmitting)` output** *(corrected from single-t typo)*: Fires before content capture starts. Use it to hide edit controls (`*ngIf="!isSubmitting"`) so they are excluded from the approval snapshot. Angular CD runs after the emit so DOM updates are captured.
10
+ - **Automatic theme support**: `<ma-arv-container>` applies `ThemeService` internally — adapts to the app's light/dark theme via `@HostBinding('class')` with no extra setup.
11
+ - **`previewRole(orgCode, level)`** added to `MaApprovalService`: Fetch candidate users for a role-based approval step.
12
+ - **Callback security**: MesAuth.Api sends `X-APP-ID` + `X-APP-KEY` headers on every callback POST using its own app credentials. Protect callback endpoints with `[MesAuth]` from `MesAuth.Authorizer`.
13
+
7
14
  ### v1.5.0 (2026-03-24) - **Approval Module**
8
15
  - **New `<ma-approval-panel>` component**: Slide-out sidebar with 3 tabs (Processing / Approved / Rejected). Shows all pending approvals requiring action, and recent approved/rejected items. Listens to `approvalEvents$` for real-time refresh via SignalR.
9
16
  - **New `<ma-arv-container>` component**: Content capture container for submitting documents for approval. Captures projected `<ng-content>` as a self-contained HTML document by inlining all computed styles, replacing canvas elements with images, and stripping Angular/script artifacts. Supports ad-hoc step builder and template selector.
10
- - **New `MaApprovalService`**: Service for all approval API calls`getPendingApprovals()`, `getMyRequests()`, `getDashboard()`, `approve()`, `reject()`, `delegate()`, `getTemplates()`, `createApproval()`, etc. Manual init pattern (same as `MesAuthService`).
17
+ - **`[templateId]` locks routing UI**: When bound, the routing mode toggle and template dropdown are hidden the template name is shown as read-only. Role-based steps auto-load candidate pickers from `GET /approval/roles/preview`; the requester selects one user per step before submitting. Pass as number binding: `[templateId]="6"`.
18
+ - **`(approvalSubmitting)` output** *(corrected from single-t typo)*: Fires before content capture starts. Use it to hide edit controls (`*ngIf="!isSubmitting"`) so they are excluded from the approval snapshot. Angular CD runs after the emit so DOM updates are captured.
19
+ - **Automatic theme support**: Applies `ThemeService` internally — adapts to the app's light/dark theme via `@HostBinding('class')` with no extra setup.
20
+ - **New `MaApprovalService`**: Service for all approval API calls — `getPendingApprovals()`, `getMyRequests()`, `getDashboard()`, `approve()`, `reject()`, `delegate()`, `getTemplates()`, `createApproval()`, `previewRole(orgCode, level)`, etc. Manual init pattern (same as `MesAuthService`).
11
21
  - **Approval icon in `ma-user-profile`**: Clipboard/checkmark icon button added between notification bell and avatar. Shows pending count badge. Emits `approvalClick` output for panel toggle.
12
22
  - **`approvalEvents$` observable in `MesAuthService`**: Real-time SignalR events (`ApprovalCompleted`, `ApprovalStepChanged`) exposed as an observable stream.
23
+ - **Callback security**: MesAuth.Api sends `X-APP-ID` + `X-APP-KEY` headers on every callback POST using its own app credentials. Protect callback endpoints with `[MesAuth]` from `MesAuth.Authorizer`.
13
24
  - **New exports**: `MaApprovalService`, `MaApprovalPanelComponent`, `MaArvContainerComponent`, and all approval model interfaces/enums.
14
25
 
15
26
  ### v1.4.0 (2026-03-20) - **Remove Unused Route Registration API**
@@ -3,7 +3,7 @@ import { InjectionToken, makeEnvironmentProviders, provideAppInitializer, inject
3
3
  import * as i4 from '@angular/common/http';
4
4
  import { HttpClient } from '@angular/common/http';
5
5
  import { HubConnectionBuilder, LogLevel } from '@microsoft/signalr';
6
- import { BehaviorSubject, Subject, EMPTY, of, timer, throwError } from 'rxjs';
6
+ import { BehaviorSubject, Subject, EMPTY, of, timer, throwError, forkJoin } from 'rxjs';
7
7
  import { tap, catchError, switchMap, takeUntil } from 'rxjs/operators';
8
8
  import * as i2 from '@angular/router';
9
9
  import { Router } from '@angular/router';
@@ -1253,7 +1253,7 @@ class NotificationPanelComponent {
1253
1253
  </div>
1254
1254
  <div class="modal-body" *ngIf="!selectedNotificationUrl" [innerHTML]="selectedNotificationHtml"></div>
1255
1255
  <div class="modal-body modal-body-iframe" *ngIf="selectedNotificationUrl">
1256
- <iframe [src]="selectedNotificationUrl" sandbox="allow-same-origin" class="notification-iframe"></iframe>
1256
+ <iframe [src]="selectedNotificationUrl" sandbox="allow-same-origin allow-scripts" class="notification-iframe"></iframe>
1257
1257
  </div>
1258
1258
  <div class="modal-footer">
1259
1259
  <button class="action-btn" (click)="closeDetails()">Close</button>
@@ -1415,7 +1415,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
1415
1415
  </div>
1416
1416
  <div class="modal-body" *ngIf="!selectedNotificationUrl" [innerHTML]="selectedNotificationHtml"></div>
1417
1417
  <div class="modal-body modal-body-iframe" *ngIf="selectedNotificationUrl">
1418
- <iframe [src]="selectedNotificationUrl" sandbox="allow-same-origin" class="notification-iframe"></iframe>
1418
+ <iframe [src]="selectedNotificationUrl" sandbox="allow-same-origin allow-scripts" class="notification-iframe"></iframe>
1419
1419
  </div>
1420
1420
  <div class="modal-footer">
1421
1421
  <button class="action-btn" (click)="closeDetails()">Close</button>
@@ -1508,6 +1508,9 @@ class MaApprovalService {
1508
1508
  deleteTemplate(id) {
1509
1509
  return this.http.delete(`${this.apiBase}/approval/templates/${id}`, this.opts);
1510
1510
  }
1511
+ previewRole(orgCode, level) {
1512
+ return this.http.get(`${this.apiBase}/approval/roles/preview?orgCode=${encodeURIComponent(orgCode)}&level=${encodeURIComponent(level)}`, this.opts);
1513
+ }
1511
1514
  // ====================== Create (used by ma-arv-container) ======================
1512
1515
  createApproval(request) {
1513
1516
  return this.http.post(`${this.apiBase}/approval/documents`, request, this.opts);
@@ -2016,12 +2019,18 @@ class MaArvContainerComponent {
2016
2019
  callbackUrl;
2017
2020
  deadlineHours;
2018
2021
  approvalSubmitted = new EventEmitter();
2019
- approvalSubmiting = new EventEmitter();
2022
+ approvalSubmitting = new EventEmitter();
2020
2023
  cancelled = new EventEmitter();
2021
2024
  contentBody;
2022
2025
  routingMode = 'template';
2023
2026
  templates = [];
2024
2027
  selectedTemplateId = null;
2028
+ selectedTemplate = null;
2029
+ loadingTemplate = false;
2030
+ // Per-step role candidates and selected user
2031
+ stepCandidates = [];
2032
+ stepLoadingCandidates = [];
2033
+ stepSelectedUsers = [];
2025
2034
  adHocSteps = [];
2026
2035
  referenceUserIds = [];
2027
2036
  refSearchQuery = '';
@@ -2032,11 +2041,17 @@ class MaArvContainerComponent {
2032
2041
  isSubmitted = false;
2033
2042
  errorMessage = '';
2034
2043
  userLabelMap = {};
2044
+ get themeClass() { return `theme-${this.currentTheme}`; }
2045
+ currentTheme = 'light';
2035
2046
  destroy$ = new Subject();
2036
2047
  mesAuth = inject(MesAuthService);
2048
+ themeService = inject(ThemeService);
2037
2049
  approvalSvc = null;
2038
2050
  http = inject(HttpClient);
2039
2051
  ngOnInit() {
2052
+ this.themeService.currentTheme$
2053
+ .pipe(takeUntil(this.destroy$))
2054
+ .subscribe(t => this.currentTheme = t);
2040
2055
  const config = this.mesAuth.getConfig();
2041
2056
  if (config) {
2042
2057
  this.approvalSvc = new MaApprovalService();
@@ -2051,13 +2066,63 @@ class MaArvContainerComponent {
2051
2066
  }
2052
2067
  loadTemplates() {
2053
2068
  this.approvalSvc.getTemplates().pipe(takeUntil(this.destroy$)).subscribe({
2054
- next: (t) => this.templates = t,
2069
+ next: (t) => {
2070
+ this.templates = t;
2071
+ if (this.templateId != null && this.selectedTemplateId == null) {
2072
+ this.selectedTemplateId = this.templateId;
2073
+ this.loadTemplateDetail(this.templateId);
2074
+ }
2075
+ },
2055
2076
  error: () => { }
2056
2077
  });
2057
2078
  }
2079
+ loadTemplateDetail(id) {
2080
+ this.loadingTemplate = true;
2081
+ this.selectedTemplate = null;
2082
+ this.stepCandidates = [];
2083
+ this.stepLoadingCandidates = [];
2084
+ this.stepSelectedUsers = [];
2085
+ this.approvalSvc.getTemplate(id).pipe(takeUntil(this.destroy$)).subscribe({
2086
+ next: (t) => {
2087
+ this.selectedTemplate = t;
2088
+ this.loadingTemplate = false;
2089
+ this.loadStepCandidates(t.steps);
2090
+ },
2091
+ error: () => { this.loadingTemplate = false; }
2092
+ });
2093
+ }
2094
+ loadStepCandidates(steps) {
2095
+ this.stepCandidates = steps.map(() => []);
2096
+ this.stepLoadingCandidates = steps.map(() => false);
2097
+ this.stepSelectedUsers = steps.map(() => '');
2098
+ steps.forEach((step, i) => {
2099
+ if (!step.roles || step.roles.length === 0)
2100
+ return;
2101
+ this.stepLoadingCandidates[i] = true;
2102
+ const calls = step.roles.map(r => this.approvalSvc.previewRole(r.orgCode, r.positionLevel));
2103
+ forkJoin(calls.length > 0 ? calls : [of([])])
2104
+ .pipe(takeUntil(this.destroy$))
2105
+ .subscribe({
2106
+ next: results => {
2107
+ const merged = new Map();
2108
+ results.forEach(list => list.forEach(u => merged.set(u.userId, u)));
2109
+ this.stepCandidates[i] = Array.from(merged.values());
2110
+ this.stepLoadingCandidates[i] = false;
2111
+ },
2112
+ error: () => { this.stepLoadingCandidates[i] = false; }
2113
+ });
2114
+ });
2115
+ }
2116
+ onStepUserChange(stepIndex, event) {
2117
+ this.stepSelectedUsers[stepIndex] = event.target.value;
2118
+ }
2058
2119
  onTemplateChange(event) {
2059
2120
  const val = event.target.value;
2060
2121
  this.selectedTemplateId = val ? parseInt(val, 10) : null;
2122
+ this.selectedTemplate = null;
2123
+ if (this.selectedTemplateId) {
2124
+ this.loadTemplateDetail(this.selectedTemplateId);
2125
+ }
2061
2126
  }
2062
2127
  addStep() {
2063
2128
  const order = this.adHocSteps.length + 1;
@@ -2138,6 +2203,8 @@ class MaArvContainerComponent {
2138
2203
  .catch(() => []);
2139
2204
  }
2140
2205
  async submit() {
2206
+ if (this.submitting)
2207
+ return;
2141
2208
  this.errorMessage = '';
2142
2209
  if (!this.approvalSvc) {
2143
2210
  this.errorMessage = 'Approval service not initialized. Ensure provideMesAuth() is configured.';
@@ -2159,10 +2226,21 @@ class MaArvContainerComponent {
2159
2226
  this.errorMessage = 'Please add at least one approval step.';
2160
2227
  return;
2161
2228
  }
2162
- this.submitting = true;
2163
- if (this.approvalSubmiting.observers.length > 0) {
2164
- this.approvalSubmiting.emit();
2229
+ // Validate role-based steps have a selected approver
2230
+ if (this.routingMode === 'template' && this.selectedTemplate) {
2231
+ for (let i = 0; i < this.selectedTemplate.steps.length; i++) {
2232
+ const step = this.selectedTemplate.steps[i];
2233
+ if (step.roles?.length > 0 && !this.stepSelectedUsers[i]) {
2234
+ this.errorMessage = `Please select an approver for step "${step.stepName}".`;
2235
+ return;
2236
+ }
2237
+ }
2165
2238
  }
2239
+ // Emit before capture so parent can hide edit controls (inputs, buttons)
2240
+ // then wait one tick for Angular CD to update the DOM before snapshotting
2241
+ this.approvalSubmitting.emit();
2242
+ await new Promise(resolve => setTimeout(resolve));
2243
+ this.submitting = true;
2166
2244
  try {
2167
2245
  const contentHtml = await this.captureContent();
2168
2246
  const thumbnailBase64 = await this.captureThumbnail().catch(() => undefined);
@@ -2178,6 +2256,20 @@ class MaArvContainerComponent {
2178
2256
  };
2179
2257
  if (this.routingMode === 'template') {
2180
2258
  request.templateId = this.selectedTemplateId ?? this.templateId;
2259
+ // If template has role-based steps with selected approvers, pass explicit steps
2260
+ if (this.selectedTemplate) {
2261
+ const hasRoleSteps = this.selectedTemplate.steps.some(s => s.roles?.length > 0);
2262
+ if (hasRoleSteps) {
2263
+ request.steps = this.selectedTemplate.steps.map((s, i) => ({
2264
+ stepOrder: s.stepOrder,
2265
+ stepName: s.stepName,
2266
+ mode: s.mode,
2267
+ approverUserIds: s.roles?.length > 0
2268
+ ? (this.stepSelectedUsers[i] ? [this.stepSelectedUsers[i]] : s.assigneeUserIds)
2269
+ : s.assigneeUserIds
2270
+ }));
2271
+ }
2272
+ }
2181
2273
  }
2182
2274
  else {
2183
2275
  request.steps = this.adHocSteps.map((s, i) => ({
@@ -2328,7 +2420,7 @@ ${clone.outerHTML}
2328
2420
  ];
2329
2421
  }
2330
2422
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: MaArvContainerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2331
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: MaArvContainerComponent, isStandalone: true, selector: "ma-arv-container", inputs: { title: "title", description: "description", referenceId: "referenceId", templateId: "templateId", callbackUrl: "callbackUrl", deadlineHours: "deadlineHours" }, outputs: { approvalSubmitted: "approvalSubmitted", approvalSubmiting: "approvalSubmiting", cancelled: "cancelled" }, viewQueries: [{ propertyName: "contentBody", first: true, predicate: ["contentBody"], descendants: true, static: true }], ngImport: i0, template: `
2423
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: MaArvContainerComponent, isStandalone: true, selector: "ma-arv-container", inputs: { title: "title", description: "description", referenceId: "referenceId", templateId: "templateId", callbackUrl: "callbackUrl", deadlineHours: "deadlineHours" }, outputs: { approvalSubmitted: "approvalSubmitted", approvalSubmitting: "approvalSubmitting", cancelled: "cancelled" }, host: { properties: { "class": "this.themeClass" } }, viewQueries: [{ propertyName: "contentBody", first: true, predicate: ["contentBody"], descendants: true, static: true }], ngImport: i0, template: `
2332
2424
  <div class="arv-container">
2333
2425
  <!-- Content Area -->
2334
2426
  <div class="arv-content-body" #contentBody>
@@ -2343,7 +2435,8 @@ ${clone.outerHTML}
2343
2435
  <div class="arv-routing">
2344
2436
  <div class="arv-routing-header">
2345
2437
  <h4 class="arv-section-title">Approval Routing</h4>
2346
- <div class="arv-routing-mode">
2438
+ <!-- Show routing toggle only when templateId is NOT locked -->
2439
+ <div *ngIf="!templateId" class="arv-routing-mode">
2347
2440
  <label class="arv-radio">
2348
2441
  <input type="radio" name="routingMode" value="template" [checked]="routingMode === 'template'" (change)="routingMode = 'template'"> Use Template
2349
2442
  </label>
@@ -2353,15 +2446,62 @@ ${clone.outerHTML}
2353
2446
  </div>
2354
2447
  </div>
2355
2448
 
2356
- <!-- Template selector -->
2357
- <div *ngIf="routingMode === 'template'" class="arv-template-select">
2449
+ <!-- Locked template: show name only, no selector -->
2450
+ <div *ngIf="templateId && routingMode === 'template'" class="arv-template-select">
2451
+ <div *ngIf="loadingTemplate" class="arv-template-loading">Loading template...</div>
2452
+ <div *ngIf="selectedTemplate && !loadingTemplate" class="arv-locked-template">
2453
+ <span class="arv-locked-label">Template</span>
2454
+ <span class="arv-locked-name">{{ selectedTemplate.name }}</span>
2455
+ </div>
2456
+ </div>
2457
+
2458
+ <!-- Free template selector (no locked templateId) -->
2459
+ <div *ngIf="!templateId && routingMode === 'template'" class="arv-template-select">
2358
2460
  <label class="arv-label">Select Template</label>
2359
2461
  <select class="arv-select" (change)="onTemplateChange($event)">
2360
2462
  <option value="">-- Select a template --</option>
2361
- <option *ngFor="let t of templates" [value]="t.id">{{ t.name }}</option>
2463
+ <option *ngFor="let t of templates" [value]="t.id" [selected]="t.id === selectedTemplateId">{{ t.name }}</option>
2362
2464
  </select>
2363
2465
  </div>
2364
2466
 
2467
+ <!-- Step pickers (shared for both locked and free template) -->
2468
+ <div *ngIf="routingMode === 'template'" class="arv-template-select">
2469
+ <div *ngIf="!loadingTemplate && selectedTemplate" class="arv-template-steps">
2470
+ <div class="arv-step-card" *ngFor="let s of selectedTemplate.steps; let i = index">
2471
+ <div class="arv-step-card-header">
2472
+ <span class="arv-step-preview-num">Step {{ s.stepOrder }}</span>
2473
+ <span class="arv-step-preview-name">{{ s.stepName }}</span>
2474
+ <span *ngIf="s.roles && s.roles.length > 0" class="arv-step-role-badge">
2475
+ {{ s.roles[0].positionLevel }}{{ s.roles[0].orgName ? ' · ' + s.roles[0].orgName : '' }}
2476
+ </span>
2477
+ </div>
2478
+ <!-- Role-based step: show approver picker -->
2479
+ <div *ngIf="s.roles && s.roles.length > 0" class="arv-step-picker">
2480
+ <div *ngIf="stepLoadingCandidates[i]" class="arv-template-loading">Loading candidates...</div>
2481
+ <select *ngIf="!stepLoadingCandidates[i]" class="arv-select arv-select-sm"
2482
+ (change)="onStepUserChange(i, $event)">
2483
+ <option value="">-- Select approver --</option>
2484
+ <option *ngFor="let u of stepCandidates[i]" [value]="u.userId"
2485
+ [selected]="u.userId === stepSelectedUsers[i]">
2486
+ {{ u.fullName || u.userId }}{{ u.department ? ' · ' + u.department : '' }}{{ u.position ? ' (' + u.position + ')' : '' }}
2487
+ </option>
2488
+ </select>
2489
+ <div *ngIf="!stepLoadingCandidates[i] && stepCandidates[i]?.length === 0" class="arv-template-loading">
2490
+ No candidates found for this role.
2491
+ </div>
2492
+ </div>
2493
+ <!-- Fixed-user step -->
2494
+ <div *ngIf="!s.roles || s.roles.length === 0" class="arv-step-fixed">
2495
+ {{ s.assigneeUserIds.length }} pre-configured approver(s)
2496
+ </div>
2497
+ </div>
2498
+ <div *ngIf="selectedTemplate.referenceUserIds.length > 0" class="arv-step-preview arv-step-preview-cc">
2499
+ <span class="arv-step-preview-num">CC</span>
2500
+ <span class="arv-step-preview-name">{{ selectedTemplate.referenceUserIds.length }} reference user(s) from template</span>
2501
+ </div>
2502
+ </div>
2503
+ </div>
2504
+
2365
2505
  <!-- Ad-hoc steps -->
2366
2506
  <div *ngIf="routingMode === 'adhoc'" class="arv-steps">
2367
2507
  <div class="arv-step" *ngFor="let step of adHocSteps; let i = index">
@@ -2443,7 +2583,7 @@ ${clone.outerHTML}
2443
2583
  <p>Submitted for approval successfully.</p>
2444
2584
  </div>
2445
2585
  </div>
2446
- `, isInline: true, styles: [":host{display:block;--arv-primary: #90caf9;--arv-primary-hover: #64b5f6;--arv-success: #66bb6a;--arv-danger: #ef5350;--arv-text: #e0e0e0;--arv-text-muted: #9e9e9e;--arv-bg: #1e1e2e;--arv-bg2: #27273a;--arv-border: #383850;--arv-radius: 8px}:host(.theme-light){--arv-primary: #1565c0;--arv-primary-hover: #0d47a1;--arv-success: #2e7d32;--arv-danger: #c62828;--arv-text: #212121;--arv-text-muted: #616161;--arv-bg: #ffffff;--arv-bg2: #f5f5f5;--arv-border: #e0e0e0}.arv-container{display:flex;flex-direction:column;height:100%}.arv-content-body{flex:1;overflow:auto}.arv-footer{border-top:1px solid var(--arv-border);background:var(--arv-bg2)}.arv-footer-inner{padding:16px;display:flex;flex-direction:column;gap:14px}.arv-section-title{margin:0 0 8px;font-size:13px;font-weight:700;color:var(--arv-primary);text-transform:uppercase;letter-spacing:.5px}.arv-routing-header{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:8px}.arv-routing-mode{display:flex;gap:12px}.arv-radio{display:flex;align-items:center;gap:6px;font-size:13px;color:var(--arv-text);cursor:pointer}.arv-label{font-size:12px;color:var(--arv-text-muted);display:block;margin-bottom:4px}.arv-select{width:100%;padding:8px 10px;border:1px solid var(--arv-border);border-radius:var(--arv-radius);background:var(--arv-bg);color:var(--arv-text);font-size:13px}.arv-input{padding:7px 10px;border:1px solid var(--arv-border);border-radius:var(--arv-radius);background:var(--arv-bg);color:var(--arv-text);font-size:13px}.arv-input-sm{width:100%;margin-top:4px}.arv-steps{display:flex;flex-direction:column;gap:10px}.arv-step{background:var(--arv-bg);border:1px solid var(--arv-border);border-radius:var(--arv-radius);padding:10px}.arv-step-header{display:flex;align-items:center;gap:8px;margin-bottom:8px}.arv-step-num{font-size:12px;font-weight:700;color:var(--arv-primary);min-width:44px}.arv-approver-tags{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:6px}.arv-tag{display:inline-flex;align-items:center;gap:4px;padding:3px 8px;background:#90caf91f;border:1px solid rgba(144,202,249,.3);border-radius:12px;font-size:12px;color:var(--arv-primary)}.arv-tag-remove{background:none;border:none;cursor:pointer;color:var(--arv-text-muted);font-size:11px;padding:0 2px;line-height:1}.arv-tag-remove:hover{color:var(--arv-danger)}.arv-user-search{position:relative}.arv-search-results{position:absolute;left:0;right:0;z-index:100;background:var(--arv-bg2);border:1px solid var(--arv-border);border-radius:var(--arv-radius);max-height:150px;overflow-y:auto}.arv-search-item{padding:8px 12px;cursor:pointer;font-size:13px;color:var(--arv-text)}.arv-search-item:hover{background:var(--arv-bg)}.arv-hint{font-size:12px;color:var(--arv-text-muted);margin:0 0 8px}.arv-btn-icon{background:none;border:none;cursor:pointer;padding:2px;border-radius:4px;display:inline-flex;align-items:center;justify-content:center}.arv-btn-danger{color:var(--arv-danger)}.arv-btn-danger:hover{background:#ef53501a}.arv-actions{display:flex;justify-content:flex-end;gap:10px}.arv-btn{padding:9px 18px;border-radius:var(--arv-radius);font-size:13px;font-weight:600;cursor:pointer;border:none;transition:background .15s}.arv-btn-primary{background:var(--arv-primary);color:#fff}.arv-btn-primary:hover:not(:disabled){background:var(--arv-primary-hover)}.arv-btn-primary:disabled{opacity:.6;cursor:not-allowed}.arv-btn-secondary{background:transparent;border:1px solid var(--arv-border);color:var(--arv-text-muted)}.arv-btn-secondary:hover{color:var(--arv-text);border-color:var(--arv-text-muted)}.arv-btn-outline{background:transparent;border:1px dashed var(--arv-border);color:var(--arv-text-muted);font-size:12px;padding:6px 12px}.arv-btn-outline:hover{border-color:var(--arv-primary);color:var(--arv-primary)}.arv-spinner{display:inline-block;width:14px;height:14px;border:2px solid rgba(255,255,255,.3);border-top-color:#fff;border-radius:50%;animation:arv-spin .6s linear infinite;margin-right:6px}@keyframes arv-spin{to{transform:rotate(360deg)}}.arv-error{padding:8px 12px;background:#ef53501a;border:1px solid rgba(239,83,80,.3);border-radius:var(--arv-radius);font-size:13px;color:var(--arv-danger)}.arv-success{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:32px;gap:12px;text-align:center}.arv-success p{color:var(--arv-success);font-size:15px;font-weight:600;margin:0}.arv-references{border-top:1px solid var(--arv-border);padding-top:12px}\n"], dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }] });
2586
+ `, isInline: true, styles: [":host{display:block;--arv-primary: #90caf9;--arv-primary-hover: #64b5f6;--arv-success: #66bb6a;--arv-danger: #ef5350;--arv-text: #e0e0e0;--arv-text-muted: #9e9e9e;--arv-bg: #1e1e2e;--arv-bg2: #27273a;--arv-border: #383850;--arv-radius: 8px}:host(.theme-light){--arv-primary: #1565c0;--arv-primary-hover: #0d47a1;--arv-success: #2e7d32;--arv-danger: #c62828;--arv-text: #212121;--arv-text-muted: #616161;--arv-bg: #ffffff;--arv-bg2: #f5f5f5;--arv-border: #e0e0e0}.arv-container{display:flex;flex-direction:column;height:100%}.arv-content-body{flex:1;overflow:auto}.arv-footer{border-top:1px solid var(--arv-border);background:var(--arv-bg2)}.arv-footer-inner{padding:16px;display:flex;flex-direction:column;gap:14px}.arv-section-title{margin:0 0 8px;font-size:13px;font-weight:700;color:var(--arv-primary);text-transform:uppercase;letter-spacing:.5px}.arv-routing-header{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:8px}.arv-routing-mode{display:flex;gap:12px}.arv-radio{display:flex;align-items:center;gap:6px;font-size:13px;color:var(--arv-text);cursor:pointer}.arv-label{font-size:12px;color:var(--arv-text-muted);display:block;margin-bottom:4px}.arv-select{width:100%;padding:8px 10px;border:1px solid var(--arv-border);border-radius:var(--arv-radius);background:var(--arv-bg);color:var(--arv-text);font-size:13px}.arv-input{padding:7px 10px;border:1px solid var(--arv-border);border-radius:var(--arv-radius);background:var(--arv-bg);color:var(--arv-text);font-size:13px}.arv-input-sm{width:100%;margin-top:4px}.arv-steps{display:flex;flex-direction:column;gap:10px}.arv-step{background:var(--arv-bg);border:1px solid var(--arv-border);border-radius:var(--arv-radius);padding:10px}.arv-step-header{display:flex;align-items:center;gap:8px;margin-bottom:8px}.arv-step-num{font-size:12px;font-weight:700;color:var(--arv-primary);min-width:44px}.arv-approver-tags{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:6px}.arv-tag{display:inline-flex;align-items:center;gap:4px;padding:3px 8px;background:#90caf91f;border:1px solid rgba(144,202,249,.3);border-radius:12px;font-size:12px;color:var(--arv-primary)}.arv-tag-remove{background:none;border:none;cursor:pointer;color:var(--arv-text-muted);font-size:11px;padding:0 2px;line-height:1}.arv-tag-remove:hover{color:var(--arv-danger)}.arv-user-search{position:relative}.arv-search-results{position:absolute;left:0;right:0;z-index:100;background:var(--arv-bg2);border:1px solid var(--arv-border);border-radius:var(--arv-radius);max-height:150px;overflow-y:auto}.arv-search-item{padding:8px 12px;cursor:pointer;font-size:13px;color:var(--arv-text)}.arv-search-item:hover{background:var(--arv-bg)}.arv-hint{font-size:12px;color:var(--arv-text-muted);margin:0 0 8px}.arv-btn-icon{background:none;border:none;cursor:pointer;padding:2px;border-radius:4px;display:inline-flex;align-items:center;justify-content:center}.arv-btn-danger{color:var(--arv-danger)}.arv-btn-danger:hover{background:#ef53501a}.arv-actions{display:flex;justify-content:flex-end;gap:10px}.arv-btn{padding:9px 18px;border-radius:var(--arv-radius);font-size:13px;font-weight:600;cursor:pointer;border:none;transition:background .15s}.arv-btn-primary{background:var(--arv-primary);color:#fff}.arv-btn-primary:hover:not(:disabled){background:var(--arv-primary-hover)}.arv-btn-primary:disabled{opacity:.6;cursor:not-allowed}.arv-btn-secondary{background:transparent;border:1px solid var(--arv-border);color:var(--arv-text-muted)}.arv-btn-secondary:hover{color:var(--arv-text);border-color:var(--arv-text-muted)}.arv-btn-outline{background:transparent;border:1px dashed var(--arv-border);color:var(--arv-text-muted);font-size:12px;padding:6px 12px}.arv-btn-outline:hover{border-color:var(--arv-primary);color:var(--arv-primary)}.arv-spinner{display:inline-block;width:14px;height:14px;border:2px solid rgba(255,255,255,.3);border-top-color:#fff;border-radius:50%;animation:arv-spin .6s linear infinite;margin-right:6px}@keyframes arv-spin{to{transform:rotate(360deg)}}.arv-error{padding:8px 12px;background:#ef53501a;border:1px solid rgba(239,83,80,.3);border-radius:var(--arv-radius);font-size:13px;color:var(--arv-danger)}.arv-success{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:32px;gap:12px;text-align:center}.arv-success p{color:var(--arv-success);font-size:15px;font-weight:600;margin:0}.arv-references{border-top:1px solid var(--arv-border);padding-top:12px}.arv-template-loading{font-size:12px;color:var(--arv-text-muted);margin-top:8px;padding:4px 0}.arv-template-steps{margin-top:8px;display:flex;flex-direction:column;gap:6px}.arv-step-card{background:var(--arv-bg);border:1px solid var(--arv-border);border-radius:var(--arv-radius);overflow:hidden}.arv-step-card-header{display:flex;align-items:center;gap:8px;padding:7px 10px;background:#00000008;border-bottom:1px solid var(--arv-border);flex-wrap:wrap}.arv-step-preview{display:flex;align-items:center;gap:8px;padding:5px 8px;background:var(--arv-bg);border:1px solid var(--arv-border);border-radius:4px;font-size:12px}.arv-step-preview-num{font-weight:700;color:var(--arv-primary);min-width:44px;flex-shrink:0;font-size:12px}.arv-step-preview-name{flex:1;color:var(--arv-text);font-size:12px;font-weight:600}.arv-step-role-badge{font-size:11px;color:var(--arv-text-muted);background:#90caf91a;border:1px solid rgba(144,202,249,.25);border-radius:10px;padding:1px 7px}.arv-step-preview-cc .arv-step-preview-num{color:var(--arv-text-muted)}.arv-locked-template{display:flex;align-items:center;gap:8px;padding:7px 10px;background:var(--arv-bg);border:1px solid var(--arv-border);border-radius:var(--arv-radius);margin-top:6px}.arv-locked-label{font-size:11px;color:var(--arv-text-muted);text-transform:uppercase;letter-spacing:.5px;flex-shrink:0}.arv-locked-name{font-size:13px;font-weight:600;color:var(--arv-text)}.arv-step-picker{padding:8px 10px}.arv-select-sm{font-size:12px;padding:6px 8px}.arv-step-fixed{padding:6px 10px;font-size:12px;color:var(--arv-text-muted)}\n"], dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }] });
2447
2587
  }
2448
2588
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: MaArvContainerComponent, decorators: [{
2449
2589
  type: Component,
@@ -2462,7 +2602,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
2462
2602
  <div class="arv-routing">
2463
2603
  <div class="arv-routing-header">
2464
2604
  <h4 class="arv-section-title">Approval Routing</h4>
2465
- <div class="arv-routing-mode">
2605
+ <!-- Show routing toggle only when templateId is NOT locked -->
2606
+ <div *ngIf="!templateId" class="arv-routing-mode">
2466
2607
  <label class="arv-radio">
2467
2608
  <input type="radio" name="routingMode" value="template" [checked]="routingMode === 'template'" (change)="routingMode = 'template'"> Use Template
2468
2609
  </label>
@@ -2472,15 +2613,62 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
2472
2613
  </div>
2473
2614
  </div>
2474
2615
 
2475
- <!-- Template selector -->
2476
- <div *ngIf="routingMode === 'template'" class="arv-template-select">
2616
+ <!-- Locked template: show name only, no selector -->
2617
+ <div *ngIf="templateId && routingMode === 'template'" class="arv-template-select">
2618
+ <div *ngIf="loadingTemplate" class="arv-template-loading">Loading template...</div>
2619
+ <div *ngIf="selectedTemplate && !loadingTemplate" class="arv-locked-template">
2620
+ <span class="arv-locked-label">Template</span>
2621
+ <span class="arv-locked-name">{{ selectedTemplate.name }}</span>
2622
+ </div>
2623
+ </div>
2624
+
2625
+ <!-- Free template selector (no locked templateId) -->
2626
+ <div *ngIf="!templateId && routingMode === 'template'" class="arv-template-select">
2477
2627
  <label class="arv-label">Select Template</label>
2478
2628
  <select class="arv-select" (change)="onTemplateChange($event)">
2479
2629
  <option value="">-- Select a template --</option>
2480
- <option *ngFor="let t of templates" [value]="t.id">{{ t.name }}</option>
2630
+ <option *ngFor="let t of templates" [value]="t.id" [selected]="t.id === selectedTemplateId">{{ t.name }}</option>
2481
2631
  </select>
2482
2632
  </div>
2483
2633
 
2634
+ <!-- Step pickers (shared for both locked and free template) -->
2635
+ <div *ngIf="routingMode === 'template'" class="arv-template-select">
2636
+ <div *ngIf="!loadingTemplate && selectedTemplate" class="arv-template-steps">
2637
+ <div class="arv-step-card" *ngFor="let s of selectedTemplate.steps; let i = index">
2638
+ <div class="arv-step-card-header">
2639
+ <span class="arv-step-preview-num">Step {{ s.stepOrder }}</span>
2640
+ <span class="arv-step-preview-name">{{ s.stepName }}</span>
2641
+ <span *ngIf="s.roles && s.roles.length > 0" class="arv-step-role-badge">
2642
+ {{ s.roles[0].positionLevel }}{{ s.roles[0].orgName ? ' · ' + s.roles[0].orgName : '' }}
2643
+ </span>
2644
+ </div>
2645
+ <!-- Role-based step: show approver picker -->
2646
+ <div *ngIf="s.roles && s.roles.length > 0" class="arv-step-picker">
2647
+ <div *ngIf="stepLoadingCandidates[i]" class="arv-template-loading">Loading candidates...</div>
2648
+ <select *ngIf="!stepLoadingCandidates[i]" class="arv-select arv-select-sm"
2649
+ (change)="onStepUserChange(i, $event)">
2650
+ <option value="">-- Select approver --</option>
2651
+ <option *ngFor="let u of stepCandidates[i]" [value]="u.userId"
2652
+ [selected]="u.userId === stepSelectedUsers[i]">
2653
+ {{ u.fullName || u.userId }}{{ u.department ? ' · ' + u.department : '' }}{{ u.position ? ' (' + u.position + ')' : '' }}
2654
+ </option>
2655
+ </select>
2656
+ <div *ngIf="!stepLoadingCandidates[i] && stepCandidates[i]?.length === 0" class="arv-template-loading">
2657
+ No candidates found for this role.
2658
+ </div>
2659
+ </div>
2660
+ <!-- Fixed-user step -->
2661
+ <div *ngIf="!s.roles || s.roles.length === 0" class="arv-step-fixed">
2662
+ {{ s.assigneeUserIds.length }} pre-configured approver(s)
2663
+ </div>
2664
+ </div>
2665
+ <div *ngIf="selectedTemplate.referenceUserIds.length > 0" class="arv-step-preview arv-step-preview-cc">
2666
+ <span class="arv-step-preview-num">CC</span>
2667
+ <span class="arv-step-preview-name">{{ selectedTemplate.referenceUserIds.length }} reference user(s) from template</span>
2668
+ </div>
2669
+ </div>
2670
+ </div>
2671
+
2484
2672
  <!-- Ad-hoc steps -->
2485
2673
  <div *ngIf="routingMode === 'adhoc'" class="arv-steps">
2486
2674
  <div class="arv-step" *ngFor="let step of adHocSteps; let i = index">
@@ -2562,7 +2750,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
2562
2750
  <p>Submitted for approval successfully.</p>
2563
2751
  </div>
2564
2752
  </div>
2565
- `, styles: [":host{display:block;--arv-primary: #90caf9;--arv-primary-hover: #64b5f6;--arv-success: #66bb6a;--arv-danger: #ef5350;--arv-text: #e0e0e0;--arv-text-muted: #9e9e9e;--arv-bg: #1e1e2e;--arv-bg2: #27273a;--arv-border: #383850;--arv-radius: 8px}:host(.theme-light){--arv-primary: #1565c0;--arv-primary-hover: #0d47a1;--arv-success: #2e7d32;--arv-danger: #c62828;--arv-text: #212121;--arv-text-muted: #616161;--arv-bg: #ffffff;--arv-bg2: #f5f5f5;--arv-border: #e0e0e0}.arv-container{display:flex;flex-direction:column;height:100%}.arv-content-body{flex:1;overflow:auto}.arv-footer{border-top:1px solid var(--arv-border);background:var(--arv-bg2)}.arv-footer-inner{padding:16px;display:flex;flex-direction:column;gap:14px}.arv-section-title{margin:0 0 8px;font-size:13px;font-weight:700;color:var(--arv-primary);text-transform:uppercase;letter-spacing:.5px}.arv-routing-header{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:8px}.arv-routing-mode{display:flex;gap:12px}.arv-radio{display:flex;align-items:center;gap:6px;font-size:13px;color:var(--arv-text);cursor:pointer}.arv-label{font-size:12px;color:var(--arv-text-muted);display:block;margin-bottom:4px}.arv-select{width:100%;padding:8px 10px;border:1px solid var(--arv-border);border-radius:var(--arv-radius);background:var(--arv-bg);color:var(--arv-text);font-size:13px}.arv-input{padding:7px 10px;border:1px solid var(--arv-border);border-radius:var(--arv-radius);background:var(--arv-bg);color:var(--arv-text);font-size:13px}.arv-input-sm{width:100%;margin-top:4px}.arv-steps{display:flex;flex-direction:column;gap:10px}.arv-step{background:var(--arv-bg);border:1px solid var(--arv-border);border-radius:var(--arv-radius);padding:10px}.arv-step-header{display:flex;align-items:center;gap:8px;margin-bottom:8px}.arv-step-num{font-size:12px;font-weight:700;color:var(--arv-primary);min-width:44px}.arv-approver-tags{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:6px}.arv-tag{display:inline-flex;align-items:center;gap:4px;padding:3px 8px;background:#90caf91f;border:1px solid rgba(144,202,249,.3);border-radius:12px;font-size:12px;color:var(--arv-primary)}.arv-tag-remove{background:none;border:none;cursor:pointer;color:var(--arv-text-muted);font-size:11px;padding:0 2px;line-height:1}.arv-tag-remove:hover{color:var(--arv-danger)}.arv-user-search{position:relative}.arv-search-results{position:absolute;left:0;right:0;z-index:100;background:var(--arv-bg2);border:1px solid var(--arv-border);border-radius:var(--arv-radius);max-height:150px;overflow-y:auto}.arv-search-item{padding:8px 12px;cursor:pointer;font-size:13px;color:var(--arv-text)}.arv-search-item:hover{background:var(--arv-bg)}.arv-hint{font-size:12px;color:var(--arv-text-muted);margin:0 0 8px}.arv-btn-icon{background:none;border:none;cursor:pointer;padding:2px;border-radius:4px;display:inline-flex;align-items:center;justify-content:center}.arv-btn-danger{color:var(--arv-danger)}.arv-btn-danger:hover{background:#ef53501a}.arv-actions{display:flex;justify-content:flex-end;gap:10px}.arv-btn{padding:9px 18px;border-radius:var(--arv-radius);font-size:13px;font-weight:600;cursor:pointer;border:none;transition:background .15s}.arv-btn-primary{background:var(--arv-primary);color:#fff}.arv-btn-primary:hover:not(:disabled){background:var(--arv-primary-hover)}.arv-btn-primary:disabled{opacity:.6;cursor:not-allowed}.arv-btn-secondary{background:transparent;border:1px solid var(--arv-border);color:var(--arv-text-muted)}.arv-btn-secondary:hover{color:var(--arv-text);border-color:var(--arv-text-muted)}.arv-btn-outline{background:transparent;border:1px dashed var(--arv-border);color:var(--arv-text-muted);font-size:12px;padding:6px 12px}.arv-btn-outline:hover{border-color:var(--arv-primary);color:var(--arv-primary)}.arv-spinner{display:inline-block;width:14px;height:14px;border:2px solid rgba(255,255,255,.3);border-top-color:#fff;border-radius:50%;animation:arv-spin .6s linear infinite;margin-right:6px}@keyframes arv-spin{to{transform:rotate(360deg)}}.arv-error{padding:8px 12px;background:#ef53501a;border:1px solid rgba(239,83,80,.3);border-radius:var(--arv-radius);font-size:13px;color:var(--arv-danger)}.arv-success{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:32px;gap:12px;text-align:center}.arv-success p{color:var(--arv-success);font-size:15px;font-weight:600;margin:0}.arv-references{border-top:1px solid var(--arv-border);padding-top:12px}\n"] }]
2753
+ `, styles: [":host{display:block;--arv-primary: #90caf9;--arv-primary-hover: #64b5f6;--arv-success: #66bb6a;--arv-danger: #ef5350;--arv-text: #e0e0e0;--arv-text-muted: #9e9e9e;--arv-bg: #1e1e2e;--arv-bg2: #27273a;--arv-border: #383850;--arv-radius: 8px}:host(.theme-light){--arv-primary: #1565c0;--arv-primary-hover: #0d47a1;--arv-success: #2e7d32;--arv-danger: #c62828;--arv-text: #212121;--arv-text-muted: #616161;--arv-bg: #ffffff;--arv-bg2: #f5f5f5;--arv-border: #e0e0e0}.arv-container{display:flex;flex-direction:column;height:100%}.arv-content-body{flex:1;overflow:auto}.arv-footer{border-top:1px solid var(--arv-border);background:var(--arv-bg2)}.arv-footer-inner{padding:16px;display:flex;flex-direction:column;gap:14px}.arv-section-title{margin:0 0 8px;font-size:13px;font-weight:700;color:var(--arv-primary);text-transform:uppercase;letter-spacing:.5px}.arv-routing-header{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:8px}.arv-routing-mode{display:flex;gap:12px}.arv-radio{display:flex;align-items:center;gap:6px;font-size:13px;color:var(--arv-text);cursor:pointer}.arv-label{font-size:12px;color:var(--arv-text-muted);display:block;margin-bottom:4px}.arv-select{width:100%;padding:8px 10px;border:1px solid var(--arv-border);border-radius:var(--arv-radius);background:var(--arv-bg);color:var(--arv-text);font-size:13px}.arv-input{padding:7px 10px;border:1px solid var(--arv-border);border-radius:var(--arv-radius);background:var(--arv-bg);color:var(--arv-text);font-size:13px}.arv-input-sm{width:100%;margin-top:4px}.arv-steps{display:flex;flex-direction:column;gap:10px}.arv-step{background:var(--arv-bg);border:1px solid var(--arv-border);border-radius:var(--arv-radius);padding:10px}.arv-step-header{display:flex;align-items:center;gap:8px;margin-bottom:8px}.arv-step-num{font-size:12px;font-weight:700;color:var(--arv-primary);min-width:44px}.arv-approver-tags{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:6px}.arv-tag{display:inline-flex;align-items:center;gap:4px;padding:3px 8px;background:#90caf91f;border:1px solid rgba(144,202,249,.3);border-radius:12px;font-size:12px;color:var(--arv-primary)}.arv-tag-remove{background:none;border:none;cursor:pointer;color:var(--arv-text-muted);font-size:11px;padding:0 2px;line-height:1}.arv-tag-remove:hover{color:var(--arv-danger)}.arv-user-search{position:relative}.arv-search-results{position:absolute;left:0;right:0;z-index:100;background:var(--arv-bg2);border:1px solid var(--arv-border);border-radius:var(--arv-radius);max-height:150px;overflow-y:auto}.arv-search-item{padding:8px 12px;cursor:pointer;font-size:13px;color:var(--arv-text)}.arv-search-item:hover{background:var(--arv-bg)}.arv-hint{font-size:12px;color:var(--arv-text-muted);margin:0 0 8px}.arv-btn-icon{background:none;border:none;cursor:pointer;padding:2px;border-radius:4px;display:inline-flex;align-items:center;justify-content:center}.arv-btn-danger{color:var(--arv-danger)}.arv-btn-danger:hover{background:#ef53501a}.arv-actions{display:flex;justify-content:flex-end;gap:10px}.arv-btn{padding:9px 18px;border-radius:var(--arv-radius);font-size:13px;font-weight:600;cursor:pointer;border:none;transition:background .15s}.arv-btn-primary{background:var(--arv-primary);color:#fff}.arv-btn-primary:hover:not(:disabled){background:var(--arv-primary-hover)}.arv-btn-primary:disabled{opacity:.6;cursor:not-allowed}.arv-btn-secondary{background:transparent;border:1px solid var(--arv-border);color:var(--arv-text-muted)}.arv-btn-secondary:hover{color:var(--arv-text);border-color:var(--arv-text-muted)}.arv-btn-outline{background:transparent;border:1px dashed var(--arv-border);color:var(--arv-text-muted);font-size:12px;padding:6px 12px}.arv-btn-outline:hover{border-color:var(--arv-primary);color:var(--arv-primary)}.arv-spinner{display:inline-block;width:14px;height:14px;border:2px solid rgba(255,255,255,.3);border-top-color:#fff;border-radius:50%;animation:arv-spin .6s linear infinite;margin-right:6px}@keyframes arv-spin{to{transform:rotate(360deg)}}.arv-error{padding:8px 12px;background:#ef53501a;border:1px solid rgba(239,83,80,.3);border-radius:var(--arv-radius);font-size:13px;color:var(--arv-danger)}.arv-success{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:32px;gap:12px;text-align:center}.arv-success p{color:var(--arv-success);font-size:15px;font-weight:600;margin:0}.arv-references{border-top:1px solid var(--arv-border);padding-top:12px}.arv-template-loading{font-size:12px;color:var(--arv-text-muted);margin-top:8px;padding:4px 0}.arv-template-steps{margin-top:8px;display:flex;flex-direction:column;gap:6px}.arv-step-card{background:var(--arv-bg);border:1px solid var(--arv-border);border-radius:var(--arv-radius);overflow:hidden}.arv-step-card-header{display:flex;align-items:center;gap:8px;padding:7px 10px;background:#00000008;border-bottom:1px solid var(--arv-border);flex-wrap:wrap}.arv-step-preview{display:flex;align-items:center;gap:8px;padding:5px 8px;background:var(--arv-bg);border:1px solid var(--arv-border);border-radius:4px;font-size:12px}.arv-step-preview-num{font-weight:700;color:var(--arv-primary);min-width:44px;flex-shrink:0;font-size:12px}.arv-step-preview-name{flex:1;color:var(--arv-text);font-size:12px;font-weight:600}.arv-step-role-badge{font-size:11px;color:var(--arv-text-muted);background:#90caf91a;border:1px solid rgba(144,202,249,.25);border-radius:10px;padding:1px 7px}.arv-step-preview-cc .arv-step-preview-num{color:var(--arv-text-muted)}.arv-locked-template{display:flex;align-items:center;gap:8px;padding:7px 10px;background:var(--arv-bg);border:1px solid var(--arv-border);border-radius:var(--arv-radius);margin-top:6px}.arv-locked-label{font-size:11px;color:var(--arv-text-muted);text-transform:uppercase;letter-spacing:.5px;flex-shrink:0}.arv-locked-name{font-size:13px;font-weight:600;color:var(--arv-text)}.arv-step-picker{padding:8px 10px}.arv-select-sm{font-size:12px;padding:6px 8px}.arv-step-fixed{padding:6px 10px;font-size:12px;color:var(--arv-text-muted)}\n"] }]
2566
2754
  }], propDecorators: { title: [{
2567
2755
  type: Input
2568
2756
  }], description: [{
@@ -2577,13 +2765,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
2577
2765
  type: Input
2578
2766
  }], approvalSubmitted: [{
2579
2767
  type: Output
2580
- }], approvalSubmiting: [{
2768
+ }], approvalSubmitting: [{
2581
2769
  type: Output
2582
2770
  }], cancelled: [{
2583
2771
  type: Output
2584
2772
  }], contentBody: [{
2585
2773
  type: ViewChild,
2586
2774
  args: ['contentBody', { static: true }]
2775
+ }], themeClass: [{
2776
+ type: HostBinding,
2777
+ args: ['class']
2587
2778
  }] } });
2588
2779
 
2589
2780
  /**