@esfaenza/flow-builder 20.3.41 → 20.3.43

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.
@@ -1162,8 +1162,14 @@ function moveCondition(holder, from, to) {
1162
1162
  * Verifica che un'espressione custom sia sintatticamente accettabile e che ogni indice
1163
1163
  * esista. Serve al feedback immediato nel form: la verita' resta della validazione
1164
1164
  * del backend (`CONDITION_LOGIC_INVALID`).
1165
+ *
1166
+ * Il `term` esiste perche' la stessa regola vale su **due strutture diverse**: le condizioni
1167
+ * (§4.3) e i filtri sui record (§4.4), che dalla revisione del contratto hanno la logica
1168
+ * personalizzata anche sui dynamic choice set. Il progetto tiene distinte le due cose in ogni
1169
+ * altro punto, e un messaggio che dice «condizione» dentro un elenco di filtri manda a cercare
1170
+ * qualcosa che in quel form non c'e'.
1165
1171
  */
1166
- function checkConditionLogic(logic, conditionCount) {
1172
+ function checkConditionLogic(logic, conditionCount, term = 'condizione') {
1167
1173
  const trimmed = logic?.trim();
1168
1174
  if (!trimmed) {
1169
1175
  return null;
@@ -1192,13 +1198,14 @@ function checkConditionLogic(logic, conditionCount) {
1192
1198
  return 'Parentesi non bilanciate.';
1193
1199
  }
1194
1200
  const indexes = trimmed.match(/\d+/g) ?? [];
1201
+ const plural = term === 'filtro' ? 'filtri' : 'condizioni';
1195
1202
  if (indexes.length === 0) {
1196
- return 'L’espressione non referenzia nessuna condizione.';
1203
+ return `L’espressione non referenzia nessun${term === 'filtro' ? '' : 'a'} ${term}.`;
1197
1204
  }
1198
1205
  for (const raw of indexes) {
1199
1206
  const index = Number(raw);
1200
1207
  if (index < 1 || index > conditionCount) {
1201
- return `La condizione ${index} non esiste: sono definite ${conditionCount} condizioni.`;
1208
+ return `${term === 'filtro' ? 'Il' : 'La'} ${term} ${index} non esiste: ${term === 'filtro' ? 'sono definiti' : 'sono definite'} ${conditionCount} ${plural}.`;
1202
1209
  }
1203
1210
  }
1204
1211
  return null;
@@ -3268,30 +3275,63 @@ class FlowDocumentStore {
3268
3275
  if (!oldName || !newName || oldName === newName) {
3269
3276
  return;
3270
3277
  }
3278
+ const reference = this.nodeByName().get(oldName);
3271
3279
  this.update((draft) => {
3272
- const reference = this.nodeByName().get(oldName);
3273
- if (reference) {
3274
- const list = draft[reference.collection];
3275
- const node = list?.[reference.index];
3276
- if (node) {
3277
- node.name = newName;
3278
- }
3280
+ FlowDocumentStore.applyRename(draft, reference, oldName, newName);
3281
+ });
3282
+ }
3283
+ /**
3284
+ * Etichetta **e** nome tecnico in una sola mutazione.
3285
+ *
3286
+ * Serve al nome che segue l'etichetta di un elemento appena creato (§3.3): farlo con due
3287
+ * `update` scriverebbe due passi di storico per ogni battuta, e l'annulla riporterebbe
3288
+ * indietro il nome lasciando l'etichetta nuova — uno stato che non e' mai esistito. Con
3289
+ * `newName` assente e' un semplice cambio di etichetta.
3290
+ */
3291
+ relabelNode(name, label, newName) {
3292
+ const reference = this.nodeByName().get(name);
3293
+ if (!reference) {
3294
+ return;
3295
+ }
3296
+ this.update((draft) => {
3297
+ const list = draft[reference.collection];
3298
+ const node = list?.[reference.index];
3299
+ if (!node) {
3300
+ return;
3279
3301
  }
3280
- FlowDocumentStore.rewriteReferences(draft, oldName, newName);
3281
- /**
3282
- * I `members` di un riquadro **non** sono riferimenti (§3.6, regola 1) e per questo non
3283
- * stanno in `FLOW_REFERENCE_FIELDS`: sono appartenenza, e la tiene allineata l'editor. Ma
3284
- * portano dei nomi di node, quindi una rinomina che li salta lascia un
3285
- * `GROUP_MEMBER_UNKNOWN` su un node che esiste ancora — cioe' un avviso che accusa un
3286
- * gesto legittimo.
3287
- */
3288
- for (const group of draft.groups ?? []) {
3289
- if (group.members?.includes(oldName)) {
3290
- group.members = group.members.map((member) => (member === oldName ? newName : member));
3291
- }
3302
+ node.label = label || undefined;
3303
+ if (newName && newName !== name) {
3304
+ FlowDocumentStore.applyRename(draft, reference, name, newName);
3292
3305
  }
3293
3306
  });
3294
3307
  }
3308
+ /**
3309
+ * Il corpo della rinomina, sul draft: nome del node, riferimenti, appartenenza ai riquadri.
3310
+ * `reference` va risolto **prima** dell'`update`, perche' l'indice viene dal documento
3311
+ * corrente e dentro il mutatore i signal derivati sono ancora quelli di prima.
3312
+ */
3313
+ static applyRename(draft, reference, oldName, newName) {
3314
+ if (reference) {
3315
+ const list = draft[reference.collection];
3316
+ const node = list?.[reference.index];
3317
+ if (node) {
3318
+ node.name = newName;
3319
+ }
3320
+ }
3321
+ FlowDocumentStore.rewriteReferences(draft, oldName, newName);
3322
+ /**
3323
+ * I `members` di un riquadro **non** sono riferimenti (§3.6, regola 1) e per questo non
3324
+ * stanno in `FLOW_REFERENCE_FIELDS`: sono appartenenza, e la tiene allineata l'editor. Ma
3325
+ * portano dei nomi di node, quindi una rinomina che li salta lascia un
3326
+ * `GROUP_MEMBER_UNKNOWN` su un node che esiste ancora — cioe' un avviso che accusa un
3327
+ * gesto legittimo.
3328
+ */
3329
+ for (const group of draft.groups ?? []) {
3330
+ if (group.members?.includes(oldName)) {
3331
+ group.members = group.members.map((member) => (member === oldName ? newName : member));
3332
+ }
3333
+ }
3334
+ }
3295
3335
  /**
3296
3336
  * §5.2 — rinomina un campo di screen dinamico riscrivendo i riferimenti che lo usano.
3297
3337
  *
@@ -9331,11 +9371,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImpo
9331
9371
  * Due dettagli che il modello impone e che qui sono espliciti:
9332
9372
  *
9333
9373
  * - **`filterLogic` non esiste su tutti gli elementi che hanno `filters`.** C'e' su Get
9334
- * Records, Create Records, Start e sugli eventi di un Wait; **non** c'e' su Update
9335
- * Records, Delete Records e sui dynamic choice set, dove i filtri sono sempre in AND.
9374
+ * Records, Create Records, Start, sugli eventi di un Wait e sui **dynamic choice set**;
9375
+ * **non** c'e' su Update Records e Delete Records, dove i filtri sono sempre in AND.
9336
9376
  * Inviarlo dove non esiste significa perderlo silenziosamente al primo salvataggio:
9337
9377
  * l'input `supportsLogic` decide se il controllo viene mostrato.
9338
9378
  *
9379
+ * - **`Formula` non e' un valore di `filterLogic`** (§4.4): su un dynamic choice set e' un
9380
+ * errore di validazione, altrove passa la validazione e **fa fallire l'esecuzione**. La
9381
+ * formula di un filtro e' un campo suo, `filterFormula`, che oggi esiste sullo Start e su Get
9382
+ * Records: la modalita' «Formula» scrive quindi **quello** e cancella `filterLogic`. Un
9383
+ * documento scritto altrove puo' comunque portarcelo, e allora si dice — trattarlo come una
9384
+ * modalita' valida vorrebbe dire riscriverlo a ogni salvataggio.
9385
+ *
9386
+ * - **gli indici dell'espressione sono 1-based e scalano.** Cancellare un filtro senza
9387
+ * riscrivere l'espressione produce un `CONDITION_LOGIC_INVALID` oppure — peggio — un'
9388
+ * espressione valida che punta al filtro sbagliato: la rimappatura e la verifica locale sono
9389
+ * le stesse delle condizioni (`core/condition-logic.util.ts`), perche' la regola e' la stessa.
9390
+ *
9339
9391
  * - I campi proposti passano da `GET /schema/{object}/fields?usage=filterable`, così un
9340
9392
  * campo non filtrabile non arriva nemmeno alla validazione (`FIELD_NOT_FILTERABLE`).
9341
9393
  * La scrittura libera resta possibile — un catalogo incompleto non deve bloccare il campo — ma i
@@ -9414,12 +9466,21 @@ class RecordFilterEditorComponent {
9414
9466
  }
9415
9467
  return this.allFields().some((field) => field.name === 'Id' && field.isFilterable === false);
9416
9468
  }, ...(ngDevMode ? [{ debugName: "identifierNotFilterable" }] : []));
9469
+ /**
9470
+ * La modalita' attiva. La **formula si riconosce dal suo campo** e non da `filterLogic`: il
9471
+ * valore `Formula` non e' supportato da nessun elemento (§4.4), quindi non puo' essere cio' che
9472
+ * accende la modalita' — altrimenti l'editor riscriverebbe nel documento un valore che fa
9473
+ * fallire l'esecuzione.
9474
+ */
9417
9475
  logicMode = computed(() => {
9476
+ if (this.supportsFormula() && this.holder().filterFormula !== undefined) {
9477
+ return 'formula';
9478
+ }
9418
9479
  const logic = this.holder().filterLogic;
9419
9480
  if (!logic) {
9420
9481
  return 'and';
9421
9482
  }
9422
- const normalized = logic?.trim().toLowerCase();
9483
+ const normalized = logic.trim().toLowerCase();
9423
9484
  if (normalized === 'and') {
9424
9485
  return 'and';
9425
9486
  }
@@ -9427,14 +9488,39 @@ class RecordFilterEditorComponent {
9427
9488
  return 'or';
9428
9489
  }
9429
9490
  if (normalized === 'formula') {
9430
- return 'formula';
9491
+ // Valore non supportato: si segnala (`unsupportedFormulaLogic`) e nel frattempo i filtri
9492
+ // si combinano come dice il default, che e' AND.
9493
+ return 'and';
9431
9494
  }
9432
9495
  return 'custom';
9433
9496
  }, ...(ngDevMode ? [{ debugName: "logicMode" }] : []));
9497
+ /**
9498
+ * `filterLogic: "Formula"` letto dal documento. Non lo scrive nessun gesto di questo editor:
9499
+ * arriva da un flow scritto altrove, e va detto invece di essere corretto di soppiatto —
9500
+ * cancellarlo sarebbe una modifica che l'utente non ha chiesto, su un campo che potrebbe
9501
+ * essere l'unico indizio di cosa quel flow voleva fare.
9502
+ */
9503
+ unsupportedFormulaLogic = computed(() => this.holder().filterLogic?.trim().toLowerCase() === 'formula', ...(ngDevMode ? [{ debugName: "unsupportedFormulaLogic" }] : []));
9434
9504
  customLogic = computed(() => {
9435
9505
  const mode = this.logicMode();
9436
9506
  return mode === 'custom' ? this.holder().filterLogic : '';
9437
9507
  }, ...(ngDevMode ? [{ debugName: "customLogic" }] : []));
9508
+ /**
9509
+ * Il riscontro locale sull'espressione: indici che non esistono, parentesi sbilanciate, termini
9510
+ * rimasti orfani (`?`) dopo la cancellazione di un filtro. La verita' resta della validazione
9511
+ * del backend (`CONDITION_LOGIC_INVALID`, che e' un **errore** e blocca l'attivazione), ma
9512
+ * scoprirlo lì significa scoprirlo dopo.
9513
+ */
9514
+ customLogicError = computed(() => {
9515
+ if (this.logicMode() !== 'custom') {
9516
+ return null;
9517
+ }
9518
+ const logic = this.customLogic();
9519
+ if (logic.includes('?')) {
9520
+ return 'Un termine si riferiva a un filtro cancellato: correggi l’espressione.';
9521
+ }
9522
+ return checkConditionLogic(logic, this.filters().length, 'filtro');
9523
+ }, ...(ngDevMode ? [{ debugName: "customLogicError" }] : []));
9438
9524
  showEmptyWarning = computed(() => !!this.emptyWarning() && this.filters().length === 0, ...(ngDevMode ? [{ debugName: "showEmptyWarning" }] : []));
9439
9525
  addFilter() {
9440
9526
  this.changed.emit((holder) => {
@@ -9444,12 +9530,22 @@ class RecordFilterEditorComponent {
9444
9530
  }
9445
9531
  removeFilter(index) {
9446
9532
  this.changed.emit((holder) => {
9533
+ const count = holder.filters?.length ?? 0;
9447
9534
  holder.filters?.splice(index, 1);
9448
9535
  if (holder.filters && holder.filters.length === 0) {
9449
9536
  // Lista vuota dichiarata vs assente: il backend le distingue, ma per un diff
9450
9537
  // pulito conviene omettere le liste vuote (§2).
9451
9538
  delete holder.filters;
9452
9539
  }
9540
+ // Gli indici dei filtri dopo quello cancellato scalano: senza riscrivere l'espressione
9541
+ // resterebbe valida ma puntata sul filtro sbagliato, che e' il difetto peggiore dei due.
9542
+ if (isCustomConditionLogic(holder.filterLogic)) {
9543
+ const mapping = [];
9544
+ for (let position = 0; position < count; position += 1) {
9545
+ mapping.push(position < index ? position : position === index ? -1 : position - 1);
9546
+ }
9547
+ holder.filterLogic = remapConditionLogic(holder.filterLogic, mapping).logic;
9548
+ }
9453
9549
  });
9454
9550
  }
9455
9551
  setField(index, field) {
@@ -9497,6 +9593,12 @@ class RecordFilterEditorComponent {
9497
9593
  }
9498
9594
  setLogicMode(mode) {
9499
9595
  this.changed.emit((holder) => {
9596
+ if (mode !== 'formula') {
9597
+ // Uscendo dalla formula il campo se ne va con la modalita': lasciarlo scritto vorrebbe
9598
+ // dire mandare filtri **e** formula, cioe' due criteri e nessun modo di sapere quale
9599
+ // vince (§4.4 la chiama «alternativa ai filtri»).
9600
+ delete holder.filterFormula;
9601
+ }
9500
9602
  switch (mode) {
9501
9603
  case 'and':
9502
9604
  holder.filterLogic = 'and';
@@ -9512,7 +9614,9 @@ class RecordFilterEditorComponent {
9512
9614
  break;
9513
9615
  }
9514
9616
  case 'formula':
9515
- holder.filterLogic = 'Formula';
9617
+ // **Non** `filterLogic: 'Formula'`: quel valore non e' supportato (§4.4). La modalita'
9618
+ // e' la presenza del campo, e la logica dei filtri non c'entra piu' niente.
9619
+ delete holder.filterLogic;
9516
9620
  holder.filterFormula ??= '';
9517
9621
  break;
9518
9622
  }
@@ -9563,11 +9667,11 @@ class RecordFilterEditorComponent {
9563
9667
  return this.types.valueSetOf(filter.field);
9564
9668
  }
9565
9669
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: RecordFilterEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
9566
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.28", type: RecordFilterEditorComponent, isStandalone: true, selector: "fb-record-filter-editor", inputs: { holder: { classPropertyName: "holder", publicName: "holder", isSignal: true, isRequired: true, transformFunction: null }, object: { classPropertyName: "object", publicName: "object", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, usage: { classPropertyName: "usage", publicName: "usage", isSignal: true, isRequired: false, transformFunction: null }, fieldOptions: { classPropertyName: "fieldOptions", publicName: "fieldOptions", isSignal: true, isRequired: false, transformFunction: null }, supportsLogic: { classPropertyName: "supportsLogic", publicName: "supportsLogic", isSignal: true, isRequired: false, transformFunction: null }, supportsFormula: { classPropertyName: "supportsFormula", publicName: "supportsFormula", isSignal: true, isRequired: false, transformFunction: null }, emptyWarning: { classPropertyName: "emptyWarning", publicName: "emptyWarning", isSignal: true, isRequired: false, transformFunction: null }, emptyWarningSeverity: { classPropertyName: "emptyWarningSeverity", publicName: "emptyWarningSeverity", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { changed: "changed" }, ngImport: i0, template: "<fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">{{ title() }}</legend>\r\n\r\n @if (!object() && !fieldOptions().length) {\r\n <p class=\"fb-field__hint\">Scegli prima un oggetto per poter filtrare sui suoi campi.</p>\r\n }\r\n\r\n @if (supportsLogic()) {\r\n <div class=\"fb-filter__modes\" role=\"group\" aria-label=\"Logica dei filtri\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"logicMode() === 'and'\"\r\n (click)=\"setLogicMode('and')\"\r\n >\r\n Tutti (AND)\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"logicMode() === 'or'\"\r\n (click)=\"setLogicMode('or')\"\r\n >\r\n Almeno uno (OR)\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"logicMode() === 'custom'\"\r\n (click)=\"setLogicMode('custom')\"\r\n >\r\n Espressione\r\n </button>\r\n @if (supportsFormula()) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"logicMode() === 'formula'\"\r\n (click)=\"setLogicMode('formula')\"\r\n >\r\n Formula\r\n </button>\r\n }\r\n </div>\r\n } @else {\r\n <!-- Qui il modello non ha `filterLogic`: mostrarlo lo farebbe perdere al salvataggio. -->\r\n <p class=\"fb-field__hint\">Su questo elemento i filtri sono sempre combinati in AND.</p>\r\n }\r\n\r\n @if (supportsLogic() && logicMode() === 'custom') {\r\n <div class=\"fb-field\">\r\n <input\r\n class=\"fb-input fb-input--mono\"\r\n [value]=\"customLogic()\"\r\n placeholder=\"1 AND (2 OR 3)\"\r\n aria-label=\"Espressione sugli indici dei filtri\"\r\n (input)=\"setCustomLogic($any($event.target).value)\"\r\n />\r\n </div>\r\n }\r\n\r\n @if (supportsFormula() && logicMode() === 'formula') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Formula di filtro</label>\r\n <fb-formula-editor\r\n [expression]=\"holder().filterFormula || ''\"\r\n usage=\"Condition\"\r\n expectedDataType=\"Boolean\"\r\n ariaLabel=\"Formula di filtro\"\r\n (expressionChange)=\"setFormula($event)\"\r\n >\r\n <p class=\"fb-field__hint\">Valutata dal motore di regole, in alternativa ai filtri.</p>\r\n </fb-formula-editor>\r\n </div>\r\n }\r\n\r\n @if (identifierNotFilterable()) {\r\n <p class=\"fb-field__hint\">\r\n Su questo oggetto l\u2019identificativo non e\u2019 filtrabile: la chiave e\u2019 composta e porta la propria\r\n forma canonica. Filtra per le colonne della chiave, una per colonna.\r\n </p>\r\n }\r\n\r\n @if (showEmptyWarning()) {\r\n <p\r\n class=\"fb-callout\"\r\n [class.fb-callout--warn]=\"emptyWarningSeverity() === 'warn'\"\r\n [class.fb-callout--error]=\"emptyWarningSeverity() === 'error'\"\r\n >\r\n {{ emptyWarning() }}\r\n </p>\r\n }\r\n\r\n <div class=\"fb-list\">\r\n @for (filter of filters(); track $index) {\r\n <div class=\"fb-list__item\">\r\n <div class=\"fb-list__header\">\r\n <span class=\"fb-list__index\">{{ $index + 1 }}</span>\r\n <span class=\"fb-list__spacer\"></span>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n aria-label=\"Rimuovi il filtro\"\r\n (click)=\"removeFilter($index)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n\r\n <!-- Campo, operatore e valore sono una frase sola: si leggono in riga. -->\r\n <div class=\"fb-fields-row\">\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Campo</label>\r\n @if (fieldOptions().length) {\r\n <!-- Insieme chiuso: qui non c'e' un catalogo incompleto da compensare. -->\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"filter.field || ''\"\r\n aria-label=\"Campo del filtro\"\r\n (change)=\"setField($index, $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 scegli \u2014</option>\r\n @for (option of fieldOptions(); track option.value) {\r\n <option [value]=\"option.value\">{{ option.label }}</option>\r\n }\r\n </select>\r\n @if (unknownFixedField(filter)) {\r\n <p class=\"fb-field__error\">\r\n \u00AB{{ filter.field }}\u00BB non e\u2019 fra i campi citabili qui (FIELD_UNKNOWN).\r\n </p>\r\n }\r\n } @else {\r\n <fb-field-picker\r\n [value]=\"filter.field\"\r\n [object]=\"object()\"\r\n [usage]=\"usage()\"\r\n placeholder=\"Scrivi o scegli un campo\"\r\n (valueChange)=\"setField($index, $event ?? '')\"\r\n />\r\n }\r\n </div>\r\n\r\n <div class=\"fb-field fb-field--compact\">\r\n <label class=\"fb-field__label\">Operatore</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"filter.operator || ''\"\r\n (change)=\"setOperator($index, $any($event.target).value)\"\r\n >\r\n @for (operator of operators(); track operator.value) {\r\n <option [value]=\"operator.value\">{{ operator.label }}</option>\r\n }\r\n </select>\r\n </div>\r\n\r\n @if (isNullOperator(filter)) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Esito atteso</label>\r\n <div class=\"fb-filter__modes\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"nullExpectation(filter)\"\r\n (click)=\"setNullExpectation($index, true)\"\r\n >\r\n \u00E8 vuoto\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"!nullExpectation(filter)\"\r\n (click)=\"setNullExpectation($index, false)\"\r\n >\r\n non \u00E8 vuoto\r\n </button>\r\n </div>\r\n </div>\r\n } @else {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Valore</label>\r\n <fb-value-editor\r\n [value]=\"filter.value\"\r\n [dataType]=\"fieldDataType(filter)\"\r\n [objectType]=\"fieldObjectType(filter)\"\r\n [valueSet]=\"fieldValueSet(filter)\"\r\n label=\"Valore del filtro\"\r\n [allowFormula]=\"false\"\r\n (valueChange)=\"setValue($index, $event)\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n } @empty {\r\n <p class=\"fb-empty\">Nessun filtro.</p>\r\n }\r\n </div>\r\n\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"addFilter()\">Aggiungi filtro</button>\r\n</fieldset>\r\n", styles: [":host{display:block}.fb-filter__modes{margin-bottom:8px}.fb-field .fb-filter__modes{margin-bottom:0}\n"], dependencies: [{ kind: "component", type: FieldPickerComponent, selector: "fb-field-picker", inputs: ["value", "object", "usage", "label", "placeholder", "disabled"], outputs: ["valueChange"] }, { kind: "component", type: FormulaEditorComponent, selector: "fb-formula-editor", inputs: ["expression", "usage", "expectedDataType", "scale", "placeholder", "ariaLabel", "disabled", "rows", "commitOn"], outputs: ["expressionChange"] }, { kind: "component", type: ValueEditorComponent, selector: "fb-value-editor", inputs: ["value", "label", "dataType", "objectType", "isCollection", "valueSet", "disabled", "allowFormula"], outputs: ["valueChange"] }, { kind: "directive", type: SelectValueDirective, selector: "select[fbValue]", inputs: ["fbValue"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
9670
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.28", type: RecordFilterEditorComponent, isStandalone: true, selector: "fb-record-filter-editor", inputs: { holder: { classPropertyName: "holder", publicName: "holder", isSignal: true, isRequired: true, transformFunction: null }, object: { classPropertyName: "object", publicName: "object", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, usage: { classPropertyName: "usage", publicName: "usage", isSignal: true, isRequired: false, transformFunction: null }, fieldOptions: { classPropertyName: "fieldOptions", publicName: "fieldOptions", isSignal: true, isRequired: false, transformFunction: null }, supportsLogic: { classPropertyName: "supportsLogic", publicName: "supportsLogic", isSignal: true, isRequired: false, transformFunction: null }, supportsFormula: { classPropertyName: "supportsFormula", publicName: "supportsFormula", isSignal: true, isRequired: false, transformFunction: null }, emptyWarning: { classPropertyName: "emptyWarning", publicName: "emptyWarning", isSignal: true, isRequired: false, transformFunction: null }, emptyWarningSeverity: { classPropertyName: "emptyWarningSeverity", publicName: "emptyWarningSeverity", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { changed: "changed" }, ngImport: i0, template: "<fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">{{ title() }}</legend>\r\n\r\n @if (!object() && !fieldOptions().length) {\r\n <p class=\"fb-field__hint\">Scegli prima un oggetto per poter filtrare sui suoi campi.</p>\r\n }\r\n\r\n @if (supportsLogic()) {\r\n <div class=\"fb-filter__modes\" role=\"group\" aria-label=\"Logica dei filtri\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"logicMode() === 'and'\"\r\n (click)=\"setLogicMode('and')\"\r\n >\r\n Tutti (AND)\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"logicMode() === 'or'\"\r\n (click)=\"setLogicMode('or')\"\r\n >\r\n Almeno uno (OR)\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"logicMode() === 'custom'\"\r\n (click)=\"setLogicMode('custom')\"\r\n >\r\n Espressione\r\n </button>\r\n @if (supportsFormula()) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"logicMode() === 'formula'\"\r\n (click)=\"setLogicMode('formula')\"\r\n >\r\n Formula\r\n </button>\r\n }\r\n </div>\r\n } @else {\r\n <!-- Qui il modello non ha `filterLogic`: mostrarlo lo farebbe perdere al salvataggio. -->\r\n <p class=\"fb-field__hint\">Su questo elemento i filtri sono sempre combinati in AND.</p>\r\n }\r\n\r\n @if (supportsLogic() && logicMode() === 'custom') {\r\n <div class=\"fb-field\">\r\n <input\r\n class=\"fb-input fb-input--mono\"\r\n [class.fb-input--invalid]=\"!!customLogicError()\"\r\n [value]=\"customLogic()\"\r\n placeholder=\"1 AND (2 OR 3)\"\r\n aria-label=\"Espressione sugli indici dei filtri\"\r\n (input)=\"setCustomLogic($any($event.target).value)\"\r\n />\r\n <!--\r\n Gli indici sono quelli numerati nell\u2019elenco qui sotto, e sono 1-based. Un indice\r\n inesistente e\u2019 `CONDITION_LOGIC_INVALID`, che e\u2019 un **errore**: blocca l\u2019attivazione,\r\n quindi conviene dirlo mentre si scrive e non alla validazione.\r\n -->\r\n @if (customLogicError()) {\r\n <p class=\"fb-field__error\">{{ customLogicError() }}</p>\r\n } @else {\r\n <p class=\"fb-field__hint\">\r\n Gli indici sono 1-based e si riferiscono ai filtri numerati sotto. Cancellare un filtro\r\n riscrive l\u2019espressione automaticamente.\r\n </p>\r\n }\r\n </div>\r\n }\r\n\r\n <!--\r\n `filterLogic: \"Formula\"` non e\u2019 supportato da nessun elemento (\u00A74.4): non lo scrive nessun\r\n gesto di questo editor, ma un flow scritto altrove puo\u2019 portarcelo. Si dice invece di\r\n correggerlo di soppiatto \u2014 e\u2019 l\u2019unico indizio di cosa quel flow voleva fare.\r\n -->\r\n @if (unsupportedFormulaLogic()) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n <code>filterLogic: \"Formula\"</code> non e\u2019 un valore supportato: sui choice set e\u2019 un errore\r\n di validazione, altrove fa fallire l\u2019esecuzione. I filtri qui sotto si combinano in AND.\r\n @if (supportsFormula()) {\r\n Per una formula usa la modalita\u2019 \u00ABFormula\u00BB, che scrive <code>filterFormula</code>.\r\n }\r\n </p>\r\n }\r\n\r\n @if (supportsFormula() && logicMode() === 'formula') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Formula di filtro</label>\r\n <fb-formula-editor\r\n [expression]=\"holder().filterFormula || ''\"\r\n usage=\"Condition\"\r\n expectedDataType=\"Boolean\"\r\n ariaLabel=\"Formula di filtro\"\r\n (expressionChange)=\"setFormula($event)\"\r\n >\r\n <p class=\"fb-field__hint\">\r\n Valutata dal motore di regole, <strong>in alternativa</strong> ai filtri: sta nel campo\r\n <code>filterFormula</code>, e la logica dei filtri non si applica.\r\n </p>\r\n </fb-formula-editor>\r\n </div>\r\n }\r\n\r\n @if (identifierNotFilterable()) {\r\n <p class=\"fb-field__hint\">\r\n Su questo oggetto l\u2019identificativo non e\u2019 filtrabile: la chiave e\u2019 composta e porta la propria\r\n forma canonica. Filtra per le colonne della chiave, una per colonna.\r\n </p>\r\n }\r\n\r\n @if (showEmptyWarning()) {\r\n <p\r\n class=\"fb-callout\"\r\n [class.fb-callout--warn]=\"emptyWarningSeverity() === 'warn'\"\r\n [class.fb-callout--error]=\"emptyWarningSeverity() === 'error'\"\r\n >\r\n {{ emptyWarning() }}\r\n </p>\r\n }\r\n\r\n <div class=\"fb-list\">\r\n @for (filter of filters(); track $index) {\r\n <div class=\"fb-list__item\">\r\n <div class=\"fb-list__header\">\r\n <span class=\"fb-list__index\">{{ $index + 1 }}</span>\r\n <span class=\"fb-list__spacer\"></span>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n aria-label=\"Rimuovi il filtro\"\r\n (click)=\"removeFilter($index)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n\r\n <!-- Campo, operatore e valore sono una frase sola: si leggono in riga. -->\r\n <div class=\"fb-fields-row\">\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Campo</label>\r\n @if (fieldOptions().length) {\r\n <!-- Insieme chiuso: qui non c'e' un catalogo incompleto da compensare. -->\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"filter.field || ''\"\r\n aria-label=\"Campo del filtro\"\r\n (change)=\"setField($index, $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 scegli \u2014</option>\r\n @for (option of fieldOptions(); track option.value) {\r\n <option [value]=\"option.value\">{{ option.label }}</option>\r\n }\r\n </select>\r\n @if (unknownFixedField(filter)) {\r\n <p class=\"fb-field__error\">\r\n \u00AB{{ filter.field }}\u00BB non e\u2019 fra i campi citabili qui (FIELD_UNKNOWN).\r\n </p>\r\n }\r\n } @else {\r\n <fb-field-picker\r\n [value]=\"filter.field\"\r\n [object]=\"object()\"\r\n [usage]=\"usage()\"\r\n placeholder=\"Scrivi o scegli un campo\"\r\n (valueChange)=\"setField($index, $event ?? '')\"\r\n />\r\n }\r\n </div>\r\n\r\n <div class=\"fb-field fb-field--compact\">\r\n <label class=\"fb-field__label\">Operatore</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"filter.operator || ''\"\r\n (change)=\"setOperator($index, $any($event.target).value)\"\r\n >\r\n @for (operator of operators(); track operator.value) {\r\n <option [value]=\"operator.value\">{{ operator.label }}</option>\r\n }\r\n </select>\r\n </div>\r\n\r\n @if (isNullOperator(filter)) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Esito atteso</label>\r\n <div class=\"fb-filter__modes\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"nullExpectation(filter)\"\r\n (click)=\"setNullExpectation($index, true)\"\r\n >\r\n \u00E8 vuoto\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"!nullExpectation(filter)\"\r\n (click)=\"setNullExpectation($index, false)\"\r\n >\r\n non \u00E8 vuoto\r\n </button>\r\n </div>\r\n </div>\r\n } @else {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Valore</label>\r\n <fb-value-editor\r\n [value]=\"filter.value\"\r\n [dataType]=\"fieldDataType(filter)\"\r\n [objectType]=\"fieldObjectType(filter)\"\r\n [valueSet]=\"fieldValueSet(filter)\"\r\n label=\"Valore del filtro\"\r\n [allowFormula]=\"false\"\r\n (valueChange)=\"setValue($index, $event)\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n } @empty {\r\n <p class=\"fb-empty\">Nessun filtro.</p>\r\n }\r\n </div>\r\n\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"addFilter()\">Aggiungi filtro</button>\r\n</fieldset>\r\n", styles: [":host{display:block}.fb-filter__modes{margin-bottom:8px}.fb-field .fb-filter__modes{margin-bottom:0}\n"], dependencies: [{ kind: "component", type: FieldPickerComponent, selector: "fb-field-picker", inputs: ["value", "object", "usage", "label", "placeholder", "disabled"], outputs: ["valueChange"] }, { kind: "component", type: FormulaEditorComponent, selector: "fb-formula-editor", inputs: ["expression", "usage", "expectedDataType", "scale", "placeholder", "ariaLabel", "disabled", "rows", "commitOn"], outputs: ["expressionChange"] }, { kind: "component", type: ValueEditorComponent, selector: "fb-value-editor", inputs: ["value", "label", "dataType", "objectType", "isCollection", "valueSet", "disabled", "allowFormula"], outputs: ["valueChange"] }, { kind: "directive", type: SelectValueDirective, selector: "select[fbValue]", inputs: ["fbValue"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
9567
9671
  }
9568
9672
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: RecordFilterEditorComponent, decorators: [{
9569
9673
  type: Component,
9570
- args: [{ selector: 'fb-record-filter-editor', standalone: true, imports: [FieldPickerComponent, FormulaEditorComponent, ValueEditorComponent, SelectValueDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">{{ title() }}</legend>\r\n\r\n @if (!object() && !fieldOptions().length) {\r\n <p class=\"fb-field__hint\">Scegli prima un oggetto per poter filtrare sui suoi campi.</p>\r\n }\r\n\r\n @if (supportsLogic()) {\r\n <div class=\"fb-filter__modes\" role=\"group\" aria-label=\"Logica dei filtri\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"logicMode() === 'and'\"\r\n (click)=\"setLogicMode('and')\"\r\n >\r\n Tutti (AND)\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"logicMode() === 'or'\"\r\n (click)=\"setLogicMode('or')\"\r\n >\r\n Almeno uno (OR)\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"logicMode() === 'custom'\"\r\n (click)=\"setLogicMode('custom')\"\r\n >\r\n Espressione\r\n </button>\r\n @if (supportsFormula()) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"logicMode() === 'formula'\"\r\n (click)=\"setLogicMode('formula')\"\r\n >\r\n Formula\r\n </button>\r\n }\r\n </div>\r\n } @else {\r\n <!-- Qui il modello non ha `filterLogic`: mostrarlo lo farebbe perdere al salvataggio. -->\r\n <p class=\"fb-field__hint\">Su questo elemento i filtri sono sempre combinati in AND.</p>\r\n }\r\n\r\n @if (supportsLogic() && logicMode() === 'custom') {\r\n <div class=\"fb-field\">\r\n <input\r\n class=\"fb-input fb-input--mono\"\r\n [value]=\"customLogic()\"\r\n placeholder=\"1 AND (2 OR 3)\"\r\n aria-label=\"Espressione sugli indici dei filtri\"\r\n (input)=\"setCustomLogic($any($event.target).value)\"\r\n />\r\n </div>\r\n }\r\n\r\n @if (supportsFormula() && logicMode() === 'formula') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Formula di filtro</label>\r\n <fb-formula-editor\r\n [expression]=\"holder().filterFormula || ''\"\r\n usage=\"Condition\"\r\n expectedDataType=\"Boolean\"\r\n ariaLabel=\"Formula di filtro\"\r\n (expressionChange)=\"setFormula($event)\"\r\n >\r\n <p class=\"fb-field__hint\">Valutata dal motore di regole, in alternativa ai filtri.</p>\r\n </fb-formula-editor>\r\n </div>\r\n }\r\n\r\n @if (identifierNotFilterable()) {\r\n <p class=\"fb-field__hint\">\r\n Su questo oggetto l\u2019identificativo non e\u2019 filtrabile: la chiave e\u2019 composta e porta la propria\r\n forma canonica. Filtra per le colonne della chiave, una per colonna.\r\n </p>\r\n }\r\n\r\n @if (showEmptyWarning()) {\r\n <p\r\n class=\"fb-callout\"\r\n [class.fb-callout--warn]=\"emptyWarningSeverity() === 'warn'\"\r\n [class.fb-callout--error]=\"emptyWarningSeverity() === 'error'\"\r\n >\r\n {{ emptyWarning() }}\r\n </p>\r\n }\r\n\r\n <div class=\"fb-list\">\r\n @for (filter of filters(); track $index) {\r\n <div class=\"fb-list__item\">\r\n <div class=\"fb-list__header\">\r\n <span class=\"fb-list__index\">{{ $index + 1 }}</span>\r\n <span class=\"fb-list__spacer\"></span>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n aria-label=\"Rimuovi il filtro\"\r\n (click)=\"removeFilter($index)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n\r\n <!-- Campo, operatore e valore sono una frase sola: si leggono in riga. -->\r\n <div class=\"fb-fields-row\">\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Campo</label>\r\n @if (fieldOptions().length) {\r\n <!-- Insieme chiuso: qui non c'e' un catalogo incompleto da compensare. -->\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"filter.field || ''\"\r\n aria-label=\"Campo del filtro\"\r\n (change)=\"setField($index, $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 scegli \u2014</option>\r\n @for (option of fieldOptions(); track option.value) {\r\n <option [value]=\"option.value\">{{ option.label }}</option>\r\n }\r\n </select>\r\n @if (unknownFixedField(filter)) {\r\n <p class=\"fb-field__error\">\r\n \u00AB{{ filter.field }}\u00BB non e\u2019 fra i campi citabili qui (FIELD_UNKNOWN).\r\n </p>\r\n }\r\n } @else {\r\n <fb-field-picker\r\n [value]=\"filter.field\"\r\n [object]=\"object()\"\r\n [usage]=\"usage()\"\r\n placeholder=\"Scrivi o scegli un campo\"\r\n (valueChange)=\"setField($index, $event ?? '')\"\r\n />\r\n }\r\n </div>\r\n\r\n <div class=\"fb-field fb-field--compact\">\r\n <label class=\"fb-field__label\">Operatore</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"filter.operator || ''\"\r\n (change)=\"setOperator($index, $any($event.target).value)\"\r\n >\r\n @for (operator of operators(); track operator.value) {\r\n <option [value]=\"operator.value\">{{ operator.label }}</option>\r\n }\r\n </select>\r\n </div>\r\n\r\n @if (isNullOperator(filter)) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Esito atteso</label>\r\n <div class=\"fb-filter__modes\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"nullExpectation(filter)\"\r\n (click)=\"setNullExpectation($index, true)\"\r\n >\r\n \u00E8 vuoto\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"!nullExpectation(filter)\"\r\n (click)=\"setNullExpectation($index, false)\"\r\n >\r\n non \u00E8 vuoto\r\n </button>\r\n </div>\r\n </div>\r\n } @else {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Valore</label>\r\n <fb-value-editor\r\n [value]=\"filter.value\"\r\n [dataType]=\"fieldDataType(filter)\"\r\n [objectType]=\"fieldObjectType(filter)\"\r\n [valueSet]=\"fieldValueSet(filter)\"\r\n label=\"Valore del filtro\"\r\n [allowFormula]=\"false\"\r\n (valueChange)=\"setValue($index, $event)\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n } @empty {\r\n <p class=\"fb-empty\">Nessun filtro.</p>\r\n }\r\n </div>\r\n\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"addFilter()\">Aggiungi filtro</button>\r\n</fieldset>\r\n", styles: [":host{display:block}.fb-filter__modes{margin-bottom:8px}.fb-field .fb-filter__modes{margin-bottom:0}\n"] }]
9674
+ args: [{ selector: 'fb-record-filter-editor', standalone: true, imports: [FieldPickerComponent, FormulaEditorComponent, ValueEditorComponent, SelectValueDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">{{ title() }}</legend>\r\n\r\n @if (!object() && !fieldOptions().length) {\r\n <p class=\"fb-field__hint\">Scegli prima un oggetto per poter filtrare sui suoi campi.</p>\r\n }\r\n\r\n @if (supportsLogic()) {\r\n <div class=\"fb-filter__modes\" role=\"group\" aria-label=\"Logica dei filtri\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"logicMode() === 'and'\"\r\n (click)=\"setLogicMode('and')\"\r\n >\r\n Tutti (AND)\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"logicMode() === 'or'\"\r\n (click)=\"setLogicMode('or')\"\r\n >\r\n Almeno uno (OR)\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"logicMode() === 'custom'\"\r\n (click)=\"setLogicMode('custom')\"\r\n >\r\n Espressione\r\n </button>\r\n @if (supportsFormula()) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"logicMode() === 'formula'\"\r\n (click)=\"setLogicMode('formula')\"\r\n >\r\n Formula\r\n </button>\r\n }\r\n </div>\r\n } @else {\r\n <!-- Qui il modello non ha `filterLogic`: mostrarlo lo farebbe perdere al salvataggio. -->\r\n <p class=\"fb-field__hint\">Su questo elemento i filtri sono sempre combinati in AND.</p>\r\n }\r\n\r\n @if (supportsLogic() && logicMode() === 'custom') {\r\n <div class=\"fb-field\">\r\n <input\r\n class=\"fb-input fb-input--mono\"\r\n [class.fb-input--invalid]=\"!!customLogicError()\"\r\n [value]=\"customLogic()\"\r\n placeholder=\"1 AND (2 OR 3)\"\r\n aria-label=\"Espressione sugli indici dei filtri\"\r\n (input)=\"setCustomLogic($any($event.target).value)\"\r\n />\r\n <!--\r\n Gli indici sono quelli numerati nell\u2019elenco qui sotto, e sono 1-based. Un indice\r\n inesistente e\u2019 `CONDITION_LOGIC_INVALID`, che e\u2019 un **errore**: blocca l\u2019attivazione,\r\n quindi conviene dirlo mentre si scrive e non alla validazione.\r\n -->\r\n @if (customLogicError()) {\r\n <p class=\"fb-field__error\">{{ customLogicError() }}</p>\r\n } @else {\r\n <p class=\"fb-field__hint\">\r\n Gli indici sono 1-based e si riferiscono ai filtri numerati sotto. Cancellare un filtro\r\n riscrive l\u2019espressione automaticamente.\r\n </p>\r\n }\r\n </div>\r\n }\r\n\r\n <!--\r\n `filterLogic: \"Formula\"` non e\u2019 supportato da nessun elemento (\u00A74.4): non lo scrive nessun\r\n gesto di questo editor, ma un flow scritto altrove puo\u2019 portarcelo. Si dice invece di\r\n correggerlo di soppiatto \u2014 e\u2019 l\u2019unico indizio di cosa quel flow voleva fare.\r\n -->\r\n @if (unsupportedFormulaLogic()) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n <code>filterLogic: \"Formula\"</code> non e\u2019 un valore supportato: sui choice set e\u2019 un errore\r\n di validazione, altrove fa fallire l\u2019esecuzione. I filtri qui sotto si combinano in AND.\r\n @if (supportsFormula()) {\r\n Per una formula usa la modalita\u2019 \u00ABFormula\u00BB, che scrive <code>filterFormula</code>.\r\n }\r\n </p>\r\n }\r\n\r\n @if (supportsFormula() && logicMode() === 'formula') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Formula di filtro</label>\r\n <fb-formula-editor\r\n [expression]=\"holder().filterFormula || ''\"\r\n usage=\"Condition\"\r\n expectedDataType=\"Boolean\"\r\n ariaLabel=\"Formula di filtro\"\r\n (expressionChange)=\"setFormula($event)\"\r\n >\r\n <p class=\"fb-field__hint\">\r\n Valutata dal motore di regole, <strong>in alternativa</strong> ai filtri: sta nel campo\r\n <code>filterFormula</code>, e la logica dei filtri non si applica.\r\n </p>\r\n </fb-formula-editor>\r\n </div>\r\n }\r\n\r\n @if (identifierNotFilterable()) {\r\n <p class=\"fb-field__hint\">\r\n Su questo oggetto l\u2019identificativo non e\u2019 filtrabile: la chiave e\u2019 composta e porta la propria\r\n forma canonica. Filtra per le colonne della chiave, una per colonna.\r\n </p>\r\n }\r\n\r\n @if (showEmptyWarning()) {\r\n <p\r\n class=\"fb-callout\"\r\n [class.fb-callout--warn]=\"emptyWarningSeverity() === 'warn'\"\r\n [class.fb-callout--error]=\"emptyWarningSeverity() === 'error'\"\r\n >\r\n {{ emptyWarning() }}\r\n </p>\r\n }\r\n\r\n <div class=\"fb-list\">\r\n @for (filter of filters(); track $index) {\r\n <div class=\"fb-list__item\">\r\n <div class=\"fb-list__header\">\r\n <span class=\"fb-list__index\">{{ $index + 1 }}</span>\r\n <span class=\"fb-list__spacer\"></span>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n aria-label=\"Rimuovi il filtro\"\r\n (click)=\"removeFilter($index)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n\r\n <!-- Campo, operatore e valore sono una frase sola: si leggono in riga. -->\r\n <div class=\"fb-fields-row\">\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Campo</label>\r\n @if (fieldOptions().length) {\r\n <!-- Insieme chiuso: qui non c'e' un catalogo incompleto da compensare. -->\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"filter.field || ''\"\r\n aria-label=\"Campo del filtro\"\r\n (change)=\"setField($index, $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 scegli \u2014</option>\r\n @for (option of fieldOptions(); track option.value) {\r\n <option [value]=\"option.value\">{{ option.label }}</option>\r\n }\r\n </select>\r\n @if (unknownFixedField(filter)) {\r\n <p class=\"fb-field__error\">\r\n \u00AB{{ filter.field }}\u00BB non e\u2019 fra i campi citabili qui (FIELD_UNKNOWN).\r\n </p>\r\n }\r\n } @else {\r\n <fb-field-picker\r\n [value]=\"filter.field\"\r\n [object]=\"object()\"\r\n [usage]=\"usage()\"\r\n placeholder=\"Scrivi o scegli un campo\"\r\n (valueChange)=\"setField($index, $event ?? '')\"\r\n />\r\n }\r\n </div>\r\n\r\n <div class=\"fb-field fb-field--compact\">\r\n <label class=\"fb-field__label\">Operatore</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"filter.operator || ''\"\r\n (change)=\"setOperator($index, $any($event.target).value)\"\r\n >\r\n @for (operator of operators(); track operator.value) {\r\n <option [value]=\"operator.value\">{{ operator.label }}</option>\r\n }\r\n </select>\r\n </div>\r\n\r\n @if (isNullOperator(filter)) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Esito atteso</label>\r\n <div class=\"fb-filter__modes\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"nullExpectation(filter)\"\r\n (click)=\"setNullExpectation($index, true)\"\r\n >\r\n \u00E8 vuoto\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"!nullExpectation(filter)\"\r\n (click)=\"setNullExpectation($index, false)\"\r\n >\r\n non \u00E8 vuoto\r\n </button>\r\n </div>\r\n </div>\r\n } @else {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Valore</label>\r\n <fb-value-editor\r\n [value]=\"filter.value\"\r\n [dataType]=\"fieldDataType(filter)\"\r\n [objectType]=\"fieldObjectType(filter)\"\r\n [valueSet]=\"fieldValueSet(filter)\"\r\n label=\"Valore del filtro\"\r\n [allowFormula]=\"false\"\r\n (valueChange)=\"setValue($index, $event)\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n } @empty {\r\n <p class=\"fb-empty\">Nessun filtro.</p>\r\n }\r\n </div>\r\n\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"addFilter()\">Aggiungi filtro</button>\r\n</fieldset>\r\n", styles: [":host{display:block}.fb-filter__modes{margin-bottom:8px}.fb-field .fb-filter__modes{margin-bottom:0}\n"] }]
9571
9675
  }], ctorParameters: () => [], propDecorators: { holder: [{ type: i0.Input, args: [{ isSignal: true, alias: "holder", required: true }] }], object: [{ type: i0.Input, args: [{ isSignal: true, alias: "object", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], usage: [{ type: i0.Input, args: [{ isSignal: true, alias: "usage", required: false }] }], fieldOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "fieldOptions", required: false }] }], supportsLogic: [{ type: i0.Input, args: [{ isSignal: true, alias: "supportsLogic", required: false }] }], supportsFormula: [{ type: i0.Input, args: [{ isSignal: true, alias: "supportsFormula", required: false }] }], emptyWarning: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyWarning", required: false }] }], emptyWarningSeverity: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyWarningSeverity", required: false }] }], changed: [{ type: i0.Output, args: ["changed"] }] } });
9572
9676
 
9573
9677
  /**
@@ -14415,6 +14519,9 @@ class ElementInspectorComponent {
14415
14519
  store = inject(FlowDocumentStore);
14416
14520
  dictionaries = inject(FlowDictionaryStore);
14417
14521
  validation = inject(FlowValidationStore);
14522
+ injector = inject(Injector);
14523
+ /** L'etichetta: e' il primo campo di un elemento nuovo, e prende il focus da se'. */
14524
+ labelInput = viewChild('labelInput', ...(ngDevMode ? [{ debugName: "labelInput" }] : []));
14418
14525
  /** Il nome dell'elemento selezionato; `$start` per lo Start. */
14419
14526
  selectedName = input(null, ...(ngDevMode ? [{ debugName: "selectedName" }] : []));
14420
14527
  /**
@@ -14482,6 +14589,139 @@ class ElementInspectorComponent {
14482
14589
  this.issuesExpanded.update((expanded) => !expanded);
14483
14590
  }
14484
14591
  pendingName = signal(null, ...(ngDevMode ? [{ debugName: "pendingName" }] : []));
14592
+ // ------------------------------------------------------- nome tecnico in composizione
14593
+ //
14594
+ // Il campo del nome ha due presentazioni, e la differenza sta in cosa si sta facendo.
14595
+ //
14596
+ // Su un elemento **già nominato** il nome e' in sola lettura dietro il comando «Rinomina»:
14597
+ // cambiarlo riscrive i riferimenti di tutto il documento (§13.8) e non e' un gesto da fare
14598
+ // per sbaglio. Su un elemento **appena creato** — cioe' senza etichetta — il nome non e'
14599
+ // ancora niente: nel documento c'e' un nome provvisorio soltanto perche' il nome e' la
14600
+ // chiave (§3.3), il campo lo mostra **vuoto** e lo costruisce dall'etichetta mentre la si
14601
+ // digita, con spazi e caratteri non ammessi sostituiti da `_`.
14602
+ /**
14603
+ * L'elemento il cui nome si sta ancora componendo, e cosa c'e' scritto nel campo.
14604
+ *
14605
+ * `draft: null` e' «segue l'etichetta», che e' lo stato iniziale e quello in cui si torna
14606
+ * svuotando il campo. `element` segue le rinomine: senza, la prima battuta sull'etichetta —
14607
+ * che rinomina — farebbe ripartire lo stato da capo e il campo tornerebbe a seguire una
14608
+ * scelta che l'utente aveva già disfatto.
14609
+ */
14610
+ composing = signal(null, ...(ngDevMode ? [{ debugName: "composing" }] : []));
14611
+ /** Il nome si compone qui invece di stare dietro il comando di rinomina. */
14612
+ isComposingName = computed(() => {
14613
+ const state = this.composing();
14614
+ return !!state && !!this.selectedName() && state.element === this.selectedName();
14615
+ }, ...(ngDevMode ? [{ debugName: "isComposingName" }] : []));
14616
+ /** Cio' che si vede nel campo: vuoto finche' non c'e' un'etichetta da cui costruirlo. */
14617
+ composedName = computed(() => {
14618
+ const state = this.composing();
14619
+ if (!state) {
14620
+ return '';
14621
+ }
14622
+ if (state.draft !== null) {
14623
+ return state.draft;
14624
+ }
14625
+ return this.node()?.label ? (this.selectedName() ?? '') : '';
14626
+ }, ...(ngDevMode ? [{ debugName: "composedName" }] : []));
14627
+ composedNameError = computed(() => {
14628
+ const draft = this.composing()?.draft;
14629
+ if (!draft) {
14630
+ // Vuoto non e' un errore: e' la richiesta di tornare a seguire l'etichetta.
14631
+ return null;
14632
+ }
14633
+ const check = checkFlowName(draft, this.store.usedNames(), this.selectedName());
14634
+ return check.isValid ? null : (check.message ?? 'Nome non valido.');
14635
+ }, ...(ngDevMode ? [{ debugName: "composedNameError" }] : []));
14636
+ constructor() {
14637
+ effect(() => {
14638
+ const name = this.selectedName();
14639
+ // Solo il cambio di elemento conta: leggere il node dentro l'effetto lo farebbe
14640
+ // ripartire a ogni battuta, e lo stato del campo si perderebbe.
14641
+ untracked(() => this.seedComposing(name));
14642
+ });
14643
+ }
14644
+ /**
14645
+ * Un elemento **senza etichetta** e' un elemento che nessuno ha ancora nominato: il campo del
14646
+ * nome parte vuoto e segue l'etichetta. Uno che ce l'ha e' un elemento vero, e il suo nome si
14647
+ * cambia solo col comando di rinomina, che dice prima quanti riferimenti riscrivera'.
14648
+ */
14649
+ seedComposing(name) {
14650
+ const state = this.composing();
14651
+ if (state && state.element === name) {
14652
+ // Rinomina, non cambio di elemento: cio' che si sta scrivendo va conservato.
14653
+ return;
14654
+ }
14655
+ const node = this.node();
14656
+ if (!name || this.isStart() || !node || node.label) {
14657
+ this.composing.set(null);
14658
+ return;
14659
+ }
14660
+ this.composing.set({ element: name, draft: null });
14661
+ /**
14662
+ * Il primo campo di un elemento nuovo e' l'etichetta, ed e' da lì che viene il nome: il
14663
+ * focus ci va da se'. `afterNextRender` e non un rinvio al task successivo — quello scatta
14664
+ * prima del refresh della view, la query e' ancora vuota e non accade niente.
14665
+ */
14666
+ afterNextRender(() => this.labelInput()?.nativeElement.focus(), { injector: this.injector });
14667
+ }
14668
+ /**
14669
+ * Il nome scritto a mano: smette di seguire l'etichetta e, appena e' valido, diventa il nome
14670
+ * dell'elemento. Un nome intermedio non valido resta nel campo senza toccare il documento —
14671
+ * ricopiarci sopra il nome corrente impedirebbe di digitare.
14672
+ */
14673
+ onComposedNameInput(value) {
14674
+ const current = this.selectedName();
14675
+ if (!this.isComposingName() || !current) {
14676
+ return;
14677
+ }
14678
+ const draft = value.trim() ? value : null;
14679
+ this.composing.set({ element: current, draft });
14680
+ if (draft === null) {
14681
+ // Svuotato: torna a seguire l'etichetta, ed e' l'unico modo di disfare un nome scritto
14682
+ // a mano senza chiudere e riaprire il form.
14683
+ this.applyDerivedName(this.node()?.label);
14684
+ return;
14685
+ }
14686
+ const trimmed = draft.trim();
14687
+ if (trimmed !== current && checkFlowName(trimmed, this.store.usedNames(), current).isValid) {
14688
+ this.store.renameNode(current, trimmed);
14689
+ this.composing.set({ element: trimmed, draft });
14690
+ this.renamed.emit(trimmed);
14691
+ }
14692
+ }
14693
+ /**
14694
+ * Il nome costruito dall'etichetta (§3.3): spazi e caratteri non ammessi diventano `_`, i
14695
+ * diacritici cadono e un nome già preso prende un suffisso.
14696
+ *
14697
+ * Etichetta vuota → `null`, cioe' resta il nome provvisorio con cui l'elemento e' nato: il
14698
+ * documento non puo' stare senza, perche' il nome e' la chiave di tutto (§3.3).
14699
+ */
14700
+ deriveName(label, currentName) {
14701
+ const base = slugifyFlowName(label);
14702
+ if (!base) {
14703
+ return null;
14704
+ }
14705
+ // Il proprio nome non conta come «già usato»: senza toglierlo, `Approva` diventerebbe
14706
+ // `Approva_1` alla prima battuta che non cambia lo slug.
14707
+ const used = Array.from(this.store.usedNames()).filter((name) => name.toLowerCase() !== currentName.toLowerCase());
14708
+ const derived = uniqueFlowName(base, used);
14709
+ return derived === currentName ? null : derived;
14710
+ }
14711
+ /** Rimette il nome a seguire l'etichetta, dopo che il campo e' stato svuotato. */
14712
+ applyDerivedName(label) {
14713
+ const current = this.selectedName();
14714
+ if (!current) {
14715
+ return;
14716
+ }
14717
+ const derived = this.deriveName(label ?? '', current);
14718
+ if (!derived) {
14719
+ return;
14720
+ }
14721
+ this.store.renameNode(current, derived);
14722
+ this.composing.set({ element: derived, draft: null });
14723
+ this.renamed.emit(derived);
14724
+ }
14485
14725
  nameError = computed(() => {
14486
14726
  const pending = this.pendingName();
14487
14727
  if (pending === null) {
@@ -14534,9 +14774,24 @@ class ElementInspectorComponent {
14534
14774
  if (!name || this.isStart()) {
14535
14775
  return;
14536
14776
  }
14537
- this.store.updateNode(name, (node) => {
14538
- node.label = value || undefined;
14539
- });
14777
+ const state = this.composing();
14778
+ if (!state || state.element !== name || state.draft !== null) {
14779
+ this.store.updateNode(name, (node) => {
14780
+ node.label = value || undefined;
14781
+ });
14782
+ return;
14783
+ }
14784
+ /**
14785
+ * Il nome sta ancora seguendo l'etichetta: le due modifiche sono **un** gesto e vanno in
14786
+ * una mutazione sola, altrimenti l'annulla riporta indietro il nome lasciando l'etichetta
14787
+ * nuova — cioe' uno stato che non e' mai esistito.
14788
+ */
14789
+ const derived = this.deriveName(value, name);
14790
+ this.store.relabelNode(name, value, derived);
14791
+ if (derived) {
14792
+ this.composing.set({ element: derived, draft: null });
14793
+ this.renamed.emit(derived);
14794
+ }
14540
14795
  }
14541
14796
  setDescription(value) {
14542
14797
  const name = this.selectedName();
@@ -14576,7 +14831,7 @@ class ElementInspectorComponent {
14576
14831
  return { x: node?.locationX ?? 0, y: node?.locationY ?? 0 };
14577
14832
  }, ...(ngDevMode ? [{ debugName: "position" }] : []));
14578
14833
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: ElementInspectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
14579
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.28", type: ElementInspectorComponent, isStandalone: true, selector: "fb-element-inspector", inputs: { selectedName: { classPropertyName: "selectedName", publicName: "selectedName", isSignal: true, isRequired: false, transformFunction: null }, showHeader: { classPropertyName: "showHeader", publicName: "showHeader", isSignal: true, isRequired: false, transformFunction: null }, initialFieldPath: { classPropertyName: "initialFieldPath", publicName: "initialFieldPath", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closed: "closed", removeRequested: "removeRequested", duplicateRequested: "duplicateRequested", renamed: "renamed" }, ngImport: i0, template: "@if (!selectedName()) {\r\n <p class=\"fb-inspector__empty\">\r\n Seleziona un elemento sul canvas per modificarlo, oppure trascina un elemento dalla palette.\r\n </p>\r\n} @else {\r\n @if (showHeader()) {\r\n <header class=\"fb-inspector__header\">\r\n <div>\r\n <span class=\"fb-inspector__type\">{{ typeLabel() }}</span>\r\n <h2 class=\"fb-inspector__title\">\r\n {{ isStart() ? 'Avvio del flow' : node()?.label || selectedName() }}\r\n </h2>\r\n </div>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" aria-label=\"Chiudi\" (click)=\"close()\">\r\n \u00D7\r\n </button>\r\n </header>\r\n }\r\n\r\n <div class=\"fb-inspector__body\">\r\n @if (issues().length) {\r\n <ul class=\"fb-inspector__issues\">\r\n <!-- `$index`: due rilievi con lo stesso codice e lo stesso path sono possibili. -->\r\n @for (issue of visibleIssues(); track $index) {\r\n <li\r\n class=\"fb-inspector__issue\"\r\n [class.fb-inspector__issue--error]=\"issue.severity === 'Error'\"\r\n [class.fb-inspector__issue--warning]=\"issue.severity === 'Warning'\"\r\n >\r\n <span class=\"fb-inspector__issue-code\">{{ issue.code }}</span>\r\n {{ issue.message }}\r\n @if (issue.path) {\r\n <span class=\"fb-inspector__issue-path\">{{ issue.path }}</span>\r\n }\r\n </li>\r\n }\r\n </ul>\r\n @if (hiddenIssueCount() > 0 || issuesExpanded()) {\r\n <!-- Sei rilievi in cima riempivano la finestra prima del primo campo: si chiedono. -->\r\n <button type=\"button\" class=\"fb-inspector__issues-more\" (click)=\"toggleIssues()\">\r\n @if (issuesExpanded()) {\r\n Mostra meno\r\n } @else {\r\n Altri {{ hiddenIssueCount() }} rilievi su questo elemento\r\n }\r\n </button>\r\n }\r\n }\r\n\r\n @if (isUnsupported()) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n Questo tipo di elemento non e\u2019 supportato dal motore: il flow che lo contiene non parte\r\n (ELEMENT_NOT_SUPPORTED). Non e\u2019 creabile dalla palette; se e\u2019 arrivato da un documento importato,\r\n va rimosso.\r\n </p>\r\n }\r\n\r\n @if (!isStart()) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Etichetta</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"$any(node()?.label) || ''\"\r\n placeholder=\"Nome mostrato sul canvas\"\r\n (input)=\"setLabel($any($event.target).value)\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Nome tecnico</label>\r\n @if (pendingName() === null) {\r\n <div class=\"fb-field__row\">\r\n <input class=\"fb-input fb-input--mono\" [value]=\"selectedName() || ''\" readonly />\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"startRename()\">Rinomina</button>\r\n </div>\r\n <p class=\"fb-field__hint\">\r\n \u00C8 l\u2019identificatore con cui i riferimenti raggiungono questo elemento.\r\n @if (referenceCount() > 1) {\r\n Compare {{ referenceCount() }} volte nel documento.\r\n }\r\n </p>\r\n } @else {\r\n <input\r\n class=\"fb-input fb-input--mono\"\r\n [class.fb-input--invalid]=\"!!nameError()\"\r\n [value]=\"pendingName() || ''\"\r\n (input)=\"onPendingNameInput($any($event.target).value)\"\r\n />\r\n @if (nameError()) {\r\n <p class=\"fb-field__error\">{{ nameError() }}</p>\r\n } @else {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n La rinomina riscrive tutti i riferimenti che puntano a questo elemento\r\n ({{ referenceCount() }} occorrenze): nessuna primitiva del backend lo fa, lo fa l\u2019editor.\r\n </p>\r\n }\r\n <div class=\"fb-field__row\">\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" [disabled]=\"!canRename()\" (click)=\"applyRename()\">\r\n Applica\r\n </button>\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"suggestNameFromLabel()\">Genera dalla label</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost\" (click)=\"cancelRename()\">Annulla</button>\r\n </div>\r\n }\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Descrizione</label>\r\n <textarea\r\n class=\"fb-textarea\"\r\n [value]=\"$any(node()?.description) || ''\"\r\n (input)=\"setDescription($any($event.target).value)\"\r\n ></textarea>\r\n </div>\r\n }\r\n\r\n <p class=\"fb-inspector__position\">\r\n Posizione sul canvas: {{ position().x }}, {{ position().y }}\r\n </p>\r\n\r\n <hr class=\"fb-inspector__divider\" />\r\n\r\n @if (isStart()) {\r\n <fb-start-inspector />\r\n } @else if (node()) {\r\n @switch (type()) {\r\n @case ('Screen') {\r\n <fb-screen-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('DynamicScreen') {\r\n <fb-dynamic-screen-inspector\r\n [name]=\"selectedName()!\"\r\n [node]=\"node()!\"\r\n [initialSelection]=\"initialFieldPath()\"\r\n />\r\n }\r\n @case ('Assignment') {\r\n <fb-assignment-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('Decision') {\r\n <fb-decision-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('Loop') {\r\n <fb-loop-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('CollectionProcessor') {\r\n <fb-collection-processor-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('CustomError') {\r\n <fb-custom-error-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('Wait') {\r\n <fb-wait-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('RecordLookup') {\r\n <fb-record-lookup-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('RecordCreate') {\r\n <fb-record-write-inspector [name]=\"selectedName()!\" [node]=\"node()!\" type=\"RecordCreate\" />\r\n }\r\n @case ('RecordUpdate') {\r\n <fb-record-write-inspector [name]=\"selectedName()!\" [node]=\"node()!\" type=\"RecordUpdate\" />\r\n }\r\n @case ('RecordDelete') {\r\n <fb-record-write-inspector [name]=\"selectedName()!\" [node]=\"node()!\" type=\"RecordDelete\" />\r\n }\r\n @case ('RecordRollback') {\r\n <fb-record-rollback-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('ActionCall') {\r\n <fb-action-call-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('ScriptCall') {\r\n <fb-script-call-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('Subflow') {\r\n <fb-subflow-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('Transform') {\r\n <fb-transform-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('OrchestratedStage') {\r\n <fb-orchestrated-stage-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @default {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Nessun form specifico per il tipo \u00AB{{ type() }}\u00BB: i campi comuni sono modificabili qui sopra.\r\n </p>\r\n }\r\n }\r\n }\r\n\r\n @if (!isStart()) {\r\n <hr class=\"fb-inspector__divider\" />\r\n <div class=\"fb-field__row\">\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"requestDuplicate()\">Duplica</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--danger\" (click)=\"requestRemove()\">Elimina</button>\r\n </div>\r\n }\r\n </div>\r\n}\r\n", styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0;background:var(--fb-surface, #fff)}.fb-inspector__empty{margin:16px 12px;font-size:12px;line-height:1.5;color:var(--fb-text-muted, #667085)}.fb-inspector__header{display:flex;align-items:flex-start;justify-content:space-between;gap:8px;padding:10px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-inspector__type{font-size:10px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;color:var(--fb-text-subtle, #98a2b3)}.fb-inspector__title{margin:2px 0 0;font-size:14px;font-weight:600;color:var(--fb-text, #1d2939)}.fb-inspector__body{flex:1;min-height:0;overflow-y:auto;padding:14px}.fb-inspector__issues{margin:0 0 12px;padding:0;list-style:none}.fb-inspector__issue{margin-bottom:4px;padding:6px 8px;border-left:3px solid var(--fb-text-subtle, #98a2b3);border-radius:3px;background:var(--fb-surface-alt, #f8f9fb);font-size:11px;line-height:1.4;color:var(--fb-text, #1d2939)}.fb-inspector__issue--error{border-left-color:var(--fb-error, #c9372c)}.fb-inspector__issue--warning{border-left-color:var(--fb-warning, #b7791f)}.fb-inspector__issue-code{display:block;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:9px;color:var(--fb-text-subtle, #98a2b3)}.fb-inspector__issue-path{display:block;margin-top:2px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:9px;color:var(--fb-text-muted, #667085)}.fb-inspector__position{margin:0;font-size:10px;color:var(--fb-text-subtle, #98a2b3)}.fb-inspector__divider{margin:12px 0;border:0;border-top:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-inspector__issues-more{display:block;margin:-4px 0 10px;padding:2px 0;border:0;background:none;color:var(--fb-accent, #4f6ef7);font:inherit;font-size:11px;cursor:pointer}.fb-inspector__issues-more:hover{text-decoration:underline}\n"], dependencies: [{ kind: "component", type: ActionCallInspectorComponent, selector: "fb-action-call-inspector" }, { kind: "component", type: AssignmentInspectorComponent, selector: "fb-assignment-inspector" }, { kind: "component", type: CollectionProcessorInspectorComponent, selector: "fb-collection-processor-inspector" }, { kind: "component", type: CustomErrorInspectorComponent, selector: "fb-custom-error-inspector" }, { kind: "component", type: DecisionInspectorComponent, selector: "fb-decision-inspector" }, { kind: "component", type: DynamicScreenInspectorComponent, selector: "fb-dynamic-screen-inspector", inputs: ["initialSelection"] }, { kind: "component", type: LoopInspectorComponent, selector: "fb-loop-inspector" }, { kind: "component", type: OrchestratedStageInspectorComponent, selector: "fb-orchestrated-stage-inspector" }, { kind: "component", type: RecordLookupInspectorComponent, selector: "fb-record-lookup-inspector" }, { kind: "component", type: RecordRollbackInspectorComponent, selector: "fb-record-rollback-inspector" }, { kind: "component", type: RecordWriteInspectorComponent, selector: "fb-record-write-inspector", inputs: ["type"] }, { kind: "component", type: ScreenInspectorComponent, selector: "fb-screen-inspector" }, { kind: "component", type: ScriptCallInspectorComponent, selector: "fb-script-call-inspector" }, { kind: "component", type: StartInspectorComponent, selector: "fb-start-inspector" }, { kind: "component", type: SubflowInspectorComponent, selector: "fb-subflow-inspector" }, { kind: "component", type: TransformInspectorComponent, selector: "fb-transform-inspector" }, { kind: "component", type: WaitInspectorComponent, selector: "fb-wait-inspector" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
14834
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.28", type: ElementInspectorComponent, isStandalone: true, selector: "fb-element-inspector", inputs: { selectedName: { classPropertyName: "selectedName", publicName: "selectedName", isSignal: true, isRequired: false, transformFunction: null }, showHeader: { classPropertyName: "showHeader", publicName: "showHeader", isSignal: true, isRequired: false, transformFunction: null }, initialFieldPath: { classPropertyName: "initialFieldPath", publicName: "initialFieldPath", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closed: "closed", removeRequested: "removeRequested", duplicateRequested: "duplicateRequested", renamed: "renamed" }, viewQueries: [{ propertyName: "labelInput", first: true, predicate: ["labelInput"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (!selectedName()) {\r\n <p class=\"fb-inspector__empty\">\r\n Seleziona un elemento sul canvas per modificarlo, oppure trascina un elemento dalla palette.\r\n </p>\r\n} @else {\r\n @if (showHeader()) {\r\n <header class=\"fb-inspector__header\">\r\n <div>\r\n <span class=\"fb-inspector__type\">{{ typeLabel() }}</span>\r\n <h2 class=\"fb-inspector__title\">\r\n {{ isStart() ? 'Avvio del flow' : node()?.label || selectedName() }}\r\n </h2>\r\n </div>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" aria-label=\"Chiudi\" (click)=\"close()\">\r\n \u00D7\r\n </button>\r\n </header>\r\n }\r\n\r\n <div class=\"fb-inspector__body\">\r\n @if (issues().length) {\r\n <ul class=\"fb-inspector__issues\">\r\n <!-- `$index`: due rilievi con lo stesso codice e lo stesso path sono possibili. -->\r\n @for (issue of visibleIssues(); track $index) {\r\n <li\r\n class=\"fb-inspector__issue\"\r\n [class.fb-inspector__issue--error]=\"issue.severity === 'Error'\"\r\n [class.fb-inspector__issue--warning]=\"issue.severity === 'Warning'\"\r\n >\r\n <span class=\"fb-inspector__issue-code\">{{ issue.code }}</span>\r\n {{ issue.message }}\r\n @if (issue.path) {\r\n <span class=\"fb-inspector__issue-path\">{{ issue.path }}</span>\r\n }\r\n </li>\r\n }\r\n </ul>\r\n @if (hiddenIssueCount() > 0 || issuesExpanded()) {\r\n <!-- Sei rilievi in cima riempivano la finestra prima del primo campo: si chiedono. -->\r\n <button type=\"button\" class=\"fb-inspector__issues-more\" (click)=\"toggleIssues()\">\r\n @if (issuesExpanded()) {\r\n Mostra meno\r\n } @else {\r\n Altri {{ hiddenIssueCount() }} rilievi su questo elemento\r\n }\r\n </button>\r\n }\r\n }\r\n\r\n @if (isUnsupported()) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n Questo tipo di elemento non e\u2019 supportato dal motore: il flow che lo contiene non parte\r\n (ELEMENT_NOT_SUPPORTED). Non e\u2019 creabile dalla palette; se e\u2019 arrivato da un documento importato,\r\n va rimosso.\r\n </p>\r\n }\r\n\r\n @if (!isStart()) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Etichetta</label>\r\n <input\r\n #labelInput\r\n class=\"fb-input\"\r\n [value]=\"$any(node()?.label) || ''\"\r\n placeholder=\"Nome mostrato sul canvas\"\r\n (input)=\"setLabel($any($event.target).value)\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Nome tecnico</label>\r\n <!--\r\n Elemento appena creato: il nome non e\u2019 ancora niente, si costruisce dall\u2019etichetta e\r\n si puo\u2019 scrivere a mano. Su un elemento gi\u00E0 nominato, invece, cambiare il nome riscrive\r\n i riferimenti di tutto il documento (\u00A713.8) e sta dietro il comando \u00ABRinomina\u00BB.\r\n -->\r\n @if (isComposingName()) {\r\n <input\r\n class=\"fb-input fb-input--mono\"\r\n [class.fb-input--invalid]=\"!!composedNameError()\"\r\n [value]=\"composedName()\"\r\n placeholder=\"si genera dall\u2019etichetta\"\r\n aria-label=\"Nome tecnico\"\r\n (input)=\"onComposedNameInput($any($event.target).value)\"\r\n />\r\n @if (composedNameError()) {\r\n <p class=\"fb-field__error\">{{ composedNameError() }}</p>\r\n } @else {\r\n <p class=\"fb-field__hint\">\r\n \u00C8 l\u2019identificatore con cui i riferimenti raggiungono questo elemento: si costruisce\r\n dall\u2019etichetta finch\u00E9 non lo si scrive a mano, e svuotandolo torna a seguirla.\r\n @if (!composedName()) {\r\n Finch\u00E9 resta vuoto vale \u00AB{{ selectedName() }}\u00BB.\r\n }\r\n </p>\r\n }\r\n } @else if (pendingName() === null) {\r\n <div class=\"fb-field__row\">\r\n <input class=\"fb-input fb-input--mono\" [value]=\"selectedName() || ''\" readonly />\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"startRename()\">Rinomina</button>\r\n </div>\r\n <p class=\"fb-field__hint\">\r\n \u00C8 l\u2019identificatore con cui i riferimenti raggiungono questo elemento.\r\n @if (referenceCount() > 1) {\r\n Compare {{ referenceCount() }} volte nel documento.\r\n }\r\n </p>\r\n } @else {\r\n <input\r\n class=\"fb-input fb-input--mono\"\r\n [class.fb-input--invalid]=\"!!nameError()\"\r\n [value]=\"pendingName() || ''\"\r\n (input)=\"onPendingNameInput($any($event.target).value)\"\r\n />\r\n @if (nameError()) {\r\n <p class=\"fb-field__error\">{{ nameError() }}</p>\r\n } @else {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n La rinomina riscrive tutti i riferimenti che puntano a questo elemento\r\n ({{ referenceCount() }} occorrenze): nessuna primitiva del backend lo fa, lo fa l\u2019editor.\r\n </p>\r\n }\r\n <div class=\"fb-field__row\">\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" [disabled]=\"!canRename()\" (click)=\"applyRename()\">\r\n Applica\r\n </button>\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"suggestNameFromLabel()\">Genera dalla label</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost\" (click)=\"cancelRename()\">Annulla</button>\r\n </div>\r\n }\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Descrizione</label>\r\n <textarea\r\n class=\"fb-textarea\"\r\n [value]=\"$any(node()?.description) || ''\"\r\n (input)=\"setDescription($any($event.target).value)\"\r\n ></textarea>\r\n </div>\r\n }\r\n\r\n <p class=\"fb-inspector__position\">\r\n Posizione sul canvas: {{ position().x }}, {{ position().y }}\r\n </p>\r\n\r\n <hr class=\"fb-inspector__divider\" />\r\n\r\n @if (isStart()) {\r\n <fb-start-inspector />\r\n } @else if (node()) {\r\n @switch (type()) {\r\n @case ('Screen') {\r\n <fb-screen-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('DynamicScreen') {\r\n <fb-dynamic-screen-inspector\r\n [name]=\"selectedName()!\"\r\n [node]=\"node()!\"\r\n [initialSelection]=\"initialFieldPath()\"\r\n />\r\n }\r\n @case ('Assignment') {\r\n <fb-assignment-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('Decision') {\r\n <fb-decision-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('Loop') {\r\n <fb-loop-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('CollectionProcessor') {\r\n <fb-collection-processor-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('CustomError') {\r\n <fb-custom-error-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('Wait') {\r\n <fb-wait-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('RecordLookup') {\r\n <fb-record-lookup-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('RecordCreate') {\r\n <fb-record-write-inspector [name]=\"selectedName()!\" [node]=\"node()!\" type=\"RecordCreate\" />\r\n }\r\n @case ('RecordUpdate') {\r\n <fb-record-write-inspector [name]=\"selectedName()!\" [node]=\"node()!\" type=\"RecordUpdate\" />\r\n }\r\n @case ('RecordDelete') {\r\n <fb-record-write-inspector [name]=\"selectedName()!\" [node]=\"node()!\" type=\"RecordDelete\" />\r\n }\r\n @case ('RecordRollback') {\r\n <fb-record-rollback-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('ActionCall') {\r\n <fb-action-call-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('ScriptCall') {\r\n <fb-script-call-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('Subflow') {\r\n <fb-subflow-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('Transform') {\r\n <fb-transform-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('OrchestratedStage') {\r\n <fb-orchestrated-stage-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @default {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Nessun form specifico per il tipo \u00AB{{ type() }}\u00BB: i campi comuni sono modificabili qui sopra.\r\n </p>\r\n }\r\n }\r\n }\r\n\r\n @if (!isStart()) {\r\n <hr class=\"fb-inspector__divider\" />\r\n <div class=\"fb-field__row\">\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"requestDuplicate()\">Duplica</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--danger\" (click)=\"requestRemove()\">Elimina</button>\r\n </div>\r\n }\r\n </div>\r\n}\r\n", styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0;background:var(--fb-surface, #fff)}.fb-inspector__empty{margin:16px 12px;font-size:12px;line-height:1.5;color:var(--fb-text-muted, #667085)}.fb-inspector__header{display:flex;align-items:flex-start;justify-content:space-between;gap:8px;padding:10px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-inspector__type{font-size:10px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;color:var(--fb-text-subtle, #98a2b3)}.fb-inspector__title{margin:2px 0 0;font-size:14px;font-weight:600;color:var(--fb-text, #1d2939)}.fb-inspector__body{flex:1;min-height:0;overflow-y:auto;padding:14px}.fb-inspector__issues{margin:0 0 12px;padding:0;list-style:none}.fb-inspector__issue{margin-bottom:4px;padding:6px 8px;border-left:3px solid var(--fb-text-subtle, #98a2b3);border-radius:3px;background:var(--fb-surface-alt, #f8f9fb);font-size:11px;line-height:1.4;color:var(--fb-text, #1d2939)}.fb-inspector__issue--error{border-left-color:var(--fb-error, #c9372c)}.fb-inspector__issue--warning{border-left-color:var(--fb-warning, #b7791f)}.fb-inspector__issue-code{display:block;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:9px;color:var(--fb-text-subtle, #98a2b3)}.fb-inspector__issue-path{display:block;margin-top:2px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:9px;color:var(--fb-text-muted, #667085)}.fb-inspector__position{margin:0;font-size:10px;color:var(--fb-text-subtle, #98a2b3)}.fb-inspector__divider{margin:12px 0;border:0;border-top:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-inspector__issues-more{display:block;margin:-4px 0 10px;padding:2px 0;border:0;background:none;color:var(--fb-accent, #4f6ef7);font:inherit;font-size:11px;cursor:pointer}.fb-inspector__issues-more:hover{text-decoration:underline}\n"], dependencies: [{ kind: "component", type: ActionCallInspectorComponent, selector: "fb-action-call-inspector" }, { kind: "component", type: AssignmentInspectorComponent, selector: "fb-assignment-inspector" }, { kind: "component", type: CollectionProcessorInspectorComponent, selector: "fb-collection-processor-inspector" }, { kind: "component", type: CustomErrorInspectorComponent, selector: "fb-custom-error-inspector" }, { kind: "component", type: DecisionInspectorComponent, selector: "fb-decision-inspector" }, { kind: "component", type: DynamicScreenInspectorComponent, selector: "fb-dynamic-screen-inspector", inputs: ["initialSelection"] }, { kind: "component", type: LoopInspectorComponent, selector: "fb-loop-inspector" }, { kind: "component", type: OrchestratedStageInspectorComponent, selector: "fb-orchestrated-stage-inspector" }, { kind: "component", type: RecordLookupInspectorComponent, selector: "fb-record-lookup-inspector" }, { kind: "component", type: RecordRollbackInspectorComponent, selector: "fb-record-rollback-inspector" }, { kind: "component", type: RecordWriteInspectorComponent, selector: "fb-record-write-inspector", inputs: ["type"] }, { kind: "component", type: ScreenInspectorComponent, selector: "fb-screen-inspector" }, { kind: "component", type: ScriptCallInspectorComponent, selector: "fb-script-call-inspector" }, { kind: "component", type: StartInspectorComponent, selector: "fb-start-inspector" }, { kind: "component", type: SubflowInspectorComponent, selector: "fb-subflow-inspector" }, { kind: "component", type: TransformInspectorComponent, selector: "fb-transform-inspector" }, { kind: "component", type: WaitInspectorComponent, selector: "fb-wait-inspector" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
14580
14835
  }
14581
14836
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: ElementInspectorComponent, decorators: [{
14582
14837
  type: Component,
@@ -14598,8 +14853,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImpo
14598
14853
  SubflowInspectorComponent,
14599
14854
  TransformInspectorComponent,
14600
14855
  WaitInspectorComponent,
14601
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (!selectedName()) {\r\n <p class=\"fb-inspector__empty\">\r\n Seleziona un elemento sul canvas per modificarlo, oppure trascina un elemento dalla palette.\r\n </p>\r\n} @else {\r\n @if (showHeader()) {\r\n <header class=\"fb-inspector__header\">\r\n <div>\r\n <span class=\"fb-inspector__type\">{{ typeLabel() }}</span>\r\n <h2 class=\"fb-inspector__title\">\r\n {{ isStart() ? 'Avvio del flow' : node()?.label || selectedName() }}\r\n </h2>\r\n </div>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" aria-label=\"Chiudi\" (click)=\"close()\">\r\n \u00D7\r\n </button>\r\n </header>\r\n }\r\n\r\n <div class=\"fb-inspector__body\">\r\n @if (issues().length) {\r\n <ul class=\"fb-inspector__issues\">\r\n <!-- `$index`: due rilievi con lo stesso codice e lo stesso path sono possibili. -->\r\n @for (issue of visibleIssues(); track $index) {\r\n <li\r\n class=\"fb-inspector__issue\"\r\n [class.fb-inspector__issue--error]=\"issue.severity === 'Error'\"\r\n [class.fb-inspector__issue--warning]=\"issue.severity === 'Warning'\"\r\n >\r\n <span class=\"fb-inspector__issue-code\">{{ issue.code }}</span>\r\n {{ issue.message }}\r\n @if (issue.path) {\r\n <span class=\"fb-inspector__issue-path\">{{ issue.path }}</span>\r\n }\r\n </li>\r\n }\r\n </ul>\r\n @if (hiddenIssueCount() > 0 || issuesExpanded()) {\r\n <!-- Sei rilievi in cima riempivano la finestra prima del primo campo: si chiedono. -->\r\n <button type=\"button\" class=\"fb-inspector__issues-more\" (click)=\"toggleIssues()\">\r\n @if (issuesExpanded()) {\r\n Mostra meno\r\n } @else {\r\n Altri {{ hiddenIssueCount() }} rilievi su questo elemento\r\n }\r\n </button>\r\n }\r\n }\r\n\r\n @if (isUnsupported()) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n Questo tipo di elemento non e\u2019 supportato dal motore: il flow che lo contiene non parte\r\n (ELEMENT_NOT_SUPPORTED). Non e\u2019 creabile dalla palette; se e\u2019 arrivato da un documento importato,\r\n va rimosso.\r\n </p>\r\n }\r\n\r\n @if (!isStart()) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Etichetta</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"$any(node()?.label) || ''\"\r\n placeholder=\"Nome mostrato sul canvas\"\r\n (input)=\"setLabel($any($event.target).value)\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Nome tecnico</label>\r\n @if (pendingName() === null) {\r\n <div class=\"fb-field__row\">\r\n <input class=\"fb-input fb-input--mono\" [value]=\"selectedName() || ''\" readonly />\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"startRename()\">Rinomina</button>\r\n </div>\r\n <p class=\"fb-field__hint\">\r\n \u00C8 l\u2019identificatore con cui i riferimenti raggiungono questo elemento.\r\n @if (referenceCount() > 1) {\r\n Compare {{ referenceCount() }} volte nel documento.\r\n }\r\n </p>\r\n } @else {\r\n <input\r\n class=\"fb-input fb-input--mono\"\r\n [class.fb-input--invalid]=\"!!nameError()\"\r\n [value]=\"pendingName() || ''\"\r\n (input)=\"onPendingNameInput($any($event.target).value)\"\r\n />\r\n @if (nameError()) {\r\n <p class=\"fb-field__error\">{{ nameError() }}</p>\r\n } @else {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n La rinomina riscrive tutti i riferimenti che puntano a questo elemento\r\n ({{ referenceCount() }} occorrenze): nessuna primitiva del backend lo fa, lo fa l\u2019editor.\r\n </p>\r\n }\r\n <div class=\"fb-field__row\">\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" [disabled]=\"!canRename()\" (click)=\"applyRename()\">\r\n Applica\r\n </button>\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"suggestNameFromLabel()\">Genera dalla label</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost\" (click)=\"cancelRename()\">Annulla</button>\r\n </div>\r\n }\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Descrizione</label>\r\n <textarea\r\n class=\"fb-textarea\"\r\n [value]=\"$any(node()?.description) || ''\"\r\n (input)=\"setDescription($any($event.target).value)\"\r\n ></textarea>\r\n </div>\r\n }\r\n\r\n <p class=\"fb-inspector__position\">\r\n Posizione sul canvas: {{ position().x }}, {{ position().y }}\r\n </p>\r\n\r\n <hr class=\"fb-inspector__divider\" />\r\n\r\n @if (isStart()) {\r\n <fb-start-inspector />\r\n } @else if (node()) {\r\n @switch (type()) {\r\n @case ('Screen') {\r\n <fb-screen-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('DynamicScreen') {\r\n <fb-dynamic-screen-inspector\r\n [name]=\"selectedName()!\"\r\n [node]=\"node()!\"\r\n [initialSelection]=\"initialFieldPath()\"\r\n />\r\n }\r\n @case ('Assignment') {\r\n <fb-assignment-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('Decision') {\r\n <fb-decision-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('Loop') {\r\n <fb-loop-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('CollectionProcessor') {\r\n <fb-collection-processor-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('CustomError') {\r\n <fb-custom-error-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('Wait') {\r\n <fb-wait-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('RecordLookup') {\r\n <fb-record-lookup-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('RecordCreate') {\r\n <fb-record-write-inspector [name]=\"selectedName()!\" [node]=\"node()!\" type=\"RecordCreate\" />\r\n }\r\n @case ('RecordUpdate') {\r\n <fb-record-write-inspector [name]=\"selectedName()!\" [node]=\"node()!\" type=\"RecordUpdate\" />\r\n }\r\n @case ('RecordDelete') {\r\n <fb-record-write-inspector [name]=\"selectedName()!\" [node]=\"node()!\" type=\"RecordDelete\" />\r\n }\r\n @case ('RecordRollback') {\r\n <fb-record-rollback-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('ActionCall') {\r\n <fb-action-call-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('ScriptCall') {\r\n <fb-script-call-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('Subflow') {\r\n <fb-subflow-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('Transform') {\r\n <fb-transform-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('OrchestratedStage') {\r\n <fb-orchestrated-stage-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @default {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Nessun form specifico per il tipo \u00AB{{ type() }}\u00BB: i campi comuni sono modificabili qui sopra.\r\n </p>\r\n }\r\n }\r\n }\r\n\r\n @if (!isStart()) {\r\n <hr class=\"fb-inspector__divider\" />\r\n <div class=\"fb-field__row\">\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"requestDuplicate()\">Duplica</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--danger\" (click)=\"requestRemove()\">Elimina</button>\r\n </div>\r\n }\r\n </div>\r\n}\r\n", styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0;background:var(--fb-surface, #fff)}.fb-inspector__empty{margin:16px 12px;font-size:12px;line-height:1.5;color:var(--fb-text-muted, #667085)}.fb-inspector__header{display:flex;align-items:flex-start;justify-content:space-between;gap:8px;padding:10px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-inspector__type{font-size:10px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;color:var(--fb-text-subtle, #98a2b3)}.fb-inspector__title{margin:2px 0 0;font-size:14px;font-weight:600;color:var(--fb-text, #1d2939)}.fb-inspector__body{flex:1;min-height:0;overflow-y:auto;padding:14px}.fb-inspector__issues{margin:0 0 12px;padding:0;list-style:none}.fb-inspector__issue{margin-bottom:4px;padding:6px 8px;border-left:3px solid var(--fb-text-subtle, #98a2b3);border-radius:3px;background:var(--fb-surface-alt, #f8f9fb);font-size:11px;line-height:1.4;color:var(--fb-text, #1d2939)}.fb-inspector__issue--error{border-left-color:var(--fb-error, #c9372c)}.fb-inspector__issue--warning{border-left-color:var(--fb-warning, #b7791f)}.fb-inspector__issue-code{display:block;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:9px;color:var(--fb-text-subtle, #98a2b3)}.fb-inspector__issue-path{display:block;margin-top:2px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:9px;color:var(--fb-text-muted, #667085)}.fb-inspector__position{margin:0;font-size:10px;color:var(--fb-text-subtle, #98a2b3)}.fb-inspector__divider{margin:12px 0;border:0;border-top:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-inspector__issues-more{display:block;margin:-4px 0 10px;padding:2px 0;border:0;background:none;color:var(--fb-accent, #4f6ef7);font:inherit;font-size:11px;cursor:pointer}.fb-inspector__issues-more:hover{text-decoration:underline}\n"] }]
14602
- }], propDecorators: { selectedName: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedName", required: false }] }], showHeader: [{ type: i0.Input, args: [{ isSignal: true, alias: "showHeader", required: false }] }], initialFieldPath: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialFieldPath", required: false }] }], closed: [{ type: i0.Output, args: ["closed"] }], removeRequested: [{ type: i0.Output, args: ["removeRequested"] }], duplicateRequested: [{ type: i0.Output, args: ["duplicateRequested"] }], renamed: [{ type: i0.Output, args: ["renamed"] }] } });
14856
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (!selectedName()) {\r\n <p class=\"fb-inspector__empty\">\r\n Seleziona un elemento sul canvas per modificarlo, oppure trascina un elemento dalla palette.\r\n </p>\r\n} @else {\r\n @if (showHeader()) {\r\n <header class=\"fb-inspector__header\">\r\n <div>\r\n <span class=\"fb-inspector__type\">{{ typeLabel() }}</span>\r\n <h2 class=\"fb-inspector__title\">\r\n {{ isStart() ? 'Avvio del flow' : node()?.label || selectedName() }}\r\n </h2>\r\n </div>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" aria-label=\"Chiudi\" (click)=\"close()\">\r\n \u00D7\r\n </button>\r\n </header>\r\n }\r\n\r\n <div class=\"fb-inspector__body\">\r\n @if (issues().length) {\r\n <ul class=\"fb-inspector__issues\">\r\n <!-- `$index`: due rilievi con lo stesso codice e lo stesso path sono possibili. -->\r\n @for (issue of visibleIssues(); track $index) {\r\n <li\r\n class=\"fb-inspector__issue\"\r\n [class.fb-inspector__issue--error]=\"issue.severity === 'Error'\"\r\n [class.fb-inspector__issue--warning]=\"issue.severity === 'Warning'\"\r\n >\r\n <span class=\"fb-inspector__issue-code\">{{ issue.code }}</span>\r\n {{ issue.message }}\r\n @if (issue.path) {\r\n <span class=\"fb-inspector__issue-path\">{{ issue.path }}</span>\r\n }\r\n </li>\r\n }\r\n </ul>\r\n @if (hiddenIssueCount() > 0 || issuesExpanded()) {\r\n <!-- Sei rilievi in cima riempivano la finestra prima del primo campo: si chiedono. -->\r\n <button type=\"button\" class=\"fb-inspector__issues-more\" (click)=\"toggleIssues()\">\r\n @if (issuesExpanded()) {\r\n Mostra meno\r\n } @else {\r\n Altri {{ hiddenIssueCount() }} rilievi su questo elemento\r\n }\r\n </button>\r\n }\r\n }\r\n\r\n @if (isUnsupported()) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n Questo tipo di elemento non e\u2019 supportato dal motore: il flow che lo contiene non parte\r\n (ELEMENT_NOT_SUPPORTED). Non e\u2019 creabile dalla palette; se e\u2019 arrivato da un documento importato,\r\n va rimosso.\r\n </p>\r\n }\r\n\r\n @if (!isStart()) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Etichetta</label>\r\n <input\r\n #labelInput\r\n class=\"fb-input\"\r\n [value]=\"$any(node()?.label) || ''\"\r\n placeholder=\"Nome mostrato sul canvas\"\r\n (input)=\"setLabel($any($event.target).value)\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Nome tecnico</label>\r\n <!--\r\n Elemento appena creato: il nome non e\u2019 ancora niente, si costruisce dall\u2019etichetta e\r\n si puo\u2019 scrivere a mano. Su un elemento gi\u00E0 nominato, invece, cambiare il nome riscrive\r\n i riferimenti di tutto il documento (\u00A713.8) e sta dietro il comando \u00ABRinomina\u00BB.\r\n -->\r\n @if (isComposingName()) {\r\n <input\r\n class=\"fb-input fb-input--mono\"\r\n [class.fb-input--invalid]=\"!!composedNameError()\"\r\n [value]=\"composedName()\"\r\n placeholder=\"si genera dall\u2019etichetta\"\r\n aria-label=\"Nome tecnico\"\r\n (input)=\"onComposedNameInput($any($event.target).value)\"\r\n />\r\n @if (composedNameError()) {\r\n <p class=\"fb-field__error\">{{ composedNameError() }}</p>\r\n } @else {\r\n <p class=\"fb-field__hint\">\r\n \u00C8 l\u2019identificatore con cui i riferimenti raggiungono questo elemento: si costruisce\r\n dall\u2019etichetta finch\u00E9 non lo si scrive a mano, e svuotandolo torna a seguirla.\r\n @if (!composedName()) {\r\n Finch\u00E9 resta vuoto vale \u00AB{{ selectedName() }}\u00BB.\r\n }\r\n </p>\r\n }\r\n } @else if (pendingName() === null) {\r\n <div class=\"fb-field__row\">\r\n <input class=\"fb-input fb-input--mono\" [value]=\"selectedName() || ''\" readonly />\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"startRename()\">Rinomina</button>\r\n </div>\r\n <p class=\"fb-field__hint\">\r\n \u00C8 l\u2019identificatore con cui i riferimenti raggiungono questo elemento.\r\n @if (referenceCount() > 1) {\r\n Compare {{ referenceCount() }} volte nel documento.\r\n }\r\n </p>\r\n } @else {\r\n <input\r\n class=\"fb-input fb-input--mono\"\r\n [class.fb-input--invalid]=\"!!nameError()\"\r\n [value]=\"pendingName() || ''\"\r\n (input)=\"onPendingNameInput($any($event.target).value)\"\r\n />\r\n @if (nameError()) {\r\n <p class=\"fb-field__error\">{{ nameError() }}</p>\r\n } @else {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n La rinomina riscrive tutti i riferimenti che puntano a questo elemento\r\n ({{ referenceCount() }} occorrenze): nessuna primitiva del backend lo fa, lo fa l\u2019editor.\r\n </p>\r\n }\r\n <div class=\"fb-field__row\">\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" [disabled]=\"!canRename()\" (click)=\"applyRename()\">\r\n Applica\r\n </button>\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"suggestNameFromLabel()\">Genera dalla label</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost\" (click)=\"cancelRename()\">Annulla</button>\r\n </div>\r\n }\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Descrizione</label>\r\n <textarea\r\n class=\"fb-textarea\"\r\n [value]=\"$any(node()?.description) || ''\"\r\n (input)=\"setDescription($any($event.target).value)\"\r\n ></textarea>\r\n </div>\r\n }\r\n\r\n <p class=\"fb-inspector__position\">\r\n Posizione sul canvas: {{ position().x }}, {{ position().y }}\r\n </p>\r\n\r\n <hr class=\"fb-inspector__divider\" />\r\n\r\n @if (isStart()) {\r\n <fb-start-inspector />\r\n } @else if (node()) {\r\n @switch (type()) {\r\n @case ('Screen') {\r\n <fb-screen-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('DynamicScreen') {\r\n <fb-dynamic-screen-inspector\r\n [name]=\"selectedName()!\"\r\n [node]=\"node()!\"\r\n [initialSelection]=\"initialFieldPath()\"\r\n />\r\n }\r\n @case ('Assignment') {\r\n <fb-assignment-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('Decision') {\r\n <fb-decision-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('Loop') {\r\n <fb-loop-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('CollectionProcessor') {\r\n <fb-collection-processor-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('CustomError') {\r\n <fb-custom-error-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('Wait') {\r\n <fb-wait-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('RecordLookup') {\r\n <fb-record-lookup-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('RecordCreate') {\r\n <fb-record-write-inspector [name]=\"selectedName()!\" [node]=\"node()!\" type=\"RecordCreate\" />\r\n }\r\n @case ('RecordUpdate') {\r\n <fb-record-write-inspector [name]=\"selectedName()!\" [node]=\"node()!\" type=\"RecordUpdate\" />\r\n }\r\n @case ('RecordDelete') {\r\n <fb-record-write-inspector [name]=\"selectedName()!\" [node]=\"node()!\" type=\"RecordDelete\" />\r\n }\r\n @case ('RecordRollback') {\r\n <fb-record-rollback-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('ActionCall') {\r\n <fb-action-call-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('ScriptCall') {\r\n <fb-script-call-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('Subflow') {\r\n <fb-subflow-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('Transform') {\r\n <fb-transform-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('OrchestratedStage') {\r\n <fb-orchestrated-stage-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @default {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Nessun form specifico per il tipo \u00AB{{ type() }}\u00BB: i campi comuni sono modificabili qui sopra.\r\n </p>\r\n }\r\n }\r\n }\r\n\r\n @if (!isStart()) {\r\n <hr class=\"fb-inspector__divider\" />\r\n <div class=\"fb-field__row\">\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"requestDuplicate()\">Duplica</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--danger\" (click)=\"requestRemove()\">Elimina</button>\r\n </div>\r\n }\r\n </div>\r\n}\r\n", styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0;background:var(--fb-surface, #fff)}.fb-inspector__empty{margin:16px 12px;font-size:12px;line-height:1.5;color:var(--fb-text-muted, #667085)}.fb-inspector__header{display:flex;align-items:flex-start;justify-content:space-between;gap:8px;padding:10px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-inspector__type{font-size:10px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;color:var(--fb-text-subtle, #98a2b3)}.fb-inspector__title{margin:2px 0 0;font-size:14px;font-weight:600;color:var(--fb-text, #1d2939)}.fb-inspector__body{flex:1;min-height:0;overflow-y:auto;padding:14px}.fb-inspector__issues{margin:0 0 12px;padding:0;list-style:none}.fb-inspector__issue{margin-bottom:4px;padding:6px 8px;border-left:3px solid var(--fb-text-subtle, #98a2b3);border-radius:3px;background:var(--fb-surface-alt, #f8f9fb);font-size:11px;line-height:1.4;color:var(--fb-text, #1d2939)}.fb-inspector__issue--error{border-left-color:var(--fb-error, #c9372c)}.fb-inspector__issue--warning{border-left-color:var(--fb-warning, #b7791f)}.fb-inspector__issue-code{display:block;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:9px;color:var(--fb-text-subtle, #98a2b3)}.fb-inspector__issue-path{display:block;margin-top:2px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:9px;color:var(--fb-text-muted, #667085)}.fb-inspector__position{margin:0;font-size:10px;color:var(--fb-text-subtle, #98a2b3)}.fb-inspector__divider{margin:12px 0;border:0;border-top:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-inspector__issues-more{display:block;margin:-4px 0 10px;padding:2px 0;border:0;background:none;color:var(--fb-accent, #4f6ef7);font:inherit;font-size:11px;cursor:pointer}.fb-inspector__issues-more:hover{text-decoration:underline}\n"] }]
14857
+ }], ctorParameters: () => [], propDecorators: { labelInput: [{ type: i0.ViewChild, args: ['labelInput', { isSignal: true }] }], selectedName: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedName", required: false }] }], showHeader: [{ type: i0.Input, args: [{ isSignal: true, alias: "showHeader", required: false }] }], initialFieldPath: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialFieldPath", required: false }] }], closed: [{ type: i0.Output, args: ["closed"] }], removeRequested: [{ type: i0.Output, args: ["removeRequested"] }], duplicateRequested: [{ type: i0.Output, args: ["duplicateRequested"] }], renamed: [{ type: i0.Output, args: ["renamed"] }] } });
14603
14858
 
14604
14859
  /**
14605
14860
  * Il vincolo di spostamento delle finestre trascinabili dell'editor.
@@ -14714,12 +14969,25 @@ class ElementDialogComponent {
14714
14969
  const node = this.reference()?.node;
14715
14970
  return node?.label || this.selectedName() || '';
14716
14971
  }, ...(ngDevMode ? [{ debugName: "title" }] : []));
14972
+ /**
14973
+ * L'ultimo nome adottato da una rinomina.
14974
+ *
14975
+ * La selezione dell'editor **e' il nome**, quindi una rinomina cambia `selectedName` senza
14976
+ * che l'elemento sia cambiato. Senza distinguere i due casi il nome che si costruisce
14977
+ * dall'etichetta mentre la si digita riportava il focus sul pannello a ogni battuta, e
14978
+ * l'etichetta si scriveva una lettera alla volta.
14979
+ */
14980
+ renamedTo;
14717
14981
  constructor() {
14718
14982
  // Il focus entra nella dialog quando cambia elemento: da tastiera si arriva subito ai
14719
14983
  // campi, e `Esc` chiude senza dover prima cliccare dentro.
14720
14984
  effect(() => {
14721
14985
  const name = this.selectedName();
14722
14986
  const panel = this.panel()?.nativeElement;
14987
+ if (name && name === this.renamedTo) {
14988
+ this.renamedTo = undefined;
14989
+ return;
14990
+ }
14723
14991
  if (name && panel) {
14724
14992
  panel.focus({ preventScroll: true });
14725
14993
  }
@@ -14737,6 +15005,7 @@ class ElementDialogComponent {
14737
15005
  this.duplicateRequested.emit(name);
14738
15006
  }
14739
15007
  onRenamed(name) {
15008
+ this.renamedTo = name;
14740
15009
  this.renamed.emit(name);
14741
15010
  }
14742
15011
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: ElementDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
@@ -14935,6 +15204,47 @@ function constantReferencesResource(reference) {
14935
15204
  const value = reference.resource['value'];
14936
15205
  return !!value?.elementReference || !!value?.formulaExpression;
14937
15206
  }
15207
+ /**
15208
+ * §5.2 — i campi che su una sorgente `collectionReference` il runtime **ignora**.
15209
+ *
15210
+ * Filtri, logica e ordinamento valgono sulle sole sorgenti `object` ed `enumType`: le opzioni di
15211
+ * una collection sono i suoi elementi nell'ordine in cui ci sono. Dichiararli lì non rompe niente
15212
+ * — per questo e' `CHOICE_SET_FILTERS_IGNORED` e resta un **avviso** — ma e' peggio di un errore
15213
+ * di sintassi: il flow gira, il choice set propone tutto, e chi l'ha scritto crede di aver
15214
+ * selezionato qualcosa. Restituisce le **etichette** dei campi trovati, perche' l'avviso deve
15215
+ * dire quali sono: «i filtri» e «l'ordinamento» sono due rimedi diversi.
15216
+ */
15217
+ function ignoredChoiceSetFiltersOf(reference) {
15218
+ if (reference.collection !== 'dynamicChoiceSets') {
15219
+ return [];
15220
+ }
15221
+ const set = reference.resource;
15222
+ if (choiceSetSourceOf(set) !== 'collection') {
15223
+ return [];
15224
+ }
15225
+ const found = [];
15226
+ if (set.filters?.length) {
15227
+ found.push('i filtri');
15228
+ }
15229
+ if (set.filterLogic) {
15230
+ found.push('la logica dei filtri');
15231
+ }
15232
+ if (set.sortField || set.sortOrder) {
15233
+ found.push('l’ordinamento');
15234
+ }
15235
+ return found;
15236
+ }
15237
+ /**
15238
+ * La frase dell'avviso, con l'accordo giusto: «i filtri e l'ordinamento non si applicano».
15239
+ * Sta accanto al controllo perche' la dicono in due — l'elenco e il form — e due frasi scritte a
15240
+ * mano diventano due frasi diverse per lo stesso rilievo.
15241
+ */
15242
+ function describeIgnoredChoiceSetFilters(ignored) {
15243
+ const list = ignored.length > 1
15244
+ ? `${ignored.slice(0, -1).join(', ')} e ${ignored[ignored.length - 1]}`
15245
+ : ignored[0] ?? '';
15246
+ return `${list} ${ignored.length > 1 ? 'non si applicano' : 'non si applica'}`;
15247
+ }
14938
15248
  /** `stageOrder` duplicati: quale stage sia il corrente all'avvio diventa arbitrario. */
14939
15249
  function duplicateStageOrder(reference, resources) {
14940
15250
  if (reference.collection !== 'stages') {
@@ -14947,6 +15257,67 @@ function duplicateStageOrder(reference, resources) {
14947
15257
  return (resources.filter((entry) => entry.collection === 'stages' && entry.resource['stageOrder'] === order)
14948
15258
  .length > 1);
14949
15259
  }
15260
+ /**
15261
+ * `true` se la risorsa ha **almeno un** rilievo locale.
15262
+ *
15263
+ * E' la sintesi dei tre controlli qui sopra, e serve al filtro dell'elenco: «quali risorse
15264
+ * hanno qualcosa che non va» e' la domanda che su venti righe si risolveva scorrendole tutte.
15265
+ * Sta qui e non nel pannello perche' un quarto controllo aggiunto sopra deve entrare nel
15266
+ * filtro da se': una sintesi che elenca i check a mano si dimentica l'ultimo aggiunto, e il
15267
+ * difetto sarebbe una riga rotta che il filtro non mostra.
15268
+ */
15269
+ function hasResourceIssue(reference, usedNames, resources) {
15270
+ return (resourceNameError(reference, usedNames) !== null ||
15271
+ constantReferencesResource(reference) ||
15272
+ duplicateStageOrder(reference, resources) ||
15273
+ ignoredChoiceSetFiltersOf(reference).length > 0);
15274
+ }
15275
+
15276
+ /**
15277
+ * Il filtro dell'elenco delle risorse.
15278
+ *
15279
+ * Esiste per la **scala**: un flow di produzione ha ventun variabili e dodici dynamic choice
15280
+ * set, e a quella misura il problema dell'elenco non e' leggerlo ma trovarci dentro una riga.
15281
+ * Non e' la ricerca del flow (`core/flow-search.ts`), che sa dov'e' un nome che si conosce
15282
+ * **già**: qui si restringe un elenco che si sta guardando, e le due domande che si fanno
15283
+ * davvero — «quali non sono usate» e «quali hanno qualcosa che non va» — non sono nomi.
15284
+ *
15285
+ * Cio' che il testo guarda e' cio' che l'elenco **mostra**, piu' il nome e l'etichetta: e' la
15286
+ * stessa scelta di `flow-search`, dove il contenuto dei valori resta fuori perche' «chi cita
15287
+ * `Totale`» e' un'altra domanda — quella la risponde il conteggio degli usi. Quindi
15288
+ * l'espressione di una formula e il valore di una costante **non** si cercano: chi digita
15289
+ * «IVA» e vede comparire una formula che non si chiama così crede di aver trovato una risorsa
15290
+ * che non c'e'.
15291
+ */
15292
+ /**
15293
+ * Il testo su cui il filtro confronta: nome, etichetta, tipo, e le **parole dei flag** così
15294
+ * come la riga le scrive.
15295
+ *
15296
+ * I flag ci stanno perche' sull'elenco si leggono come testo (`· input`), e chi digita
15297
+ * «input» sta cercando proprio quelle: cercare in cio' che si vede e non trovarlo e' il modo
15298
+ * piu' rapido di far credere che il filtro sia rotto. `objectType` invece non si vede, ed e'
15299
+ * lì lo stesso perche' il tipo di una variabile record e' `Ordine`, non `Object`.
15300
+ */
15301
+ function haystackOf(reference) {
15302
+ const resource = reference.resource;
15303
+ const parts = [
15304
+ reference.name,
15305
+ typeof resource['label'] === 'string' ? resource['label'] : '',
15306
+ typeof resource['dataType'] === 'string' ? resource['dataType'] : '',
15307
+ typeof resource['objectType'] === 'string' ? resource['objectType'] : '',
15308
+ resource['isCollection'] === true ? 'collection' : '',
15309
+ resource['isInput'] === true ? 'input' : '',
15310
+ resource['isOutput'] === true ? 'output' : '',
15311
+ ];
15312
+ return parts.join(' ').toLowerCase();
15313
+ }
15314
+ /** `true` se la risorsa corrisponde al testo digitato; un testo vuoto non filtra niente. */
15315
+ function matchesResourceQuery(reference, needle) {
15316
+ if (!needle) {
15317
+ return true;
15318
+ }
15319
+ return haystackOf(reference).includes(needle);
15320
+ }
14950
15321
 
14951
15322
  /**
14952
15323
  * Le sette collection di risorse (§4.6).
@@ -14997,7 +15368,7 @@ const RESOURCE_KINDS = [
14997
15368
  label: 'Dynamic choice set',
14998
15369
  singular: 'dynamic choice set',
14999
15370
  isWritable: false,
15000
- note: 'Scelte generate da una collection, da una query o da un tipo enum. I filtri sono sempre in AND.',
15371
+ note: 'Scelte generate da una collection, da una query o da un tipo enum. Su una collection filtri e ordinamento non si applicano.',
15001
15372
  },
15002
15373
  {
15003
15374
  collection: 'stages',
@@ -15025,6 +15396,10 @@ function resourceKindOf(collection) {
15025
15396
  * per cui l'elenco esiste. Cio' che resta qui e' quello che si legge **senza** aprire: nome,
15026
15397
  * tipo, quante volte e' usata, e i rilievi locali della riga.
15027
15398
  *
15399
+ * Il **filtro** e' l'altra cosa che resta qui, e per la stessa ragione dell'elenco: su un flow
15400
+ * di produzione le righe sono cinquanta, e allora la domanda non e' piu' «cosa dice questa
15401
+ * risorsa» ma «dov'e' quella che cerco». Le regole del filtro stanno in `resource-filter.ts`.
15402
+ *
15028
15403
  * La finestra non la monta il pannello ma il builder, e per una ragione sola: qui dentro
15029
15404
  * sarebbe ritagliata dallo scorrimento della colonna laterale. Il pannello **annuncia** quale
15030
15405
  * risorsa si vuole configurare (`editRequested`) e riceve indietro quale sia aperta
@@ -15049,6 +15424,11 @@ class ResourcePanelComponent {
15049
15424
  * ricalcolo del documento.
15050
15425
  */
15051
15426
  pendingRemoval = signal(null, ...(ngDevMode ? [{ debugName: "pendingRemoval" }] : []));
15427
+ /** Il testo del filtro. Non si azzera cambiando scheda: vedi `select`. */
15428
+ query = signal('', ...(ngDevMode ? [{ debugName: "query" }] : []));
15429
+ /** Lo stato su cui si restringe: sono le due domande che un nome non sa fare. */
15430
+ stateFilter = signal('all', ...(ngDevMode ? [{ debugName: "stateFilter" }] : []));
15431
+ hasFilter = computed(() => this.query().trim() !== '' || this.stateFilter() !== 'all', ...(ngDevMode ? [{ debugName: "hasFilter" }] : []));
15052
15432
  constructor() {
15053
15433
  // L'arrivo dalla ricerca: apre la scheda giusta e la risorsa, nella finestra.
15054
15434
  effect(() => {
@@ -15057,19 +15437,126 @@ class ResourcePanelComponent {
15057
15437
  return;
15058
15438
  }
15059
15439
  this.activeCollection.set(target.collection);
15440
+ // Il filtro si azzera: la ricerca promette **quella** risorsa, e un filtro rimasto acceso
15441
+ // la terrebbe fuori dall'elenco proprio mentre la sua finestra si apre — cioe' una riga
15442
+ // evidenziata che non si vede. E' la stessa regola con cui `bringIntoView` apre il
15443
+ // riquadro chiuso che nasconde l'elemento cercato.
15444
+ this.resetFilter();
15060
15445
  this.editRequested.emit({ collection: target.collection, index: target.index });
15061
15446
  });
15062
15447
  }
15063
15448
  activeKind = computed(() => resourceKindOf(this.activeCollection()), ...(ngDevMode ? [{ debugName: "activeKind" }] : []));
15064
- items = computed(() => this.store.resources().filter((reference) => reference.collection === this.activeCollection()), ...(ngDevMode ? [{ debugName: "items" }] : []));
15065
- countOf(collection) {
15066
- return this.store.resources().filter((reference) => reference.collection === collection).length;
15067
- }
15449
+ /** Tutte le risorse della collection aperta, **prima** del filtro. */
15450
+ collectionItems = computed(() => this.store.resources().filter((reference) => reference.collection === this.activeCollection()), ...(ngDevMode ? [{ debugName: "collectionItems" }] : []));
15451
+ /** Le righe che l'elenco disegna: la collection aperta, filtrata. */
15452
+ items = computed(() => this.collectionItems().filter((reference) => this.matches(reference)), ...(ngDevMode ? [{ debugName: "items" }] : []));
15068
15453
  select(collection) {
15069
15454
  this.activeCollection.set(collection);
15070
15455
  // Cambiare scheda con una finestra aperta su un'altra collection lascerebbe due contesti
15071
- // discordi: si chiude.
15456
+ // discordi: si chiude. La conferma di rimozione pure: la riga a cui si riferisce non e'
15457
+ // piu' nell'elenco, e resterebbe appesa su una risorsa che non si vede.
15072
15458
  this.editRequested.emit(null);
15459
+ this.pendingRemoval.set(null);
15460
+ }
15461
+ // -------------------------------------------------------------------------
15462
+ // Filtro
15463
+ // -------------------------------------------------------------------------
15464
+ needle = computed(() => this.query().trim().toLowerCase(), ...(ngDevMode ? [{ debugName: "needle" }] : []));
15465
+ /** `true` se la risorsa passa testo **e** stato. */
15466
+ matches(reference) {
15467
+ if (!matchesResourceQuery(reference, this.needle())) {
15468
+ return false;
15469
+ }
15470
+ switch (this.stateFilter()) {
15471
+ case 'unused':
15472
+ return this.usesOf(reference) === 0;
15473
+ case 'issues':
15474
+ return hasResourceIssue(reference, this.store.usedNames(), this.store.resources());
15475
+ default:
15476
+ return true;
15477
+ }
15478
+ }
15479
+ /**
15480
+ * Quante risorse di ogni collection passano il filtro, e quante ce ne sono in tutto.
15481
+ *
15482
+ * Il numero sulla scheda diventa quello delle **corrispondenze** appena un filtro e' attivo,
15483
+ * ed e' il solo modo di rispondere alla domanda vera di chi digita: la risorsa che cerca sta
15484
+ * spesso in un'altra collection, e senza questo numero bisognerebbe aprire le sette schede
15485
+ * una per una per scoprire che non c'e' da nessuna parte. Il conteggio e' **uno** per tutte
15486
+ * le schede e non una `filter` per scheda nel template: quello lo rifaceva a ogni giro di
15487
+ * change detection, e con il filtro «non usate» dentro sarebbero cinquanta walk del
15488
+ * documento per volta.
15489
+ */
15490
+ tabCounts = computed(() => {
15491
+ const counts = {};
15492
+ for (const kind of RESOURCE_KINDS) {
15493
+ counts[kind.collection] = { total: 0, matching: 0 };
15494
+ }
15495
+ for (const reference of this.store.resources()) {
15496
+ const entry = counts[reference.collection];
15497
+ if (!entry) {
15498
+ continue;
15499
+ }
15500
+ entry.total += 1;
15501
+ if (this.matches(reference)) {
15502
+ entry.matching += 1;
15503
+ }
15504
+ }
15505
+ return counts;
15506
+ }, ...(ngDevMode ? [{ debugName: "tabCounts" }] : []));
15507
+ countOf(collection) {
15508
+ return this.tabCounts()[collection]?.matching ?? 0;
15509
+ }
15510
+ totalOf(collection) {
15511
+ return this.tabCounts()[collection]?.total ?? 0;
15512
+ }
15513
+ /**
15514
+ * Quante risorse ha ogni stato nella scheda aperta, contate **dopo** il testo: i due chip
15515
+ * dicono quanto resterebbe, non quanto ce n'e' in assoluto. A zero si disabilitano, perche'
15516
+ * un filtro che da' certamente un elenco vuoto non e' un filtro ma un modo di nascondere
15517
+ * l'elenco.
15518
+ */
15519
+ stateCounts = computed(() => {
15520
+ const needle = this.needle();
15521
+ const usedNames = this.store.usedNames();
15522
+ const resources = this.store.resources();
15523
+ let unused = 0;
15524
+ let issues = 0;
15525
+ for (const reference of this.collectionItems()) {
15526
+ if (!matchesResourceQuery(reference, needle)) {
15527
+ continue;
15528
+ }
15529
+ if (this.usesOf(reference) === 0) {
15530
+ unused += 1;
15531
+ }
15532
+ if (hasResourceIssue(reference, usedNames, resources)) {
15533
+ issues += 1;
15534
+ }
15535
+ }
15536
+ return { unused, issues };
15537
+ }, ...(ngDevMode ? [{ debugName: "stateCounts" }] : []));
15538
+ /**
15539
+ * Le corrispondenze nelle **altre** schede: e' cio' che si dice quando qui non c'e' niente.
15540
+ * «Nessuna corrispondenza» su un elenco vuoto e' vero e inutile — il rimedio e' cambiare
15541
+ * scheda, e bisogna sapere se ce n'e' una da cambiare.
15542
+ */
15543
+ matchesElsewhere = computed(() => {
15544
+ const active = this.activeCollection();
15545
+ return RESOURCE_KINDS.filter((kind) => kind.collection !== active).reduce((total, kind) => total + this.countOf(kind.collection), 0);
15546
+ }, ...(ngDevMode ? [{ debugName: "matchesElsewhere" }] : []));
15547
+ onQuery(value) {
15548
+ this.query.set(value);
15549
+ // La riga della conferma potrebbe non corrispondere piu': la conferma se ne va con lei.
15550
+ this.pendingRemoval.set(null);
15551
+ }
15552
+ /** Il secondo clic sullo stesso stato lo spegne, come nel pannello dei problemi. */
15553
+ setStateFilter(filter) {
15554
+ this.stateFilter.set(this.stateFilter() === filter ? 'all' : filter);
15555
+ this.pendingRemoval.set(null);
15556
+ }
15557
+ resetFilter() {
15558
+ this.query.set('');
15559
+ this.stateFilter.set('all');
15073
15560
  }
15074
15561
  isEditing(reference) {
15075
15562
  const editing = this.editing();
@@ -15092,6 +15579,15 @@ class ResourcePanelComponent {
15092
15579
  duplicateStageOrder(reference) {
15093
15580
  return duplicateStageOrder(reference, this.store.resources());
15094
15581
  }
15582
+ /**
15583
+ * §5.2 — cio' che su una sorgente collection il runtime ignora. Sta anche qui e non solo nel
15584
+ * form per la ragione di sempre: il filtro «con rilievi» porta la riga a galla, e una riga che
15585
+ * non dice **cosa** non va costringe ad aprirla per scoprirlo.
15586
+ */
15587
+ ignoredChoiceSetFilters(reference) {
15588
+ const ignored = ignoredChoiceSetFiltersOf(reference);
15589
+ return ignored.length ? describeIgnoredChoiceSetFilters(ignored) : null;
15590
+ }
15095
15591
  // -------------------------------------------------------------------------
15096
15592
  // Aggiunta e rimozione
15097
15593
  // -------------------------------------------------------------------------
@@ -15118,9 +15614,14 @@ class ResourcePanelComponent {
15118
15614
  resource['label'] = name;
15119
15615
  }
15120
15616
  this.store.addResource(kind.collection, resource);
15617
+ // Il filtro si spegne, e l'indice si conta sulla collection **intera**: la risorsa appena
15618
+ // aggiunta si chiama «variabile_1» e non corrisponderebbe a niente di cio' che si stava
15619
+ // cercando — con il filtro acceso si aprirebbe la finestra su una riga invisibile, e
15620
+ // `items()` filtrato darebbe l'indice di un'altra risorsa.
15621
+ this.resetFilter();
15121
15622
  // La risorsa nuova e' l'ultima della collection, e si apre subito: aggiungerla senza aprirla
15122
15623
  // lascerebbe una riga chiamata «variabile_1» da configurare in un secondo gesto.
15123
- this.editRequested.emit({ collection: kind.collection, index: this.items().length - 1 });
15624
+ this.editRequested.emit({ collection: kind.collection, index: this.collectionItems().length - 1 });
15124
15625
  }
15125
15626
  /**
15126
15627
  * Quante volte la risorsa e' **usata** nel documento.
@@ -15134,8 +15635,30 @@ class ResourcePanelComponent {
15134
15635
  * senza, tre formule che leggono una variabile la direbbero «non usata».
15135
15636
  */
15136
15637
  usesOf(reference) {
15137
- return countNameUses(this.store.document(), reference.name);
15638
+ return this.useCounts().get(reference.name) ?? 0;
15138
15639
  }
15640
+ /**
15641
+ * Il conteggio di **tutte** le risorse, calcolato una volta per documento.
15642
+ *
15643
+ * `countNameUses` cammina il documento intero a ogni chiamata, e da quando il filtro lo
15644
+ * chiede per ogni risorsa di ogni collection — i numeri delle schede, il chip «non usate» —
15645
+ * chiamarlo dal template significherebbe cinquanta walk per giro di change detection.
15646
+ *
15647
+ * La chiave e' il nome **esatto** e non quello minuscolo: `countNameUses` confronta le
15648
+ * stringhe come sono, quindi due nomi che differiscono solo per il case — che sono un
15649
+ * `NAME_DUPLICATED`, non un caso da normalizzare qui — hanno due conteggi diversi, ed e'
15650
+ * quello che il pannello mostrava anche prima della memoizzazione.
15651
+ */
15652
+ useCounts = computed(() => {
15653
+ const document = this.store.document();
15654
+ const counts = new Map();
15655
+ for (const reference of this.store.resources()) {
15656
+ if (!counts.has(reference.name)) {
15657
+ counts.set(reference.name, countNameUses(document, reference.name));
15658
+ }
15659
+ }
15660
+ return counts;
15661
+ }, ...(ngDevMode ? [{ debugName: "useCounts" }] : []));
15139
15662
  keyOf(reference) {
15140
15663
  return `${reference.collection}:${reference.index}`;
15141
15664
  }
@@ -15178,11 +15701,11 @@ class ResourcePanelComponent {
15178
15701
  this.closed.emit();
15179
15702
  }
15180
15703
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: ResourcePanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
15181
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.28", type: ResourcePanelComponent, isStandalone: true, selector: "fb-resource-panel", inputs: { target: { classPropertyName: "target", publicName: "target", isSignal: true, isRequired: false, transformFunction: null }, editing: { classPropertyName: "editing", publicName: "editing", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closed: "closed", editRequested: "editRequested" }, ngImport: i0, template: "<header class=\"fb-res__header\">\r\n <h2 class=\"fb-res__title\">Risorse</h2>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" aria-label=\"Chiudi\" (click)=\"close()\">\u00D7</button>\r\n</header>\r\n\r\n<nav class=\"fb-res__tabs\" aria-label=\"Tipi di risorsa\">\r\n @for (kind of kinds; track kind.collection) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-res__tab\"\r\n [class.fb-res__tab--active]=\"activeCollection() === kind.collection\"\r\n (click)=\"select(kind.collection)\"\r\n >\r\n {{ kind.label }}\r\n <span class=\"fb-res__count\">{{ countOf(kind.collection) }}</span>\r\n </button>\r\n }\r\n</nav>\r\n\r\n<div class=\"fb-res__body\">\r\n <p class=\"fb-section__note\">{{ activeKind().note }}</p>\r\n\r\n <div class=\"fb-list\">\r\n @for (item of items(); track item.collection + ':' + item.index) {\r\n <div class=\"fb-list__item\" [class.fb-list__item--active]=\"isEditing(item)\">\r\n <div class=\"fb-list__header\">\r\n <!--\r\n Il nome apre la finestra di modifica: qui l\u2019elenco resta un elenco, e cio\u2019 che si\r\n configura non lo copre. \u00C8 la stessa scelta della dialog del dettaglio.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-res__name\"\r\n [title]=\"'Configura ' + (item.name || 'la risorsa')\"\r\n (click)=\"edit(item)\"\r\n >\r\n <span class=\"fb-res__name-text\">{{ item.name || '(senza nome)' }}</span>\r\n <span class=\"fb-res__meta\">\r\n {{ string(item, 'dataType') }}{{ boolean(item, 'isCollection') ? '[]' : '' }}\r\n @if (boolean(item, 'isInput')) {\r\n \u00B7 input\r\n }\r\n @if (boolean(item, 'isOutput')) {\r\n \u00B7 output\r\n }\r\n </span>\r\n </button>\r\n <span class=\"fb-list__spacer\"></span>\r\n <!--\r\n Il numero di usi sta accanto al nome perche\u2019 e\u2019 cio\u2019 che decide se si puo\u2019 togliere:\r\n \u00ABnon usata\u00BB e\u2019 un invito a fare pulizia, \u00ABusata 15 volte\u00BB e\u2019 un avvertimento.\r\n -->\r\n @let uses = usesOf(item);\r\n @if (uses === 0) {\r\n <span class=\"fb-res__uses fb-res__uses--none\" title=\"Nessun riferimento la usa: si puo\u2019 togliere\">\r\n non usata\r\n </span>\r\n } @else {\r\n <span\r\n class=\"fb-res__uses\"\r\n [title]=\"'Referenziata ' + uses + ' volte nel documento, formule comprese'\"\r\n >\r\n {{ uses }} usi\r\n </span>\r\n }\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n aria-label=\"Rimuovi la risorsa\"\r\n [title]=\"uses ? 'Rimuovi: e\u2019 usata ' + uses + ' volte, verra\u2019 chiesta conferma' : 'Rimuovi'\"\r\n (click)=\"requestRemove(item)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n\r\n @if (isPendingRemoval(item)) {\r\n <!--\r\n La conferma sta qui e non in una finestra: la riga che si sta togliendo resta sotto\r\n gli occhi, e il numero di riferimenti che si rompono e\u2019 il dato della decisione.\r\n -->\r\n <p class=\"fb-callout fb-callout--warn fb-res__confirm\">\r\n <span>\r\n \u00AB{{ item.name }}\u00BB e\u2019 usata {{ usesOf(item) }} volte: togliendola quei riferimenti\r\n restano orfani e la validazione li segnalera\u2019.\r\n </span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--icon\" (click)=\"remove(item)\">Rimuovi comunque</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" (click)=\"cancelRemove()\">\r\n Annulla\r\n </button>\r\n </p>\r\n }\r\n\r\n <!--\r\n I rilievi locali restano nell\u2019elenco anche ora che il form sta altrove: senza, per\r\n sapere quali risorse hanno qualcosa che non va bisognerebbe aprirle una per una.\r\n -->\r\n @if (nameError(item); as message) {\r\n <p class=\"fb-field__error\">{{ message }}</p>\r\n }\r\n @if (constantReferencesResource(item)) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n Una costante deve essere un valore fisso: non puo\u2019 referenziare altre risorse\r\n (CONSTANT_REFERENCES_RESOURCE).\r\n </p>\r\n }\r\n @if (duplicateStageOrder(item)) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Due stage con lo stesso ordine: quale sia il corrente all\u2019avvio diventa arbitrario\r\n (STAGE_ORDER_DUPLICATED).\r\n </p>\r\n }\r\n </div>\r\n } @empty {\r\n <p class=\"fb-empty\">Nessuna risorsa di questo tipo.</p>\r\n }\r\n </div>\r\n\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" (click)=\"add()\">\r\n Aggiungi {{ activeKind().singular }}\r\n </button>\r\n</div>\r\n", styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0;background:var(--fb-surface, #fff)}.fb-res__header{display:flex;align-items:center;justify-content:space-between;padding:10px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-res__title{margin:0;font-size:14px;font-weight:600;color:var(--fb-text, #1d2939)}.fb-res__tabs{display:flex;flex-wrap:wrap;gap:2px;padding:6px 8px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-res__tab{display:inline-flex;align-items:center;gap:4px;padding:3px 8px;border:1px solid var(--fb-border-subtle, #e6e9ee);border-radius:12px;background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:11px;cursor:pointer}.fb-res__tab:hover{background:var(--fb-surface-alt, #f8f9fb)}.fb-res__tab--active{border-color:var(--fb-accent, #2f6feb);background:color-mix(in srgb,var(--fb-accent, #2f6feb) 10%,transparent);color:var(--fb-accent, #2f6feb);font-weight:600}.fb-res__count{padding:0 4px;border-radius:6px;background:var(--fb-border, #d6dae1);font-size:9px;color:var(--fb-text, #1d2939)}.fb-res__body{flex:1;min-height:0;overflow-y:auto;padding:10px 12px}.fb-res__name{flex:1;min-width:0;display:flex;flex-direction:column;padding:0;border:0;background:transparent;color:var(--fb-text, #1d2939);font:inherit;text-align:left;cursor:pointer}.fb-res__name-text{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-res__meta{font-size:10px;color:var(--fb-text-muted, #667085)}.fb-res__uses{flex:none;padding:1px 6px;border-radius:999px;background:var(--fb-surface-sunken, #eef0f4);color:var(--fb-text-muted, #6b7086);font-size:10px;font-variant-numeric:tabular-nums;white-space:nowrap}.fb-res__uses--none{background:color-mix(in srgb,var(--fb-warning, #b7791f) 12%,transparent);color:var(--fb-warning, #b7791f)}.fb-res__confirm{display:flex;flex-wrap:wrap;align-items:center;gap:6px}.fb-list__item--active{border-color:var(--fb-accent, #2f6feb);background:color-mix(in srgb,var(--fb-accent, #2f6feb) 6%,var(--fb-surface-alt, #f8f9fb))}.fb-list__item .fb-list__header{margin-bottom:0}.fb-list__item .fb-list__header+*{margin-top:6px}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
15704
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.28", type: ResourcePanelComponent, isStandalone: true, selector: "fb-resource-panel", inputs: { target: { classPropertyName: "target", publicName: "target", isSignal: true, isRequired: false, transformFunction: null }, editing: { classPropertyName: "editing", publicName: "editing", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closed: "closed", editRequested: "editRequested" }, ngImport: i0, template: "<header class=\"fb-res__header\">\r\n <h2 class=\"fb-res__title\">Risorse</h2>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" aria-label=\"Chiudi\" (click)=\"close()\">\u00D7</button>\r\n</header>\r\n\r\n<nav class=\"fb-res__tabs\" aria-label=\"Tipi di risorsa\">\r\n @for (kind of kinds; track kind.collection) {\r\n <!--\r\n Con un filtro acceso il numero della scheda e\u2019 quello delle **corrispondenze**: e\u2019 cio\u2019\r\n che dice dove sta la risorsa che si cerca, che il piu\u2019 delle volte e\u2019 in un\u2019altra\r\n collection. La scheda a zero si sbiadisce invece di sparire \u2014 nasconderla farebbe\r\n ballare la barra a ogni lettera digitata.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-res__tab\"\r\n [class.fb-res__tab--active]=\"activeCollection() === kind.collection\"\r\n [class.fb-res__tab--dim]=\"hasFilter() && countOf(kind.collection) === 0\"\r\n [title]=\"\r\n hasFilter()\r\n ? countOf(kind.collection) + ' su ' + totalOf(kind.collection) + ' con il filtro attivo'\r\n : kind.label\r\n \"\r\n (click)=\"select(kind.collection)\"\r\n >\r\n {{ kind.label }}\r\n <span class=\"fb-res__count\" [class.fb-res__count--match]=\"hasFilter()\">\r\n {{ countOf(kind.collection) }}\r\n </span>\r\n </button>\r\n }\r\n</nav>\r\n\r\n<!--\r\n Il filtro sta fra le schede e l\u2019elenco, e non scorre con esso: su una collection lunga\r\n scorrere per raggiungere la casella con cui accorciarla e\u2019 il gesto che non deve servire.\r\n-->\r\n<div class=\"fb-res__filter\">\r\n <input\r\n type=\"search\"\r\n class=\"fb-res__search\"\r\n placeholder=\"Filtra per nome o tipo\u2026\"\r\n aria-label=\"Filtra le risorse per nome o tipo\"\r\n title=\"Cerca nel nome, nell\u2019etichetta, nel tipo e nei flag (input, output, collection). Non guarda dentro i valori e le formule: a dire chi cita una risorsa e\u2019 il conteggio degli usi\"\r\n [value]=\"query()\"\r\n (input)=\"onQuery($any($event.target).value)\"\r\n />\r\n\r\n <div class=\"fb-res__chips\" role=\"group\" aria-label=\"Filtra per stato\">\r\n @let counts = stateCounts();\r\n <!--\r\n I due stati sono le domande che un nome non sa fare, e sono quelle che si fanno davvero\r\n su un flow grande: \u00ABquali variabili sono morte\u00BB e \u00ABquali righe hanno qualcosa che non\r\n va\u00BB. A zero il chip si disabilita: un filtro che darebbe certamente un elenco vuoto non\r\n restringe niente, nasconde tutto.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-res__chip\"\r\n [class.fb-res__chip--active]=\"stateFilter() === 'unused'\"\r\n [attr.aria-pressed]=\"stateFilter() === 'unused'\"\r\n [disabled]=\"counts.unused === 0 && stateFilter() !== 'unused'\"\r\n title=\"Solo le risorse che nessun riferimento e nessuna formula usa\"\r\n (click)=\"setStateFilter('unused')\"\r\n >\r\n Non usate <span class=\"fb-res__count\">{{ counts.unused }}</span>\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-res__chip fb-res__chip--issues\"\r\n [class.fb-res__chip--active]=\"stateFilter() === 'issues'\"\r\n [attr.aria-pressed]=\"stateFilter() === 'issues'\"\r\n [disabled]=\"counts.issues === 0 && stateFilter() !== 'issues'\"\r\n title=\"Solo le risorse con un rilievo locale: nome duplicato, costante che referenzia, ordine di stage doppio\"\r\n (click)=\"setStateFilter('issues')\"\r\n >\r\n Con rilievi <span class=\"fb-res__count\">{{ counts.issues }}</span>\r\n </button>\r\n @if (hasFilter()) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-res__chip fb-res__chip--reset\"\r\n title=\"Mostra tutte le risorse della scheda\"\r\n (click)=\"resetFilter()\"\r\n >\r\n Azzera\r\n </button>\r\n }\r\n </div>\r\n</div>\r\n\r\n<div class=\"fb-res__body\">\r\n <p class=\"fb-section__note\">{{ activeKind().note }}</p>\r\n\r\n <div class=\"fb-list\">\r\n @for (item of items(); track item.collection + ':' + item.index) {\r\n <div class=\"fb-list__item\" [class.fb-list__item--active]=\"isEditing(item)\">\r\n <div class=\"fb-list__header\">\r\n <!--\r\n Il nome apre la finestra di modifica: qui l\u2019elenco resta un elenco, e cio\u2019 che si\r\n configura non lo copre. \u00C8 la stessa scelta della dialog del dettaglio.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-res__name\"\r\n [title]=\"'Configura ' + (item.name || 'la risorsa')\"\r\n (click)=\"edit(item)\"\r\n >\r\n <span class=\"fb-res__name-text\">{{ item.name || '(senza nome)' }}</span>\r\n <!--\r\n L\u2019`objectType` sta accanto al tipo perche\u2019 su una `Structure` il tipo da solo non\r\n dice niente: sei variabili su ventuno dicevano \u00ABStructure\u00BB e la classe si leggeva\r\n solo aprendole. E\u2019 anche cio\u2019 che rende spiegabile il filtro, che l\u2019`objectType`\r\n lo guarda: senza, cercare \u00ABdto\u00BB faceva comparire tre righe che \u00ABdto\u00BB non lo\r\n mostrano da nessuna parte \u2014 un filtro che sembra sbagliato.\r\n -->\r\n <span class=\"fb-res__meta\">\r\n {{ string(item, 'dataType') }}{{ boolean(item, 'isCollection') ? '[]' : '' }}\r\n @if (string(item, 'objectType')) {\r\n \u00B7 {{ string(item, 'objectType') }}\r\n }\r\n @if (boolean(item, 'isInput')) {\r\n \u00B7 input\r\n }\r\n @if (boolean(item, 'isOutput')) {\r\n \u00B7 output\r\n }\r\n </span>\r\n </button>\r\n <span class=\"fb-list__spacer\"></span>\r\n <!--\r\n Il numero di usi sta accanto al nome perche\u2019 e\u2019 cio\u2019 che decide se si puo\u2019 togliere:\r\n \u00ABnon usata\u00BB e\u2019 un invito a fare pulizia, \u00ABusata 15 volte\u00BB e\u2019 un avvertimento.\r\n -->\r\n @let uses = usesOf(item);\r\n @if (uses === 0) {\r\n <span class=\"fb-res__uses fb-res__uses--none\" title=\"Nessun riferimento la usa: si puo\u2019 togliere\">\r\n non usata\r\n </span>\r\n } @else {\r\n <span\r\n class=\"fb-res__uses\"\r\n [title]=\"'Referenziata ' + uses + ' volte nel documento, formule comprese'\"\r\n >\r\n {{ uses }} usi\r\n </span>\r\n }\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n aria-label=\"Rimuovi la risorsa\"\r\n [title]=\"uses ? 'Rimuovi: e\u2019 usata ' + uses + ' volte, verra\u2019 chiesta conferma' : 'Rimuovi'\"\r\n (click)=\"requestRemove(item)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n\r\n @if (isPendingRemoval(item)) {\r\n <!--\r\n La conferma sta qui e non in una finestra: la riga che si sta togliendo resta sotto\r\n gli occhi, e il numero di riferimenti che si rompono e\u2019 il dato della decisione.\r\n -->\r\n <p class=\"fb-callout fb-callout--warn fb-res__confirm\">\r\n <span>\r\n \u00AB{{ item.name }}\u00BB e\u2019 usata {{ usesOf(item) }} volte: togliendola quei riferimenti\r\n restano orfani e la validazione li segnalera\u2019.\r\n </span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--icon\" (click)=\"remove(item)\">Rimuovi comunque</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" (click)=\"cancelRemove()\">\r\n Annulla\r\n </button>\r\n </p>\r\n }\r\n\r\n <!--\r\n I rilievi locali restano nell\u2019elenco anche ora che il form sta altrove: senza, per\r\n sapere quali risorse hanno qualcosa che non va bisognerebbe aprirle una per una.\r\n -->\r\n @if (nameError(item); as message) {\r\n <p class=\"fb-field__error\">{{ message }}</p>\r\n }\r\n @if (constantReferencesResource(item)) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n Una costante deve essere un valore fisso: non puo\u2019 referenziare altre risorse\r\n (CONSTANT_REFERENCES_RESOURCE).\r\n </p>\r\n }\r\n @if (duplicateStageOrder(item)) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Due stage con lo stesso ordine: quale sia il corrente all\u2019avvio diventa arbitrario\r\n (STAGE_ORDER_DUPLICATED).\r\n </p>\r\n }\r\n @if (ignoredChoiceSetFilters(item); as ignored) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Le opzioni vengono da una collection: {{ ignored }} (CHOICE_SET_FILTERS_IGNORED).\r\n </p>\r\n }\r\n </div>\r\n } @empty {\r\n <!--\r\n Tre casi e tre rimedi: la collection e\u2019 vuota (si aggiunge), il filtro non trova\r\n niente qui ma trova altrove (si cambia scheda), il filtro non trova niente da nessuna\r\n parte (si azzera). Un solo \u00ABnessuna risorsa\u00BB per tutti e tre manda a cercare il\r\n difetto dove non c\u2019e\u2019.\r\n -->\r\n @if (!hasFilter()) {\r\n <p class=\"fb-empty\">Nessuna risorsa di questo tipo.</p>\r\n } @else if (matchesElsewhere() > 0) {\r\n <p class=\"fb-empty\">\r\n Nessuna corrispondenza in questa scheda, ma {{ matchesElsewhere() }} in altre: il\r\n numero accanto a ognuna dice dove.\r\n </p>\r\n } @else {\r\n <p class=\"fb-empty\">\r\n Nessuna risorsa corrisponde al filtro.\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" (click)=\"resetFilter()\">\r\n Azzera il filtro\r\n </button>\r\n </p>\r\n }\r\n }\r\n </div>\r\n\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" (click)=\"add()\">\r\n Aggiungi {{ activeKind().singular }}\r\n </button>\r\n</div>\r\n", styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0;background:var(--fb-surface, #fff)}.fb-res__header{display:flex;align-items:center;justify-content:space-between;padding:10px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-res__title{margin:0;font-size:14px;font-weight:600;color:var(--fb-text, #1d2939)}.fb-res__tabs{display:flex;flex-wrap:wrap;gap:2px;padding:6px 8px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-res__tab{display:inline-flex;align-items:center;gap:4px;padding:3px 8px;border:1px solid var(--fb-border-subtle, #e6e9ee);border-radius:12px;background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:11px;cursor:pointer}.fb-res__tab:hover{background:var(--fb-surface-alt, #f8f9fb)}.fb-res__tab--active{border-color:var(--fb-accent, #2f6feb);background:color-mix(in srgb,var(--fb-accent, #2f6feb) 10%,transparent);color:var(--fb-accent, #2f6feb);font-weight:600}.fb-res__count{padding:0 4px;border-radius:6px;background:var(--fb-border, #d6dae1);font-size:9px;color:var(--fb-text, #1d2939)}.fb-res__body{flex:1;min-height:0;overflow-y:auto;padding:10px 12px}.fb-res__name{flex:1;min-width:0;display:flex;flex-direction:column;padding:0;border:0;background:transparent;color:var(--fb-text, #1d2939);font:inherit;text-align:left;cursor:pointer}.fb-res__name-text{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-res__meta{font-size:10px;color:var(--fb-text-muted, #667085);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-res__uses{flex:none;padding:1px 6px;border-radius:999px;background:var(--fb-surface-sunken, #eef0f4);color:var(--fb-text-muted, #6b7086);font-size:10px;font-variant-numeric:tabular-nums;white-space:nowrap}.fb-res__uses--none{background:color-mix(in srgb,var(--fb-warning, #b7791f) 12%,transparent);color:var(--fb-warning, #b7791f)}.fb-res__confirm{display:flex;flex-wrap:wrap;align-items:center;gap:6px}.fb-list__item--active{border-color:var(--fb-accent, #2f6feb);background:color-mix(in srgb,var(--fb-accent, #2f6feb) 6%,var(--fb-surface-alt, #f8f9fb))}.fb-list__item .fb-list__header{margin-bottom:0}.fb-list__item .fb-list__header+*{margin-top:6px}.fb-res__filter{display:flex;flex-direction:column;gap:6px;padding:8px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-res__search{box-sizing:border-box;width:100%;padding:5px 8px;border:1px solid var(--fb-border, #d6dae1);border-radius:6px;background:var(--fb-surface, #fff);color:var(--fb-text, #1d2939);font:inherit;font-size:12px}.fb-res__search:focus-visible{outline:2px solid var(--fb-accent, #2f6feb);outline-offset:-1px}.fb-res__chips{display:flex;flex-wrap:wrap;gap:4px}.fb-res__chip{display:inline-flex;align-items:center;gap:4px;padding:3px 8px;border:0;border-radius:6px;background:var(--fb-surface-sunken, #eef0f4);color:var(--fb-text-muted, #667085);font:inherit;font-size:11px;cursor:pointer}.fb-res__chip:hover:not(:disabled){background:var(--fb-border, #d6dae1)}.fb-res__chip:disabled{opacity:.45;cursor:default}.fb-res__chip--active{background:var(--fb-accent, #2f6feb);color:#fff;font-weight:600}.fb-res__chip--active .fb-res__count{background:color-mix(in srgb,#fff 30%,transparent);color:#fff}.fb-res__chip--reset{margin-left:auto}.fb-res__tab--dim{opacity:.4}.fb-res__count--match{background:color-mix(in srgb,var(--fb-accent, #2f6feb) 20%,transparent);color:var(--fb-accent, #2f6feb);font-weight:600}.fb-empty .fb-btn{margin-left:6px}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
15182
15705
  }
15183
15706
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: ResourcePanelComponent, decorators: [{
15184
15707
  type: Component,
15185
- args: [{ selector: 'fb-resource-panel', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<header class=\"fb-res__header\">\r\n <h2 class=\"fb-res__title\">Risorse</h2>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" aria-label=\"Chiudi\" (click)=\"close()\">\u00D7</button>\r\n</header>\r\n\r\n<nav class=\"fb-res__tabs\" aria-label=\"Tipi di risorsa\">\r\n @for (kind of kinds; track kind.collection) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-res__tab\"\r\n [class.fb-res__tab--active]=\"activeCollection() === kind.collection\"\r\n (click)=\"select(kind.collection)\"\r\n >\r\n {{ kind.label }}\r\n <span class=\"fb-res__count\">{{ countOf(kind.collection) }}</span>\r\n </button>\r\n }\r\n</nav>\r\n\r\n<div class=\"fb-res__body\">\r\n <p class=\"fb-section__note\">{{ activeKind().note }}</p>\r\n\r\n <div class=\"fb-list\">\r\n @for (item of items(); track item.collection + ':' + item.index) {\r\n <div class=\"fb-list__item\" [class.fb-list__item--active]=\"isEditing(item)\">\r\n <div class=\"fb-list__header\">\r\n <!--\r\n Il nome apre la finestra di modifica: qui l\u2019elenco resta un elenco, e cio\u2019 che si\r\n configura non lo copre. \u00C8 la stessa scelta della dialog del dettaglio.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-res__name\"\r\n [title]=\"'Configura ' + (item.name || 'la risorsa')\"\r\n (click)=\"edit(item)\"\r\n >\r\n <span class=\"fb-res__name-text\">{{ item.name || '(senza nome)' }}</span>\r\n <span class=\"fb-res__meta\">\r\n {{ string(item, 'dataType') }}{{ boolean(item, 'isCollection') ? '[]' : '' }}\r\n @if (boolean(item, 'isInput')) {\r\n \u00B7 input\r\n }\r\n @if (boolean(item, 'isOutput')) {\r\n \u00B7 output\r\n }\r\n </span>\r\n </button>\r\n <span class=\"fb-list__spacer\"></span>\r\n <!--\r\n Il numero di usi sta accanto al nome perche\u2019 e\u2019 cio\u2019 che decide se si puo\u2019 togliere:\r\n \u00ABnon usata\u00BB e\u2019 un invito a fare pulizia, \u00ABusata 15 volte\u00BB e\u2019 un avvertimento.\r\n -->\r\n @let uses = usesOf(item);\r\n @if (uses === 0) {\r\n <span class=\"fb-res__uses fb-res__uses--none\" title=\"Nessun riferimento la usa: si puo\u2019 togliere\">\r\n non usata\r\n </span>\r\n } @else {\r\n <span\r\n class=\"fb-res__uses\"\r\n [title]=\"'Referenziata ' + uses + ' volte nel documento, formule comprese'\"\r\n >\r\n {{ uses }} usi\r\n </span>\r\n }\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n aria-label=\"Rimuovi la risorsa\"\r\n [title]=\"uses ? 'Rimuovi: e\u2019 usata ' + uses + ' volte, verra\u2019 chiesta conferma' : 'Rimuovi'\"\r\n (click)=\"requestRemove(item)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n\r\n @if (isPendingRemoval(item)) {\r\n <!--\r\n La conferma sta qui e non in una finestra: la riga che si sta togliendo resta sotto\r\n gli occhi, e il numero di riferimenti che si rompono e\u2019 il dato della decisione.\r\n -->\r\n <p class=\"fb-callout fb-callout--warn fb-res__confirm\">\r\n <span>\r\n \u00AB{{ item.name }}\u00BB e\u2019 usata {{ usesOf(item) }} volte: togliendola quei riferimenti\r\n restano orfani e la validazione li segnalera\u2019.\r\n </span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--icon\" (click)=\"remove(item)\">Rimuovi comunque</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" (click)=\"cancelRemove()\">\r\n Annulla\r\n </button>\r\n </p>\r\n }\r\n\r\n <!--\r\n I rilievi locali restano nell\u2019elenco anche ora che il form sta altrove: senza, per\r\n sapere quali risorse hanno qualcosa che non va bisognerebbe aprirle una per una.\r\n -->\r\n @if (nameError(item); as message) {\r\n <p class=\"fb-field__error\">{{ message }}</p>\r\n }\r\n @if (constantReferencesResource(item)) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n Una costante deve essere un valore fisso: non puo\u2019 referenziare altre risorse\r\n (CONSTANT_REFERENCES_RESOURCE).\r\n </p>\r\n }\r\n @if (duplicateStageOrder(item)) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Due stage con lo stesso ordine: quale sia il corrente all\u2019avvio diventa arbitrario\r\n (STAGE_ORDER_DUPLICATED).\r\n </p>\r\n }\r\n </div>\r\n } @empty {\r\n <p class=\"fb-empty\">Nessuna risorsa di questo tipo.</p>\r\n }\r\n </div>\r\n\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" (click)=\"add()\">\r\n Aggiungi {{ activeKind().singular }}\r\n </button>\r\n</div>\r\n", styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0;background:var(--fb-surface, #fff)}.fb-res__header{display:flex;align-items:center;justify-content:space-between;padding:10px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-res__title{margin:0;font-size:14px;font-weight:600;color:var(--fb-text, #1d2939)}.fb-res__tabs{display:flex;flex-wrap:wrap;gap:2px;padding:6px 8px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-res__tab{display:inline-flex;align-items:center;gap:4px;padding:3px 8px;border:1px solid var(--fb-border-subtle, #e6e9ee);border-radius:12px;background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:11px;cursor:pointer}.fb-res__tab:hover{background:var(--fb-surface-alt, #f8f9fb)}.fb-res__tab--active{border-color:var(--fb-accent, #2f6feb);background:color-mix(in srgb,var(--fb-accent, #2f6feb) 10%,transparent);color:var(--fb-accent, #2f6feb);font-weight:600}.fb-res__count{padding:0 4px;border-radius:6px;background:var(--fb-border, #d6dae1);font-size:9px;color:var(--fb-text, #1d2939)}.fb-res__body{flex:1;min-height:0;overflow-y:auto;padding:10px 12px}.fb-res__name{flex:1;min-width:0;display:flex;flex-direction:column;padding:0;border:0;background:transparent;color:var(--fb-text, #1d2939);font:inherit;text-align:left;cursor:pointer}.fb-res__name-text{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-res__meta{font-size:10px;color:var(--fb-text-muted, #667085)}.fb-res__uses{flex:none;padding:1px 6px;border-radius:999px;background:var(--fb-surface-sunken, #eef0f4);color:var(--fb-text-muted, #6b7086);font-size:10px;font-variant-numeric:tabular-nums;white-space:nowrap}.fb-res__uses--none{background:color-mix(in srgb,var(--fb-warning, #b7791f) 12%,transparent);color:var(--fb-warning, #b7791f)}.fb-res__confirm{display:flex;flex-wrap:wrap;align-items:center;gap:6px}.fb-list__item--active{border-color:var(--fb-accent, #2f6feb);background:color-mix(in srgb,var(--fb-accent, #2f6feb) 6%,var(--fb-surface-alt, #f8f9fb))}.fb-list__item .fb-list__header{margin-bottom:0}.fb-list__item .fb-list__header+*{margin-top:6px}\n"] }]
15708
+ args: [{ selector: 'fb-resource-panel', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<header class=\"fb-res__header\">\r\n <h2 class=\"fb-res__title\">Risorse</h2>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" aria-label=\"Chiudi\" (click)=\"close()\">\u00D7</button>\r\n</header>\r\n\r\n<nav class=\"fb-res__tabs\" aria-label=\"Tipi di risorsa\">\r\n @for (kind of kinds; track kind.collection) {\r\n <!--\r\n Con un filtro acceso il numero della scheda e\u2019 quello delle **corrispondenze**: e\u2019 cio\u2019\r\n che dice dove sta la risorsa che si cerca, che il piu\u2019 delle volte e\u2019 in un\u2019altra\r\n collection. La scheda a zero si sbiadisce invece di sparire \u2014 nasconderla farebbe\r\n ballare la barra a ogni lettera digitata.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-res__tab\"\r\n [class.fb-res__tab--active]=\"activeCollection() === kind.collection\"\r\n [class.fb-res__tab--dim]=\"hasFilter() && countOf(kind.collection) === 0\"\r\n [title]=\"\r\n hasFilter()\r\n ? countOf(kind.collection) + ' su ' + totalOf(kind.collection) + ' con il filtro attivo'\r\n : kind.label\r\n \"\r\n (click)=\"select(kind.collection)\"\r\n >\r\n {{ kind.label }}\r\n <span class=\"fb-res__count\" [class.fb-res__count--match]=\"hasFilter()\">\r\n {{ countOf(kind.collection) }}\r\n </span>\r\n </button>\r\n }\r\n</nav>\r\n\r\n<!--\r\n Il filtro sta fra le schede e l\u2019elenco, e non scorre con esso: su una collection lunga\r\n scorrere per raggiungere la casella con cui accorciarla e\u2019 il gesto che non deve servire.\r\n-->\r\n<div class=\"fb-res__filter\">\r\n <input\r\n type=\"search\"\r\n class=\"fb-res__search\"\r\n placeholder=\"Filtra per nome o tipo\u2026\"\r\n aria-label=\"Filtra le risorse per nome o tipo\"\r\n title=\"Cerca nel nome, nell\u2019etichetta, nel tipo e nei flag (input, output, collection). Non guarda dentro i valori e le formule: a dire chi cita una risorsa e\u2019 il conteggio degli usi\"\r\n [value]=\"query()\"\r\n (input)=\"onQuery($any($event.target).value)\"\r\n />\r\n\r\n <div class=\"fb-res__chips\" role=\"group\" aria-label=\"Filtra per stato\">\r\n @let counts = stateCounts();\r\n <!--\r\n I due stati sono le domande che un nome non sa fare, e sono quelle che si fanno davvero\r\n su un flow grande: \u00ABquali variabili sono morte\u00BB e \u00ABquali righe hanno qualcosa che non\r\n va\u00BB. A zero il chip si disabilita: un filtro che darebbe certamente un elenco vuoto non\r\n restringe niente, nasconde tutto.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-res__chip\"\r\n [class.fb-res__chip--active]=\"stateFilter() === 'unused'\"\r\n [attr.aria-pressed]=\"stateFilter() === 'unused'\"\r\n [disabled]=\"counts.unused === 0 && stateFilter() !== 'unused'\"\r\n title=\"Solo le risorse che nessun riferimento e nessuna formula usa\"\r\n (click)=\"setStateFilter('unused')\"\r\n >\r\n Non usate <span class=\"fb-res__count\">{{ counts.unused }}</span>\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-res__chip fb-res__chip--issues\"\r\n [class.fb-res__chip--active]=\"stateFilter() === 'issues'\"\r\n [attr.aria-pressed]=\"stateFilter() === 'issues'\"\r\n [disabled]=\"counts.issues === 0 && stateFilter() !== 'issues'\"\r\n title=\"Solo le risorse con un rilievo locale: nome duplicato, costante che referenzia, ordine di stage doppio\"\r\n (click)=\"setStateFilter('issues')\"\r\n >\r\n Con rilievi <span class=\"fb-res__count\">{{ counts.issues }}</span>\r\n </button>\r\n @if (hasFilter()) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-res__chip fb-res__chip--reset\"\r\n title=\"Mostra tutte le risorse della scheda\"\r\n (click)=\"resetFilter()\"\r\n >\r\n Azzera\r\n </button>\r\n }\r\n </div>\r\n</div>\r\n\r\n<div class=\"fb-res__body\">\r\n <p class=\"fb-section__note\">{{ activeKind().note }}</p>\r\n\r\n <div class=\"fb-list\">\r\n @for (item of items(); track item.collection + ':' + item.index) {\r\n <div class=\"fb-list__item\" [class.fb-list__item--active]=\"isEditing(item)\">\r\n <div class=\"fb-list__header\">\r\n <!--\r\n Il nome apre la finestra di modifica: qui l\u2019elenco resta un elenco, e cio\u2019 che si\r\n configura non lo copre. \u00C8 la stessa scelta della dialog del dettaglio.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-res__name\"\r\n [title]=\"'Configura ' + (item.name || 'la risorsa')\"\r\n (click)=\"edit(item)\"\r\n >\r\n <span class=\"fb-res__name-text\">{{ item.name || '(senza nome)' }}</span>\r\n <!--\r\n L\u2019`objectType` sta accanto al tipo perche\u2019 su una `Structure` il tipo da solo non\r\n dice niente: sei variabili su ventuno dicevano \u00ABStructure\u00BB e la classe si leggeva\r\n solo aprendole. E\u2019 anche cio\u2019 che rende spiegabile il filtro, che l\u2019`objectType`\r\n lo guarda: senza, cercare \u00ABdto\u00BB faceva comparire tre righe che \u00ABdto\u00BB non lo\r\n mostrano da nessuna parte \u2014 un filtro che sembra sbagliato.\r\n -->\r\n <span class=\"fb-res__meta\">\r\n {{ string(item, 'dataType') }}{{ boolean(item, 'isCollection') ? '[]' : '' }}\r\n @if (string(item, 'objectType')) {\r\n \u00B7 {{ string(item, 'objectType') }}\r\n }\r\n @if (boolean(item, 'isInput')) {\r\n \u00B7 input\r\n }\r\n @if (boolean(item, 'isOutput')) {\r\n \u00B7 output\r\n }\r\n </span>\r\n </button>\r\n <span class=\"fb-list__spacer\"></span>\r\n <!--\r\n Il numero di usi sta accanto al nome perche\u2019 e\u2019 cio\u2019 che decide se si puo\u2019 togliere:\r\n \u00ABnon usata\u00BB e\u2019 un invito a fare pulizia, \u00ABusata 15 volte\u00BB e\u2019 un avvertimento.\r\n -->\r\n @let uses = usesOf(item);\r\n @if (uses === 0) {\r\n <span class=\"fb-res__uses fb-res__uses--none\" title=\"Nessun riferimento la usa: si puo\u2019 togliere\">\r\n non usata\r\n </span>\r\n } @else {\r\n <span\r\n class=\"fb-res__uses\"\r\n [title]=\"'Referenziata ' + uses + ' volte nel documento, formule comprese'\"\r\n >\r\n {{ uses }} usi\r\n </span>\r\n }\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n aria-label=\"Rimuovi la risorsa\"\r\n [title]=\"uses ? 'Rimuovi: e\u2019 usata ' + uses + ' volte, verra\u2019 chiesta conferma' : 'Rimuovi'\"\r\n (click)=\"requestRemove(item)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n\r\n @if (isPendingRemoval(item)) {\r\n <!--\r\n La conferma sta qui e non in una finestra: la riga che si sta togliendo resta sotto\r\n gli occhi, e il numero di riferimenti che si rompono e\u2019 il dato della decisione.\r\n -->\r\n <p class=\"fb-callout fb-callout--warn fb-res__confirm\">\r\n <span>\r\n \u00AB{{ item.name }}\u00BB e\u2019 usata {{ usesOf(item) }} volte: togliendola quei riferimenti\r\n restano orfani e la validazione li segnalera\u2019.\r\n </span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--icon\" (click)=\"remove(item)\">Rimuovi comunque</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" (click)=\"cancelRemove()\">\r\n Annulla\r\n </button>\r\n </p>\r\n }\r\n\r\n <!--\r\n I rilievi locali restano nell\u2019elenco anche ora che il form sta altrove: senza, per\r\n sapere quali risorse hanno qualcosa che non va bisognerebbe aprirle una per una.\r\n -->\r\n @if (nameError(item); as message) {\r\n <p class=\"fb-field__error\">{{ message }}</p>\r\n }\r\n @if (constantReferencesResource(item)) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n Una costante deve essere un valore fisso: non puo\u2019 referenziare altre risorse\r\n (CONSTANT_REFERENCES_RESOURCE).\r\n </p>\r\n }\r\n @if (duplicateStageOrder(item)) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Due stage con lo stesso ordine: quale sia il corrente all\u2019avvio diventa arbitrario\r\n (STAGE_ORDER_DUPLICATED).\r\n </p>\r\n }\r\n @if (ignoredChoiceSetFilters(item); as ignored) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Le opzioni vengono da una collection: {{ ignored }} (CHOICE_SET_FILTERS_IGNORED).\r\n </p>\r\n }\r\n </div>\r\n } @empty {\r\n <!--\r\n Tre casi e tre rimedi: la collection e\u2019 vuota (si aggiunge), il filtro non trova\r\n niente qui ma trova altrove (si cambia scheda), il filtro non trova niente da nessuna\r\n parte (si azzera). Un solo \u00ABnessuna risorsa\u00BB per tutti e tre manda a cercare il\r\n difetto dove non c\u2019e\u2019.\r\n -->\r\n @if (!hasFilter()) {\r\n <p class=\"fb-empty\">Nessuna risorsa di questo tipo.</p>\r\n } @else if (matchesElsewhere() > 0) {\r\n <p class=\"fb-empty\">\r\n Nessuna corrispondenza in questa scheda, ma {{ matchesElsewhere() }} in altre: il\r\n numero accanto a ognuna dice dove.\r\n </p>\r\n } @else {\r\n <p class=\"fb-empty\">\r\n Nessuna risorsa corrisponde al filtro.\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" (click)=\"resetFilter()\">\r\n Azzera il filtro\r\n </button>\r\n </p>\r\n }\r\n }\r\n </div>\r\n\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" (click)=\"add()\">\r\n Aggiungi {{ activeKind().singular }}\r\n </button>\r\n</div>\r\n", styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0;background:var(--fb-surface, #fff)}.fb-res__header{display:flex;align-items:center;justify-content:space-between;padding:10px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-res__title{margin:0;font-size:14px;font-weight:600;color:var(--fb-text, #1d2939)}.fb-res__tabs{display:flex;flex-wrap:wrap;gap:2px;padding:6px 8px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-res__tab{display:inline-flex;align-items:center;gap:4px;padding:3px 8px;border:1px solid var(--fb-border-subtle, #e6e9ee);border-radius:12px;background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:11px;cursor:pointer}.fb-res__tab:hover{background:var(--fb-surface-alt, #f8f9fb)}.fb-res__tab--active{border-color:var(--fb-accent, #2f6feb);background:color-mix(in srgb,var(--fb-accent, #2f6feb) 10%,transparent);color:var(--fb-accent, #2f6feb);font-weight:600}.fb-res__count{padding:0 4px;border-radius:6px;background:var(--fb-border, #d6dae1);font-size:9px;color:var(--fb-text, #1d2939)}.fb-res__body{flex:1;min-height:0;overflow-y:auto;padding:10px 12px}.fb-res__name{flex:1;min-width:0;display:flex;flex-direction:column;padding:0;border:0;background:transparent;color:var(--fb-text, #1d2939);font:inherit;text-align:left;cursor:pointer}.fb-res__name-text{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-res__meta{font-size:10px;color:var(--fb-text-muted, #667085);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-res__uses{flex:none;padding:1px 6px;border-radius:999px;background:var(--fb-surface-sunken, #eef0f4);color:var(--fb-text-muted, #6b7086);font-size:10px;font-variant-numeric:tabular-nums;white-space:nowrap}.fb-res__uses--none{background:color-mix(in srgb,var(--fb-warning, #b7791f) 12%,transparent);color:var(--fb-warning, #b7791f)}.fb-res__confirm{display:flex;flex-wrap:wrap;align-items:center;gap:6px}.fb-list__item--active{border-color:var(--fb-accent, #2f6feb);background:color-mix(in srgb,var(--fb-accent, #2f6feb) 6%,var(--fb-surface-alt, #f8f9fb))}.fb-list__item .fb-list__header{margin-bottom:0}.fb-list__item .fb-list__header+*{margin-top:6px}.fb-res__filter{display:flex;flex-direction:column;gap:6px;padding:8px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-res__search{box-sizing:border-box;width:100%;padding:5px 8px;border:1px solid var(--fb-border, #d6dae1);border-radius:6px;background:var(--fb-surface, #fff);color:var(--fb-text, #1d2939);font:inherit;font-size:12px}.fb-res__search:focus-visible{outline:2px solid var(--fb-accent, #2f6feb);outline-offset:-1px}.fb-res__chips{display:flex;flex-wrap:wrap;gap:4px}.fb-res__chip{display:inline-flex;align-items:center;gap:4px;padding:3px 8px;border:0;border-radius:6px;background:var(--fb-surface-sunken, #eef0f4);color:var(--fb-text-muted, #667085);font:inherit;font-size:11px;cursor:pointer}.fb-res__chip:hover:not(:disabled){background:var(--fb-border, #d6dae1)}.fb-res__chip:disabled{opacity:.45;cursor:default}.fb-res__chip--active{background:var(--fb-accent, #2f6feb);color:#fff;font-weight:600}.fb-res__chip--active .fb-res__count{background:color-mix(in srgb,#fff 30%,transparent);color:#fff}.fb-res__chip--reset{margin-left:auto}.fb-res__tab--dim{opacity:.4}.fb-res__count--match{background:color-mix(in srgb,var(--fb-accent, #2f6feb) 20%,transparent);color:var(--fb-accent, #2f6feb);font-weight:600}.fb-empty .fb-btn{margin-left:6px}\n"] }]
15186
15709
  }], ctorParameters: () => [], propDecorators: { closed: [{ type: i0.Output, args: ["closed"] }], target: [{ type: i0.Input, args: [{ isSignal: true, alias: "target", required: false }] }], editing: [{ type: i0.Input, args: [{ isSignal: true, alias: "editing", required: false }] }], editRequested: [{ type: i0.Output, args: ["editRequested"] }] } });
15187
15710
 
15188
15711
  /**
@@ -15340,7 +15863,12 @@ class ResourceFormComponent {
15340
15863
  delete resource['displayField'];
15341
15864
  delete resource['valueField'];
15342
15865
  delete resource['sortField'];
15866
+ // `sortOrder` e `filterLogic` stanno qui per la stessa ragione degli altri: cambiando
15867
+ // sorgente i campi citati non esistono piu', e un ordinamento o una logica rimasti
15868
+ // indietro diventano un rilievo che l'utente non ha scritto.
15869
+ delete resource['sortOrder'];
15343
15870
  delete resource['filters'];
15871
+ delete resource['filterLogic'];
15344
15872
  if (source === 'enum') {
15345
15873
  // Il tipo lo dichiara `enumType`: lasciare anche `objectType` vorrebbe dire due campi
15346
15874
  // che dicono la stessa cosa, e due occasioni di dirla in modo diverso.
@@ -15348,6 +15876,25 @@ class ResourceFormComponent {
15348
15876
  }
15349
15877
  });
15350
15878
  }
15879
+ /**
15880
+ * §5.2 — cio' che su una sorgente collection il runtime ignora, se il documento lo dichiara.
15881
+ * Il controllo sta in `resource-checks.ts` perche' serve anche all'**elenco**: senza, la riga
15882
+ * sarebbe verde e il rilievo si vedrebbe solo aprendo il form.
15883
+ */
15884
+ ignoredChoiceSetFilters() {
15885
+ const ignored = ignoredChoiceSetFiltersOf(this.reference());
15886
+ return ignored.length ? describeIgnoredChoiceSetFilters(ignored) : null;
15887
+ }
15888
+ /** La ripulitura e' un comando, non un effetto dell'apertura del form. */
15889
+ clearIgnoredChoiceSetFilters() {
15890
+ const reference = this.reference();
15891
+ this.store.updateResource(reference.collection, reference.index, (resource) => {
15892
+ delete resource['filters'];
15893
+ delete resource['filterLogic'];
15894
+ delete resource['sortField'];
15895
+ delete resource['sortOrder'];
15896
+ });
15897
+ }
15351
15898
  /** Su un choice set da enum il tipo e' `enumType`: il campo generico sarebbe un doppione. */
15352
15899
  hidesObjectType() {
15353
15900
  return this.collection() === 'dynamicChoiceSets' && this.choiceSetSource() === 'enum';
@@ -15435,7 +15982,7 @@ class ResourceFormComponent {
15435
15982
  return this.resource()['value'];
15436
15983
  }
15437
15984
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: ResourceFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
15438
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.28", type: ResourceFormComponent, isStandalone: true, selector: "fb-resource-form", inputs: { reference: { classPropertyName: "reference", publicName: "reference", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<!--\r\n Il form di **una** risorsa: e' cio' che prima stava inline sotto la riga dell'elenco, e ora\r\n vive nella dialog. I rilievi locali stanno in cima e non accanto al campo che li produce\r\n perche' qui il form si apre gi\u00E0 puntato sulla risorsa: la domanda \u00ABcosa non va\u00BB viene prima\r\n di \u00ABdove\u00BB.\r\n-->\r\n@if (nameError(); as message) {\r\n <p class=\"fb-field__error\">{{ message }}</p>\r\n}\r\n@if (constantReferencesResource()) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n Una costante deve essere un valore fisso: non puo\u2019 referenziare altre risorse\r\n (CONSTANT_REFERENCES_RESOURCE).\r\n </p>\r\n}\r\n@if (duplicateStageOrder()) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Due stage con lo stesso ordine: quale sia il corrente all\u2019avvio diventa arbitrario\r\n (STAGE_ORDER_DUPLICATED).\r\n </p>\r\n}\r\n\r\n<div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Nome</label>\r\n <input\r\n class=\"fb-input fb-input--mono\"\r\n [value]=\"reference().name\"\r\n (change)=\"rename($any($event.target).value)\"\r\n />\r\n <p class=\"fb-field__hint\">\r\n Il nome vive nello stesso spazio dei nomi degli elementi. Rinominare riscrive i riferimenti.\r\n </p>\r\n</div>\r\n\r\n@if (collection() === 'stages') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Etichetta</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"string('label')\"\r\n (input)=\"setField('label', $any($event.target).value)\"\r\n />\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Ordine</label>\r\n <input\r\n class=\"fb-input\"\r\n type=\"number\"\r\n min=\"1\"\r\n [value]=\"string('stageOrder')\"\r\n (input)=\"setNumberField('stageOrder', $any($event.target).value)\"\r\n />\r\n </div>\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"boolean('isActive')\"\r\n (change)=\"setBooleanField('isActive', $any($event.target).checked)\"\r\n />\r\n Attivo all\u2019avvio\r\n </label>\r\n <p class=\"fb-field__hint\">\r\n All\u2019avvio lo stage corrente e\u2019 il primo attivo per ordine. Si avanza con un Assignment su\r\n <code>$Flow.CurrentStage</code>.\r\n </p>\r\n}\r\n\r\n@if (collection() === 'textTemplates') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Testo</label>\r\n <textarea\r\n class=\"fb-textarea\"\r\n [value]=\"string('text')\"\r\n placeholder=\"Gentile {!Cliente.Nome},\"\r\n (input)=\"setField('text', $any($event.target).value)\"\r\n ></textarea>\r\n </div>\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"boolean('isViewedAsPlainText')\"\r\n (change)=\"setBooleanField('isViewedAsPlainText', $any($event.target).checked)\"\r\n />\r\n Testo semplice\r\n </label>\r\n}\r\n\r\n@if (collection() !== 'textTemplates' && collection() !== 'stages') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Tipo</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"string('dataType')\"\r\n (change)=\"setDataType($any($event.target).value)\"\r\n >\r\n @for (type of dataTypes(); track type.value) {\r\n <option [value]=\"type.value\">{{ type.label }}</option>\r\n }\r\n </select>\r\n @if (collection() === 'dynamicChoiceSets') {\r\n <!--\r\n \u00A75.2 \u2014 il valore di un'opzione **generata** non lo scrive l'autore del flow: lo produce\r\n la sorgente, nel tipo in cui il sistema ospite lo tiene, e `dataType` decide il tipo in\r\n cui viaggia. La regola e' generale \u2014 vale per tutte e tre le sorgenti \u2014 e si vede su un\r\n oggetto la cui chiave e' un enum, dove nella tendina serve di norma il numero.\r\n -->\r\n <p class=\"fb-field__hint\">\r\n Decide il tipo in cui viaggia il valore dell\u2019opzione, con qualunque sorgente:\r\n <code>String</code> il nome come testo, <code>Integer</code> o <code>Number</code> il\r\n numero sottostante, <code>Enum</code> il valore tipizzato \u2014 che e\u2019 cio\u2019 che serve in un\r\n parametro o in un membro di classe <code>Enum</code>. Una conversione impossibile non fa\r\n fallire niente: resta il valore prodotto dalla sorgente.\r\n </p>\r\n }\r\n </div>\r\n\r\n @if (requiresObjectType() && !hidesObjectType()) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">\r\n @if (isEnum()) {\r\n Tipo di enumerazione\r\n } @else if (isStructure()) {\r\n Classe\r\n } @else {\r\n Oggetto\r\n }\r\n </label>\r\n @if (isEnum()) {\r\n <!-- Dizionario chiuso: qui la scrittura libera non serve. -->\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"string('objectType')\"\r\n (change)=\"setField('objectType', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 scegli \u2014</option>\r\n @for (entry of enumOptions(); track entry.name) {\r\n <option [value]=\"entry.name\">{{ entry.label || entry.name }}</option>\r\n }\r\n </select>\r\n } @else if (isStructure()) {\r\n <!-- \u00A74.7: una classe del backend, non un'entita' del modello dati. -->\r\n <fb-structure-picker\r\n [value]=\"string('objectType') || undefined\"\r\n label=\"Classe\"\r\n (valueChange)=\"setField('objectType', $event ?? '')\"\r\n />\r\n } @else {\r\n <fb-object-picker\r\n [value]=\"string('objectType') || undefined\"\r\n label=\"Oggetto\"\r\n placeholder=\"Scrivi o scegli un oggetto\"\r\n (valueChange)=\"setField('objectType', $event ?? '')\"\r\n />\r\n }\r\n @if (missingObjectType()) {\r\n <!-- Su una Structure non e' un avviso: senza classe non c'e' nulla da istanziare. -->\r\n <p class=\"fb-field__error\">\r\n La classe e\u2019 obbligatoria: senza, l\u2019attivazione e\u2019 bloccata (OBJECT_TYPE_MISSING).\r\n </p>\r\n } @else if (isStructure()) {\r\n <p class=\"fb-field__hint\">\r\n Il flow ne legge e scrive i <strong>membri</strong> (<code>Nome.Membro</code>): non si\r\n interroga con Get Records ne\u2019 si salva con Create/Update.\r\n </p>\r\n } @else {\r\n <p class=\"fb-field__hint\">Obbligatorio per Object ed Enum (OBJECT_TYPE_MISSING).</p>\r\n }\r\n </div>\r\n }\r\n\r\n @if (supportsScale()) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Decimali</label>\r\n <input\r\n class=\"fb-input\"\r\n type=\"number\"\r\n min=\"0\"\r\n [value]=\"string('scale')\"\r\n (input)=\"setNumberField('scale', $any($event.target).value)\"\r\n />\r\n </div>\r\n }\r\n}\r\n\r\n@if (collection() === 'variables') {\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"boolean('isCollection')\"\r\n (change)=\"setBooleanField('isCollection', $any($event.target).checked)\"\r\n />\r\n \u00C8 una collection\r\n </label>\r\n <p class=\"fb-field__hint\">Solo una collection puo\u2019 essere iterata da un Loop.</p>\r\n\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"boolean('isInput')\"\r\n (change)=\"setBooleanField('isInput', $any($event.target).checked)\"\r\n />\r\n Valorizzabile all\u2019avvio (input)\r\n </label>\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"boolean('isOutput')\"\r\n (change)=\"setBooleanField('isOutput', $any($event.target).checked)\"\r\n />\r\n Leggibile alla fine (output)\r\n </label>\r\n <p class=\"fb-field__hint\">\r\n Input e output sono il contratto del flow verso chi lo invoca, subflow compresi.\r\n </p>\r\n}\r\n\r\n@if (collection() === 'formulas') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Espressione</label>\r\n <!--\r\n Il tipo atteso e' quello che la risorsa dichiara, e `scale` va con lui: sono cio'\r\n che permette al motore di dire che l'espressione produce altro (\u00A76.3).\r\n -->\r\n <fb-formula-editor\r\n [expression]=\"string('expression')\"\r\n usage=\"Resource\"\r\n [expectedDataType]=\"$any(resource()['dataType'])\"\r\n [scale]=\"$any(resource()['scale'])\"\r\n placeholder=\"Importo * 1.22\"\r\n ariaLabel=\"Espressione della formula\"\r\n (expressionChange)=\"setField('expression', $event)\"\r\n >\r\n <p class=\"fb-field__hint\">\r\n Passata verbatim al motore di regole: la sintassi delle funzioni e\u2019 del motore.\r\n </p>\r\n </fb-formula-editor>\r\n </div>\r\n}\r\n\r\n@if (collection() === 'choices') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Testo mostrato</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"string('choiceText')\"\r\n (input)=\"setField('choiceText', $any($event.target).value)\"\r\n />\r\n </div>\r\n}\r\n\r\n@if (collection() === 'dynamicChoiceSets') {\r\n <!--\r\n \u00A75.2 \u2014 le sorgenti sono tre e si escludono a vicenda: i pulsanti le rendono\r\n mutuamente esclusive nel documento, non solo nel form.\r\n -->\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Da dove arrivano le opzioni</label>\r\n <div class=\"fb-filter__modes\" role=\"group\" aria-label=\"Sorgente delle opzioni\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"isChoiceSetSource('collection')\"\r\n (click)=\"setChoiceSetSource('collection')\"\r\n >\r\n Collection del flow\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"isChoiceSetSource('object')\"\r\n (click)=\"setChoiceSetSource('object')\"\r\n >\r\n Query su un oggetto\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"isChoiceSetSource('enum')\"\r\n (click)=\"setChoiceSetSource('enum')\"\r\n >\r\n Valori di un enum\r\n </button>\r\n </div>\r\n </div>\r\n\r\n @if (ambiguousChoiceSetSource()) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Questo choice set dichiara piu\u2019 di una sorgente: il runtime ne usa una sola\r\n (CHOICE_SET_SOURCE_AMBIGUOUS). Scegli quella giusta qui sopra: le altre vengono tolte.\r\n </p>\r\n }\r\n\r\n @if (isChoiceSetSource('collection')) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Collection</label>\r\n <fb-reference-picker\r\n [value]=\"string('collectionReference')\"\r\n [isCollection]=\"true\"\r\n placeholder=\"Scegli una collection\"\r\n (valueChange)=\"setField('collectionReference', $event ?? '')\"\r\n />\r\n <p class=\"fb-field__hint\">\r\n Tipicamente il risultato di un Get Records: le opzioni sono i record che il flow ha gi\u00E0\r\n letto.\r\n </p>\r\n </div>\r\n }\r\n\r\n @if (isChoiceSetSource('object')) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Oggetto</label>\r\n <fb-object-picker\r\n [value]=\"string('object') || undefined\"\r\n placeholder=\"Scrivi o scegli un oggetto\"\r\n (valueChange)=\"setField('object', $event ?? '')\"\r\n />\r\n </div>\r\n }\r\n\r\n @if (isChoiceSetSource('enum')) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Tipo di enumerazione</label>\r\n <!-- Dizionario chiuso, e qui e' anche **autorevole**: un tipo fuori elenco e' un errore. -->\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"string('enumType')\"\r\n (change)=\"setField('enumType', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 scegli \u2014</option>\r\n @for (entry of enumOptions(); track entry.name) {\r\n <option [value]=\"entry.name\">{{ entry.label || entry.name }}</option>\r\n }\r\n </select>\r\n @if (unknownEnumType()) {\r\n <p class=\"fb-field__error\">\r\n Il tipo \u00AB{{ string('enumType') }}\u00BB non e\u2019 fra quelli utilizzabili\r\n (ENUM_TYPE_UNKNOWN).\r\n </p>\r\n } @else {\r\n <p class=\"fb-field__hint\">\r\n Le opzioni sono i valori dichiarati dal tipo: nessuna choice da tenere allineata a mano.\r\n </p>\r\n }\r\n </div>\r\n }\r\n\r\n @if (isChoiceSetSource('enum')) {\r\n <!--\r\n \u00A75.2 \u2014 qui i campi citabili sono le tre proprieta' di un valore di enum, non i campi\r\n di un'entita': l'elenco viene dal dizionario, e i default rendono superfluo dichiararli.\r\n -->\r\n <div class=\"fb-field__row\">\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Campo mostrato</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"string('displayField')\"\r\n (change)=\"setField('displayField', $any($event.target).value)\"\r\n >\r\n <option value=\"\">Predefinito ({{ defaultDisplayField }})</option>\r\n @for (field of enumChoiceSetFields(); track field.value) {\r\n <option [value]=\"field.value\">{{ field.label }}</option>\r\n }\r\n </select>\r\n @if (unknownEnumChoiceSetField('displayField')) {\r\n <p class=\"fb-field__error\">\r\n \u00AB{{ string('displayField') }}\u00BB non e\u2019 una proprieta\u2019 di un valore di enum\r\n (FIELD_UNKNOWN).\r\n </p>\r\n }\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Campo del valore</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"string('valueField')\"\r\n (change)=\"setField('valueField', $any($event.target).value)\"\r\n >\r\n <option value=\"\">Predefinito ({{ defaultValueField }})</option>\r\n @for (field of enumChoiceSetFields(); track field.value) {\r\n <option [value]=\"field.value\">{{ field.label }}</option>\r\n }\r\n </select>\r\n @if (unknownEnumChoiceSetField('valueField')) {\r\n <p class=\"fb-field__error\">\r\n \u00AB{{ string('valueField') }}\u00BB non e\u2019 una proprieta\u2019 di un valore di enum\r\n (FIELD_UNKNOWN).\r\n </p>\r\n }\r\n </div>\r\n </div>\r\n <p class=\"fb-field__hint\">\r\n Dichiarare <code>Integer</code> come tipo della risorsa e\u2019 la stessa cosa che scegliere qui\r\n \u00ABValore numerico\u00BB: il valore dell\u2019opzione e\u2019 il numero sottostante.\r\n </p>\r\n } @else {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Campo mostrato</label>\r\n <fb-field-picker\r\n [value]=\"string('displayField') || undefined\"\r\n [object]=\"string('object') || undefined\"\r\n usage=\"any\"\r\n label=\"Campo mostrato\"\r\n (valueChange)=\"setField('displayField', $event ?? '')\"\r\n />\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Campo del valore</label>\r\n <fb-field-picker\r\n [value]=\"string('valueField') || undefined\"\r\n [object]=\"string('object') || undefined\"\r\n usage=\"any\"\r\n label=\"Campo del valore\"\r\n (valueChange)=\"setField('valueField', $event ?? '')\"\r\n />\r\n </div>\r\n }\r\n <div class=\"fb-field__row\">\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Ordina per</label>\r\n @if (isChoiceSetSource('enum')) {\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"string('sortField')\"\r\n (change)=\"setField('sortField', $any($event.target).value)\"\r\n >\r\n <option value=\"\">Ordine di dichiarazione</option>\r\n @for (field of enumChoiceSetFields(); track field.value) {\r\n <option [value]=\"field.value\">{{ field.label }}</option>\r\n }\r\n </select>\r\n @if (unknownEnumChoiceSetField('sortField')) {\r\n <p class=\"fb-field__error\">\r\n \u00AB{{ string('sortField') }}\u00BB non e\u2019 una proprieta\u2019 di un valore di enum\r\n (FIELD_UNKNOWN).\r\n </p>\r\n }\r\n } @else {\r\n <fb-field-picker\r\n [value]=\"string('sortField') || undefined\"\r\n [object]=\"string('object') || undefined\"\r\n usage=\"sortable\"\r\n label=\"Campo di ordinamento\"\r\n placeholder=\"Nessun ordinamento\"\r\n (valueChange)=\"setField('sortField', $event ?? '')\"\r\n />\r\n }\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Direzione</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"string('sortOrder')\"\r\n (change)=\"setField('sortOrder', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014</option>\r\n @for (order of sortOrders(); track order.value) {\r\n <option [value]=\"order.value\">{{ order.label }}</option>\r\n }\r\n </select>\r\n </div>\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Numero massimo</label>\r\n <input\r\n class=\"fb-input\"\r\n type=\"number\"\r\n min=\"1\"\r\n [value]=\"string('limit')\"\r\n (input)=\"setNumberField('limit', $any($event.target).value)\"\r\n />\r\n </div>\r\n <!--\r\n `supportsLogic` a false: qui `filterLogic` non esiste nel modello, e inviarlo lo\r\n perderebbe in silenzio al primo salvataggio (\u00A74.4). Nessun `emptyWarning`: senza\r\n filtri il set legge tutti i record dell\u2019oggetto, che qui e\u2019 un uso legittimo.\r\n Su un enum i filtri li valuta il runtime **in memoria**, e i campi sono le tre\r\n proprieta\u2019 di un valore: `fieldOptions` e\u2019 cio\u2019 che sostituisce il catalogo (\u00A75.2).\r\n -->\r\n <fb-record-filter-editor\r\n [holder]=\"$any(resource())\"\r\n [object]=\"isChoiceSetSource('enum') ? undefined : string('object') || undefined\"\r\n [fieldOptions]=\"isChoiceSetSource('enum') ? enumChoiceSetFields() : []\"\r\n [title]=\"\r\n isChoiceSetSource('enum') ? 'Quali valori diventano opzioni' : 'Quali record diventano opzioni'\r\n \"\r\n usage=\"filterable\"\r\n [supportsLogic]=\"false\"\r\n (changed)=\"onFiltersChanged($event)\"\r\n />\r\n}\r\n\r\n@if (isStructure() && collection() === 'variables') {\r\n <p class=\"fb-field__hint\">\r\n Non serve un valore iniziale: la variabile parte con un\u2019istanza vuota, e il flow ne assegna i\r\n membri uno alla volta con un Assignment.\r\n </p>\r\n}\r\n\r\n@if (\r\n supportsInitialValue() &&\r\n (collection() === 'variables' || collection() === 'constants' || collection() === 'choices')\r\n) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">\r\n {{ collection() === 'variables' ? 'Valore iniziale' : 'Valore' }}\r\n </label>\r\n <fb-value-editor\r\n [value]=\"value()\"\r\n [dataType]=\"$any(resource()['dataType'])\"\r\n [objectType]=\"$any(resource()['objectType'])\"\r\n [isCollection]=\"boolean('isCollection')\"\r\n [allowFormula]=\"collection() !== 'constants'\"\r\n label=\"Valore\"\r\n (valueChange)=\"setValue($event)\"\r\n />\r\n </div>\r\n}\r\n\r\n<div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Descrizione</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"string('description')\"\r\n (input)=\"setField('description', $any($event.target).value)\"\r\n />\r\n</div>\r\n", dependencies: [{ kind: "component", type: FieldPickerComponent, selector: "fb-field-picker", inputs: ["value", "object", "usage", "label", "placeholder", "disabled"], outputs: ["valueChange"] }, { kind: "component", type: FormulaEditorComponent, selector: "fb-formula-editor", inputs: ["expression", "usage", "expectedDataType", "scale", "placeholder", "ariaLabel", "disabled", "rows", "commitOn"], outputs: ["expressionChange"] }, { kind: "component", type: ObjectPickerComponent, selector: "fb-object-picker", inputs: ["value", "label", "placeholder", "disabled"], outputs: ["valueChange"] }, { kind: "component", type: ReferencePickerComponent, selector: "fb-reference-picker", inputs: ["value", "label", "placeholder", "disabled", "dataType", "isCollection", "objectType", "writableOnly", "elementsOnly", "extraReferences"], outputs: ["valueChange"] }, { kind: "component", type: RecordFilterEditorComponent, selector: "fb-record-filter-editor", inputs: ["holder", "object", "title", "usage", "fieldOptions", "supportsLogic", "supportsFormula", "emptyWarning", "emptyWarningSeverity"], outputs: ["changed"] }, { kind: "component", type: StructurePickerComponent, selector: "fb-structure-picker", inputs: ["value", "label", "placeholder", "disabled"], outputs: ["valueChange"] }, { kind: "component", type: ValueEditorComponent, selector: "fb-value-editor", inputs: ["value", "label", "dataType", "objectType", "isCollection", "valueSet", "disabled", "allowFormula"], outputs: ["valueChange"] }, { kind: "directive", type: SelectValueDirective, selector: "select[fbValue]", inputs: ["fbValue"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
15985
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.28", type: ResourceFormComponent, isStandalone: true, selector: "fb-resource-form", inputs: { reference: { classPropertyName: "reference", publicName: "reference", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<!--\r\n Il form di **una** risorsa: e' cio' che prima stava inline sotto la riga dell'elenco, e ora\r\n vive nella dialog. I rilievi locali stanno in cima e non accanto al campo che li produce\r\n perche' qui il form si apre gi\u00E0 puntato sulla risorsa: la domanda \u00ABcosa non va\u00BB viene prima\r\n di \u00ABdove\u00BB.\r\n-->\r\n@if (nameError(); as message) {\r\n <p class=\"fb-field__error\">{{ message }}</p>\r\n}\r\n@if (constantReferencesResource()) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n Una costante deve essere un valore fisso: non puo\u2019 referenziare altre risorse\r\n (CONSTANT_REFERENCES_RESOURCE).\r\n </p>\r\n}\r\n@if (duplicateStageOrder()) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Due stage con lo stesso ordine: quale sia il corrente all\u2019avvio diventa arbitrario\r\n (STAGE_ORDER_DUPLICATED).\r\n </p>\r\n}\r\n\r\n<div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Nome</label>\r\n <input\r\n class=\"fb-input fb-input--mono\"\r\n [value]=\"reference().name\"\r\n (change)=\"rename($any($event.target).value)\"\r\n />\r\n <p class=\"fb-field__hint\">\r\n Il nome vive nello stesso spazio dei nomi degli elementi. Rinominare riscrive i riferimenti.\r\n </p>\r\n</div>\r\n\r\n@if (collection() === 'stages') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Etichetta</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"string('label')\"\r\n (input)=\"setField('label', $any($event.target).value)\"\r\n />\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Ordine</label>\r\n <input\r\n class=\"fb-input\"\r\n type=\"number\"\r\n min=\"1\"\r\n [value]=\"string('stageOrder')\"\r\n (input)=\"setNumberField('stageOrder', $any($event.target).value)\"\r\n />\r\n </div>\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"boolean('isActive')\"\r\n (change)=\"setBooleanField('isActive', $any($event.target).checked)\"\r\n />\r\n Attivo all\u2019avvio\r\n </label>\r\n <p class=\"fb-field__hint\">\r\n All\u2019avvio lo stage corrente e\u2019 il primo attivo per ordine. Si avanza con un Assignment su\r\n <code>$Flow.CurrentStage</code>.\r\n </p>\r\n}\r\n\r\n@if (collection() === 'textTemplates') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Testo</label>\r\n <textarea\r\n class=\"fb-textarea\"\r\n [value]=\"string('text')\"\r\n placeholder=\"Gentile {!Cliente.Nome},\"\r\n (input)=\"setField('text', $any($event.target).value)\"\r\n ></textarea>\r\n </div>\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"boolean('isViewedAsPlainText')\"\r\n (change)=\"setBooleanField('isViewedAsPlainText', $any($event.target).checked)\"\r\n />\r\n Testo semplice\r\n </label>\r\n}\r\n\r\n@if (collection() !== 'textTemplates' && collection() !== 'stages') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Tipo</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"string('dataType')\"\r\n (change)=\"setDataType($any($event.target).value)\"\r\n >\r\n @for (type of dataTypes(); track type.value) {\r\n <option [value]=\"type.value\">{{ type.label }}</option>\r\n }\r\n </select>\r\n @if (collection() === 'dynamicChoiceSets') {\r\n <!--\r\n \u00A75.2 \u2014 il valore di un'opzione **generata** non lo scrive l'autore del flow: lo produce\r\n la sorgente, nel tipo in cui il sistema ospite lo tiene, e `dataType` decide il tipo in\r\n cui viaggia. La regola e' generale \u2014 vale per tutte e tre le sorgenti \u2014 e si vede su un\r\n oggetto la cui chiave e' un enum, dove nella tendina serve di norma il numero.\r\n -->\r\n <p class=\"fb-field__hint\">\r\n Decide il tipo in cui viaggia il valore dell\u2019opzione, con qualunque sorgente:\r\n <code>String</code> il nome come testo, <code>Integer</code> o <code>Number</code> il\r\n numero sottostante, <code>Enum</code> il valore tipizzato \u2014 che e\u2019 cio\u2019 che serve in un\r\n parametro o in un membro di classe <code>Enum</code>. Una conversione impossibile non fa\r\n fallire niente: resta il valore prodotto dalla sorgente.\r\n </p>\r\n }\r\n </div>\r\n\r\n @if (requiresObjectType() && !hidesObjectType()) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">\r\n @if (isEnum()) {\r\n Tipo di enumerazione\r\n } @else if (isStructure()) {\r\n Classe\r\n } @else {\r\n Oggetto\r\n }\r\n </label>\r\n @if (isEnum()) {\r\n <!-- Dizionario chiuso: qui la scrittura libera non serve. -->\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"string('objectType')\"\r\n (change)=\"setField('objectType', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 scegli \u2014</option>\r\n @for (entry of enumOptions(); track entry.name) {\r\n <option [value]=\"entry.name\">{{ entry.label || entry.name }}</option>\r\n }\r\n </select>\r\n } @else if (isStructure()) {\r\n <!-- \u00A74.7: una classe del backend, non un'entita' del modello dati. -->\r\n <fb-structure-picker\r\n [value]=\"string('objectType') || undefined\"\r\n label=\"Classe\"\r\n (valueChange)=\"setField('objectType', $event ?? '')\"\r\n />\r\n } @else {\r\n <fb-object-picker\r\n [value]=\"string('objectType') || undefined\"\r\n label=\"Oggetto\"\r\n placeholder=\"Scrivi o scegli un oggetto\"\r\n (valueChange)=\"setField('objectType', $event ?? '')\"\r\n />\r\n }\r\n @if (missingObjectType()) {\r\n <!-- Su una Structure non e' un avviso: senza classe non c'e' nulla da istanziare. -->\r\n <p class=\"fb-field__error\">\r\n La classe e\u2019 obbligatoria: senza, l\u2019attivazione e\u2019 bloccata (OBJECT_TYPE_MISSING).\r\n </p>\r\n } @else if (isStructure()) {\r\n <p class=\"fb-field__hint\">\r\n Il flow ne legge e scrive i <strong>membri</strong> (<code>Nome.Membro</code>): non si\r\n interroga con Get Records ne\u2019 si salva con Create/Update.\r\n </p>\r\n } @else {\r\n <p class=\"fb-field__hint\">Obbligatorio per Object ed Enum (OBJECT_TYPE_MISSING).</p>\r\n }\r\n </div>\r\n }\r\n\r\n @if (supportsScale()) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Decimali</label>\r\n <input\r\n class=\"fb-input\"\r\n type=\"number\"\r\n min=\"0\"\r\n [value]=\"string('scale')\"\r\n (input)=\"setNumberField('scale', $any($event.target).value)\"\r\n />\r\n </div>\r\n }\r\n}\r\n\r\n@if (collection() === 'variables') {\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"boolean('isCollection')\"\r\n (change)=\"setBooleanField('isCollection', $any($event.target).checked)\"\r\n />\r\n \u00C8 una collection\r\n </label>\r\n <p class=\"fb-field__hint\">Solo una collection puo\u2019 essere iterata da un Loop.</p>\r\n\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"boolean('isInput')\"\r\n (change)=\"setBooleanField('isInput', $any($event.target).checked)\"\r\n />\r\n Valorizzabile all\u2019avvio (input)\r\n </label>\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"boolean('isOutput')\"\r\n (change)=\"setBooleanField('isOutput', $any($event.target).checked)\"\r\n />\r\n Leggibile alla fine (output)\r\n </label>\r\n <p class=\"fb-field__hint\">\r\n Input e output sono il contratto del flow verso chi lo invoca, subflow compresi.\r\n </p>\r\n}\r\n\r\n@if (collection() === 'formulas') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Espressione</label>\r\n <!--\r\n Il tipo atteso e' quello che la risorsa dichiara, e `scale` va con lui: sono cio'\r\n che permette al motore di dire che l'espressione produce altro (\u00A76.3).\r\n -->\r\n <fb-formula-editor\r\n [expression]=\"string('expression')\"\r\n usage=\"Resource\"\r\n [expectedDataType]=\"$any(resource()['dataType'])\"\r\n [scale]=\"$any(resource()['scale'])\"\r\n placeholder=\"Importo * 1.22\"\r\n ariaLabel=\"Espressione della formula\"\r\n (expressionChange)=\"setField('expression', $event)\"\r\n >\r\n <p class=\"fb-field__hint\">\r\n Passata verbatim al motore di regole: la sintassi delle funzioni e\u2019 del motore.\r\n </p>\r\n </fb-formula-editor>\r\n </div>\r\n}\r\n\r\n@if (collection() === 'choices') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Testo mostrato</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"string('choiceText')\"\r\n (input)=\"setField('choiceText', $any($event.target).value)\"\r\n />\r\n </div>\r\n}\r\n\r\n@if (collection() === 'dynamicChoiceSets') {\r\n <!--\r\n \u00A75.2 \u2014 le sorgenti sono tre e si escludono a vicenda: i pulsanti le rendono\r\n mutuamente esclusive nel documento, non solo nel form.\r\n -->\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Da dove arrivano le opzioni</label>\r\n <div class=\"fb-filter__modes\" role=\"group\" aria-label=\"Sorgente delle opzioni\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"isChoiceSetSource('collection')\"\r\n (click)=\"setChoiceSetSource('collection')\"\r\n >\r\n Collection del flow\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"isChoiceSetSource('object')\"\r\n (click)=\"setChoiceSetSource('object')\"\r\n >\r\n Query su un oggetto\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"isChoiceSetSource('enum')\"\r\n (click)=\"setChoiceSetSource('enum')\"\r\n >\r\n Valori di un enum\r\n </button>\r\n </div>\r\n </div>\r\n\r\n @if (ambiguousChoiceSetSource()) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Questo choice set dichiara piu\u2019 di una sorgente: il runtime ne usa una sola\r\n (CHOICE_SET_SOURCE_AMBIGUOUS). Scegli quella giusta qui sopra: le altre vengono tolte.\r\n </p>\r\n }\r\n\r\n @if (isChoiceSetSource('collection')) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Collection</label>\r\n <fb-reference-picker\r\n [value]=\"string('collectionReference')\"\r\n [isCollection]=\"true\"\r\n placeholder=\"Scegli una collection\"\r\n (valueChange)=\"setField('collectionReference', $event ?? '')\"\r\n />\r\n <p class=\"fb-field__hint\">\r\n Tipicamente il risultato di un Get Records: le opzioni sono i record che il flow ha gi\u00E0\r\n letto, <strong>nell\u2019ordine in cui ci sono</strong>. Filtri e ordinamento non si applicano\r\n qui: per sceglierne una parte, filtra la collection con un elemento Filter prima dello\r\n screen.\r\n </p>\r\n </div>\r\n }\r\n\r\n @if (isChoiceSetSource('object')) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Oggetto</label>\r\n <fb-object-picker\r\n [value]=\"string('object') || undefined\"\r\n placeholder=\"Scrivi o scegli un oggetto\"\r\n (valueChange)=\"setField('object', $event ?? '')\"\r\n />\r\n </div>\r\n }\r\n\r\n @if (isChoiceSetSource('enum')) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Tipo di enumerazione</label>\r\n <!-- Dizionario chiuso, e qui e' anche **autorevole**: un tipo fuori elenco e' un errore. -->\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"string('enumType')\"\r\n (change)=\"setField('enumType', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 scegli \u2014</option>\r\n @for (entry of enumOptions(); track entry.name) {\r\n <option [value]=\"entry.name\">{{ entry.label || entry.name }}</option>\r\n }\r\n </select>\r\n @if (unknownEnumType()) {\r\n <p class=\"fb-field__error\">\r\n Il tipo \u00AB{{ string('enumType') }}\u00BB non e\u2019 fra quelli utilizzabili\r\n (ENUM_TYPE_UNKNOWN).\r\n </p>\r\n } @else {\r\n <p class=\"fb-field__hint\">\r\n Le opzioni sono i valori dichiarati dal tipo: nessuna choice da tenere allineata a mano.\r\n </p>\r\n }\r\n </div>\r\n }\r\n\r\n @if (isChoiceSetSource('enum')) {\r\n <!--\r\n \u00A75.2 \u2014 qui i campi citabili sono le tre proprieta' di un valore di enum, non i campi\r\n di un'entita': l'elenco viene dal dizionario, e i default rendono superfluo dichiararli.\r\n -->\r\n <div class=\"fb-field__row\">\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Campo mostrato</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"string('displayField')\"\r\n (change)=\"setField('displayField', $any($event.target).value)\"\r\n >\r\n <option value=\"\">Predefinito ({{ defaultDisplayField }})</option>\r\n @for (field of enumChoiceSetFields(); track field.value) {\r\n <option [value]=\"field.value\">{{ field.label }}</option>\r\n }\r\n </select>\r\n @if (unknownEnumChoiceSetField('displayField')) {\r\n <p class=\"fb-field__error\">\r\n \u00AB{{ string('displayField') }}\u00BB non e\u2019 una proprieta\u2019 di un valore di enum\r\n (FIELD_UNKNOWN).\r\n </p>\r\n }\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Campo del valore</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"string('valueField')\"\r\n (change)=\"setField('valueField', $any($event.target).value)\"\r\n >\r\n <option value=\"\">Predefinito ({{ defaultValueField }})</option>\r\n @for (field of enumChoiceSetFields(); track field.value) {\r\n <option [value]=\"field.value\">{{ field.label }}</option>\r\n }\r\n </select>\r\n @if (unknownEnumChoiceSetField('valueField')) {\r\n <p class=\"fb-field__error\">\r\n \u00AB{{ string('valueField') }}\u00BB non e\u2019 una proprieta\u2019 di un valore di enum\r\n (FIELD_UNKNOWN).\r\n </p>\r\n }\r\n </div>\r\n </div>\r\n <p class=\"fb-field__hint\">\r\n Dichiarare <code>Integer</code> come tipo della risorsa e\u2019 la stessa cosa che scegliere qui\r\n \u00ABValore numerico\u00BB: il valore dell\u2019opzione e\u2019 il numero sottostante.\r\n </p>\r\n } @else {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Campo mostrato</label>\r\n <fb-field-picker\r\n [value]=\"string('displayField') || undefined\"\r\n [object]=\"string('object') || undefined\"\r\n usage=\"any\"\r\n label=\"Campo mostrato\"\r\n (valueChange)=\"setField('displayField', $event ?? '')\"\r\n />\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Campo del valore</label>\r\n <fb-field-picker\r\n [value]=\"string('valueField') || undefined\"\r\n [object]=\"string('object') || undefined\"\r\n usage=\"any\"\r\n label=\"Campo del valore\"\r\n (valueChange)=\"setField('valueField', $event ?? '')\"\r\n />\r\n </div>\r\n }\r\n <!--\r\n \u00A75.2 \u2014 `filters`, `filterLogic`, `sortField` e `sortOrder` valgono sulle **sole** sorgenti\r\n `object` (dove diventano la query) ed `enumType` (valutati in memoria). Su una collection le\r\n opzioni sono i suoi elementi nell\u2019ordine in cui ci sono: mostrare i controlli l\u00EC\r\n inviterebbe a dichiarare cio\u2019 che il runtime ignora, ed e\u2019 `CHOICE_SET_FILTERS_IGNORED`.\r\n -->\r\n @if (!isChoiceSetSource('collection')) {\r\n <div class=\"fb-field__row\">\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Ordina per</label>\r\n @if (isChoiceSetSource('enum')) {\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"string('sortField')\"\r\n (change)=\"setField('sortField', $any($event.target).value)\"\r\n >\r\n <option value=\"\">Ordine di dichiarazione</option>\r\n @for (field of enumChoiceSetFields(); track field.value) {\r\n <option [value]=\"field.value\">{{ field.label }}</option>\r\n }\r\n </select>\r\n @if (unknownEnumChoiceSetField('sortField')) {\r\n <p class=\"fb-field__error\">\r\n \u00AB{{ string('sortField') }}\u00BB non e\u2019 una proprieta\u2019 di un valore di enum\r\n (FIELD_UNKNOWN).\r\n </p>\r\n }\r\n } @else {\r\n <fb-field-picker\r\n [value]=\"string('sortField') || undefined\"\r\n [object]=\"string('object') || undefined\"\r\n usage=\"sortable\"\r\n label=\"Campo di ordinamento\"\r\n placeholder=\"Nessun ordinamento\"\r\n (valueChange)=\"setField('sortField', $event ?? '')\"\r\n />\r\n }\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Direzione</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"string('sortOrder')\"\r\n (change)=\"setField('sortOrder', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014</option>\r\n @for (order of sortOrders(); track order.value) {\r\n <option [value]=\"order.value\">{{ order.label }}</option>\r\n }\r\n </select>\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- `limit` e' l'unico dei quattro che vale su tutt'e tre le sorgenti (\u00A75.2). -->\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Numero massimo</label>\r\n <input\r\n class=\"fb-input\"\r\n type=\"number\"\r\n min=\"1\"\r\n [value]=\"string('limit')\"\r\n (input)=\"setNumberField('limit', $any($event.target).value)\"\r\n />\r\n </div>\r\n\r\n @if (!isChoiceSetSource('collection')) {\r\n <!--\r\n `supportsLogic` a true: dalla revisione del contratto `filterLogic` **esiste** anche qui, con\r\n le stesse forme e gli stessi rilievi degli elementi che interrogano il database (\u00A74.4).\r\n Nessun `emptyWarning`: senza filtri il set legge tutti i record dell\u2019oggetto, che qui e\u2019 un\r\n uso legittimo. Su un enum i filtri li valuta il runtime **in memoria**, e i campi sono le tre\r\n proprieta\u2019 di un valore: `fieldOptions` e\u2019 cio\u2019 che sostituisce il catalogo (\u00A75.2).\r\n -->\r\n <fb-record-filter-editor\r\n [holder]=\"$any(resource())\"\r\n [object]=\"isChoiceSetSource('enum') ? undefined : string('object') || undefined\"\r\n [fieldOptions]=\"isChoiceSetSource('enum') ? enumChoiceSetFields() : []\"\r\n [title]=\"\r\n isChoiceSetSource('enum') ? 'Quali valori diventano opzioni' : 'Quali record diventano opzioni'\r\n \"\r\n usage=\"filterable\"\r\n [supportsLogic]=\"true\"\r\n (changed)=\"onFiltersChanged($event)\"\r\n />\r\n } @else if (ignoredChoiceSetFilters(); as ignored) {\r\n <!--\r\n Il documento dichiara cio' che su una collection il runtime ignora. E\u2019 un **avviso**, e la\r\n ripulitura e\u2019 un comando: cancellare da se\u2019 i filtri di un flow scritto altrove sarebbe una\r\n modifica che nessuno ha chiesto, e farebbe sparire l\u2019unico indizio di cosa quel flow\r\n voleva selezionare. E\u2019 la stessa scelta dei membri di un riquadro (\u00A73.6).\r\n -->\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Su una collection {{ ignored }}: le opzioni sono gli elementi della collection nell\u2019ordine in\r\n cui ci sono (CHOICE_SET_FILTERS_IGNORED).\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" (click)=\"clearIgnoredChoiceSetFilters()\">\r\n Togli\r\n </button>\r\n </p>\r\n }\r\n}\r\n\r\n@if (isStructure() && collection() === 'variables') {\r\n <p class=\"fb-field__hint\">\r\n Non serve un valore iniziale: la variabile parte con un\u2019istanza vuota, e il flow ne assegna i\r\n membri uno alla volta con un Assignment.\r\n </p>\r\n}\r\n\r\n@if (\r\n supportsInitialValue() &&\r\n (collection() === 'variables' || collection() === 'constants' || collection() === 'choices')\r\n) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">\r\n {{ collection() === 'variables' ? 'Valore iniziale' : 'Valore' }}\r\n </label>\r\n <fb-value-editor\r\n [value]=\"value()\"\r\n [dataType]=\"$any(resource()['dataType'])\"\r\n [objectType]=\"$any(resource()['objectType'])\"\r\n [isCollection]=\"boolean('isCollection')\"\r\n [allowFormula]=\"collection() !== 'constants'\"\r\n label=\"Valore\"\r\n (valueChange)=\"setValue($event)\"\r\n />\r\n </div>\r\n}\r\n\r\n<div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Descrizione</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"string('description')\"\r\n (input)=\"setField('description', $any($event.target).value)\"\r\n />\r\n</div>\r\n", dependencies: [{ kind: "component", type: FieldPickerComponent, selector: "fb-field-picker", inputs: ["value", "object", "usage", "label", "placeholder", "disabled"], outputs: ["valueChange"] }, { kind: "component", type: FormulaEditorComponent, selector: "fb-formula-editor", inputs: ["expression", "usage", "expectedDataType", "scale", "placeholder", "ariaLabel", "disabled", "rows", "commitOn"], outputs: ["expressionChange"] }, { kind: "component", type: ObjectPickerComponent, selector: "fb-object-picker", inputs: ["value", "label", "placeholder", "disabled"], outputs: ["valueChange"] }, { kind: "component", type: ReferencePickerComponent, selector: "fb-reference-picker", inputs: ["value", "label", "placeholder", "disabled", "dataType", "isCollection", "objectType", "writableOnly", "elementsOnly", "extraReferences"], outputs: ["valueChange"] }, { kind: "component", type: RecordFilterEditorComponent, selector: "fb-record-filter-editor", inputs: ["holder", "object", "title", "usage", "fieldOptions", "supportsLogic", "supportsFormula", "emptyWarning", "emptyWarningSeverity"], outputs: ["changed"] }, { kind: "component", type: StructurePickerComponent, selector: "fb-structure-picker", inputs: ["value", "label", "placeholder", "disabled"], outputs: ["valueChange"] }, { kind: "component", type: ValueEditorComponent, selector: "fb-value-editor", inputs: ["value", "label", "dataType", "objectType", "isCollection", "valueSet", "disabled", "allowFormula"], outputs: ["valueChange"] }, { kind: "directive", type: SelectValueDirective, selector: "select[fbValue]", inputs: ["fbValue"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
15439
15986
  }
15440
15987
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: ResourceFormComponent, decorators: [{
15441
15988
  type: Component,
@@ -15448,7 +15995,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImpo
15448
15995
  StructurePickerComponent,
15449
15996
  ValueEditorComponent,
15450
15997
  SelectValueDirective,
15451
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<!--\r\n Il form di **una** risorsa: e' cio' che prima stava inline sotto la riga dell'elenco, e ora\r\n vive nella dialog. I rilievi locali stanno in cima e non accanto al campo che li produce\r\n perche' qui il form si apre gi\u00E0 puntato sulla risorsa: la domanda \u00ABcosa non va\u00BB viene prima\r\n di \u00ABdove\u00BB.\r\n-->\r\n@if (nameError(); as message) {\r\n <p class=\"fb-field__error\">{{ message }}</p>\r\n}\r\n@if (constantReferencesResource()) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n Una costante deve essere un valore fisso: non puo\u2019 referenziare altre risorse\r\n (CONSTANT_REFERENCES_RESOURCE).\r\n </p>\r\n}\r\n@if (duplicateStageOrder()) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Due stage con lo stesso ordine: quale sia il corrente all\u2019avvio diventa arbitrario\r\n (STAGE_ORDER_DUPLICATED).\r\n </p>\r\n}\r\n\r\n<div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Nome</label>\r\n <input\r\n class=\"fb-input fb-input--mono\"\r\n [value]=\"reference().name\"\r\n (change)=\"rename($any($event.target).value)\"\r\n />\r\n <p class=\"fb-field__hint\">\r\n Il nome vive nello stesso spazio dei nomi degli elementi. Rinominare riscrive i riferimenti.\r\n </p>\r\n</div>\r\n\r\n@if (collection() === 'stages') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Etichetta</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"string('label')\"\r\n (input)=\"setField('label', $any($event.target).value)\"\r\n />\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Ordine</label>\r\n <input\r\n class=\"fb-input\"\r\n type=\"number\"\r\n min=\"1\"\r\n [value]=\"string('stageOrder')\"\r\n (input)=\"setNumberField('stageOrder', $any($event.target).value)\"\r\n />\r\n </div>\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"boolean('isActive')\"\r\n (change)=\"setBooleanField('isActive', $any($event.target).checked)\"\r\n />\r\n Attivo all\u2019avvio\r\n </label>\r\n <p class=\"fb-field__hint\">\r\n All\u2019avvio lo stage corrente e\u2019 il primo attivo per ordine. Si avanza con un Assignment su\r\n <code>$Flow.CurrentStage</code>.\r\n </p>\r\n}\r\n\r\n@if (collection() === 'textTemplates') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Testo</label>\r\n <textarea\r\n class=\"fb-textarea\"\r\n [value]=\"string('text')\"\r\n placeholder=\"Gentile {!Cliente.Nome},\"\r\n (input)=\"setField('text', $any($event.target).value)\"\r\n ></textarea>\r\n </div>\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"boolean('isViewedAsPlainText')\"\r\n (change)=\"setBooleanField('isViewedAsPlainText', $any($event.target).checked)\"\r\n />\r\n Testo semplice\r\n </label>\r\n}\r\n\r\n@if (collection() !== 'textTemplates' && collection() !== 'stages') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Tipo</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"string('dataType')\"\r\n (change)=\"setDataType($any($event.target).value)\"\r\n >\r\n @for (type of dataTypes(); track type.value) {\r\n <option [value]=\"type.value\">{{ type.label }}</option>\r\n }\r\n </select>\r\n @if (collection() === 'dynamicChoiceSets') {\r\n <!--\r\n \u00A75.2 \u2014 il valore di un'opzione **generata** non lo scrive l'autore del flow: lo produce\r\n la sorgente, nel tipo in cui il sistema ospite lo tiene, e `dataType` decide il tipo in\r\n cui viaggia. La regola e' generale \u2014 vale per tutte e tre le sorgenti \u2014 e si vede su un\r\n oggetto la cui chiave e' un enum, dove nella tendina serve di norma il numero.\r\n -->\r\n <p class=\"fb-field__hint\">\r\n Decide il tipo in cui viaggia il valore dell\u2019opzione, con qualunque sorgente:\r\n <code>String</code> il nome come testo, <code>Integer</code> o <code>Number</code> il\r\n numero sottostante, <code>Enum</code> il valore tipizzato \u2014 che e\u2019 cio\u2019 che serve in un\r\n parametro o in un membro di classe <code>Enum</code>. Una conversione impossibile non fa\r\n fallire niente: resta il valore prodotto dalla sorgente.\r\n </p>\r\n }\r\n </div>\r\n\r\n @if (requiresObjectType() && !hidesObjectType()) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">\r\n @if (isEnum()) {\r\n Tipo di enumerazione\r\n } @else if (isStructure()) {\r\n Classe\r\n } @else {\r\n Oggetto\r\n }\r\n </label>\r\n @if (isEnum()) {\r\n <!-- Dizionario chiuso: qui la scrittura libera non serve. -->\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"string('objectType')\"\r\n (change)=\"setField('objectType', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 scegli \u2014</option>\r\n @for (entry of enumOptions(); track entry.name) {\r\n <option [value]=\"entry.name\">{{ entry.label || entry.name }}</option>\r\n }\r\n </select>\r\n } @else if (isStructure()) {\r\n <!-- \u00A74.7: una classe del backend, non un'entita' del modello dati. -->\r\n <fb-structure-picker\r\n [value]=\"string('objectType') || undefined\"\r\n label=\"Classe\"\r\n (valueChange)=\"setField('objectType', $event ?? '')\"\r\n />\r\n } @else {\r\n <fb-object-picker\r\n [value]=\"string('objectType') || undefined\"\r\n label=\"Oggetto\"\r\n placeholder=\"Scrivi o scegli un oggetto\"\r\n (valueChange)=\"setField('objectType', $event ?? '')\"\r\n />\r\n }\r\n @if (missingObjectType()) {\r\n <!-- Su una Structure non e' un avviso: senza classe non c'e' nulla da istanziare. -->\r\n <p class=\"fb-field__error\">\r\n La classe e\u2019 obbligatoria: senza, l\u2019attivazione e\u2019 bloccata (OBJECT_TYPE_MISSING).\r\n </p>\r\n } @else if (isStructure()) {\r\n <p class=\"fb-field__hint\">\r\n Il flow ne legge e scrive i <strong>membri</strong> (<code>Nome.Membro</code>): non si\r\n interroga con Get Records ne\u2019 si salva con Create/Update.\r\n </p>\r\n } @else {\r\n <p class=\"fb-field__hint\">Obbligatorio per Object ed Enum (OBJECT_TYPE_MISSING).</p>\r\n }\r\n </div>\r\n }\r\n\r\n @if (supportsScale()) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Decimali</label>\r\n <input\r\n class=\"fb-input\"\r\n type=\"number\"\r\n min=\"0\"\r\n [value]=\"string('scale')\"\r\n (input)=\"setNumberField('scale', $any($event.target).value)\"\r\n />\r\n </div>\r\n }\r\n}\r\n\r\n@if (collection() === 'variables') {\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"boolean('isCollection')\"\r\n (change)=\"setBooleanField('isCollection', $any($event.target).checked)\"\r\n />\r\n \u00C8 una collection\r\n </label>\r\n <p class=\"fb-field__hint\">Solo una collection puo\u2019 essere iterata da un Loop.</p>\r\n\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"boolean('isInput')\"\r\n (change)=\"setBooleanField('isInput', $any($event.target).checked)\"\r\n />\r\n Valorizzabile all\u2019avvio (input)\r\n </label>\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"boolean('isOutput')\"\r\n (change)=\"setBooleanField('isOutput', $any($event.target).checked)\"\r\n />\r\n Leggibile alla fine (output)\r\n </label>\r\n <p class=\"fb-field__hint\">\r\n Input e output sono il contratto del flow verso chi lo invoca, subflow compresi.\r\n </p>\r\n}\r\n\r\n@if (collection() === 'formulas') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Espressione</label>\r\n <!--\r\n Il tipo atteso e' quello che la risorsa dichiara, e `scale` va con lui: sono cio'\r\n che permette al motore di dire che l'espressione produce altro (\u00A76.3).\r\n -->\r\n <fb-formula-editor\r\n [expression]=\"string('expression')\"\r\n usage=\"Resource\"\r\n [expectedDataType]=\"$any(resource()['dataType'])\"\r\n [scale]=\"$any(resource()['scale'])\"\r\n placeholder=\"Importo * 1.22\"\r\n ariaLabel=\"Espressione della formula\"\r\n (expressionChange)=\"setField('expression', $event)\"\r\n >\r\n <p class=\"fb-field__hint\">\r\n Passata verbatim al motore di regole: la sintassi delle funzioni e\u2019 del motore.\r\n </p>\r\n </fb-formula-editor>\r\n </div>\r\n}\r\n\r\n@if (collection() === 'choices') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Testo mostrato</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"string('choiceText')\"\r\n (input)=\"setField('choiceText', $any($event.target).value)\"\r\n />\r\n </div>\r\n}\r\n\r\n@if (collection() === 'dynamicChoiceSets') {\r\n <!--\r\n \u00A75.2 \u2014 le sorgenti sono tre e si escludono a vicenda: i pulsanti le rendono\r\n mutuamente esclusive nel documento, non solo nel form.\r\n -->\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Da dove arrivano le opzioni</label>\r\n <div class=\"fb-filter__modes\" role=\"group\" aria-label=\"Sorgente delle opzioni\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"isChoiceSetSource('collection')\"\r\n (click)=\"setChoiceSetSource('collection')\"\r\n >\r\n Collection del flow\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"isChoiceSetSource('object')\"\r\n (click)=\"setChoiceSetSource('object')\"\r\n >\r\n Query su un oggetto\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"isChoiceSetSource('enum')\"\r\n (click)=\"setChoiceSetSource('enum')\"\r\n >\r\n Valori di un enum\r\n </button>\r\n </div>\r\n </div>\r\n\r\n @if (ambiguousChoiceSetSource()) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Questo choice set dichiara piu\u2019 di una sorgente: il runtime ne usa una sola\r\n (CHOICE_SET_SOURCE_AMBIGUOUS). Scegli quella giusta qui sopra: le altre vengono tolte.\r\n </p>\r\n }\r\n\r\n @if (isChoiceSetSource('collection')) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Collection</label>\r\n <fb-reference-picker\r\n [value]=\"string('collectionReference')\"\r\n [isCollection]=\"true\"\r\n placeholder=\"Scegli una collection\"\r\n (valueChange)=\"setField('collectionReference', $event ?? '')\"\r\n />\r\n <p class=\"fb-field__hint\">\r\n Tipicamente il risultato di un Get Records: le opzioni sono i record che il flow ha gi\u00E0\r\n letto.\r\n </p>\r\n </div>\r\n }\r\n\r\n @if (isChoiceSetSource('object')) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Oggetto</label>\r\n <fb-object-picker\r\n [value]=\"string('object') || undefined\"\r\n placeholder=\"Scrivi o scegli un oggetto\"\r\n (valueChange)=\"setField('object', $event ?? '')\"\r\n />\r\n </div>\r\n }\r\n\r\n @if (isChoiceSetSource('enum')) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Tipo di enumerazione</label>\r\n <!-- Dizionario chiuso, e qui e' anche **autorevole**: un tipo fuori elenco e' un errore. -->\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"string('enumType')\"\r\n (change)=\"setField('enumType', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 scegli \u2014</option>\r\n @for (entry of enumOptions(); track entry.name) {\r\n <option [value]=\"entry.name\">{{ entry.label || entry.name }}</option>\r\n }\r\n </select>\r\n @if (unknownEnumType()) {\r\n <p class=\"fb-field__error\">\r\n Il tipo \u00AB{{ string('enumType') }}\u00BB non e\u2019 fra quelli utilizzabili\r\n (ENUM_TYPE_UNKNOWN).\r\n </p>\r\n } @else {\r\n <p class=\"fb-field__hint\">\r\n Le opzioni sono i valori dichiarati dal tipo: nessuna choice da tenere allineata a mano.\r\n </p>\r\n }\r\n </div>\r\n }\r\n\r\n @if (isChoiceSetSource('enum')) {\r\n <!--\r\n \u00A75.2 \u2014 qui i campi citabili sono le tre proprieta' di un valore di enum, non i campi\r\n di un'entita': l'elenco viene dal dizionario, e i default rendono superfluo dichiararli.\r\n -->\r\n <div class=\"fb-field__row\">\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Campo mostrato</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"string('displayField')\"\r\n (change)=\"setField('displayField', $any($event.target).value)\"\r\n >\r\n <option value=\"\">Predefinito ({{ defaultDisplayField }})</option>\r\n @for (field of enumChoiceSetFields(); track field.value) {\r\n <option [value]=\"field.value\">{{ field.label }}</option>\r\n }\r\n </select>\r\n @if (unknownEnumChoiceSetField('displayField')) {\r\n <p class=\"fb-field__error\">\r\n \u00AB{{ string('displayField') }}\u00BB non e\u2019 una proprieta\u2019 di un valore di enum\r\n (FIELD_UNKNOWN).\r\n </p>\r\n }\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Campo del valore</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"string('valueField')\"\r\n (change)=\"setField('valueField', $any($event.target).value)\"\r\n >\r\n <option value=\"\">Predefinito ({{ defaultValueField }})</option>\r\n @for (field of enumChoiceSetFields(); track field.value) {\r\n <option [value]=\"field.value\">{{ field.label }}</option>\r\n }\r\n </select>\r\n @if (unknownEnumChoiceSetField('valueField')) {\r\n <p class=\"fb-field__error\">\r\n \u00AB{{ string('valueField') }}\u00BB non e\u2019 una proprieta\u2019 di un valore di enum\r\n (FIELD_UNKNOWN).\r\n </p>\r\n }\r\n </div>\r\n </div>\r\n <p class=\"fb-field__hint\">\r\n Dichiarare <code>Integer</code> come tipo della risorsa e\u2019 la stessa cosa che scegliere qui\r\n \u00ABValore numerico\u00BB: il valore dell\u2019opzione e\u2019 il numero sottostante.\r\n </p>\r\n } @else {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Campo mostrato</label>\r\n <fb-field-picker\r\n [value]=\"string('displayField') || undefined\"\r\n [object]=\"string('object') || undefined\"\r\n usage=\"any\"\r\n label=\"Campo mostrato\"\r\n (valueChange)=\"setField('displayField', $event ?? '')\"\r\n />\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Campo del valore</label>\r\n <fb-field-picker\r\n [value]=\"string('valueField') || undefined\"\r\n [object]=\"string('object') || undefined\"\r\n usage=\"any\"\r\n label=\"Campo del valore\"\r\n (valueChange)=\"setField('valueField', $event ?? '')\"\r\n />\r\n </div>\r\n }\r\n <div class=\"fb-field__row\">\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Ordina per</label>\r\n @if (isChoiceSetSource('enum')) {\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"string('sortField')\"\r\n (change)=\"setField('sortField', $any($event.target).value)\"\r\n >\r\n <option value=\"\">Ordine di dichiarazione</option>\r\n @for (field of enumChoiceSetFields(); track field.value) {\r\n <option [value]=\"field.value\">{{ field.label }}</option>\r\n }\r\n </select>\r\n @if (unknownEnumChoiceSetField('sortField')) {\r\n <p class=\"fb-field__error\">\r\n \u00AB{{ string('sortField') }}\u00BB non e\u2019 una proprieta\u2019 di un valore di enum\r\n (FIELD_UNKNOWN).\r\n </p>\r\n }\r\n } @else {\r\n <fb-field-picker\r\n [value]=\"string('sortField') || undefined\"\r\n [object]=\"string('object') || undefined\"\r\n usage=\"sortable\"\r\n label=\"Campo di ordinamento\"\r\n placeholder=\"Nessun ordinamento\"\r\n (valueChange)=\"setField('sortField', $event ?? '')\"\r\n />\r\n }\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Direzione</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"string('sortOrder')\"\r\n (change)=\"setField('sortOrder', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014</option>\r\n @for (order of sortOrders(); track order.value) {\r\n <option [value]=\"order.value\">{{ order.label }}</option>\r\n }\r\n </select>\r\n </div>\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Numero massimo</label>\r\n <input\r\n class=\"fb-input\"\r\n type=\"number\"\r\n min=\"1\"\r\n [value]=\"string('limit')\"\r\n (input)=\"setNumberField('limit', $any($event.target).value)\"\r\n />\r\n </div>\r\n <!--\r\n `supportsLogic` a false: qui `filterLogic` non esiste nel modello, e inviarlo lo\r\n perderebbe in silenzio al primo salvataggio (\u00A74.4). Nessun `emptyWarning`: senza\r\n filtri il set legge tutti i record dell\u2019oggetto, che qui e\u2019 un uso legittimo.\r\n Su un enum i filtri li valuta il runtime **in memoria**, e i campi sono le tre\r\n proprieta\u2019 di un valore: `fieldOptions` e\u2019 cio\u2019 che sostituisce il catalogo (\u00A75.2).\r\n -->\r\n <fb-record-filter-editor\r\n [holder]=\"$any(resource())\"\r\n [object]=\"isChoiceSetSource('enum') ? undefined : string('object') || undefined\"\r\n [fieldOptions]=\"isChoiceSetSource('enum') ? enumChoiceSetFields() : []\"\r\n [title]=\"\r\n isChoiceSetSource('enum') ? 'Quali valori diventano opzioni' : 'Quali record diventano opzioni'\r\n \"\r\n usage=\"filterable\"\r\n [supportsLogic]=\"false\"\r\n (changed)=\"onFiltersChanged($event)\"\r\n />\r\n}\r\n\r\n@if (isStructure() && collection() === 'variables') {\r\n <p class=\"fb-field__hint\">\r\n Non serve un valore iniziale: la variabile parte con un\u2019istanza vuota, e il flow ne assegna i\r\n membri uno alla volta con un Assignment.\r\n </p>\r\n}\r\n\r\n@if (\r\n supportsInitialValue() &&\r\n (collection() === 'variables' || collection() === 'constants' || collection() === 'choices')\r\n) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">\r\n {{ collection() === 'variables' ? 'Valore iniziale' : 'Valore' }}\r\n </label>\r\n <fb-value-editor\r\n [value]=\"value()\"\r\n [dataType]=\"$any(resource()['dataType'])\"\r\n [objectType]=\"$any(resource()['objectType'])\"\r\n [isCollection]=\"boolean('isCollection')\"\r\n [allowFormula]=\"collection() !== 'constants'\"\r\n label=\"Valore\"\r\n (valueChange)=\"setValue($event)\"\r\n />\r\n </div>\r\n}\r\n\r\n<div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Descrizione</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"string('description')\"\r\n (input)=\"setField('description', $any($event.target).value)\"\r\n />\r\n</div>\r\n" }]
15998
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<!--\r\n Il form di **una** risorsa: e' cio' che prima stava inline sotto la riga dell'elenco, e ora\r\n vive nella dialog. I rilievi locali stanno in cima e non accanto al campo che li produce\r\n perche' qui il form si apre gi\u00E0 puntato sulla risorsa: la domanda \u00ABcosa non va\u00BB viene prima\r\n di \u00ABdove\u00BB.\r\n-->\r\n@if (nameError(); as message) {\r\n <p class=\"fb-field__error\">{{ message }}</p>\r\n}\r\n@if (constantReferencesResource()) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n Una costante deve essere un valore fisso: non puo\u2019 referenziare altre risorse\r\n (CONSTANT_REFERENCES_RESOURCE).\r\n </p>\r\n}\r\n@if (duplicateStageOrder()) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Due stage con lo stesso ordine: quale sia il corrente all\u2019avvio diventa arbitrario\r\n (STAGE_ORDER_DUPLICATED).\r\n </p>\r\n}\r\n\r\n<div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Nome</label>\r\n <input\r\n class=\"fb-input fb-input--mono\"\r\n [value]=\"reference().name\"\r\n (change)=\"rename($any($event.target).value)\"\r\n />\r\n <p class=\"fb-field__hint\">\r\n Il nome vive nello stesso spazio dei nomi degli elementi. Rinominare riscrive i riferimenti.\r\n </p>\r\n</div>\r\n\r\n@if (collection() === 'stages') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Etichetta</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"string('label')\"\r\n (input)=\"setField('label', $any($event.target).value)\"\r\n />\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Ordine</label>\r\n <input\r\n class=\"fb-input\"\r\n type=\"number\"\r\n min=\"1\"\r\n [value]=\"string('stageOrder')\"\r\n (input)=\"setNumberField('stageOrder', $any($event.target).value)\"\r\n />\r\n </div>\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"boolean('isActive')\"\r\n (change)=\"setBooleanField('isActive', $any($event.target).checked)\"\r\n />\r\n Attivo all\u2019avvio\r\n </label>\r\n <p class=\"fb-field__hint\">\r\n All\u2019avvio lo stage corrente e\u2019 il primo attivo per ordine. Si avanza con un Assignment su\r\n <code>$Flow.CurrentStage</code>.\r\n </p>\r\n}\r\n\r\n@if (collection() === 'textTemplates') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Testo</label>\r\n <textarea\r\n class=\"fb-textarea\"\r\n [value]=\"string('text')\"\r\n placeholder=\"Gentile {!Cliente.Nome},\"\r\n (input)=\"setField('text', $any($event.target).value)\"\r\n ></textarea>\r\n </div>\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"boolean('isViewedAsPlainText')\"\r\n (change)=\"setBooleanField('isViewedAsPlainText', $any($event.target).checked)\"\r\n />\r\n Testo semplice\r\n </label>\r\n}\r\n\r\n@if (collection() !== 'textTemplates' && collection() !== 'stages') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Tipo</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"string('dataType')\"\r\n (change)=\"setDataType($any($event.target).value)\"\r\n >\r\n @for (type of dataTypes(); track type.value) {\r\n <option [value]=\"type.value\">{{ type.label }}</option>\r\n }\r\n </select>\r\n @if (collection() === 'dynamicChoiceSets') {\r\n <!--\r\n \u00A75.2 \u2014 il valore di un'opzione **generata** non lo scrive l'autore del flow: lo produce\r\n la sorgente, nel tipo in cui il sistema ospite lo tiene, e `dataType` decide il tipo in\r\n cui viaggia. La regola e' generale \u2014 vale per tutte e tre le sorgenti \u2014 e si vede su un\r\n oggetto la cui chiave e' un enum, dove nella tendina serve di norma il numero.\r\n -->\r\n <p class=\"fb-field__hint\">\r\n Decide il tipo in cui viaggia il valore dell\u2019opzione, con qualunque sorgente:\r\n <code>String</code> il nome come testo, <code>Integer</code> o <code>Number</code> il\r\n numero sottostante, <code>Enum</code> il valore tipizzato \u2014 che e\u2019 cio\u2019 che serve in un\r\n parametro o in un membro di classe <code>Enum</code>. Una conversione impossibile non fa\r\n fallire niente: resta il valore prodotto dalla sorgente.\r\n </p>\r\n }\r\n </div>\r\n\r\n @if (requiresObjectType() && !hidesObjectType()) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">\r\n @if (isEnum()) {\r\n Tipo di enumerazione\r\n } @else if (isStructure()) {\r\n Classe\r\n } @else {\r\n Oggetto\r\n }\r\n </label>\r\n @if (isEnum()) {\r\n <!-- Dizionario chiuso: qui la scrittura libera non serve. -->\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"string('objectType')\"\r\n (change)=\"setField('objectType', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 scegli \u2014</option>\r\n @for (entry of enumOptions(); track entry.name) {\r\n <option [value]=\"entry.name\">{{ entry.label || entry.name }}</option>\r\n }\r\n </select>\r\n } @else if (isStructure()) {\r\n <!-- \u00A74.7: una classe del backend, non un'entita' del modello dati. -->\r\n <fb-structure-picker\r\n [value]=\"string('objectType') || undefined\"\r\n label=\"Classe\"\r\n (valueChange)=\"setField('objectType', $event ?? '')\"\r\n />\r\n } @else {\r\n <fb-object-picker\r\n [value]=\"string('objectType') || undefined\"\r\n label=\"Oggetto\"\r\n placeholder=\"Scrivi o scegli un oggetto\"\r\n (valueChange)=\"setField('objectType', $event ?? '')\"\r\n />\r\n }\r\n @if (missingObjectType()) {\r\n <!-- Su una Structure non e' un avviso: senza classe non c'e' nulla da istanziare. -->\r\n <p class=\"fb-field__error\">\r\n La classe e\u2019 obbligatoria: senza, l\u2019attivazione e\u2019 bloccata (OBJECT_TYPE_MISSING).\r\n </p>\r\n } @else if (isStructure()) {\r\n <p class=\"fb-field__hint\">\r\n Il flow ne legge e scrive i <strong>membri</strong> (<code>Nome.Membro</code>): non si\r\n interroga con Get Records ne\u2019 si salva con Create/Update.\r\n </p>\r\n } @else {\r\n <p class=\"fb-field__hint\">Obbligatorio per Object ed Enum (OBJECT_TYPE_MISSING).</p>\r\n }\r\n </div>\r\n }\r\n\r\n @if (supportsScale()) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Decimali</label>\r\n <input\r\n class=\"fb-input\"\r\n type=\"number\"\r\n min=\"0\"\r\n [value]=\"string('scale')\"\r\n (input)=\"setNumberField('scale', $any($event.target).value)\"\r\n />\r\n </div>\r\n }\r\n}\r\n\r\n@if (collection() === 'variables') {\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"boolean('isCollection')\"\r\n (change)=\"setBooleanField('isCollection', $any($event.target).checked)\"\r\n />\r\n \u00C8 una collection\r\n </label>\r\n <p class=\"fb-field__hint\">Solo una collection puo\u2019 essere iterata da un Loop.</p>\r\n\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"boolean('isInput')\"\r\n (change)=\"setBooleanField('isInput', $any($event.target).checked)\"\r\n />\r\n Valorizzabile all\u2019avvio (input)\r\n </label>\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"boolean('isOutput')\"\r\n (change)=\"setBooleanField('isOutput', $any($event.target).checked)\"\r\n />\r\n Leggibile alla fine (output)\r\n </label>\r\n <p class=\"fb-field__hint\">\r\n Input e output sono il contratto del flow verso chi lo invoca, subflow compresi.\r\n </p>\r\n}\r\n\r\n@if (collection() === 'formulas') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Espressione</label>\r\n <!--\r\n Il tipo atteso e' quello che la risorsa dichiara, e `scale` va con lui: sono cio'\r\n che permette al motore di dire che l'espressione produce altro (\u00A76.3).\r\n -->\r\n <fb-formula-editor\r\n [expression]=\"string('expression')\"\r\n usage=\"Resource\"\r\n [expectedDataType]=\"$any(resource()['dataType'])\"\r\n [scale]=\"$any(resource()['scale'])\"\r\n placeholder=\"Importo * 1.22\"\r\n ariaLabel=\"Espressione della formula\"\r\n (expressionChange)=\"setField('expression', $event)\"\r\n >\r\n <p class=\"fb-field__hint\">\r\n Passata verbatim al motore di regole: la sintassi delle funzioni e\u2019 del motore.\r\n </p>\r\n </fb-formula-editor>\r\n </div>\r\n}\r\n\r\n@if (collection() === 'choices') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Testo mostrato</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"string('choiceText')\"\r\n (input)=\"setField('choiceText', $any($event.target).value)\"\r\n />\r\n </div>\r\n}\r\n\r\n@if (collection() === 'dynamicChoiceSets') {\r\n <!--\r\n \u00A75.2 \u2014 le sorgenti sono tre e si escludono a vicenda: i pulsanti le rendono\r\n mutuamente esclusive nel documento, non solo nel form.\r\n -->\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Da dove arrivano le opzioni</label>\r\n <div class=\"fb-filter__modes\" role=\"group\" aria-label=\"Sorgente delle opzioni\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"isChoiceSetSource('collection')\"\r\n (click)=\"setChoiceSetSource('collection')\"\r\n >\r\n Collection del flow\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"isChoiceSetSource('object')\"\r\n (click)=\"setChoiceSetSource('object')\"\r\n >\r\n Query su un oggetto\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"isChoiceSetSource('enum')\"\r\n (click)=\"setChoiceSetSource('enum')\"\r\n >\r\n Valori di un enum\r\n </button>\r\n </div>\r\n </div>\r\n\r\n @if (ambiguousChoiceSetSource()) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Questo choice set dichiara piu\u2019 di una sorgente: il runtime ne usa una sola\r\n (CHOICE_SET_SOURCE_AMBIGUOUS). Scegli quella giusta qui sopra: le altre vengono tolte.\r\n </p>\r\n }\r\n\r\n @if (isChoiceSetSource('collection')) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Collection</label>\r\n <fb-reference-picker\r\n [value]=\"string('collectionReference')\"\r\n [isCollection]=\"true\"\r\n placeholder=\"Scegli una collection\"\r\n (valueChange)=\"setField('collectionReference', $event ?? '')\"\r\n />\r\n <p class=\"fb-field__hint\">\r\n Tipicamente il risultato di un Get Records: le opzioni sono i record che il flow ha gi\u00E0\r\n letto, <strong>nell\u2019ordine in cui ci sono</strong>. Filtri e ordinamento non si applicano\r\n qui: per sceglierne una parte, filtra la collection con un elemento Filter prima dello\r\n screen.\r\n </p>\r\n </div>\r\n }\r\n\r\n @if (isChoiceSetSource('object')) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Oggetto</label>\r\n <fb-object-picker\r\n [value]=\"string('object') || undefined\"\r\n placeholder=\"Scrivi o scegli un oggetto\"\r\n (valueChange)=\"setField('object', $event ?? '')\"\r\n />\r\n </div>\r\n }\r\n\r\n @if (isChoiceSetSource('enum')) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Tipo di enumerazione</label>\r\n <!-- Dizionario chiuso, e qui e' anche **autorevole**: un tipo fuori elenco e' un errore. -->\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"string('enumType')\"\r\n (change)=\"setField('enumType', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 scegli \u2014</option>\r\n @for (entry of enumOptions(); track entry.name) {\r\n <option [value]=\"entry.name\">{{ entry.label || entry.name }}</option>\r\n }\r\n </select>\r\n @if (unknownEnumType()) {\r\n <p class=\"fb-field__error\">\r\n Il tipo \u00AB{{ string('enumType') }}\u00BB non e\u2019 fra quelli utilizzabili\r\n (ENUM_TYPE_UNKNOWN).\r\n </p>\r\n } @else {\r\n <p class=\"fb-field__hint\">\r\n Le opzioni sono i valori dichiarati dal tipo: nessuna choice da tenere allineata a mano.\r\n </p>\r\n }\r\n </div>\r\n }\r\n\r\n @if (isChoiceSetSource('enum')) {\r\n <!--\r\n \u00A75.2 \u2014 qui i campi citabili sono le tre proprieta' di un valore di enum, non i campi\r\n di un'entita': l'elenco viene dal dizionario, e i default rendono superfluo dichiararli.\r\n -->\r\n <div class=\"fb-field__row\">\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Campo mostrato</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"string('displayField')\"\r\n (change)=\"setField('displayField', $any($event.target).value)\"\r\n >\r\n <option value=\"\">Predefinito ({{ defaultDisplayField }})</option>\r\n @for (field of enumChoiceSetFields(); track field.value) {\r\n <option [value]=\"field.value\">{{ field.label }}</option>\r\n }\r\n </select>\r\n @if (unknownEnumChoiceSetField('displayField')) {\r\n <p class=\"fb-field__error\">\r\n \u00AB{{ string('displayField') }}\u00BB non e\u2019 una proprieta\u2019 di un valore di enum\r\n (FIELD_UNKNOWN).\r\n </p>\r\n }\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Campo del valore</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"string('valueField')\"\r\n (change)=\"setField('valueField', $any($event.target).value)\"\r\n >\r\n <option value=\"\">Predefinito ({{ defaultValueField }})</option>\r\n @for (field of enumChoiceSetFields(); track field.value) {\r\n <option [value]=\"field.value\">{{ field.label }}</option>\r\n }\r\n </select>\r\n @if (unknownEnumChoiceSetField('valueField')) {\r\n <p class=\"fb-field__error\">\r\n \u00AB{{ string('valueField') }}\u00BB non e\u2019 una proprieta\u2019 di un valore di enum\r\n (FIELD_UNKNOWN).\r\n </p>\r\n }\r\n </div>\r\n </div>\r\n <p class=\"fb-field__hint\">\r\n Dichiarare <code>Integer</code> come tipo della risorsa e\u2019 la stessa cosa che scegliere qui\r\n \u00ABValore numerico\u00BB: il valore dell\u2019opzione e\u2019 il numero sottostante.\r\n </p>\r\n } @else {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Campo mostrato</label>\r\n <fb-field-picker\r\n [value]=\"string('displayField') || undefined\"\r\n [object]=\"string('object') || undefined\"\r\n usage=\"any\"\r\n label=\"Campo mostrato\"\r\n (valueChange)=\"setField('displayField', $event ?? '')\"\r\n />\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Campo del valore</label>\r\n <fb-field-picker\r\n [value]=\"string('valueField') || undefined\"\r\n [object]=\"string('object') || undefined\"\r\n usage=\"any\"\r\n label=\"Campo del valore\"\r\n (valueChange)=\"setField('valueField', $event ?? '')\"\r\n />\r\n </div>\r\n }\r\n <!--\r\n \u00A75.2 \u2014 `filters`, `filterLogic`, `sortField` e `sortOrder` valgono sulle **sole** sorgenti\r\n `object` (dove diventano la query) ed `enumType` (valutati in memoria). Su una collection le\r\n opzioni sono i suoi elementi nell\u2019ordine in cui ci sono: mostrare i controlli l\u00EC\r\n inviterebbe a dichiarare cio\u2019 che il runtime ignora, ed e\u2019 `CHOICE_SET_FILTERS_IGNORED`.\r\n -->\r\n @if (!isChoiceSetSource('collection')) {\r\n <div class=\"fb-field__row\">\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Ordina per</label>\r\n @if (isChoiceSetSource('enum')) {\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"string('sortField')\"\r\n (change)=\"setField('sortField', $any($event.target).value)\"\r\n >\r\n <option value=\"\">Ordine di dichiarazione</option>\r\n @for (field of enumChoiceSetFields(); track field.value) {\r\n <option [value]=\"field.value\">{{ field.label }}</option>\r\n }\r\n </select>\r\n @if (unknownEnumChoiceSetField('sortField')) {\r\n <p class=\"fb-field__error\">\r\n \u00AB{{ string('sortField') }}\u00BB non e\u2019 una proprieta\u2019 di un valore di enum\r\n (FIELD_UNKNOWN).\r\n </p>\r\n }\r\n } @else {\r\n <fb-field-picker\r\n [value]=\"string('sortField') || undefined\"\r\n [object]=\"string('object') || undefined\"\r\n usage=\"sortable\"\r\n label=\"Campo di ordinamento\"\r\n placeholder=\"Nessun ordinamento\"\r\n (valueChange)=\"setField('sortField', $event ?? '')\"\r\n />\r\n }\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Direzione</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"string('sortOrder')\"\r\n (change)=\"setField('sortOrder', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014</option>\r\n @for (order of sortOrders(); track order.value) {\r\n <option [value]=\"order.value\">{{ order.label }}</option>\r\n }\r\n </select>\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- `limit` e' l'unico dei quattro che vale su tutt'e tre le sorgenti (\u00A75.2). -->\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Numero massimo</label>\r\n <input\r\n class=\"fb-input\"\r\n type=\"number\"\r\n min=\"1\"\r\n [value]=\"string('limit')\"\r\n (input)=\"setNumberField('limit', $any($event.target).value)\"\r\n />\r\n </div>\r\n\r\n @if (!isChoiceSetSource('collection')) {\r\n <!--\r\n `supportsLogic` a true: dalla revisione del contratto `filterLogic` **esiste** anche qui, con\r\n le stesse forme e gli stessi rilievi degli elementi che interrogano il database (\u00A74.4).\r\n Nessun `emptyWarning`: senza filtri il set legge tutti i record dell\u2019oggetto, che qui e\u2019 un\r\n uso legittimo. Su un enum i filtri li valuta il runtime **in memoria**, e i campi sono le tre\r\n proprieta\u2019 di un valore: `fieldOptions` e\u2019 cio\u2019 che sostituisce il catalogo (\u00A75.2).\r\n -->\r\n <fb-record-filter-editor\r\n [holder]=\"$any(resource())\"\r\n [object]=\"isChoiceSetSource('enum') ? undefined : string('object') || undefined\"\r\n [fieldOptions]=\"isChoiceSetSource('enum') ? enumChoiceSetFields() : []\"\r\n [title]=\"\r\n isChoiceSetSource('enum') ? 'Quali valori diventano opzioni' : 'Quali record diventano opzioni'\r\n \"\r\n usage=\"filterable\"\r\n [supportsLogic]=\"true\"\r\n (changed)=\"onFiltersChanged($event)\"\r\n />\r\n } @else if (ignoredChoiceSetFilters(); as ignored) {\r\n <!--\r\n Il documento dichiara cio' che su una collection il runtime ignora. E\u2019 un **avviso**, e la\r\n ripulitura e\u2019 un comando: cancellare da se\u2019 i filtri di un flow scritto altrove sarebbe una\r\n modifica che nessuno ha chiesto, e farebbe sparire l\u2019unico indizio di cosa quel flow\r\n voleva selezionare. E\u2019 la stessa scelta dei membri di un riquadro (\u00A73.6).\r\n -->\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Su una collection {{ ignored }}: le opzioni sono gli elementi della collection nell\u2019ordine in\r\n cui ci sono (CHOICE_SET_FILTERS_IGNORED).\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" (click)=\"clearIgnoredChoiceSetFilters()\">\r\n Togli\r\n </button>\r\n </p>\r\n }\r\n}\r\n\r\n@if (isStructure() && collection() === 'variables') {\r\n <p class=\"fb-field__hint\">\r\n Non serve un valore iniziale: la variabile parte con un\u2019istanza vuota, e il flow ne assegna i\r\n membri uno alla volta con un Assignment.\r\n </p>\r\n}\r\n\r\n@if (\r\n supportsInitialValue() &&\r\n (collection() === 'variables' || collection() === 'constants' || collection() === 'choices')\r\n) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">\r\n {{ collection() === 'variables' ? 'Valore iniziale' : 'Valore' }}\r\n </label>\r\n <fb-value-editor\r\n [value]=\"value()\"\r\n [dataType]=\"$any(resource()['dataType'])\"\r\n [objectType]=\"$any(resource()['objectType'])\"\r\n [isCollection]=\"boolean('isCollection')\"\r\n [allowFormula]=\"collection() !== 'constants'\"\r\n label=\"Valore\"\r\n (valueChange)=\"setValue($event)\"\r\n />\r\n </div>\r\n}\r\n\r\n<div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Descrizione</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"string('description')\"\r\n (input)=\"setField('description', $any($event.target).value)\"\r\n />\r\n</div>\r\n" }]
15452
15999
  }], ctorParameters: () => [], propDecorators: { reference: [{ type: i0.Input, args: [{ isSignal: true, alias: "reference", required: true }] }] } });
15453
16000
 
15454
16001
  /**
@@ -16902,12 +17449,36 @@ class FlowBuilderComponent {
16902
17449
  definition.interviewLabel = value || undefined;
16903
17450
  });
16904
17451
  }
17452
+ /**
17453
+ * Il tipo di flow si sceglie **alla creazione**, e su un flow salvato non si tocca.
17454
+ *
17455
+ * Non e' una preferenza: decide quali globali esistono (`$Record` non c'e' in uno screen
17456
+ * flow, §4.1), quali elementi sono ammessi (§13.6) e come il motore lo esegue. Cambiarlo su
17457
+ * un documento già scritto lo lascia pieno di riferimenti a cose che da quel momento non
17458
+ * esistono — e nessuna primitiva del backend rimedia. Il comando non c'e' nemmeno
17459
+ * nell'interfaccia; il controllo qui e' perché la chiamata resta pubblica.
17460
+ */
16905
17461
  setProcessType(value) {
17462
+ if (!this.session.isNew()) {
17463
+ return;
17464
+ }
16906
17465
  this.store.updateHeader((definition) => {
16907
17466
  definition.processType = value;
16908
17467
  });
16909
17468
  }
16910
17469
  processTypes = computed(() => this.dictionaries.processTypes(), ...(ngDevMode ? [{ debugName: "processTypes" }] : []));
17470
+ /**
17471
+ * Il tipo scritto per esteso, per il flow salvato dove non e' più una scelta. Fuori catalogo
17472
+ * si mostra il valore grezzo: e' del backend, non nostro da correggere (vedi
17473
+ * {@link isProcessTypeOutOfCatalog}).
17474
+ */
17475
+ processTypeLabel = computed(() => {
17476
+ const current = this.store.document().processType;
17477
+ if (!current) {
17478
+ return '— tipo di flow —';
17479
+ }
17480
+ return this.processTypes().find((type) => type.value === current)?.label ?? current;
17481
+ }, ...(ngDevMode ? [{ debugName: "processTypeLabel" }] : []));
16911
17482
  /**
16912
17483
  * Il `processType` del documento non e' fra quelli del dizionario. Non e' un caso di scuola:
16913
17484
  * basta che il dizionario arrivi senza `processTypes`, o che il backend salvi una scrittura
@@ -17098,21 +17669,26 @@ class FlowBuilderComponent {
17098
17669
  return;
17099
17670
  }
17100
17671
  /**
17101
- * Su un tipo a varianti la label e' quella della variante: un node chiamato «Ordina o
17102
- * filtra» non dice cosa fa, e da lì viene anche il nome tecnico. Se la palette non ha
17103
- * passato nessuna variante — dizionario non disponibile — si prende la prima dichiarata,
17104
- * così l'elemento nasce comunque completo (§5.6).
17672
+ * Su un tipo a varianti il nome provvisorio viene dalla variante: un node chiamato
17673
+ * «Ordina o filtra» non dice cosa fa. Se la palette non ha passato nessuna variante —
17674
+ * dizionario non disponibile — si prende la prima dichiarata, così l'elemento nasce
17675
+ * comunque completo (§5.6).
17105
17676
  */
17106
17677
  const variants = this.dictionaries.variantsOf(type);
17107
17678
  const chosenVariant = variant ?? variants[0]?.value;
17108
- const label = chosenVariant
17679
+ const typeLabel = chosenVariant
17109
17680
  ? this.dictionaries.variantLabelOf(type, chosenVariant) || this.dictionaries.labelOf(type)
17110
17681
  : this.dictionaries.labelOf(type);
17111
- // Il nome si genera dalla label e si verifica contro node **e** risorse (§3.3, §11).
17112
- const name = uniqueFlowName(label, this.store.usedNames());
17682
+ /**
17683
+ * L'elemento nasce **senza etichetta**: nominarlo e' la prima cosa che si fa nel suo form,
17684
+ * e il nome tecnico si costruisce da lì mentre la si digita. Il nome invece non può stare
17685
+ * vuoto nemmeno un istante — e' la chiave con cui il documento lo raggiunge (§3.3) —
17686
+ * quindi ne nasce uno provvisorio dal tipo, verificato contro node **e** risorse (§11):
17687
+ * e' anche cio' che il canvas mostra al posto del titolo finché l'etichetta non c'e'.
17688
+ */
17689
+ const name = uniqueFlowName(typeLabel, this.store.usedNames());
17113
17690
  const node = {
17114
17691
  name,
17115
- label,
17116
17692
  locationX: Math.round(x),
17117
17693
  locationY: Math.round(y),
17118
17694
  ...variantPresetOf(type, chosenVariant),
@@ -17473,10 +18049,41 @@ class FlowBuilderComponent {
17473
18049
  }
17474
18050
  }
17475
18051
  undo() {
17476
- this.store.undo();
18052
+ this.stepHistory(() => this.store.undo());
17477
18053
  }
17478
18054
  redo() {
17479
- this.store.redo();
18055
+ this.stepHistory(() => this.store.redo());
18056
+ }
18057
+ /**
18058
+ * Un passo di storico, con la selezione che lo segue.
18059
+ *
18060
+ * La selezione dell'editor **e' il nome** (§3.3), e un annulla può riportare indietro una
18061
+ * rinomina — succede a ogni battuta sull'etichetta di un elemento appena creato, dove il nome
18062
+ * la segue. Il nome selezionato allora non esiste più: l'inspector resta puntato sul vuoto, il
18063
+ * form del tipo sparisce e l'intestazione mostra un nome che nel documento non c'è. L'annulla
18064
+ * però non sposta gli elementi nella loro collection, quindi **dove** stavano — collection e
18065
+ * indice — e' la traccia che sopravvive: dopo il passo si adotta il nome che si trova lì.
18066
+ */
18067
+ stepHistory(step) {
18068
+ const selected = this.selectedName();
18069
+ const before = selected ? this.store.nodeByName().get(selected) : undefined;
18070
+ step();
18071
+ if (!selected || this.store.nodeByName().has(selected)) {
18072
+ return;
18073
+ }
18074
+ const restored = before
18075
+ ? this.store
18076
+ .nodes()
18077
+ .find((entry) => entry.collection === before.collection && entry.index === before.index)
18078
+ : undefined;
18079
+ this.selectedName.set(restored?.name ?? START_NODE_NAME);
18080
+ this.selectedNames.update((names) => restored
18081
+ ? names.map((name) => (name === selected ? restored.name : name))
18082
+ : names.filter((name) => name !== selected));
18083
+ if (!restored) {
18084
+ // L'elemento non c'e' proprio piu': una dialog aperta sul vuoto non ha senso.
18085
+ this.isDialogOpen.set(false);
18086
+ }
17480
18087
  }
17481
18088
  // -------------------------------------------------------------------------
17482
18089
  // Salvataggio e ciclo di vita
@@ -17838,7 +18445,7 @@ class FlowBuilderComponent {
17838
18445
  FormulaValidationService,
17839
18446
  FlowEditorSession,
17840
18447
  FlowLayoutService,
17841
- ], viewQueries: [{ propertyName: "canvas", first: true, predicate: FlowCanvasComponent, descendants: true, isSignal: true }, { propertyName: "search", first: true, predicate: FlowSearchComponent, descendants: true, isSignal: true }, { propertyName: "copyNameInput", first: true, predicate: ["copyNameInput"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"fb-builder\" [attr.data-fb-theme]=\"null\">\r\n <header class=\"fb-top\">\r\n <div class=\"fb-top__identity\">\r\n <input\r\n class=\"fb-top__label\"\r\n [value]=\"document().label || ''\"\r\n placeholder=\"Nome del flow\"\r\n aria-label=\"Nome del flow\"\r\n [disabled]=\"!isEditable()\"\r\n (input)=\"setLabel($any($event.target).value)\"\r\n />\r\n <div class=\"fb-top__meta\">\r\n <input\r\n class=\"fb-top__name\"\r\n [value]=\"document().fullName || ''\"\r\n placeholder=\"NomeTecnico\"\r\n aria-label=\"Nome tecnico del flow\"\r\n [disabled]=\"!isEditable()\"\r\n (input)=\"setFullName($any($event.target).value)\"\r\n />\r\n <select\r\n class=\"fb-top__process\"\r\n [fbValue]=\"document().processType || ''\"\r\n aria-label=\"Tipo di flow\"\r\n [disabled]=\"!isEditable()\"\r\n (change)=\"setProcessType($any($event.target).value)\"\r\n >\r\n <!--\r\n Il segnaposto esiste perche' un `<select>` senza opzione corrispondente non e'\r\n \u00ABvuoto\u00BB: e' a `selectedIndex = -1`, e mostra una casella bianca. Su un flow nuovo\r\n (`processType` non ancora scelto) e' questa la riga che si vede, disabilitata\r\n perche' non e' un valore valido da salvare.\r\n -->\r\n @if (!document().processType) {\r\n <option value=\"\" disabled>\u2014 tipo di flow \u2014</option>\r\n }\r\n @for (type of processTypes(); track type.value) {\r\n <option [value]=\"type.value\">{{ type.label }}</option>\r\n }\r\n <!--\r\n Il tipo del documento che il dizionario non conosce: senza questa opzione la select\r\n resta bianca su un flow che il backend ha salvato con un `processType` fuori\r\n catalogo (o quando `processTypes` manca dalla risposta dei dizionari), e sembra che\r\n l'editor non abbia ricaricato il flow. Va mostrato **senza** riscrivere il\r\n documento: il valore e' del backend, non nostro da correggere.\r\n -->\r\n @if (isProcessTypeOutOfCatalog()) {\r\n <option [value]=\"document().processType\">{{ document().processType }} (fuori catalogo)</option>\r\n }\r\n </select>\r\n <span class=\"fb-top__status\">{{ statusLabel() }}</span>\r\n @if (session.version() != null) {\r\n <span class=\"fb-top__version\">v{{ session.version() }}</span>\r\n }\r\n @if (isDirty()) {\r\n <span class=\"fb-top__dirty\" title=\"Ci sono modifiche non salvate\">modificato</span>\r\n }\r\n </div>\r\n </div>\r\n\r\n <!--\r\n La ricerca **nel documento**: e\u2019 l\u2019unico modo di arrivare a un elemento su un flow che non\r\n sta in una schermata. Non e\u2019 la casella della palette, che filtra i tipi da aggiungere.\r\n -->\r\n <fb-flow-search (picked)=\"onSearchPicked($event)\" />\r\n\r\n <div class=\"fb-top__actions\">\r\n <button type=\"button\" class=\"fb-btn\" [disabled]=\"!canUndo()\" aria-label=\"Annulla\" (click)=\"undo()\">\u21B6</button>\r\n <button type=\"button\" class=\"fb-btn\" [disabled]=\"!canRedo()\" aria-label=\"Ripeti\" (click)=\"redo()\">\u21B7</button>\r\n <button type=\"button\" class=\"fb-btn\" title=\"Ricalcola le posizioni\" (click)=\"autoLayout()\">Riordina</button>\r\n <!--\r\n Un riquadro e\u2019 un commento sul canvas (\u00A73.6): non viene eseguito, quindi il comando sta\r\n qui accanto a \u00ABRiordina\u00BB e non nella palette degli elementi. Nasce attorno a cio\u2019 che e\u2019\r\n selezionato, che e\u2019 il gesto per cui serve.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"!isEditable()\"\r\n [title]=\"\r\n canCopy()\r\n ? 'Racchiudi gli elementi selezionati in un riquadro di commento'\r\n : 'Crea un riquadro di commento vuoto: non cambia il comportamento del flow'\r\n \"\r\n aria-label=\"Nuovo riquadro di raggruppamento\"\r\n (click)=\"createGroup()\"\r\n >\r\n \u2B1A\r\n </button>\r\n\r\n <!--\r\n Copia e incolla stanno **anche** qui e non solo sui tasti: una scorciatoia che nessuno\r\n annuncia non esiste. Il titolo la dice, cos\u00EC si impara usandola una volta.\r\n L\u2019incolla e\u2019 acceso anche con la selezione vuota: cio\u2019 che si incolla sta negli appunti,\r\n e puo\u2019 venire da un altro flow.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"!canCopy()\"\r\n title=\"Copia gli elementi selezionati, con le risorse che usano (Ctrl+C)\"\r\n aria-label=\"Copia gli elementi selezionati\"\r\n (click)=\"copySelection()\"\r\n >\r\n \u29C9\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"!canPaste() || !isEditable()\"\r\n title=\"Incolla gli elementi copiati, anche da un altro flow (Ctrl+V)\"\r\n aria-label=\"Incolla gli elementi copiati\"\r\n (click)=\"startPaste()\"\r\n >\r\n \u2398\r\n </button>\r\n @if (isDialogMode()) {\r\n <!-- Il doppio click sul node fa la stessa cosa, ma non si vede: questo comando s\u00EC. -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"!selectedName()\"\r\n title=\"Apri il dettaglio dell\u2019elemento selezionato\"\r\n (click)=\"openSelectedElement()\"\r\n >\r\n Dettaglio\r\n </button>\r\n }\r\n <button type=\"button\" class=\"fb-btn\" [disabled]=\"isBusy()\" (click)=\"validateNow()\">Valida</button>\r\n\r\n <!--\r\n Esegui e Debug non eseguono niente qui dentro: raccolgono i valori di ingresso e li\r\n consegnano all\u2019applicazione ospite. Si esegue la versione **salvata**, quindi su un flow\r\n mai scritto sono spenti e il titolo dice perche\u2019.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"!canRun()\"\r\n [title]=\"canRun() ? 'Esegui il flow salvato' : 'Salva il flow prima di eseguirlo'\"\r\n (click)=\"openRun('run')\"\r\n >\r\n Esegui\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"!canRun()\"\r\n [title]=\"canRun() ? 'Esegui il flow salvato in debug' : 'Salva il flow prima di eseguirlo'\"\r\n (click)=\"openRun('debug')\"\r\n >\r\n Debug\r\n </button>\r\n\r\n @switch (primaryCommand()) {\r\n @case ('newVersion') {\r\n <!-- Su una versione non modificabile il comando primario e' \"Nuova versione\" (\u00A78.1). -->\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" [disabled]=\"isBusy()\" (click)=\"createNewVersion()\">\r\n Nuova versione\r\n </button>\r\n }\r\n @case ('create') {\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" [disabled]=\"isBusy()\" (click)=\"save()\">\r\n Crea\r\n </button>\r\n }\r\n @default {\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" [disabled]=\"isBusy()\" (click)=\"save()\">\r\n Salva\r\n </button>\r\n }\r\n }\r\n\r\n @if (canDuplicate()) {\r\n <!-- \u00A76.2 \u00ABDuplica\u00BB: il flow sotto un altro nome, alla versione 1. -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"isBusy()\"\r\n title=\"Salva una copia con un altro nome\"\r\n (click)=\"startCopy()\"\r\n >\r\n Duplica\u2026\r\n </button>\r\n }\r\n\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"isBusy() || !canActivate()\"\r\n [title]=\"\r\n canActivate()\r\n ? 'Attiva questa versione'\r\n : 'L\u2019attivazione esige zero errori: correggili nel pannello dei problemi'\r\n \"\r\n (click)=\"activate()\"\r\n >\r\n Attiva\r\n </button>\r\n </div>\r\n </header>\r\n\r\n @if (conflict()) {\r\n <!-- \u00A79.3: qualcun altro ha salvato. Due strade, entrambe offerte. -->\r\n <div class=\"fb-banner fb-banner--warn\" role=\"alert\">\r\n <span>\r\n {{ conflict()?.message }}\r\n @if (conflict()?.conflictingAuthor) {\r\n Ha salvato {{ conflict()?.conflictingAuthor }}.\r\n }\r\n </span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--icon\" (click)=\"reloadAfterConflict()\">Ricarica</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--icon\" (click)=\"saveAsNewVersionAfterConflict()\">\r\n Salva come nuova versione\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" (click)=\"dismissConflict()\">\u00D7</button>\r\n </div>\r\n }\r\n\r\n @if (isCopyOpen()) {\r\n <!--\r\n Il nome si chiede prima di scrivere: e\u2019 l\u2019unico dato che il backend non puo\u2019 inventare, e\r\n un doppione lo rifiuta con AlreadyExists (\u00A76.2). Invio conferma, Esc annulla.\r\n -->\r\n <div class=\"fb-banner fb-copy\" role=\"group\" aria-label=\"Duplica il flow\">\r\n <label class=\"fb-copy__field\">\r\n <span class=\"fb-copy__caption\">Nome tecnico della copia</span>\r\n <input\r\n #copyNameInput\r\n class=\"fb-copy__input\"\r\n [value]=\"copyName()\"\r\n placeholder=\"NomeTecnico_Copia\"\r\n (input)=\"setCopyName($any($event.target).value)\"\r\n (keydown.enter)=\"confirmCopy()\"\r\n (keydown.escape)=\"cancelCopy()\"\r\n />\r\n </label>\r\n <label class=\"fb-copy__field\">\r\n <span class=\"fb-copy__caption\">Nome visibile</span>\r\n <input\r\n class=\"fb-copy__input\"\r\n [value]=\"copyLabel()\"\r\n placeholder=\"(facoltativo)\"\r\n (input)=\"setCopyLabel($any($event.target).value)\"\r\n (keydown.enter)=\"confirmCopy()\"\r\n (keydown.escape)=\"cancelCopy()\"\r\n />\r\n </label>\r\n <span class=\"fb-copy__hint\" [class.fb-copy__hint--error]=\"!!copyNameProblem()\">\r\n {{ copyNameProblem() || copyHint() }}\r\n </span>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--primary fb-btn--icon\"\r\n [disabled]=\"isBusy() || !!copyNameProblem()\"\r\n (click)=\"confirmCopy()\"\r\n >\r\n Duplica\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" (click)=\"cancelCopy()\">Annulla</button>\r\n </div>\r\n }\r\n\r\n @if (notice()) {\r\n <div\r\n class=\"fb-banner\"\r\n [class.fb-banner--error]=\"notice()?.kind === 'error'\"\r\n role=\"status\"\r\n >\r\n <span>{{ notice()?.message }}</span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" aria-label=\"Chiudi\" (click)=\"dismissNotice()\">\r\n \u00D7\r\n </button>\r\n </div>\r\n }\r\n\r\n @if (hasScreensInAutoLaunched()) {\r\n <div class=\"fb-banner fb-banner--error\">\r\n Un flow AutoLaunched che contiene screen e\u2019 un errore di validazione: non c\u2019e\u2019 nessuno a cui mostrarli.\r\n </div>\r\n }\r\n @if (isOrchestrationWithoutStages()) {\r\n <div class=\"fb-banner fb-banner--warn\">\r\n Un flow Orchestration senza stage di orchestrazione viene segnalato dalla validazione\r\n (ORCHESTRATION_WITHOUT_STAGES).\r\n </div>\r\n }\r\n @if (hasNoScreensInScreenFlow()) {\r\n <div class=\"fb-banner fb-banner--warn\">\r\n Un flow di tipo Screen senza nessuno screen viene segnalato dalla validazione.\r\n </div>\r\n }\r\n @if (!isEditable()) {\r\n <div class=\"fb-banner\">\r\n Questa versione e\u2019 in sola lettura: per modificarla creane una nuova.\r\n </div>\r\n }\r\n\r\n <div class=\"fb-main\">\r\n <aside class=\"fb-main__palette\">\r\n <fb-element-palette [processType]=\"document().processType\" (elementPicked)=\"onElementPicked($event)\" />\r\n </aside>\r\n\r\n <div class=\"fb-main__center\">\r\n <fb-flow-canvas\r\n class=\"fb-main__canvas\"\r\n [selectedName]=\"selectedName()\"\r\n [selectedNames]=\"selectedNames()\"\r\n [selectedGroupName]=\"selectedGroupName()\"\r\n [outline]=\"outline()\"\r\n [isEditable]=\"isEditable()\"\r\n (selectionChange)=\"onSelectionChange($event)\"\r\n (nodeOpened)=\"onNodeOpened($event)\"\r\n (nodeRemoveRequested)=\"onRemoveNode($event)\"\r\n (nodeDuplicateRequested)=\"onDuplicateNode($event)\"\r\n (elementDropped)=\"onElementDropped($event)\"\r\n (groupSelected)=\"onGroupSelected($event)\"\r\n (groupRemoveRequested)=\"onGroupRemoved($event)\"\r\n />\r\n\r\n @if (showProblems()) {\r\n <fb-problems-panel\r\n class=\"fb-main__problems\"\r\n (elementFocused)=\"revealIssueElement($event)\"\r\n (elementOpened)=\"openIssueElement($event)\"\r\n (closed)=\"toggleProblems()\"\r\n />\r\n } @else {\r\n <button type=\"button\" class=\"fb-main__problems-toggle\" (click)=\"toggleProblems()\">\r\n Problemi\r\n @if (errorCount()) {\r\n <span class=\"fb-main__count fb-main__count--error\">{{ errorCount() }}</span>\r\n }\r\n @if (warningCount()) {\r\n <span class=\"fb-main__count fb-main__count--warn\">{{ warningCount() }}</span>\r\n }\r\n </button>\r\n }\r\n </div>\r\n\r\n <aside class=\"fb-main__side\">\r\n <nav class=\"fb-side__tabs\" aria-label=\"Pannelli\">\r\n @if (!isDialogMode()) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-side__tab\"\r\n [class.fb-side__tab--active]=\"sidePanel() === 'inspector'\"\r\n (click)=\"setPanel('inspector')\"\r\n >\r\n Elemento\r\n </button>\r\n }\r\n <button\r\n type=\"button\"\r\n class=\"fb-side__tab\"\r\n [class.fb-side__tab--active]=\"sidePanel() === 'resources'\"\r\n (click)=\"setPanel('resources')\"\r\n >\r\n Risorse\r\n </button>\r\n <!--\r\n I riquadri (\u00A73.6). Il tab porta il conteggio perche\u2019 e\u2019 l\u2019unico modo di sapere che un\r\n flow ne ha, quando sono chiusi o fuori dalla vista.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-side__tab\"\r\n [class.fb-side__tab--active]=\"sidePanel() === 'groups'\"\r\n (click)=\"setPanel('groups')\"\r\n >\r\n Riquadri\r\n @if (store.groups().length) {\r\n <span class=\"fb-main__count\">{{ store.groups().length }}</span>\r\n }\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-side__tab\"\r\n [class.fb-side__tab--active]=\"sidePanel() === 'versions'\"\r\n (click)=\"setPanel('versions')\"\r\n >\r\n Versioni\r\n </button>\r\n <!--\r\n Il pannello dell\u2019ultima esecuzione. \u00C8 l\u2019unico posto in cui si vede la traccia di un\r\n flow **senza schermate**: l\u00EC il runtime non apre nessuna interfaccia, e senza questo\r\n tab non ci sarebbe niente da guardare.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-side__tab\"\r\n [class.fb-side__tab--active]=\"sidePanel() === 'debug'\"\r\n (click)=\"setPanel('debug')\"\r\n >\r\n Debug\r\n @if (isRunning()) {\r\n <span class=\"fb-side__tab-dot\" aria-label=\"esecuzione in corso\"></span>\r\n }\r\n </button>\r\n </nav>\r\n\r\n <div class=\"fb-side__content\">\r\n @switch (sidePanel()) {\r\n @case ('inspector') {\r\n <fb-element-inspector\r\n [selectedName]=\"selectedName()\"\r\n [initialFieldPath]=\"pendingFieldPath()\"\r\n (removeRequested)=\"onRemoveNode($event)\"\r\n (duplicateRequested)=\"onDuplicateNode($event)\"\r\n (renamed)=\"onNodeRenamed($event)\"\r\n (closed)=\"setPanel('resources')\"\r\n />\r\n }\r\n @case ('resources') {\r\n <fb-resource-panel\r\n [target]=\"resourceTarget()\"\r\n [editing]=\"editingResource()\"\r\n (editRequested)=\"onResourceEditRequested($event)\"\r\n (closed)=\"setPanel('inspector')\"\r\n />\r\n }\r\n @case ('groups') {\r\n <fb-group-panel\r\n [isEditable]=\"isEditable()\"\r\n [selectedNodeNames]=\"selectedNames()\"\r\n [target]=\"groupTarget()\"\r\n (createRequested)=\"createGroup()\"\r\n (memberFocused)=\"focusMember($event)\"\r\n (closed)=\"setPanel('resources')\"\r\n />\r\n }\r\n @case ('versions') {\r\n <fb-version-panel\r\n (versionOpened)=\"openVersion($event)\"\r\n (notice)=\"showNotice($event)\"\r\n (closed)=\"setPanel('inspector')\"\r\n />\r\n }\r\n @case ('debug') {\r\n <!-- Evidenzia sul canvas senza rubare il pannello: la traccia si sta leggendo. -->\r\n <fb-debug-panel\r\n [outcome]=\"shownOutcome()\"\r\n [wasDebug]=\"wasRunInDebug()\"\r\n [isRunning]=\"isRunning()\"\r\n (elementFocused)=\"highlightElement($event)\"\r\n (cleared)=\"clearRunOutcome()\"\r\n (closed)=\"setPanel('inspector')\"\r\n />\r\n }\r\n }\r\n </div>\r\n\r\n <footer class=\"fb-side__footer\">\r\n <label class=\"fb-btn fb-btn--icon\">\r\n Importa JSON\r\n <input type=\"file\" accept=\"application/json,.json\" hidden (change)=\"onFileSelected($event)\" />\r\n </label>\r\n </footer>\r\n </aside>\r\n </div>\r\n\r\n @if (pastePreview(); as preview) {\r\n <!--\r\n L\u2019incolla passa da una conferma perche\u2019 non e\u2019 mai una copia identica: nomi gi\u00E0 presi,\r\n risorse che qui non esistono, riferimenti che restano orfani. Il piano e\u2019 gi\u00E0 calcolato,\r\n la finestra lo mostra e basta.\r\n -->\r\n <fb-paste-dialog\r\n [payload]=\"preview.payload\"\r\n [plan]=\"preview.plan\"\r\n (confirmed)=\"confirmPaste($event)\"\r\n (cancelled)=\"cancelPaste()\"\r\n />\r\n }\r\n\r\n @if (runMode(); as mode) {\r\n <!--\r\n La finestra degli ingressi. Non avvia niente e non chiama niente: raccoglie i valori e li\r\n annuncia con `runRequested`, perche\u2019 l\u2019unico a sapere dove gira il motore e\u2019 l\u2019ospite.\r\n -->\r\n <fb-run-dialog\r\n [mode]=\"mode\"\r\n [flowName]=\"session.flowName()\"\r\n [version]=\"session.version()\"\r\n [isDirty]=\"isDirty()\"\r\n [isBusy]=\"isRunning()\"\r\n (confirmed)=\"confirmRun($event)\"\r\n (cancelled)=\"closeRun()\"\r\n />\r\n }\r\n\r\n @if (editingResource(); as edit) {\r\n <!--\r\n La finestra di una risorsa. Sta qui e non dentro il pannello per una ragione sola: nella\r\n colonna laterale la ritaglierebbe lo scorrimento.\r\n -->\r\n <fb-resource-dialog [edit]=\"edit\" (closed)=\"onResourceEditRequested(null)\" />\r\n }\r\n\r\n @if (isDialogMode() && isDialogOpen() && selectedName()) {\r\n <!-- La dialog sta dentro il builder, non nel body: la libreria e\u2019 innestabile. -->\r\n <fb-element-dialog\r\n [selectedName]=\"selectedName()\"\r\n [initialFieldPath]=\"pendingFieldPath()\"\r\n (closed)=\"closeDialog()\"\r\n (removeRequested)=\"onRemoveNode($event)\"\r\n (duplicateRequested)=\"onDuplicateNode($event)\"\r\n (renamed)=\"onNodeRenamed($event)\"\r\n />\r\n }\r\n</div>\r\n", styles: [":host{display:block;width:100%;height:100%;min-height:0;font:inherit;color:var(--fb-text, #1d2939)}.fb-builder{position:relative;display:flex;flex-direction:column;width:100%;height:100%;min-height:0;background:var(--fb-surface, #fff)}.fb-top{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:9px 14px;border-bottom:1px solid var(--fb-border, #e2e5eb);background:var(--fb-surface, #fff)}.fb-top__identity{min-width:0}.fb-top__label{width:100%;max-width:420px;padding:2px 4px;border:1px solid transparent;border-radius:4px;background:transparent;color:var(--fb-text, #1d2939);font:inherit;font-size:15px;font-weight:600}.fb-top__label:hover:not(:disabled),.fb-top__label:focus-visible{border-color:var(--fb-border, #d6dae1);background:var(--fb-surface, #fff)}.fb-top__meta{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-top:2px}.fb-top__name{width:180px;padding:1px 4px;border:1px solid transparent;border-radius:4px;background:transparent;color:var(--fb-text-muted, #667085);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px}.fb-top__name:hover:not(:disabled),.fb-top__name:focus-visible{border-color:var(--fb-border, #d6dae1)}.fb-top__process{padding:1px 4px;border:1px solid var(--fb-border-subtle, #e6e9ee);border-radius:4px;background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:11px}.fb-top__status,.fb-top__version,.fb-top__dirty{padding:2px 7px;border-radius:999px;background:var(--fb-surface-sunken, #eef0f4);color:var(--fb-text-muted, #6b7086);font-size:10px;font-weight:600}.fb-top__version{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.fb-top__dirty{background:color-mix(in srgb,var(--fb-warning, #b7791f) 16%,transparent);color:var(--fb-warning, #b7791f)}.fb-top__actions{display:flex;flex-wrap:wrap;gap:4px}.fb-banner{display:flex;flex-wrap:wrap;align-items:center;gap:8px;padding:6px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee);background:color-mix(in srgb,var(--fb-accent, #2f6feb) 7%,transparent);font-size:11px}.fb-banner--warn{background:color-mix(in srgb,var(--fb-warning, #b7791f) 12%,transparent);color:var(--fb-warning, #b7791f)}.fb-banner--error{background:color-mix(in srgb,var(--fb-error, #c9372c) 10%,transparent);color:var(--fb-error, #c9372c)}.fb-banner>span{flex:1;min-width:200px}.fb-copy__field{display:flex;align-items:center;gap:6px}.fb-copy__caption{color:var(--fb-text-muted, #667085);white-space:nowrap}.fb-copy__input{width:180px;padding:2px 5px;border:1px solid var(--fb-border, #d6dae1);border-radius:4px;background:var(--fb-surface, #fff);color:var(--fb-text, #1d2939);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px}.fb-copy__hint{flex:1;min-width:180px;color:var(--fb-text-muted, #667085)}.fb-copy__hint--error{color:var(--fb-error, #c9372c)}.fb-main{display:flex;flex:1;min-height:0}.fb-main__palette{flex:0 0 190px;min-width:0}.fb-main__center{display:flex;flex:1;flex-direction:column;min-width:0;min-height:0}.fb-main__canvas{flex:1;min-height:0}.fb-main__problems{flex:0 0 auto;height:220px}.fb-main__problems-toggle{display:flex;align-items:center;gap:6px;padding:4px 12px;border:0;border-top:1px solid var(--fb-border, #d6dae1);background:var(--fb-surface-alt, #f8f9fb);color:var(--fb-text-muted, #667085);font:inherit;font-size:11px;cursor:pointer}.fb-main__count{padding:0 5px;border-radius:8px;background:var(--fb-border, #d6dae1);font-size:9px;font-weight:700}.fb-main__count--error{background:color-mix(in srgb,var(--fb-error, #c9372c) 16%,transparent);color:var(--fb-error, #c9372c)}.fb-main__count--warn{background:color-mix(in srgb,var(--fb-warning, #b7791f) 16%,transparent);color:var(--fb-warning, #b7791f)}.fb-main__side{display:flex;flex-direction:column;flex:0 0 340px;min-width:0;min-height:0;border-left:1px solid var(--fb-border, #d6dae1);background:var(--fb-surface, #fff)}.fb-side__tabs{display:flex;gap:2px;margin:8px 10px;padding:3px;border-radius:var(--fb-radius, 10px);background:var(--fb-surface-sunken, #eef0f4)}.fb-side__tab{flex:1;padding:5px 8px;border:0;border-radius:var(--fb-radius-xs, 6px);background:transparent;color:var(--fb-text-muted, #6b7086);font:inherit;font-size:11px;cursor:pointer;transition:background .12s ease,color .12s ease}.fb-side__tab-dot{display:inline-block;width:6px;height:6px;margin-left:4px;border-radius:50%;background:var(--fb-accent, #2f6feb);vertical-align:middle}.fb-side__tab:hover:not(.fb-side__tab--active){color:var(--fb-text, #1a1c23)}.fb-side__tab--active{background:var(--fb-surface, #fff);box-shadow:var(--fb-shadow-sm, 0 1px 2px rgb(16 24 40 / 6%));color:var(--fb-text, #1a1c23);font-weight:600}.fb-side__content{flex:1;min-height:0;border-top:1px solid var(--fb-border-subtle, #eef0f4)}.fb-side__content>*{height:100%}.fb-side__footer{display:flex;gap:6px;padding:6px 8px;border-top:1px solid var(--fb-border-subtle, #e6e9ee)}@media(max-width:1200px){.fb-main__palette{flex-basis:150px}.fb-main__side{flex-basis:290px}}\n"], dependencies: [{ kind: "component", type: DebugPanelComponent, selector: "fb-debug-panel", inputs: ["outcome", "wasDebug", "isRunning"], outputs: ["closed", "elementFocused", "cleared"] }, { kind: "component", type: ElementDialogComponent, selector: "fb-element-dialog", inputs: ["selectedName", "initialFieldPath"], outputs: ["closed", "removeRequested", "duplicateRequested", "renamed"] }, { kind: "component", type: ElementInspectorComponent, selector: "fb-element-inspector", inputs: ["selectedName", "showHeader", "initialFieldPath"], outputs: ["closed", "removeRequested", "duplicateRequested", "renamed"] }, { kind: "component", type: ElementPaletteComponent, selector: "fb-element-palette", inputs: ["processType"], outputs: ["elementPicked"] }, { kind: "component", type: FlowCanvasComponent, selector: "fb-flow-canvas", inputs: ["selectedName", "selectedGroupName", "selectedNames", "outline", "isEditable"], outputs: ["selectionChange", "nodeOpened", "nodeRemoveRequested", "nodeDuplicateRequested", "groupSelected", "groupRemoveRequested", "elementDropped"] }, { kind: "component", type: FlowSearchComponent, selector: "fb-flow-search", outputs: ["picked"] }, { kind: "component", type: GroupPanelComponent, selector: "fb-group-panel", inputs: ["isEditable", "selectedNodeNames", "target"], outputs: ["closed", "createRequested", "memberFocused"] }, { kind: "component", type: PasteDialogComponent, selector: "fb-paste-dialog", inputs: ["payload", "plan"], outputs: ["confirmed", "cancelled"] }, { kind: "component", type: ProblemsPanelComponent, selector: "fb-problems-panel", outputs: ["elementFocused", "elementOpened", "closed"] }, { kind: "component", type: ResourceDialogComponent, selector: "fb-resource-dialog", inputs: ["edit"], outputs: ["closed"] }, { kind: "component", type: ResourcePanelComponent, selector: "fb-resource-panel", inputs: ["target", "editing"], outputs: ["closed", "editRequested"] }, { kind: "component", type: RunDialogComponent, selector: "fb-run-dialog", inputs: ["mode", "flowName", "version", "isDirty", "isBusy"], outputs: ["confirmed", "cancelled"] }, { kind: "component", type: VersionPanelComponent, selector: "fb-version-panel", outputs: ["closed", "versionOpened", "notice"] }, { kind: "directive", type: SelectValueDirective, selector: "select[fbValue]", inputs: ["fbValue"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
18448
+ ], viewQueries: [{ propertyName: "canvas", first: true, predicate: FlowCanvasComponent, descendants: true, isSignal: true }, { propertyName: "search", first: true, predicate: FlowSearchComponent, descendants: true, isSignal: true }, { propertyName: "copyNameInput", first: true, predicate: ["copyNameInput"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"fb-builder\" [attr.data-fb-theme]=\"null\">\r\n <header class=\"fb-top\">\r\n <div class=\"fb-top__identity\">\r\n <input\r\n class=\"fb-top__label\"\r\n [value]=\"document().label || ''\"\r\n placeholder=\"Nome del flow\"\r\n aria-label=\"Nome del flow\"\r\n [disabled]=\"!isEditable()\"\r\n (input)=\"setLabel($any($event.target).value)\"\r\n />\r\n <div class=\"fb-top__meta\">\r\n <input\r\n class=\"fb-top__name\"\r\n [value]=\"document().fullName || ''\"\r\n placeholder=\"NomeTecnico\"\r\n aria-label=\"Nome tecnico del flow\"\r\n [disabled]=\"!isEditable()\"\r\n (input)=\"setFullName($any($event.target).value)\"\r\n />\r\n <!--\r\n Il tipo di flow si sceglie **alla creazione** e poi non si tocca pi\u00F9: decide quali\r\n globali esistono (\u00A74.1), quali elementi sono ammessi e come il motore esegue il flow,\r\n e cambiarlo su un documento gi\u00E0 scritto lo lascia pieno di riferimenti che non\r\n esistono. Su un flow salvato resta quindi un\u2019etichetta, non un comando.\r\n -->\r\n @if (session.isNew()) {\r\n <select\r\n class=\"fb-top__process\"\r\n [fbValue]=\"document().processType || ''\"\r\n aria-label=\"Tipo di flow\"\r\n [disabled]=\"!isEditable()\"\r\n (change)=\"setProcessType($any($event.target).value)\"\r\n >\r\n <!--\r\n Il segnaposto esiste perche' un `<select>` senza opzione corrispondente non e'\r\n \u00ABvuoto\u00BB: e' a `selectedIndex = -1`, e mostra una casella bianca. Su un flow nuovo\r\n (`processType` non ancora scelto) e' questa la riga che si vede, disabilitata\r\n perche' non e' un valore valido da salvare.\r\n -->\r\n @if (!document().processType) {\r\n <option value=\"\" disabled>\u2014 tipo di flow \u2014</option>\r\n }\r\n @for (type of processTypes(); track type.value) {\r\n <option [value]=\"type.value\">{{ type.label }}</option>\r\n }\r\n <!--\r\n Il tipo del documento che il dizionario non conosce: senza questa opzione la select\r\n resta bianca su un flow che il backend ha salvato con un `processType` fuori\r\n catalogo (o quando `processTypes` manca dalla risposta dei dizionari), e sembra che\r\n l'editor non abbia ricaricato il flow. Va mostrato **senza** riscrivere il\r\n documento: il valore e' del backend, non nostro da correggere.\r\n -->\r\n @if (isProcessTypeOutOfCatalog()) {\r\n <option [value]=\"document().processType\">{{ document().processType }} (fuori catalogo)</option>\r\n }\r\n </select>\r\n } @else {\r\n <span class=\"fb-top__process fb-top__process--fixed\" title=\"Il tipo di flow si sceglie alla creazione\">\r\n {{ processTypeLabel() }}\r\n </span>\r\n }\r\n <span class=\"fb-top__status\">{{ statusLabel() }}</span>\r\n @if (session.version() != null) {\r\n <span class=\"fb-top__version\">v{{ session.version() }}</span>\r\n }\r\n @if (isDirty()) {\r\n <span class=\"fb-top__dirty\" title=\"Ci sono modifiche non salvate\">modificato</span>\r\n }\r\n </div>\r\n </div>\r\n\r\n <!--\r\n La ricerca **nel documento**: e\u2019 l\u2019unico modo di arrivare a un elemento su un flow che non\r\n sta in una schermata. Non e\u2019 la casella della palette, che filtra i tipi da aggiungere.\r\n -->\r\n <fb-flow-search (picked)=\"onSearchPicked($event)\" />\r\n\r\n <div class=\"fb-top__actions\">\r\n <button type=\"button\" class=\"fb-btn\" [disabled]=\"!canUndo()\" aria-label=\"Annulla\" (click)=\"undo()\">\u21B6</button>\r\n <button type=\"button\" class=\"fb-btn\" [disabled]=\"!canRedo()\" aria-label=\"Ripeti\" (click)=\"redo()\">\u21B7</button>\r\n <button type=\"button\" class=\"fb-btn\" title=\"Ricalcola le posizioni\" (click)=\"autoLayout()\">Riordina</button>\r\n <!--\r\n Un riquadro e\u2019 un commento sul canvas (\u00A73.6): non viene eseguito, quindi il comando sta\r\n qui accanto a \u00ABRiordina\u00BB e non nella palette degli elementi. Nasce attorno a cio\u2019 che e\u2019\r\n selezionato, che e\u2019 il gesto per cui serve.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"!isEditable()\"\r\n [title]=\"\r\n canCopy()\r\n ? 'Racchiudi gli elementi selezionati in un riquadro di commento'\r\n : 'Crea un riquadro di commento vuoto: non cambia il comportamento del flow'\r\n \"\r\n aria-label=\"Nuovo riquadro di raggruppamento\"\r\n (click)=\"createGroup()\"\r\n >\r\n \u2B1A\r\n </button>\r\n\r\n <!--\r\n Copia e incolla stanno **anche** qui e non solo sui tasti: una scorciatoia che nessuno\r\n annuncia non esiste. Il titolo la dice, cos\u00EC si impara usandola una volta.\r\n L\u2019incolla e\u2019 acceso anche con la selezione vuota: cio\u2019 che si incolla sta negli appunti,\r\n e puo\u2019 venire da un altro flow.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"!canCopy()\"\r\n title=\"Copia gli elementi selezionati, con le risorse che usano (Ctrl+C)\"\r\n aria-label=\"Copia gli elementi selezionati\"\r\n (click)=\"copySelection()\"\r\n >\r\n \u29C9\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"!canPaste() || !isEditable()\"\r\n title=\"Incolla gli elementi copiati, anche da un altro flow (Ctrl+V)\"\r\n aria-label=\"Incolla gli elementi copiati\"\r\n (click)=\"startPaste()\"\r\n >\r\n \u2398\r\n </button>\r\n @if (isDialogMode()) {\r\n <!-- Il doppio click sul node fa la stessa cosa, ma non si vede: questo comando s\u00EC. -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"!selectedName()\"\r\n title=\"Apri il dettaglio dell\u2019elemento selezionato\"\r\n (click)=\"openSelectedElement()\"\r\n >\r\n Dettaglio\r\n </button>\r\n }\r\n <button type=\"button\" class=\"fb-btn\" [disabled]=\"isBusy()\" (click)=\"validateNow()\">Valida</button>\r\n\r\n <!--\r\n Esegui e Debug non eseguono niente qui dentro: raccolgono i valori di ingresso e li\r\n consegnano all\u2019applicazione ospite. Si esegue la versione **salvata**, quindi su un flow\r\n mai scritto sono spenti e il titolo dice perche\u2019.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"!canRun()\"\r\n [title]=\"canRun() ? 'Esegui il flow salvato' : 'Salva il flow prima di eseguirlo'\"\r\n (click)=\"openRun('run')\"\r\n >\r\n Esegui\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"!canRun()\"\r\n [title]=\"canRun() ? 'Esegui il flow salvato in debug' : 'Salva il flow prima di eseguirlo'\"\r\n (click)=\"openRun('debug')\"\r\n >\r\n Debug\r\n </button>\r\n\r\n @switch (primaryCommand()) {\r\n @case ('newVersion') {\r\n <!-- Su una versione non modificabile il comando primario e' \"Nuova versione\" (\u00A78.1). -->\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" [disabled]=\"isBusy()\" (click)=\"createNewVersion()\">\r\n Nuova versione\r\n </button>\r\n }\r\n @case ('create') {\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" [disabled]=\"isBusy()\" (click)=\"save()\">\r\n Crea\r\n </button>\r\n }\r\n @default {\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" [disabled]=\"isBusy()\" (click)=\"save()\">\r\n Salva\r\n </button>\r\n }\r\n }\r\n\r\n @if (canDuplicate()) {\r\n <!-- \u00A76.2 \u00ABDuplica\u00BB: il flow sotto un altro nome, alla versione 1. -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"isBusy()\"\r\n title=\"Salva una copia con un altro nome\"\r\n (click)=\"startCopy()\"\r\n >\r\n Duplica\u2026\r\n </button>\r\n }\r\n\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"isBusy() || !canActivate()\"\r\n [title]=\"\r\n canActivate()\r\n ? 'Attiva questa versione'\r\n : 'L\u2019attivazione esige zero errori: correggili nel pannello dei problemi'\r\n \"\r\n (click)=\"activate()\"\r\n >\r\n Attiva\r\n </button>\r\n </div>\r\n </header>\r\n\r\n @if (conflict()) {\r\n <!-- \u00A79.3: qualcun altro ha salvato. Due strade, entrambe offerte. -->\r\n <div class=\"fb-banner fb-banner--warn\" role=\"alert\">\r\n <span>\r\n {{ conflict()?.message }}\r\n @if (conflict()?.conflictingAuthor) {\r\n Ha salvato {{ conflict()?.conflictingAuthor }}.\r\n }\r\n </span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--icon\" (click)=\"reloadAfterConflict()\">Ricarica</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--icon\" (click)=\"saveAsNewVersionAfterConflict()\">\r\n Salva come nuova versione\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" (click)=\"dismissConflict()\">\u00D7</button>\r\n </div>\r\n }\r\n\r\n @if (isCopyOpen()) {\r\n <!--\r\n Il nome si chiede prima di scrivere: e\u2019 l\u2019unico dato che il backend non puo\u2019 inventare, e\r\n un doppione lo rifiuta con AlreadyExists (\u00A76.2). Invio conferma, Esc annulla.\r\n -->\r\n <div class=\"fb-banner fb-copy\" role=\"group\" aria-label=\"Duplica il flow\">\r\n <label class=\"fb-copy__field\">\r\n <span class=\"fb-copy__caption\">Nome tecnico della copia</span>\r\n <input\r\n #copyNameInput\r\n class=\"fb-copy__input\"\r\n [value]=\"copyName()\"\r\n placeholder=\"NomeTecnico_Copia\"\r\n (input)=\"setCopyName($any($event.target).value)\"\r\n (keydown.enter)=\"confirmCopy()\"\r\n (keydown.escape)=\"cancelCopy()\"\r\n />\r\n </label>\r\n <label class=\"fb-copy__field\">\r\n <span class=\"fb-copy__caption\">Nome visibile</span>\r\n <input\r\n class=\"fb-copy__input\"\r\n [value]=\"copyLabel()\"\r\n placeholder=\"(facoltativo)\"\r\n (input)=\"setCopyLabel($any($event.target).value)\"\r\n (keydown.enter)=\"confirmCopy()\"\r\n (keydown.escape)=\"cancelCopy()\"\r\n />\r\n </label>\r\n <span class=\"fb-copy__hint\" [class.fb-copy__hint--error]=\"!!copyNameProblem()\">\r\n {{ copyNameProblem() || copyHint() }}\r\n </span>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--primary fb-btn--icon\"\r\n [disabled]=\"isBusy() || !!copyNameProblem()\"\r\n (click)=\"confirmCopy()\"\r\n >\r\n Duplica\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" (click)=\"cancelCopy()\">Annulla</button>\r\n </div>\r\n }\r\n\r\n @if (notice()) {\r\n <div\r\n class=\"fb-banner\"\r\n [class.fb-banner--error]=\"notice()?.kind === 'error'\"\r\n role=\"status\"\r\n >\r\n <span>{{ notice()?.message }}</span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" aria-label=\"Chiudi\" (click)=\"dismissNotice()\">\r\n \u00D7\r\n </button>\r\n </div>\r\n }\r\n\r\n @if (hasScreensInAutoLaunched()) {\r\n <div class=\"fb-banner fb-banner--error\">\r\n Un flow AutoLaunched che contiene screen e\u2019 un errore di validazione: non c\u2019e\u2019 nessuno a cui mostrarli.\r\n </div>\r\n }\r\n @if (isOrchestrationWithoutStages()) {\r\n <div class=\"fb-banner fb-banner--warn\">\r\n Un flow Orchestration senza stage di orchestrazione viene segnalato dalla validazione\r\n (ORCHESTRATION_WITHOUT_STAGES).\r\n </div>\r\n }\r\n @if (hasNoScreensInScreenFlow()) {\r\n <div class=\"fb-banner fb-banner--warn\">\r\n Un flow di tipo Screen senza nessuno screen viene segnalato dalla validazione.\r\n </div>\r\n }\r\n @if (!isEditable()) {\r\n <div class=\"fb-banner\">\r\n Questa versione e\u2019 in sola lettura: per modificarla creane una nuova.\r\n </div>\r\n }\r\n\r\n <div class=\"fb-main\">\r\n <aside class=\"fb-main__palette\">\r\n <fb-element-palette [processType]=\"document().processType\" (elementPicked)=\"onElementPicked($event)\" />\r\n </aside>\r\n\r\n <div class=\"fb-main__center\">\r\n <fb-flow-canvas\r\n class=\"fb-main__canvas\"\r\n [selectedName]=\"selectedName()\"\r\n [selectedNames]=\"selectedNames()\"\r\n [selectedGroupName]=\"selectedGroupName()\"\r\n [outline]=\"outline()\"\r\n [isEditable]=\"isEditable()\"\r\n (selectionChange)=\"onSelectionChange($event)\"\r\n (nodeOpened)=\"onNodeOpened($event)\"\r\n (nodeRemoveRequested)=\"onRemoveNode($event)\"\r\n (nodeDuplicateRequested)=\"onDuplicateNode($event)\"\r\n (elementDropped)=\"onElementDropped($event)\"\r\n (groupSelected)=\"onGroupSelected($event)\"\r\n (groupRemoveRequested)=\"onGroupRemoved($event)\"\r\n />\r\n\r\n @if (showProblems()) {\r\n <fb-problems-panel\r\n class=\"fb-main__problems\"\r\n (elementFocused)=\"revealIssueElement($event)\"\r\n (elementOpened)=\"openIssueElement($event)\"\r\n (closed)=\"toggleProblems()\"\r\n />\r\n } @else {\r\n <button type=\"button\" class=\"fb-main__problems-toggle\" (click)=\"toggleProblems()\">\r\n Problemi\r\n @if (errorCount()) {\r\n <span class=\"fb-main__count fb-main__count--error\">{{ errorCount() }}</span>\r\n }\r\n @if (warningCount()) {\r\n <span class=\"fb-main__count fb-main__count--warn\">{{ warningCount() }}</span>\r\n }\r\n </button>\r\n }\r\n </div>\r\n\r\n <aside class=\"fb-main__side\">\r\n <nav class=\"fb-side__tabs\" aria-label=\"Pannelli\">\r\n @if (!isDialogMode()) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-side__tab\"\r\n [class.fb-side__tab--active]=\"sidePanel() === 'inspector'\"\r\n (click)=\"setPanel('inspector')\"\r\n >\r\n Elemento\r\n </button>\r\n }\r\n <button\r\n type=\"button\"\r\n class=\"fb-side__tab\"\r\n [class.fb-side__tab--active]=\"sidePanel() === 'resources'\"\r\n (click)=\"setPanel('resources')\"\r\n >\r\n Risorse\r\n </button>\r\n <!--\r\n I riquadri (\u00A73.6). Il tab porta il conteggio perche\u2019 e\u2019 l\u2019unico modo di sapere che un\r\n flow ne ha, quando sono chiusi o fuori dalla vista.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-side__tab\"\r\n [class.fb-side__tab--active]=\"sidePanel() === 'groups'\"\r\n (click)=\"setPanel('groups')\"\r\n >\r\n Riquadri\r\n @if (store.groups().length) {\r\n <span class=\"fb-main__count\">{{ store.groups().length }}</span>\r\n }\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-side__tab\"\r\n [class.fb-side__tab--active]=\"sidePanel() === 'versions'\"\r\n (click)=\"setPanel('versions')\"\r\n >\r\n Versioni\r\n </button>\r\n <!--\r\n Il pannello dell\u2019ultima esecuzione. \u00C8 l\u2019unico posto in cui si vede la traccia di un\r\n flow **senza schermate**: l\u00EC il runtime non apre nessuna interfaccia, e senza questo\r\n tab non ci sarebbe niente da guardare.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-side__tab\"\r\n [class.fb-side__tab--active]=\"sidePanel() === 'debug'\"\r\n (click)=\"setPanel('debug')\"\r\n >\r\n Debug\r\n @if (isRunning()) {\r\n <span class=\"fb-side__tab-dot\" aria-label=\"esecuzione in corso\"></span>\r\n }\r\n </button>\r\n </nav>\r\n\r\n <div class=\"fb-side__content\">\r\n @switch (sidePanel()) {\r\n @case ('inspector') {\r\n <fb-element-inspector\r\n [selectedName]=\"selectedName()\"\r\n [initialFieldPath]=\"pendingFieldPath()\"\r\n (removeRequested)=\"onRemoveNode($event)\"\r\n (duplicateRequested)=\"onDuplicateNode($event)\"\r\n (renamed)=\"onNodeRenamed($event)\"\r\n (closed)=\"setPanel('resources')\"\r\n />\r\n }\r\n @case ('resources') {\r\n <fb-resource-panel\r\n [target]=\"resourceTarget()\"\r\n [editing]=\"editingResource()\"\r\n (editRequested)=\"onResourceEditRequested($event)\"\r\n (closed)=\"setPanel('inspector')\"\r\n />\r\n }\r\n @case ('groups') {\r\n <fb-group-panel\r\n [isEditable]=\"isEditable()\"\r\n [selectedNodeNames]=\"selectedNames()\"\r\n [target]=\"groupTarget()\"\r\n (createRequested)=\"createGroup()\"\r\n (memberFocused)=\"focusMember($event)\"\r\n (closed)=\"setPanel('resources')\"\r\n />\r\n }\r\n @case ('versions') {\r\n <fb-version-panel\r\n (versionOpened)=\"openVersion($event)\"\r\n (notice)=\"showNotice($event)\"\r\n (closed)=\"setPanel('inspector')\"\r\n />\r\n }\r\n @case ('debug') {\r\n <!-- Evidenzia sul canvas senza rubare il pannello: la traccia si sta leggendo. -->\r\n <fb-debug-panel\r\n [outcome]=\"shownOutcome()\"\r\n [wasDebug]=\"wasRunInDebug()\"\r\n [isRunning]=\"isRunning()\"\r\n (elementFocused)=\"highlightElement($event)\"\r\n (cleared)=\"clearRunOutcome()\"\r\n (closed)=\"setPanel('inspector')\"\r\n />\r\n }\r\n }\r\n </div>\r\n\r\n <footer class=\"fb-side__footer\">\r\n <label class=\"fb-btn fb-btn--icon\">\r\n Importa JSON\r\n <input type=\"file\" accept=\"application/json,.json\" hidden (change)=\"onFileSelected($event)\" />\r\n </label>\r\n </footer>\r\n </aside>\r\n </div>\r\n\r\n @if (pastePreview(); as preview) {\r\n <!--\r\n L\u2019incolla passa da una conferma perche\u2019 non e\u2019 mai una copia identica: nomi gi\u00E0 presi,\r\n risorse che qui non esistono, riferimenti che restano orfani. Il piano e\u2019 gi\u00E0 calcolato,\r\n la finestra lo mostra e basta.\r\n -->\r\n <fb-paste-dialog\r\n [payload]=\"preview.payload\"\r\n [plan]=\"preview.plan\"\r\n (confirmed)=\"confirmPaste($event)\"\r\n (cancelled)=\"cancelPaste()\"\r\n />\r\n }\r\n\r\n @if (runMode(); as mode) {\r\n <!--\r\n La finestra degli ingressi. Non avvia niente e non chiama niente: raccoglie i valori e li\r\n annuncia con `runRequested`, perche\u2019 l\u2019unico a sapere dove gira il motore e\u2019 l\u2019ospite.\r\n -->\r\n <fb-run-dialog\r\n [mode]=\"mode\"\r\n [flowName]=\"session.flowName()\"\r\n [version]=\"session.version()\"\r\n [isDirty]=\"isDirty()\"\r\n [isBusy]=\"isRunning()\"\r\n (confirmed)=\"confirmRun($event)\"\r\n (cancelled)=\"closeRun()\"\r\n />\r\n }\r\n\r\n @if (editingResource(); as edit) {\r\n <!--\r\n La finestra di una risorsa. Sta qui e non dentro il pannello per una ragione sola: nella\r\n colonna laterale la ritaglierebbe lo scorrimento.\r\n -->\r\n <fb-resource-dialog [edit]=\"edit\" (closed)=\"onResourceEditRequested(null)\" />\r\n }\r\n\r\n @if (isDialogMode() && isDialogOpen() && selectedName()) {\r\n <!-- La dialog sta dentro il builder, non nel body: la libreria e\u2019 innestabile. -->\r\n <fb-element-dialog\r\n [selectedName]=\"selectedName()\"\r\n [initialFieldPath]=\"pendingFieldPath()\"\r\n (closed)=\"closeDialog()\"\r\n (removeRequested)=\"onRemoveNode($event)\"\r\n (duplicateRequested)=\"onDuplicateNode($event)\"\r\n (renamed)=\"onNodeRenamed($event)\"\r\n />\r\n }\r\n</div>\r\n", styles: [":host{display:block;width:100%;height:100%;min-height:0;font:inherit;color:var(--fb-text, #1d2939)}.fb-builder{position:relative;display:flex;flex-direction:column;width:100%;height:100%;min-height:0;background:var(--fb-surface, #fff)}.fb-top{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:9px 14px;border-bottom:1px solid var(--fb-border, #e2e5eb);background:var(--fb-surface, #fff)}.fb-top__identity{min-width:0}.fb-top__label{width:100%;max-width:420px;padding:2px 4px;border:1px solid transparent;border-radius:4px;background:transparent;color:var(--fb-text, #1d2939);font:inherit;font-size:15px;font-weight:600}.fb-top__label:hover:not(:disabled),.fb-top__label:focus-visible{border-color:var(--fb-border, #d6dae1);background:var(--fb-surface, #fff)}.fb-top__meta{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-top:2px}.fb-top__name{width:180px;padding:1px 4px;border:1px solid transparent;border-radius:4px;background:transparent;color:var(--fb-text-muted, #667085);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px}.fb-top__name:hover:not(:disabled),.fb-top__name:focus-visible{border-color:var(--fb-border, #d6dae1)}.fb-top__process{padding:1px 4px;border:1px solid var(--fb-border-subtle, #e6e9ee);border-radius:4px;background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:11px}.fb-top__process--fixed{border-color:transparent;background:var(--fb-surface-sunken, #eef0f4);border-radius:999px;padding:2px 7px;font-weight:600}.fb-top__status,.fb-top__version,.fb-top__dirty{padding:2px 7px;border-radius:999px;background:var(--fb-surface-sunken, #eef0f4);color:var(--fb-text-muted, #6b7086);font-size:10px;font-weight:600}.fb-top__version{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.fb-top__dirty{background:color-mix(in srgb,var(--fb-warning, #b7791f) 16%,transparent);color:var(--fb-warning, #b7791f)}.fb-top__actions{display:flex;flex-wrap:wrap;gap:4px}.fb-banner{display:flex;flex-wrap:wrap;align-items:center;gap:8px;padding:6px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee);background:color-mix(in srgb,var(--fb-accent, #2f6feb) 7%,transparent);font-size:11px}.fb-banner--warn{background:color-mix(in srgb,var(--fb-warning, #b7791f) 12%,transparent);color:var(--fb-warning, #b7791f)}.fb-banner--error{background:color-mix(in srgb,var(--fb-error, #c9372c) 10%,transparent);color:var(--fb-error, #c9372c)}.fb-banner>span{flex:1;min-width:200px}.fb-copy__field{display:flex;align-items:center;gap:6px}.fb-copy__caption{color:var(--fb-text-muted, #667085);white-space:nowrap}.fb-copy__input{width:180px;padding:2px 5px;border:1px solid var(--fb-border, #d6dae1);border-radius:4px;background:var(--fb-surface, #fff);color:var(--fb-text, #1d2939);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px}.fb-copy__hint{flex:1;min-width:180px;color:var(--fb-text-muted, #667085)}.fb-copy__hint--error{color:var(--fb-error, #c9372c)}.fb-main{display:flex;flex:1;min-height:0}.fb-main__palette{flex:0 0 190px;min-width:0}.fb-main__center{display:flex;flex:1;flex-direction:column;min-width:0;min-height:0}.fb-main__canvas{flex:1;min-height:0}.fb-main__problems{flex:0 0 auto;height:220px}.fb-main__problems-toggle{display:flex;align-items:center;gap:6px;padding:4px 12px;border:0;border-top:1px solid var(--fb-border, #d6dae1);background:var(--fb-surface-alt, #f8f9fb);color:var(--fb-text-muted, #667085);font:inherit;font-size:11px;cursor:pointer}.fb-main__count{padding:0 5px;border-radius:8px;background:var(--fb-border, #d6dae1);font-size:9px;font-weight:700}.fb-main__count--error{background:color-mix(in srgb,var(--fb-error, #c9372c) 16%,transparent);color:var(--fb-error, #c9372c)}.fb-main__count--warn{background:color-mix(in srgb,var(--fb-warning, #b7791f) 16%,transparent);color:var(--fb-warning, #b7791f)}.fb-main__side{display:flex;flex-direction:column;flex:0 0 340px;min-width:0;min-height:0;border-left:1px solid var(--fb-border, #d6dae1);background:var(--fb-surface, #fff)}.fb-side__tabs{display:flex;gap:2px;margin:8px 10px;padding:3px;border-radius:var(--fb-radius, 10px);background:var(--fb-surface-sunken, #eef0f4)}.fb-side__tab{flex:1;padding:5px 8px;border:0;border-radius:var(--fb-radius-xs, 6px);background:transparent;color:var(--fb-text-muted, #6b7086);font:inherit;font-size:11px;cursor:pointer;transition:background .12s ease,color .12s ease}.fb-side__tab-dot{display:inline-block;width:6px;height:6px;margin-left:4px;border-radius:50%;background:var(--fb-accent, #2f6feb);vertical-align:middle}.fb-side__tab:hover:not(.fb-side__tab--active){color:var(--fb-text, #1a1c23)}.fb-side__tab--active{background:var(--fb-surface, #fff);box-shadow:var(--fb-shadow-sm, 0 1px 2px rgb(16 24 40 / 6%));color:var(--fb-text, #1a1c23);font-weight:600}.fb-side__content{flex:1;min-height:0;border-top:1px solid var(--fb-border-subtle, #eef0f4)}.fb-side__content>*{height:100%}.fb-side__footer{display:flex;gap:6px;padding:6px 8px;border-top:1px solid var(--fb-border-subtle, #e6e9ee)}@media(max-width:1200px){.fb-main__palette{flex-basis:150px}.fb-main__side{flex-basis:290px}}\n"], dependencies: [{ kind: "component", type: DebugPanelComponent, selector: "fb-debug-panel", inputs: ["outcome", "wasDebug", "isRunning"], outputs: ["closed", "elementFocused", "cleared"] }, { kind: "component", type: ElementDialogComponent, selector: "fb-element-dialog", inputs: ["selectedName", "initialFieldPath"], outputs: ["closed", "removeRequested", "duplicateRequested", "renamed"] }, { kind: "component", type: ElementInspectorComponent, selector: "fb-element-inspector", inputs: ["selectedName", "showHeader", "initialFieldPath"], outputs: ["closed", "removeRequested", "duplicateRequested", "renamed"] }, { kind: "component", type: ElementPaletteComponent, selector: "fb-element-palette", inputs: ["processType"], outputs: ["elementPicked"] }, { kind: "component", type: FlowCanvasComponent, selector: "fb-flow-canvas", inputs: ["selectedName", "selectedGroupName", "selectedNames", "outline", "isEditable"], outputs: ["selectionChange", "nodeOpened", "nodeRemoveRequested", "nodeDuplicateRequested", "groupSelected", "groupRemoveRequested", "elementDropped"] }, { kind: "component", type: FlowSearchComponent, selector: "fb-flow-search", outputs: ["picked"] }, { kind: "component", type: GroupPanelComponent, selector: "fb-group-panel", inputs: ["isEditable", "selectedNodeNames", "target"], outputs: ["closed", "createRequested", "memberFocused"] }, { kind: "component", type: PasteDialogComponent, selector: "fb-paste-dialog", inputs: ["payload", "plan"], outputs: ["confirmed", "cancelled"] }, { kind: "component", type: ProblemsPanelComponent, selector: "fb-problems-panel", outputs: ["elementFocused", "elementOpened", "closed"] }, { kind: "component", type: ResourceDialogComponent, selector: "fb-resource-dialog", inputs: ["edit"], outputs: ["closed"] }, { kind: "component", type: ResourcePanelComponent, selector: "fb-resource-panel", inputs: ["target", "editing"], outputs: ["closed", "editRequested"] }, { kind: "component", type: RunDialogComponent, selector: "fb-run-dialog", inputs: ["mode", "flowName", "version", "isDirty", "isBusy"], outputs: ["confirmed", "cancelled"] }, { kind: "component", type: VersionPanelComponent, selector: "fb-version-panel", outputs: ["closed", "versionOpened", "notice"] }, { kind: "directive", type: SelectValueDirective, selector: "select[fbValue]", inputs: ["fbValue"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
17842
18449
  }
17843
18450
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: FlowBuilderComponent, decorators: [{
17844
18451
  type: Component,
@@ -17864,7 +18471,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImpo
17864
18471
  FormulaValidationService,
17865
18472
  FlowEditorSession,
17866
18473
  FlowLayoutService,
17867
- ], changeDetection: ChangeDetectionStrategy.OnPush, host: { '(keydown)': 'onKeyDown($event)' }, template: "<div class=\"fb-builder\" [attr.data-fb-theme]=\"null\">\r\n <header class=\"fb-top\">\r\n <div class=\"fb-top__identity\">\r\n <input\r\n class=\"fb-top__label\"\r\n [value]=\"document().label || ''\"\r\n placeholder=\"Nome del flow\"\r\n aria-label=\"Nome del flow\"\r\n [disabled]=\"!isEditable()\"\r\n (input)=\"setLabel($any($event.target).value)\"\r\n />\r\n <div class=\"fb-top__meta\">\r\n <input\r\n class=\"fb-top__name\"\r\n [value]=\"document().fullName || ''\"\r\n placeholder=\"NomeTecnico\"\r\n aria-label=\"Nome tecnico del flow\"\r\n [disabled]=\"!isEditable()\"\r\n (input)=\"setFullName($any($event.target).value)\"\r\n />\r\n <select\r\n class=\"fb-top__process\"\r\n [fbValue]=\"document().processType || ''\"\r\n aria-label=\"Tipo di flow\"\r\n [disabled]=\"!isEditable()\"\r\n (change)=\"setProcessType($any($event.target).value)\"\r\n >\r\n <!--\r\n Il segnaposto esiste perche' un `<select>` senza opzione corrispondente non e'\r\n \u00ABvuoto\u00BB: e' a `selectedIndex = -1`, e mostra una casella bianca. Su un flow nuovo\r\n (`processType` non ancora scelto) e' questa la riga che si vede, disabilitata\r\n perche' non e' un valore valido da salvare.\r\n -->\r\n @if (!document().processType) {\r\n <option value=\"\" disabled>\u2014 tipo di flow \u2014</option>\r\n }\r\n @for (type of processTypes(); track type.value) {\r\n <option [value]=\"type.value\">{{ type.label }}</option>\r\n }\r\n <!--\r\n Il tipo del documento che il dizionario non conosce: senza questa opzione la select\r\n resta bianca su un flow che il backend ha salvato con un `processType` fuori\r\n catalogo (o quando `processTypes` manca dalla risposta dei dizionari), e sembra che\r\n l'editor non abbia ricaricato il flow. Va mostrato **senza** riscrivere il\r\n documento: il valore e' del backend, non nostro da correggere.\r\n -->\r\n @if (isProcessTypeOutOfCatalog()) {\r\n <option [value]=\"document().processType\">{{ document().processType }} (fuori catalogo)</option>\r\n }\r\n </select>\r\n <span class=\"fb-top__status\">{{ statusLabel() }}</span>\r\n @if (session.version() != null) {\r\n <span class=\"fb-top__version\">v{{ session.version() }}</span>\r\n }\r\n @if (isDirty()) {\r\n <span class=\"fb-top__dirty\" title=\"Ci sono modifiche non salvate\">modificato</span>\r\n }\r\n </div>\r\n </div>\r\n\r\n <!--\r\n La ricerca **nel documento**: e\u2019 l\u2019unico modo di arrivare a un elemento su un flow che non\r\n sta in una schermata. Non e\u2019 la casella della palette, che filtra i tipi da aggiungere.\r\n -->\r\n <fb-flow-search (picked)=\"onSearchPicked($event)\" />\r\n\r\n <div class=\"fb-top__actions\">\r\n <button type=\"button\" class=\"fb-btn\" [disabled]=\"!canUndo()\" aria-label=\"Annulla\" (click)=\"undo()\">\u21B6</button>\r\n <button type=\"button\" class=\"fb-btn\" [disabled]=\"!canRedo()\" aria-label=\"Ripeti\" (click)=\"redo()\">\u21B7</button>\r\n <button type=\"button\" class=\"fb-btn\" title=\"Ricalcola le posizioni\" (click)=\"autoLayout()\">Riordina</button>\r\n <!--\r\n Un riquadro e\u2019 un commento sul canvas (\u00A73.6): non viene eseguito, quindi il comando sta\r\n qui accanto a \u00ABRiordina\u00BB e non nella palette degli elementi. Nasce attorno a cio\u2019 che e\u2019\r\n selezionato, che e\u2019 il gesto per cui serve.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"!isEditable()\"\r\n [title]=\"\r\n canCopy()\r\n ? 'Racchiudi gli elementi selezionati in un riquadro di commento'\r\n : 'Crea un riquadro di commento vuoto: non cambia il comportamento del flow'\r\n \"\r\n aria-label=\"Nuovo riquadro di raggruppamento\"\r\n (click)=\"createGroup()\"\r\n >\r\n \u2B1A\r\n </button>\r\n\r\n <!--\r\n Copia e incolla stanno **anche** qui e non solo sui tasti: una scorciatoia che nessuno\r\n annuncia non esiste. Il titolo la dice, cos\u00EC si impara usandola una volta.\r\n L\u2019incolla e\u2019 acceso anche con la selezione vuota: cio\u2019 che si incolla sta negli appunti,\r\n e puo\u2019 venire da un altro flow.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"!canCopy()\"\r\n title=\"Copia gli elementi selezionati, con le risorse che usano (Ctrl+C)\"\r\n aria-label=\"Copia gli elementi selezionati\"\r\n (click)=\"copySelection()\"\r\n >\r\n \u29C9\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"!canPaste() || !isEditable()\"\r\n title=\"Incolla gli elementi copiati, anche da un altro flow (Ctrl+V)\"\r\n aria-label=\"Incolla gli elementi copiati\"\r\n (click)=\"startPaste()\"\r\n >\r\n \u2398\r\n </button>\r\n @if (isDialogMode()) {\r\n <!-- Il doppio click sul node fa la stessa cosa, ma non si vede: questo comando s\u00EC. -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"!selectedName()\"\r\n title=\"Apri il dettaglio dell\u2019elemento selezionato\"\r\n (click)=\"openSelectedElement()\"\r\n >\r\n Dettaglio\r\n </button>\r\n }\r\n <button type=\"button\" class=\"fb-btn\" [disabled]=\"isBusy()\" (click)=\"validateNow()\">Valida</button>\r\n\r\n <!--\r\n Esegui e Debug non eseguono niente qui dentro: raccolgono i valori di ingresso e li\r\n consegnano all\u2019applicazione ospite. Si esegue la versione **salvata**, quindi su un flow\r\n mai scritto sono spenti e il titolo dice perche\u2019.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"!canRun()\"\r\n [title]=\"canRun() ? 'Esegui il flow salvato' : 'Salva il flow prima di eseguirlo'\"\r\n (click)=\"openRun('run')\"\r\n >\r\n Esegui\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"!canRun()\"\r\n [title]=\"canRun() ? 'Esegui il flow salvato in debug' : 'Salva il flow prima di eseguirlo'\"\r\n (click)=\"openRun('debug')\"\r\n >\r\n Debug\r\n </button>\r\n\r\n @switch (primaryCommand()) {\r\n @case ('newVersion') {\r\n <!-- Su una versione non modificabile il comando primario e' \"Nuova versione\" (\u00A78.1). -->\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" [disabled]=\"isBusy()\" (click)=\"createNewVersion()\">\r\n Nuova versione\r\n </button>\r\n }\r\n @case ('create') {\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" [disabled]=\"isBusy()\" (click)=\"save()\">\r\n Crea\r\n </button>\r\n }\r\n @default {\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" [disabled]=\"isBusy()\" (click)=\"save()\">\r\n Salva\r\n </button>\r\n }\r\n }\r\n\r\n @if (canDuplicate()) {\r\n <!-- \u00A76.2 \u00ABDuplica\u00BB: il flow sotto un altro nome, alla versione 1. -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"isBusy()\"\r\n title=\"Salva una copia con un altro nome\"\r\n (click)=\"startCopy()\"\r\n >\r\n Duplica\u2026\r\n </button>\r\n }\r\n\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"isBusy() || !canActivate()\"\r\n [title]=\"\r\n canActivate()\r\n ? 'Attiva questa versione'\r\n : 'L\u2019attivazione esige zero errori: correggili nel pannello dei problemi'\r\n \"\r\n (click)=\"activate()\"\r\n >\r\n Attiva\r\n </button>\r\n </div>\r\n </header>\r\n\r\n @if (conflict()) {\r\n <!-- \u00A79.3: qualcun altro ha salvato. Due strade, entrambe offerte. -->\r\n <div class=\"fb-banner fb-banner--warn\" role=\"alert\">\r\n <span>\r\n {{ conflict()?.message }}\r\n @if (conflict()?.conflictingAuthor) {\r\n Ha salvato {{ conflict()?.conflictingAuthor }}.\r\n }\r\n </span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--icon\" (click)=\"reloadAfterConflict()\">Ricarica</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--icon\" (click)=\"saveAsNewVersionAfterConflict()\">\r\n Salva come nuova versione\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" (click)=\"dismissConflict()\">\u00D7</button>\r\n </div>\r\n }\r\n\r\n @if (isCopyOpen()) {\r\n <!--\r\n Il nome si chiede prima di scrivere: e\u2019 l\u2019unico dato che il backend non puo\u2019 inventare, e\r\n un doppione lo rifiuta con AlreadyExists (\u00A76.2). Invio conferma, Esc annulla.\r\n -->\r\n <div class=\"fb-banner fb-copy\" role=\"group\" aria-label=\"Duplica il flow\">\r\n <label class=\"fb-copy__field\">\r\n <span class=\"fb-copy__caption\">Nome tecnico della copia</span>\r\n <input\r\n #copyNameInput\r\n class=\"fb-copy__input\"\r\n [value]=\"copyName()\"\r\n placeholder=\"NomeTecnico_Copia\"\r\n (input)=\"setCopyName($any($event.target).value)\"\r\n (keydown.enter)=\"confirmCopy()\"\r\n (keydown.escape)=\"cancelCopy()\"\r\n />\r\n </label>\r\n <label class=\"fb-copy__field\">\r\n <span class=\"fb-copy__caption\">Nome visibile</span>\r\n <input\r\n class=\"fb-copy__input\"\r\n [value]=\"copyLabel()\"\r\n placeholder=\"(facoltativo)\"\r\n (input)=\"setCopyLabel($any($event.target).value)\"\r\n (keydown.enter)=\"confirmCopy()\"\r\n (keydown.escape)=\"cancelCopy()\"\r\n />\r\n </label>\r\n <span class=\"fb-copy__hint\" [class.fb-copy__hint--error]=\"!!copyNameProblem()\">\r\n {{ copyNameProblem() || copyHint() }}\r\n </span>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--primary fb-btn--icon\"\r\n [disabled]=\"isBusy() || !!copyNameProblem()\"\r\n (click)=\"confirmCopy()\"\r\n >\r\n Duplica\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" (click)=\"cancelCopy()\">Annulla</button>\r\n </div>\r\n }\r\n\r\n @if (notice()) {\r\n <div\r\n class=\"fb-banner\"\r\n [class.fb-banner--error]=\"notice()?.kind === 'error'\"\r\n role=\"status\"\r\n >\r\n <span>{{ notice()?.message }}</span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" aria-label=\"Chiudi\" (click)=\"dismissNotice()\">\r\n \u00D7\r\n </button>\r\n </div>\r\n }\r\n\r\n @if (hasScreensInAutoLaunched()) {\r\n <div class=\"fb-banner fb-banner--error\">\r\n Un flow AutoLaunched che contiene screen e\u2019 un errore di validazione: non c\u2019e\u2019 nessuno a cui mostrarli.\r\n </div>\r\n }\r\n @if (isOrchestrationWithoutStages()) {\r\n <div class=\"fb-banner fb-banner--warn\">\r\n Un flow Orchestration senza stage di orchestrazione viene segnalato dalla validazione\r\n (ORCHESTRATION_WITHOUT_STAGES).\r\n </div>\r\n }\r\n @if (hasNoScreensInScreenFlow()) {\r\n <div class=\"fb-banner fb-banner--warn\">\r\n Un flow di tipo Screen senza nessuno screen viene segnalato dalla validazione.\r\n </div>\r\n }\r\n @if (!isEditable()) {\r\n <div class=\"fb-banner\">\r\n Questa versione e\u2019 in sola lettura: per modificarla creane una nuova.\r\n </div>\r\n }\r\n\r\n <div class=\"fb-main\">\r\n <aside class=\"fb-main__palette\">\r\n <fb-element-palette [processType]=\"document().processType\" (elementPicked)=\"onElementPicked($event)\" />\r\n </aside>\r\n\r\n <div class=\"fb-main__center\">\r\n <fb-flow-canvas\r\n class=\"fb-main__canvas\"\r\n [selectedName]=\"selectedName()\"\r\n [selectedNames]=\"selectedNames()\"\r\n [selectedGroupName]=\"selectedGroupName()\"\r\n [outline]=\"outline()\"\r\n [isEditable]=\"isEditable()\"\r\n (selectionChange)=\"onSelectionChange($event)\"\r\n (nodeOpened)=\"onNodeOpened($event)\"\r\n (nodeRemoveRequested)=\"onRemoveNode($event)\"\r\n (nodeDuplicateRequested)=\"onDuplicateNode($event)\"\r\n (elementDropped)=\"onElementDropped($event)\"\r\n (groupSelected)=\"onGroupSelected($event)\"\r\n (groupRemoveRequested)=\"onGroupRemoved($event)\"\r\n />\r\n\r\n @if (showProblems()) {\r\n <fb-problems-panel\r\n class=\"fb-main__problems\"\r\n (elementFocused)=\"revealIssueElement($event)\"\r\n (elementOpened)=\"openIssueElement($event)\"\r\n (closed)=\"toggleProblems()\"\r\n />\r\n } @else {\r\n <button type=\"button\" class=\"fb-main__problems-toggle\" (click)=\"toggleProblems()\">\r\n Problemi\r\n @if (errorCount()) {\r\n <span class=\"fb-main__count fb-main__count--error\">{{ errorCount() }}</span>\r\n }\r\n @if (warningCount()) {\r\n <span class=\"fb-main__count fb-main__count--warn\">{{ warningCount() }}</span>\r\n }\r\n </button>\r\n }\r\n </div>\r\n\r\n <aside class=\"fb-main__side\">\r\n <nav class=\"fb-side__tabs\" aria-label=\"Pannelli\">\r\n @if (!isDialogMode()) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-side__tab\"\r\n [class.fb-side__tab--active]=\"sidePanel() === 'inspector'\"\r\n (click)=\"setPanel('inspector')\"\r\n >\r\n Elemento\r\n </button>\r\n }\r\n <button\r\n type=\"button\"\r\n class=\"fb-side__tab\"\r\n [class.fb-side__tab--active]=\"sidePanel() === 'resources'\"\r\n (click)=\"setPanel('resources')\"\r\n >\r\n Risorse\r\n </button>\r\n <!--\r\n I riquadri (\u00A73.6). Il tab porta il conteggio perche\u2019 e\u2019 l\u2019unico modo di sapere che un\r\n flow ne ha, quando sono chiusi o fuori dalla vista.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-side__tab\"\r\n [class.fb-side__tab--active]=\"sidePanel() === 'groups'\"\r\n (click)=\"setPanel('groups')\"\r\n >\r\n Riquadri\r\n @if (store.groups().length) {\r\n <span class=\"fb-main__count\">{{ store.groups().length }}</span>\r\n }\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-side__tab\"\r\n [class.fb-side__tab--active]=\"sidePanel() === 'versions'\"\r\n (click)=\"setPanel('versions')\"\r\n >\r\n Versioni\r\n </button>\r\n <!--\r\n Il pannello dell\u2019ultima esecuzione. \u00C8 l\u2019unico posto in cui si vede la traccia di un\r\n flow **senza schermate**: l\u00EC il runtime non apre nessuna interfaccia, e senza questo\r\n tab non ci sarebbe niente da guardare.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-side__tab\"\r\n [class.fb-side__tab--active]=\"sidePanel() === 'debug'\"\r\n (click)=\"setPanel('debug')\"\r\n >\r\n Debug\r\n @if (isRunning()) {\r\n <span class=\"fb-side__tab-dot\" aria-label=\"esecuzione in corso\"></span>\r\n }\r\n </button>\r\n </nav>\r\n\r\n <div class=\"fb-side__content\">\r\n @switch (sidePanel()) {\r\n @case ('inspector') {\r\n <fb-element-inspector\r\n [selectedName]=\"selectedName()\"\r\n [initialFieldPath]=\"pendingFieldPath()\"\r\n (removeRequested)=\"onRemoveNode($event)\"\r\n (duplicateRequested)=\"onDuplicateNode($event)\"\r\n (renamed)=\"onNodeRenamed($event)\"\r\n (closed)=\"setPanel('resources')\"\r\n />\r\n }\r\n @case ('resources') {\r\n <fb-resource-panel\r\n [target]=\"resourceTarget()\"\r\n [editing]=\"editingResource()\"\r\n (editRequested)=\"onResourceEditRequested($event)\"\r\n (closed)=\"setPanel('inspector')\"\r\n />\r\n }\r\n @case ('groups') {\r\n <fb-group-panel\r\n [isEditable]=\"isEditable()\"\r\n [selectedNodeNames]=\"selectedNames()\"\r\n [target]=\"groupTarget()\"\r\n (createRequested)=\"createGroup()\"\r\n (memberFocused)=\"focusMember($event)\"\r\n (closed)=\"setPanel('resources')\"\r\n />\r\n }\r\n @case ('versions') {\r\n <fb-version-panel\r\n (versionOpened)=\"openVersion($event)\"\r\n (notice)=\"showNotice($event)\"\r\n (closed)=\"setPanel('inspector')\"\r\n />\r\n }\r\n @case ('debug') {\r\n <!-- Evidenzia sul canvas senza rubare il pannello: la traccia si sta leggendo. -->\r\n <fb-debug-panel\r\n [outcome]=\"shownOutcome()\"\r\n [wasDebug]=\"wasRunInDebug()\"\r\n [isRunning]=\"isRunning()\"\r\n (elementFocused)=\"highlightElement($event)\"\r\n (cleared)=\"clearRunOutcome()\"\r\n (closed)=\"setPanel('inspector')\"\r\n />\r\n }\r\n }\r\n </div>\r\n\r\n <footer class=\"fb-side__footer\">\r\n <label class=\"fb-btn fb-btn--icon\">\r\n Importa JSON\r\n <input type=\"file\" accept=\"application/json,.json\" hidden (change)=\"onFileSelected($event)\" />\r\n </label>\r\n </footer>\r\n </aside>\r\n </div>\r\n\r\n @if (pastePreview(); as preview) {\r\n <!--\r\n L\u2019incolla passa da una conferma perche\u2019 non e\u2019 mai una copia identica: nomi gi\u00E0 presi,\r\n risorse che qui non esistono, riferimenti che restano orfani. Il piano e\u2019 gi\u00E0 calcolato,\r\n la finestra lo mostra e basta.\r\n -->\r\n <fb-paste-dialog\r\n [payload]=\"preview.payload\"\r\n [plan]=\"preview.plan\"\r\n (confirmed)=\"confirmPaste($event)\"\r\n (cancelled)=\"cancelPaste()\"\r\n />\r\n }\r\n\r\n @if (runMode(); as mode) {\r\n <!--\r\n La finestra degli ingressi. Non avvia niente e non chiama niente: raccoglie i valori e li\r\n annuncia con `runRequested`, perche\u2019 l\u2019unico a sapere dove gira il motore e\u2019 l\u2019ospite.\r\n -->\r\n <fb-run-dialog\r\n [mode]=\"mode\"\r\n [flowName]=\"session.flowName()\"\r\n [version]=\"session.version()\"\r\n [isDirty]=\"isDirty()\"\r\n [isBusy]=\"isRunning()\"\r\n (confirmed)=\"confirmRun($event)\"\r\n (cancelled)=\"closeRun()\"\r\n />\r\n }\r\n\r\n @if (editingResource(); as edit) {\r\n <!--\r\n La finestra di una risorsa. Sta qui e non dentro il pannello per una ragione sola: nella\r\n colonna laterale la ritaglierebbe lo scorrimento.\r\n -->\r\n <fb-resource-dialog [edit]=\"edit\" (closed)=\"onResourceEditRequested(null)\" />\r\n }\r\n\r\n @if (isDialogMode() && isDialogOpen() && selectedName()) {\r\n <!-- La dialog sta dentro il builder, non nel body: la libreria e\u2019 innestabile. -->\r\n <fb-element-dialog\r\n [selectedName]=\"selectedName()\"\r\n [initialFieldPath]=\"pendingFieldPath()\"\r\n (closed)=\"closeDialog()\"\r\n (removeRequested)=\"onRemoveNode($event)\"\r\n (duplicateRequested)=\"onDuplicateNode($event)\"\r\n (renamed)=\"onNodeRenamed($event)\"\r\n />\r\n }\r\n</div>\r\n", styles: [":host{display:block;width:100%;height:100%;min-height:0;font:inherit;color:var(--fb-text, #1d2939)}.fb-builder{position:relative;display:flex;flex-direction:column;width:100%;height:100%;min-height:0;background:var(--fb-surface, #fff)}.fb-top{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:9px 14px;border-bottom:1px solid var(--fb-border, #e2e5eb);background:var(--fb-surface, #fff)}.fb-top__identity{min-width:0}.fb-top__label{width:100%;max-width:420px;padding:2px 4px;border:1px solid transparent;border-radius:4px;background:transparent;color:var(--fb-text, #1d2939);font:inherit;font-size:15px;font-weight:600}.fb-top__label:hover:not(:disabled),.fb-top__label:focus-visible{border-color:var(--fb-border, #d6dae1);background:var(--fb-surface, #fff)}.fb-top__meta{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-top:2px}.fb-top__name{width:180px;padding:1px 4px;border:1px solid transparent;border-radius:4px;background:transparent;color:var(--fb-text-muted, #667085);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px}.fb-top__name:hover:not(:disabled),.fb-top__name:focus-visible{border-color:var(--fb-border, #d6dae1)}.fb-top__process{padding:1px 4px;border:1px solid var(--fb-border-subtle, #e6e9ee);border-radius:4px;background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:11px}.fb-top__status,.fb-top__version,.fb-top__dirty{padding:2px 7px;border-radius:999px;background:var(--fb-surface-sunken, #eef0f4);color:var(--fb-text-muted, #6b7086);font-size:10px;font-weight:600}.fb-top__version{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.fb-top__dirty{background:color-mix(in srgb,var(--fb-warning, #b7791f) 16%,transparent);color:var(--fb-warning, #b7791f)}.fb-top__actions{display:flex;flex-wrap:wrap;gap:4px}.fb-banner{display:flex;flex-wrap:wrap;align-items:center;gap:8px;padding:6px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee);background:color-mix(in srgb,var(--fb-accent, #2f6feb) 7%,transparent);font-size:11px}.fb-banner--warn{background:color-mix(in srgb,var(--fb-warning, #b7791f) 12%,transparent);color:var(--fb-warning, #b7791f)}.fb-banner--error{background:color-mix(in srgb,var(--fb-error, #c9372c) 10%,transparent);color:var(--fb-error, #c9372c)}.fb-banner>span{flex:1;min-width:200px}.fb-copy__field{display:flex;align-items:center;gap:6px}.fb-copy__caption{color:var(--fb-text-muted, #667085);white-space:nowrap}.fb-copy__input{width:180px;padding:2px 5px;border:1px solid var(--fb-border, #d6dae1);border-radius:4px;background:var(--fb-surface, #fff);color:var(--fb-text, #1d2939);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px}.fb-copy__hint{flex:1;min-width:180px;color:var(--fb-text-muted, #667085)}.fb-copy__hint--error{color:var(--fb-error, #c9372c)}.fb-main{display:flex;flex:1;min-height:0}.fb-main__palette{flex:0 0 190px;min-width:0}.fb-main__center{display:flex;flex:1;flex-direction:column;min-width:0;min-height:0}.fb-main__canvas{flex:1;min-height:0}.fb-main__problems{flex:0 0 auto;height:220px}.fb-main__problems-toggle{display:flex;align-items:center;gap:6px;padding:4px 12px;border:0;border-top:1px solid var(--fb-border, #d6dae1);background:var(--fb-surface-alt, #f8f9fb);color:var(--fb-text-muted, #667085);font:inherit;font-size:11px;cursor:pointer}.fb-main__count{padding:0 5px;border-radius:8px;background:var(--fb-border, #d6dae1);font-size:9px;font-weight:700}.fb-main__count--error{background:color-mix(in srgb,var(--fb-error, #c9372c) 16%,transparent);color:var(--fb-error, #c9372c)}.fb-main__count--warn{background:color-mix(in srgb,var(--fb-warning, #b7791f) 16%,transparent);color:var(--fb-warning, #b7791f)}.fb-main__side{display:flex;flex-direction:column;flex:0 0 340px;min-width:0;min-height:0;border-left:1px solid var(--fb-border, #d6dae1);background:var(--fb-surface, #fff)}.fb-side__tabs{display:flex;gap:2px;margin:8px 10px;padding:3px;border-radius:var(--fb-radius, 10px);background:var(--fb-surface-sunken, #eef0f4)}.fb-side__tab{flex:1;padding:5px 8px;border:0;border-radius:var(--fb-radius-xs, 6px);background:transparent;color:var(--fb-text-muted, #6b7086);font:inherit;font-size:11px;cursor:pointer;transition:background .12s ease,color .12s ease}.fb-side__tab-dot{display:inline-block;width:6px;height:6px;margin-left:4px;border-radius:50%;background:var(--fb-accent, #2f6feb);vertical-align:middle}.fb-side__tab:hover:not(.fb-side__tab--active){color:var(--fb-text, #1a1c23)}.fb-side__tab--active{background:var(--fb-surface, #fff);box-shadow:var(--fb-shadow-sm, 0 1px 2px rgb(16 24 40 / 6%));color:var(--fb-text, #1a1c23);font-weight:600}.fb-side__content{flex:1;min-height:0;border-top:1px solid var(--fb-border-subtle, #eef0f4)}.fb-side__content>*{height:100%}.fb-side__footer{display:flex;gap:6px;padding:6px 8px;border-top:1px solid var(--fb-border-subtle, #e6e9ee)}@media(max-width:1200px){.fb-main__palette{flex-basis:150px}.fb-main__side{flex-basis:290px}}\n"] }]
18474
+ ], changeDetection: ChangeDetectionStrategy.OnPush, host: { '(keydown)': 'onKeyDown($event)' }, template: "<div class=\"fb-builder\" [attr.data-fb-theme]=\"null\">\r\n <header class=\"fb-top\">\r\n <div class=\"fb-top__identity\">\r\n <input\r\n class=\"fb-top__label\"\r\n [value]=\"document().label || ''\"\r\n placeholder=\"Nome del flow\"\r\n aria-label=\"Nome del flow\"\r\n [disabled]=\"!isEditable()\"\r\n (input)=\"setLabel($any($event.target).value)\"\r\n />\r\n <div class=\"fb-top__meta\">\r\n <input\r\n class=\"fb-top__name\"\r\n [value]=\"document().fullName || ''\"\r\n placeholder=\"NomeTecnico\"\r\n aria-label=\"Nome tecnico del flow\"\r\n [disabled]=\"!isEditable()\"\r\n (input)=\"setFullName($any($event.target).value)\"\r\n />\r\n <!--\r\n Il tipo di flow si sceglie **alla creazione** e poi non si tocca pi\u00F9: decide quali\r\n globali esistono (\u00A74.1), quali elementi sono ammessi e come il motore esegue il flow,\r\n e cambiarlo su un documento gi\u00E0 scritto lo lascia pieno di riferimenti che non\r\n esistono. Su un flow salvato resta quindi un\u2019etichetta, non un comando.\r\n -->\r\n @if (session.isNew()) {\r\n <select\r\n class=\"fb-top__process\"\r\n [fbValue]=\"document().processType || ''\"\r\n aria-label=\"Tipo di flow\"\r\n [disabled]=\"!isEditable()\"\r\n (change)=\"setProcessType($any($event.target).value)\"\r\n >\r\n <!--\r\n Il segnaposto esiste perche' un `<select>` senza opzione corrispondente non e'\r\n \u00ABvuoto\u00BB: e' a `selectedIndex = -1`, e mostra una casella bianca. Su un flow nuovo\r\n (`processType` non ancora scelto) e' questa la riga che si vede, disabilitata\r\n perche' non e' un valore valido da salvare.\r\n -->\r\n @if (!document().processType) {\r\n <option value=\"\" disabled>\u2014 tipo di flow \u2014</option>\r\n }\r\n @for (type of processTypes(); track type.value) {\r\n <option [value]=\"type.value\">{{ type.label }}</option>\r\n }\r\n <!--\r\n Il tipo del documento che il dizionario non conosce: senza questa opzione la select\r\n resta bianca su un flow che il backend ha salvato con un `processType` fuori\r\n catalogo (o quando `processTypes` manca dalla risposta dei dizionari), e sembra che\r\n l'editor non abbia ricaricato il flow. Va mostrato **senza** riscrivere il\r\n documento: il valore e' del backend, non nostro da correggere.\r\n -->\r\n @if (isProcessTypeOutOfCatalog()) {\r\n <option [value]=\"document().processType\">{{ document().processType }} (fuori catalogo)</option>\r\n }\r\n </select>\r\n } @else {\r\n <span class=\"fb-top__process fb-top__process--fixed\" title=\"Il tipo di flow si sceglie alla creazione\">\r\n {{ processTypeLabel() }}\r\n </span>\r\n }\r\n <span class=\"fb-top__status\">{{ statusLabel() }}</span>\r\n @if (session.version() != null) {\r\n <span class=\"fb-top__version\">v{{ session.version() }}</span>\r\n }\r\n @if (isDirty()) {\r\n <span class=\"fb-top__dirty\" title=\"Ci sono modifiche non salvate\">modificato</span>\r\n }\r\n </div>\r\n </div>\r\n\r\n <!--\r\n La ricerca **nel documento**: e\u2019 l\u2019unico modo di arrivare a un elemento su un flow che non\r\n sta in una schermata. Non e\u2019 la casella della palette, che filtra i tipi da aggiungere.\r\n -->\r\n <fb-flow-search (picked)=\"onSearchPicked($event)\" />\r\n\r\n <div class=\"fb-top__actions\">\r\n <button type=\"button\" class=\"fb-btn\" [disabled]=\"!canUndo()\" aria-label=\"Annulla\" (click)=\"undo()\">\u21B6</button>\r\n <button type=\"button\" class=\"fb-btn\" [disabled]=\"!canRedo()\" aria-label=\"Ripeti\" (click)=\"redo()\">\u21B7</button>\r\n <button type=\"button\" class=\"fb-btn\" title=\"Ricalcola le posizioni\" (click)=\"autoLayout()\">Riordina</button>\r\n <!--\r\n Un riquadro e\u2019 un commento sul canvas (\u00A73.6): non viene eseguito, quindi il comando sta\r\n qui accanto a \u00ABRiordina\u00BB e non nella palette degli elementi. Nasce attorno a cio\u2019 che e\u2019\r\n selezionato, che e\u2019 il gesto per cui serve.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"!isEditable()\"\r\n [title]=\"\r\n canCopy()\r\n ? 'Racchiudi gli elementi selezionati in un riquadro di commento'\r\n : 'Crea un riquadro di commento vuoto: non cambia il comportamento del flow'\r\n \"\r\n aria-label=\"Nuovo riquadro di raggruppamento\"\r\n (click)=\"createGroup()\"\r\n >\r\n \u2B1A\r\n </button>\r\n\r\n <!--\r\n Copia e incolla stanno **anche** qui e non solo sui tasti: una scorciatoia che nessuno\r\n annuncia non esiste. Il titolo la dice, cos\u00EC si impara usandola una volta.\r\n L\u2019incolla e\u2019 acceso anche con la selezione vuota: cio\u2019 che si incolla sta negli appunti,\r\n e puo\u2019 venire da un altro flow.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"!canCopy()\"\r\n title=\"Copia gli elementi selezionati, con le risorse che usano (Ctrl+C)\"\r\n aria-label=\"Copia gli elementi selezionati\"\r\n (click)=\"copySelection()\"\r\n >\r\n \u29C9\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"!canPaste() || !isEditable()\"\r\n title=\"Incolla gli elementi copiati, anche da un altro flow (Ctrl+V)\"\r\n aria-label=\"Incolla gli elementi copiati\"\r\n (click)=\"startPaste()\"\r\n >\r\n \u2398\r\n </button>\r\n @if (isDialogMode()) {\r\n <!-- Il doppio click sul node fa la stessa cosa, ma non si vede: questo comando s\u00EC. -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"!selectedName()\"\r\n title=\"Apri il dettaglio dell\u2019elemento selezionato\"\r\n (click)=\"openSelectedElement()\"\r\n >\r\n Dettaglio\r\n </button>\r\n }\r\n <button type=\"button\" class=\"fb-btn\" [disabled]=\"isBusy()\" (click)=\"validateNow()\">Valida</button>\r\n\r\n <!--\r\n Esegui e Debug non eseguono niente qui dentro: raccolgono i valori di ingresso e li\r\n consegnano all\u2019applicazione ospite. Si esegue la versione **salvata**, quindi su un flow\r\n mai scritto sono spenti e il titolo dice perche\u2019.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"!canRun()\"\r\n [title]=\"canRun() ? 'Esegui il flow salvato' : 'Salva il flow prima di eseguirlo'\"\r\n (click)=\"openRun('run')\"\r\n >\r\n Esegui\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"!canRun()\"\r\n [title]=\"canRun() ? 'Esegui il flow salvato in debug' : 'Salva il flow prima di eseguirlo'\"\r\n (click)=\"openRun('debug')\"\r\n >\r\n Debug\r\n </button>\r\n\r\n @switch (primaryCommand()) {\r\n @case ('newVersion') {\r\n <!-- Su una versione non modificabile il comando primario e' \"Nuova versione\" (\u00A78.1). -->\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" [disabled]=\"isBusy()\" (click)=\"createNewVersion()\">\r\n Nuova versione\r\n </button>\r\n }\r\n @case ('create') {\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" [disabled]=\"isBusy()\" (click)=\"save()\">\r\n Crea\r\n </button>\r\n }\r\n @default {\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" [disabled]=\"isBusy()\" (click)=\"save()\">\r\n Salva\r\n </button>\r\n }\r\n }\r\n\r\n @if (canDuplicate()) {\r\n <!-- \u00A76.2 \u00ABDuplica\u00BB: il flow sotto un altro nome, alla versione 1. -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"isBusy()\"\r\n title=\"Salva una copia con un altro nome\"\r\n (click)=\"startCopy()\"\r\n >\r\n Duplica\u2026\r\n </button>\r\n }\r\n\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"isBusy() || !canActivate()\"\r\n [title]=\"\r\n canActivate()\r\n ? 'Attiva questa versione'\r\n : 'L\u2019attivazione esige zero errori: correggili nel pannello dei problemi'\r\n \"\r\n (click)=\"activate()\"\r\n >\r\n Attiva\r\n </button>\r\n </div>\r\n </header>\r\n\r\n @if (conflict()) {\r\n <!-- \u00A79.3: qualcun altro ha salvato. Due strade, entrambe offerte. -->\r\n <div class=\"fb-banner fb-banner--warn\" role=\"alert\">\r\n <span>\r\n {{ conflict()?.message }}\r\n @if (conflict()?.conflictingAuthor) {\r\n Ha salvato {{ conflict()?.conflictingAuthor }}.\r\n }\r\n </span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--icon\" (click)=\"reloadAfterConflict()\">Ricarica</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--icon\" (click)=\"saveAsNewVersionAfterConflict()\">\r\n Salva come nuova versione\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" (click)=\"dismissConflict()\">\u00D7</button>\r\n </div>\r\n }\r\n\r\n @if (isCopyOpen()) {\r\n <!--\r\n Il nome si chiede prima di scrivere: e\u2019 l\u2019unico dato che il backend non puo\u2019 inventare, e\r\n un doppione lo rifiuta con AlreadyExists (\u00A76.2). Invio conferma, Esc annulla.\r\n -->\r\n <div class=\"fb-banner fb-copy\" role=\"group\" aria-label=\"Duplica il flow\">\r\n <label class=\"fb-copy__field\">\r\n <span class=\"fb-copy__caption\">Nome tecnico della copia</span>\r\n <input\r\n #copyNameInput\r\n class=\"fb-copy__input\"\r\n [value]=\"copyName()\"\r\n placeholder=\"NomeTecnico_Copia\"\r\n (input)=\"setCopyName($any($event.target).value)\"\r\n (keydown.enter)=\"confirmCopy()\"\r\n (keydown.escape)=\"cancelCopy()\"\r\n />\r\n </label>\r\n <label class=\"fb-copy__field\">\r\n <span class=\"fb-copy__caption\">Nome visibile</span>\r\n <input\r\n class=\"fb-copy__input\"\r\n [value]=\"copyLabel()\"\r\n placeholder=\"(facoltativo)\"\r\n (input)=\"setCopyLabel($any($event.target).value)\"\r\n (keydown.enter)=\"confirmCopy()\"\r\n (keydown.escape)=\"cancelCopy()\"\r\n />\r\n </label>\r\n <span class=\"fb-copy__hint\" [class.fb-copy__hint--error]=\"!!copyNameProblem()\">\r\n {{ copyNameProblem() || copyHint() }}\r\n </span>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--primary fb-btn--icon\"\r\n [disabled]=\"isBusy() || !!copyNameProblem()\"\r\n (click)=\"confirmCopy()\"\r\n >\r\n Duplica\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" (click)=\"cancelCopy()\">Annulla</button>\r\n </div>\r\n }\r\n\r\n @if (notice()) {\r\n <div\r\n class=\"fb-banner\"\r\n [class.fb-banner--error]=\"notice()?.kind === 'error'\"\r\n role=\"status\"\r\n >\r\n <span>{{ notice()?.message }}</span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" aria-label=\"Chiudi\" (click)=\"dismissNotice()\">\r\n \u00D7\r\n </button>\r\n </div>\r\n }\r\n\r\n @if (hasScreensInAutoLaunched()) {\r\n <div class=\"fb-banner fb-banner--error\">\r\n Un flow AutoLaunched che contiene screen e\u2019 un errore di validazione: non c\u2019e\u2019 nessuno a cui mostrarli.\r\n </div>\r\n }\r\n @if (isOrchestrationWithoutStages()) {\r\n <div class=\"fb-banner fb-banner--warn\">\r\n Un flow Orchestration senza stage di orchestrazione viene segnalato dalla validazione\r\n (ORCHESTRATION_WITHOUT_STAGES).\r\n </div>\r\n }\r\n @if (hasNoScreensInScreenFlow()) {\r\n <div class=\"fb-banner fb-banner--warn\">\r\n Un flow di tipo Screen senza nessuno screen viene segnalato dalla validazione.\r\n </div>\r\n }\r\n @if (!isEditable()) {\r\n <div class=\"fb-banner\">\r\n Questa versione e\u2019 in sola lettura: per modificarla creane una nuova.\r\n </div>\r\n }\r\n\r\n <div class=\"fb-main\">\r\n <aside class=\"fb-main__palette\">\r\n <fb-element-palette [processType]=\"document().processType\" (elementPicked)=\"onElementPicked($event)\" />\r\n </aside>\r\n\r\n <div class=\"fb-main__center\">\r\n <fb-flow-canvas\r\n class=\"fb-main__canvas\"\r\n [selectedName]=\"selectedName()\"\r\n [selectedNames]=\"selectedNames()\"\r\n [selectedGroupName]=\"selectedGroupName()\"\r\n [outline]=\"outline()\"\r\n [isEditable]=\"isEditable()\"\r\n (selectionChange)=\"onSelectionChange($event)\"\r\n (nodeOpened)=\"onNodeOpened($event)\"\r\n (nodeRemoveRequested)=\"onRemoveNode($event)\"\r\n (nodeDuplicateRequested)=\"onDuplicateNode($event)\"\r\n (elementDropped)=\"onElementDropped($event)\"\r\n (groupSelected)=\"onGroupSelected($event)\"\r\n (groupRemoveRequested)=\"onGroupRemoved($event)\"\r\n />\r\n\r\n @if (showProblems()) {\r\n <fb-problems-panel\r\n class=\"fb-main__problems\"\r\n (elementFocused)=\"revealIssueElement($event)\"\r\n (elementOpened)=\"openIssueElement($event)\"\r\n (closed)=\"toggleProblems()\"\r\n />\r\n } @else {\r\n <button type=\"button\" class=\"fb-main__problems-toggle\" (click)=\"toggleProblems()\">\r\n Problemi\r\n @if (errorCount()) {\r\n <span class=\"fb-main__count fb-main__count--error\">{{ errorCount() }}</span>\r\n }\r\n @if (warningCount()) {\r\n <span class=\"fb-main__count fb-main__count--warn\">{{ warningCount() }}</span>\r\n }\r\n </button>\r\n }\r\n </div>\r\n\r\n <aside class=\"fb-main__side\">\r\n <nav class=\"fb-side__tabs\" aria-label=\"Pannelli\">\r\n @if (!isDialogMode()) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-side__tab\"\r\n [class.fb-side__tab--active]=\"sidePanel() === 'inspector'\"\r\n (click)=\"setPanel('inspector')\"\r\n >\r\n Elemento\r\n </button>\r\n }\r\n <button\r\n type=\"button\"\r\n class=\"fb-side__tab\"\r\n [class.fb-side__tab--active]=\"sidePanel() === 'resources'\"\r\n (click)=\"setPanel('resources')\"\r\n >\r\n Risorse\r\n </button>\r\n <!--\r\n I riquadri (\u00A73.6). Il tab porta il conteggio perche\u2019 e\u2019 l\u2019unico modo di sapere che un\r\n flow ne ha, quando sono chiusi o fuori dalla vista.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-side__tab\"\r\n [class.fb-side__tab--active]=\"sidePanel() === 'groups'\"\r\n (click)=\"setPanel('groups')\"\r\n >\r\n Riquadri\r\n @if (store.groups().length) {\r\n <span class=\"fb-main__count\">{{ store.groups().length }}</span>\r\n }\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-side__tab\"\r\n [class.fb-side__tab--active]=\"sidePanel() === 'versions'\"\r\n (click)=\"setPanel('versions')\"\r\n >\r\n Versioni\r\n </button>\r\n <!--\r\n Il pannello dell\u2019ultima esecuzione. \u00C8 l\u2019unico posto in cui si vede la traccia di un\r\n flow **senza schermate**: l\u00EC il runtime non apre nessuna interfaccia, e senza questo\r\n tab non ci sarebbe niente da guardare.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-side__tab\"\r\n [class.fb-side__tab--active]=\"sidePanel() === 'debug'\"\r\n (click)=\"setPanel('debug')\"\r\n >\r\n Debug\r\n @if (isRunning()) {\r\n <span class=\"fb-side__tab-dot\" aria-label=\"esecuzione in corso\"></span>\r\n }\r\n </button>\r\n </nav>\r\n\r\n <div class=\"fb-side__content\">\r\n @switch (sidePanel()) {\r\n @case ('inspector') {\r\n <fb-element-inspector\r\n [selectedName]=\"selectedName()\"\r\n [initialFieldPath]=\"pendingFieldPath()\"\r\n (removeRequested)=\"onRemoveNode($event)\"\r\n (duplicateRequested)=\"onDuplicateNode($event)\"\r\n (renamed)=\"onNodeRenamed($event)\"\r\n (closed)=\"setPanel('resources')\"\r\n />\r\n }\r\n @case ('resources') {\r\n <fb-resource-panel\r\n [target]=\"resourceTarget()\"\r\n [editing]=\"editingResource()\"\r\n (editRequested)=\"onResourceEditRequested($event)\"\r\n (closed)=\"setPanel('inspector')\"\r\n />\r\n }\r\n @case ('groups') {\r\n <fb-group-panel\r\n [isEditable]=\"isEditable()\"\r\n [selectedNodeNames]=\"selectedNames()\"\r\n [target]=\"groupTarget()\"\r\n (createRequested)=\"createGroup()\"\r\n (memberFocused)=\"focusMember($event)\"\r\n (closed)=\"setPanel('resources')\"\r\n />\r\n }\r\n @case ('versions') {\r\n <fb-version-panel\r\n (versionOpened)=\"openVersion($event)\"\r\n (notice)=\"showNotice($event)\"\r\n (closed)=\"setPanel('inspector')\"\r\n />\r\n }\r\n @case ('debug') {\r\n <!-- Evidenzia sul canvas senza rubare il pannello: la traccia si sta leggendo. -->\r\n <fb-debug-panel\r\n [outcome]=\"shownOutcome()\"\r\n [wasDebug]=\"wasRunInDebug()\"\r\n [isRunning]=\"isRunning()\"\r\n (elementFocused)=\"highlightElement($event)\"\r\n (cleared)=\"clearRunOutcome()\"\r\n (closed)=\"setPanel('inspector')\"\r\n />\r\n }\r\n }\r\n </div>\r\n\r\n <footer class=\"fb-side__footer\">\r\n <label class=\"fb-btn fb-btn--icon\">\r\n Importa JSON\r\n <input type=\"file\" accept=\"application/json,.json\" hidden (change)=\"onFileSelected($event)\" />\r\n </label>\r\n </footer>\r\n </aside>\r\n </div>\r\n\r\n @if (pastePreview(); as preview) {\r\n <!--\r\n L\u2019incolla passa da una conferma perche\u2019 non e\u2019 mai una copia identica: nomi gi\u00E0 presi,\r\n risorse che qui non esistono, riferimenti che restano orfani. Il piano e\u2019 gi\u00E0 calcolato,\r\n la finestra lo mostra e basta.\r\n -->\r\n <fb-paste-dialog\r\n [payload]=\"preview.payload\"\r\n [plan]=\"preview.plan\"\r\n (confirmed)=\"confirmPaste($event)\"\r\n (cancelled)=\"cancelPaste()\"\r\n />\r\n }\r\n\r\n @if (runMode(); as mode) {\r\n <!--\r\n La finestra degli ingressi. Non avvia niente e non chiama niente: raccoglie i valori e li\r\n annuncia con `runRequested`, perche\u2019 l\u2019unico a sapere dove gira il motore e\u2019 l\u2019ospite.\r\n -->\r\n <fb-run-dialog\r\n [mode]=\"mode\"\r\n [flowName]=\"session.flowName()\"\r\n [version]=\"session.version()\"\r\n [isDirty]=\"isDirty()\"\r\n [isBusy]=\"isRunning()\"\r\n (confirmed)=\"confirmRun($event)\"\r\n (cancelled)=\"closeRun()\"\r\n />\r\n }\r\n\r\n @if (editingResource(); as edit) {\r\n <!--\r\n La finestra di una risorsa. Sta qui e non dentro il pannello per una ragione sola: nella\r\n colonna laterale la ritaglierebbe lo scorrimento.\r\n -->\r\n <fb-resource-dialog [edit]=\"edit\" (closed)=\"onResourceEditRequested(null)\" />\r\n }\r\n\r\n @if (isDialogMode() && isDialogOpen() && selectedName()) {\r\n <!-- La dialog sta dentro il builder, non nel body: la libreria e\u2019 innestabile. -->\r\n <fb-element-dialog\r\n [selectedName]=\"selectedName()\"\r\n [initialFieldPath]=\"pendingFieldPath()\"\r\n (closed)=\"closeDialog()\"\r\n (removeRequested)=\"onRemoveNode($event)\"\r\n (duplicateRequested)=\"onDuplicateNode($event)\"\r\n (renamed)=\"onNodeRenamed($event)\"\r\n />\r\n }\r\n</div>\r\n", styles: [":host{display:block;width:100%;height:100%;min-height:0;font:inherit;color:var(--fb-text, #1d2939)}.fb-builder{position:relative;display:flex;flex-direction:column;width:100%;height:100%;min-height:0;background:var(--fb-surface, #fff)}.fb-top{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:9px 14px;border-bottom:1px solid var(--fb-border, #e2e5eb);background:var(--fb-surface, #fff)}.fb-top__identity{min-width:0}.fb-top__label{width:100%;max-width:420px;padding:2px 4px;border:1px solid transparent;border-radius:4px;background:transparent;color:var(--fb-text, #1d2939);font:inherit;font-size:15px;font-weight:600}.fb-top__label:hover:not(:disabled),.fb-top__label:focus-visible{border-color:var(--fb-border, #d6dae1);background:var(--fb-surface, #fff)}.fb-top__meta{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-top:2px}.fb-top__name{width:180px;padding:1px 4px;border:1px solid transparent;border-radius:4px;background:transparent;color:var(--fb-text-muted, #667085);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px}.fb-top__name:hover:not(:disabled),.fb-top__name:focus-visible{border-color:var(--fb-border, #d6dae1)}.fb-top__process{padding:1px 4px;border:1px solid var(--fb-border-subtle, #e6e9ee);border-radius:4px;background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:11px}.fb-top__process--fixed{border-color:transparent;background:var(--fb-surface-sunken, #eef0f4);border-radius:999px;padding:2px 7px;font-weight:600}.fb-top__status,.fb-top__version,.fb-top__dirty{padding:2px 7px;border-radius:999px;background:var(--fb-surface-sunken, #eef0f4);color:var(--fb-text-muted, #6b7086);font-size:10px;font-weight:600}.fb-top__version{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.fb-top__dirty{background:color-mix(in srgb,var(--fb-warning, #b7791f) 16%,transparent);color:var(--fb-warning, #b7791f)}.fb-top__actions{display:flex;flex-wrap:wrap;gap:4px}.fb-banner{display:flex;flex-wrap:wrap;align-items:center;gap:8px;padding:6px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee);background:color-mix(in srgb,var(--fb-accent, #2f6feb) 7%,transparent);font-size:11px}.fb-banner--warn{background:color-mix(in srgb,var(--fb-warning, #b7791f) 12%,transparent);color:var(--fb-warning, #b7791f)}.fb-banner--error{background:color-mix(in srgb,var(--fb-error, #c9372c) 10%,transparent);color:var(--fb-error, #c9372c)}.fb-banner>span{flex:1;min-width:200px}.fb-copy__field{display:flex;align-items:center;gap:6px}.fb-copy__caption{color:var(--fb-text-muted, #667085);white-space:nowrap}.fb-copy__input{width:180px;padding:2px 5px;border:1px solid var(--fb-border, #d6dae1);border-radius:4px;background:var(--fb-surface, #fff);color:var(--fb-text, #1d2939);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px}.fb-copy__hint{flex:1;min-width:180px;color:var(--fb-text-muted, #667085)}.fb-copy__hint--error{color:var(--fb-error, #c9372c)}.fb-main{display:flex;flex:1;min-height:0}.fb-main__palette{flex:0 0 190px;min-width:0}.fb-main__center{display:flex;flex:1;flex-direction:column;min-width:0;min-height:0}.fb-main__canvas{flex:1;min-height:0}.fb-main__problems{flex:0 0 auto;height:220px}.fb-main__problems-toggle{display:flex;align-items:center;gap:6px;padding:4px 12px;border:0;border-top:1px solid var(--fb-border, #d6dae1);background:var(--fb-surface-alt, #f8f9fb);color:var(--fb-text-muted, #667085);font:inherit;font-size:11px;cursor:pointer}.fb-main__count{padding:0 5px;border-radius:8px;background:var(--fb-border, #d6dae1);font-size:9px;font-weight:700}.fb-main__count--error{background:color-mix(in srgb,var(--fb-error, #c9372c) 16%,transparent);color:var(--fb-error, #c9372c)}.fb-main__count--warn{background:color-mix(in srgb,var(--fb-warning, #b7791f) 16%,transparent);color:var(--fb-warning, #b7791f)}.fb-main__side{display:flex;flex-direction:column;flex:0 0 340px;min-width:0;min-height:0;border-left:1px solid var(--fb-border, #d6dae1);background:var(--fb-surface, #fff)}.fb-side__tabs{display:flex;gap:2px;margin:8px 10px;padding:3px;border-radius:var(--fb-radius, 10px);background:var(--fb-surface-sunken, #eef0f4)}.fb-side__tab{flex:1;padding:5px 8px;border:0;border-radius:var(--fb-radius-xs, 6px);background:transparent;color:var(--fb-text-muted, #6b7086);font:inherit;font-size:11px;cursor:pointer;transition:background .12s ease,color .12s ease}.fb-side__tab-dot{display:inline-block;width:6px;height:6px;margin-left:4px;border-radius:50%;background:var(--fb-accent, #2f6feb);vertical-align:middle}.fb-side__tab:hover:not(.fb-side__tab--active){color:var(--fb-text, #1a1c23)}.fb-side__tab--active{background:var(--fb-surface, #fff);box-shadow:var(--fb-shadow-sm, 0 1px 2px rgb(16 24 40 / 6%));color:var(--fb-text, #1a1c23);font-weight:600}.fb-side__content{flex:1;min-height:0;border-top:1px solid var(--fb-border-subtle, #eef0f4)}.fb-side__content>*{height:100%}.fb-side__footer{display:flex;gap:6px;padding:6px 8px;border-top:1px solid var(--fb-border-subtle, #e6e9ee)}@media(max-width:1200px){.fb-main__palette{flex-basis:150px}.fb-main__side{flex-basis:290px}}\n"] }]
17868
18475
  }], ctorParameters: () => [], propDecorators: { flowName: [{ type: i0.Input, args: [{ isSignal: true, alias: "flowName", required: false }] }], version: [{ type: i0.Input, args: [{ isSignal: true, alias: "version", required: false }] }], author: [{ type: i0.Input, args: [{ isSignal: true, alias: "author", required: false }] }], defaultProcessType: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultProcessType", required: false }] }], inspectorMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "inspectorMode", required: false }] }], runOutcome: [{ type: i0.Input, args: [{ isSignal: true, alias: "runOutcome", required: false }] }], isRunning: [{ type: i0.Input, args: [{ isSignal: true, alias: "isRunning", required: false }] }], saved: [{ type: i0.Output, args: ["saved"] }], activated: [{ type: i0.Output, args: ["activated"] }], closeRequested: [{ type: i0.Output, args: ["closeRequested"] }], runRequested: [{ type: i0.Output, args: ["runRequested"] }], canvas: [{ type: i0.ViewChild, args: [i0.forwardRef(() => FlowCanvasComponent), { isSignal: true }] }], search: [{ type: i0.ViewChild, args: [i0.forwardRef(() => FlowSearchComponent), { isSignal: true }] }], copyNameInput: [{ type: i0.ViewChild, args: ['copyNameInput', { isSignal: true }] }] } });
17869
18476
 
17870
18477
  /**