@epistola.app/valtimo-plugin 0.2.3 → 0.3.1

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.
@@ -1,7 +1,7 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Injectable, EventEmitter, Output, Input, Component, NgModule } from '@angular/core';
2
+ import { Injectable, EventEmitter, Output, Input, Component, ChangeDetectionStrategy, NgModule } from '@angular/core';
3
3
  import * as i1 from '@angular/common/http';
4
- import { HttpClientModule } from '@angular/common/http';
4
+ import { HttpHeaders, HttpClientModule } from '@angular/common/http';
5
5
  import * as i2 from '@valtimo/shared';
6
6
  import * as i1$1 from '@angular/common';
7
7
  import { CommonModule } from '@angular/common';
@@ -10,10 +10,13 @@ import { PluginTranslatePipeModule } from '@valtimo/plugin';
10
10
  import * as i3 from '@valtimo/components';
11
11
  import { FormModule, InputModule, ValuePathSelectorPrefix, ValuePathSelectorComponent, SelectModule, registerCustomFormioComponent } from '@valtimo/components';
12
12
  import { BehaviorSubject, combineLatest, take, Subject, merge, of } from 'rxjs';
13
- import { startWith, delay, takeUntil, filter, map, catchError, take as take$1 } from 'rxjs/operators';
13
+ import { startWith, delay, takeUntil, filter, map, catchError, take as take$1, debounceTime } from 'rxjs/operators';
14
14
  import * as i4 from '@angular/forms';
15
15
  import { FormsModule } from '@angular/forms';
16
16
  import * as i2$2 from '@valtimo/process-link';
17
+ import * as i7 from '@formio/angular';
18
+ import { FormioModule } from '@formio/angular';
19
+ import * as i4$1 from '@angular/platform-browser';
17
20
 
18
21
  /**
19
22
  * Service for interacting with Epistola plugin API endpoints.
@@ -65,10 +68,42 @@ class EpistolaPluginService {
65
68
  validateMapping(pluginConfigurationId, templateId, dataMapping) {
66
69
  return this.http.post(`${this.apiEndpoint}/configurations/${pluginConfigurationId}/templates/${templateId}/validate-mapping`, { dataMapping });
67
70
  }
68
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: EpistolaPluginService, deps: [{ token: i1.HttpClient }, { token: i2.ConfigService }], target: i0.ɵɵFactoryTarget.Injectable });
69
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: EpistolaPluginService });
71
+ /**
72
+ * Get a dynamically generated Formio form for retrying a failed document generation.
73
+ */
74
+ getRetryForm(processInstanceId, documentId, sourceActivityId) {
75
+ const params = { processInstanceId };
76
+ if (documentId) {
77
+ params['documentId'] = documentId;
78
+ }
79
+ if (sourceActivityId) {
80
+ params['sourceActivityId'] = sourceActivityId;
81
+ }
82
+ return this.http.get(`${this.apiEndpoint}/retry-form`, { params });
83
+ }
84
+ /**
85
+ * Discover all previewable document sources for a given Valtimo document.
86
+ */
87
+ getPreviewSources(documentId) {
88
+ return this.http.get(`${this.apiEndpoint}/preview-sources`, { params: { documentId } });
89
+ }
90
+ /**
91
+ * Preview a document by dry-running the generate-document process link.
92
+ * Returns the resolved data as a mock preview (Phase 1).
93
+ */
94
+ previewDocument(documentId, processDefinitionKey, sourceActivityId, processInstanceId, overrides) {
95
+ return this.http.post(`${this.apiEndpoint}/preview`, {
96
+ documentId,
97
+ processDefinitionKey,
98
+ sourceActivityId,
99
+ processInstanceId: processInstanceId || null,
100
+ overrides: overrides || null
101
+ });
102
+ }
103
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaPluginService, deps: [{ token: i1.HttpClient }, { token: i2.ConfigService }], target: i0.ɵɵFactoryTarget.Injectable });
104
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaPluginService });
70
105
  }
71
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: EpistolaPluginService, decorators: [{
106
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaPluginService, decorators: [{
72
107
  type: Injectable
73
108
  }], ctorParameters: () => [{ type: i1.HttpClient }, { type: i2.ConfigService }] });
74
109
 
@@ -132,10 +167,10 @@ class EpistolaConfigurationComponent {
132
167
  });
133
168
  });
134
169
  }
135
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: EpistolaConfigurationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
136
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.17", type: EpistolaConfigurationComponent, isStandalone: true, selector: "epistola-configuration", inputs: { save$: "save$", disabled$: "disabled$", pluginId: "pluginId", prefillConfiguration$: "prefillConfiguration$" }, outputs: { valid: "valid", configuration: "configuration" }, ngImport: i0, template: "<v-form\n (valueChange)=\"formValueChange($event)\"\n *ngIf=\"{\n disabled: safeDisabled$ | async,\n prefill: prefillConfiguration$ ? (prefillConfiguration$ | async) : null\n } as obs\"\n>\n <v-input\n name=\"configurationTitle\"\n [title]=\"'configurationTitle' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.configurationTitle\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-input>\n\n <v-input\n name=\"baseUrl\"\n [title]=\"'baseUrl' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'baseUrlTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.baseUrl\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-input>\n\n <v-input\n name=\"apiKey\"\n [title]=\"'apiKey' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'apiKeyTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.apiKey\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n type=\"password\"\n >\n </v-input>\n\n <v-input\n name=\"tenantId\"\n [title]=\"'tenantId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'tenantIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.tenantId\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-input>\n\n <v-input\n name=\"defaultEnvironmentId\"\n [title]=\"'defaultEnvironmentId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'defaultEnvironmentIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.defaultEnvironmentId\"\n [disabled]=\"obs.disabled\"\n [required]=\"false\"\n >\n </v-input>\n\n <v-input\n name=\"templateSyncEnabled\"\n type=\"checkbox\"\n [title]=\"'templateSyncEnabled' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'templateSyncEnabledTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.templateSyncEnabled ? 'true' : ''\"\n [disabled]=\"obs.disabled\"\n [required]=\"false\"\n >\n </v-input>\n</v-form>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "ngmodule", type: PluginTranslatePipeModule }, { kind: "pipe", type: i2$1.PluginTranslatePipe, name: "pluginTranslate" }, { kind: "ngmodule", type: FormModule }, { kind: "component", type: i3.FormComponent, selector: "v-form", inputs: ["className"], outputs: ["valueChange"] }, { kind: "ngmodule", type: InputModule }, { kind: "component", type: i3.InputComponent, selector: "v-input", inputs: ["name", "type", "title", "titleTranslationKey", "defaultValue", "widthPx", "fullWidth", "margin", "smallMargin", "disabled", "step", "min", "maxLength", "tooltip", "required", "hideNumberSpinBox", "smallLabel", "rows", "clear$", "carbonTheme", "placeholder", "dataTestId", "trim", "presetsTitle", "presetOptions"], outputs: ["valueChange"] }] });
170
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaConfigurationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
171
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: EpistolaConfigurationComponent, isStandalone: true, selector: "epistola-configuration", inputs: { save$: "save$", disabled$: "disabled$", pluginId: "pluginId", prefillConfiguration$: "prefillConfiguration$" }, outputs: { valid: "valid", configuration: "configuration" }, ngImport: i0, template: "<v-form\n (valueChange)=\"formValueChange($event)\"\n *ngIf=\"{\n disabled: safeDisabled$ | async,\n prefill: prefillConfiguration$ ? (prefillConfiguration$ | async) : null\n } as obs\"\n>\n <v-input\n name=\"configurationTitle\"\n [title]=\"'configurationTitle' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.configurationTitle\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-input>\n\n <v-input\n name=\"baseUrl\"\n [title]=\"'baseUrl' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'baseUrlTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.baseUrl\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-input>\n\n <v-input\n name=\"apiKey\"\n [title]=\"'apiKey' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'apiKeyTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.apiKey\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n type=\"password\"\n >\n </v-input>\n\n <v-input\n name=\"tenantId\"\n [title]=\"'tenantId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'tenantIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.tenantId\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-input>\n\n <v-input\n name=\"defaultEnvironmentId\"\n [title]=\"'defaultEnvironmentId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'defaultEnvironmentIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.defaultEnvironmentId\"\n [disabled]=\"obs.disabled\"\n [required]=\"false\"\n >\n </v-input>\n\n <v-input\n name=\"templateSyncEnabled\"\n type=\"checkbox\"\n [title]=\"'templateSyncEnabled' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'templateSyncEnabledTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.templateSyncEnabled ? 'true' : ''\"\n [disabled]=\"obs.disabled\"\n [required]=\"false\"\n >\n </v-input>\n</v-form>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "ngmodule", type: PluginTranslatePipeModule }, { kind: "pipe", type: i2$1.PluginTranslatePipe, name: "pluginTranslate" }, { kind: "ngmodule", type: FormModule }, { kind: "component", type: i3.FormComponent, selector: "v-form", inputs: ["className"], outputs: ["valueChange"] }, { kind: "ngmodule", type: InputModule }, { kind: "component", type: i3.InputComponent, selector: "v-input", inputs: ["name", "type", "title", "titleTranslationKey", "defaultValue", "widthPx", "fullWidth", "margin", "smallMargin", "disabled", "step", "min", "maxLength", "tooltip", "required", "hideNumberSpinBox", "smallLabel", "rows", "clear$", "carbonTheme", "placeholder", "dataTestId", "trim", "presetsTitle", "presetOptions"], outputs: ["valueChange"] }] });
137
172
  }
138
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: EpistolaConfigurationComponent, decorators: [{
173
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaConfigurationComponent, decorators: [{
139
174
  type: Component,
140
175
  args: [{ selector: 'epistola-configuration', standalone: true, imports: [CommonModule, PluginTranslatePipeModule, FormModule, InputModule], template: "<v-form\n (valueChange)=\"formValueChange($event)\"\n *ngIf=\"{\n disabled: safeDisabled$ | async,\n prefill: prefillConfiguration$ ? (prefillConfiguration$ | async) : null\n } as obs\"\n>\n <v-input\n name=\"configurationTitle\"\n [title]=\"'configurationTitle' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.configurationTitle\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-input>\n\n <v-input\n name=\"baseUrl\"\n [title]=\"'baseUrl' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'baseUrlTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.baseUrl\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-input>\n\n <v-input\n name=\"apiKey\"\n [title]=\"'apiKey' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'apiKeyTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.apiKey\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n type=\"password\"\n >\n </v-input>\n\n <v-input\n name=\"tenantId\"\n [title]=\"'tenantId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'tenantIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.tenantId\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-input>\n\n <v-input\n name=\"defaultEnvironmentId\"\n [title]=\"'defaultEnvironmentId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'defaultEnvironmentIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.defaultEnvironmentId\"\n [disabled]=\"obs.disabled\"\n [required]=\"false\"\n >\n </v-input>\n\n <v-input\n name=\"templateSyncEnabled\"\n type=\"checkbox\"\n [title]=\"'templateSyncEnabled' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'templateSyncEnabledTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.templateSyncEnabled ? 'true' : ''\"\n [disabled]=\"obs.disabled\"\n [required]=\"false\"\n >\n </v-input>\n</v-form>\n" }]
141
176
  }], propDecorators: { save$: [{
@@ -152,6 +187,41 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
152
187
  type: Output
153
188
  }] } });
154
189
 
