@epistola.app/valtimo-plugin 0.3.0 → 0.3.2

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, ChangeDetectionStrategy, NgModule } from '@angular/core';
2
+ import { Injectable, EventEmitter, Output, Input, Component, ChangeDetectionStrategy, forwardRef, ENVIRONMENT_INITIALIZER, inject, Injector, 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,13 +10,26 @@ 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, debounceTime } from 'rxjs/operators';
14
- import * as i4 from '@angular/forms';
13
+ import { startWith, delay, takeUntil, filter, map, tap, switchMap, catchError, take as take$1, debounceTime } from 'rxjs/operators';
14
+ import * as i2$2 from '@angular/forms';
15
15
  import { FormsModule } from '@angular/forms';
16
- import * as i2$2 from '@valtimo/process-link';
16
+ import * as i2$3 from '@valtimo/process-link';
17
17
  import * as i7 from '@formio/angular';
18
18
  import { FormioModule } from '@formio/angular';
19
- import * as i4$1 from '@angular/platform-browser';
19
+ import * as i4 from '@angular/platform-browser';
20
+
21
+ function initialResource(empty) {
22
+ return { data: empty, loading: false, error: null };
23
+ }
24
+ function loadingResource(current) {
25
+ return { data: current, loading: true, error: null };
26
+ }
27
+ function successResource(data) {
28
+ return { data, loading: false, error: null };
29
+ }
30
+ function errorResource(current, error) {
31
+ return { data: current, loading: false, error };
32
+ }
20
33
 
21
34
  /**
22
35
  * Service for interacting with Epistola plugin API endpoints.
@@ -81,6 +94,12 @@ class EpistolaPluginService {
81
94
  }
82
95
  return this.http.get(`${this.apiEndpoint}/retry-form`, { params });
83
96
  }
97
+ /**
98
+ * Discover all previewable document sources for a given Valtimo document.
99
+ */
100
+ getPreviewSources(documentId) {
101
+ return this.http.get(`${this.apiEndpoint}/preview-sources`, { params: { documentId } });
102
+ }
84
103
  /**
85
104
  * Preview a document by dry-running the generate-document process link.
86
105
  * Returns the resolved data as a mock preview (Phase 1).
@@ -217,149 +236,203 @@ function countRequiredMapped(fields, mapping) {
217
236
  }
218
237
 
219
238
  /**
220
- * Recursive component that renders a single TemplateField node as part of a tree form.
221
- *
222
- * - SCALAR fields render as a label + input row with a 3-mode selector (browse / pv / expression)
223
- * - OBJECT fields render as a collapsible section with children indented inside
224
- * - ARRAY fields render as a collapsible section with source collection input and optional per-item field mappings
225
- *
226
- * Input modes:
227
- * - Browse (⊞): ValuePathSelector for doc:/case: paths
228
- * - PV (pv): Dropdown of discovered process variables (text fallback when none found)
229
- * - Expression (fx): Free-text input for manual expressions
239
+ * Reusable 3-mode input (browse / pv / expression) for value resolver expressions.
240
+ * Used by both ScalarFieldComponent and ArrayFieldComponent for source mapping.
230
241
  */
231
- class FieldTreeComponent {
232
- field;
233
- value = undefined;
234
- pluginId;
242
+ class ValueInputComponent {
243
+ cdr;
244
+ name = '';
245
+ value = '';
246
+ pluginId = '';
235
247
  caseDefinitionKey = null;
236
248
  processVariables = [];
237
249
  disabled = false;
250
+ placeholder = 'e.g. pv:variableName or doc:path.to.field';
238
251
  valueChange = new EventEmitter();
239
252
  ValuePathSelectorPrefix = ValuePathSelectorPrefix;
240
253
  inputMode = 'browse';
241
- expanded = false;
242
- /** For ARRAY fields: whether to show per-item field mapping */
243
- arrayPerFieldMode = false;
244
- /** Completeness badge for collapsed object/array sections */
245
- mappedCount = 0;
246
- totalRequired = 0;
254
+ selectedPv = '';
255
+ browseDefault = '';
256
+ constructor(cdr) {
257
+ this.cdr = cdr;
258
+ }
247
259
  ngOnChanges(changes) {
248
- if (changes['value'] || changes['field']) {
249
- this.updateCompleteness();
250
- // Auto-expand if there are unmapped required children
251
- if (!this.expanded && this.totalRequired > 0 && this.mappedCount < this.totalRequired) {
252
- this.expanded = true;
253
- }
260
+ if (changes['value']) {
261
+ this.inputMode = this.detectInputMode(this.value);
262
+ this.browseDefault = normalizeToDots(this.value);
263
+ this.selectedPv = extractPvName(this.value);
254
264
  }
255
- // Detect input mode from prefill value
256
- if (changes['value'] && this.value != null) {
257
- if (this.field?.fieldType === 'SCALAR') {
258
- this.inputMode = this.detectInputMode(this.value);
259
- }
260
- else if (this.field?.fieldType === 'ARRAY') {
261
- const sourceValue = this.getSourceValue();
262
- if (sourceValue) {
263
- this.inputMode = this.detectInputMode(sourceValue);
264
- }
265
- // Detect per-field mode from value shape
266
- if (typeof this.value === 'object' && this.value !== null && '_source' in this.value) {
267
- this.arrayPerFieldMode = true;
268
- }
269
- }
265
+ if (changes['processVariables']) {
266
+ this.cdr.markForCheck();
270
267
  }
271
268
  }
272
- toggleExpanded() {
273
- this.expanded = !this.expanded;
274
- }
275
269
  setInputMode(mode) {
276
270
  this.inputMode = mode;
277
271
  }
278
- /** Handle value change from ValuePathSelector (browse mode) */
279
272
  onBrowseValueChange(newValue) {
280
- this.emitScalarValue(newValue);
273
+ this.valueChange.emit(normalizeToDots(newValue));
281
274
  }
282
- /** Handle value change from PV dropdown */
283
275
  onPvChange(newValue) {
284
- if (newValue) {
285
- this.emitScalarValue('pv:' + newValue);
286
- }
287
- else {
288
- this.emitScalarValue('');
289
- }
276
+ this.selectedPv = newValue;
277
+ this.valueChange.emit(newValue ? 'pv:' + newValue : '');
290
278
  }
291
- /** Handle value change from text input (expression mode) */
292
279
  onExpressionValueChange(newValue) {
293
- this.emitScalarValue(newValue);
280
+ this.valueChange.emit(newValue);
294
281
  }
295
- /** Handle child value change for OBJECT fields */
296
- onChildChange(childName, childValue) {
297
- const current = (typeof this.value === 'object' && this.value !== null) ? { ...this.value } : {};
298
- if (childValue === undefined || childValue === null || childValue === '') {
299
- delete current[childName];
300
- }
301
- else {
302
- current[childName] = childValue;
303
- }
304
- this.valueChange.emit(Object.keys(current).length > 0 ? current : undefined);
282
+ detectInputMode(value) {
283
+ if (!value)
284
+ return 'browse';
285
+ if (value.startsWith('doc:') || value.startsWith('case:'))
286
+ return 'browse';
287
+ if (value.startsWith('pv:'))
288
+ return 'pv';
289
+ if (value.length > 0)
290
+ return 'expression';
291
+ return 'browse';
305
292
  }
306
- /** Get the current value for a child field within an OBJECT */
307
- getChildValue(childName) {
308
- if (typeof this.value === 'object' && this.value !== null) {
309
- return this.value[childName];
310
- }
311
- return undefined;
293
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: ValueInputComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
294
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: ValueInputComponent, isStandalone: true, selector: "epistola-value-input", inputs: { name: "name", value: "value", pluginId: "pluginId", caseDefinitionKey: "caseDefinitionKey", processVariables: "processVariables", disabled: "disabled", placeholder: "placeholder" }, outputs: { valueChange: "valueChange" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"value-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=\"input-control\" *ngIf=\"inputMode === 'browse'\">\n <valtimo-value-path-selector\n [name]=\"name\"\n [caseDefinitionKey]=\"caseDefinitionKey\"\n [prefixes]=\"[ValuePathSelectorPrefix.DOC, ValuePathSelectorPrefix.CASE]\"\n [notation]=\"'dots'\"\n [disabled]=\"disabled\"\n [defaultValue]=\"browseDefault\"\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=\"input-control\" *ngIf=\"inputMode === 'pv'\">\n <select\n *ngIf=\"processVariables.length > 0; else pvFallback\"\n class=\"pv-select\"\n [disabled]=\"disabled\"\n [(ngModel)]=\"selectedPv\"\n (ngModelChange)=\"onPvChange($event)\"\n >\n <option value=\"\">{{ 'pvPlaceholder' | pluginTranslate: pluginId | async }}</option>\n <option *ngFor=\"let pv of processVariables\" [value]=\"pv\">{{ pv }}</option>\n </select>\n <ng-template #pvFallback>\n <v-input\n [name]=\"'pvfb_' + name\"\n [defaultValue]=\"selectedPv\"\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=\"input-control\" *ngIf=\"inputMode === 'expression'\">\n <v-input\n [name]=\"'fx_' + name\"\n [defaultValue]=\"value\"\n [disabled]=\"disabled\"\n [placeholder]=\"placeholder\"\n (valueChange)=\"onExpressionValueChange($event)\"\n ></v-input>\n </div>\n</div>\n", styles: [".value-input{display:flex;align-items:flex-start;gap:.5rem;min-width:0}.value-input ::ng-deep [data-test-id=valuePathSelectorToggle]{display:none!important}.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}\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: i2$2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2$2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2$2.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i2$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$2.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: 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 });
295
+ }
296
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: ValueInputComponent, decorators: [{
297
+ type: Component,
298
+ args: [{ selector: 'epistola-value-input', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [
299
+ CommonModule,
300
+ FormsModule,
301
+ PluginTranslatePipeModule,
302
+ InputModule,
303
+ ValuePathSelectorComponent
304
+ ], template: "<div class=\"value-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=\"input-control\" *ngIf=\"inputMode === 'browse'\">\n <valtimo-value-path-selector\n [name]=\"name\"\n [caseDefinitionKey]=\"caseDefinitionKey\"\n [prefixes]=\"[ValuePathSelectorPrefix.DOC, ValuePathSelectorPrefix.CASE]\"\n [notation]=\"'dots'\"\n [disabled]=\"disabled\"\n [defaultValue]=\"browseDefault\"\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=\"input-control\" *ngIf=\"inputMode === 'pv'\">\n <select\n *ngIf=\"processVariables.length > 0; else pvFallback\"\n class=\"pv-select\"\n [disabled]=\"disabled\"\n [(ngModel)]=\"selectedPv\"\n (ngModelChange)=\"onPvChange($event)\"\n >\n <option value=\"\">{{ 'pvPlaceholder' | pluginTranslate: pluginId | async }}</option>\n <option *ngFor=\"let pv of processVariables\" [value]=\"pv\">{{ pv }}</option>\n </select>\n <ng-template #pvFallback>\n <v-input\n [name]=\"'pvfb_' + name\"\n [defaultValue]=\"selectedPv\"\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=\"input-control\" *ngIf=\"inputMode === 'expression'\">\n <v-input\n [name]=\"'fx_' + name\"\n [defaultValue]=\"value\"\n [disabled]=\"disabled\"\n [placeholder]=\"placeholder\"\n (valueChange)=\"onExpressionValueChange($event)\"\n ></v-input>\n </div>\n</div>\n", styles: [".value-input{display:flex;align-items:flex-start;gap:.5rem;min-width:0}.value-input ::ng-deep [data-test-id=valuePathSelectorToggle]{display:none!important}.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}\n"] }]
305
+ }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }], propDecorators: { name: [{
306
+ type: Input
307
+ }], value: [{
308
+ type: Input
309
+ }], pluginId: [{
310
+ type: Input
311
+ }], caseDefinitionKey: [{
312
+ type: Input
313
+ }], processVariables: [{
314
+ type: Input
315
+ }], disabled: [{
316
+ type: Input
317
+ }], placeholder: [{
318
+ type: Input
319
+ }], valueChange: [{
320
+ type: Output
321
+ }] } });
322
+ /** Convert slash-notation paths (e.g. doc:/a/b) to dot notation (doc:a.b). */
323
+ function normalizeToDots(value) {
324
+ if (typeof value !== 'string')
325
+ return value;
326
+ const colonIndex = value.indexOf(':');
327
+ if (colonIndex < 0)
328
+ return value;
329
+ const prefix = value.substring(0, colonIndex);
330
+ const path = value.substring(colonIndex + 1);
331
+ if (!path.includes('/'))
332
+ return value;
333
+ const normalized = path.split('/').filter(p => p.length > 0).join('.');
334
+ return `${prefix}:${normalized}`;
335
+ }
336
+ function extractPvName(value) {
337
+ if (typeof value === 'string' && value.startsWith('pv:')) {
338
+ return value.substring(3);
312
339
  }
313
- /** Get the string value for display (for SCALAR leaf inputs and direct array mode) */
314
- getStringValue() {
340
+ return '';
341
+ }
342
+
343
+ class ScalarFieldComponent {
344
+ field;
345
+ value = undefined;
346
+ pluginId;
347
+ caseDefinitionKey = null;
348
+ processVariables = [];
349
+ disabled = false;
350
+ valueChange = new EventEmitter();
351
+ get stringValue() {
315
352
  return typeof this.value === 'string' ? this.value : '';
316
353
  }
317
- /** Get the PV name from a pv: prefixed value (for PV dropdown default selection) */
318
- getPvName() {
319
- const str = this.getStringValue();
320
- return str.startsWith('pv:') ? str.substring(3) : '';
354
+ onValueChange(newValue) {
355
+ this.valueChange.emit(newValue || undefined);
356
+ }
357
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: ScalarFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
358
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: ScalarFieldComponent, isStandalone: true, selector: "epistola-scalar-field", inputs: { field: "field", value: "value", pluginId: "pluginId", caseDefinitionKey: "caseDefinitionKey", processVariables: "processVariables", disabled: "disabled" }, outputs: { valueChange: "valueChange" }, ngImport: i0, template: "<div class=\"field-row\" [class.field-required-unmapped]=\"field.required && !stringValue\">\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 <epistola-value-input\n [name]=\"'field_' + field.path\"\n [value]=\"stringValue\"\n [pluginId]=\"pluginId\"\n [caseDefinitionKey]=\"caseDefinitionKey\"\n [processVariables]=\"processVariables\"\n [disabled]=\"disabled\"\n (valueChange)=\"onValueChange($event)\"\n ></epistola-value-input>\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}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: PluginTranslatePipeModule }, { kind: "component", type: ValueInputComponent, selector: "epistola-value-input", inputs: ["name", "value", "pluginId", "caseDefinitionKey", "processVariables", "disabled", "placeholder"], outputs: ["valueChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
359
+ }
360
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: ScalarFieldComponent, decorators: [{
361
+ type: Component,
362
+ args: [{ selector: 'epistola-scalar-field', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, PluginTranslatePipeModule, ValueInputComponent], template: "<div class=\"field-row\" [class.field-required-unmapped]=\"field.required && !stringValue\">\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 <epistola-value-input\n [name]=\"'field_' + field.path\"\n [value]=\"stringValue\"\n [pluginId]=\"pluginId\"\n [caseDefinitionKey]=\"caseDefinitionKey\"\n [processVariables]=\"processVariables\"\n [disabled]=\"disabled\"\n (valueChange)=\"onValueChange($event)\"\n ></epistola-value-input>\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}\n"] }]
363
+ }], propDecorators: { field: [{
364
+ type: Input
365
+ }], value: [{
366
+ type: Input
367
+ }], pluginId: [{
368
+ type: Input
369
+ }], caseDefinitionKey: [{
370
+ type: Input
371
+ }], processVariables: [{
372
+ type: Input
373
+ }], disabled: [{
374
+ type: Input
375
+ }], valueChange: [{
376
+ type: Output
377
+ }] } });
378
+
379
+ class ArrayFieldComponent {
380
+ field;
381
+ value = undefined;
382
+ pluginId;
383
+ caseDefinitionKey = null;
384
+ processVariables = [];
385
+ disabled = false;
386
+ valueChange = new EventEmitter();
387
+ expanded = false;
388
+ arrayPerFieldMode = false;
389
+ mappedCount = 0;
390
+ totalRequired = 0;
391
+ ngOnChanges(changes) {
392
+ if (changes['value'] || changes['field']) {
393
+ this.updateCompleteness();
394
+ if (!this.expanded && this.totalRequired > 0 && this.mappedCount < this.totalRequired) {
395
+ this.expanded = true;
396
+ }
397
+ // Detect per-field mode from value shape
398
+ if (changes['value'] && typeof this.value === 'object' && this.value !== null && '_source' in this.value) {
399
+ this.arrayPerFieldMode = true;
400
+ }
401
+ }
402
+ }
403
+ toggleExpanded() {
404
+ this.expanded = !this.expanded;
321
405
  }
322
- // --- ARRAY-specific methods ---
323
- /** Get the source collection value (works for both direct string and _source format) */
324
406
  getSourceValue() {
325
407
  if (typeof this.value === 'string') {
326
- return this.value;
408
+ return normalizeToDots(this.value);
327
409
  }
328
410
  if (typeof this.value === 'object' && this.value !== null && '_source' in this.value) {
329
- return this.value['_source'] || '';
411
+ return normalizeToDots(this.value['_source'] || '');
330
412
  }
331
413
  return '';
332
414
  }
333
- /** Get the PV name from the source value */
334
- getSourcePvName() {
335
- const source = this.getSourceValue();
336
- return source.startsWith('pv:') ? source.substring(3) : '';
415
+ onSourceValueChange(newValue) {
416
+ if (this.arrayPerFieldMode) {
417
+ const current = (typeof this.value === 'object' && this.value !== null) ? { ...this.value } : {};
418
+ current['_source'] = newValue || '';
419
+ this.valueChange.emit(current);
420
+ }
421
+ else {
422
+ this.valueChange.emit(newValue || undefined);
423
+ }
337
424
  }
338
425
  toggleArrayPerFieldMode() {
339
426
  this.arrayPerFieldMode = !this.arrayPerFieldMode;
340
427
  if (this.arrayPerFieldMode) {
341
- // Switch from direct to per-field: convert string value to _source object
342
428
  const currentSource = this.getSourceValue();
343
- const obj = { _source: currentSource };
344
- this.valueChange.emit(obj);
429
+ this.valueChange.emit({ _source: currentSource });
345
430
  }
346
431
  else {
347
- // Switch from per-field to direct: extract _source as string value
348
432
  const source = this.getSourceValue();
349
433
  this.valueChange.emit(source || undefined);
350
434
  }
351
435
  }
352
- /** Handle source collection value change (used in ARRAY mode) */
353
- onSourceBrowseChange(newValue) {
354
- this.updateSourceValue(newValue);
355
- }
356
- onSourcePvChange(newValue) {
357
- this.updateSourceValue(newValue ? 'pv:' + newValue : '');
358
- }
359
- onSourceExpressionChange(newValue) {
360
- this.updateSourceValue(newValue);
361
- }
362
- /** Handle per-item field mapping change */
363
436
  onItemFieldChange(childName, sourceFieldName) {
364
437
  const current = (typeof this.value === 'object' && this.value !== null) ? { ...this.value } : { _source: '' };
365
438
  if (sourceFieldName && sourceFieldName.trim().length > 0) {
@@ -370,59 +443,30 @@ class FieldTreeComponent {
370
443
  }
371
444
  this.valueChange.emit(current);
372
445
  }
373
- /** Get the current source field name mapping for a child */
374
446
  getItemFieldValue(childName) {
375
447
  if (typeof this.value === 'object' && this.value !== null) {
376
448
  return this.value[childName] || '';
377
449
  }
378
450
  return '';
379
451
  }
380
- /** Check if the array has any children that can be mapped per-item */
381
452
  hasArrayChildren() {
382
453
  return !!(this.field?.children && this.field.children.length > 0);
383
454
  }
384
- emitScalarValue(newValue) {
385
- this.valueChange.emit(newValue || undefined);
386
- }
387
- updateSourceValue(newValue) {
388
- if (this.arrayPerFieldMode) {
389
- const current = (typeof this.value === 'object' && this.value !== null) ? { ...this.value } : {};
390
- current['_source'] = newValue || '';
391
- this.valueChange.emit(current);
392
- }
393
- else {
394
- this.valueChange.emit(newValue || undefined);
395
- }
396
- }
397
455
  updateCompleteness() {
398
- if (this.field?.fieldType === 'OBJECT' && this.field.children) {
399
- const stats = countRequiredMapped(this.field.children, this.value || {});
400
- this.mappedCount = stats.mapped;
401
- this.totalRequired = stats.total;
402
- }
403
- else if (this.field?.fieldType === 'ARRAY') {
404
- this.updateArrayCompleteness();
405
- }
406
- }
407
- updateArrayCompleteness() {
408
456
  if (!this.field?.children || this.field.children.length === 0) {
409
- // No children: just check if source is set
410
457
  this.totalRequired = this.field?.required ? 1 : 0;
411
458
  this.mappedCount = this.getSourceValue() ? (this.field?.required ? 1 : 0) : 0;
412
459
  return;
413
460
  }
414
461
  if (typeof this.value === 'object' && this.value !== null && '_source' in this.value) {
415
- // Per-field mode: count source + required children
416
462
  let total = 0;
417
463
  let mapped = 0;
418
- // Source counts as 1 required
419
464
  if (this.field?.required) {
420
465
  total++;
421
466
  if (this.value['_source'] && this.value['_source'].trim().length > 0) {
422
467
  mapped++;
423
468
  }
424
469
  }
425
- // Count required children
426
470
  for (const child of this.field.children) {
427
471
  if (child.required) {
428
472
  total++;
@@ -436,37 +480,87 @@ class FieldTreeComponent {
436
480
  this.totalRequired = total;
437
481
  }
438
482
  else {
439
- // Direct mode: just check if source is set
440
483
  this.totalRequired = this.field?.required ? 1 : 0;
441
484
  this.mappedCount = this.getSourceValue() ? (this.field?.required ? 1 : 0) : 0;
442
485
  }
443
486
  }
444
- detectInputMode(value) {
445
- if (typeof value !== 'string')
446
- return 'browse';
447
- if (value.startsWith('doc:') || value.startsWith('case:'))
448
- return 'browse';
449
- if (value.startsWith('pv:'))
450
- return 'pv';
451
- if (value.length > 0)
452
- return 'expression';
453
- return 'browse';
487
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: ArrayFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
488
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: ArrayFieldComponent, isStandalone: true, selector: "epistola-array-field", inputs: { field: "field", value: "value", pluginId: "pluginId", caseDefinitionKey: "caseDefinitionKey", processVariables: "processVariables", disabled: "disabled" }, outputs: { valueChange: "valueChange" }, usesOnChanges: true, ngImport: i0, template: "<div 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 <epistola-value-input\n [name]=\"'field_' + field.path\"\n [value]=\"getSourceValue()\"\n [pluginId]=\"pluginId\"\n [caseDefinitionKey]=\"caseDefinitionKey\"\n [processVariables]=\"processVariables\"\n [disabled]=\"disabled\"\n (valueChange)=\"onSourceValueChange($event)\"\n ></epistola-value-input>\n </div>\n\n <!-- Per-item field mapping toggle -->\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-array{margin:.25rem 0}.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-array-header:hover{background:#f4f4f4}.field-array-header.field-required-unmapped{border-left-color:#dc3545;background-color:#fff5f5}.field-array-header .expand-icon{flex:0 0 1rem;font-size:.75rem;color:#525252}.field-array-header .field-name{font-weight:500}.field-array-header .field-meta{font-size:.8125rem;color:#6c757d}.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-array-header .mapped-indicator{margin-left:auto;color:#198754;font-weight:600}.field-array-content{padding-left:1.25rem;border-left:1px solid #e0e0e0;margin-left:.5rem}.field-row{display:flex;align-items:flex-start;gap:.75rem;padding:.5rem 0}.field-label{flex:0 0 200px;min-width:140px;padding-top:.5rem;word-break:break-word}.field-label .field-name{font-weight:500}.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: "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: "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: ValueInputComponent, selector: "epistola-value-input", inputs: ["name", "value", "pluginId", "caseDefinitionKey", "processVariables", "disabled", "placeholder"], outputs: ["valueChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
489
+ }
490
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: ArrayFieldComponent, decorators: [{
491
+ type: Component,
492
+ args: [{ selector: 'epistola-array-field', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, PluginTranslatePipeModule, InputModule, ValueInputComponent], template: "<div 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 <epistola-value-input\n [name]=\"'field_' + field.path\"\n [value]=\"getSourceValue()\"\n [pluginId]=\"pluginId\"\n [caseDefinitionKey]=\"caseDefinitionKey\"\n [processVariables]=\"processVariables\"\n [disabled]=\"disabled\"\n (valueChange)=\"onSourceValueChange($event)\"\n ></epistola-value-input>\n </div>\n\n <!-- Per-item field mapping toggle -->\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-array{margin:.25rem 0}.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-array-header:hover{background:#f4f4f4}.field-array-header.field-required-unmapped{border-left-color:#dc3545;background-color:#fff5f5}.field-array-header .expand-icon{flex:0 0 1rem;font-size:.75rem;color:#525252}.field-array-header .field-name{font-weight:500}.field-array-header .field-meta{font-size:.8125rem;color:#6c757d}.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-array-header .mapped-indicator{margin-left:auto;color:#198754;font-weight:600}.field-array-content{padding-left:1.25rem;border-left:1px solid #e0e0e0;margin-left:.5rem}.field-row{display:flex;align-items:flex-start;gap:.75rem;padding:.5rem 0}.field-label{flex:0 0 200px;min-width:140px;padding-top:.5rem;word-break:break-word}.field-label .field-name{font-weight:500}.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"] }]
493
+ }], propDecorators: { field: [{
494
+ type: Input
495
+ }], value: [{
496
+ type: Input
497
+ }], pluginId: [{
498
+ type: Input
499
+ }], caseDefinitionKey: [{
500
+ type: Input
501
+ }], processVariables: [{
502
+ type: Input
503
+ }], disabled: [{
504
+ type: Input
505
+ }], valueChange: [{
506
+ type: Output
507
+ }] } });
508
+
509
+ /**
510
+ * Recursive field tree component.
511
+ * Dispatches SCALAR and ARRAY to dedicated sub-components.
512
+ * Handles OBJECT inline to avoid circular import issues (OBJECT children recurse back to this component).
513
+ * Uses forwardRef(() => FieldTreeComponent) in imports to allow self-referencing in the template.
514
+ */
515
+ class FieldTreeComponent {
516
+ field;
517
+ value = undefined;
518
+ pluginId;
519
+ caseDefinitionKey = null;
520
+ processVariables = [];
521
+ disabled = false;
522
+ valueChange = new EventEmitter();
523
+ // OBJECT-specific state
524
+ expanded = false;
525
+ mappedCount = 0;
526
+ totalRequired = 0;
527
+ ngOnChanges(changes) {
528
+ if (this.field?.fieldType === 'OBJECT' && (changes['value'] || changes['field'])) {
529
+ if (this.field.children) {
530
+ const stats = countRequiredMapped(this.field.children, this.value || {});
531
+ this.mappedCount = stats.mapped;
532
+ this.totalRequired = stats.total;
533
+ }
534
+ if (!this.expanded && this.totalRequired > 0 && this.mappedCount < this.totalRequired) {
535
+ this.expanded = true;
536
+ }
537
+ }
454
538
  }
455
- isResolvableValue(value) {
456
- return value.startsWith('doc:') || value.startsWith('case:') || value.startsWith('pv:') || value.startsWith('template:');
539
+ toggleExpanded() {
540
+ this.expanded = !this.expanded;
541
+ }
542
+ onChildChange(childName, childValue) {
543
+ const current = (typeof this.value === 'object' && this.value !== null) ? { ...this.value } : {};
544
+ if (childValue === undefined || childValue === null || childValue === '') {
545
+ delete current[childName];
546
+ }
547
+ else {
548
+ current[childName] = childValue;
549
+ }
550
+ this.valueChange.emit(Object.keys(current).length > 0 ? current : undefined);
551
+ }
552
+ getChildValue(childName) {
553
+ if (typeof this.value === 'object' && this.value !== null) {
554
+ return this.value[childName];
555
+ }
556
+ return undefined;
457
557
  }
458
558
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: FieldTreeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
459
- 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 });
559
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.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: "@switch (field.fieldType) {\n @case ('SCALAR') {\n <epistola-scalar-field\n [field]=\"field\"\n [value]=\"value\"\n [pluginId]=\"pluginId\"\n [caseDefinitionKey]=\"caseDefinitionKey\"\n [processVariables]=\"processVariables\"\n [disabled]=\"disabled\"\n (valueChange)=\"valueChange.emit($event)\"\n ></epistola-scalar-field>\n }\n @case ('OBJECT') {\n <div 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 @case ('ARRAY') {\n <epistola-array-field\n [field]=\"field\"\n [value]=\"value\"\n [pluginId]=\"pluginId\"\n [caseDefinitionKey]=\"caseDefinitionKey\"\n [processVariables]=\"processVariables\"\n [disabled]=\"disabled\"\n (valueChange)=\"valueChange.emit($event)\"\n ></epistola-array-field>\n }\n}\n", styles: [".field-object{margin:.25rem 0}.field-object-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{background:#f4f4f4}.field-object-header.field-required-unmapped{border-left-color:#dc3545;background-color:#fff5f5}.field-object-header .expand-icon{flex:0 0 1rem;font-size:.75rem;color:#525252}.field-object-header .field-name{font-weight:500}.field-object-header .field-meta{font-size:.8125rem;color:#6c757d}.field-object-header .completeness-badge{margin-left:auto;font-size:.75rem;padding:.125rem .5rem;border-radius:10px;background:#e0e0e0;color:#525252;font-weight:500}.field-object-children{padding-left:1.25rem;border-left:1px solid #e0e0e0;margin-left:.5rem}\n"], dependencies: [{ kind: "component", type: i0.forwardRef(() => FieldTreeComponent), selector: "epistola-field-tree", inputs: ["field", "value", "pluginId", "caseDefinitionKey", "processVariables", "disabled"], outputs: ["valueChange"] }, { kind: "ngmodule", type: i0.forwardRef(() => CommonModule) }, { kind: "directive", type: i0.forwardRef(() => i1$1.NgForOf), selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i0.forwardRef(() => i1$1.NgIf), selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: i0.forwardRef(() => PluginTranslatePipeModule) }, { kind: "component", type: i0.forwardRef(() => ScalarFieldComponent), selector: "epistola-scalar-field", inputs: ["field", "value", "pluginId", "caseDefinitionKey", "processVariables", "disabled"], outputs: ["valueChange"] }, { kind: "component", type: i0.forwardRef(() => ArrayFieldComponent), selector: "epistola-array-field", inputs: ["field", "value", "pluginId", "caseDefinitionKey", "processVariables", "disabled"], outputs: ["valueChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
460
560
  }
461
561
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: FieldTreeComponent, decorators: [{
462
562
  type: Component,
463
- args: [{ selector: 'epistola-field-tree', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [
464
- CommonModule,
465
- FormsModule,
466
- PluginTranslatePipeModule,
467
- InputModule,
468
- ValuePathSelectorComponent
469
- ], 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"] }]
563
+ args: [{ selector: 'epistola-field-tree', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, PluginTranslatePipeModule, ScalarFieldComponent, ArrayFieldComponent, forwardRef(() => FieldTreeComponent)], template: "@switch (field.fieldType) {\n @case ('SCALAR') {\n <epistola-scalar-field\n [field]=\"field\"\n [value]=\"value\"\n [pluginId]=\"pluginId\"\n [caseDefinitionKey]=\"caseDefinitionKey\"\n [processVariables]=\"processVariables\"\n [disabled]=\"disabled\"\n (valueChange)=\"valueChange.emit($event)\"\n ></epistola-scalar-field>\n }\n @case ('OBJECT') {\n <div 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 @case ('ARRAY') {\n <epistola-array-field\n [field]=\"field\"\n [value]=\"value\"\n [pluginId]=\"pluginId\"\n [caseDefinitionKey]=\"caseDefinitionKey\"\n [processVariables]=\"processVariables\"\n [disabled]=\"disabled\"\n (valueChange)=\"valueChange.emit($event)\"\n ></epistola-array-field>\n }\n}\n", styles: [".field-object{margin:.25rem 0}.field-object-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{background:#f4f4f4}.field-object-header.field-required-unmapped{border-left-color:#dc3545;background-color:#fff5f5}.field-object-header .expand-icon{flex:0 0 1rem;font-size:.75rem;color:#525252}.field-object-header .field-name{font-weight:500}.field-object-header .field-meta{font-size:.8125rem;color:#6c757d}.field-object-header .completeness-badge{margin-left:auto;font-size:.75rem;padding:.125rem .5rem;border-radius:10px;background:#e0e0e0;color:#525252;font-weight:500}.field-object-children{padding-left:1.25rem;border-left:1px solid #e0e0e0;margin-left:.5rem}\n"] }]
470
564
  }], propDecorators: { field: [{
471
565
  type: Input
472
566
  }], value: [{
@@ -489,35 +583,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
489
583
  */
490
584
  class DataMappingTreeComponent {
491
585
  pluginId;
492
- templateFields$;
493
- prefillMapping$;
494
- disabled$;
586
+ templateFields = [];
587
+ prefillMapping = {};
588
+ disabled = false;
495
589
  caseDefinitionKey = null;
496
590
  processVariables = [];
497
591
  mappingChange = new EventEmitter();
498
592
  requiredFieldsStatus = new EventEmitter();
499
- templateFields = [];
500
593
  mapping = {};
501
- disabled = false;
502
- destroy$ = new Subject();
503
- ngOnInit() {
504
- this.templateFields$.pipe(takeUntil(this.destroy$)).subscribe(fields => {
505
- this.templateFields = fields;
506
- this.emitRequiredFieldsStatus();
507
- });
508
- this.prefillMapping$.pipe(takeUntil(this.destroy$)).subscribe(mapping => {
594
+ ngOnChanges(changes) {
595
+ if (changes['prefillMapping']) {
596
+ const mapping = this.prefillMapping;
509
597
  if (mapping && Object.keys(mapping).length > 0) {
510
598
  this.mapping = { ...mapping };
511
599
  }
600
+ }
601
+ if (changes['templateFields'] || changes['prefillMapping']) {
512
602
  this.emitRequiredFieldsStatus();
513
- });
514
- this.disabled$.pipe(takeUntil(this.destroy$)).subscribe(disabled => {
515
- this.disabled = disabled;
516
- });
517
- }
518
- ngOnDestroy() {
519
- this.destroy$.next();
520
- this.destroy$.complete();
603
+ }
521
604
  }
522
605
  onFieldValueChange(fieldName, value) {
523
606
  if (value === undefined || value === null || value === '') {
@@ -538,7 +621,7 @@ class DataMappingTreeComponent {
538
621
  this.requiredFieldsStatus.emit(stats);
539
622
  }
540
623
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: DataMappingTreeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
541
- 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 });
624
+ 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" }, usesOnChanges: true, 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 });
542
625
  }
543
626
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: DataMappingTreeComponent, decorators: [{
544
627
  type: Component,
@@ -549,11 +632,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
549
632
  ], 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"] }]
550
633
  }], propDecorators: { pluginId: [{
551
634
  type: Input
552
- }], templateFields$: [{
635
+ }], templateFields: [{
553
636
  type: Input
554
- }], prefillMapping$: [{
637
+ }], prefillMapping: [{
555
638
  type: Input
556
- }], disabled$: [{
639
+ }], disabled: [{
557
640
  type: Input
558
641
  }], caseDefinitionKey: [{
559
642
  type: Input
@@ -569,53 +652,31 @@ class GenerateDocumentConfigurationComponent {
569
652
  epistolaPluginService;
570
653
  processLinkStateService;
571
654
  cdr;
572
- // Required inputs from FunctionConfigurationComponent
573
655
  save$;
574
656
  disabled$;
575
657
  pluginId;
576
658
  prefillConfiguration$;
577
- // Optional inputs from FunctionConfigurationComponent
578
659
  selectedPluginConfigurationData$;
579
660
  context$;
580
661
  valid = new EventEmitter();
581
662
  configuration = new EventEmitter();
582
- // Template options loaded from API
583
- templateOptions$ = new BehaviorSubject([]);
584
- templatesLoading$ = new BehaviorSubject(false);
585
- templatesError$ = new BehaviorSubject(null);
586
- // Variant options loaded based on selected template
587
- variantOptions$ = new BehaviorSubject([]);
588
- variantsLoading$ = new BehaviorSubject(false);
589
- variantsError$ = new BehaviorSubject(null);
590
- // Environment options loaded from API
591
- environmentOptions$ = new BehaviorSubject([]);
592
- environmentsLoading$ = new BehaviorSubject(false);
593
- environmentsError$ = new BehaviorSubject(null);
594
- // Template fields for data mapping
595
- templateFields$ = new BehaviorSubject([]);
596
- templateFieldsLoading$ = new BehaviorSubject(false);
597
- templateFieldsError$ = new BehaviorSubject(null);
598
- // Current data mapping (nested structure mirroring template schema)
663
+ templates$ = new BehaviorSubject(initialResource([]));
664
+ variants$ = new BehaviorSubject(initialResource([]));
665
+ environments$ = new BehaviorSubject(initialResource([]));
666
+ templateFields$ = new BehaviorSubject(initialResource([]));
599
667
  dataMapping$ = new BehaviorSubject({});
600
- // Prefill data mapping observable for the tree
601
- prefillDataMapping$;
602
668
  outputFormatOptions = [
603
669
  { id: 'PDF', text: 'PDF' },
604
670
  { id: 'HTML', text: 'HTML' }
605
671
  ];
606
- // Show data mapping builder only when template is selected
607
672
  selectedTemplateId$ = new BehaviorSubject('');
608
673
  selectedVariantId$ = new BehaviorSubject('');
609
- // Variant selection mode: 'explicit' (dropdown) or 'attributes' (key-value pairs)
610
674
  variantSelectionMode = 'explicit';
611
- // Variant attributes for attribute-based selection
612
675
  variantAttributeEntries = [];
613
- // Case definition key from context (for ValuePathSelector)
614
676
  caseDefinitionKey = null;
615
- // Discovered process variables
616
677
  processVariables = [];
617
- // Required fields status
618
678
  requiredFieldsStatus = { mapped: 0, total: 0 };
679
+ prefillDataMapping = {};
619
680
  destroy$ = new Subject();
620
681
  saveSubscription;
621
682
  formValue$ = new BehaviorSubject(null);
@@ -628,7 +689,7 @@ class GenerateDocumentConfigurationComponent {
628
689
  }
629
690
  ngOnInit() {
630
691
  this.initContext();
631
- this.initPrefillDataMapping();
692
+ this.initPrefill();
632
693
  this.initPluginConfiguration();
633
694
  this.initTemplatesLoading();
634
695
  this.initEnvironmentsLoading();
@@ -644,12 +705,10 @@ class GenerateDocumentConfigurationComponent {
644
705
  formValueChange(formOutput) {
645
706
  const formValue = formOutput;
646
707
  this.formValue$.next(formValue);
647
- // Update selected template if changed (also clears variant selection)
648
708
  if (formValue.templateId && formValue.templateId !== this.selectedTemplateId$.getValue()) {
649
709
  this.selectedTemplateId$.next(formValue.templateId);
650
710
  this.selectedVariantId$.next('');
651
711
  }
652
- // Update selected variant if changed
653
712
  if (formValue.variantId && formValue.variantId !== this.selectedVariantId$.getValue()) {
654
713
  this.selectedVariantId$.next(formValue.variantId);
655
714
  }
@@ -657,7 +716,6 @@ class GenerateDocumentConfigurationComponent {
657
716
  }
658
717
  onDataMappingChange(mapping) {
659
718
  this.dataMapping$.next(mapping);
660
- // Re-validate when data mapping changes
661
719
  const currentFormValue = this.formValue$.getValue();
662
720
  if (currentFormValue) {
663
721
  this.handleValid(currentFormValue);
@@ -665,7 +723,6 @@ class GenerateDocumentConfigurationComponent {
665
723
  }
666
724
  onRequiredFieldsStatusChange(status) {
667
725
  this.requiredFieldsStatus = status;
668
- // Re-validate when required fields status changes
669
726
  const currentFormValue = this.formValue$.getValue();
670
727
  if (currentFormValue) {
671
728
  this.handleValid(currentFormValue);
@@ -676,11 +733,7 @@ class GenerateDocumentConfigurationComponent {
676
733
  if (mode === 'attributes' && this.variantAttributeEntries.length === 0) {
677
734
  this.variantAttributeEntries = [{ key: '', value: '' }];
678
735
  }
679
- // Re-validate
680
- const currentFormValue = this.formValue$.getValue();
681
- if (currentFormValue) {
682
- this.handleValid(currentFormValue);
683
- }
736
+ this.revalidate();
684
737
  }
685
738
  addAttributeEntry() {
686
739
  this.variantAttributeEntries = [...this.variantAttributeEntries, { key: '', value: '' }];
@@ -713,10 +766,8 @@ class GenerateDocumentConfigurationComponent {
713
766
  });
714
767
  }
715
768
  }
716
- initPrefillDataMapping() {
769
+ initPrefill() {
717
770
  if (this.prefillConfiguration$) {
718
- this.prefillDataMapping$ = this.prefillConfiguration$.pipe(map(config => config?.dataMapping || {}));
719
- // Also set initial selected template, variant mode, and variant
720
771
  this.prefillConfiguration$.pipe(takeUntil(this.destroy$), filter(config => !!config?.templateId)).subscribe(config => {
721
772
  this.selectedTemplateId$.next(config.templateId);
722
773
  if (config.variantAttributes && Object.keys(config.variantAttributes).length > 0) {
@@ -729,102 +780,65 @@ class GenerateDocumentConfigurationComponent {
729
780
  this.selectedVariantId$.next(config.variantId);
730
781
  }
731
782
  if (config.dataMapping) {
783
+ this.prefillDataMapping = config.dataMapping;
732
784
  this.dataMapping$.next(config.dataMapping);
733
785
  }
734
786
  this.cdr.markForCheck();
735
787
  });
736
788
  }
737
- else {
738
- this.prefillDataMapping$ = new BehaviorSubject({}).asObservable();
739
- }
740
789
  }
741
790
  initPluginConfiguration() {
742
791
  const sources = [];
743
- // Create mode: framework emits when user selects a plugin configuration
744
792
  if (this.selectedPluginConfigurationData$) {
745
793
  sources.push(this.selectedPluginConfigurationData$.pipe(filter(config => !!config?.configurationId), map(config => config.configurationId)));
746
794
  }
747
- // Edit mode: read pluginConfigurationId from the ProcessLink entity
748
- // (selectedPluginConfigurationData$ does not emit in edit mode)
749
795
  sources.push(this.processLinkStateService.selectedProcessLink$.pipe(filter(processLink => !!processLink?.pluginConfigurationId), map(processLink => processLink.pluginConfigurationId)));
750
796
  merge(...sources).pipe(takeUntil(this.destroy$)).subscribe(configurationId => {
751
797
  this.pluginConfigurationId$.next(configurationId);
752
798
  });
753
799
  }
754
800
  initTemplatesLoading() {
755
- this.pluginConfigurationId$.pipe(takeUntil(this.destroy$), filter(id => !!id)).subscribe(configurationId => {
756
- this.templatesLoading$.next(true);
757
- this.templatesError$.next(null);
758
- this.epistolaPluginService.getTemplates(configurationId).pipe(takeUntil(this.destroy$), catchError(() => {
759
- this.templatesError$.next('Failed to load templates');
760
- return of([]);
761
- })).subscribe(templates => {
762
- const options = templates.map(t => ({
763
- id: t.id,
764
- text: t.name
765
- }));
766
- this.templateOptions$.next(options);
767
- this.templatesLoading$.next(false);
768
- });
801
+ this.pluginConfigurationId$.pipe(takeUntil(this.destroy$), filter(id => !!id), tap(() => this.templates$.next(loadingResource(this.templates$.getValue().data))), switchMap(configurationId => this.epistolaPluginService.getTemplates(configurationId).pipe(catchError(() => {
802
+ this.templates$.next(errorResource([], 'Failed to load templates'));
803
+ return of(null);
804
+ }))), filter(result => result !== null)).subscribe(templates => {
805
+ this.templates$.next(successResource(templates.map(t => ({ id: t.id, text: t.name }))));
769
806
  });
770
807
  }
771
808
  initEnvironmentsLoading() {
772
- this.pluginConfigurationId$.pipe(takeUntil(this.destroy$), filter(id => !!id)).subscribe(configurationId => {
773
- this.environmentsLoading$.next(true);
774
- this.environmentsError$.next(null);
775
- this.epistolaPluginService.getEnvironments(configurationId).pipe(takeUntil(this.destroy$), catchError(() => {
776
- this.environmentsError$.next('Failed to load environments');
777
- return of([]);
778
- })).subscribe(environments => {
779
- const options = environments.map(e => ({
780
- id: e.id,
781
- text: e.name
782
- }));
783
- this.environmentOptions$.next(options);
784
- this.environmentsLoading$.next(false);
785
- });
809
+ this.pluginConfigurationId$.pipe(takeUntil(this.destroy$), filter(id => !!id), tap(() => this.environments$.next(loadingResource(this.environments$.getValue().data))), switchMap(configurationId => this.epistolaPluginService.getEnvironments(configurationId).pipe(catchError(() => {
810
+ this.environments$.next(errorResource([], 'Failed to load environments'));
811
+ return of(null);
812
+ }))), filter(result => result !== null)).subscribe(environments => {
813
+ this.environments$.next(successResource(environments.map(e => ({ id: e.id, text: e.name }))));
786
814
  });
787
815
  }
788
816
  initVariantsLoading() {
789
817
  combineLatest([
790
818
  this.pluginConfigurationId$,
791
819
  this.selectedTemplateId$
792
- ]).pipe(takeUntil(this.destroy$), filter(([configId, templateId]) => !!configId && !!templateId)).subscribe(([configurationId, templateId]) => {
793
- this.variantsLoading$.next(true);
794
- this.variantsError$.next(null);
795
- this.epistolaPluginService.getVariants(configurationId, templateId).pipe(takeUntil(this.destroy$), catchError(() => {
796
- this.variantsError$.next('Failed to load variants');
797
- return of([]);
798
- })).subscribe(variants => {
799
- const options = variants.map(v => ({
800
- id: v.id,
801
- text: v.name + this.formatAttributes(v.attributes)
802
- }));
803
- this.variantOptions$.next(options);
804
- this.variantsLoading$.next(false);
805
- });
820
+ ]).pipe(takeUntil(this.destroy$), filter(([configId, templateId]) => !!configId && !!templateId), tap(() => this.variants$.next(loadingResource(this.variants$.getValue().data))), switchMap(([configurationId, templateId]) => this.epistolaPluginService.getVariants(configurationId, templateId).pipe(catchError(() => {
821
+ this.variants$.next(errorResource([], 'Failed to load variants'));
822
+ return of(null);
823
+ }))), filter(result => result !== null)).subscribe(variants => {
824
+ this.variants$.next(successResource(variants.map(v => ({ id: v.id, text: v.name + this.formatAttributes(v.attributes) }))));
806
825
  });
807
826
  }
808
827
  initTemplateFieldsLoading() {
809
828
  combineLatest([
810
829
  this.pluginConfigurationId$,
811
830
  this.selectedTemplateId$
812
- ]).pipe(takeUntil(this.destroy$), filter(([configId, templateId]) => !!configId && !!templateId)).subscribe(([configurationId, templateId]) => {
813
- this.templateFieldsLoading$.next(true);
814
- this.templateFieldsError$.next(null);
815
- this.epistolaPluginService.getTemplateDetails(configurationId, templateId).pipe(takeUntil(this.destroy$), catchError(() => {
816
- this.templateFieldsError$.next('Failed to load template fields');
817
- return of({ fields: [] });
818
- })).subscribe(details => {
819
- this.templateFields$.next(details.fields || []);
820
- this.templateFieldsLoading$.next(false);
821
- });
822
- // Also load process variables if we have a case context
831
+ ]).pipe(takeUntil(this.destroy$), filter(([configId, templateId]) => !!configId && !!templateId), tap(() => {
832
+ this.templateFields$.next(loadingResource(this.templateFields$.getValue().data));
823
833
  this.loadProcessVariables();
834
+ }), switchMap(([configurationId, templateId]) => this.epistolaPluginService.getTemplateDetails(configurationId, templateId).pipe(catchError(() => {
835
+ this.templateFields$.next(errorResource([], 'Failed to load template fields'));
836
+ return of(null);
837
+ }))), filter(result => result !== null)).subscribe(details => {
838
+ this.templateFields$.next(successResource(details.fields || []));
824
839
  });
825
840
  }
826
841
  loadProcessVariables() {
827
- // Try to discover process variables (best-effort, may not always have context)
828
842
  if (this.caseDefinitionKey) {
829
843
  this.epistolaPluginService.getProcessVariables(this.caseDefinitionKey).pipe(takeUntil(this.destroy$), catchError(() => of([]))).subscribe(variables => {
830
844
  this.processVariables = variables;
@@ -837,13 +851,10 @@ class GenerateDocumentConfigurationComponent {
837
851
  formValue?.outputFormat &&
838
852
  formValue?.filename &&
839
853
  formValue?.resultProcessVariable);
840
- // Variant selection: if attribute mode is used, all entries must have both key and value filled.
841
- // Neither variant nor attributes are required — omitting both uses the template's default variant.
842
854
  let variantValid = true;
843
855
  if (this.variantSelectionMode === 'attributes' && this.variantAttributeEntries.length > 0) {
844
856
  variantValid = this.variantAttributeEntries.every(e => !!e.key && !!e.value);
845
857
  }
846
- // Check if all required template fields are mapped
847
858
  const requiredFieldsMapped = this.requiredFieldsStatus.total === 0 ||
848
859
  this.requiredFieldsStatus.mapped === this.requiredFieldsStatus.total;
849
860
  const valid = baseComplete && variantValid && requiredFieldsMapped;
@@ -881,8 +892,8 @@ class GenerateDocumentConfigurationComponent {
881
892
  });
882
893
  });
883
894
  }
884
- 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 });
885
- 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 });
895
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: GenerateDocumentConfigurationComponent, deps: [{ token: EpistolaPluginService }, { token: i2$3.ProcessLinkStateService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
896
+ 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 templates: templates$ | async,\n variants: variants$ | async,\n environments: environments$ | async,\n templateFields: templateFields$ | 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.templates.data\"\n [defaultSelectionId]=\"obs.prefill?.templateId\"\n [disabled]=\"obs.disabled || obs.templates.loading\"\n [required]=\"true\"\n [loading]=\"obs.templates.loading\"\n >\n </v-select>\n <div *ngIf=\"obs.templates.error\" class=\"loading-error\">{{ obs.templates.error }}</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.variants.data\"\n [defaultSelectionId]=\"obs.prefill?.variantId\"\n [disabled]=\"obs.disabled || obs.variants.loading || !obs.selectedTemplateId\"\n [required]=\"false\"\n [loading]=\"obs.variants.loading\"\n >\n </v-select>\n <div *ngIf=\"obs.variants.error\" class=\"loading-error\">{{ obs.variants.error }}</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.environments.data\"\n [defaultSelectionId]=\"obs.prefill?.environmentId\"\n [disabled]=\"obs.disabled || obs.environments.loading\"\n [required]=\"false\"\n [loading]=\"obs.environments.loading\"\n >\n </v-select>\n <div *ngIf=\"obs.environments.error\" class=\"loading-error\">{{ obs.environments.error }}</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=\"(templateFields$ | async)?.error as templateFieldsError\" class=\"loading-error\">{{ templateFieldsError }}</div>\n\n<epistola-data-mapping-tree\n *ngIf=\"(selectedTemplateId$ | async)\"\n [pluginId]=\"pluginId\"\n [templateFields]=\"(templateFields$ | async)?.data ?? []\"\n [disabled]=\"!!(disabled$ | async)\"\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: i2$2.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: i2$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$2.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 });
886
897
  }
887
898
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: GenerateDocumentConfigurationComponent, decorators: [{
888
899
  type: Component,
@@ -894,8 +905,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
894
905
  InputModule,
895
906
  SelectModule,
896
907
  DataMappingTreeComponent
897
- ], 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"] }]
898
- }], ctorParameters: () => [{ type: EpistolaPluginService }, { type: i2$2.ProcessLinkStateService }, { type: i0.ChangeDetectorRef }], propDecorators: { save$: [{
908
+ ], template: "<v-form\n (valueChange)=\"formValueChange($event)\"\n *ngIf=\"{\n disabled: disabled$ | async,\n prefill: prefillConfiguration$ ? (prefillConfiguration$ | async) : null,\n templates: templates$ | async,\n variants: variants$ | async,\n environments: environments$ | async,\n templateFields: templateFields$ | 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.templates.data\"\n [defaultSelectionId]=\"obs.prefill?.templateId\"\n [disabled]=\"obs.disabled || obs.templates.loading\"\n [required]=\"true\"\n [loading]=\"obs.templates.loading\"\n >\n </v-select>\n <div *ngIf=\"obs.templates.error\" class=\"loading-error\">{{ obs.templates.error }}</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.variants.data\"\n [defaultSelectionId]=\"obs.prefill?.variantId\"\n [disabled]=\"obs.disabled || obs.variants.loading || !obs.selectedTemplateId\"\n [required]=\"false\"\n [loading]=\"obs.variants.loading\"\n >\n </v-select>\n <div *ngIf=\"obs.variants.error\" class=\"loading-error\">{{ obs.variants.error }}</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.environments.data\"\n [defaultSelectionId]=\"obs.prefill?.environmentId\"\n [disabled]=\"obs.disabled || obs.environments.loading\"\n [required]=\"false\"\n [loading]=\"obs.environments.loading\"\n >\n </v-select>\n <div *ngIf=\"obs.environments.error\" class=\"loading-error\">{{ obs.environments.error }}</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=\"(templateFields$ | async)?.error as templateFieldsError\" class=\"loading-error\">{{ templateFieldsError }}</div>\n\n<epistola-data-mapping-tree\n *ngIf=\"(selectedTemplateId$ | async)\"\n [pluginId]=\"pluginId\"\n [templateFields]=\"(templateFields$ | async)?.data ?? []\"\n [disabled]=\"!!(disabled$ | async)\"\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"] }]
909
+ }], ctorParameters: () => [{ type: EpistolaPluginService }, { type: i2$3.ProcessLinkStateService }, { type: i0.ChangeDetectorRef }], propDecorators: { save$: [{
899
910
  type: Input
900
911
  }], disabled$: [{
901
912
  type: Input
@@ -1275,7 +1286,7 @@ class EpistolaRetryFormComponent {
1275
1286
  }
1276
1287
  });
1277
1288
  }
1278
- 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 });
1289
+ 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.DomSanitizer }, { token: i2.ConfigService }], target: i0.ɵɵFactoryTarget.Component });
1279
1290
  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: `
1280
1291
  <div *ngIf="loading" class="epistola-retry-loading">Loading form...</div>
1281
1292
  <div *ngIf="error" class="epistola-retry-error">{{ error }}</div>
@@ -1339,7 +1350,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
1339
1350
  </div>
1340
1351
  </div>
1341
1352
  `, 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"] }]
1342
- }], ctorParameters: () => [{ type: EpistolaPluginService }, { type: i3.FormIoStateService }, { type: i0.ChangeDetectorRef }, { type: i1.HttpClient }, { type: i4$1.DomSanitizer }, { type: i2.ConfigService }], propDecorators: { value: [{
1353
+ }], ctorParameters: () => [{ type: EpistolaPluginService }, { type: i3.FormIoStateService }, { type: i0.ChangeDetectorRef }, { type: i1.HttpClient }, { type: i4.DomSanitizer }, { type: i2.ConfigService }], propDecorators: { value: [{
1343
1354
  type: Input
1344
1355
  }], valueChange: [{
1345
1356
  type: Output
@@ -1416,7 +1427,7 @@ class EpistolaPreviewButtonComponent {
1416
1427
  this.currentBlobUrl = null;
1417
1428
  }
1418
1429
  }
1419
- 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 });
1430
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaPreviewButtonComponent, deps: [{ token: i1.HttpClient }, { token: i4.DomSanitizer }, { token: i2.ConfigService }], target: i0.ɵɵFactoryTarget.Component });
1420
1431
  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: `
1421
1432
  <button
1422
1433
  type="button"
@@ -1484,7 +1495,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
1484
1495
  </div>
1485
1496
  </div>
1486
1497
  `, 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"] }]
1487
- }], ctorParameters: () => [{ type: i1.HttpClient }, { type: i4$1.DomSanitizer }, { type: i2.ConfigService }], propDecorators: { value: [{
1498
+ }], ctorParameters: () => [{ type: i1.HttpClient }, { type: i4.DomSanitizer }, { type: i2.ConfigService }], propDecorators: { value: [{
1488
1499
  type: Input
1489
1500
  }], valueChange: [{
1490
1501
  type: Output
@@ -1494,7 +1505,325 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
1494
1505
  type: Input
1495
1506
  }] } });
1496
1507
 
1508
+ class EpistolaDocumentPreviewComponent {
1509
+ epistolaPluginService;
1510
+ http;
1511
+ sanitizer;
1512
+ configService;
1513
+ formIoStateService;
1514
+ cdr;
1515
+ value;
1516
+ valueChange = new EventEmitter();
1517
+ disabled = false;
1518
+ label = 'Document Preview';
1519
+ sources = [];
1520
+ selectedIndex = 0;
1521
+ discovering = false;
1522
+ loading = false;
1523
+ error = null;
1524
+ previewUrl = null;
1525
+ initialized = false;
1526
+ currentBlobUrl = null;
1527
+ discoverSubscription;
1528
+ previewSubscription;
1529
+ apiEndpoint;
1530
+ constructor(epistolaPluginService, http, sanitizer, configService, formIoStateService, cdr) {
1531
+ this.epistolaPluginService = epistolaPluginService;
1532
+ this.http = http;
1533
+ this.sanitizer = sanitizer;
1534
+ this.configService = configService;
1535
+ this.formIoStateService = formIoStateService;
1536
+ this.cdr = cdr;
1537
+ this.apiEndpoint = `${this.configService.config.valtimoApi.endpointUri}v1/plugin/epistola`;
1538
+ }
1539
+ ngOnChanges(changes) {
1540
+ if (!this.initialized) {
1541
+ this.initialized = true;
1542
+ this.discoverSources();
1543
+ }
1544
+ }
1545
+ ngOnDestroy() {
1546
+ this.discoverSubscription?.unsubscribe();
1547
+ this.previewSubscription?.unsubscribe();
1548
+ this.revokeBlobUrl();
1549
+ }
1550
+ onSourceChange(event) {
1551
+ this.selectedIndex = +event.target.value;
1552
+ this.loadPreview();
1553
+ }
1554
+ refresh() {
1555
+ this.loadPreview();
1556
+ }
1557
+ discoverSources() {
1558
+ const documentId = this.formIoStateService.documentId;
1559
+ if (!documentId) {
1560
+ this.error = 'Could not determine document ID from context.';
1561
+ this.cdr.markForCheck();
1562
+ return;
1563
+ }
1564
+ this.discovering = true;
1565
+ this.error = null;
1566
+ this.cdr.markForCheck();
1567
+ this.discoverSubscription = this.epistolaPluginService.getPreviewSources(documentId).subscribe({
1568
+ next: (sources) => {
1569
+ this.sources = sources;
1570
+ this.discovering = false;
1571
+ this.cdr.markForCheck();
1572
+ if (sources.length > 0) {
1573
+ this.selectedIndex = 0;
1574
+ this.loadPreview();
1575
+ }
1576
+ },
1577
+ error: (err) => {
1578
+ this.error = err.error?.error || 'Failed to discover preview sources';
1579
+ this.discovering = false;
1580
+ this.cdr.markForCheck();
1581
+ }
1582
+ });
1583
+ }
1584
+ loadPreview() {
1585
+ const source = this.sources[this.selectedIndex];
1586
+ if (!source)
1587
+ return;
1588
+ const documentId = this.formIoStateService.documentId;
1589
+ if (!documentId)
1590
+ return;
1591
+ this.loading = true;
1592
+ this.error = null;
1593
+ this.cdr.markForCheck();
1594
+ this.revokeBlobUrl();
1595
+ this.previewSubscription?.unsubscribe();
1596
+ this.previewSubscription = this.http.post(`${this.apiEndpoint}/preview`, {
1597
+ documentId,
1598
+ processInstanceId: source.processInstanceId,
1599
+ sourceActivityId: source.activityId,
1600
+ overrides: null
1601
+ }, {
1602
+ responseType: 'blob',
1603
+ headers: new HttpHeaders().set('X-Skip-Interceptor', '422')
1604
+ }).subscribe({
1605
+ next: (blob) => {
1606
+ this.currentBlobUrl = URL.createObjectURL(blob);
1607
+ this.previewUrl = this.sanitizer.bypassSecurityTrustResourceUrl(this.currentBlobUrl);
1608
+ this.error = null;
1609
+ this.loading = false;
1610
+ this.cdr.markForCheck();
1611
+ },
1612
+ error: (err) => {
1613
+ this.previewUrl = null;
1614
+ if (err.error instanceof Blob) {
1615
+ err.error.text().then((text) => {
1616
+ try {
1617
+ const body = JSON.parse(text);
1618
+ this.error = body.details || body.error || 'Preview could not be generated';
1619
+ }
1620
+ catch {
1621
+ this.error = 'Preview could not be generated';
1622
+ }
1623
+ this.loading = false;
1624
+ this.cdr.markForCheck();
1625
+ });
1626
+ }
1627
+ else {
1628
+ this.error = err.error?.error || 'Preview could not be generated';
1629
+ this.loading = false;
1630
+ this.cdr.markForCheck();
1631
+ }
1632
+ }
1633
+ });
1634
+ }
1635
+ revokeBlobUrl() {
1636
+ if (this.currentBlobUrl) {
1637
+ URL.revokeObjectURL(this.currentBlobUrl);
1638
+ this.currentBlobUrl = null;
1639
+ this.previewUrl = null;
1640
+ }
1641
+ }
1642
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaDocumentPreviewComponent, deps: [{ token: EpistolaPluginService }, { token: i1.HttpClient }, { token: i4.DomSanitizer }, { token: i2.ConfigService }, { token: i3.FormIoStateService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
1643
+ 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: `
1644
+ <div class="epistola-preview-panel">
1645
+ <div class="preview-header">
1646
+ <span>{{ label || 'Document Preview' }}</span>
1647
+ <div class="preview-controls">
1648
+ <select
1649
+ *ngIf="sources.length > 1"
1650
+ class="preview-select"
1651
+ [value]="selectedIndex"
1652
+ (change)="onSourceChange($event)"
1653
+ >
1654
+ <option *ngFor="let source of sources; let i = index" [value]="i">
1655
+ {{ source.templateName }} ({{ source.activityId }})
1656
+ </option>
1657
+ </select>
1658
+ <button type="button" class="preview-refresh" [disabled]="loading || discovering" (click)="refresh()">
1659
+ <i class="mdi mdi-refresh mr-1"></i>
1660
+ {{ loading ? 'Generating...' : 'Refresh' }}
1661
+ </button>
1662
+ </div>
1663
+ </div>
1664
+ <div class="preview-body">
1665
+ <div *ngIf="discovering" class="preview-loading">
1666
+ Discovering documents...
1667
+ </div>
1668
+ <div *ngIf="loading && !discovering" class="preview-loading">
1669
+ Generating preview...
1670
+ </div>
1671
+ <div *ngIf="error && !loading && !discovering" class="preview-unavailable">
1672
+ <i class="mdi mdi-information-outline"></i>
1673
+ Preview is niet beschikbaar — niet alle gegevens zijn al ingevuld.
1674
+ </div>
1675
+ <object
1676
+ *ngIf="previewUrl && !loading && !discovering"
1677
+ [data]="previewUrl"
1678
+ type="application/pdf"
1679
+ class="preview-pdf"
1680
+ >
1681
+ PDF preview is not supported in this browser.
1682
+ </object>
1683
+ <div *ngIf="!previewUrl && !loading && !discovering && !error && sources.length === 0" class="preview-empty">
1684
+ No previewable documents found
1685
+ </div>
1686
+ </div>
1687
+ </div>
1688
+ `, 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 });
1689
+ }
1690
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaDocumentPreviewComponent, decorators: [{
1691
+ type: Component,
1692
+ args: [{ standalone: true, imports: [CommonModule], selector: 'epistola-document-preview-component', changeDetection: ChangeDetectionStrategy.OnPush, template: `
1693
+ <div class="epistola-preview-panel">
1694
+ <div class="preview-header">
1695
+ <span>{{ label || 'Document Preview' }}</span>
1696
+ <div class="preview-controls">
1697
+ <select
1698
+ *ngIf="sources.length > 1"
1699
+ class="preview-select"
1700
+ [value]="selectedIndex"
1701
+ (change)="onSourceChange($event)"
1702
+ >
1703
+ <option *ngFor="let source of sources; let i = index" [value]="i">
1704
+ {{ source.templateName }} ({{ source.activityId }})
1705
+ </option>
1706
+ </select>
1707
+ <button type="button" class="preview-refresh" [disabled]="loading || discovering" (click)="refresh()">
1708
+ <i class="mdi mdi-refresh mr-1"></i>
1709
+ {{ loading ? 'Generating...' : 'Refresh' }}
1710
+ </button>
1711
+ </div>
1712
+ </div>
1713
+ <div class="preview-body">
1714
+ <div *ngIf="discovering" class="preview-loading">
1715
+ Discovering documents...
1716
+ </div>
1717
+ <div *ngIf="loading && !discovering" class="preview-loading">
1718
+ Generating preview...
1719
+ </div>
1720
+ <div *ngIf="error && !loading && !discovering" class="preview-unavailable">
1721
+ <i class="mdi mdi-information-outline"></i>
1722
+ Preview is niet beschikbaar — niet alle gegevens zijn al ingevuld.
1723
+ </div>
1724
+ <object
1725
+ *ngIf="previewUrl && !loading && !discovering"
1726
+ [data]="previewUrl"
1727
+ type="application/pdf"
1728
+ class="preview-pdf"
1729
+ >
1730
+ PDF preview is not supported in this browser.
1731
+ </object>
1732
+ <div *ngIf="!previewUrl && !loading && !discovering && !error && sources.length === 0" class="preview-empty">
1733
+ No previewable documents found
1734
+ </div>
1735
+ </div>
1736
+ </div>
1737
+ `, 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"] }]
1738
+ }], ctorParameters: () => [{ type: EpistolaPluginService }, { type: i1.HttpClient }, { type: i4.DomSanitizer }, { type: i2.ConfigService }, { type: i3.FormIoStateService }, { type: i0.ChangeDetectorRef }], propDecorators: { value: [{
1739
+ type: Input
1740
+ }], valueChange: [{
1741
+ type: Output
1742
+ }], disabled: [{
1743
+ type: Input
1744
+ }], label: [{
1745
+ type: Input
1746
+ }] } });
1747
+
1748
+ const EPISTOLA_DOWNLOAD_OPTIONS = {
1749
+ type: 'epistola-download',
1750
+ selector: 'epistola-download-button',
1751
+ title: 'Epistola Download',
1752
+ group: 'basic',
1753
+ icon: 'download',
1754
+ emptyValue: null,
1755
+ fieldOptions: ['filename', 'label'],
1756
+ };
1757
+ function registerEpistolaDownloadComponent(injector) {
1758
+ if (!customElements.get(EPISTOLA_DOWNLOAD_OPTIONS.selector)) {
1759
+ registerCustomFormioComponent(EPISTOLA_DOWNLOAD_OPTIONS, EpistolaDownloadComponent, injector);
1760
+ }
1761
+ }
1762
+
1763
+ const EPISTOLA_RETRY_FORM_OPTIONS = {
1764
+ type: 'epistola-retry-form',
1765
+ selector: 'epistola-retry-form-element',
1766
+ title: 'Epistola Retry Form',
1767
+ group: 'basic',
1768
+ icon: 'refresh',
1769
+ emptyValue: null,
1770
+ fieldOptions: ['sourceActivityId', 'label'], // sourceActivityId is optional (set via BPMN input parameter)
1771
+ };
1772
+ function registerEpistolaRetryFormComponent(injector) {
1773
+ if (!customElements.get(EPISTOLA_RETRY_FORM_OPTIONS.selector)) {
1774
+ registerCustomFormioComponent(EPISTOLA_RETRY_FORM_OPTIONS, EpistolaRetryFormComponent, injector);
1775
+ }
1776
+ }
1777
+
1778
+ const EPISTOLA_PREVIEW_BUTTON_OPTIONS = {
1779
+ type: 'epistola-preview-button',
1780
+ selector: 'epistola-preview-button-element',
1781
+ title: 'Epistola Preview',
1782
+ group: 'basic',
1783
+ icon: 'eye',
1784
+ emptyValue: null,
1785
+ fieldOptions: ['label'],
1786
+ };
1787
+ function registerEpistolaPreviewButtonComponent(injector) {
1788
+ if (!customElements.get(EPISTOLA_PREVIEW_BUTTON_OPTIONS.selector)) {
1789
+ registerCustomFormioComponent(EPISTOLA_PREVIEW_BUTTON_OPTIONS, EpistolaPreviewButtonComponent, injector);
1790
+ }
1791
+ }
1792
+
1793
+ const EPISTOLA_DOCUMENT_PREVIEW_OPTIONS = {
1794
+ type: 'epistola-document-preview',
1795
+ selector: 'epistola-document-preview-element',
1796
+ title: 'Epistola Document Preview',
1797
+ group: 'basic',
1798
+ icon: 'file-pdf-o',
1799
+ emptyValue: null,
1800
+ fieldOptions: ['label'],
1801
+ };
1802
+ function registerEpistolaDocumentPreviewComponent(injector) {
1803
+ if (!customElements.get(EPISTOLA_DOCUMENT_PREVIEW_OPTIONS.selector)) {
1804
+ registerCustomFormioComponent(EPISTOLA_DOCUMENT_PREVIEW_OPTIONS, EpistolaDocumentPreviewComponent, injector);
1805
+ }
1806
+ }
1807
+
1497
1808
  class EpistolaPluginModule {
1809
+ static forRoot() {
1810
+ return {
1811
+ ngModule: EpistolaPluginModule,
1812
+ providers: [
1813
+ {
1814
+ provide: ENVIRONMENT_INITIALIZER,
1815
+ multi: true,
1816
+ useValue: () => {
1817
+ const injector = inject(Injector);
1818
+ registerEpistolaDownloadComponent(injector);
1819
+ registerEpistolaRetryFormComponent(injector);
1820
+ registerEpistolaPreviewButtonComponent(injector);
1821
+ registerEpistolaDocumentPreviewComponent(injector);
1822
+ }
1823
+ }
1824
+ ]
1825
+ };
1826
+ }
1498
1827
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaPluginModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
1499
1828
  static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.20", ngImport: i0, type: EpistolaPluginModule, imports: [CommonModule,
1500
1829
  HttpClientModule,
@@ -1507,18 +1836,26 @@ class EpistolaPluginModule {
1507
1836
  CheckJobStatusConfigurationComponent,
1508
1837
  DownloadDocumentConfigurationComponent,
1509
1838
  DataMappingTreeComponent,
1839
+ ValueInputComponent,
1840
+ ScalarFieldComponent,
1841
+ ArrayFieldComponent,
1510
1842
  FieldTreeComponent,
1511
1843
  EpistolaDownloadComponent,
1512
1844
  EpistolaRetryFormComponent,
1513
- EpistolaPreviewButtonComponent], exports: [EpistolaConfigurationComponent,
1845
+ EpistolaPreviewButtonComponent,
1846
+ EpistolaDocumentPreviewComponent], exports: [EpistolaConfigurationComponent,
1514
1847
  GenerateDocumentConfigurationComponent,
1515
1848
  CheckJobStatusConfigurationComponent,
1516
1849
  DownloadDocumentConfigurationComponent,
1517
1850
  DataMappingTreeComponent,
1851
+ ValueInputComponent,
1852
+ ScalarFieldComponent,
1853
+ ArrayFieldComponent,
1518
1854
  FieldTreeComponent,
1519
1855
  EpistolaDownloadComponent,
1520
1856
  EpistolaRetryFormComponent,
1521
- EpistolaPreviewButtonComponent] });
1857
+ EpistolaPreviewButtonComponent,
1858
+ EpistolaDocumentPreviewComponent] });
1522
1859
  static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaPluginModule, providers: [
1523
1860
  EpistolaPluginService
1524
1861
  ], imports: [CommonModule,
@@ -1532,10 +1869,14 @@ class EpistolaPluginModule {
1532
1869
  CheckJobStatusConfigurationComponent,
1533
1870
  DownloadDocumentConfigurationComponent,
1534
1871
  DataMappingTreeComponent,
1872
+ ValueInputComponent,
1873
+ ScalarFieldComponent,
1874
+ ArrayFieldComponent,
1535
1875
  FieldTreeComponent,
1536
1876
  EpistolaDownloadComponent,
1537
1877
  EpistolaRetryFormComponent,
1538
- EpistolaPreviewButtonComponent] });
1878
+ EpistolaPreviewButtonComponent,
1879
+ EpistolaDocumentPreviewComponent] });
1539
1880
  }