190
+ function countRequiredMapped(fields, mapping) {
191
+ let mapped = 0;
192
+ let total = 0;
193
+ for (const field of fields) {
194
+ if (field.fieldType === 'SCALAR' && field.required) {
195
+ total++;
196
+ const val = mapping[field.name];
197
+ if (typeof val === 'string' && val.trim().length > 0) {
198
+ mapped++;
199
+ }
200
+ }
201
+ else if (field.fieldType === 'ARRAY' && field.required) {
202
+ total++;
203
+ const val = mapping[field.name];
204
+ if (typeof val === 'string' && val.trim().length > 0) {
205
+ mapped++;
206
+ }
207
+ else if (typeof val === 'object' && val !== null && '_source' in val) {
208
+ if (typeof val['_source'] === 'string' && val['_source'].trim().length > 0) {
209
+ mapped++;
210
+ }
211
+ }
212
+ }
213
+ else if (field.fieldType === 'OBJECT' && field.children) {
214
+ const nested = (typeof mapping[field.name] === 'object' && mapping[field.name] !== null)
215
+ ? mapping[field.name]
216
+ : {};
217
+ const childStats = countRequiredMapped(field.children, nested);
218
+ mapped += childStats.mapped;
219
+ total += childStats.total;
220
+ }
221
+ }
222
+ return { mapped, total };
223
+ }
224
+
155
225
  /**
156
226
  * Recursive component that renders a single TemplateField node as part of a tree form.
157
227
  *
@@ -332,7 +402,7 @@ class FieldTreeComponent {
332
402
  }
333
403
  updateCompleteness() {
334
404
  if (this.field?.fieldType === 'OBJECT' && this.field.children) {
335
- const stats = this.countRequiredMapped(this.field.children, this.value || {});
405
+ const stats = countRequiredMapped(this.field.children, this.value || {});
336
406
  this.mappedCount = stats.mapped;
337
407
  this.totalRequired = stats.total;
338
408
  }
@@ -377,40 +447,6 @@ class FieldTreeComponent {
377
447
  this.mappedCount = this.getSourceValue() ? (this.field?.required ? 1 : 0) : 0;
378
448
  }
379
449
  }
380
- countRequiredMapped(fields, mapping) {
381
- let mapped = 0;
382
- let total = 0;
383
- for (const field of fields) {
384
- if (field.fieldType === 'SCALAR' && field.required) {
385
- total++;
386
- const val = mapping[field.name];
387
- if (typeof val === 'string' && val.trim().length > 0) {
388
- mapped++;
389
- }
390
- }
391
- else if (field.fieldType === 'ARRAY' && field.required) {
392
- total++;
393
- const val = mapping[field.name];
394
- if (typeof val === 'string' && val.trim().length > 0) {
395
- mapped++;
396
- }
397
- else if (typeof val === 'object' && val !== null && '_source' in val) {
398
- if (typeof val['_source'] === 'string' && val['_source'].trim().length > 0) {
399
- mapped++;
400
- }
401
- }
402
- }
403
- else if (field.fieldType === 'OBJECT' && field.children) {
404
- const nested = (typeof mapping[field.name] === 'object' && mapping[field.name] !== null)
405
- ? mapping[field.name]
406
- : {};
407
- const childStats = this.countRequiredMapped(field.children, nested);
408
- mapped += childStats.mapped;
409
- total += childStats.total;
410
- }
411
- }
412
- return { mapped, total };
413
- }
414
450
  detectInputMode(value) {
415
451
  if (typeof value !== 'string')
416
452
  return 'browse';
@@ -425,12 +461,12 @@ class FieldTreeComponent {
425
461
  isResolvableValue(value) {
426
462
  return value.startsWith('doc:') || value.startsWith('case:') || value.startsWith('pv:') || value.startsWith('template:');
427
463
  }
428
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: FieldTreeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
429
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.17", type: FieldTreeComponent, isStandalone: true, selector: "epistola-field-tree", inputs: { field: "field", value: "value", pluginId: "pluginId", caseDefinitionKey: "caseDefinitionKey", processVariables: "processVariables", disabled: "disabled" }, outputs: { valueChange: "valueChange" }, usesOnChanges: true, ngImport: i0, template: "<!-- SCALAR field: label row + input with 3-mode selector -->\n<div *ngIf=\"field.fieldType === 'SCALAR'\" class=\"field-row\" [class.field-required-unmapped]=\"field.required && !getStringValue()\">\n <div class=\"field-label\">\n <span class=\"field-name\">{{ field.name }}</span>\n <span class=\"field-meta\">({{ field.type }}{{ field.required ? ', required' : '' }})</span>\n </div>\n <div class=\"field-input\">\n <div class=\"input-mode-group\">\n <button type=\"button\" class=\"mode-btn\" [class.mode-active]=\"inputMode === 'browse'\" [disabled]=\"disabled\" (click)=\"setInputMode('browse')\" [title]=\"'browseMode' | pluginTranslate: pluginId | async\">\u229E</button>\n <button type=\"button\" class=\"mode-btn\" [class.mode-active]=\"inputMode === 'pv'\" [disabled]=\"disabled\" (click)=\"setInputMode('pv')\" [title]=\"'pvMode' | pluginTranslate: pluginId | async\">pv</button>\n <button type=\"button\" class=\"mode-btn\" [class.mode-active]=\"inputMode === 'expression'\" [disabled]=\"disabled\" (click)=\"setInputMode('expression')\" [title]=\"'expressionMode' | pluginTranslate: pluginId | async\">fx</button>\n </div>\n\n <!-- Browse mode: ValuePathSelector -->\n <div class=\"field-input-control\" *ngIf=\"inputMode === 'browse'\">\n <valtimo-value-path-selector\n [name]=\"'field_' + field.path\"\n [caseDefinitionKey]=\"caseDefinitionKey\"\n [prefixes]=\"[ValuePathSelectorPrefix.DOC, ValuePathSelectorPrefix.CASE]\"\n [notation]=\"'dots'\"\n [disabled]=\"disabled\"\n [defaultValue]=\"getStringValue()\"\n [showCaseDefinitionSelector]=\"!caseDefinitionKey\"\n (valueChangeEvent)=\"onBrowseValueChange($event)\"\n ></valtimo-value-path-selector>\n </div>\n\n <!-- PV mode: dropdown (when available) or text fallback -->\n <div class=\"field-input-control\" *ngIf=\"inputMode === 'pv'\">\n <select\n *ngIf=\"processVariables.length > 0; else pvFallback\"\n class=\"pv-select\"\n [disabled]=\"disabled\"\n (change)=\"onPvChange($any($event.target).value)\"\n >\n <option value=\"\">{{ 'pvPlaceholder' | pluginTranslate: pluginId | async }}</option>\n <option *ngFor=\"let pv of processVariables\" [value]=\"pv\" [selected]=\"pv === getPvName()\">{{ pv }}</option>\n </select>\n <ng-template #pvFallback>\n <v-input\n [name]=\"'pvfb_' + field.path\"\n [defaultValue]=\"getPvName()\"\n [disabled]=\"disabled\"\n [placeholder]=\"'pvPlaceholder' | pluginTranslate: pluginId | async\"\n (valueChange)=\"onPvChange($event)\"\n ></v-input>\n </ng-template>\n </div>\n\n <!-- Expression mode: text input -->\n <div class=\"field-input-control\" *ngIf=\"inputMode === 'expression'\">\n <v-input\n [name]=\"'fx_' + field.path\"\n [defaultValue]=\"getStringValue()\"\n [disabled]=\"disabled\"\n [placeholder]=\"'e.g. pv:variableName or doc:path.to.field'\"\n (valueChange)=\"onExpressionValueChange($event)\"\n ></v-input>\n </div>\n </div>\n</div>\n\n<!-- OBJECT field: collapsible section with children -->\n<div *ngIf=\"field.fieldType === 'OBJECT'\" class=\"field-object\">\n <div class=\"field-object-header\" (click)=\"toggleExpanded()\" [class.field-required-unmapped]=\"totalRequired > 0 && mappedCount < totalRequired\">\n <span class=\"expand-icon\">{{ expanded ? '\u25BC' : '\u25B6' }}</span>\n <span class=\"field-name\">{{ field.name }}</span>\n <span class=\"field-meta\">(object{{ field.required ? ', required' : '' }})</span>\n <span class=\"completeness-badge\" *ngIf=\"totalRequired > 0 && !expanded\">\n {{ mappedCount }}/{{ totalRequired }}\n </span>\n </div>\n <div class=\"field-object-children\" *ngIf=\"expanded\">\n <epistola-field-tree\n *ngFor=\"let child of field.children\"\n [field]=\"child\"\n [value]=\"getChildValue(child.name)\"\n [pluginId]=\"pluginId\"\n [caseDefinitionKey]=\"caseDefinitionKey\"\n [processVariables]=\"processVariables\"\n [disabled]=\"disabled\"\n (valueChange)=\"onChildChange(child.name, $event)\"\n ></epistola-field-tree>\n </div>\n</div>\n\n<!-- ARRAY field: collapsible section with source collection + optional per-item mapping -->\n<div *ngIf=\"field.fieldType === 'ARRAY'\" class=\"field-array\">\n <div class=\"field-array-header\" (click)=\"toggleExpanded()\" [class.field-required-unmapped]=\"field.required && !getSourceValue()\">\n <span class=\"expand-icon\">{{ expanded ? '\u25BC' : '\u25B6' }}</span>\n <span class=\"field-name\">{{ field.name }}</span>\n <span class=\"field-meta\">(array{{ field.required ? ', required' : '' }})</span>\n <span class=\"completeness-badge\" *ngIf=\"totalRequired > 0 && !expanded\">\n {{ mappedCount }}/{{ totalRequired }}\n </span>\n <span class=\"mapped-indicator\" *ngIf=\"totalRequired === 0 && getSourceValue() && !expanded\">\u2713</span>\n </div>\n <div class=\"field-array-content\" *ngIf=\"expanded\">\n <!-- Source collection input -->\n <div class=\"field-row\">\n <div class=\"field-label\">\n <span class=\"field-name\">{{ 'mapCollectionTo' | pluginTranslate: pluginId | async }}</span>\n </div>\n <div class=\"field-input\">\n <div class=\"input-mode-group\">\n <button type=\"button\" class=\"mode-btn\" [class.mode-active]=\"inputMode === 'browse'\" [disabled]=\"disabled\" (click)=\"setInputMode('browse')\" [title]=\"'browseMode' | pluginTranslate: pluginId | async\">\u229E</button>\n <button type=\"button\" class=\"mode-btn\" [class.mode-active]=\"inputMode === 'pv'\" [disabled]=\"disabled\" (click)=\"setInputMode('pv')\" [title]=\"'pvMode' | pluginTranslate: pluginId | async\">pv</button>\n <button type=\"button\" class=\"mode-btn\" [class.mode-active]=\"inputMode === 'expression'\" [disabled]=\"disabled\" (click)=\"setInputMode('expression')\" [title]=\"'expressionMode' | pluginTranslate: pluginId | async\">fx</button>\n </div>\n\n <div class=\"field-input-control\" *ngIf=\"inputMode === 'browse'\">\n <valtimo-value-path-selector\n [name]=\"'field_' + field.path\"\n [caseDefinitionKey]=\"caseDefinitionKey\"\n [prefixes]=\"[ValuePathSelectorPrefix.DOC, ValuePathSelectorPrefix.CASE]\"\n [notation]=\"'dots'\"\n [disabled]=\"disabled\"\n [defaultValue]=\"getSourceValue()\"\n [showCaseDefinitionSelector]=\"!caseDefinitionKey\"\n (valueChangeEvent)=\"onSourceBrowseChange($event)\"\n ></valtimo-value-path-selector>\n </div>\n\n <div class=\"field-input-control\" *ngIf=\"inputMode === 'pv'\">\n <select\n *ngIf=\"processVariables.length > 0; else pvSourceFallback\"\n class=\"pv-select\"\n [disabled]=\"disabled\"\n (change)=\"onSourcePvChange($any($event.target).value)\"\n >\n <option value=\"\">{{ 'pvPlaceholder' | pluginTranslate: pluginId | async }}</option>\n <option *ngFor=\"let pv of processVariables\" [value]=\"pv\" [selected]=\"pv === getSourcePvName()\">{{ pv }}</option>\n </select>\n <ng-template #pvSourceFallback>\n <v-input\n [name]=\"'pvfb_' + field.path\"\n [defaultValue]=\"getSourcePvName()\"\n [disabled]=\"disabled\"\n [placeholder]=\"'pvPlaceholder' | pluginTranslate: pluginId | async\"\n (valueChange)=\"onSourcePvChange($event)\"\n ></v-input>\n </ng-template>\n </div>\n\n <div class=\"field-input-control\" *ngIf=\"inputMode === 'expression'\">\n <v-input\n [name]=\"'fx_' + field.path\"\n [defaultValue]=\"getSourceValue()\"\n [disabled]=\"disabled\"\n [placeholder]=\"'e.g. pv:variableName or doc:path.to.field'\"\n (valueChange)=\"onSourceExpressionChange($event)\"\n ></v-input>\n </div>\n </div>\n </div>\n\n <!-- Per-item field mapping toggle (only shown when array has children) -->\n <div class=\"array-per-field-toggle\" *ngIf=\"hasArrayChildren()\">\n <label class=\"toggle-label\">\n <input\n type=\"checkbox\"\n [checked]=\"arrayPerFieldMode\"\n [disabled]=\"disabled\"\n (change)=\"toggleArrayPerFieldMode()\"\n />\n <span>{{ 'itemFieldMapping' | pluginTranslate: pluginId | async }}</span>\n </label>\n </div>\n\n <!-- Per-item field mappings -->\n <div class=\"array-item-fields\" *ngIf=\"arrayPerFieldMode && hasArrayChildren()\">\n <div class=\"item-fields-header\">\n <span class=\"item-fields-title\">{{ 'itemFieldMappingTitle' | pluginTranslate: pluginId | async }}</span>\n </div>\n <div class=\"item-field-row\" *ngFor=\"let child of field.children\">\n <div class=\"item-field-label\">\n <span class=\"field-name\">{{ child.name }}</span>\n <span class=\"field-meta\">({{ child.type }}{{ child.required ? ', required' : '' }})</span>\n </div>\n <div class=\"item-field-input\">\n <v-input\n [name]=\"'itemField_' + child.path\"\n [defaultValue]=\"getItemFieldValue(child.name)\"\n [disabled]=\"disabled\"\n [placeholder]=\"'sourceFieldPlaceholder' | pluginTranslate: pluginId | async\"\n (valueChange)=\"onItemFieldChange(child.name, $event)\"\n ></v-input>\n </div>\n </div>\n </div>\n </div>\n</div>\n", styles: [".field-row{display:flex;align-items:flex-start;gap:.75rem;padding:.5rem 0;border-left:3px solid transparent}.field-row.field-required-unmapped{border-left-color:#dc3545;background-color:#fff5f5;padding-left:.5rem}.field-label{flex:0 0 200px;min-width:140px;padding-top:.5rem;word-break:break-word}.field-label .field-name{font-weight:500}.field-label .field-meta{font-size:.8125rem;color:#6c757d;margin-left:.25rem}.field-input{flex:1;display:flex;align-items:flex-start;gap:.5rem;min-width:0}.field-input-control{flex:1;min-width:0}.input-mode-group{flex:0 0 auto;display:flex;margin-top:.25rem;border:1px solid #c6c6c6;border-radius:4px;overflow:hidden}.input-mode-group .mode-btn{width:32px;height:32px;padding:0;border:none;border-right:1px solid #c6c6c6;background:#f4f4f4;color:#525252;font-size:.75rem;font-weight:600;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:background-color .15s}.input-mode-group .mode-btn:last-child{border-right:none}.input-mode-group .mode-btn:hover:not(:disabled){background:#e0e0e0}.input-mode-group .mode-btn.mode-active{background:#0f62fe;color:#fff}.input-mode-group .mode-btn.mode-active:hover:not(:disabled){background:#0353e9}.input-mode-group .mode-btn:disabled{opacity:.5;cursor:not-allowed}.pv-select{width:100%;height:2.5rem;padding:0 .75rem;border:1px solid #c6c6c6;border-radius:4px;background:#fff;color:#161616;font-size:.875rem;cursor:pointer}.pv-select:focus{outline:2px solid #0f62fe;outline-offset:-2px}.pv-select:disabled{opacity:.5;cursor:not-allowed;background:#f4f4f4}.field-object{margin:.25rem 0}.field-object-header,.field-array-header{display:flex;align-items:center;gap:.5rem;padding:.5rem .25rem;cursor:pointer;-webkit-user-select:none;user-select:none;border-left:3px solid transparent;border-radius:2px}.field-object-header:hover,.field-array-header:hover{background:#f4f4f4}.field-object-header.field-required-unmapped,.field-array-header.field-required-unmapped{border-left-color:#dc3545;background-color:#fff5f5}.field-object-header .expand-icon,.field-array-header .expand-icon{flex:0 0 1rem;font-size:.75rem;color:#525252}.field-object-header .field-name,.field-array-header .field-name{font-weight:500}.field-object-header .field-meta,.field-array-header .field-meta{font-size:.8125rem;color:#6c757d}.field-object-header .completeness-badge,.field-array-header .completeness-badge{margin-left:auto;font-size:.75rem;padding:.125rem .5rem;border-radius:10px;background:#e0e0e0;color:#525252;font-weight:500}.field-object-header .mapped-indicator,.field-array-header .mapped-indicator{margin-left:auto;color:#198754;font-weight:600}.field-object-children{padding-left:1.25rem;border-left:1px solid #e0e0e0;margin-left:.5rem}.field-array{margin:.25rem 0}.field-array-content{padding-left:1.25rem;border-left:1px solid #e0e0e0;margin-left:.5rem}.array-per-field-toggle{padding:.5rem 0}.array-per-field-toggle .toggle-label{display:flex;align-items:center;gap:.5rem;cursor:pointer;font-size:.875rem;color:#525252}.array-per-field-toggle .toggle-label input[type=checkbox]{cursor:pointer}.array-item-fields{margin-left:.5rem;border-left:1px dashed #c6c6c6;padding:.25rem 0 .5rem 1rem}.item-fields-header{padding-bottom:.25rem}.item-fields-header .item-fields-title{font-size:.8125rem;font-weight:500;color:#6c757d}.item-field-row{display:flex;align-items:center;gap:.75rem;padding:.25rem 0}.item-field-label{flex:0 0 180px;min-width:120px;word-break:break-word}.item-field-label .field-name{font-weight:500;font-size:.875rem}.item-field-label .field-meta{font-size:.75rem;color:#6c757d;margin-left:.25rem}.item-field-input{flex:1;min-width:0}\n"], dependencies: [{ kind: "component", type: FieldTreeComponent, selector: "epistola-field-tree", inputs: ["field", "value", "pluginId", "caseDefinitionKey", "processVariables", "disabled"], outputs: ["valueChange"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i4.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i4.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "ngmodule", type: PluginTranslatePipeModule }, { kind: "pipe", type: i2$1.PluginTranslatePipe, name: "pluginTranslate" }, { kind: "ngmodule", type: InputModule }, { kind: "component", type: i3.InputComponent, selector: "v-input", inputs: ["name", "type", "title", "titleTranslationKey", "defaultValue", "widthPx", "fullWidth", "margin", "smallMargin", "disabled", "step", "min", "maxLength", "tooltip", "required", "hideNumberSpinBox", "smallLabel", "rows", "clear$", "carbonTheme", "placeholder", "dataTestId", "trim", "presetsTitle", "presetOptions"], outputs: ["valueChange"] }, { kind: "component", type: ValuePathSelectorComponent, selector: "valtimo-value-path-selector", inputs: ["name", "appendInline", "margin", "marginLg", "marginXl", "disabled", "caseDefinitionKey", "caseDefinitionVersionTag", "prefixes", "label", "tooltip", "required", "showCaseDefinitionSelector", "notation", "defaultValue", "type", "parentItem"], outputs: ["valueChangeEvent", "collectionSelected"] }] });
464
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: FieldTreeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
465
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: FieldTreeComponent, isStandalone: true, selector: "epistola-field-tree", inputs: { field: "field", value: "value", pluginId: "pluginId", caseDefinitionKey: "caseDefinitionKey", processVariables: "processVariables", disabled: "disabled" }, outputs: { valueChange: "valueChange" }, usesOnChanges: true, ngImport: i0, template: "<!-- SCALAR field: label row + input with 3-mode selector -->\n<div *ngIf=\"field.fieldType === 'SCALAR'\" class=\"field-row\" [class.field-required-unmapped]=\"field.required && !getStringValue()\">\n <div class=\"field-label\">\n <span class=\"field-name\">{{ field.name }}</span>\n <span class=\"field-meta\">({{ field.type }}{{ field.required ? ', required' : '' }})</span>\n </div>\n <div class=\"field-input\">\n <div class=\"input-mode-group\">\n <button type=\"button\" class=\"mode-btn\" [class.mode-active]=\"inputMode === 'browse'\" [disabled]=\"disabled\" (click)=\"setInputMode('browse')\" [title]=\"'browseMode' | pluginTranslate: pluginId | async\">\u229E</button>\n <button type=\"button\" class=\"mode-btn\" [class.mode-active]=\"inputMode === 'pv'\" [disabled]=\"disabled\" (click)=\"setInputMode('pv')\" [title]=\"'pvMode' | pluginTranslate: pluginId | async\">pv</button>\n <button type=\"button\" class=\"mode-btn\" [class.mode-active]=\"inputMode === 'expression'\" [disabled]=\"disabled\" (click)=\"setInputMode('expression')\" [title]=\"'expressionMode' | pluginTranslate: pluginId | async\">fx</button>\n </div>\n\n <!-- Browse mode: ValuePathSelector -->\n <div class=\"field-input-control\" *ngIf=\"inputMode === 'browse'\">\n <valtimo-value-path-selector\n [name]=\"'field_' + field.path\"\n [caseDefinitionKey]=\"caseDefinitionKey\"\n [prefixes]=\"[ValuePathSelectorPrefix.DOC, ValuePathSelectorPrefix.CASE]\"\n [notation]=\"'dots'\"\n [disabled]=\"disabled\"\n [defaultValue]=\"getStringValue()\"\n [showCaseDefinitionSelector]=\"!caseDefinitionKey\"\n (valueChangeEvent)=\"onBrowseValueChange($event)\"\n ></valtimo-value-path-selector>\n </div>\n\n <!-- PV mode: dropdown (when available) or text fallback -->\n <div class=\"field-input-control\" *ngIf=\"inputMode === 'pv'\">\n <select\n *ngIf=\"processVariables.length > 0; else pvFallback\"\n class=\"pv-select\"\n [disabled]=\"disabled\"\n (change)=\"onPvChange($any($event.target).value)\"\n >\n <option value=\"\">{{ 'pvPlaceholder' | pluginTranslate: pluginId | async }}</option>\n <option *ngFor=\"let pv of processVariables\" [value]=\"pv\" [selected]=\"pv === getPvName()\">{{ pv }}</option>\n </select>\n <ng-template #pvFallback>\n <v-input\n [name]=\"'pvfb_' + field.path\"\n [defaultValue]=\"getPvName()\"\n [disabled]=\"disabled\"\n [placeholder]=\"'pvPlaceholder' | pluginTranslate: pluginId | async\"\n (valueChange)=\"onPvChange($event)\"\n ></v-input>\n </ng-template>\n </div>\n\n <!-- Expression mode: text input -->\n <div class=\"field-input-control\" *ngIf=\"inputMode === 'expression'\">\n <v-input\n [name]=\"'fx_' + field.path\"\n [defaultValue]=\"getStringValue()\"\n [disabled]=\"disabled\"\n [placeholder]=\"'e.g. pv:variableName or doc:path.to.field'\"\n (valueChange)=\"onExpressionValueChange($event)\"\n ></v-input>\n </div>\n </div>\n</div>\n\n<!-- OBJECT field: collapsible section with children -->\n<div *ngIf=\"field.fieldType === 'OBJECT'\" class=\"field-object\">\n <div class=\"field-object-header\" (click)=\"toggleExpanded()\" [class.field-required-unmapped]=\"totalRequired > 0 && mappedCount < totalRequired\">\n <span class=\"expand-icon\">{{ expanded ? '\u25BC' : '\u25B6' }}</span>\n <span class=\"field-name\">{{ field.name }}</span>\n <span class=\"field-meta\">(object{{ field.required ? ', required' : '' }})</span>\n <span class=\"completeness-badge\" *ngIf=\"totalRequired > 0 && !expanded\">\n {{ mappedCount }}/{{ totalRequired }}\n </span>\n </div>\n <div class=\"field-object-children\" *ngIf=\"expanded\">\n <epistola-field-tree\n *ngFor=\"let child of field.children\"\n [field]=\"child\"\n [value]=\"getChildValue(child.name)\"\n [pluginId]=\"pluginId\"\n [caseDefinitionKey]=\"caseDefinitionKey\"\n [processVariables]=\"processVariables\"\n [disabled]=\"disabled\"\n (valueChange)=\"onChildChange(child.name, $event)\"\n ></epistola-field-tree>\n </div>\n</div>\n\n<!-- ARRAY field: collapsible section with source collection + optional per-item mapping -->\n<div *ngIf=\"field.fieldType === 'ARRAY'\" class=\"field-array\">\n <div class=\"field-array-header\" (click)=\"toggleExpanded()\" [class.field-required-unmapped]=\"field.required && !getSourceValue()\">\n <span class=\"expand-icon\">{{ expanded ? '\u25BC' : '\u25B6' }}</span>\n <span class=\"field-name\">{{ field.name }}</span>\n <span class=\"field-meta\">(array{{ field.required ? ', required' : '' }})</span>\n <span class=\"completeness-badge\" *ngIf=\"totalRequired > 0 && !expanded\">\n {{ mappedCount }}/{{ totalRequired }}\n </span>\n <span class=\"mapped-indicator\" *ngIf=\"totalRequired === 0 && getSourceValue() && !expanded\">\u2713</span>\n </div>\n <div class=\"field-array-content\" *ngIf=\"expanded\">\n <!-- Source collection input -->\n <div class=\"field-row\">\n <div class=\"field-label\">\n <span class=\"field-name\">{{ 'mapCollectionTo' | pluginTranslate: pluginId | async }}</span>\n </div>\n <div class=\"field-input\">\n <div class=\"input-mode-group\">\n <button type=\"button\" class=\"mode-btn\" [class.mode-active]=\"inputMode === 'browse'\" [disabled]=\"disabled\" (click)=\"setInputMode('browse')\" [title]=\"'browseMode' | pluginTranslate: pluginId | async\">\u229E</button>\n <button type=\"button\" class=\"mode-btn\" [class.mode-active]=\"inputMode === 'pv'\" [disabled]=\"disabled\" (click)=\"setInputMode('pv')\" [title]=\"'pvMode' | pluginTranslate: pluginId | async\">pv</button>\n <button type=\"button\" class=\"mode-btn\" [class.mode-active]=\"inputMode === 'expression'\" [disabled]=\"disabled\" (click)=\"setInputMode('expression')\" [title]=\"'expressionMode' | pluginTranslate: pluginId | async\">fx</button>\n </div>\n\n <div class=\"field-input-control\" *ngIf=\"inputMode === 'browse'\">\n <valtimo-value-path-selector\n [name]=\"'field_' + field.path\"\n [caseDefinitionKey]=\"caseDefinitionKey\"\n [prefixes]=\"[ValuePathSelectorPrefix.DOC, ValuePathSelectorPrefix.CASE]\"\n [notation]=\"'dots'\"\n [disabled]=\"disabled\"\n [defaultValue]=\"getSourceValue()\"\n [showCaseDefinitionSelector]=\"!caseDefinitionKey\"\n (valueChangeEvent)=\"onSourceBrowseChange($event)\"\n ></valtimo-value-path-selector>\n </div>\n\n <div class=\"field-input-control\" *ngIf=\"inputMode === 'pv'\">\n <select\n *ngIf=\"processVariables.length > 0; else pvSourceFallback\"\n class=\"pv-select\"\n [disabled]=\"disabled\"\n (change)=\"onSourcePvChange($any($event.target).value)\"\n >\n <option value=\"\">{{ 'pvPlaceholder' | pluginTranslate: pluginId | async }}</option>\n <option *ngFor=\"let pv of processVariables\" [value]=\"pv\" [selected]=\"pv === getSourcePvName()\">{{ pv }}</option>\n </select>\n <ng-template #pvSourceFallback>\n <v-input\n [name]=\"'pvfb_' + field.path\"\n [defaultValue]=\"getSourcePvName()\"\n [disabled]=\"disabled\"\n [placeholder]=\"'pvPlaceholder' | pluginTranslate: pluginId | async\"\n (valueChange)=\"onSourcePvChange($event)\"\n ></v-input>\n </ng-template>\n </div>\n\n <div class=\"field-input-control\" *ngIf=\"inputMode === 'expression'\">\n <v-input\n [name]=\"'fx_' + field.path\"\n [defaultValue]=\"getSourceValue()\"\n [disabled]=\"disabled\"\n [placeholder]=\"'e.g. pv:variableName or doc:path.to.field'\"\n (valueChange)=\"onSourceExpressionChange($event)\"\n ></v-input>\n </div>\n </div>\n </div>\n\n <!-- Per-item field mapping toggle (only shown when array has children) -->\n <div class=\"array-per-field-toggle\" *ngIf=\"hasArrayChildren()\">\n <label class=\"toggle-label\">\n <input\n type=\"checkbox\"\n [checked]=\"arrayPerFieldMode\"\n [disabled]=\"disabled\"\n (change)=\"toggleArrayPerFieldMode()\"\n />\n <span>{{ 'itemFieldMapping' | pluginTranslate: pluginId | async }}</span>\n </label>\n </div>\n\n <!-- Per-item field mappings -->\n <div class=\"array-item-fields\" *ngIf=\"arrayPerFieldMode && hasArrayChildren()\">\n <div class=\"item-fields-header\">\n <span class=\"item-fields-title\">{{ 'itemFieldMappingTitle' | pluginTranslate: pluginId | async }}</span>\n </div>\n <div class=\"item-field-row\" *ngFor=\"let child of field.children\">\n <div class=\"item-field-label\">\n <span class=\"field-name\">{{ child.name }}</span>\n <span class=\"field-meta\">({{ child.type }}{{ child.required ? ', required' : '' }})</span>\n </div>\n <div class=\"item-field-input\">\n <v-input\n [name]=\"'itemField_' + child.path\"\n [defaultValue]=\"getItemFieldValue(child.name)\"\n [disabled]=\"disabled\"\n [placeholder]=\"'sourceFieldPlaceholder' | pluginTranslate: pluginId | async\"\n (valueChange)=\"onItemFieldChange(child.name, $event)\"\n ></v-input>\n </div>\n </div>\n </div>\n </div>\n</div>\n", styles: [".field-row{display:flex;align-items:flex-start;gap:.75rem;padding:.5rem 0;border-left:3px solid transparent}.field-row.field-required-unmapped{border-left-color:#dc3545;background-color:#fff5f5;padding-left:.5rem}.field-label{flex:0 0 200px;min-width:140px;padding-top:.5rem;word-break:break-word}.field-label .field-name{font-weight:500}.field-label .field-meta{font-size:.8125rem;color:#6c757d;margin-left:.25rem}.field-input{flex:1;display:flex;align-items:flex-start;gap:.5rem;min-width:0}.field-input-control{flex:1;min-width:0}.input-mode-group{flex:0 0 auto;display:flex;margin-top:.25rem;border:1px solid #c6c6c6;border-radius:4px;overflow:hidden}.input-mode-group .mode-btn{width:32px;height:32px;padding:0;border:none;border-right:1px solid #c6c6c6;background:#f4f4f4;color:#525252;font-size:.75rem;font-weight:600;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:background-color .15s}.input-mode-group .mode-btn:last-child{border-right:none}.input-mode-group .mode-btn:hover:not(:disabled){background:#e0e0e0}.input-mode-group .mode-btn.mode-active{background:#0f62fe;color:#fff}.input-mode-group .mode-btn.mode-active:hover:not(:disabled){background:#0353e9}.input-mode-group .mode-btn:disabled{opacity:.5;cursor:not-allowed}.pv-select{width:100%;height:2.5rem;padding:0 .75rem;border:1px solid #c6c6c6;border-radius:4px;background:#fff;color:#161616;font-size:.875rem;cursor:pointer}.pv-select:focus{outline:2px solid #0f62fe;outline-offset:-2px}.pv-select:disabled{opacity:.5;cursor:not-allowed;background:#f4f4f4}.field-object{margin:.25rem 0}.field-object-header,.field-array-header{display:flex;align-items:center;gap:.5rem;padding:.5rem .25rem;cursor:pointer;-webkit-user-select:none;user-select:none;border-left:3px solid transparent;border-radius:2px}.field-object-header:hover,.field-array-header:hover{background:#f4f4f4}.field-object-header.field-required-unmapped,.field-array-header.field-required-unmapped{border-left-color:#dc3545;background-color:#fff5f5}.field-object-header .expand-icon,.field-array-header .expand-icon{flex:0 0 1rem;font-size:.75rem;color:#525252}.field-object-header .field-name,.field-array-header .field-name{font-weight:500}.field-object-header .field-meta,.field-array-header .field-meta{font-size:.8125rem;color:#6c757d}.field-object-header .completeness-badge,.field-array-header .completeness-badge{margin-left:auto;font-size:.75rem;padding:.125rem .5rem;border-radius:10px;background:#e0e0e0;color:#525252;font-weight:500}.field-object-header .mapped-indicator,.field-array-header .mapped-indicator{margin-left:auto;color:#198754;font-weight:600}.field-object-children{padding-left:1.25rem;border-left:1px solid #e0e0e0;margin-left:.5rem}.field-array{margin:.25rem 0}.field-array-content{padding-left:1.25rem;border-left:1px solid #e0e0e0;margin-left:.5rem}.array-per-field-toggle{padding:.5rem 0}.array-per-field-toggle .toggle-label{display:flex;align-items:center;gap:.5rem;cursor:pointer;font-size:.875rem;color:#525252}.array-per-field-toggle .toggle-label input[type=checkbox]{cursor:pointer}.array-item-fields{margin-left:.5rem;border-left:1px dashed #c6c6c6;padding:.25rem 0 .5rem 1rem}.item-fields-header{padding-bottom:.25rem}.item-fields-header .item-fields-title{font-size:.8125rem;font-weight:500;color:#6c757d}.item-field-row{display:flex;align-items:center;gap:.75rem;padding:.25rem 0}.item-field-label{flex:0 0 180px;min-width:120px;word-break:break-word}.item-field-label .field-name{font-weight:500;font-size:.875rem}.item-field-label .field-meta{font-size:.75rem;color:#6c757d;margin-left:.25rem}.item-field-input{flex:1;min-width:0}\n"], dependencies: [{ kind: "component", type: FieldTreeComponent, selector: "epistola-field-tree", inputs: ["field", "value", "pluginId", "caseDefinitionKey", "processVariables", "disabled"], outputs: ["valueChange"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i4.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i4.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "ngmodule", type: PluginTranslatePipeModule }, { kind: "pipe", type: i2$1.PluginTranslatePipe, name: "pluginTranslate" }, { kind: "ngmodule", type: InputModule }, { kind: "component", type: i3.InputComponent, selector: "v-input", inputs: ["name", "type", "title", "titleTranslationKey", "defaultValue", "widthPx", "fullWidth", "margin", "smallMargin", "disabled", "step", "min", "maxLength", "tooltip", "required", "hideNumberSpinBox", "smallLabel", "rows", "clear$", "carbonTheme", "placeholder", "dataTestId", "trim", "presetsTitle", "presetOptions"], outputs: ["valueChange"] }, { kind: "component", type: ValuePathSelectorComponent, selector: "valtimo-value-path-selector", inputs: ["name", "appendInline", "margin", "marginLg", "marginXl", "disabled", "caseDefinitionKey", "caseDefinitionVersionTag", "buildingBlockDefinitionKey", "buildingBlockDefinitionVersionTag", "prefixes", "label", "tooltip", "required", "showCaseDefinitionSelector", "notation", "dropUp", "defaultValue", "type", "parentItem", "filterItems"], outputs: ["valueChangeEvent", "collectionSelected"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
430
466
  }
431
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: FieldTreeComponent, decorators: [{
467
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: FieldTreeComponent, decorators: [{
432
468
  type: Component,
433
- args: [{ selector: 'epistola-field-tree', standalone: true, imports: [
469
+ args: [{ selector: 'epistola-field-tree', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [
434
470
  CommonModule,
435
471
  FormsModule,
436
472
  PluginTranslatePipeModule,
@@ -504,49 +540,15 @@ class DataMappingTreeComponent {
504
540
  return this.mapping[fieldName];
505
541
  }
506
542
  emitRequiredFieldsStatus() {
507
- const stats = this.countRequiredMapped(this.templateFields, this.mapping);
543
+ const stats = countRequiredMapped(this.templateFields, this.mapping);
508
544
  this.requiredFieldsStatus.emit(stats);
509
545
  }
510
- countRequiredMapped(fields, mapping) {
511
- let mapped = 0;
512
- let total = 0;
513
- for (const field of fields) {
514
- if (field.fieldType === 'SCALAR' && field.required) {
515
- total++;
516
- const val = mapping[field.name];
517
- if (typeof val === 'string' && val.trim().length > 0) {
518
- mapped++;
519
- }
520
- }
521
- else if (field.fieldType === 'ARRAY' && field.required) {
522
- total++;
523
- const val = mapping[field.name];
524
- if (typeof val === 'string' && val.trim().length > 0) {
525
- mapped++;
526
- }
527
- else if (typeof val === 'object' && val !== null && '_source' in val) {
528
- if (typeof val['_source'] === 'string' && val['_source'].trim().length > 0) {
529
- mapped++;
530
- }
531
- }
532
- }
533
- else if (field.fieldType === 'OBJECT' && field.children) {
534
- const nested = (typeof mapping[field.name] === 'object' && mapping[field.name] !== null)
535
- ? mapping[field.name]
536
- : {};
537
- const childStats = this.countRequiredMapped(field.children, nested);
538
- mapped += childStats.mapped;
539
- total += childStats.total;
540
- }
541
- }
542
- return { mapped, total };
543
- }
544
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: DataMappingTreeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
545
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.17", type: DataMappingTreeComponent, isStandalone: true, selector: "epistola-data-mapping-tree", inputs: { pluginId: "pluginId", templateFields$: "templateFields$", prefillMapping$: "prefillMapping$", disabled$: "disabled$", caseDefinitionKey: "caseDefinitionKey", processVariables: "processVariables" }, outputs: { mappingChange: "mappingChange", requiredFieldsStatus: "requiredFieldsStatus" }, ngImport: i0, template: "<div class=\"data-mapping-tree\">\n <div class=\"mapping-header\">\n <h5>{{ 'dataMappingTitle' | pluginTranslate: pluginId | async }}</h5>\n <p class=\"helper-text\">{{ 'dataMappingDescription' | pluginTranslate: pluginId | async }}</p>\n </div>\n\n <div class=\"field-tree-root\" *ngIf=\"templateFields.length > 0\">\n <epistola-field-tree\n *ngFor=\"let field of templateFields\"\n [field]=\"field\"\n [value]=\"getFieldValue(field.name)\"\n [pluginId]=\"pluginId\"\n [caseDefinitionKey]=\"caseDefinitionKey\"\n [processVariables]=\"processVariables\"\n [disabled]=\"disabled\"\n (valueChange)=\"onFieldValueChange(field.name, $event)\"\n ></epistola-field-tree>\n </div>\n\n <div class=\"no-fields\" *ngIf=\"templateFields.length === 0\">\n <p>{{ 'noTemplateFields' | pluginTranslate: pluginId | async }}</p>\n </div>\n</div>\n", styles: [".data-mapping-tree{margin-top:1rem;margin-bottom:1rem}.mapping-header{margin-bottom:.75rem}.mapping-header h5{margin-bottom:.25rem;font-weight:600}.mapping-header .helper-text{color:#6c757d;font-size:.875rem;margin-bottom:0}.field-tree-root{border:1px solid #e0e0e0;border-radius:4px;padding:.5rem .75rem}.no-fields{padding:1rem;text-align:center;color:#6c757d;background-color:#f8f9fa;border:1px solid #dee2e6;border-radius:4px}.no-fields p{margin-bottom:0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "ngmodule", type: PluginTranslatePipeModule }, { kind: "pipe", type: i2$1.PluginTranslatePipe, name: "pluginTranslate" }, { kind: "component", type: FieldTreeComponent, selector: "epistola-field-tree", inputs: ["field", "value", "pluginId", "caseDefinitionKey", "processVariables", "disabled"], outputs: ["valueChange"] }] });
546
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: DataMappingTreeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
547
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: DataMappingTreeComponent, isStandalone: true, selector: "epistola-data-mapping-tree", inputs: { pluginId: "pluginId", templateFields$: "templateFields$", prefillMapping$: "prefillMapping$", disabled$: "disabled$", caseDefinitionKey: "caseDefinitionKey", processVariables: "processVariables" }, outputs: { mappingChange: "mappingChange", requiredFieldsStatus: "requiredFieldsStatus" }, ngImport: i0, template: "<div class=\"data-mapping-tree\">\n <div class=\"mapping-header\">\n <h5>{{ 'dataMappingTitle' | pluginTranslate: pluginId | async }}</h5>\n <p class=\"helper-text\">{{ 'dataMappingDescription' | pluginTranslate: pluginId | async }}</p>\n </div>\n\n <div class=\"field-tree-root\" *ngIf=\"templateFields.length > 0\">\n <epistola-field-tree\n *ngFor=\"let field of templateFields\"\n [field]=\"field\"\n [value]=\"getFieldValue(field.name)\"\n [pluginId]=\"pluginId\"\n [caseDefinitionKey]=\"caseDefinitionKey\"\n [processVariables]=\"processVariables\"\n [disabled]=\"disabled\"\n (valueChange)=\"onFieldValueChange(field.name, $event)\"\n ></epistola-field-tree>\n </div>\n\n <div class=\"no-fields\" *ngIf=\"templateFields.length === 0\">\n <p>{{ 'noTemplateFields' | pluginTranslate: pluginId | async }}</p>\n </div>\n</div>\n", styles: [".data-mapping-tree{margin-top:1rem;margin-bottom:1rem}.mapping-header{margin-bottom:.75rem}.mapping-header h5{margin-bottom:.25rem;font-weight:600}.mapping-header .helper-text{color:#6c757d;font-size:.875rem;margin-bottom:0}.field-tree-root{border:1px solid #e0e0e0;border-radius:4px;padding:.5rem .75rem}.no-fields{padding:1rem;text-align:center;color:#6c757d;background-color:#f8f9fa;border:1px solid #dee2e6;border-radius:4px}.no-fields p{margin-bottom:0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "ngmodule", type: PluginTranslatePipeModule }, { kind: "pipe", type: i2$1.PluginTranslatePipe, name: "pluginTranslate" }, { kind: "component", type: FieldTreeComponent, selector: "epistola-field-tree", inputs: ["field", "value", "pluginId", "caseDefinitionKey", "processVariables", "disabled"], outputs: ["valueChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
546
548
  }
547
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: DataMappingTreeComponent, decorators: [{
549
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: DataMappingTreeComponent, decorators: [{
548
550
  type: Component,
549
- args: [{ selector: 'epistola-data-mapping-tree', standalone: true, imports: [
551
+ args: [{ selector: 'epistola-data-mapping-tree', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [
550
552
  CommonModule,
551
553
  PluginTranslatePipeModule,
552
554
  FieldTreeComponent
@@ -572,6 +574,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
572
574
  class GenerateDocumentConfigurationComponent {
573
575
  epistolaPluginService;
574
576
  processLinkStateService;
577
+ cdr;
575
578
  // Required inputs from FunctionConfigurationComponent
576
579
  save$;
577
580
  disabled$;
@@ -585,15 +588,19 @@ class GenerateDocumentConfigurationComponent {
585
588
  // Template options loaded from API
586
589
  templateOptions$ = new BehaviorSubject([]);
587
590
  templatesLoading$ = new BehaviorSubject(false);
591
+ templatesError$ = new BehaviorSubject(null);
588
592
  // Variant options loaded based on selected template
589
593
  variantOptions$ = new BehaviorSubject([]);
590
594
  variantsLoading$ = new BehaviorSubject(false);
595
+ variantsError$ = new BehaviorSubject(null);
591
596
  // Environment options loaded from API
592
597
  environmentOptions$ = new BehaviorSubject([]);
593
598
  environmentsLoading$ = new BehaviorSubject(false);
599
+ environmentsError$ = new BehaviorSubject(null);
594
600
  // Template fields for data mapping
595
601
  templateFields$ = new BehaviorSubject([]);
596
602
  templateFieldsLoading$ = new BehaviorSubject(false);
603
+ templateFieldsError$ = new BehaviorSubject(null);
597
604
  // Current data mapping (nested structure mirroring template schema)
598
605
  dataMapping$ = new BehaviorSubject({});
599
606
  // Prefill data mapping observable for the tree
@@ -620,9 +627,10 @@ class GenerateDocumentConfigurationComponent {
620
627
  formValue$ = new BehaviorSubject(null);
621
628
  valid$ = new BehaviorSubject(false);
622
629
  pluginConfigurationId$ = new BehaviorSubject('');
623
- constructor(epistolaPluginService, processLinkStateService) {
630
+ constructor(epistolaPluginService, processLinkStateService, cdr) {
624
631
  this.epistolaPluginService = epistolaPluginService;
625
632
  this.processLinkStateService = processLinkStateService;
633
+ this.cdr = cdr;
626
634
  }
627
635
  ngOnInit() {
628
636
  this.initContext();
@@ -707,6 +715,7 @@ class GenerateDocumentConfigurationComponent {
707
715
  if (this.context$) {
708
716
  this.context$.pipe(takeUntil(this.destroy$), filter(([context]) => context === 'case')).subscribe(([, params]) => {
709
717
  this.caseDefinitionKey = params.caseDefinitionKey;
718
+ this.cdr.markForCheck();
710
719
  });
711
720
  }
712
721
  }
@@ -728,6 +737,7 @@ class GenerateDocumentConfigurationComponent {
728
737
  if (config.dataMapping) {
729
738
  this.dataMapping$.next(config.dataMapping);
730
739
  }
740
+ this.cdr.markForCheck();
731
741
  });
732
742
  }
733
743
  else {
@@ -750,7 +760,11 @@ class GenerateDocumentConfigurationComponent {
750
760
  initTemplatesLoading() {
751
761
  this.pluginConfigurationId$.pipe(takeUntil(this.destroy$), filter(id => !!id)).subscribe(configurationId => {
752
762
  this.templatesLoading$.next(true);
753
- this.epistolaPluginService.getTemplates(configurationId).pipe(takeUntil(this.destroy$), catchError(() => of([]))).subscribe(templates => {
763
+ this.templatesError$.next(null);
764
+ this.epistolaPluginService.getTemplates(configurationId).pipe(takeUntil(this.destroy$), catchError(() => {
765
+ this.templatesError$.next('Failed to load templates');
766
+ return of([]);
767
+ })).subscribe(templates => {
754
768
  const options = templates.map(t => ({
755
769
  id: t.id,
756
770
  text: t.name
@@ -763,7 +777,11 @@ class GenerateDocumentConfigurationComponent {
763
777
  initEnvironmentsLoading() {
764
778
  this.pluginConfigurationId$.pipe(takeUntil(this.destroy$), filter(id => !!id)).subscribe(configurationId => {
765
779
  this.environmentsLoading$.next(true);
766
- this.epistolaPluginService.getEnvironments(configurationId).pipe(takeUntil(this.destroy$), catchError(() => of([]))).subscribe(environments => {
780
+ this.environmentsError$.next(null);
781
+ this.epistolaPluginService.getEnvironments(configurationId).pipe(takeUntil(this.destroy$), catchError(() => {
782
+ this.environmentsError$.next('Failed to load environments');
783
+ return of([]);
784
+ })).subscribe(environments => {
767
785
  const options = environments.map(e => ({
768
786
  id: e.id,
769
787
  text: e.name
@@ -779,7 +797,11 @@ class GenerateDocumentConfigurationComponent {
779
797
  this.selectedTemplateId$
780
798
  ]).pipe(takeUntil(this.destroy$), filter(([configId, templateId]) => !!configId && !!templateId)).subscribe(([configurationId, templateId]) => {
781
799
  this.variantsLoading$.next(true);
782
- this.epistolaPluginService.getVariants(configurationId, templateId).pipe(takeUntil(this.destroy$), catchError(() => of([]))).subscribe(variants => {
800
+ this.variantsError$.next(null);
801
+ this.epistolaPluginService.getVariants(configurationId, templateId).pipe(takeUntil(this.destroy$), catchError(() => {
802
+ this.variantsError$.next('Failed to load variants');
803
+ return of([]);
804
+ })).subscribe(variants => {
783
805
  const options = variants.map(v => ({
784
806
  id: v.id,
785
807
  text: v.name + this.formatAttributes(v.attributes)
@@ -795,7 +817,11 @@ class GenerateDocumentConfigurationComponent {
795
817
  this.selectedTemplateId$
796
818
  ]).pipe(takeUntil(this.destroy$), filter(([configId, templateId]) => !!configId && !!templateId)).subscribe(([configurationId, templateId]) => {
797
819
  this.templateFieldsLoading$.next(true);
798
- this.epistolaPluginService.getTemplateDetails(configurationId, templateId).pipe(takeUntil(this.destroy$), catchError(() => of({ fields: [] }))).subscribe(details => {
820
+ this.templateFieldsError$.next(null);
821
+ this.epistolaPluginService.getTemplateDetails(configurationId, templateId).pipe(takeUntil(this.destroy$), catchError(() => {
822
+ this.templateFieldsError$.next('Failed to load template fields');
823
+ return of({ fields: [] });
824
+ })).subscribe(details => {
799
825
  this.templateFields$.next(details.fields || []);
800
826
  this.templateFieldsLoading$.next(false);
801
827
  });
@@ -808,6 +834,7 @@ class GenerateDocumentConfigurationComponent {
808
834
  if (this.caseDefinitionKey) {
809
835
  this.epistolaPluginService.getProcessVariables(this.caseDefinitionKey).pipe(takeUntil(this.destroy$), catchError(() => of([]))).subscribe(variables => {
810
836
  this.processVariables = variables;
837
+ this.cdr.markForCheck();
811
838
  });
812
839
  }
813
840
  }
@@ -860,12 +887,12 @@ class GenerateDocumentConfigurationComponent {
860
887
  });
861
888
  });
862
889
  }
863
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: GenerateDocumentConfigurationComponent, deps: [{ token: EpistolaPluginService }, { token: i2$2.ProcessLinkStateService }], target: i0.ɵɵFactoryTarget.Component });
864
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.17", type: GenerateDocumentConfigurationComponent, isStandalone: true, selector: "epistola-generate-document-configuration", inputs: { save$: "save$", disabled$: "disabled$", pluginId: "pluginId", prefillConfiguration$: "prefillConfiguration$", selectedPluginConfigurationData$: "selectedPluginConfigurationData$", context$: "context$" }, outputs: { valid: "valid", configuration: "configuration" }, ngImport: i0, template: "<v-form\n (valueChange)=\"formValueChange($event)\"\n *ngIf=\"{\n disabled: disabled$ | async,\n prefill: prefillConfiguration$ ? (prefillConfiguration$ | async) : null,\n templateOptions: templateOptions$ | async,\n templatesLoading: templatesLoading$ | async,\n variantOptions: variantOptions$ | async,\n variantsLoading: variantsLoading$ | async,\n environmentOptions: environmentOptions$ | async,\n environmentsLoading: environmentsLoading$ | async,\n templateFieldsLoading: templateFieldsLoading$ | async,\n selectedTemplateId: selectedTemplateId$ | async\n } as obs\"\n>\n <v-select\n name=\"templateId\"\n [title]=\"'templateId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'templateIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"obs.templateOptions\"\n [defaultSelectionId]=\"obs.prefill?.templateId\"\n [disabled]=\"obs.disabled || obs.templatesLoading\"\n [required]=\"true\"\n [loading]=\"obs.templatesLoading\"\n >\n </v-select>\n\n <!-- Variant selection mode toggle -->\n <div class=\"variant-mode-toggle\" *ngIf=\"obs.selectedTemplateId\">\n <label class=\"variant-mode-label\">{{ 'variantSelectionMode' | pluginTranslate: pluginId | async }}</label>\n <div class=\"variant-mode-buttons\">\n <button\n type=\"button\"\n class=\"variant-mode-btn\"\n [class.active]=\"variantSelectionMode === 'explicit'\"\n (click)=\"onVariantSelectionModeChange('explicit')\"\n [disabled]=\"obs.disabled\"\n >{{ 'selectByVariant' | pluginTranslate: pluginId | async }}</button>\n <button\n type=\"button\"\n class=\"variant-mode-btn\"\n [class.active]=\"variantSelectionMode === 'attributes'\"\n (click)=\"onVariantSelectionModeChange('attributes')\"\n [disabled]=\"obs.disabled\"\n >{{ 'selectByAttributes' | pluginTranslate: pluginId | async }}</button>\n </div>\n </div>\n\n <!-- Explicit variant selection (dropdown) -->\n <v-select\n *ngIf=\"variantSelectionMode === 'explicit'\"\n name=\"variantId\"\n [title]=\"'variantId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'variantIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"obs.variantOptions\"\n [defaultSelectionId]=\"obs.prefill?.variantId\"\n [disabled]=\"obs.disabled || obs.variantsLoading || !obs.selectedTemplateId\"\n [required]=\"false\"\n [loading]=\"obs.variantsLoading\"\n >\n </v-select>\n\n <!-- Attribute-based variant selection -->\n <div *ngIf=\"variantSelectionMode === 'attributes' && obs.selectedTemplateId\" class=\"variant-attributes-section\">\n <label class=\"variant-attributes-label\">{{ 'variantAttributes' | pluginTranslate: pluginId | async }}</label>\n <div class=\"variant-attributes-list\">\n <div *ngFor=\"let entry of variantAttributeEntries; let i = index\" class=\"variant-attribute-row\">\n <input\n type=\"text\"\n class=\"variant-attribute-input\"\n [placeholder]=\"'attributeKey' | pluginTranslate: pluginId | async\"\n [(ngModel)]=\"entry.key\"\n (ngModelChange)=\"onAttributeEntryChange()\"\n [disabled]=\"obs.disabled\"\n />\n <input\n type=\"text\"\n class=\"variant-attribute-input\"\n [placeholder]=\"'attributeValue' | pluginTranslate: pluginId | async\"\n [(ngModel)]=\"entry.value\"\n (ngModelChange)=\"onAttributeEntryChange()\"\n [disabled]=\"obs.disabled\"\n />\n <button\n type=\"button\"\n class=\"variant-attribute-remove-btn\"\n (click)=\"removeAttributeEntry(i)\"\n [disabled]=\"obs.disabled\"\n title=\"{{ 'removeAttribute' | pluginTranslate: pluginId | async }}\"\n >&times;</button>\n </div>\n </div>\n <button\n type=\"button\"\n class=\"variant-attribute-add-btn\"\n (click)=\"addAttributeEntry()\"\n [disabled]=\"obs.disabled\"\n >+ {{ 'addAttribute' | pluginTranslate: pluginId | async }}</button>\n </div>\n\n <v-select\n name=\"environmentId\"\n [title]=\"'environmentId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'environmentIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"obs.environmentOptions\"\n [defaultSelectionId]=\"obs.prefill?.environmentId\"\n [disabled]=\"obs.disabled || obs.environmentsLoading\"\n [required]=\"false\"\n [loading]=\"obs.environmentsLoading\"\n >\n </v-select>\n\n <v-select\n name=\"outputFormat\"\n [title]=\"'outputFormat' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'outputFormatTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"outputFormatOptions\"\n [defaultSelectionId]=\"obs.prefill?.outputFormat || 'PDF'\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-select>\n\n <v-input\n name=\"filename\"\n [title]=\"'filename' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'filenameTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.filename\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-input>\n\n <v-input\n name=\"correlationId\"\n [title]=\"'correlationId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'correlationIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.correlationId\"\n [disabled]=\"obs.disabled\"\n [required]=\"false\"\n >\n </v-input>\n\n <v-input\n name=\"resultProcessVariable\"\n [title]=\"'resultProcessVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'resultProcessVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.resultProcessVariable\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-input>\n</v-form>\n\n<epistola-data-mapping-tree\n *ngIf=\"(selectedTemplateId$ | async)\"\n [pluginId]=\"pluginId\"\n [templateFields$]=\"templateFields$\"\n [disabled$]=\"disabled$\"\n [prefillMapping$]=\"prefillDataMapping$\"\n [caseDefinitionKey]=\"caseDefinitionKey\"\n [processVariables]=\"processVariables\"\n (mappingChange)=\"onDataMappingChange($event)\"\n (requiredFieldsStatus)=\"onRequiredFieldsStatusChange($event)\"\n></epistola-data-mapping-tree>\n\n<div class=\"validation-summary\" *ngIf=\"(selectedTemplateId$ | async) && requiredFieldsStatus.total > 0\">\n <span *ngIf=\"requiredFieldsStatus.mapped === requiredFieldsStatus.total\" class=\"validation-complete\">\n {{ 'requiredFieldsComplete' | pluginTranslate: pluginId | async }}\n </span>\n <span *ngIf=\"requiredFieldsStatus.mapped < requiredFieldsStatus.total\" class=\"validation-incomplete\">\n {{ requiredFieldsStatus.mapped }} / {{ requiredFieldsStatus.total }}\n {{ 'validationSummary' | pluginTranslate: pluginId | async }}\n </span>\n</div>\n", styles: [".validation-summary{margin-top:.5rem;padding:.5rem .75rem;border-radius:4px;font-size:.875rem}.validation-summary .validation-complete{color:#198754}.validation-summary .validation-incomplete{color:#dc3545;font-weight:500}.variant-mode-toggle{margin-bottom:1rem;padding:0 .75rem}.variant-mode-label{display:block;font-size:.875rem;font-weight:500;margin-bottom:.375rem}.variant-mode-buttons{display:flex;gap:0;border:1px solid #d1d5db;border-radius:4px;overflow:hidden;width:fit-content}.variant-mode-btn{padding:.375rem .75rem;font-size:.8125rem;background:#fff;border:none;border-right:1px solid #d1d5db;cursor:pointer;color:#374151;transition:background-color .15s,color .15s}.variant-mode-btn:last-child{border-right:none}.variant-mode-btn:hover:not([disabled]){background:#f3f4f6}.variant-mode-btn.active{background:#2563eb;color:#fff}.variant-mode-btn[disabled]{opacity:.5;cursor:not-allowed}.variant-attributes-section{margin-bottom:1rem;padding:0 .75rem}.variant-attributes-label{display:block;font-size:.875rem;font-weight:500;margin-bottom:.375rem}.variant-attributes-list{display:flex;flex-direction:column;gap:.375rem}.variant-attribute-row{display:flex;gap:.375rem;align-items:center}.variant-attribute-input{flex:1;padding:.375rem .5rem;font-size:.8125rem;border:1px solid #d1d5db;border-radius:4px;outline:none}.variant-attribute-input:focus{border-color:#2563eb;box-shadow:0 0 0 1px #2563eb}.variant-attribute-input[disabled]{opacity:.5;background:#f9fafb}.variant-attribute-remove-btn{padding:.25rem .5rem;font-size:1rem;line-height:1;background:none;border:1px solid #d1d5db;border-radius:4px;cursor:pointer;color:#6b7280}.variant-attribute-remove-btn:hover:not([disabled]){color:#dc3545;border-color:#dc3545}.variant-attribute-remove-btn[disabled]{opacity:.5;cursor:not-allowed}.variant-attribute-add-btn{margin-top:.375rem;padding:.25rem .5rem;font-size:.8125rem;background:none;border:1px dashed #d1d5db;border-radius:4px;cursor:pointer;color:#6b7280}.variant-attribute-add-btn:hover:not([disabled]){color:#2563eb;border-color:#2563eb}.variant-attribute-add-btn[disabled]{opacity:.5;cursor:not-allowed}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i4.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i4.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i4.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: PluginTranslatePipeModule }, { kind: "pipe", type: i2$1.PluginTranslatePipe, name: "pluginTranslate" }, { kind: "ngmodule", type: FormModule }, { kind: "component", type: i3.FormComponent, selector: "v-form", inputs: ["className"], outputs: ["valueChange"] }, { kind: "ngmodule", type: InputModule }, { kind: "component", type: i3.InputComponent, selector: "v-input", inputs: ["name", "type", "title", "titleTranslationKey", "defaultValue", "widthPx", "fullWidth", "margin", "smallMargin", "disabled", "step", "min", "maxLength", "tooltip", "required", "hideNumberSpinBox", "smallLabel", "rows", "clear$", "carbonTheme", "placeholder", "dataTestId", "trim", "presetsTitle", "presetOptions"], outputs: ["valueChange"] }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: i3.SelectComponent, selector: "v-select", inputs: ["items", "defaultSelection", "defaultSelectionId", "defaultSelectionIds", "disabled", "dropUp", "invalid", "multiple", "margin", "widthInPx", "notFoundText", "clearAllText", "clearText", "clearable", "name", "title", "titleTranslationKey", "clearSelectionSubject$", "tooltip", "required", "loading", "loadingText", "placeholder", "smallMargin", "carbonTheme", "appendInline", "dataTestId"], outputs: ["selectedChange"] }, { kind: "component", type: DataMappingTreeComponent, selector: "epistola-data-mapping-tree", inputs: ["pluginId", "templateFields$", "prefillMapping$", "disabled$", "caseDefinitionKey", "processVariables"], outputs: ["mappingChange", "requiredFieldsStatus"] }] });
890
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: GenerateDocumentConfigurationComponent, deps: [{ token: EpistolaPluginService }, { token: i2$2.ProcessLinkStateService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
891
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: GenerateDocumentConfigurationComponent, isStandalone: true, selector: "epistola-generate-document-configuration", inputs: { save$: "save$", disabled$: "disabled$", pluginId: "pluginId", prefillConfiguration$: "prefillConfiguration$", selectedPluginConfigurationData$: "selectedPluginConfigurationData$", context$: "context$" }, outputs: { valid: "valid", configuration: "configuration" }, ngImport: i0, template: "<v-form\n (valueChange)=\"formValueChange($event)\"\n *ngIf=\"{\n disabled: disabled$ | async,\n prefill: prefillConfiguration$ ? (prefillConfiguration$ | async) : null,\n templateOptions: templateOptions$ | async,\n templatesLoading: templatesLoading$ | async,\n templatesError: templatesError$ | async,\n variantOptions: variantOptions$ | async,\n variantsLoading: variantsLoading$ | async,\n variantsError: variantsError$ | async,\n environmentOptions: environmentOptions$ | async,\n environmentsLoading: environmentsLoading$ | async,\n environmentsError: environmentsError$ | async,\n templateFieldsLoading: templateFieldsLoading$ | async,\n selectedTemplateId: selectedTemplateId$ | async\n } as obs\"\n>\n <v-select\n name=\"templateId\"\n [title]=\"'templateId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'templateIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"obs.templateOptions\"\n [defaultSelectionId]=\"obs.prefill?.templateId\"\n [disabled]=\"obs.disabled || obs.templatesLoading\"\n [required]=\"true\"\n [loading]=\"obs.templatesLoading\"\n >\n </v-select>\n <div *ngIf=\"obs.templatesError\" class=\"loading-error\">{{ obs.templatesError }}</div>\n\n <!-- Variant selection mode toggle -->\n <div class=\"variant-mode-toggle\" *ngIf=\"obs.selectedTemplateId\">\n <label class=\"variant-mode-label\">{{ 'variantSelectionMode' | pluginTranslate: pluginId | async }}</label>\n <div class=\"variant-mode-buttons\">\n <button\n type=\"button\"\n class=\"variant-mode-btn\"\n [class.active]=\"variantSelectionMode === 'explicit'\"\n (click)=\"onVariantSelectionModeChange('explicit')\"\n [disabled]=\"obs.disabled\"\n >{{ 'selectByVariant' | pluginTranslate: pluginId | async }}</button>\n <button\n type=\"button\"\n class=\"variant-mode-btn\"\n [class.active]=\"variantSelectionMode === 'attributes'\"\n (click)=\"onVariantSelectionModeChange('attributes')\"\n [disabled]=\"obs.disabled\"\n >{{ 'selectByAttributes' | pluginTranslate: pluginId | async }}</button>\n </div>\n </div>\n\n <!-- Explicit variant selection (dropdown) -->\n <v-select\n *ngIf=\"variantSelectionMode === 'explicit'\"\n name=\"variantId\"\n [title]=\"'variantId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'variantIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"obs.variantOptions\"\n [defaultSelectionId]=\"obs.prefill?.variantId\"\n [disabled]=\"obs.disabled || obs.variantsLoading || !obs.selectedTemplateId\"\n [required]=\"false\"\n [loading]=\"obs.variantsLoading\"\n >\n </v-select>\n <div *ngIf=\"obs.variantsError\" class=\"loading-error\">{{ obs.variantsError }}</div>\n\n <!-- Attribute-based variant selection -->\n <div *ngIf=\"variantSelectionMode === 'attributes' && obs.selectedTemplateId\" class=\"variant-attributes-section\">\n <label class=\"variant-attributes-label\">{{ 'variantAttributes' | pluginTranslate: pluginId | async }}</label>\n <div class=\"variant-attributes-list\">\n <div *ngFor=\"let entry of variantAttributeEntries; let i = index\" class=\"variant-attribute-row\">\n <input\n type=\"text\"\n class=\"variant-attribute-input\"\n [placeholder]=\"'attributeKey' | pluginTranslate: pluginId | async\"\n [(ngModel)]=\"entry.key\"\n (ngModelChange)=\"onAttributeEntryChange()\"\n [disabled]=\"obs.disabled\"\n />\n <input\n type=\"text\"\n class=\"variant-attribute-input\"\n [placeholder]=\"'attributeValue' | pluginTranslate: pluginId | async\"\n [(ngModel)]=\"entry.value\"\n (ngModelChange)=\"onAttributeEntryChange()\"\n [disabled]=\"obs.disabled\"\n />\n <button\n type=\"button\"\n class=\"variant-attribute-remove-btn\"\n (click)=\"removeAttributeEntry(i)\"\n [disabled]=\"obs.disabled\"\n title=\"{{ 'removeAttribute' | pluginTranslate: pluginId | async }}\"\n >&times;</button>\n </div>\n </div>\n <button\n type=\"button\"\n class=\"variant-attribute-add-btn\"\n (click)=\"addAttributeEntry()\"\n [disabled]=\"obs.disabled\"\n >+ {{ 'addAttribute' | pluginTranslate: pluginId | async }}</button>\n </div>\n\n <v-select\n name=\"environmentId\"\n [title]=\"'environmentId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'environmentIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"obs.environmentOptions\"\n [defaultSelectionId]=\"obs.prefill?.environmentId\"\n [disabled]=\"obs.disabled || obs.environmentsLoading\"\n [required]=\"false\"\n [loading]=\"obs.environmentsLoading\"\n >\n </v-select>\n <div *ngIf=\"obs.environmentsError\" class=\"loading-error\">{{ obs.environmentsError }}</div>\n\n <v-select\n name=\"outputFormat\"\n [title]=\"'outputFormat' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'outputFormatTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"outputFormatOptions\"\n [defaultSelectionId]=\"obs.prefill?.outputFormat || 'PDF'\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-select>\n\n <v-input\n name=\"filename\"\n [title]=\"'filename' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'filenameTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.filename\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-input>\n\n <v-input\n name=\"correlationId\"\n [title]=\"'correlationId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'correlationIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.correlationId\"\n [disabled]=\"obs.disabled\"\n [required]=\"false\"\n >\n </v-input>\n\n <v-input\n name=\"resultProcessVariable\"\n [title]=\"'resultProcessVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'resultProcessVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.resultProcessVariable\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-input>\n</v-form>\n\n<div *ngIf=\"templateFieldsError$ | async as templateFieldsError\" class=\"loading-error\">{{ templateFieldsError }}</div>\n\n<epistola-data-mapping-tree\n *ngIf=\"(selectedTemplateId$ | async)\"\n [pluginId]=\"pluginId\"\n [templateFields$]=\"templateFields$\"\n [disabled$]=\"disabled$\"\n [prefillMapping$]=\"prefillDataMapping$\"\n [caseDefinitionKey]=\"caseDefinitionKey\"\n [processVariables]=\"processVariables\"\n (mappingChange)=\"onDataMappingChange($event)\"\n (requiredFieldsStatus)=\"onRequiredFieldsStatusChange($event)\"\n></epistola-data-mapping-tree>\n\n<div class=\"validation-summary\" *ngIf=\"(selectedTemplateId$ | async) && requiredFieldsStatus.total > 0\">\n <span *ngIf=\"requiredFieldsStatus.mapped === requiredFieldsStatus.total\" class=\"validation-complete\">\n {{ 'requiredFieldsComplete' | pluginTranslate: pluginId | async }}\n </span>\n <span *ngIf=\"requiredFieldsStatus.mapped < requiredFieldsStatus.total\" class=\"validation-incomplete\">\n {{ requiredFieldsStatus.mapped }} / {{ requiredFieldsStatus.total }}\n {{ 'validationSummary' | pluginTranslate: pluginId | async }}\n </span>\n</div>\n", styles: [".loading-error{padding:.25rem .75rem;font-size:.8125rem;color:#dc3545}.validation-summary{margin-top:.5rem;padding:.5rem .75rem;border-radius:4px;font-size:.875rem}.validation-summary .validation-complete{color:#198754}.validation-summary .validation-incomplete{color:#dc3545;font-weight:500}.variant-mode-toggle{margin-bottom:1rem;padding:0 .75rem}.variant-mode-label{display:block;font-size:.875rem;font-weight:500;margin-bottom:.375rem}.variant-mode-buttons{display:flex;gap:0;border:1px solid #d1d5db;border-radius:4px;overflow:hidden;width:fit-content}.variant-mode-btn{padding:.375rem .75rem;font-size:.8125rem;background:#fff;border:none;border-right:1px solid #d1d5db;cursor:pointer;color:#374151;transition:background-color .15s,color .15s}.variant-mode-btn:last-child{border-right:none}.variant-mode-btn:hover:not([disabled]){background:#f3f4f6}.variant-mode-btn.active{background:#2563eb;color:#fff}.variant-mode-btn[disabled]{opacity:.5;cursor:not-allowed}.variant-attributes-section{margin-bottom:1rem;padding:0 .75rem}.variant-attributes-label{display:block;font-size:.875rem;font-weight:500;margin-bottom:.375rem}.variant-attributes-list{display:flex;flex-direction:column;gap:.375rem}.variant-attribute-row{display:flex;gap:.375rem;align-items:center}.variant-attribute-input{flex:1;padding:.375rem .5rem;font-size:.8125rem;border:1px solid #d1d5db;border-radius:4px;outline:none}.variant-attribute-input:focus{border-color:#2563eb;box-shadow:0 0 0 1px #2563eb}.variant-attribute-input[disabled]{opacity:.5;background:#f9fafb}.variant-attribute-remove-btn{padding:.25rem .5rem;font-size:1rem;line-height:1;background:none;border:1px solid #d1d5db;border-radius:4px;cursor:pointer;color:#6b7280}.variant-attribute-remove-btn:hover:not([disabled]){color:#dc3545;border-color:#dc3545}.variant-attribute-remove-btn[disabled]{opacity:.5;cursor:not-allowed}.variant-attribute-add-btn{margin-top:.375rem;padding:.25rem .5rem;font-size:.8125rem;background:none;border:1px dashed #d1d5db;border-radius:4px;cursor:pointer;color:#6b7280}.variant-attribute-add-btn:hover:not([disabled]){color:#2563eb;border-color:#2563eb}.variant-attribute-add-btn[disabled]{opacity:.5;cursor:not-allowed}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i4.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i4.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i4.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: PluginTranslatePipeModule }, { kind: "pipe", type: i2$1.PluginTranslatePipe, name: "pluginTranslate" }, { kind: "ngmodule", type: FormModule }, { kind: "component", type: i3.FormComponent, selector: "v-form", inputs: ["className"], outputs: ["valueChange"] }, { kind: "ngmodule", type: InputModule }, { kind: "component", type: i3.InputComponent, selector: "v-input", inputs: ["name", "type", "title", "titleTranslationKey", "defaultValue", "widthPx", "fullWidth", "margin", "smallMargin", "disabled", "step", "min", "maxLength", "tooltip", "required", "hideNumberSpinBox", "smallLabel", "rows", "clear$", "carbonTheme", "placeholder", "dataTestId", "trim", "presetsTitle", "presetOptions"], outputs: ["valueChange"] }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: i3.SelectComponent, selector: "v-select", inputs: ["items", "defaultSelection", "defaultSelectionId", "defaultSelectionIds", "disabled", "dropUp", "invalid", "multiple", "margin", "widthInPx", "notFoundText", "clearAllText", "clearText", "clearable", "name", "title", "titleTranslationKey", "clearSelectionSubject$", "tooltip", "required", "loading", "loadingText", "placeholder", "smallMargin", "carbonTheme", "appendInline", "warn", "warnText", "dataTestId"], outputs: ["selectedChange"] }, { kind: "component", type: DataMappingTreeComponent, selector: "epistola-data-mapping-tree", inputs: ["pluginId", "templateFields$", "prefillMapping$", "disabled$", "caseDefinitionKey", "processVariables"], outputs: ["mappingChange", "requiredFieldsStatus"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
865
892
  }
866
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: GenerateDocumentConfigurationComponent, decorators: [{
893
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: GenerateDocumentConfigurationComponent, decorators: [{
867
894
  type: Component,
868
- args: [{ selector: 'epistola-generate-document-configuration', standalone: true, imports: [
895
+ args: [{ selector: 'epistola-generate-document-configuration', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [
869
896
  CommonModule,
870
897
  FormsModule,
871
898
  PluginTranslatePipeModule,
@@ -873,8 +900,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
873
900
  InputModule,
874
901
  SelectModule,
875
902
  DataMappingTreeComponent
876
- ], template: "<v-form\n (valueChange)=\"formValueChange($event)\"\n *ngIf=\"{\n disabled: disabled$ | async,\n prefill: prefillConfiguration$ ? (prefillConfiguration$ | async) : null,\n templateOptions: templateOptions$ | async,\n templatesLoading: templatesLoading$ | async,\n variantOptions: variantOptions$ | async,\n variantsLoading: variantsLoading$ | async,\n environmentOptions: environmentOptions$ | async,\n environmentsLoading: environmentsLoading$ | async,\n templateFieldsLoading: templateFieldsLoading$ | async,\n selectedTemplateId: selectedTemplateId$ | async\n } as obs\"\n>\n <v-select\n name=\"templateId\"\n [title]=\"'templateId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'templateIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"obs.templateOptions\"\n [defaultSelectionId]=\"obs.prefill?.templateId\"\n [disabled]=\"obs.disabled || obs.templatesLoading\"\n [required]=\"true\"\n [loading]=\"obs.templatesLoading\"\n >\n </v-select>\n\n <!-- Variant selection mode toggle -->\n <div class=\"variant-mode-toggle\" *ngIf=\"obs.selectedTemplateId\">\n <label class=\"variant-mode-label\">{{ 'variantSelectionMode' | pluginTranslate: pluginId | async }}</label>\n <div class=\"variant-mode-buttons\">\n <button\n type=\"button\"\n class=\"variant-mode-btn\"\n [class.active]=\"variantSelectionMode === 'explicit'\"\n (click)=\"onVariantSelectionModeChange('explicit')\"\n [disabled]=\"obs.disabled\"\n >{{ 'selectByVariant' | pluginTranslate: pluginId | async }}</button>\n <button\n type=\"button\"\n class=\"variant-mode-btn\"\n [class.active]=\"variantSelectionMode === 'attributes'\"\n (click)=\"onVariantSelectionModeChange('attributes')\"\n [disabled]=\"obs.disabled\"\n >{{ 'selectByAttributes' | pluginTranslate: pluginId | async }}</button>\n </div>\n </div>\n\n <!-- Explicit variant selection (dropdown) -->\n <v-select\n *ngIf=\"variantSelectionMode === 'explicit'\"\n name=\"variantId\"\n [title]=\"'variantId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'variantIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"obs.variantOptions\"\n [defaultSelectionId]=\"obs.prefill?.variantId\"\n [disabled]=\"obs.disabled || obs.variantsLoading || !obs.selectedTemplateId\"\n [required]=\"false\"\n [loading]=\"obs.variantsLoading\"\n >\n </v-select>\n\n <!-- Attribute-based variant selection -->\n <div *ngIf=\"variantSelectionMode === 'attributes' && obs.selectedTemplateId\" class=\"variant-attributes-section\">\n <label class=\"variant-attributes-label\">{{ 'variantAttributes' | pluginTranslate: pluginId | async }}</label>\n <div class=\"variant-attributes-list\">\n <div *ngFor=\"let entry of variantAttributeEntries; let i = index\" class=\"variant-attribute-row\">\n <input\n type=\"text\"\n class=\"variant-attribute-input\"\n [placeholder]=\"'attributeKey' | pluginTranslate: pluginId | async\"\n [(ngModel)]=\"entry.key\"\n (ngModelChange)=\"onAttributeEntryChange()\"\n [disabled]=\"obs.disabled\"\n />\n <input\n type=\"text\"\n class=\"variant-attribute-input\"\n [placeholder]=\"'attributeValue' | pluginTranslate: pluginId | async\"\n [(ngModel)]=\"entry.value\"\n (ngModelChange)=\"onAttributeEntryChange()\"\n [disabled]=\"obs.disabled\"\n />\n <button\n type=\"button\"\n class=\"variant-attribute-remove-btn\"\n (click)=\"removeAttributeEntry(i)\"\n [disabled]=\"obs.disabled\"\n title=\"{{ 'removeAttribute' | pluginTranslate: pluginId | async }}\"\n >&times;</button>\n </div>\n </div>\n <button\n type=\"button\"\n class=\"variant-attribute-add-btn\"\n (click)=\"addAttributeEntry()\"\n [disabled]=\"obs.disabled\"\n >+ {{ 'addAttribute' | pluginTranslate: pluginId | async }}</button>\n </div>\n\n <v-select\n name=\"environmentId\"\n [title]=\"'environmentId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'environmentIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"obs.environmentOptions\"\n [defaultSelectionId]=\"obs.prefill?.environmentId\"\n [disabled]=\"obs.disabled || obs.environmentsLoading\"\n [required]=\"false\"\n [loading]=\"obs.environmentsLoading\"\n >\n </v-select>\n\n <v-select\n name=\"outputFormat\"\n [title]=\"'outputFormat' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'outputFormatTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"outputFormatOptions\"\n [defaultSelectionId]=\"obs.prefill?.outputFormat || 'PDF'\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-select>\n\n <v-input\n name=\"filename\"\n [title]=\"'filename' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'filenameTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.filename\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-input>\n\n <v-input\n name=\"correlationId\"\n [title]=\"'correlationId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'correlationIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.correlationId\"\n [disabled]=\"obs.disabled\"\n [required]=\"false\"\n >\n </v-input>\n\n <v-input\n name=\"resultProcessVariable\"\n [title]=\"'resultProcessVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'resultProcessVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.resultProcessVariable\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-input>\n</v-form>\n\n<epistola-data-mapping-tree\n *ngIf=\"(selectedTemplateId$ | async)\"\n [pluginId]=\"pluginId\"\n [templateFields$]=\"templateFields$\"\n [disabled$]=\"disabled$\"\n [prefillMapping$]=\"prefillDataMapping$\"\n [caseDefinitionKey]=\"caseDefinitionKey\"\n [processVariables]=\"processVariables\"\n (mappingChange)=\"onDataMappingChange($event)\"\n (requiredFieldsStatus)=\"onRequiredFieldsStatusChange($event)\"\n></epistola-data-mapping-tree>\n\n<div class=\"validation-summary\" *ngIf=\"(selectedTemplateId$ | async) && requiredFieldsStatus.total > 0\">\n <span *ngIf=\"requiredFieldsStatus.mapped === requiredFieldsStatus.total\" class=\"validation-complete\">\n {{ 'requiredFieldsComplete' | pluginTranslate: pluginId | async }}\n </span>\n <span *ngIf=\"requiredFieldsStatus.mapped < requiredFieldsStatus.total\" class=\"validation-incomplete\">\n {{ requiredFieldsStatus.mapped }} / {{ requiredFieldsStatus.total }}\n {{ 'validationSummary' | pluginTranslate: pluginId | async }}\n </span>\n</div>\n", styles: [".validation-summary{margin-top:.5rem;padding:.5rem .75rem;border-radius:4px;font-size:.875rem}.validation-summary .validation-complete{color:#198754}.validation-summary .validation-incomplete{color:#dc3545;font-weight:500}.variant-mode-toggle{margin-bottom:1rem;padding:0 .75rem}.variant-mode-label{display:block;font-size:.875rem;font-weight:500;margin-bottom:.375rem}.variant-mode-buttons{display:flex;gap:0;border:1px solid #d1d5db;border-radius:4px;overflow:hidden;width:fit-content}.variant-mode-btn{padding:.375rem .75rem;font-size:.8125rem;background:#fff;border:none;border-right:1px solid #d1d5db;cursor:pointer;color:#374151;transition:background-color .15s,color .15s}.variant-mode-btn:last-child{border-right:none}.variant-mode-btn:hover:not([disabled]){background:#f3f4f6}.variant-mode-btn.active{background:#2563eb;color:#fff}.variant-mode-btn[disabled]{opacity:.5;cursor:not-allowed}.variant-attributes-section{margin-bottom:1rem;padding:0 .75rem}.variant-attributes-label{display:block;font-size:.875rem;font-weight:500;margin-bottom:.375rem}.variant-attributes-list{display:flex;flex-direction:column;gap:.375rem}.variant-attribute-row{display:flex;gap:.375rem;align-items:center}.variant-attribute-input{flex:1;padding:.375rem .5rem;font-size:.8125rem;border:1px solid #d1d5db;border-radius:4px;outline:none}.variant-attribute-input:focus{border-color:#2563eb;box-shadow:0 0 0 1px #2563eb}.variant-attribute-input[disabled]{opacity:.5;background:#f9fafb}.variant-attribute-remove-btn{padding:.25rem .5rem;font-size:1rem;line-height:1;background:none;border:1px solid #d1d5db;border-radius:4px;cursor:pointer;color:#6b7280}.variant-attribute-remove-btn:hover:not([disabled]){color:#dc3545;border-color:#dc3545}.variant-attribute-remove-btn[disabled]{opacity:.5;cursor:not-allowed}.variant-attribute-add-btn{margin-top:.375rem;padding:.25rem .5rem;font-size:.8125rem;background:none;border:1px dashed #d1d5db;border-radius:4px;cursor:pointer;color:#6b7280}.variant-attribute-add-btn:hover:not([disabled]){color:#2563eb;border-color:#2563eb}.variant-attribute-add-btn[disabled]{opacity:.5;cursor:not-allowed}\n"] }]
877
- }], ctorParameters: () => [{ type: EpistolaPluginService }, { type: i2$2.ProcessLinkStateService }], propDecorators: { save$: [{
903
+ ], template: "<v-form\n (valueChange)=\"formValueChange($event)\"\n *ngIf=\"{\n disabled: disabled$ | async,\n prefill: prefillConfiguration$ ? (prefillConfiguration$ | async) : null,\n templateOptions: templateOptions$ | async,\n templatesLoading: templatesLoading$ | async,\n templatesError: templatesError$ | async,\n variantOptions: variantOptions$ | async,\n variantsLoading: variantsLoading$ | async,\n variantsError: variantsError$ | async,\n environmentOptions: environmentOptions$ | async,\n environmentsLoading: environmentsLoading$ | async,\n environmentsError: environmentsError$ | async,\n templateFieldsLoading: templateFieldsLoading$ | async,\n selectedTemplateId: selectedTemplateId$ | async\n } as obs\"\n>\n <v-select\n name=\"templateId\"\n [title]=\"'templateId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'templateIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"obs.templateOptions\"\n [defaultSelectionId]=\"obs.prefill?.templateId\"\n [disabled]=\"obs.disabled || obs.templatesLoading\"\n [required]=\"true\"\n [loading]=\"obs.templatesLoading\"\n >\n </v-select>\n <div *ngIf=\"obs.templatesError\" class=\"loading-error\">{{ obs.templatesError }}</div>\n\n <!-- Variant selection mode toggle -->\n <div class=\"variant-mode-toggle\" *ngIf=\"obs.selectedTemplateId\">\n <label class=\"variant-mode-label\">{{ 'variantSelectionMode' | pluginTranslate: pluginId | async }}</label>\n <div class=\"variant-mode-buttons\">\n <button\n type=\"button\"\n class=\"variant-mode-btn\"\n [class.active]=\"variantSelectionMode === 'explicit'\"\n (click)=\"onVariantSelectionModeChange('explicit')\"\n [disabled]=\"obs.disabled\"\n >{{ 'selectByVariant' | pluginTranslate: pluginId | async }}</button>\n <button\n type=\"button\"\n class=\"variant-mode-btn\"\n [class.active]=\"variantSelectionMode === 'attributes'\"\n (click)=\"onVariantSelectionModeChange('attributes')\"\n [disabled]=\"obs.disabled\"\n >{{ 'selectByAttributes' | pluginTranslate: pluginId | async }}</button>\n </div>\n </div>\n\n <!-- Explicit variant selection (dropdown) -->\n <v-select\n *ngIf=\"variantSelectionMode === 'explicit'\"\n name=\"variantId\"\n [title]=\"'variantId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'variantIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"obs.variantOptions\"\n [defaultSelectionId]=\"obs.prefill?.variantId\"\n [disabled]=\"obs.disabled || obs.variantsLoading || !obs.selectedTemplateId\"\n [required]=\"false\"\n [loading]=\"obs.variantsLoading\"\n >\n </v-select>\n <div *ngIf=\"obs.variantsError\" class=\"loading-error\">{{ obs.variantsError }}</div>\n\n <!-- Attribute-based variant selection -->\n <div *ngIf=\"variantSelectionMode === 'attributes' && obs.selectedTemplateId\" class=\"variant-attributes-section\">\n <label class=\"variant-attributes-label\">{{ 'variantAttributes' | pluginTranslate: pluginId | async }}</label>\n <div class=\"variant-attributes-list\">\n <div *ngFor=\"let entry of variantAttributeEntries; let i = index\" class=\"variant-attribute-row\">\n <input\n type=\"text\"\n class=\"variant-attribute-input\"\n [placeholder]=\"'attributeKey' | pluginTranslate: pluginId | async\"\n [(ngModel)]=\"entry.key\"\n (ngModelChange)=\"onAttributeEntryChange()\"\n [disabled]=\"obs.disabled\"\n />\n <input\n type=\"text\"\n class=\"variant-attribute-input\"\n [placeholder]=\"'attributeValue' | pluginTranslate: pluginId | async\"\n [(ngModel)]=\"entry.value\"\n (ngModelChange)=\"onAttributeEntryChange()\"\n [disabled]=\"obs.disabled\"\n />\n <button\n type=\"button\"\n class=\"variant-attribute-remove-btn\"\n (click)=\"removeAttributeEntry(i)\"\n [disabled]=\"obs.disabled\"\n title=\"{{ 'removeAttribute' | pluginTranslate: pluginId | async }}\"\n >&times;</button>\n </div>\n </div>\n <button\n type=\"button\"\n class=\"variant-attribute-add-btn\"\n (click)=\"addAttributeEntry()\"\n [disabled]=\"obs.disabled\"\n >+ {{ 'addAttribute' | pluginTranslate: pluginId | async }}</button>\n </div>\n\n <v-select\n name=\"environmentId\"\n [title]=\"'environmentId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'environmentIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"obs.environmentOptions\"\n [defaultSelectionId]=\"obs.prefill?.environmentId\"\n [disabled]=\"obs.disabled || obs.environmentsLoading\"\n [required]=\"false\"\n [loading]=\"obs.environmentsLoading\"\n >\n </v-select>\n <div *ngIf=\"obs.environmentsError\" class=\"loading-error\">{{ obs.environmentsError }}</div>\n\n <v-select\n name=\"outputFormat\"\n [title]=\"'outputFormat' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'outputFormatTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"outputFormatOptions\"\n [defaultSelectionId]=\"obs.prefill?.outputFormat || 'PDF'\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-select>\n\n <v-input\n name=\"filename\"\n [title]=\"'filename' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'filenameTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.filename\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-input>\n\n <v-input\n name=\"correlationId\"\n [title]=\"'correlationId' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'correlationIdTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.correlationId\"\n [disabled]=\"obs.disabled\"\n [required]=\"false\"\n >\n </v-input>\n\n <v-input\n name=\"resultProcessVariable\"\n [title]=\"'resultProcessVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'resultProcessVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.resultProcessVariable\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-input>\n</v-form>\n\n<div *ngIf=\"templateFieldsError$ | async as templateFieldsError\" class=\"loading-error\">{{ templateFieldsError }}</div>\n\n<epistola-data-mapping-tree\n *ngIf=\"(selectedTemplateId$ | async)\"\n [pluginId]=\"pluginId\"\n [templateFields$]=\"templateFields$\"\n [disabled$]=\"disabled$\"\n [prefillMapping$]=\"prefillDataMapping$\"\n [caseDefinitionKey]=\"caseDefinitionKey\"\n [processVariables]=\"processVariables\"\n (mappingChange)=\"onDataMappingChange($event)\"\n (requiredFieldsStatus)=\"onRequiredFieldsStatusChange($event)\"\n></epistola-data-mapping-tree>\n\n<div class=\"validation-summary\" *ngIf=\"(selectedTemplateId$ | async) && requiredFieldsStatus.total > 0\">\n <span *ngIf=\"requiredFieldsStatus.mapped === requiredFieldsStatus.total\" class=\"validation-complete\">\n {{ 'requiredFieldsComplete' | pluginTranslate: pluginId | async }}\n </span>\n <span *ngIf=\"requiredFieldsStatus.mapped < requiredFieldsStatus.total\" class=\"validation-incomplete\">\n {{ requiredFieldsStatus.mapped }} / {{ requiredFieldsStatus.total }}\n {{ 'validationSummary' | pluginTranslate: pluginId | async }}\n </span>\n</div>\n", styles: [".loading-error{padding:.25rem .75rem;font-size:.8125rem;color:#dc3545}.validation-summary{margin-top:.5rem;padding:.5rem .75rem;border-radius:4px;font-size:.875rem}.validation-summary .validation-complete{color:#198754}.validation-summary .validation-incomplete{color:#dc3545;font-weight:500}.variant-mode-toggle{margin-bottom:1rem;padding:0 .75rem}.variant-mode-label{display:block;font-size:.875rem;font-weight:500;margin-bottom:.375rem}.variant-mode-buttons{display:flex;gap:0;border:1px solid #d1d5db;border-radius:4px;overflow:hidden;width:fit-content}.variant-mode-btn{padding:.375rem .75rem;font-size:.8125rem;background:#fff;border:none;border-right:1px solid #d1d5db;cursor:pointer;color:#374151;transition:background-color .15s,color .15s}.variant-mode-btn:last-child{border-right:none}.variant-mode-btn:hover:not([disabled]){background:#f3f4f6}.variant-mode-btn.active{background:#2563eb;color:#fff}.variant-mode-btn[disabled]{opacity:.5;cursor:not-allowed}.variant-attributes-section{margin-bottom:1rem;padding:0 .75rem}.variant-attributes-label{display:block;font-size:.875rem;font-weight:500;margin-bottom:.375rem}.variant-attributes-list{display:flex;flex-direction:column;gap:.375rem}.variant-attribute-row{display:flex;gap:.375rem;align-items:center}.variant-attribute-input{flex:1;padding:.375rem .5rem;font-size:.8125rem;border:1px solid #d1d5db;border-radius:4px;outline:none}.variant-attribute-input:focus{border-color:#2563eb;box-shadow:0 0 0 1px #2563eb}.variant-attribute-input[disabled]{opacity:.5;background:#f9fafb}.variant-attribute-remove-btn{padding:.25rem .5rem;font-size:1rem;line-height:1;background:none;border:1px solid #d1d5db;border-radius:4px;cursor:pointer;color:#6b7280}.variant-attribute-remove-btn:hover:not([disabled]){color:#dc3545;border-color:#dc3545}.variant-attribute-remove-btn[disabled]{opacity:.5;cursor:not-allowed}.variant-attribute-add-btn{margin-top:.375rem;padding:.25rem .5rem;font-size:.8125rem;background:none;border:1px dashed #d1d5db;border-radius:4px;cursor:pointer;color:#6b7280}.variant-attribute-add-btn:hover:not([disabled]){color:#2563eb;border-color:#2563eb}.variant-attribute-add-btn[disabled]{opacity:.5;cursor:not-allowed}\n"] }]
904
+ }], ctorParameters: () => [{ type: EpistolaPluginService }, { type: i2$2.ProcessLinkStateService }, { type: i0.ChangeDetectorRef }], propDecorators: { save$: [{
878
905
  type: Input
879
906
  }], disabled$: [{
880
907
  type: Input
@@ -932,10 +959,10 @@ class CheckJobStatusConfigurationComponent {
932
959
  });
933
960
  });
934
961
  }
935
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: CheckJobStatusConfigurationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
936
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.17", type: CheckJobStatusConfigurationComponent, isStandalone: true, selector: "epistola-check-job-status-configuration", inputs: { save$: "save$", disabled$: "disabled$", pluginId: "pluginId", prefillConfiguration$: "prefillConfiguration$" }, outputs: { valid: "valid", configuration: "configuration" }, ngImport: i0, template: "<v-form\n (valueChange)=\"formValueChange($event)\"\n *ngIf=\"{\n disabled: safeDisabled$ | async,\n prefill: prefillConfiguration$ ? (prefillConfiguration$ | async) : null\n } as obs\"\n>\n <v-input\n name=\"requestIdVariable\"\n [title]=\"'requestIdVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'requestIdVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.requestIdVariable || 'epistolaRequestId'\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-input>\n\n <v-input\n name=\"statusVariable\"\n [title]=\"'statusVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'statusVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.statusVariable || 'epistolaStatus'\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-input>\n\n <v-input\n name=\"documentIdVariable\"\n [title]=\"'documentIdVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'documentIdVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.documentIdVariable || 'epistolaDocumentId'\"\n [disabled]=\"obs.disabled\"\n [required]=\"false\"\n >\n </v-input>\n\n <v-input\n name=\"errorMessageVariable\"\n [title]=\"'errorMessageVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'errorMessageVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.errorMessageVariable || 'epistolaErrorMessage'\"\n [disabled]=\"obs.disabled\"\n [required]=\"false\"\n >\n </v-input>\n</v-form>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "ngmodule", type: PluginTranslatePipeModule }, { kind: "pipe", type: i2$1.PluginTranslatePipe, name: "pluginTranslate" }, { kind: "ngmodule", type: FormModule }, { kind: "component", type: i3.FormComponent, selector: "v-form", inputs: ["className"], outputs: ["valueChange"] }, { kind: "ngmodule", type: InputModule }, { kind: "component", type: i3.InputComponent, selector: "v-input", inputs: ["name", "type", "title", "titleTranslationKey", "defaultValue", "widthPx", "fullWidth", "margin", "smallMargin", "disabled", "step", "min", "maxLength", "tooltip", "required", "hideNumberSpinBox", "smallLabel", "rows", "clear$", "carbonTheme", "placeholder", "dataTestId", "trim", "presetsTitle", "presetOptions"], outputs: ["valueChange"] }] });
962
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: CheckJobStatusConfigurationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
963
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: CheckJobStatusConfigurationComponent, isStandalone: true, selector: "epistola-check-job-status-configuration", inputs: { save$: "save$", disabled$: "disabled$", pluginId: "pluginId", prefillConfiguration$: "prefillConfiguration$" }, outputs: { valid: "valid", configuration: "configuration" }, ngImport: i0, template: "<v-form\n (valueChange)=\"formValueChange($event)\"\n *ngIf=\"{\n disabled: safeDisabled$ | async,\n prefill: prefillConfiguration$ ? (prefillConfiguration$ | async) : null\n } as obs\"\n>\n <v-input\n name=\"requestIdVariable\"\n [title]=\"'requestIdVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'requestIdVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.requestIdVariable || 'epistolaRequestId'\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-input>\n\n <v-input\n name=\"statusVariable\"\n [title]=\"'statusVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'statusVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.statusVariable || 'epistolaStatus'\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-input>\n\n <v-input\n name=\"documentIdVariable\"\n [title]=\"'documentIdVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'documentIdVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.documentIdVariable || 'epistolaDocumentId'\"\n [disabled]=\"obs.disabled\"\n [required]=\"false\"\n >\n </v-input>\n\n <v-input\n name=\"errorMessageVariable\"\n [title]=\"'errorMessageVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'errorMessageVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.errorMessageVariable || 'epistolaErrorMessage'\"\n [disabled]=\"obs.disabled\"\n [required]=\"false\"\n >\n </v-input>\n</v-form>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "ngmodule", type: PluginTranslatePipeModule }, { kind: "pipe", type: i2$1.PluginTranslatePipe, name: "pluginTranslate" }, { kind: "ngmodule", type: FormModule }, { kind: "component", type: i3.FormComponent, selector: "v-form", inputs: ["className"], outputs: ["valueChange"] }, { kind: "ngmodule", type: InputModule }, { kind: "component", type: i3.InputComponent, selector: "v-input", inputs: ["name", "type", "title", "titleTranslationKey", "defaultValue", "widthPx", "fullWidth", "margin", "smallMargin", "disabled", "step", "min", "maxLength", "tooltip", "required", "hideNumberSpinBox", "smallLabel", "rows", "clear$", "carbonTheme", "placeholder", "dataTestId", "trim", "presetsTitle", "presetOptions"], outputs: ["valueChange"] }] });
937
964
  }
938
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: CheckJobStatusConfigurationComponent, decorators: [{
965
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: CheckJobStatusConfigurationComponent, decorators: [{
939
966
  type: Component,
940
967
  args: [{ selector: 'epistola-check-job-status-configuration', standalone: true, imports: [CommonModule, PluginTranslatePipeModule, FormModule, InputModule], template: "<v-form\n (valueChange)=\"formValueChange($event)\"\n *ngIf=\"{\n disabled: safeDisabled$ | async,\n prefill: prefillConfiguration$ ? (prefillConfiguration$ | async) : null\n } as obs\"\n>\n <v-input\n name=\"requestIdVariable\"\n [title]=\"'requestIdVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'requestIdVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.requestIdVariable || 'epistolaRequestId'\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-input>\n\n <v-input\n name=\"statusVariable\"\n [title]=\"'statusVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'statusVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.statusVariable || 'epistolaStatus'\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-input>\n\n <v-input\n name=\"documentIdVariable\"\n [title]=\"'documentIdVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'documentIdVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.documentIdVariable || 'epistolaDocumentId'\"\n [disabled]=\"obs.disabled\"\n [required]=\"false\"\n >\n </v-input>\n\n <v-input\n name=\"errorMessageVariable\"\n [title]=\"'errorMessageVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'errorMessageVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.errorMessageVariable || 'epistolaErrorMessage'\"\n [disabled]=\"obs.disabled\"\n [required]=\"false\"\n >\n </v-input>\n</v-form>\n" }]
941
968
  }], propDecorators: { save$: [{
@@ -992,10 +1019,10 @@ class DownloadDocumentConfigurationComponent {
992
1019
  });
993
1020
  });
994
1021
  }
995
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: DownloadDocumentConfigurationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
996
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.17", type: DownloadDocumentConfigurationComponent, isStandalone: true, selector: "epistola-download-document-configuration", inputs: { save$: "save$", disabled$: "disabled$", pluginId: "pluginId", prefillConfiguration$: "prefillConfiguration$" }, outputs: { valid: "valid", configuration: "configuration" }, ngImport: i0, template: "<v-form\n (valueChange)=\"formValueChange($event)\"\n *ngIf=\"{\n disabled: safeDisabled$ | async,\n prefill: prefillConfiguration$ ? (prefillConfiguration$ | async) : null\n } as obs\"\n>\n <v-input\n name=\"documentIdVariable\"\n [title]=\"'documentIdVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'documentIdVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.documentIdVariable || 'epistolaDocumentId'\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-input>\n\n <v-input\n name=\"contentVariable\"\n [title]=\"'contentVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'contentVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.contentVariable || 'documentContent'\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-input>\n</v-form>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "ngmodule", type: PluginTranslatePipeModule }, { kind: "pipe", type: i2$1.PluginTranslatePipe, name: "pluginTranslate" }, { kind: "ngmodule", type: FormModule }, { kind: "component", type: i3.FormComponent, selector: "v-form", inputs: ["className"], outputs: ["valueChange"] }, { kind: "ngmodule", type: InputModule }, { kind: "component", type: i3.InputComponent, selector: "v-input", inputs: ["name", "type", "title", "titleTranslationKey", "defaultValue", "widthPx", "fullWidth", "margin", "smallMargin", "disabled", "step", "min", "maxLength", "tooltip", "required", "hideNumberSpinBox", "smallLabel", "rows", "clear$", "carbonTheme", "placeholder", "dataTestId", "trim", "presetsTitle", "presetOptions"], outputs: ["valueChange"] }] });
1022
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: DownloadDocumentConfigurationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1023
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: DownloadDocumentConfigurationComponent, isStandalone: true, selector: "epistola-download-document-configuration", inputs: { save$: "save$", disabled$: "disabled$", pluginId: "pluginId", prefillConfiguration$: "prefillConfiguration$" }, outputs: { valid: "valid", configuration: "configuration" }, ngImport: i0, template: "<v-form\n (valueChange)=\"formValueChange($event)\"\n *ngIf=\"{\n disabled: safeDisabled$ | async,\n prefill: prefillConfiguration$ ? (prefillConfiguration$ | async) : null\n } as obs\"\n>\n <v-input\n name=\"documentIdVariable\"\n [title]=\"'documentIdVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'documentIdVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.documentIdVariable || 'epistolaDocumentId'\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-input>\n\n <v-input\n name=\"contentVariable\"\n [title]=\"'contentVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'contentVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.contentVariable || 'documentContent'\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-input>\n</v-form>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "ngmodule", type: PluginTranslatePipeModule }, { kind: "pipe", type: i2$1.PluginTranslatePipe, name: "pluginTranslate" }, { kind: "ngmodule", type: FormModule }, { kind: "component", type: i3.FormComponent, selector: "v-form", inputs: ["className"], outputs: ["valueChange"] }, { kind: "ngmodule", type: InputModule }, { kind: "component", type: i3.InputComponent, selector: "v-input", inputs: ["name", "type", "title", "titleTranslationKey", "defaultValue", "widthPx", "fullWidth", "margin", "smallMargin", "disabled", "step", "min", "maxLength", "tooltip", "required", "hideNumberSpinBox", "smallLabel", "rows", "clear$", "carbonTheme", "placeholder", "dataTestId", "trim", "presetsTitle", "presetOptions"], outputs: ["valueChange"] }] });
997
1024
  }
998
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: DownloadDocumentConfigurationComponent, decorators: [{
1025
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: DownloadDocumentConfigurationComponent, decorators: [{
999
1026
  type: Component,
1000
1027
  args: [{ selector: 'epistola-download-document-configuration', standalone: true, imports: [CommonModule, PluginTranslatePipeModule, FormModule, InputModule], template: "<v-form\n (valueChange)=\"formValueChange($event)\"\n *ngIf=\"{\n disabled: safeDisabled$ | async,\n prefill: prefillConfiguration$ ? (prefillConfiguration$ | async) : null\n } as obs\"\n>\n <v-input\n name=\"documentIdVariable\"\n [title]=\"'documentIdVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'documentIdVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.documentIdVariable || 'epistolaDocumentId'\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-input>\n\n <v-input\n name=\"contentVariable\"\n [title]=\"'contentVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'contentVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"obs.prefill?.contentVariable || 'documentContent'\"\n [disabled]=\"obs.disabled\"\n [required]=\"true\"\n >\n </v-input>\n</v-form>\n" }]
1001
1028
  }], propDecorators: { save$: [{
@@ -1057,8 +1084,8 @@ class EpistolaDownloadComponent {
1057
1084
  },
1058
1085
  });
1059
1086
  }
1060
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: EpistolaDownloadComponent, deps: [{ token: i1.HttpClient }], target: i0.ɵɵFactoryTarget.Component });
1061
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.17", type: EpistolaDownloadComponent, isStandalone: true, selector: "epistola-download-component", inputs: { value: "value", disabled: "disabled", filename: "filename", label: "label" }, outputs: { valueChange: "valueChange" }, ngImport: i0, template: `
1087
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaDownloadComponent, deps: [{ token: i1.HttpClient }], target: i0.ɵɵFactoryTarget.Component });
1088
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: EpistolaDownloadComponent, isStandalone: true, selector: "epistola-download-component", inputs: { value: "value", disabled: "disabled", filename: "filename", label: "label" }, outputs: { valueChange: "valueChange" }, ngImport: i0, template: `
1062
1089
  <button
1063
1090
  type="button"
1064
1091
  class="btn btn-outline-primary"
@@ -1071,7 +1098,7 @@ class EpistolaDownloadComponent {
1071
1098
  <span *ngIf="error" class="text-danger ml-2">{{ error }}</span>
1072
1099
  `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
1073
1100
  }
1074
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: EpistolaDownloadComponent, decorators: [{
1101
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaDownloadComponent, decorators: [{
1075
1102
  type: Component,
1076
1103
  args: [{
1077
1104
  standalone: true,
@@ -1102,9 +1129,620 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
1102
1129
  type: Input
1103
1130
  }] } });
1104
1131
 
1132
+ class EpistolaRetryFormComponent {
1133
+ epistolaPluginService;
1134
+ formIoStateService;
1135
+ cdr;
1136
+ http;
1137
+ sanitizer;
1138
+ configService;
1139
+ value;
1140
+ valueChange = new EventEmitter();
1141
+ disabled = false;
1142
+ label = 'Document Data';
1143
+ sourceActivityId;
1144
+ formDefinition;
1145
+ submission;
1146
+ loading = true;
1147
+ error = null;
1148
+ previewUrl = null;
1149
+ previewLoading = false;
1150
+ previewError = null;
1151
+ previewExpanded = false;
1152
+ loaded = false;
1153
+ loadSubscription;
1154
+ previewSubscription;
1155
+ previewSubject = new Subject();
1156
+ currentBlobUrl = null;
1157
+ resolvedSourceActivityId;
1158
+ processDefinitionKey;
1159
+ apiEndpoint;
1160
+ formOptions = {
1161
+ noAlerts: true,
1162
+ buttonSettings: { showCancel: false, showSubmit: false, showPrevious: false, showNext: false }
1163
+ };
1164
+ constructor(epistolaPluginService, formIoStateService, cdr, http, sanitizer, configService) {
1165
+ this.epistolaPluginService = epistolaPluginService;
1166
+ this.formIoStateService = formIoStateService;
1167
+ this.cdr = cdr;
1168
+ this.http = http;
1169
+ this.sanitizer = sanitizer;
1170
+ this.configService = configService;
1171
+ this.apiEndpoint = `${this.configService.config.valtimoApi.endpointUri}v1/plugin/epistola`;
1172
+ // Debounce preview calls
1173
+ this.previewSubscription = this.previewSubject.pipe(debounceTime(1500)).subscribe(data => {
1174
+ this.loadPreview(data);
1175
+ });
1176
+ }
1177
+ ngOnChanges(changes) {
1178
+ if (!this.loaded) {
1179
+ this.loaded = true;
1180
+ this.loadForm();
1181
+ }
1182
+ }
1183
+ ngOnDestroy() {
1184
+ this.loadSubscription?.unsubscribe();
1185
+ this.previewSubscription?.unsubscribe();
1186
+ if (this.currentBlobUrl) {
1187
+ URL.revokeObjectURL(this.currentBlobUrl);
1188
+ }
1189
+ }
1190
+ togglePreview() {
1191
+ this.previewExpanded = !this.previewExpanded;
1192
+ this.cdr.markForCheck();
1193
+ }
1194
+ onFormChange(event) {
1195
+ if (event?.data) {
1196
+ const json = JSON.stringify(event.data);
1197
+ this.value = json; // Formio reads element.value on change event
1198
+ this.valueChange.emit(json);
1199
+ // Trigger debounced preview
1200
+ this.previewSubject.next(event.data);
1201
+ }
1202
+ }
1203
+ loadPreview(formData) {
1204
+ const documentId = this.formIoStateService.documentId;
1205
+ const processInstanceId = this.formIoStateService.processInstanceId;
1206
+ if (!documentId || !processInstanceId)
1207
+ return;
1208
+ this.previewLoading = true;
1209
+ this.previewError = null;
1210
+ this.cdr.markForCheck();
1211
+ // Revoke previous blob URL to prevent memory leaks
1212
+ if (this.currentBlobUrl) {
1213
+ URL.revokeObjectURL(this.currentBlobUrl);
1214
+ this.currentBlobUrl = null;
1215
+ }
1216
+ this.http.post(`${this.apiEndpoint}/preview`, {
1217
+ documentId,
1218
+ processInstanceId,
1219
+ sourceActivityId: this.sourceActivityId || null,
1220
+ overrides: formData
1221
+ }, { responseType: 'blob' }).subscribe({
1222
+ next: (blob) => {
1223
+ this.currentBlobUrl = URL.createObjectURL(blob);
1224
+ this.previewUrl = this.sanitizer.bypassSecurityTrustResourceUrl(this.currentBlobUrl);
1225
+ this.previewError = null;
1226
+ this.previewLoading = false;
1227
+ this.cdr.markForCheck();
1228
+ },
1229
+ error: (err) => {
1230
+ this.previewUrl = null;
1231
+ // Try to extract error message from JSON response body
1232
+ if (err.error instanceof Blob) {
1233
+ err.error.text().then((text) => {
1234
+ try {
1235
+ const body = JSON.parse(text);
1236
+ this.previewError = body.details || body.error || 'Preview could not be generated';
1237
+ }
1238
+ catch {
1239
+ this.previewError = 'Preview could not be generated';
1240
+ }
1241
+ this.previewLoading = false;
1242
+ this.cdr.markForCheck();
1243
+ });
1244
+ }
1245
+ else {
1246
+ this.previewError = err.error?.error || 'Preview could not be generated';
1247
+ this.previewLoading = false;
1248
+ this.cdr.markForCheck();
1249
+ }
1250
+ }
1251
+ });
1252
+ }
1253
+ loadForm() {
1254
+ const processInstanceId = this.formIoStateService.processInstanceId;
1255
+ const documentId = this.formIoStateService.documentId;
1256
+ if (!processInstanceId) {
1257
+ this.error = 'Could not determine process instance ID.';
1258
+ this.loading = false;
1259
+ this.cdr.markForCheck();
1260
+ return;
1261
+ }
1262
+ this.loadSubscription = this.epistolaPluginService.getRetryForm(processInstanceId, documentId ?? undefined, this.sourceActivityId).subscribe({
1263
+ next: (form) => {
1264
+ this.formDefinition = form;
1265
+ if (this.value) {
1266
+ try {
1267
+ this.submission = { data: JSON.parse(this.value) };
1268
+ }
1269
+ catch {
1270
+ // value is not valid JSON, start fresh
1271
+ }
1272
+ }
1273
+ this.loading = false;
1274
+ this.cdr.markForCheck();
1275
+ },
1276
+ error: (err) => {
1277
+ console.error('Failed to load retry form', err);
1278
+ this.error = 'Failed to load the retry form. Please try again.';
1279
+ this.loading = false;
1280
+ this.cdr.markForCheck();
1281
+ }
1282
+ });
1283
+ }
1284
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaRetryFormComponent, deps: [{ token: EpistolaPluginService }, { token: i3.FormIoStateService }, { token: i0.ChangeDetectorRef }, { token: i1.HttpClient }, { token: i4$1.DomSanitizer }, { token: i2.ConfigService }], target: i0.ɵɵFactoryTarget.Component });
1285
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: EpistolaRetryFormComponent, isStandalone: true, selector: "epistola-retry-form-component", inputs: { value: "value", disabled: "disabled", label: "label", sourceActivityId: "sourceActivityId" }, outputs: { valueChange: "valueChange" }, usesOnChanges: true, ngImport: i0, template: `
1286
+ <div *ngIf="loading" class="epistola-retry-loading">Loading form...</div>
1287
+ <div *ngIf="error" class="epistola-retry-error">{{ error }}</div>
1288
+ <div *ngIf="formDefinition && !loading" class="epistola-retry-container" [class.preview-expanded]="previewExpanded">
1289
+ <div class="epistola-retry-form" [hidden]="previewExpanded">
1290
+ <formio
1291
+ [form]="formDefinition"
1292
+ [submission]="submission"
1293
+ (change)="onFormChange($event)"
1294
+ [options]="formOptions"
1295
+ ></formio>
1296
+ </div>
1297
+ <div class="epistola-retry-preview">
1298
+ <div class="preview-header">
1299
+ <span>Preview</span>
1300
+ <button type="button" class="preview-toggle" (click)="togglePreview()">
1301
+ {{ previewExpanded ? 'Show form' : 'Expand' }}
1302
+ </button>
1303
+ </div>
1304
+ <div *ngIf="previewLoading" class="preview-loading">Generating preview...</div>
1305
+ <object *ngIf="previewUrl && !previewLoading" [data]="previewUrl" type="application/pdf" class="preview-pdf">
1306
+ PDF preview not supported in this browser.
1307
+ </object>
1308
+ <div *ngIf="previewError" class="preview-error">{{ previewError }}</div>
1309
+ <div *ngIf="!previewUrl && !previewLoading && !previewError" class="preview-empty">
1310
+ Edit fields to see a preview
1311
+ </div>
1312
+ </div>
1313
+ </div>
1314
+ `, isInline: true, styles: [".epistola-retry-loading{padding:1rem;color:#6c757d}.epistola-retry-error{padding:.5rem;color:#dc3545}.epistola-retry-container{display:flex;gap:1rem}.epistola-retry-form{flex:2;min-width:0}.epistola-retry-preview{flex:1;min-width:0;border:1px solid #dee2e6;border-radius:4px;padding:1rem;background:#f8f9fa;display:flex;flex-direction:column}.preview-expanded .epistola-retry-preview{flex:1}.preview-header{display:flex;justify-content:space-between;align-items:center;font-weight:700;margin-bottom:.5rem;color:#495057}.preview-toggle{background:none;border:1px solid #6c757d;border-radius:4px;color:#6c757d;padding:.2rem .5rem;font-size:.75rem;cursor:pointer}.preview-toggle:hover{background:#e9ecef}.preview-loading{color:#6c757d;font-style:italic}.preview-pdf{width:100%;flex:1;min-height:500px}.preview-expanded .preview-pdf{min-height:80vh}.preview-error{color:#dc3545}.preview-empty{color:#6c757d;font-style:italic}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormioModule }, { kind: "component", type: i7.FormioComponent, selector: "formio" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1315
+ }
1316
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaRetryFormComponent, decorators: [{
1317
+ type: Component,
1318
+ args: [{ standalone: true, imports: [CommonModule, FormioModule], selector: 'epistola-retry-form-component', changeDetection: ChangeDetectionStrategy.OnPush, template: `
1319
+ <div *ngIf="loading" class="epistola-retry-loading">Loading form...</div>
1320
+ <div *ngIf="error" class="epistola-retry-error">{{ error }}</div>
1321
+ <div *ngIf="formDefinition && !loading" class="epistola-retry-container" [class.preview-expanded]="previewExpanded">
1322
+ <div class="epistola-retry-form" [hidden]="previewExpanded">
1323
+ <formio
1324
+ [form]="formDefinition"
1325
+ [submission]="submission"
1326
+ (change)="onFormChange($event)"
1327
+ [options]="formOptions"
1328
+ ></formio>
1329
+ </div>
1330
+ <div class="epistola-retry-preview">
1331
+ <div class="preview-header">
1332
+ <span>Preview</span>
1333
+ <button type="button" class="preview-toggle" (click)="togglePreview()">
1334
+ {{ previewExpanded ? 'Show form' : 'Expand' }}
1335
+ </button>
1336
+ </div>
1337
+ <div *ngIf="previewLoading" class="preview-loading">Generating preview...</div>
1338
+ <object *ngIf="previewUrl && !previewLoading" [data]="previewUrl" type="application/pdf" class="preview-pdf">
1339
+ PDF preview not supported in this browser.
1340
+ </object>
1341
+ <div *ngIf="previewError" class="preview-error">{{ previewError }}</div>
1342
+ <div *ngIf="!previewUrl && !previewLoading && !previewError" class="preview-empty">
1343
+ Edit fields to see a preview
1344
+ </div>
1345
+ </div>
1346
+ </div>
1347
+ `, styles: [".epistola-retry-loading{padding:1rem;color:#6c757d}.epistola-retry-error{padding:.5rem;color:#dc3545}.epistola-retry-container{display:flex;gap:1rem}.epistola-retry-form{flex:2;min-width:0}.epistola-retry-preview{flex:1;min-width:0;border:1px solid #dee2e6;border-radius:4px;padding:1rem;background:#f8f9fa;display:flex;flex-direction:column}.preview-expanded .epistola-retry-preview{flex:1}.preview-header{display:flex;justify-content:space-between;align-items:center;font-weight:700;margin-bottom:.5rem;color:#495057}.preview-toggle{background:none;border:1px solid #6c757d;border-radius:4px;color:#6c757d;padding:.2rem .5rem;font-size:.75rem;cursor:pointer}.preview-toggle:hover{background:#e9ecef}.preview-loading{color:#6c757d;font-style:italic}.preview-pdf{width:100%;flex:1;min-height:500px}.preview-expanded .preview-pdf{min-height:80vh}.preview-error{color:#dc3545}.preview-empty{color:#6c757d;font-style:italic}\n"] }]
1348
+ }], ctorParameters: () => [{ type: EpistolaPluginService }, { type: i3.FormIoStateService }, { type: i0.ChangeDetectorRef }, { type: i1.HttpClient }, { type: i4$1.DomSanitizer }, { type: i2.ConfigService }], propDecorators: { value: [{
1349
+ type: Input
1350
+ }], valueChange: [{
1351
+ type: Output
1352
+ }], disabled: [{
1353
+ type: Input
1354
+ }], label: [{
1355
+ type: Input
1356
+ }], sourceActivityId: [{
1357
+ type: Input
1358
+ }] } });
1359
+
1360
+ class EpistolaPreviewButtonComponent {
1361
+ http;
1362
+ sanitizer;
1363
+ configService;
1364
+ value;
1365
+ valueChange = new EventEmitter();
1366
+ disabled = false;
1367
+ label = 'Preview PDF';
1368
+ modalOpen = false;
1369
+ loading = false;
1370
+ previewLoading = false;
1371
+ previewError = null;
1372
+ previewUrl = null;
1373
+ currentBlobUrl = null;
1374
+ apiEndpoint;
1375
+ get buttonLabel() {
1376
+ return this.label || 'Preview PDF';
1377
+ }
1378
+ constructor(http, sanitizer, configService) {
1379
+ this.http = http;
1380
+ this.sanitizer = sanitizer;
1381
+ this.configService = configService;
1382
+ this.apiEndpoint = `${this.configService.config.valtimoApi.endpointUri}v1/plugin/epistola`;
1383
+ }
1384
+ ngOnDestroy() {
1385
+ this.revokeBlobUrl();
1386
+ }
1387
+ hasRequiredData() {
1388
+ return !!(this.value?.documentId && this.value?.tenantId);
1389
+ }
1390
+ openPreview() {
1391
+ if (!this.hasRequiredData() || this.loading)
1392
+ return;
1393
+ this.modalOpen = true;
1394
+ this.previewLoading = true;
1395
+ this.previewError = null;
1396
+ this.revokeBlobUrl();
1397
+ const { documentId, tenantId } = this.value;
1398
+ const url = `${this.apiEndpoint}/documents/${encodeURIComponent(documentId)}/download`
1399
+ + `?tenantId=${encodeURIComponent(tenantId)}`
1400
+ + `&filename=preview.pdf`;
1401
+ this.http.get(url, { responseType: 'blob' }).subscribe({
1402
+ next: (blob) => {
1403
+ this.currentBlobUrl = URL.createObjectURL(blob);
1404
+ this.previewUrl = this.sanitizer.bypassSecurityTrustResourceUrl(this.currentBlobUrl);
1405
+ this.previewLoading = false;
1406
+ },
1407
+ error: () => {
1408
+ this.previewError = 'Could not load the document.';
1409
+ this.previewLoading = false;
1410
+ }
1411
+ });
1412
+ }
1413
+ closePreview() {
1414
+ this.modalOpen = false;
1415
+ this.revokeBlobUrl();
1416
+ this.previewUrl = null;
1417
+ this.previewError = null;
1418
+ }
1419
+ revokeBlobUrl() {
1420
+ if (this.currentBlobUrl) {
1421
+ URL.revokeObjectURL(this.currentBlobUrl);
1422
+ this.currentBlobUrl = null;
1423
+ }
1424
+ }
1425
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaPreviewButtonComponent, deps: [{ token: i1.HttpClient }, { token: i4$1.DomSanitizer }, { token: i2.ConfigService }], target: i0.ɵɵFactoryTarget.Component });
1426
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: EpistolaPreviewButtonComponent, isStandalone: true, selector: "epistola-preview-button-component", inputs: { value: "value", disabled: "disabled", label: "label" }, outputs: { valueChange: "valueChange" }, ngImport: i0, template: `
1427
+ <button
1428
+ type="button"
1429
+ class="btn btn-outline-secondary"
1430
+ [disabled]="disabled || loading || !hasRequiredData()"
1431
+ (click)="openPreview()"
1432
+ >
1433
+ <i class="mdi mdi-eye mr-1"></i>
1434
+ {{ loading ? 'Loading...' : buttonLabel }}
1435
+ </button>
1436
+
1437
+ <div *ngIf="modalOpen" class="preview-modal-overlay" (click)="closePreview()">
1438
+ <div class="preview-modal-content" (click)="$event.stopPropagation()">
1439
+ <div class="preview-modal-header">
1440
+ <span>Document Preview</span>
1441
+ <button type="button" class="preview-modal-close" (click)="closePreview()">&times;</button>
1442
+ </div>
1443
+ <div class="preview-modal-body">
1444
+ <div *ngIf="previewLoading" class="preview-loading">Generating preview...</div>
1445
+ <div *ngIf="previewError" class="preview-error">{{ previewError }}</div>
1446
+ <object
1447
+ *ngIf="previewUrl && !previewLoading"
1448
+ [data]="previewUrl"
1449
+ type="application/pdf"
1450
+ class="preview-pdf"
1451
+ >
1452
+ PDF preview is not supported in this browser.
1453
+ </object>
1454
+ </div>
1455
+ </div>
1456
+ </div>
1457
+ `, isInline: true, styles: [".preview-modal-overlay{position:fixed;top:0;left:0;width:100vw;height:100vh;background:#00000080;display:flex;align-items:center;justify-content:center;z-index:10000}.preview-modal-content{background:#fff;border-radius:8px;width:90vw;height:90vh;max-width:1200px;display:flex;flex-direction:column;overflow:hidden;box-shadow:0 8px 32px #0000004d}.preview-modal-header{display:flex;justify-content:space-between;align-items:center;padding:.75rem 1rem;border-bottom:1px solid #dee2e6;font-weight:700;font-size:1rem}.preview-modal-close{background:none;border:none;font-size:1.5rem;cursor:pointer;color:#6c757d;line-height:1;padding:0 .25rem}.preview-modal-close:hover{color:#333}.preview-modal-body{flex:1;overflow:hidden;display:flex;flex-direction:column}.preview-loading,.preview-error{padding:2rem;text-align:center}.preview-error{color:#dc3545}.preview-pdf{width:100%;flex:1}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
1458
+ }
1459
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaPreviewButtonComponent, decorators: [{
1460
+ type: Component,
1461
+ args: [{ standalone: true, imports: [CommonModule], selector: 'epistola-preview-button-component', template: `
1462
+ <button
1463
+ type="button"
1464
+ class="btn btn-outline-secondary"
1465
+ [disabled]="disabled || loading || !hasRequiredData()"
1466
+ (click)="openPreview()"
1467
+ >
1468
+ <i class="mdi mdi-eye mr-1"></i>
1469
+ {{ loading ? 'Loading...' : buttonLabel }}
1470
+ </button>
1471
+
1472
+ <div *ngIf="modalOpen" class="preview-modal-overlay" (click)="closePreview()">
1473
+ <div class="preview-modal-content" (click)="$event.stopPropagation()">
1474
+ <div class="preview-modal-header">
1475
+ <span>Document Preview</span>
1476
+ <button type="button" class="preview-modal-close" (click)="closePreview()">&times;</button>
1477
+ </div>
1478
+ <div class="preview-modal-body">
1479
+ <div *ngIf="previewLoading" class="preview-loading">Generating preview...</div>
1480
+ <div *ngIf="previewError" class="preview-error">{{ previewError }}</div>
1481
+ <object
1482
+ *ngIf="previewUrl && !previewLoading"
1483
+ [data]="previewUrl"
1484
+ type="application/pdf"
1485
+ class="preview-pdf"
1486
+ >
1487
+ PDF preview is not supported in this browser.
1488
+ </object>
1489
+ </div>
1490
+ </div>
1491
+ </div>
1492
+ `, styles: [".preview-modal-overlay{position:fixed;top:0;left:0;width:100vw;height:100vh;background:#00000080;display:flex;align-items:center;justify-content:center;z-index:10000}.preview-modal-content{background:#fff;border-radius:8px;width:90vw;height:90vh;max-width:1200px;display:flex;flex-direction:column;overflow:hidden;box-shadow:0 8px 32px #0000004d}.preview-modal-header{display:flex;justify-content:space-between;align-items:center;padding:.75rem 1rem;border-bottom:1px solid #dee2e6;font-weight:700;font-size:1rem}.preview-modal-close{background:none;border:none;font-size:1.5rem;cursor:pointer;color:#6c757d;line-height:1;padding:0 .25rem}.preview-modal-close:hover{color:#333}.preview-modal-body{flex:1;overflow:hidden;display:flex;flex-direction:column}.preview-loading,.preview-error{padding:2rem;text-align:center}.preview-error{color:#dc3545}.preview-pdf{width:100%;flex:1}\n"] }]
1493
+ }], ctorParameters: () => [{ type: i1.HttpClient }, { type: i4$1.DomSanitizer }, { type: i2.ConfigService }], propDecorators: { value: [{
1494
+ type: Input
1495
+ }], valueChange: [{
1496
+ type: Output
1497
+ }], disabled: [{
1498
+ type: Input
1499
+ }], label: [{
1500
+ type: Input
1501
+ }] } });
1502
+
1503
+ class EpistolaDocumentPreviewComponent {
1504
+ epistolaPluginService;
1505
+ http;
1506
+ sanitizer;
1507
+ configService;
1508
+ formIoStateService;
1509
+ cdr;
1510
+ value;
1511
+ valueChange = new EventEmitter();
1512
+ disabled = false;
1513
+ label = 'Document Preview';
1514
+ sources = [];
1515
+ selectedIndex = 0;
1516
+ discovering = false;
1517
+ loading = false;
1518
+ error = null;
1519
+ previewUrl = null;
1520
+ initialized = false;
1521
+ currentBlobUrl = null;
1522
+ discoverSubscription;
1523
+ previewSubscription;
1524
+ apiEndpoint;
1525
+ constructor(epistolaPluginService, http, sanitizer, configService, formIoStateService, cdr) {
1526
+ this.epistolaPluginService = epistolaPluginService;
1527
+ this.http = http;
1528
+ this.sanitizer = sanitizer;
1529
+ this.configService = configService;
1530
+ this.formIoStateService = formIoStateService;
1531
+ this.cdr = cdr;
1532
+ this.apiEndpoint = `${this.configService.config.valtimoApi.endpointUri}v1/plugin/epistola`;
1533
+ }
1534
+ ngOnChanges(changes) {
1535
+ if (!this.initialized) {
1536
+ this.initialized = true;
1537
+ this.discoverSources();
1538
+ }
1539
+ }
1540
+ ngOnDestroy() {
1541
+ this.discoverSubscription?.unsubscribe();
1542
+ this.previewSubscription?.unsubscribe();
1543
+ this.revokeBlobUrl();
1544
+ }
1545
+ onSourceChange(event) {
1546
+ this.selectedIndex = +event.target.value;
1547
+ this.loadPreview();
1548
+ }
1549
+ refresh() {
1550
+ this.loadPreview();
1551
+ }
1552
+ discoverSources() {
1553
+ const documentId = this.formIoStateService.documentId;
1554
+ if (!documentId) {
1555
+ this.error = 'Could not determine document ID from context.';
1556
+ this.cdr.markForCheck();
1557
+ return;
1558
+ }
1559
+ this.discovering = true;
1560
+ this.error = null;
1561
+ this.cdr.markForCheck();
1562
+ this.discoverSubscription = this.epistolaPluginService.getPreviewSources(documentId).subscribe({
1563
+ next: (sources) => {
1564
+ this.sources = sources;
1565
+ this.discovering = false;
1566
+ this.cdr.markForCheck();
1567
+ if (sources.length > 0) {
1568
+ this.selectedIndex = 0;
1569
+ this.loadPreview();
1570
+ }
1571
+ },
1572
+ error: (err) => {
1573
+ this.error = err.error?.error || 'Failed to discover preview sources';
1574
+ this.discovering = false;
1575
+ this.cdr.markForCheck();
1576
+ }
1577
+ });
1578
+ }
1579
+ loadPreview() {
1580
+ const source = this.sources[this.selectedIndex];
1581
+ if (!source)
1582
+ return;
1583
+ const documentId = this.formIoStateService.documentId;
1584
+ if (!documentId)
1585
+ return;
1586
+ this.loading = true;
1587
+ this.error = null;
1588
+ this.cdr.markForCheck();
1589
+ this.revokeBlobUrl();
1590
+ this.previewSubscription?.unsubscribe();
1591
+ this.previewSubscription = this.http.post(`${this.apiEndpoint}/preview`, {
1592
+ documentId,
1593
+ processInstanceId: source.processInstanceId,
1594
+ sourceActivityId: source.activityId,
1595
+ overrides: null
1596
+ }, {
1597
+ responseType: 'blob',
1598
+ headers: new HttpHeaders().set('X-Skip-Interceptor', '422')
1599
+ }).subscribe({
1600
+ next: (blob) => {
1601
+ this.currentBlobUrl = URL.createObjectURL(blob);
1602
+ this.previewUrl = this.sanitizer.bypassSecurityTrustResourceUrl(this.currentBlobUrl);
1603
+ this.error = null;
1604
+ this.loading = false;
1605
+ this.cdr.markForCheck();
1606
+ },
1607
+ error: (err) => {
1608
+ this.previewUrl = null;
1609
+ if (err.error instanceof Blob) {
1610
+ err.error.text().then((text) => {
1611
+ try {
1612
+ const body = JSON.parse(text);
1613
+ this.error = body.details || body.error || 'Preview could not be generated';
1614
+ }
1615
+ catch {
1616
+ this.error = 'Preview could not be generated';
1617
+ }
1618
+ this.loading = false;
1619
+ this.cdr.markForCheck();
1620
+ });
1621
+ }
1622
+ else {
1623
+ this.error = err.error?.error || 'Preview could not be generated';
1624
+ this.loading = false;
1625
+ this.cdr.markForCheck();
1626
+ }
1627
+ }
1628
+ });
1629
+ }
1630
+ revokeBlobUrl() {
1631
+ if (this.currentBlobUrl) {
1632
+ URL.revokeObjectURL(this.currentBlobUrl);
1633
+ this.currentBlobUrl = null;
1634
+ this.previewUrl = null;
1635
+ }
1636
+ }
1637
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaDocumentPreviewComponent, deps: [{ token: EpistolaPluginService }, { token: i1.HttpClient }, { token: i4$1.DomSanitizer }, { token: i2.ConfigService }, { token: i3.FormIoStateService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
1638
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: EpistolaDocumentPreviewComponent, isStandalone: true, selector: "epistola-document-preview-component", inputs: { value: "value", disabled: "disabled", label: "label" }, outputs: { valueChange: "valueChange" }, usesOnChanges: true, ngImport: i0, template: `
1639
+ <div class="epistola-preview-panel">
1640
+ <div class="preview-header">
1641
+ <span>{{ label || 'Document Preview' }}</span>
1642
+ <div class="preview-controls">
1643
+ <select
1644
+ *ngIf="sources.length > 1"
1645
+ class="preview-select"
1646
+ [value]="selectedIndex"
1647
+ (change)="onSourceChange($event)"
1648
+ >
1649
+ <option *ngFor="let source of sources; let i = index" [value]="i">
1650
+ {{ source.templateName }} ({{ source.activityId }})
1651
+ </option>
1652
+ </select>
1653
+ <button type="button" class="preview-refresh" [disabled]="loading || discovering" (click)="refresh()">
1654
+ <i class="mdi mdi-refresh mr-1"></i>
1655
+ {{ loading ? 'Generating...' : 'Refresh' }}
1656
+ </button>
1657
+ </div>
1658
+ </div>
1659
+ <div class="preview-body">
1660
+ <div *ngIf="discovering" class="preview-loading">
1661
+ Discovering documents...
1662
+ </div>
1663
+ <div *ngIf="loading && !discovering" class="preview-loading">
1664
+ Generating preview...
1665
+ </div>
1666
+ <div *ngIf="error && !loading && !discovering" class="preview-unavailable">
1667
+ <i class="mdi mdi-information-outline"></i>
1668
+ Preview is niet beschikbaar — niet alle gegevens zijn al ingevuld.
1669
+ </div>
1670
+ <object
1671
+ *ngIf="previewUrl && !loading && !discovering"
1672
+ [data]="previewUrl"
1673
+ type="application/pdf"
1674
+ class="preview-pdf"
1675
+ >
1676
+ PDF preview is not supported in this browser.
1677
+ </object>
1678
+ <div *ngIf="!previewUrl && !loading && !discovering && !error && sources.length === 0" class="preview-empty">
1679
+ No previewable documents found
1680
+ </div>
1681
+ </div>
1682
+ </div>
1683
+ `, isInline: true, styles: [".epistola-preview-panel{border:1px solid #dee2e6;border-radius:4px;background:#f8f9fa;display:flex;flex-direction:column}.preview-header{display:flex;justify-content:space-between;align-items:center;padding:.5rem 1rem;border-bottom:1px solid #dee2e6;font-weight:700;color:#495057;flex-wrap:wrap;gap:.5rem}.preview-controls{display:flex;align-items:center;gap:.5rem}.preview-select{border:1px solid #ced4da;border-radius:4px;padding:.25rem .5rem;font-size:.8rem;background:#fff;max-width:300px}.preview-refresh{background:none;border:1px solid #6c757d;border-radius:4px;color:#6c757d;padding:.25rem .75rem;font-size:.8rem;cursor:pointer;display:flex;align-items:center;white-space:nowrap}.preview-refresh:hover:not(:disabled){background:#e9ecef}.preview-refresh:disabled{opacity:.5;cursor:not-allowed}.preview-body{display:flex;flex-direction:column;min-height:500px}.preview-loading{padding:2rem;text-align:center;color:#6c757d;font-style:italic}.preview-unavailable{padding:1.5rem;text-align:center;color:#6c757d;font-style:italic}.preview-unavailable i{margin-right:.25rem}.preview-pdf{width:100%;flex:1;min-height:500px}.preview-empty{padding:2rem;text-align:center;color:#6c757d;font-style:italic}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1684
+ }
1685
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaDocumentPreviewComponent, decorators: [{
1686
+ type: Component,
1687
+ args: [{ standalone: true, imports: [CommonModule], selector: 'epistola-document-preview-component', changeDetection: ChangeDetectionStrategy.OnPush, template: `
1688
+ <div class="epistola-preview-panel">
1689
+ <div class="preview-header">
1690
+ <span>{{ label || 'Document Preview' }}</span>
1691
+ <div class="preview-controls">
1692
+ <select
1693
+ *ngIf="sources.length > 1"
1694
+ class="preview-select"
1695
+ [value]="selectedIndex"
1696
+ (change)="onSourceChange($event)"
1697
+ >
1698
+ <option *ngFor="let source of sources; let i = index" [value]="i">
1699
+ {{ source.templateName }} ({{ source.activityId }})
1700
+ </option>
1701
+ </select>
1702
+ <button type="button" class="preview-refresh" [disabled]="loading || discovering" (click)="refresh()">
1703
+ <i class="mdi mdi-refresh mr-1"></i>
1704
+ {{ loading ? 'Generating...' : 'Refresh' }}
1705
+ </button>
1706
+ </div>
1707
+ </div>
1708
+ <div class="preview-body">
1709
+ <div *ngIf="discovering" class="preview-loading">
1710
+ Discovering documents...
1711
+ </div>
1712
+ <div *ngIf="loading && !discovering" class="preview-loading">
1713
+ Generating preview...
1714
+ </div>
1715
+ <div *ngIf="error && !loading && !discovering" class="preview-unavailable">
1716
+ <i class="mdi mdi-information-outline"></i>
1717
+ Preview is niet beschikbaar — niet alle gegevens zijn al ingevuld.
1718
+ </div>
1719
+ <object
1720
+ *ngIf="previewUrl && !loading && !discovering"
1721
+ [data]="previewUrl"
1722
+ type="application/pdf"
1723
+ class="preview-pdf"
1724
+ >
1725
+ PDF preview is not supported in this browser.
1726
+ </object>
1727
+ <div *ngIf="!previewUrl && !loading && !discovering && !error && sources.length === 0" class="preview-empty">
1728
+ No previewable documents found
1729
+ </div>
1730
+ </div>
1731
+ </div>
1732
+ `, styles: [".epistola-preview-panel{border:1px solid #dee2e6;border-radius:4px;background:#f8f9fa;display:flex;flex-direction:column}.preview-header{display:flex;justify-content:space-between;align-items:center;padding:.5rem 1rem;border-bottom:1px solid #dee2e6;font-weight:700;color:#495057;flex-wrap:wrap;gap:.5rem}.preview-controls{display:flex;align-items:center;gap:.5rem}.preview-select{border:1px solid #ced4da;border-radius:4px;padding:.25rem .5rem;font-size:.8rem;background:#fff;max-width:300px}.preview-refresh{background:none;border:1px solid #6c757d;border-radius:4px;color:#6c757d;padding:.25rem .75rem;font-size:.8rem;cursor:pointer;display:flex;align-items:center;white-space:nowrap}.preview-refresh:hover:not(:disabled){background:#e9ecef}.preview-refresh:disabled{opacity:.5;cursor:not-allowed}.preview-body{display:flex;flex-direction:column;min-height:500px}.preview-loading{padding:2rem;text-align:center;color:#6c757d;font-style:italic}.preview-unavailable{padding:1.5rem;text-align:center;color:#6c757d;font-style:italic}.preview-unavailable i{margin-right:.25rem}.preview-pdf{width:100%;flex:1;min-height:500px}.preview-empty{padding:2rem;text-align:center;color:#6c757d;font-style:italic}\n"] }]
1733
+ }], ctorParameters: () => [{ type: EpistolaPluginService }, { type: i1.HttpClient }, { type: i4$1.DomSanitizer }, { type: i2.ConfigService }, { type: i3.FormIoStateService }, { type: i0.ChangeDetectorRef }], propDecorators: { value: [{
1734
+ type: Input
1735
+ }], valueChange: [{
1736
+ type: Output
1737
+ }], disabled: [{
1738
+ type: Input
1739
+ }], label: [{
1740
+ type: Input
1741
+ }] } });
1742
+
1105
1743
  class EpistolaPluginModule {
1106
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: EpistolaPluginModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
1107
- static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.17", ngImport: i0, type: EpistolaPluginModule, imports: [CommonModule,
1744
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaPluginModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
1745
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.20", ngImport: i0, type: EpistolaPluginModule, imports: [CommonModule,
1108
1746
  HttpClientModule,
1109
1747
  PluginTranslatePipeModule,
1110
1748
  FormModule,
@@ -1116,14 +1754,20 @@ class EpistolaPluginModule {
1116
1754
  DownloadDocumentConfigurationComponent,
1117
1755
  DataMappingTreeComponent,
1118
1756
  FieldTreeComponent,
1119
- EpistolaDownloadComponent], exports: [EpistolaConfigurationComponent,
1757
+ EpistolaDownloadComponent,
1758
+ EpistolaRetryFormComponent,
1759
+ EpistolaPreviewButtonComponent,
1760
+ EpistolaDocumentPreviewComponent], exports: [EpistolaConfigurationComponent,
1120
1761
  GenerateDocumentConfigurationComponent,
1121
1762
  CheckJobStatusConfigurationComponent,
1122
1763
  DownloadDocumentConfigurationComponent,
1123
1764
  DataMappingTreeComponent,
1124
1765
  FieldTreeComponent,
1125
- EpistolaDownloadComponent] });
1126
- static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: EpistolaPluginModule, providers: [
1766
+ EpistolaDownloadComponent,
1767
+ EpistolaRetryFormComponent,
1768
+ EpistolaPreviewButtonComponent,
1769
+ EpistolaDocumentPreviewComponent] });
1770
+ static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaPluginModule, providers: [
1127
1771
  EpistolaPluginService
1128
1772
  ], imports: [CommonModule,
1129
1773
  HttpClientModule,
@@ -1137,9 +1781,12 @@ class EpistolaPluginModule {
1137
1781
  DownloadDocumentConfigurationComponent,
1138
1782
  DataMappingTreeComponent,
1139
1783
  FieldTreeComponent,
1140
- EpistolaDownloadComponent] });
1784
+ EpistolaDownloadComponent,
1785
+ EpistolaRetryFormComponent,
1786
+ EpistolaPreviewButtonComponent,
1787
+ EpistolaDocumentPreviewComponent] });
1141
1788
  }
1142
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: EpistolaPluginModule, decorators: [{
1789
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaPluginModule, decorators: [{
1143
1790
  type: NgModule,
1144
1791
  args: [{
1145
1792
  imports: [
@@ -1155,7 +1802,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
1155
1802
  DownloadDocumentConfigurationComponent,
1156
1803
  DataMappingTreeComponent,
1157
1804
  FieldTreeComponent,
1158
- EpistolaDownloadComponent
1805
+ EpistolaDownloadComponent,
1806
+ EpistolaRetryFormComponent,
1807
+ EpistolaPreviewButtonComponent,
1808
+ EpistolaDocumentPreviewComponent
1159
1809
  ],
1160
1810
  exports: [
1161
1811
  EpistolaConfigurationComponent,
@@ -1164,7 +1814,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
1164
1814
  DownloadDocumentConfigurationComponent,
1165
1815
  DataMappingTreeComponent,
1166
1816
  FieldTreeComponent,
1167
- EpistolaDownloadComponent
1817
+ EpistolaDownloadComponent,
1818
+ EpistolaRetryFormComponent,
1819
+ EpistolaPreviewButtonComponent,
1820
+ EpistolaDocumentPreviewComponent
1168
1821
  ],
1169
1822
  providers: [
1170
1823
  EpistolaPluginService
@@ -1381,6 +2034,51 @@ function registerEpistolaDownloadComponent(injector) {
1381
2034
  }
1382
2035
  }
1383
2036
 
2037
+ const EPISTOLA_RETRY_FORM_OPTIONS = {
2038
+ type: 'epistola-retry-form',
2039
+ selector: 'epistola-retry-form-element',
2040
+ title: 'Epistola Retry Form',
2041
+ group: 'basic',
2042
+ icon: 'refresh',
2043
+ emptyValue: null,
2044
+ fieldOptions: ['sourceActivityId', 'label'], // sourceActivityId is optional (set via BPMN input parameter)
2045
+ };
2046
+ function registerEpistolaRetryFormComponent(injector) {
2047
+ if (!customElements.get(EPISTOLA_RETRY_FORM_OPTIONS.selector)) {
2048
+ registerCustomFormioComponent(EPISTOLA_RETRY_FORM_OPTIONS, EpistolaRetryFormComponent, injector);
2049
+ }
2050
+ }
2051
+
2052
+ const EPISTOLA_PREVIEW_BUTTON_OPTIONS = {
2053
+ type: 'epistola-preview-button',
2054
+ selector: 'epistola-preview-button-element',
2055
+ title: 'Epistola Preview',
2056
+ group: 'basic',
2057
+ icon: 'eye',
2058
+ emptyValue: null,
2059
+ fieldOptions: ['label'],
2060
+ };
2061
+ function registerEpistolaPreviewButtonComponent(injector) {
2062
+ if (!customElements.get(EPISTOLA_PREVIEW_BUTTON_OPTIONS.selector)) {
2063
+ registerCustomFormioComponent(EPISTOLA_PREVIEW_BUTTON_OPTIONS, EpistolaPreviewButtonComponent, injector);
2064
+ }
2065
+ }
2066
+
2067
+ const EPISTOLA_DOCUMENT_PREVIEW_OPTIONS = {
2068
+ type: 'epistola-document-preview',
2069
+ selector: 'epistola-document-preview-element',
2070
+ title: 'Epistola Document Preview',
2071
+ group: 'basic',
2072
+ icon: 'file-pdf-o',
2073
+ emptyValue: null,
2074
+ fieldOptions: ['label'],
2075
+ };
2076
+ function registerEpistolaDocumentPreviewComponent(injector) {
2077
+ if (!customElements.get(EPISTOLA_DOCUMENT_PREVIEW_OPTIONS.selector)) {
2078
+ registerCustomFormioComponent(EPISTOLA_DOCUMENT_PREVIEW_OPTIONS, EpistolaDocumentPreviewComponent, injector);
2079
+ }
2080
+ }
2081
+
1384
2082
  /*
1385
2083
  * Public API Surface of epistola plugin
1386
2084
  */