1540
1881
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaPluginModule, decorators: [{
1541
1882
  type: NgModule,
@@ -1552,10 +1893,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
1552
1893
  CheckJobStatusConfigurationComponent,
1553
1894
  DownloadDocumentConfigurationComponent,
1554
1895
  DataMappingTreeComponent,
1896
+ ValueInputComponent,
1897
+ ScalarFieldComponent,
1898
+ ArrayFieldComponent,
1555
1899
  FieldTreeComponent,
1556
1900
  EpistolaDownloadComponent,
1557
1901
  EpistolaRetryFormComponent,
1558
- EpistolaPreviewButtonComponent
1902
+ EpistolaPreviewButtonComponent,
1903
+ EpistolaDocumentPreviewComponent
1559
1904
  ],
1560
1905
  exports: [
1561
1906
  EpistolaConfigurationComponent,
@@ -1563,10 +1908,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
1563
1908
  CheckJobStatusConfigurationComponent,
1564
1909
  DownloadDocumentConfigurationComponent,
1565
1910
  DataMappingTreeComponent,
1911
+ ValueInputComponent,
1912
+ ScalarFieldComponent,
1913
+ ArrayFieldComponent,
1566
1914
  FieldTreeComponent,
1567
1915
  EpistolaDownloadComponent,
1568
1916
  EpistolaRetryFormComponent,
1569
- EpistolaPreviewButtonComponent
1917
+ EpistolaPreviewButtonComponent,
1918
+ EpistolaDocumentPreviewComponent
1570
1919
  ],
1571
1920
  providers: [
1572
1921
  EpistolaPluginService
@@ -1768,51 +2117,6 @@ const epistolaPluginSpecification = {
1768
2117
  }
1769
2118
  };
1770
2119
 
1771
- const EPISTOLA_DOWNLOAD_OPTIONS = {
1772
- type: 'epistola-download',
1773
- selector: 'epistola-download-button',
1774
- title: 'Epistola Download',
1775
- group: 'basic',
1776
- icon: 'download',
1777
- emptyValue: null,
1778
- fieldOptions: ['filename', 'label'],
1779
- };
1780
- function registerEpistolaDownloadComponent(injector) {
1781
- if (!customElements.get(EPISTOLA_DOWNLOAD_OPTIONS.selector)) {
1782
- registerCustomFormioComponent(EPISTOLA_DOWNLOAD_OPTIONS, EpistolaDownloadComponent, injector);
1783
- }
1784
- }
1785
-
1786
- const EPISTOLA_RETRY_FORM_OPTIONS = {
1787
- type: 'epistola-retry-form',
1788
- selector: 'epistola-retry-form-element',
1789
- title: 'Epistola Retry Form',
1790
- group: 'basic',
1791
- icon: 'refresh',
1792
- emptyValue: null,
1793
- fieldOptions: ['sourceActivityId', 'label'], // sourceActivityId is optional (set via BPMN input parameter)
1794
- };
1795
- function registerEpistolaRetryFormComponent(injector) {
1796
- if (!customElements.get(EPISTOLA_RETRY_FORM_OPTIONS.selector)) {
1797
- registerCustomFormioComponent(EPISTOLA_RETRY_FORM_OPTIONS, EpistolaRetryFormComponent, injector);
1798
- }
1799
- }
1800
-
1801
- const EPISTOLA_PREVIEW_BUTTON_OPTIONS = {
1802
- type: 'epistola-preview-button',
1803
- selector: 'epistola-preview-button-element',
1804
- title: 'Epistola Preview',
1805
- group: 'basic',
1806
- icon: 'eye',
1807
- emptyValue: null,
1808
- fieldOptions: ['label'],
1809
- };
1810
- function registerEpistolaPreviewButtonComponent(injector) {
1811
- if (!customElements.get(EPISTOLA_PREVIEW_BUTTON_OPTIONS.selector)) {
1812
- registerCustomFormioComponent(EPISTOLA_PREVIEW_BUTTON_OPTIONS, EpistolaPreviewButtonComponent, injector);
1813
- }
1814
- }
1815
-
1816
2120
  /*
1817
2121
  * Public API Surface of epistola plugin
1818
2122
  */
@@ -1821,5 +2125,5 @@ function registerEpistolaPreviewButtonComponent(injector) {
1821
2125
  * Generated bundle index. Do not edit.
1822
2126
  */
1823
2127
 
1824
- export { CheckJobStatusConfigurationComponent, DataMappingTreeComponent, DownloadDocumentConfigurationComponent, EPISTOLA_DOWNLOAD_OPTIONS, EPISTOLA_PREVIEW_BUTTON_OPTIONS, EPISTOLA_RETRY_FORM_OPTIONS, EpistolaConfigurationComponent, EpistolaDownloadComponent, EpistolaPluginModule, EpistolaPluginService, EpistolaPreviewButtonComponent, EpistolaRetryFormComponent, FieldTreeComponent, GenerateDocumentConfigurationComponent, epistolaPluginSpecification, registerEpistolaDownloadComponent, registerEpistolaPreviewButtonComponent, registerEpistolaRetryFormComponent };
2128
+ export { ArrayFieldComponent, 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, ScalarFieldComponent, ValueInputComponent, epistolaPluginSpecification, errorResource, initialResource, loadingResource, normalizeToDots, registerEpistolaDocumentPreviewComponent, registerEpistolaDownloadComponent, registerEpistolaPreviewButtonComponent, registerEpistolaRetryFormComponent, successResource };
1825
2129
  //# sourceMappingURL=epistola.app-valtimo-plugin.mjs.map