@@ -1389,5 +2087,5 @@ function registerEpistolaDownloadComponent(injector) {
1389
2087
  * Generated bundle index. Do not edit.
1390
2088
  */
1391
2089
 
1392
- export { CheckJobStatusConfigurationComponent, DataMappingTreeComponent, DownloadDocumentConfigurationComponent, EPISTOLA_DOWNLOAD_OPTIONS, EpistolaConfigurationComponent, EpistolaDownloadComponent, EpistolaPluginModule, EpistolaPluginService, FieldTreeComponent, GenerateDocumentConfigurationComponent, epistolaPluginSpecification, registerEpistolaDownloadComponent };
2090
+ export { CheckJobStatusConfigurationComponent, DataMappingTreeComponent, DownloadDocumentConfigurationComponent, EPISTOLA_DOCUMENT_PREVIEW_OPTIONS, EPISTOLA_DOWNLOAD_OPTIONS, EPISTOLA_PREVIEW_BUTTON_OPTIONS, EPISTOLA_RETRY_FORM_OPTIONS, EpistolaConfigurationComponent, EpistolaDocumentPreviewComponent, EpistolaDownloadComponent, EpistolaPluginModule, EpistolaPluginService, EpistolaPreviewButtonComponent, EpistolaRetryFormComponent, FieldTreeComponent, GenerateDocumentConfigurationComponent, epistolaPluginSpecification, registerEpistolaDocumentPreviewComponent, registerEpistolaDownloadComponent, registerEpistolaPreviewButtonComponent, registerEpistolaRetryFormComponent };
1393
2091
  //# sourceMappingURL=epistola.app-valtimo-plugin.mjs.map