@esfaenza/flow-builder 20.3.56 → 20.3.58

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.
@@ -119,7 +119,7 @@ const FLOW_ERROR_FALLBACK_MESSAGE = {
119
119
  FlowNotFound: 'Il flow non esiste piu’.',
120
120
  VersionNotFound: 'La versione richiesta non esiste.',
121
121
  AlreadyExists: 'Esiste già un flow con questo nome.',
122
- NotEditable: 'Questa versione non e’ modificabile: creane una nuova.',
122
+ NotEditable: 'Questa e’ la versione attiva: per modificarla creane una nuova, per eliminarla va prima disattivata.',
123
123
  VersionConflict: 'Qualcun altro ha salvato questa versione nel frattempo.',
124
124
  MissingService: 'Funzione non configurata su questo ambiente.',
125
125
  RegistrationFailed: 'L’infrastruttura non ha accettato la presa in carico del flow: la versione non e’ attiva.',
@@ -174,7 +174,7 @@ class FlowBuilderApi {
174
174
  void request;
175
175
  return missing('cloneFlow');
176
176
  }
177
- /** `DELETE /flows/{name}/versions/{v}` — rifiutata sulla versione attiva. */
177
+ /** `DELETE /flows/{name}/versions/{v}` — rifiutata sulla versione attiva, con `NotEditable` (§10). */
178
178
  deleteVersion(flowName, version) {
179
179
  void flowName;
180
180
  void version;
@@ -1689,9 +1689,11 @@ function isFieldResource(field, entry) {
1689
1689
  * esiste una volta per elemento della collection: il loro nome non individua alcun valore.
1690
1690
  *
1691
1691
  * Da qui discende tutto cio' che l'editor fa di diverso lì dentro. Non sono risorse del flow, non si
1692
- * propongono fra i riferimenti, non innescano screen action e non sono destinazioni: nominarli e'
1693
- * `SCREEN_FIELD_IN_REPEATER`. Dentro una riga si nomina il **cursore** (`itemReference`), che e' una
1694
- * variabile vera — `PuntoCorrente.PodId`, mai `Luce_POD`.
1692
+ * propongono fra i riferimenti e non sono destinazioni: nominarli e' `SCREEN_FIELD_IN_REPEATER`. Dentro
1693
+ * una riga si nomina il **cursore** (`itemReference`), che e' una variabile vera — `PuntoCorrente.PodId`,
1694
+ * mai `Luce_POD`. L'unico posto in cui il nome di un campo di riga si scrive e' `triggerFieldName`: un
1695
+ * trigger nomina un **campo della schermata**, non un riferimento, e l'action gira per riga
1696
+ * ({@link triggerRowSectionOf}).
1695
1697
  *
1696
1698
  * Cio' che invece **non** cambia: il nome resta unico, perche' e' l'indirizzo con cui il frontend rende
1697
1699
  * il campo, quindi un omonimo di una variabile e' `NAME_DUPLICATED` come sempre e `screenFieldNames` li
@@ -1700,8 +1702,8 @@ function isFieldResource(field, entry) {
1700
1702
  function repeatedFieldNames(definition, typeOf) {
1701
1703
  const names = new Set();
1702
1704
  for (const screen of definition?.dynamicScreens ?? []) {
1703
- walkInside(screen.fields, typeOf, false, (field, inside) => {
1704
- if (inside && field.name) {
1705
+ walkRows(screen.fields, typeOf, undefined, (field, section) => {
1706
+ if (section && field.name) {
1705
1707
  names.add(field.name);
1706
1708
  }
1707
1709
  });
@@ -1710,13 +1712,13 @@ function repeatedFieldNames(definition, typeOf) {
1710
1712
  }
1711
1713
  /**
1712
1714
  * I campi di una schermata che sono **risorse del flow**: raccolgono un valore e non stanno dentro una
1713
- * riga. Sono gli unici da proporre come riferimento, come innesco di una screen action e come
1714
- * destinazione dei suoi output.
1715
+ * riga. Sono gli unici da proporre come riferimento e come destinazione degli output di una screen
1716
+ * action; come **innesco** no — lì valgono anche quelli di riga, e l'elenco e' {@link triggerableFieldsOf}.
1715
1717
  */
1716
1718
  function resourceFieldsOf(screen, typeOf) {
1717
1719
  const fields = [];
1718
- walkInside(screen?.fields, typeOf, false, (field, inside) => {
1719
- if (!inside && isFieldResource(field, typeOf(field))) {
1720
+ walkRows(screen?.fields, typeOf, undefined, (field, section) => {
1721
+ if (!section && isFieldResource(field, typeOf(field))) {
1720
1722
  fields.push(field);
1721
1723
  }
1722
1724
  });
@@ -1739,12 +1741,201 @@ function repeatingSectionOf(screen, path, typeOf) {
1739
1741
  }
1740
1742
  return undefined;
1741
1743
  }
1742
- /** Cammina l'albero portandosi dietro «sto dentro una riga»: le sezioni **non si annidano** (§5.2). */
1743
- function walkInside(fields, typeOf, inside, visit) {
1744
+ /**
1745
+ * Cammina l'albero portandosi dietro la sezione ripetibile in cui si sta, se c'e'. Una sola: le sezioni
1746
+ * **non si annidano** (§5.2), quindi quella piu' esterna e' anche l'unica.
1747
+ */
1748
+ function walkRows(fields, typeOf, section, visit) {
1744
1749
  for (const field of fields ?? []) {
1745
- visit(field, inside);
1746
- walkInside(field.fields, typeOf, inside || typeOf(field)?.isRepeatingSection === true, visit);
1750
+ visit(field, section);
1751
+ const inner = section ?? (typeOf(field)?.isRepeatingSection === true ? field : undefined);
1752
+ walkRows(field.fields, typeOf, inner, visit);
1753
+ }
1754
+ }
1755
+ /** Le sezioni ripetibili di una schermata, in ordine di visita. */
1756
+ function repeatingSectionsOf(screen, typeOf) {
1757
+ return allFieldsOf(screen).filter((field) => typeOf(field)?.isRepeatingSection === true);
1758
+ }
1759
+ /**
1760
+ * §5.2 — i campi che possono innescare una screen action: quelli che raccolgono un valore, **compresi**
1761
+ * quelli dentro una riga. È l'unica domanda a cui un campo di riga risponde come uno di primo livello:
1762
+ * `triggerFieldName` nomina un campo della schermata e non un riferimento, e il trigger non deve dire
1763
+ * *quale* riga osserva — l'action gira una volta per ogni riga in cui il campo e' cambiato.
1764
+ *
1765
+ * Il criterio del valore e' lo stesso di {@link isFieldResource} sul solo campo: un contenitore non
1766
+ * cambia, un `ComponentInstance` non ha un valore proprio.
1767
+ */
1768
+ function triggerableFieldsOf(screen, typeOf) {
1769
+ const result = [];
1770
+ walkRows(screen?.fields, typeOf, undefined, (field, section) => {
1771
+ if (isFieldResource(field, typeOf(field))) {
1772
+ result.push(section ? { field, section } : { field });
1773
+ }
1774
+ });
1775
+ return result;
1776
+ }
1777
+ function triggerScopeOf(screen, trigger, typeOf) {
1778
+ const name = trigger.triggerFieldName?.trim().toLowerCase();
1779
+ if (!name) {
1780
+ return { kind: 'screen' };
1781
+ }
1782
+ const found = triggerableFieldsOf(screen, typeOf).find((entry) => entry.field.name?.toLowerCase() === name);
1783
+ if (!found) {
1784
+ return { kind: 'unknown' };
1785
+ }
1786
+ return found.section ? { kind: 'row', section: found.section } : { kind: 'screen' };
1787
+ }
1788
+ /** I trigger che invocano un'action, col loro indice: il confronto dei nomi e' case-insensitive (§3.3). */
1789
+ function triggersOf(screen, action) {
1790
+ const name = action.name?.trim().toLowerCase();
1791
+ if (!name) {
1792
+ return [];
1793
+ }
1794
+ const indexes = [];
1795
+ (screen?.triggers ?? []).forEach((trigger, index) => {
1796
+ if (trigger.screenActionName?.trim().toLowerCase() === name) {
1797
+ indexes.push(index);
1798
+ }
1799
+ });
1800
+ return indexes;
1801
+ }
1802
+ /**
1803
+ * Le sezioni per le cui righe gira un'action, e se gira anche per la schermata. Piu' trigger possono
1804
+ * invocare la stessa action, quindi le due cose non si escludono — ed e' proprio lì che le destinazioni
1805
+ * giuste per l'uno diventano sbagliate per l'altro ({@link screenActionRowTargetIssues}).
1806
+ */
1807
+ function screenActionScopes(screen, action, typeOf) {
1808
+ const triggers = screen?.triggers ?? [];
1809
+ const sections = [];
1810
+ let runsForScreen = false;
1811
+ for (const index of triggersOf(screen, action)) {
1812
+ const scope = triggerScopeOf(screen, triggers[index], typeOf);
1813
+ if (scope.kind === 'screen') {
1814
+ runsForScreen = true;
1815
+ }
1816
+ else if (scope.kind === 'row' && !sections.includes(scope.section)) {
1817
+ sections.push(scope.section);
1818
+ }
1819
+ }
1820
+ return { sections, runsForScreen };
1821
+ }
1822
+ /**
1823
+ * §5.2, *Una screen action per ogni riga* — le destinazioni che un'action non puo' raggiungere, dato
1824
+ * **chi** la invoca. Il rilievo e' dell'output e non del trigger perche' e' lì che si corregge, ma nomina
1825
+ * il trigger: la stessa destinazione e' giusta per un innesco e sbagliata per un altro.
1826
+ *
1827
+ * Con un trigger di **riga** non si scrive nel cursore intero (la riga si scrive un membro alla volta),
1828
+ * nella collection delle righe, nelle righe di un'altra sezione, ne' con l'output automatico (una risorsa
1829
+ * sola per tutte le righe — l'ultima che ha girato). Con un trigger di **primo livello** non si scrive nel
1830
+ * cursore di una sezione: fuori dalla riga il cursore non porta alcuna riga, e il valore andrebbe perso.
1831
+ * Riscrivere l'intera collection da lì resta lecito — e' il codice fiscale che porta con se' i punti gia'
1832
+ * censiti.
1833
+ *
1834
+ * Il nome di un campo della riga come destinazione non e' qui: e' `SCREEN_FIELD_IN_REPEATER`, come
1835
+ * ovunque, perche' il difetto e' un altro — quel nome non e' un riferimento.
1836
+ *
1837
+ * La usano i due capi, l'inspector mentre si scrive e il validatore della demo: due copie divergono, e
1838
+ * la si scopre sull'ambiente in cui l'action gira davvero.
1839
+ */
1840
+ function screenActionRowTargetIssues(screen, action, typeOf) {
1841
+ const triggers = screen?.triggers ?? [];
1842
+ const sections = repeatingSectionsOf(screen, typeOf);
1843
+ const cursorOf = (section) => section.itemReference?.trim().toLowerCase() ?? '';
1844
+ const collectionOf = (section) => section.collectionReference?.trim().toLowerCase() ?? '';
1845
+ const sectionOwning = (root) => sections.find((section) => root && (cursorOf(section) === root || collectionOf(section) === root));
1846
+ const issues = [];
1847
+ const seen = new Set();
1848
+ const push = (issue) => {
1849
+ const key = `${issue.outputIndex ?? 'auto'}|${issue.reason}`;
1850
+ if (!seen.has(key)) {
1851
+ seen.add(key);
1852
+ issues.push(issue);
1853
+ }
1854
+ };
1855
+ for (const triggerIndex of triggersOf(screen, action)) {
1856
+ const trigger = triggers[triggerIndex];
1857
+ const scope = triggerScopeOf(screen, trigger, typeOf);
1858
+ if (scope.kind === 'unknown') {
1859
+ continue;
1860
+ }
1861
+ const who = describeTrigger(trigger, triggerIndex);
1862
+ if (scope.kind === 'row') {
1863
+ const section = scope.section;
1864
+ const sectionName = section.name ?? 'la sezione';
1865
+ const cursor = section.itemReference ?? 'il cursore';
1866
+ if (action.storeOutputAutomatically === true) {
1867
+ push({
1868
+ triggerIndex,
1869
+ reason: 'automaticOutput',
1870
+ message: `Con ${who} l’action gira per ogni riga di «${sectionName}»: l’output automatico sarebbe una risorsa sola per tutte le righe — l’ultima che ha girato. Gli output vanno sui membri del cursore, «${cursor}.<membro>».`,
1871
+ });
1872
+ }
1873
+ (action.outputParameters ?? []).forEach((output, outputIndex) => {
1874
+ const target = output.assignToReference?.trim();
1875
+ if (!target) {
1876
+ return;
1877
+ }
1878
+ const dot = target.indexOf('.');
1879
+ const root = (dot < 0 ? target : target.slice(0, dot)).toLowerCase();
1880
+ const owner = sectionOwning(root);
1881
+ if (!owner) {
1882
+ return;
1883
+ }
1884
+ if (owner !== section) {
1885
+ push({
1886
+ outputIndex,
1887
+ target,
1888
+ triggerIndex,
1889
+ reason: 'otherSection',
1890
+ message: `«${target}» appartiene alle righe di «${owner.name ?? 'un’altra sezione'}», ma con ${who} l’action gira per le righe di «${sectionName}»: da lì le righe dell’altra sezione non si raggiungono.`,
1891
+ });
1892
+ }
1893
+ else if (root === collectionOf(section)) {
1894
+ push({
1895
+ outputIndex,
1896
+ target,
1897
+ triggerIndex,
1898
+ reason: 'collection',
1899
+ message: `«${target}» e’ la collection delle righe di «${sectionName}»: con ${who} l’action gira per una riga sola, e la scrive attraverso il cursore — «${cursor}.<membro>».`,
1900
+ });
1901
+ }
1902
+ else if (dot < 0) {
1903
+ push({
1904
+ outputIndex,
1905
+ target,
1906
+ triggerIndex,
1907
+ reason: 'wholeCursor',
1908
+ message: `«${target}» e’ il cursore intero di «${sectionName}»: la riga si scrive un membro alla volta — «${cursor}.<membro>» (lo richiede ${who}).`,
1909
+ });
1910
+ }
1911
+ });
1912
+ continue;
1913
+ }
1914
+ (action.outputParameters ?? []).forEach((output, outputIndex) => {
1915
+ const target = output.assignToReference?.trim();
1916
+ if (!target) {
1917
+ return;
1918
+ }
1919
+ const dot = target.indexOf('.');
1920
+ const root = (dot < 0 ? target : target.slice(0, dot)).toLowerCase();
1921
+ const owner = sections.find((section) => root && cursorOf(section) === root);
1922
+ if (owner) {
1923
+ push({
1924
+ outputIndex,
1925
+ target,
1926
+ triggerIndex,
1927
+ reason: 'cursorOutsideRow',
1928
+ message: `«${target}» scrive nel cursore di «${owner.name ?? 'una sezione ripetibile'}», ma ${who} non gira dentro una riga: fuori dalla riga il cursore non porta alcuna riga, e il valore andrebbe perso.`,
1929
+ });
1930
+ }
1931
+ });
1747
1932
  }
1933
+ return issues;
1934
+ }
1935
+ /** Come un rilievo nomina un trigger: per posizione e campo, che e' cio' che il form mostra. */
1936
+ function describeTrigger(trigger, index) {
1937
+ const field = trigger.triggerFieldName?.trim();
1938
+ return field ? `il trigger ${index + 1} (su «${field}»)` : `il trigger ${index + 1} (al caricamento)`;
1748
1939
  }
1749
1940
  // ---------------------------------------------------------------------------
1750
1941
  // Mutazioni (lavorano sulla copia di lavoro dello store)
@@ -4905,8 +5096,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.32", ngImpo
4905
5096
  * FRONTEND.md §6.2, §8, §9, §11.
4906
5097
  *
4907
5098
  * Tre responsabilita' che stanno bene insieme perche' condividono lo stesso stato:
4908
- * - il ciclo di vita: si modifica una bozza; su una versione non modificabile il comando
4909
- * e' "Nuova versione", non "Salva" (§8.1);
5099
+ * - il ciclo di vita: si modifica tutto tranne la versione attiva, e lì il comando e' "Nuova
5100
+ * versione", non "Salva" (§8, regola 1);
4910
5101
  * - la concorrenza ottimistica: `updatedAt` conservato al caricamento e rimandato come
4911
5102
  * `expectedUpdatedAt` al salvataggio (§9);
4912
5103
  * - la distinzione fra "salvato" e "sano": il salvataggio riesce anche con errori, e
@@ -5006,7 +5197,7 @@ class FlowEditorSession {
5006
5197
  }
5007
5198
  return this.currentVersionSummary()?.isEditable ?? false;
5008
5199
  }, ...(ngDevMode ? [{ debugName: "isEditable" }] : []));
5009
- /** Su una versione non modificabile il comando primario diventa "Nuova versione" (§8.1). */
5200
+ /** Sulla versione attiva il comando primario diventa "Nuova versione" (§8, regola 1). */
5010
5201
  primaryCommand = computed(() => {
5011
5202
  if (this.isNew()) {
5012
5203
  return 'create';
@@ -5014,6 +5205,16 @@ class FlowEditorSession {
5014
5205
  return this.isEditable() ? 'save' : 'newVersion';
5015
5206
  }, ...(ngDevMode ? [{ debugName: "primaryCommand" }] : []));
5016
5207
  isActiveVersion = computed(() => isVersionActive(this.currentVersionSummary()), ...(ngDevMode ? [{ debugName: "isActiveVersion" }] : []));
5208
+ /**
5209
+ * §8, regola 1 — la versione aperta e' una **superata**: si modifica, ma il primo salvataggio va
5210
+ * confermato. Un'esecuzione sospesa nata quando era attiva — una pausa, un'attesa, uno step, una
5211
+ * schermata parcheggiata — riprende sulla definizione modificata, e se il node su cui era ferma e'
5212
+ * stato tolto o rinominato fallisce. Il backend non puo' saperlo al posto nostro.
5213
+ *
5214
+ * Si legge lo `status` della riga, che e' del backend come `isEditable`: salvata, la versione torna
5215
+ * bozza e la domanda smette di porsi da se' al primo `refreshVersions`.
5216
+ */
5217
+ isObsoleteVersion = computed(() => this.currentVersionSummary()?.status === 'Obsolete', ...(ngDevMode ? [{ debugName: "isObsoleteVersion" }] : []));
5017
5218
  /** Attivare esige zero errori (§8.3): il comando resta disabilitato finche' ce ne sono. */
5018
5219
  canActivate = computed(() => !this.isNew() && !this.isActiveVersion() && this.validation.canActivate(), ...(ngDevMode ? [{ debugName: "canActivate" }] : []));
5019
5220
  setAuthor(author) {
@@ -5177,7 +5378,7 @@ class FlowEditorSession {
5177
5378
  // Il nome e' quello con cui abbiamo scritto; la versione la decide il backend — un
5178
5379
  // salvataggio puo' creare una versione nuova — quindi come ripiego vale quella caricata.
5179
5380
  const result = this.normalizeResult(response, { flowName: targetName });
5180
- this.applySaveResult(result);
5381
+ void this.applySaveResult(result);
5181
5382
  return result;
5182
5383
  }
5183
5384
  catch (error) {
@@ -5191,6 +5392,13 @@ class FlowEditorSession {
5191
5392
  // `activate: true` fallisce senza scrivere nulla se ci sono errori (§6.2).
5192
5393
  this.validation.adopt(error.validation);
5193
5394
  }
5395
+ // §10 — `NotEditable` vuol dire che la versione in mano e' **diventata** quella attiva: l'ha
5396
+ // attivata qualcun altro, perche' l'elenco su cui avevamo deciso «Salva» diceva il contrario.
5397
+ // Rileggerlo e' cio' che fa comparire «Nuova versione» al posto di un comando che fallira'
5398
+ // sempre.
5399
+ if (FlowApiError.is(error) && error.category === 'NotEditable') {
5400
+ await this.refreshVersions().catch(() => undefined);
5401
+ }
5194
5402
  // §6.2, §8 — se invece a fallire e' la **presa in carico**, la bozza **resta salvata** e non
5195
5403
  // attivata: non si perde il lavoro, ma nemmeno si puo' credere di essere fermi a prima. Si
5196
5404
  // rilegge l'elenco versioni, adottando il numero se il salvataggio era una creazione.
@@ -5235,6 +5443,11 @@ class FlowEditorSession {
5235
5443
  FlowEditorSession.warned.add(message);
5236
5444
  console.warn(`[flow-builder] ${message}`);
5237
5445
  }
5446
+ /**
5447
+ * Restituisce la rilettura dell'elenco versioni, che parte comunque: chi la aspetta e' chi ha
5448
+ * appena scritto una versione **nuova**, che nell'elenco vecchio non c'e' — e finche' non c'e'
5449
+ * `currentVersionSummary` e' vuoto, cioe' stato «Nuovo» e editor in sola lettura.
5450
+ */
5238
5451
  applySaveResult(result) {
5239
5452
  // Un nome vuoto non e' una rinomina: se la risposta non lo porta si tiene quello con cui
5240
5453
  // stiamo lavorando, altrimenti la sessione perde il flow su cui e' aperta.
@@ -5259,7 +5472,7 @@ class FlowEditorSession {
5259
5472
  }, { silent: true });
5260
5473
  this.documentStore.markSaved();
5261
5474
  this.validation.adopt(result.validation);
5262
- void this.refreshVersions({ adoptVersion: true });
5475
+ return this.refreshVersions({ adoptVersion: true }).catch(() => undefined);
5263
5476
  }
5264
5477
  /**
5265
5478
  * Duplica: scrive il flow **sotto un altro nome** e sposta la sessione sulla copia
@@ -5324,7 +5537,7 @@ class FlowEditorSession {
5324
5537
  definition.label = options.label;
5325
5538
  }, { silent: true });
5326
5539
  }
5327
- this.applySaveResult(result);
5540
+ void this.applySaveResult(result);
5328
5541
  return result;
5329
5542
  }
5330
5543
  catch (error) {
@@ -5400,7 +5613,7 @@ class FlowEditorSession {
5400
5613
  flowName,
5401
5614
  version,
5402
5615
  });
5403
- this.applySaveResult(result);
5616
+ void this.applySaveResult(result);
5404
5617
  return result;
5405
5618
  }
5406
5619
  catch (error) {
@@ -5474,12 +5687,20 @@ class FlowEditorSession {
5474
5687
  this._conflict.set(null);
5475
5688
  await this.open(flowName, version ?? undefined);
5476
5689
  }
5690
+ /** Risoluzione del conflitto, seconda opzione: le modifiche locali in una nuova versione (§9.3). */
5691
+ async saveAsNewVersionAfterConflict() {
5692
+ return this.saveAsNewVersion();
5693
+ }
5477
5694
  /**
5478
- * Risoluzione del conflitto, seconda opzione: salva come nuova versione, conservando
5479
- * le modifiche locali (§9.3). Il salvataggio senza `expectedUpdatedAt` e senza `version`
5480
- * lascia scegliere al backend, che crea una nuova versione se l'ultima non e' toccabile.
5695
+ * Il documento in mano, modifiche comprese, scritto in una versione **nuova**: lascia intatta
5696
+ * quella da cui si era partiti. Serve dove sovrascrivere e' la cosa sbagliata o rischiosa — un
5697
+ * conflitto (§9.3), o una superata su cui ci sono esecuzioni sospese (§8, regola 1).
5698
+ *
5699
+ * Sono due chiamate e non «salva senza `version`»: quella strada sceglie da se' e sovrascrive
5700
+ * l'ultima se e' una bozza (§8), cioe' proprio cio' che qui si vuole evitare. `createVersion`
5701
+ * copia una versione salvata, e il salvataggio subito dopo ci scrive sopra il documento.
5481
5702
  */
5482
- async saveAsNewVersionAfterConflict() {
5703
+ async saveAsNewVersion() {
5483
5704
  const flowName = this._flowName();
5484
5705
  if (!flowName) {
5485
5706
  throw new FlowApiError('InvalidRequest', 'Nessun flow aperto.');
@@ -5488,13 +5709,15 @@ class FlowEditorSession {
5488
5709
  this._conflict.set(null);
5489
5710
  try {
5490
5711
  const created = await this.api.createVersion(flowName, { author: this._author() });
5491
- const result = await this.api.saveFlow(flowName, {
5712
+ const response = await this.api.saveFlow(flowName, {
5492
5713
  definition: this.documentStore.document(),
5493
- version: created.version,
5494
- expectedUpdatedAt: created.updatedAt,
5714
+ version: created?.version,
5715
+ expectedUpdatedAt: created?.updatedAt,
5495
5716
  author: this._author(),
5496
5717
  });
5497
- this.applySaveResult(result);
5718
+ // Il ripiego e' la versione appena creata, non quella aperta: e' lì che il documento e' finito.
5719
+ const result = this.normalizeResult(response, { flowName, version: created?.version ?? null });
5720
+ await this.applySaveResult(result);
5498
5721
  return result;
5499
5722
  }
5500
5723
  finally {
@@ -7525,6 +7748,28 @@ class ReferencePickerComponent {
7525
7748
  * destinazione farebbe attivare un flow che poi fallisce davanti all'utente. L'eccezione sono i
7526
7749
  * campi del record che innesca, che si assegnano (§4.1).
7527
7750
  */
7751
+ /**
7752
+ * In un campo di destinazione, la radice del valore quando e' **nota ma non scrivibile**: sta fra
7753
+ * le radici di `getReferences` e non fra quelle di `getWritableReferences`. E' il caso di chi
7754
+ * scrive a mano `Leggi_Ordine.Stato` — l'output automatico si legge, ma solo le variabili si
7755
+ * assegnano (§4.6, §5.3) — e di una costante o una formula. Senza, finivano in `navigated`, che
7756
+ * dice «non verificato» di cio' che la validazione rifiuta.
7757
+ *
7758
+ * Le globali restano fuori: le giudica {@link isReadOnlyPath}, e i campi di `$Record` si
7759
+ * assegnano. E senza l'elenco scrivibile — in volo, fallito, o un host che risponde vuoto — non
7760
+ * si accusa nessuno: la regola e' quella del validatore, che senza nomi scrivibili tace.
7761
+ */
7762
+ readOnlyRoot = computed(() => {
7763
+ const writable = mergeReferences(this.available(), this.extraReferences());
7764
+ const root = referenceRoot((this.value() ?? '').trim()).toLowerCase();
7765
+ if (!this.writableOnly() || !writable.length || !root || isGlobalReference(root)) {
7766
+ return undefined;
7767
+ }
7768
+ if (writable.some((reference) => referenceRoot(reference.name).toLowerCase() === root)) {
7769
+ return undefined;
7770
+ }
7771
+ return this.roots().find((reference) => reference.name.toLowerCase() === root);
7772
+ }, ...(ngDevMode ? [{ debugName: "readOnlyRoot" }] : []));
7528
7773
  isReadOnlyPath = computed(() => {
7529
7774
  const reference = this.proposalRoot()?.reference;
7530
7775
  if (!reference) {
@@ -7704,6 +7949,9 @@ class ReferencePickerComponent {
7704
7949
  * classe, `FIELD_UNKNOWN` su un'entita', e a differenza di prima si sa per certo;
7705
7950
  * - `memberNotWritable`: il segmento esiste, non e' scrivibile, e questo campo vuole una
7706
7951
  * destinazione: `TARGET_NOT_WRITABLE`;
7952
+ * - `rootNotWritable`: la radice esiste ma non e' una variabile — un output automatico, una
7953
+ * costante, una formula — e questo campo vuole una destinazione: `TARGET_NOT_WRITABLE` sul
7954
+ * nome intero come sui suoi campi (§4.6, §5.3);
7707
7955
  * - `pathUnverified`: la catena si interrompe (`PATH_NOT_VERIFIABLE`): avviso, non errore;
7708
7956
  * - `globalPathUntyped`: percorso globale che porta un contenitore senza dichiararne il tipo:
7709
7957
  * `PATH_NOT_VERIFIABLE`, e la correzione sta nel catalogo dell'host, non nel flow (§4.1);
@@ -7768,6 +8016,9 @@ class ReferencePickerComponent {
7768
8016
  ? 'globalPathUntyped'
7769
8017
  : 'globalPathInvalid';
7770
8018
  }
8019
+ if (this.readOnlyRoot()) {
8020
+ return 'rootNotWritable';
8021
+ }
7771
8022
  // Fra **tutte** le radici: `Leggi_Ordine` non e' un `String`, ma `Leggi_Ordine.Numero` sì, e
7772
8023
  // accusarlo di non esistere sarebbe falso (§4.5).
7773
8024
  if (this.roots().some((reference) => reference.name === root)) {
@@ -7819,6 +8070,14 @@ class ReferencePickerComponent {
7819
8070
  return this.tailIsObject()
7820
8071
  ? 'Questo campo non si scrive ne’ creando ne’ aggiornando un record: assegnarlo e’ TARGET_NOT_WRITABLE.'
7821
8072
  : 'Questo membro e’ calcolato: si legge, ma assegnarlo e’ TARGET_NOT_WRITABLE.';
8073
+ case 'rootNotWritable': {
8074
+ const reference = this.readOnlyRoot();
8075
+ // Il rimedio non e' lo stesso: il risultato di un elemento si puo' mettere in una variabile,
8076
+ // una costante no.
8077
+ return reference?.kind === 'ElementOutput'
8078
+ ? `«${reference.name}» e’ l’output automatico di un elemento: si legge, ma non si assegna, e nemmeno i suoi campi (TARGET_NOT_WRITABLE). Per scriverci, salva il risultato dell’elemento in una variabile e assegna quella.`
8079
+ : `«${reference?.name}» non e’ una variabile: solo le variabili sono scrivibili (TARGET_NOT_WRITABLE).`;
8080
+ }
7822
8081
  case 'pathUnverified':
7823
8082
  return resolution ? pathNotVerifiableMessage(resolution) : null;
7824
8083
  case 'globalPathUntyped':
@@ -7859,7 +8118,7 @@ class ReferencePickerComponent {
7859
8118
  if (state === 'priorValuesMissing') {
7860
8119
  return this.priorValuesSeverity() === 'error';
7861
8120
  }
7862
- return ['memberUnknown', 'memberNotWritable', 'globalPathInvalid'].includes(state);
8121
+ return ['memberUnknown', 'memberNotWritable', 'rootNotWritable', 'globalPathInvalid'].includes(state);
7863
8122
  }, ...(ngDevMode ? [{ debugName: "isHintError" }] : []));
7864
8123
  errorMessage = computed(() => this.loadError(), ...(ngDevMode ? [{ debugName: "errorMessage" }] : []));
7865
8124
  open() {
@@ -11975,6 +12234,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.32", ngImpo
11975
12234
  * `TARGET_NOT_WRITABLE`. Le action e i trigger stanno in **due** elenchi perche' li separa il
11976
12235
  * contratto — piu' trigger possono invocare la stessa action — e si aprono una alla volta,
11977
12236
  * perche' i cataloghi dipendono da `actionType`/`actionName`.
12237
+ * 7. **Un trigger puo' osservare un campo di riga**, e allora l'action gira per ogni riga (§5.2,
12238
+ * *Una screen action per ogni riga*). Da quel momento le destinazioni dell'action dipendono da
12239
+ * **chi** la invoca: dentro la riga si scrive nei membri del cursore, fuori il cursore non porta
12240
+ * nessuna riga. La regola sta in `core/screen-field.util.ts` e la applica anche il validatore
12241
+ * della demo; qui si dice mentre si scrive, e il picker propone il cursore per primo.
11978
12242
  *
11979
12243
  * Sul trascinamento, vedi {@link hitTest}: il bersaglio lo calcoliamo noi dal DOM, non le drop
11980
12244
  * list del CDK.
@@ -13185,13 +13449,13 @@ class DynamicScreenInspectorComponent extends NodeInspectorBase {
13185
13449
  description: 'campo di questa schermata',
13186
13450
  })), ...(ngDevMode ? [{ debugName: "screenFieldTargets" }] : []));
13187
13451
  /**
13188
- * I campi che raccolgono un valore: gli unici che un'action possa riempire, e gli unici che
13189
- * innescano.
13452
+ * I campi che raccolgono un valore e che sono **risorse**: gli unici che un'action possa riempire per
13453
+ * nome, e gli unici a cui agganciare il messaggio di una regola della schermata.
13190
13454
  *
13191
- * **Senza quelli dentro una riga**, che raccolgono un valore ma non sono risorse: un trigger nomina
13192
- * un campo, e il campo di una riga non ne ha uno solo — sarebbe da dichiarare *quale* riga osserva.
13193
- * Un `triggerFieldName` che punta lì e' `SCREEN_FIELD_IN_REPEATER`, e lo stesso vale per
13194
- * l'`assignToReference` di un output (§5.2).
13455
+ * **Senza quelli dentro una riga**, che raccolgono un valore ma non sono risorse: esistono in una
13456
+ * copia per elemento della collection, quindi come destinazione il nome non individua nessuna riga —
13457
+ * lì si scrive nel cursore (`SCREEN_FIELD_IN_REPEATER`, §5.2). Gli **inneschi** invece li comprendono,
13458
+ * e hanno un elenco loro ({@link triggerFieldOptions}).
13195
13459
  */
13196
13460
  valueFieldsOfScreen() {
13197
13461
  return resourceFieldsOf(this.screen(), this.typeOfField);
@@ -13199,12 +13463,82 @@ class DynamicScreenInspectorComponent extends NodeInspectorBase {
13199
13463
  /**
13200
13464
  * I campi proponibili come innesco. Sono **i campi di questa schermata**, non i riferimenti in
13201
13465
  * generale: un trigger che nomina altro e' `SCREEN_TRIGGER_FIELD_UNKNOWN` (§5.2).
13466
+ *
13467
+ * **Compresi quelli di una riga**: `triggerFieldName` nomina un campo e non un riferimento, e l'action
13468
+ * gira per ogni riga in cui quel campo e' cambiato (§5.2, *Una screen action per ogni riga*). La
13469
+ * descrizione dice in quale sezione stanno, perche' da quella scelta cambia dove vanno gli output.
13202
13470
  */
13203
- triggerFieldOptions = computed(() => this.valueFieldsOfScreen().map((field) => ({
13471
+ triggerFieldOptions = computed(() => triggerableFieldsOf(this.screen(), this.typeOfField).map(({ field, section }) => {
13472
+ const type = this.dictionary.screenFieldType(field.fieldType)?.label ?? field.fieldType ?? null;
13473
+ return {
13474
+ name: field.name,
13475
+ label: field.fieldText ?? null,
13476
+ description: section ? `${type ?? 'campo'} · per ogni riga di ${section.name ?? 'una sezione ripetibile'}` : type,
13477
+ };
13478
+ }), ...(ngDevMode ? [{ debugName: "triggerFieldOptions" }] : []));
13479
+ /**
13480
+ * I campi a cui agganciare il messaggio di una regola della schermata: quelli di primo livello. Un
13481
+ * campo di riga esiste in una copia per elemento, e non individua il controllo accanto a cui mostrare
13482
+ * il messaggio (`SCREEN_FIELD_IN_REPEATER`). Era lo stesso elenco degli inneschi finche' i campi di
13483
+ * riga non innescavano niente; da quando lo fanno, i due elenchi sono diversi.
13484
+ */
13485
+ ruleFieldOptions = computed(() => this.valueFieldsOfScreen().map((field) => ({
13204
13486
  name: field.name,
13205
13487
  label: field.fieldText ?? null,
13206
13488
  description: this.dictionary.screenFieldType(field.fieldType)?.label ?? field.fieldType ?? null,
13207
- })), ...(ngDevMode ? [{ debugName: "triggerFieldOptions" }] : []));
13489
+ })), ...(ngDevMode ? [{ debugName: "ruleFieldOptions" }] : []));
13490
+ /**
13491
+ * §5.2 — dove gira l'action di ciascun trigger: per la schermata, o per ogni riga di una sezione. Per
13492
+ * indice, come l'elenco: il template lo legge accanto al trigger, e ricalcolarlo per ognuno a ogni
13493
+ * ciclo di rilevamento camminerebbe l'albero dei campi una volta per trigger.
13494
+ */
13495
+ triggerScopes = computed(() => this.screenTriggers().map((trigger) => triggerScopeOf(this.screen(), trigger, this.typeOfField)), ...(ngDevMode ? [{ debugName: "triggerScopes" }] : []));
13496
+ /** La sezione per le cui righe gira un trigger, se osserva un campo di riga. */
13497
+ triggerRowSection(index) {
13498
+ const scope = this.triggerScopes()[index];
13499
+ return scope?.kind === 'row' ? scope.section : undefined;
13500
+ }
13501
+ /**
13502
+ * Le radici da proporre per prime nelle condizioni di un trigger di riga: il cursore, perche' le
13503
+ * condizioni leggono la riga per cui l'action sta girando (`PuntoLuce.ProductId`), e il nome del campo
13504
+ * lì e' `SCREEN_FIELD_IN_REPEATER` come ovunque.
13505
+ */
13506
+ triggerPreferredRoots(index) {
13507
+ const cursor = this.triggerRowSection(index)?.itemReference;
13508
+ return cursor ? [cursor] : [];
13509
+ }
13510
+ /**
13511
+ * §5.2, *Una screen action per ogni riga* — cio' che di un'action dipende da **chi** la invoca: per le
13512
+ * righe di quali sezioni gira, e quali delle sue destinazioni diventano per questo irraggiungibili
13513
+ * (`SCREEN_ACTION_ROW_TARGET_INVALID`). La regola e' in `core/screen-field.util.ts`, ed e' la stessa
13514
+ * che applica il validatore della demo.
13515
+ */
13516
+ actionRowContexts = computed(() => this.screenActions().map((action) => {
13517
+ const scopes = screenActionScopes(this.screen(), action, this.typeOfField);
13518
+ return {
13519
+ sections: scopes.sections,
13520
+ runsForScreen: scopes.runsForScreen,
13521
+ issues: screenActionRowTargetIssues(this.screen(), action, this.typeOfField),
13522
+ // Gli input e le destinazioni di un'action di riga si scrivono attraverso il cursore: e' la
13523
+ // strada normale, e i membri li propone il picker entrando nella sua classe.
13524
+ preferredRoots: scopes.sections
13525
+ .map((section) => section.itemReference)
13526
+ .filter((cursor) => !!cursor),
13527
+ };
13528
+ }), ...(ngDevMode ? [{ debugName: "actionRowContexts" }] : []));
13529
+ /** Il contesto di riga dell'action a un indice; mai `undefined`, cosi' il template non lo guarda. */
13530
+ actionRowContext(index) {
13531
+ return (this.actionRowContexts()[index] ?? { sections: [], runsForScreen: false, issues: [], preferredRoots: [] });
13532
+ }
13533
+ /**
13534
+ * La spunta dell'output automatico non si propone su un'action che gira per riga: sarebbe una risorsa
13535
+ * sola per tutte le righe, ed e' `SCREEN_ACTION_ROW_TARGET_INVALID` — la stessa ragione per cui sparisce
13536
+ * su un `ComponentInstance` dentro una riga. Resta visibile se e' gia' accesa, perche' e' da lì che la
13537
+ * si spegne.
13538
+ */
13539
+ actionOffersAutomaticOutput(index, action) {
13540
+ return action.storeOutputAutomatically === true || this.actionRowContext(index).sections.length === 0;
13541
+ }
13208
13542
  /** Le action **di questo screen**: un `screenActionName` fuori da qui e' `SCREEN_ACTION_UNKNOWN`. */
13209
13543
  screenActionOptions = computed(() => this.screenActions()
13210
13544
  .filter((action) => action.name)
@@ -13240,8 +13574,10 @@ class DynamicScreenInspectorComponent extends NodeInspectorBase {
13240
13574
  if (!name || trigger.initBehavior) {
13241
13575
  return false;
13242
13576
  }
13243
- const field = this.valueFieldsOfScreen().find((entry) => entry.name === name);
13244
- return !!field?.defaultValue;
13577
+ // Vale anche per un campo di riga: le righe iniziali nascono precompilate dal default, e l'action
13578
+ // gira su ciascuna — come sulla riga aggiunta col "+" (§5.2).
13579
+ const field = triggerableFieldsOf(this.screen(), this.typeOfField).find((entry) => entry.field.name === name);
13580
+ return !!field?.field.defaultValue;
13245
13581
  }
13246
13582
  /**
13247
13583
  * §5.2 — l'unico valore ammesso e' `runOnLoad`. Il `runOnRevisit` della specifica Salesforce e'
@@ -13523,7 +13859,7 @@ class DynamicScreenInspectorComponent extends NodeInspectorBase {
13523
13859
  });
13524
13860
  }
13525
13861
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.32", ngImport: i0, type: DynamicScreenInspectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
13526
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.32", type: DynamicScreenInspectorComponent, isStandalone: true, selector: "fb-dynamic-screen-inspector", inputs: { initialSelection: { classPropertyName: "initialSelection", publicName: "initialSelection", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "composer", first: true, predicate: ["composer"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<div class=\"fb-scr\" #composer>\r\n <!-- ============================================================ sinistra -->\r\n <aside class=\"fb-scr__side\" aria-label=\"Componenti e campi\">\r\n <div class=\"fb-scr__tabs\" role=\"tablist\">\r\n <button\r\n type=\"button\"\r\n role=\"tab\"\r\n class=\"fb-scr__tab\"\r\n [class.fb-scr__tab--active]=\"paletteTab() === 'components'\"\r\n [attr.aria-selected]=\"paletteTab() === 'components'\"\r\n (click)=\"setPaletteTab('components')\"\r\n >\r\n Componenti\r\n </button>\r\n <button\r\n type=\"button\"\r\n role=\"tab\"\r\n class=\"fb-scr__tab\"\r\n [class.fb-scr__tab--active]=\"paletteTab() === 'fields'\"\r\n [attr.aria-selected]=\"paletteTab() === 'fields'\"\r\n (click)=\"setPaletteTab('fields')\"\r\n >\r\n Campi\r\n </button>\r\n </div>\r\n\r\n @if (paletteTab() === 'components') {\r\n @if (!dictionary.screenFieldTypes().length) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Il dizionario <code>screenFieldTypes</code> non e\u2019 disponibile: senza i suoi flag l\u2019editor non\r\n sa quali campi ammette ciascun tipo, e non li inventa.\r\n </p>\r\n }\r\n <p class=\"fb-scr__side-hint\">Trascina sulla schermata, oppure clicca per aggiungere.</p>\r\n <ul class=\"fb-scr__palette\">\r\n @for (entry of dictionary.screenFieldTypes(); track entry.value) {\r\n <li>\r\n <!--\r\n \u00A75.2 \u2014 l'unico chip che si spegne: una sezione ripetibile dentro un'altra. Non si\r\n annidano, e il cursore e' una variabile sola per sezione.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-scr__chip\"\r\n cdkDrag\r\n [disabled]=\"!canAddAtSelection(entry.value)\"\r\n [title]=\"\r\n canAddAtSelection(entry.value)\r\n ? entry.description || entry.label\r\n : 'Le sezioni ripetibili non si annidano: seleziona un punto fuori dalla riga.'\r\n \"\r\n (cdkDragMoved)=\"onPaletteDragMoved($event)\"\r\n (cdkDragEnded)=\"onComponentDragEnded($event, entry.value)\"\r\n (click)=\"addComponentAtSelection(entry.value)\"\r\n >\r\n <span class=\"fb-scr__chip-icon\" aria-hidden=\"true\">{{ iconOf(entry.value) }}</span>\r\n <span class=\"fb-scr__chip-label\">{{ entry.label }}</span>\r\n </button>\r\n </li>\r\n }\r\n </ul>\r\n } @else {\r\n <p class=\"fb-scr__side-hint\">\r\n Un campo dell\u2019entita\u2019 porta con se\u2019 tipo, etichetta, obbligatorieta\u2019 e opzioni: arrivano dallo\r\n schema dati, qui non si ridichiarano.\r\n </p>\r\n <!-- Il catalogo non e' un dizionario chiuso: si sceglie o si scrive, come ovunque si\r\n indichi un'entita' (vedi `object-picker`). -->\r\n <fb-object-picker\r\n [value]=\"fieldsObject()\"\r\n label=\"Entita\u2019\"\r\n placeholder=\"Scrivi o scegli un\u2019entita\u2019\"\r\n (valueChange)=\"setFieldsObject($event)\"\r\n />\r\n @if (fieldsObject() && !fieldsOfObject().length) {\r\n <p class=\"fb-scr__side-empty\">\r\n Nessun campo dichiarato per questa entita\u2019: il catalogo non li espone, non significa che non\r\n ci siano.\r\n </p>\r\n }\r\n <ul class=\"fb-scr__palette\">\r\n @for (field of fieldsOfObject(); track $index) {\r\n <li>\r\n <button\r\n type=\"button\"\r\n class=\"fb-scr__chip\"\r\n cdkDrag\r\n [title]=\"field.name + (field.dataType ? ' \u00B7 ' + field.dataType : '')\"\r\n (cdkDragMoved)=\"onPaletteDragMoved($event)\"\r\n (cdkDragEnded)=\"onObjectFieldDragEnded($event, field)\"\r\n (click)=\"addObjectFieldAtSelection(field)\"\r\n >\r\n <span class=\"fb-scr__chip-icon\" aria-hidden=\"true\">\u2317</span>\r\n <span class=\"fb-scr__chip-label\">\r\n {{ field.label || field.name }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n <small>{{ field.dataType }}</small>\r\n </span>\r\n </button>\r\n </li>\r\n }\r\n </ul>\r\n }\r\n </aside>\r\n\r\n <!-- ============================================================== centro -->\r\n <!-- Cliccare fuori da un campo riporta a destra le proprieta' della schermata. -->\r\n <section class=\"fb-scr__canvas\" aria-label=\"Anteprima della schermata\" (click)=\"selectScreen()\">\r\n <div class=\"fb-scr__frame\">\r\n @if (showHeader()) {\r\n <header class=\"fb-scr__frame-head\">\r\n {{ $any(node()).label || name() }}\r\n @if (screen().helpText) {\r\n <span class=\"fb-scr__help\" [title]=\"screen().helpText || ''\">?</span>\r\n }\r\n </header>\r\n }\r\n\r\n <div\r\n class=\"fb-scr__body\"\r\n [attr.data-drop]=\"''\"\r\n data-axis=\"column\"\r\n [class.fb-scr__body--empty]=\"isEmpty()\"\r\n >\r\n <ng-container\r\n [ngTemplateOutlet]=\"listTpl\"\r\n [ngTemplateOutletContext]=\"{ $implicit: screen().fields || [], parent: [], axis: 'column' }\"\r\n />\r\n </div>\r\n\r\n <!--\r\n La barra di inserimento e' un **velo** sopra l'anteprima, non un figlio del contenitore di\r\n rilascio: dentro la griglia occupava una riga intera e spostava in basso proprio la sezione\r\n che si stava puntando, che allora usciva da sotto il puntatore e il bersaglio oscillava.\r\n -->\r\n @if (dropMarker(); as bar) {\r\n <div\r\n class=\"fb-scr__marker\"\r\n [style.left.px]=\"bar.left\"\r\n [style.top.px]=\"bar.top\"\r\n [style.width.px]=\"bar.width\"\r\n [style.height.px]=\"bar.height\"\r\n ></div>\r\n }\r\n\r\n @if (showFooter()) {\r\n <footer class=\"fb-scr__frame-foot\">\r\n @if (allowPause()) {\r\n <span class=\"fb-scr__btn fb-scr__btn--ghost\">{{ screen().pauseButtonLabel || 'Pausa' }}</span>\r\n }\r\n <span class=\"fb-scr__spacer\"></span>\r\n @if (allowBack()) {\r\n <span class=\"fb-scr__btn fb-scr__btn--ghost\">{{ screen().backButtonLabel || 'Indietro' }}</span>\r\n }\r\n <span class=\"fb-scr__btn fb-scr__btn--primary\">\r\n {{ screen().nextOrFinishButtonLabel || (allowFinish() ? 'Fine' : 'Avanti') }}\r\n </span>\r\n </footer>\r\n }\r\n </div>\r\n\r\n @if (isEmpty()) {\r\n <p class=\"fb-callout fb-callout--warn fb-scr__canvas-note\">\r\n La schermata non ha campi: non c\u2019e\u2019 niente da mostrare all\u2019utente (SCREEN_WITHOUT_FIELDS).\r\n </p>\r\n }\r\n </section>\r\n\r\n <!-- ============================================================== destra -->\r\n <aside class=\"fb-scr__props\" aria-label=\"Proprieta\u2019\">\r\n @if (!selectedField()) {\r\n <!-- ------------------------------------------------ proprieta' della schermata -->\r\n <header class=\"fb-scr__props-head\">\r\n <h3 class=\"fb-scr__props-title\">Schermata</h3>\r\n </header>\r\n <p class=\"fb-scr__side-hint\">Seleziona un campo nell\u2019anteprima per configurarlo.</p>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Testo di aiuto</label>\r\n <textarea\r\n class=\"fb-textarea\"\r\n [value]=\"screen().helpText || ''\"\r\n (input)=\"setScreenText('helpText', $any($event.target).value)\"\r\n ></textarea>\r\n </div>\r\n\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Navigazione</legend>\r\n <p class=\"fb-section__note\">\r\n Questi flag sono un\u2019intenzione, non la verita\u2019 finale: a runtime il motore comunica\r\n <code>canGoBack</code>, <code>canFinish</code> e <code>canPause</code> nella richiesta della\r\n schermata.\r\n </p>\r\n <label class=\"fb-check\">\r\n <input type=\"checkbox\" [checked]=\"allowBack()\" (change)=\"setScreenFlag('allowBack', $any($event.target).checked)\" />\r\n Consenti \u00ABindietro\u00BB\r\n </label>\r\n <label class=\"fb-check\">\r\n <input type=\"checkbox\" [checked]=\"allowFinish()\" (change)=\"setScreenFlag('allowFinish', $any($event.target).checked)\" />\r\n Consenti \u00ABfine\u00BB\r\n </label>\r\n <label class=\"fb-check\">\r\n <input type=\"checkbox\" [checked]=\"allowPause()\" (change)=\"setScreenFlag('allowPause', $any($event.target).checked)\" />\r\n Consenti \u00ABpausa\u00BB\r\n </label>\r\n <label class=\"fb-check\">\r\n <input type=\"checkbox\" [checked]=\"showHeader()\" (change)=\"setScreenFlag('showHeader', $any($event.target).checked)\" />\r\n Mostra l\u2019intestazione\r\n </label>\r\n <label class=\"fb-check\">\r\n <input type=\"checkbox\" [checked]=\"showFooter()\" (change)=\"setScreenFlag('showFooter', $any($event.target).checked)\" />\r\n Mostra il piede\r\n </label>\r\n\r\n @if (allowPause()) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Testo mostrato alla pausa</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"screen().pausedText || ''\"\r\n (input)=\"setScreenText('pausedText', $any($event.target).value)\"\r\n />\r\n </div>\r\n }\r\n\r\n @if (isDeadEnd()) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n Questa schermata non ha una destinazione e non consente \u00ABfine\u00BB: e\u2019 un vicolo cieco, e\r\n l\u2019utente resterebbe bloccato (SCREEN_DEAD_END).\r\n </p>\r\n }\r\n </fieldset>\r\n\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Pulsanti</legend>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Etichetta \u00ABindietro\u00BB</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"screen().backButtonLabel || ''\"\r\n (input)=\"setScreenText('backButtonLabel', $any($event.target).value)\"\r\n />\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Etichetta \u00ABavanti / fine\u00BB</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"screen().nextOrFinishButtonLabel || ''\"\r\n (input)=\"setScreenText('nextOrFinishButtonLabel', $any($event.target).value)\"\r\n />\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Etichetta \u00ABpausa\u00BB</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"screen().pauseButtonLabel || ''\"\r\n (input)=\"setScreenText('pauseButtonLabel', $any($event.target).value)\"\r\n />\r\n </div>\r\n </fieldset>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Stage mostrato nell\u2019avanzamento</label>\r\n <fb-name-picker\r\n [value]=\"screen().stageReference\"\r\n [options]=\"stageOptions()\"\r\n label=\"Stage\"\r\n placeholder=\"Scegli uno stage\"\r\n unknownMessage=\"Questo stage non e\u2019 dichiarato dal flow.\"\r\n emptyMessage=\"Il flow non dichiara stage: creali nel pannello delle risorse.\"\r\n (valueChange)=\"setScreenText('stageReference', $event ?? '')\"\r\n />\r\n </div>\r\n\r\n <!--\r\n Screen action (\u00A75.2). Due elenchi e non uno perche' il contratto li separa: piu' trigger\r\n possono invocare la stessa action, quindi \u00ABchi la chiama\u00BB non e' una sua proprieta'. Le\r\n action si aprono una alla volta: i cataloghi dipendono da tipo e nome, e tenerne N in volo\r\n sarebbe N volte la corsa fra risposte.\r\n -->\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Azioni della schermata</legend>\r\n <p class=\"fb-section__note\">\r\n Un campo che l\u2019utente compila puo\u2019 innescare un\u2019action i cui risultati finiscono negli\r\n <strong>altri campi di questa schermata</strong>: e\u2019 il caso \u00ABscrivi il codice fiscale e nome\r\n e cognome compaiono da soli\u00BB. L\u2019alternativa sarebbe spezzare la schermata in due con un\r\n elemento Action in mezzo.\r\n </p>\r\n\r\n <div class=\"fb-list\">\r\n @for (action of screenActions(); track $index) {\r\n <div class=\"fb-list__item\">\r\n <div class=\"fb-list__header\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost\"\r\n [attr.aria-expanded]=\"selectedActionIndex() === $index\"\r\n (click)=\"selectAction(selectedActionIndex() === $index ? null : $index)\"\r\n >\r\n {{ selectedActionIndex() === $index ? '\u25BE' : '\u25B8' }}\r\n {{ action.name || '(senza nome)' }}\r\n </button>\r\n <span class=\"fb-list__spacer\"></span>\r\n <span class=\"fb-scr__side-hint\">{{ action.actionName || 'action non scelta' }}</span>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n aria-label=\"Rimuovi l\u2019action\"\r\n (click)=\"removeScreenAction($index)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n\r\n @if (actionLosesResult(action)) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Questa action viene invocata ma il suo risultato si perde: aggiungi un parametro di\r\n uscita, oppure accendi l\u2019output automatico (SCREEN_ACTION_WITHOUT_OUTPUTS).\r\n </p>\r\n }\r\n @if (actionHasNoTrigger(action)) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Nessun trigger la invoca: senza, non girera\u2019 mai.\r\n </p>\r\n }\r\n\r\n @if (selectedActionIndex() === $index) {\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]=\"action.name || ''\"\r\n (change)=\"setActionName($index, $any($event.target).value)\"\r\n />\r\n @if (actionNameError(action); as error) {\r\n <p class=\"fb-field__error\">{{ error }}</p>\r\n }\r\n <p class=\"fb-field__hint\">\r\n E\u2019 il nome con cui la citano i trigger, e vive nello spazio dei nomi del flow. Con\r\n l\u2019output automatico e\u2019 anche la radice del riferimento:\r\n <code>{{ action.name || 'Cerca' }}.NomeOutput</code>.\r\n </p>\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Tipo di action</label>\r\n <fb-name-picker\r\n [value]=\"action.actionType\"\r\n [options]=\"actionTypeOptions()\"\r\n label=\"Tipo di action\"\r\n placeholder=\"Scrivi o scegli un tipo\"\r\n [isMono]=\"false\"\r\n unknownMessage=\"Questo tipo di action non e\u2019 fra quelli dichiarati dal sistema ospite.\"\r\n emptyMessage=\"Catalogo dei tipi di action non disponibile: puoi scrivere il nome a mano.\"\r\n (valueChange)=\"setActionType($index, $event ?? '')\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Action</label>\r\n <fb-name-picker\r\n [value]=\"action.actionName\"\r\n [options]=\"actionOptions()\"\r\n label=\"Action\"\r\n placeholder=\"Scrivi o scegli un\u2019action\"\r\n [isMono]=\"false\"\r\n unknownMessage=\"Questa action non esiste nel catalogo del tipo scelto: e\u2019 ACTION_UNKNOWN.\"\r\n unknownSeverity=\"error\"\r\n emptyMessage=\"Scegli prima il tipo di action, oppure scrivi il nome a mano.\"\r\n (valueChange)=\"setActionTarget($index, $event ?? '')\"\r\n />\r\n @if (!action.actionName) {\r\n <p class=\"fb-field__error\">Obbligatoria: senza, ACTION_NAME_MISSING.</p>\r\n }\r\n </div>\r\n\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"action.storeOutputAutomatically === true\"\r\n (change)=\"setActionStoreOutput($index, $any($event.target).checked)\"\r\n />\r\n Output automatico\r\n </label>\r\n <p class=\"fb-field__hint\">\r\n L\u2019output automatico rende il risultato leggibile come\r\n <code>{{ action.name || 'Cerca' }}.NomeOutput</code>, ma\r\n <strong>non scrive in nessun campo</strong>: e\u2019 la forma da usare quando il risultato\r\n serve a una condizione, non a precompilare.\r\n </p>\r\n\r\n <!--\r\n L'unico posto in cui un campo di schermata e' una destinazione (\u00A75.2):\r\n `POST /flows/references/writable` continua a escluderlo, e un Assignment che ci\r\n scrive resta TARGET_NOT_WRITABLE.\r\n -->\r\n <fb-parameter-editor\r\n [holder]=\"action\"\r\n [catalogParameters]=\"actionParameterCatalog()\"\r\n [showOutputs]=\"action.storeOutputAutomatically !== true\"\r\n outputTitle=\"Campi da riempire\"\r\n [extraTargets]=\"screenFieldTargets()\"\r\n [outputsDisabledReason]=\"\r\n action.storeOutputAutomatically === true\r\n ? 'Con l\u2019output automatico il risultato non viene scritto nei campi.'\r\n : null\r\n \"\r\n (changed)=\"onActionParametersChanged($index, $event)\"\r\n />\r\n }\r\n </div>\r\n } @empty {\r\n <p class=\"fb-empty\">Nessuna action: i campi di questa schermata li compila solo l\u2019utente.</p>\r\n }\r\n </div>\r\n\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"addScreenAction()\">Aggiungi action</button>\r\n </fieldset>\r\n\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Trigger</legend>\r\n <p class=\"fb-section__note\">\r\n Quale campo invoca quale action. Senza condizioni l\u2019action gira a ogni cambio del campo.\r\n </p>\r\n\r\n <div class=\"fb-list\">\r\n @for (trigger of screenTriggers(); 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 trigger\"\r\n (click)=\"removeScreenTrigger($index)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Action da invocare</label>\r\n <fb-name-picker\r\n [value]=\"trigger.screenActionName\"\r\n [options]=\"screenActionOptions()\"\r\n label=\"Action della schermata\"\r\n placeholder=\"Scegli un\u2019action\"\r\n unknownMessage=\"Questa schermata non dichiara un\u2019action con questo nome: e\u2019 SCREEN_ACTION_UNKNOWN.\"\r\n unknownSeverity=\"error\"\r\n emptyMessage=\"Aggiungi prima un\u2019action qui sopra.\"\r\n (valueChange)=\"setTriggerProperty($index, 'screenActionName', $event ?? '')\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Campo che la innesca</label>\r\n <!-- I campi di **questa** schermata, non i riferimenti in generale (\u00A75.2). -->\r\n <fb-name-picker\r\n [value]=\"trigger.triggerFieldName\"\r\n [options]=\"triggerFieldOptions()\"\r\n label=\"Campo della schermata\"\r\n placeholder=\"Scegli un campo\"\r\n unknownMessage=\"Questo non e\u2019 un campo di questa schermata: e\u2019 SCREEN_TRIGGER_FIELD_UNKNOWN.\"\r\n unknownSeverity=\"error\"\r\n emptyMessage=\"La schermata non ha ancora campi che raccolgono un valore.\"\r\n (valueChange)=\"setTriggerProperty($index, 'triggerFieldName', $event ?? '')\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">All\u2019arrivo sulla schermata</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"trigger.initBehavior || ''\"\r\n (change)=\"setTriggerProperty($index, 'initBehavior', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 non invocare \u2014</option>\r\n <option value=\"runOnLoad\">Invoca al caricamento (runOnLoad)</option>\r\n </select>\r\n @if (triggerInitIsUnknown(trigger)) {\r\n <p class=\"fb-field__error\">\r\n \u00AB{{ trigger.initBehavior }}\u00BB non e\u2019 ammesso: l\u2019unico valore e\u2019 <code>runOnLoad</code>\r\n (SCREEN_TRIGGER_INIT_BEHAVIOR_UNKNOWN).\r\n </p>\r\n }\r\n @if (triggerFiresOnLoadByDefault(trigger)) {\r\n <p class=\"fb-field__hint\">\r\n Non serve: \u00AB{{ trigger.triggerFieldName }}\u00BB ha un valore di default, e un campo\r\n precompilato dal flow innesca l\u2019action gi\u00E0 all\u2019arrivo sulla schermata.\r\n </p>\r\n }\r\n </div>\r\n\r\n @if (triggerHasNoCause(trigger)) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Senza un campo che la innesca e senza l\u2019invocazione al caricamento, questo trigger non\r\n scattera\u2019 mai (SCREEN_TRIGGER_WITHOUT_CAUSE).\r\n </p>\r\n }\r\n\r\n <fb-condition-editor\r\n [holder]=\"triggerConditions(trigger)\"\r\n title=\"Invoca l\u2019action quando\"\r\n [allowFormula]=\"false\"\r\n [issuePath]=\"'triggers[' + $index + ']'\"\r\n (changed)=\"onTriggerConditionsChanged($index, $event)\"\r\n />\r\n </div>\r\n } @empty {\r\n <p class=\"fb-empty\">Nessun trigger: le action dichiarate non verranno invocate.</p>\r\n }\r\n </div>\r\n\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"addScreenTrigger()\">Aggiungi trigger</button>\r\n </fieldset>\r\n\r\n <!-- --------------------------------------------- regole della schermata (\u00A75.2) -->\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Regole della schermata</legend>\r\n <p class=\"fb-section__note\">\r\n Le verifiche che <strong>non appartengono a nessun campo</strong>: \u00ABalmeno un punto fra le\r\n due sezioni\u00BB, \u00ABla somma delle percentuali fa cento\u00BB. Si valutano all\u2019invio, dopo i campi e\r\n dopo le righe, e una regola non soddisfatta <strong>impedisce alla schermata di\r\n avanzare</strong>. La <code>validationRule</code> di un campo non basta \u2014 giudica un solo\r\n valore, e su una sezione ripetibile e\u2019 addirittura inerte.\r\n </p>\r\n\r\n <div class=\"fb-list\">\r\n @for (rule of screenRules(); 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__title\">{{ rule.errorMessage || '(senza messaggio)' }}</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--sm\"\r\n aria-label=\"Rimuovi la regola\"\r\n (click)=\"removeScreenRule($index)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n\r\n <!--\r\n \u00A75.2 \u2014 qui `Formula` **e'** un `conditionLogic` ammesso, a differenza di un\r\n `filterLogic`: la regola puo' essere un'espressione booleana. Resta il consiglio,\r\n scritto sotto, di preferire le condizioni.\r\n -->\r\n <fb-condition-editor\r\n [holder]=\"rule\"\r\n title=\"La schermata e\u2019 valida quando\"\r\n [allowFormula]=\"true\"\r\n [issuePath]=\"'validationRules[' + $index + ']'\"\r\n (changed)=\"onScreenRuleConditionsChanged($index, $event)\"\r\n />\r\n @if (screenRuleIsEmpty(rule)) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n La regola non verifica niente: senza condizioni non impedisce mai l\u2019invio.\r\n </p>\r\n }\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Messaggio</label>\r\n <fb-merge-text-editor\r\n [text]=\"rule.errorMessage || ''\"\r\n [rows]=\"2\"\r\n commitOn=\"change\"\r\n placeholder=\"Indica almeno un punto di fornitura, luce o gas.\"\r\n ariaLabel=\"Messaggio della regola\"\r\n (textChange)=\"setScreenRuleProperty($index, 'errorMessage', $event)\"\r\n >\r\n <p class=\"fb-field__hint\">\r\n \u00C8 il testo dell\u2019autore e non nomina nessun campo: il runtime non ne costruisce\r\n uno, perche\u2019 un campo da nominare non c\u2019e\u2019.\r\n </p>\r\n </fb-merge-text-editor>\r\n @if (screenRuleHasNoMessage(rule)) {\r\n <p class=\"fb-field__error\">\r\n Obbligatorio: senza, la schermata si rifiuta di avanzare senza dire perche\u2019\r\n (SCREEN_RULE_INVALID).\r\n </p>\r\n }\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Aggancia il messaggio a un campo</label>\r\n <!--\r\n Stesso elenco degli inneschi: i campi della schermata **senza** quelli dentro una\r\n riga, che esistono in una copia per elemento e non individuano un controllo (\u00A75.2).\r\n -->\r\n <fb-name-picker\r\n [value]=\"rule.fieldName\"\r\n [options]=\"triggerFieldOptions()\"\r\n label=\"Campo\"\r\n placeholder=\"Nessuno: il messaggio e\u2019 della schermata\"\r\n unknownMessage=\"Questo nome non e\u2019 un campo di questa schermata.\"\r\n unknownSeverity=\"error\"\r\n emptyMessage=\"La schermata non ha campi a cui agganciare il messaggio.\"\r\n (valueChange)=\"setScreenRuleProperty($index, 'fieldName', $event ?? '')\"\r\n />\r\n <p class=\"fb-field__hint\">\r\n Facoltativo. Senza, il rilievo e\u2019 <strong>della schermata</strong> e il frontend lo\r\n mostra dove mostra i messaggi che non hanno un controllo \u2014 in testa al form, o\r\n accanto ad Avanti.\r\n </p>\r\n </div>\r\n </div>\r\n } @empty {\r\n <p class=\"fb-empty\">Nessuna regola: la schermata avanza se i campi sono a posto.</p>\r\n }\r\n </div>\r\n\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"addScreenRule()\">Aggiungi regola</button>\r\n <p class=\"fb-field__hint\">\r\n Scrivile con le <strong>condizioni</strong>, non con la formula: <code>IsEmpty</code> su una\r\n collection e\u2019 un operatore che il runtime valuta, mentre la stessa domanda in una formula\r\n chiede al motore una funzione che potrebbe non avere. Vale soprattutto per la verifica piu\u2019\r\n frequente \u2014 che una sezione ripetibile abbia almeno una riga.\r\n </p>\r\n </fieldset>\r\n } @else {\r\n <!-- ---------------------------------------------------- proprieta' del campo -->\r\n <header class=\"fb-scr__props-head\">\r\n <h3 class=\"fb-scr__props-title\">{{ captionOf(selectedField()!) }}</h3>\r\n <div class=\"fb-scr__props-actions\">\r\n <button type=\"button\" class=\"fb-btn fb-btn--sm\" title=\"Sposta su\" (click)=\"moveSelected(-1)\">\u2191</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--sm\" title=\"Sposta giu\u2019\" (click)=\"moveSelected(1)\">\u2193</button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--sm\"\r\n title=\"Porta fuori dal contenitore\"\r\n [disabled]=\"!canOutdent()\"\r\n (click)=\"outdentSelected()\"\r\n >\r\n \u21E4\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--sm\" title=\"Duplica\" (click)=\"duplicateSelected()\">\u29C9</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--danger fb-btn--sm\" title=\"Elimina\" (click)=\"removeSelected()\">\r\n \u00D7\r\n </button>\r\n </div>\r\n </header>\r\n\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]=\"selectedField()!.fieldType || ''\"\r\n (change)=\"setFieldType($any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 scegli \u2014</option>\r\n <!--\r\n \u00A75.2 \u2014 dentro una riga la sezione ripetibile non c'e': **non si annidano**, perche' il\r\n cursore e' una variabile sola per sezione. La validazione lo rifiuterebbe comunque, ma\r\n lasciar comporre e poi accusare fa perdere tempo due volte.\r\n -->\r\n @for (entry of fieldTypeOptions(); track entry.value) {\r\n <option [value]=\"entry.value\">{{ entry.label }}</option>\r\n }\r\n </select>\r\n @if (isInsideRepeater()) {\r\n <p class=\"fb-field__hint\">\r\n Questo campo sta dentro la riga di <code>{{ selectedRepeater()!.name }}</code>: esiste una\r\n volta per ogni elemento della collection, quindi\r\n <strong>non e\u2019 una risorsa del flow</strong> e non si nomina per nome. Dentro una riga si\r\n nomina il cursore \u2014\r\n <code>{{ rowCursor() || 'itemReference' }}.&lt;membro&gt;</code> \u2014 e scrivere\r\n <code>{{ selectedField()!.name }}</code> in una condizione, in una formula o in un parametro\r\n e\u2019 <code>SCREEN_FIELD_IN_REPEATER</code>.\r\n </p>\r\n }\r\n @if (!selectedField()!.fieldType) {\r\n <p class=\"fb-field__error\">Il tipo e\u2019 obbligatorio: senza, SCREEN_FIELD_TYPE_MISSING.</p>\r\n } @else if (isUnknownType(selectedField())) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n Questo tipo non e\u2019 nel dizionario: l\u2019editor non sa quali campi ammetta e mostra solo i comuni.\r\n </p>\r\n } @else if (selectedType()?.description) {\r\n <p class=\"fb-field__hint\">{{ selectedType()?.description }}</p>\r\n }\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Nome</label>\r\n <!-- Sul `change` e non sull\u2019`input`: la rinomina riscrive i riferimenti nel documento,\r\n e farlo a ogni tasto significherebbe riscriverlo per ogni lettera. -->\r\n <input\r\n class=\"fb-input fb-input--mono\"\r\n [value]=\"selectedField()!.name || ''\"\r\n (change)=\"setFieldName($any($event.target).value)\"\r\n />\r\n @if (selectedIsResource() && !isInsideRepeater()) {\r\n <p class=\"fb-field__hint\">\r\n Questo campo e\u2019 una <strong>risorsa</strong>: lo referenzi come\r\n <code>{{ selectedField()!.name || 'Nome' }}</code> in condizioni, formule e parametri. \u00C8 di\r\n sola lettura per il flow \u2014 un Assignment che ci scrive e\u2019 TARGET_NOT_WRITABLE.\r\n </p>\r\n } @else if (isInsideRepeater()) {\r\n <p class=\"fb-field__hint\">\r\n Il nome resta comunque <strong>unico</strong>: e\u2019 l\u2019indirizzo con cui il frontend rende il\r\n campo, e un omonimo di una variabile e\u2019 <code>NAME_DUPLICATED</code> come sempre.\r\n </p>\r\n }\r\n </div>\r\n\r\n <!--\r\n \u00A75.2 \u2014 `memberName` e il riferimento con cui gli altri campi lo nominano sono la **stessa\r\n domanda posta due volte**: `memberName: \"PodId\"` e `PuntoCorrente.PodId` sono lo stesso\r\n membro, e la classe da cui vengono e' l'`objectType` della variabile in `collectionReference`.\r\n -->\r\n @if (isInsideRepeater() && selectedType()?.storesValue) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Membro della riga</label>\r\n @if (rowClass()) {\r\n <fb-structure-member-picker\r\n [value]=\"selectedField()!.memberName\"\r\n [className]=\"rowClass()\"\r\n usage=\"writable\"\r\n label=\"Membro della riga\"\r\n (valueChange)=\"setFieldProperty('memberName', $event ?? '')\"\r\n />\r\n } @else {\r\n <input\r\n class=\"fb-input fb-input--mono\"\r\n [value]=\"selectedField()!.memberName || ''\"\r\n (change)=\"setFieldProperty('memberName', $any($event.target).value)\"\r\n />\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n La classe delle righe non e\u2019 nota: la sezione non dichiara una <code>collectionReference</code>\r\n valida, quindi i membri non si possono proporre.\r\n </p>\r\n }\r\n <p class=\"fb-field__hint\">\r\n Il membro della riga in cui finisce il valore. Senza, vale il nome del campo. Dentro una\r\n riga lo si legge come\r\n <code>{{ rowCursor() || 'itemReference' }}.{{ selectedField()!.memberName || selectedField()!.name }}</code>.\r\n </p>\r\n </div>\r\n }\r\n\r\n <!--\r\n Il ramo dei contenitori vale anche per una sezione ripetibile: l\u00EC `fieldText` e'\r\n l'intestazione del blocco, non l'etichetta di un valore \u2014 l'etichetta della **riga** e'\r\n `itemLabel`, ed e' un campo suo, piu' sotto (\u00A75.2).\r\n -->\r\n @if (!holdsFields(selectedField())) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">\r\n {{ selectedField()!.fieldType === 'DisplayText' ? 'Testo' : 'Etichetta' }}\r\n </label>\r\n <textarea\r\n class=\"fb-textarea\"\r\n [value]=\"selectedField()!.fieldText || ''\"\r\n (input)=\"setFieldProperty('fieldText', $any($event.target).value)\"\r\n ></textarea>\r\n <p class=\"fb-field__hint\">\r\n Supporta i merge field <code>&#123;!Riferimento&#125;</code>.\r\n @if (selectedField()!.fieldType === 'ComponentInstance') {\r\n <!--\r\n \u00A75.2 \u2014 su un componente l\u2019etichetta e\u2019 **ammessa** e viene consegnata al frontend\r\n in `label`, ma la sua assenza non e\u2019 un rilievo: un componente di solito disegna\r\n la propria intestazione. Nasconderla, come faceva prima l\u2019editor, toglieva un\r\n campo che il contratto prevede.\r\n -->\r\n Facoltativa: un componente di solito disegna la propria intestazione.\r\n }\r\n </p>\r\n </div>\r\n } @else {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Intestazione</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"selectedField()!.fieldText || ''\"\r\n (input)=\"setFieldProperty('fieldText', $any($event.target).value)\"\r\n />\r\n </div>\r\n }\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Testo di aiuto</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"selectedField()!.helpText || ''\"\r\n (input)=\"setFieldProperty('helpText', $any($event.target).value)\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Larghezza</label>\r\n <div class=\"fb-scr__width\">\r\n <input\r\n type=\"range\"\r\n min=\"1\"\r\n max=\"12\"\r\n step=\"1\"\r\n [value]=\"widthOf(selectedField()!)\"\r\n (input)=\"setNumberProperty('width', $any($event.target).value)\"\r\n />\r\n <span class=\"fb-scr__width-value\">{{ widthOf(selectedField()!) }}/12</span>\r\n </div>\r\n <p class=\"fb-field__hint\">\r\n Colonne della griglia della schermata. \u00C8 un\u2019indicazione: il frontend puo\u2019 ignorarla.\r\n </p>\r\n </div>\r\n\r\n @if (selectedField()!.fieldType === 'ComponentInstance') {\r\n <!--\r\n \u00A75.2 \u2014 perche\u2019 qui non c\u2019e\u2019 \u00ABobbligatorio\u00BB. La domanda arriva sempre, e la risposta non\r\n si indovina guardando l\u2019interfaccia: un componente non ha un valore proprio, ce l\u2019hanno\r\n i suoi output, che vanno in **variabili** scelte dall\u2019autore. \u00ABObbligatorio\u00BB dovrebbe\r\n dire *quale output* deve essere valorizzato, e il metadata non ha modo di dirlo \u2014\r\n dichiararlo qui sarebbe `SCREEN_FIELD_CONFIGURATION_INVALID` e il runtime lo\r\n ignorerebbe. Le due strade vere stanno nel contratto e sono queste.\r\n -->\r\n <p class=\"fb-field__hint\">\r\n Un componente non raccoglie un valore \u2014 lo fanno i suoi output \u2014 quindi obbligatorieta\u2019, valore\r\n predefinito e regola di validazione non si applicano: dichiararli e\u2019\r\n <code>SCREEN_FIELD_CONFIGURATION_INVALID</code> e il runtime li ignora. Per pretendere una\r\n compilazione: la validazione la fa il componente stesso, oppure si controlla la variabile di\r\n destinazione con una Decision dopo la schermata.\r\n </p>\r\n }\r\n\r\n <!-- ------------------------------------------------ campi che raccolgono un valore -->\r\n @if (selectedType()?.storesValue) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Valore</legend>\r\n\r\n @if (selectedField()!.fieldType !== 'ObjectProvided') {\r\n <div class=\"fb-field\">\r\n <label\r\n class=\"fb-field__label\"\r\n [class.fb-field__label--required]=\"!!selectedType()?.requiresDataType\"\r\n >\r\n Tipo di dato\r\n </label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"selectedField()!.dataType || ''\"\r\n (change)=\"setFieldProperty('dataType', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 scegli \u2014</option>\r\n @for (entry of dictionary.dataTypes(); track entry.value) {\r\n <option [value]=\"entry.value\">{{ entry.label }}</option>\r\n }\r\n </select>\r\n @if (selectedType()?.requiresDataType && !selectedField()!.dataType) {\r\n <p class=\"fb-field__error\">Obbligatorio per questo tipo di campo (DATA_TYPE_MISSING).</p>\r\n }\r\n @if (selectedType()?.isCollection) {\r\n <p class=\"fb-field__hint\">\r\n Il valore raccolto e\u2019 una <strong>collection</strong>, non una stringa con i valori\r\n separati: nelle condizioni si usano gli operatori di collection.\r\n </p>\r\n }\r\n </div>\r\n\r\n @if (requiresObjectType()) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\" [class.fb-field__label--required]=\"objectTypeIsStructure()\">\r\n {{\r\n objectTypeIsStructure()\r\n ? 'Classe'\r\n : selectedField()!.dataType === 'Enum'\r\n ? 'Tipo di enumerazione'\r\n : 'Oggetto'\r\n }}\r\n </label>\r\n @if (selectedField()!.dataType === 'Enum') {\r\n <!-- Dizionario chiuso: si sceglie, non si scrive. -->\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"selectedField()!.objectType || ''\"\r\n (change)=\"setFieldProperty('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 @if (!selectedField()!.objectType) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n Senza il tipo concreto l\u2019editor non puo\u2019 proporre i valori dell\u2019enumerazione.\r\n </p>\r\n }\r\n } @else if (objectTypeIsStructure()) {\r\n <fb-structure-picker\r\n [value]=\"selectedField()!.objectType\"\r\n label=\"Classe\"\r\n (valueChange)=\"setFieldProperty('objectType', $event ?? '')\"\r\n />\r\n @if (!selectedField()!.objectType) {\r\n <p class=\"fb-field__error\">\r\n La classe e\u2019 obbligatoria: senza, il runtime non ha nulla da istanziare\r\n (OBJECT_TYPE_MISSING).\r\n </p>\r\n }\r\n } @else {\r\n <fb-object-picker\r\n [value]=\"selectedField()!.objectType\"\r\n label=\"Oggetto\"\r\n (valueChange)=\"setFieldProperty('objectType', $event ?? '')\"\r\n />\r\n }\r\n </div>\r\n }\r\n } @else {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Campo dell\u2019entita\u2019</label>\r\n <p class=\"fb-field__hint\">\r\n Tipo, label, obbligatorieta\u2019, scala e \u2014 se il campo e\u2019 una picklist \u2014 le opzioni arrivano\r\n dallo schema dati: qui non si ridichiarano.\r\n </p>\r\n <fb-object-picker\r\n [value]=\"providedObject() || undefined\"\r\n label=\"Oggetto\"\r\n (valueChange)=\"setProvidedObject($event)\"\r\n />\r\n <fb-field-picker\r\n [value]=\"providedField() || undefined\"\r\n [object]=\"providedObject() || undefined\"\r\n label=\"Campo\"\r\n (valueChange)=\"setProvidedField($event)\"\r\n />\r\n </div>\r\n }\r\n\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"selectedIsRequired()\"\r\n (change)=\"setRequired($any($event.target).checked)\"\r\n />\r\n Obbligatorio\r\n </label>\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"selectedIsEditable()\"\r\n (change)=\"setEditable($any($event.target).checked)\"\r\n />\r\n Modificabile\r\n </label>\r\n @if (!selectedIsEditable()) {\r\n <p class=\"fb-field__hint\">\r\n A <code>false</code> il runtime <strong>ignora</strong> cio\u2019 che il client rimanda indietro:\r\n e\u2019 l\u2019unico modo di rendere un campo davvero di sola lettura.\r\n </p>\r\n }\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Valore di default</label>\r\n <fb-value-editor\r\n [value]=\"selectedField()!.defaultValue\"\r\n [dataType]=\"selectedField()!.dataType\"\r\n [objectType]=\"selectedField()!.objectType\"\r\n [isCollection]=\"selectedType()?.isCollection\"\r\n [preferredRoots]=\"preferredRoots()\"\r\n label=\"Valore di default\"\r\n (valueChange)=\"setDefaultValue($event)\"\r\n />\r\n </div>\r\n\r\n @if (scaleApplies()) {\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]=\"selectedField()!.scale ?? ''\"\r\n (change)=\"setNumberProperty('scale', $any($event.target).value)\"\r\n />\r\n </div>\r\n } @else if (selectedField()!.scale !== undefined) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n <code>scale</code> su un campo non numerico e\u2019 SCALE_NOT_APPLICABLE.\r\n </p>\r\n }\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Lunghezza massima</label>\r\n <input\r\n class=\"fb-input\"\r\n type=\"number\"\r\n min=\"1\"\r\n [value]=\"selectedField()!.maxLength ?? ''\"\r\n (change)=\"setNumberProperty('maxLength', $any($event.target).value)\"\r\n />\r\n <p class=\"fb-field__hint\">Verificata anche dal runtime (TOO_LONG).</p>\r\n </div>\r\n\r\n @if (formatApplies()) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Formato</label>\r\n <fb-name-picker\r\n [value]=\"selectedField()!.format\"\r\n [options]=\"formatOptions()\"\r\n label=\"Formato\"\r\n placeholder=\"Nessuno: un testo qualunque\"\r\n unknownSeverity=\"note\"\r\n unknownMessage=\"Non e\u2019 fra i formati proposti: se l\u2019host ha registrato il suo pattern va bene cosi\u2019, altrimenti il campo non verifica niente (SCREEN_FIELD_FORMAT_UNKNOWN).\"\r\n emptyMessage=\"Nessun formato dichiarato: puoi scriverne uno registrato dall\u2019host.\"\r\n (valueChange)=\"setFieldProperty('format', $event)\"\r\n />\r\n <p class=\"fb-field__hint\">\r\n <strong>Che cosa</strong> il campo raccoglie, oltre al fatto di essere un testo: il\r\n frontend ne ricava il tipo di input, e il runtime rifiuta l\u2019invio sbagliato\r\n (FORMAT_INVALID). L\u2019elenco non e\u2019 chiuso \u2014 i pattern li registra l\u2019host, non la\r\n libreria \u2014 quindi un nome fuori elenco non e\u2019 un difetto.\r\n </p>\r\n <p class=\"fb-field__hint\">\r\n Non e\u2019 un\u2019etichetta che il flow puo\u2019 leggere: se a valle serve sapere che un numero e\u2019\r\n un cellulare, quella distinzione sta nel dato (due campi, o due membri di una\r\n <code>Structure</code>). E non riscrive il valore \u2014 togliere spazi o aggiungere il\r\n prefisso e\u2019 una trasformazione, e la fa una screen action.\r\n </p>\r\n </div>\r\n } @else if (selectedField()!.format) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n <code>format</code> su un campo che non raccoglie un solo testo non ha effetto\r\n (SCREEN_FIELD_FORMAT_NOT_APPLICABLE).\r\n </p>\r\n }\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Tornando sulla schermata</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"selectedField()!.inputsOnNextNavToAssocScrn || ''\"\r\n (change)=\"setFieldProperty('inputsOnNextNavToAssocScrn', $any($event.target).value)\"\r\n >\r\n <option value=\"\">Predefinito (mantieni i valori)</option>\r\n @for (entry of dictionary.screenFieldInputsRevisited(); track entry.value) {\r\n <option [value]=\"entry.value\">{{ entry.label }}</option>\r\n }\r\n </select>\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Regola di validazione</label>\r\n <!--\r\n `commitOn=\"change\"`: qui mutare il documento ridisegna l'anteprima, e riscriverla a\r\n ogni tasto costa. La verifica parte lo stesso mentre si digita (\u00A76.3).\r\n -->\r\n <fb-formula-editor\r\n [expression]=\"selectedField()!.validationRule?.formulaExpression || ''\"\r\n usage=\"ValidationRule\"\r\n expectedDataType=\"Boolean\"\r\n commitOn=\"change\"\r\n [preferredRoots]=\"preferredRoots()\"\r\n [rows]=\"2\"\r\n placeholder=\"Espressione, es. LEN(Nome) > 3\"\r\n ariaLabel=\"Regola di validazione\"\r\n (expressionChange)=\"setValidationRule('formulaExpression', $event)\"\r\n />\r\n <input\r\n class=\"fb-input\"\r\n placeholder=\"Messaggio mostrato quando l\u2019espressione e\u2019 falsa\"\r\n [value]=\"selectedField()!.validationRule?.errorMessage || ''\"\r\n (change)=\"setValidationRule('errorMessage', $any($event.target).value)\"\r\n />\r\n <p class=\"fb-field__hint\">\r\n L\u2019espressione la valuta il motore di regole: il backend non la interpreta, la fa\r\n verificare al motore.\r\n </p>\r\n </div>\r\n </fieldset>\r\n }\r\n\r\n <!-- --------------------------------------------------------------- choice -->\r\n @if (selectedType()?.acceptsChoices) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Opzioni</legend>\r\n <p class=\"fb-section__note\">\r\n Accetta <strong>Choice</strong> e <strong>Dynamic choice set</strong>, nell\u2019ordine in cui le\r\n opzioni compaiono. Puntare a una variabile e\u2019 SCREEN_FIELD_CHOICE_UNKNOWN.\r\n </p>\r\n\r\n @if (!selectedField()!.choiceReferences?.length) {\r\n @if (selectedField()!.fieldType === 'ObjectProvided') {\r\n <!-- Le opzioni di una picklist arrivano dallo schema: qui si aggiungono solo se\r\n si vuole sostituirle, e non dichiararne nessuna e' il caso normale. -->\r\n <p class=\"fb-field__hint\">\r\n Se il campo dello schema e\u2019 una picklist, le opzioni arrivano da l\u00EC: dichiararle qui\r\n serve solo a sostituirle.\r\n </p>\r\n } @else {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Nessuna opzione: il campo non ha niente da mostrare (SCREEN_FIELD_CHOICES_MISSING).\r\n </p>\r\n }\r\n }\r\n\r\n <div class=\"fb-list\">\r\n @for (choice of selectedField()!.choiceReferences || []; 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__title\">{{ choice }}</span>\r\n <span class=\"fb-list__spacer\"></span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--sm\" (click)=\"moveChoice($index, -1)\">\u2191</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--sm\" (click)=\"moveChoice($index, 1)\">\u2193</button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--sm\"\r\n aria-label=\"Rimuovi\"\r\n (click)=\"removeChoice($index)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Aggiungi un\u2019opzione</label>\r\n <!--\r\n `commitOn=\"enter\"`: qui ogni emissione e\u2019 una riga in piu\u2019, non un campo che cambia\r\n valore \u2014 a tasto, scrivere \u00ABAlta\u00BB aggiungeva \u00ABA\u00BB, \u00ABAl\u00BB, \u00ABAlt\u00BB e \u00ABAlta\u00BB.\r\n -->\r\n <fb-name-picker\r\n [options]=\"choiceOptions()\"\r\n label=\"Choice\"\r\n commitOn=\"enter\"\r\n placeholder=\"Scegli una choice o un choice set\"\r\n unknownMessage=\"Questo nome non e\u2019 una choice ne\u2019 un choice set: e\u2019 SCREEN_FIELD_CHOICE_UNKNOWN.\"\r\n unknownSeverity=\"error\"\r\n emptyMessage=\"Il flow non dichiara nessuna choice: creale nel pannello delle risorse.\"\r\n (valueChange)=\"addChoice($event)\"\r\n />\r\n <p class=\"fb-field__hint\">\r\n Scegli dall\u2019elenco, oppure scrivi il nome e premi Invio.\r\n </p>\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Opzione selezionata all\u2019apertura</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"selectedField()!.defaultSelectedChoiceReference || ''\"\r\n (change)=\"setFieldProperty('defaultSelectedChoiceReference', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 nessuna \u2014</option>\r\n @for (choice of selectedField()!.choiceReferences || []; track $index) {\r\n <option [value]=\"choice\">{{ choice }}</option>\r\n }\r\n </select>\r\n @if (defaultChoiceIsForeign()) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n \u00AB{{ selectedField()!.defaultSelectedChoiceReference }}\u00BB non e\u2019 fra le opzioni elencate qui\r\n sopra.\r\n </p>\r\n }\r\n </div>\r\n\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"selectedAutoSelectsSingleChoice()\"\r\n (change)=\"setAutoSelectSingleChoice($any($event.target).checked)\"\r\n />\r\n Scegli da sola l\u2019unica opzione\r\n </label>\r\n <p class=\"fb-field__hint\">\r\n Quando le opzioni risolte sono <strong>esattamente una</strong>, il campo la prende da se\u2019.\r\n Serve alle tendine che dipendono da un\u2019altra tendina \u2014 i CAP di un comune \u2014 dove chiedere\r\n di scegliere l\u2019unica voce non decide niente.\r\n </p>\r\n </fieldset>\r\n }\r\n\r\n <!-- ------------------------------------------------------- contenitori -->\r\n @if (selectedType()?.isContainer) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Contenitore</legend>\r\n @if (selectedField()!.fieldType === 'RegionContainer') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Tipo di sezione</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"selectedField()!.regionContainerType || ''\"\r\n (change)=\"setFieldProperty('regionContainerType', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 predefinito \u2014</option>\r\n @for (entry of dictionary.regionContainerTypes(); track entry.value) {\r\n <option [value]=\"entry.value\">{{ entry.label }}</option>\r\n }\r\n </select>\r\n </div>\r\n }\r\n @if (!selectedField()!.fields?.length) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Il contenitore e\u2019 vuoto: non produce niente sulla schermata (SCREEN_CONTAINER_EMPTY).\r\n </p>\r\n }\r\n </fieldset>\r\n }\r\n\r\n <!-- ------------------------------------------------ sezione ripetibile (\u00A75.2) -->\r\n @if (selectedIsRepeatingSection()) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Righe</legend>\r\n <p class=\"fb-section__note\">\r\n I campi qui dentro sono il <strong>modello di una riga</strong>: la schermata ne rende una\r\n copia per ogni elemento della collection, e l\u2019utente decide quante con un tasto. I due\r\n riferimenti vanno pensati insieme \u2014 la <strong>collection</strong> e\u2019 dove le righe vivono,\r\n il <strong>cursore</strong> e\u2019 la riga corrente, come\r\n l\u2019<code>assignNextValueToReference</code> di un Loop.\r\n </p>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Collection delle righe</label>\r\n <fb-reference-picker\r\n [value]=\"selectedField()!.collectionReference\"\r\n dataType=\"Structure\"\r\n [isCollection]=\"true\"\r\n [writableOnly]=\"true\"\r\n label=\"Collection delle righe\"\r\n placeholder=\"Una variabile collection di tipo Structure\"\r\n (valueChange)=\"setFieldProperty('collectionReference', $event ?? '')\"\r\n />\r\n <p class=\"fb-field__hint\">\r\n \u00C8 quella che il resto del flow attraversa con un Loop: la sezione non introduce un posto\r\n nuovo in cui i dati stanno. Se la riga e\u2019 il DTO che poi finisce in una action, conviene\r\n che sia una collection <strong>di quel DTO</strong>.\r\n </p>\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Cursore della riga</label>\r\n <fb-reference-picker\r\n [value]=\"selectedField()!.itemReference\"\r\n dataType=\"Structure\"\r\n [isCollection]=\"false\"\r\n [objectType]=\"rowClass()\"\r\n [writableOnly]=\"true\"\r\n label=\"Cursore della riga\"\r\n placeholder=\"Una variabile Structure della stessa classe\"\r\n (valueChange)=\"setFieldProperty('itemReference', $event ?? '')\"\r\n />\r\n <p class=\"fb-field__hint\">\r\n \u00C8 il nome che si scrive <strong>dentro</strong> la riga: visibilita\u2019, regole di\r\n validazione, filtri delle tendine, parametri dei componenti e\r\n <code>itemLabel</code> nominano\r\n <code>{{ selectedField()!.itemReference || 'Cursore' }}.&lt;membro&gt;</code>, mai il nome\r\n di un campo della riga.\r\n </p>\r\n </div>\r\n\r\n @if (repeaterIssue(); as issue) {\r\n <p class=\"fb-callout fb-callout--error\">{{ issue }} (REPEATING_SECTION_INVALID)</p>\r\n }\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Righe minime</label>\r\n <input\r\n class=\"fb-input\"\r\n type=\"number\"\r\n min=\"0\"\r\n [value]=\"selectedField()!.minInstances ?? ''\"\r\n (change)=\"setNumberProperty('minInstances', $any($event.target).value)\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Righe massime</label>\r\n <input\r\n class=\"fb-input\"\r\n type=\"number\"\r\n min=\"1\"\r\n [value]=\"selectedField()!.maxInstances ?? ''\"\r\n (change)=\"setNumberProperty('maxInstances', $any($event.target).value)\"\r\n />\r\n @if (repeaterHasNoLimit()) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n Senza un massimo l\u2019utente puo\u2019 aggiungere righe all\u2019infinito\r\n (REPEATING_SECTION_WITHOUT_LIMIT).\r\n </p>\r\n }\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Righe gia\u2019 aperte</label>\r\n <input\r\n class=\"fb-input\"\r\n type=\"number\"\r\n min=\"0\"\r\n [value]=\"selectedField()!.initialInstances ?? ''\"\r\n (change)=\"setNumberProperty('initialInstances', $any($event.target).value)\"\r\n />\r\n <p class=\"fb-field__hint\">\r\n Quante righe trovare <strong>gia\u2019 aperte</strong> arrivando sulla schermata, ed e\u2019 un\u2019altra\r\n cosa dal minimo: il minimo e\u2019 un vincolo, queste sono una comodita\u2019 e si possono togliere\r\n tutte. Un punto luce e un punto gas gia\u2019 aperti, dove chi ha una sola commodity toglie\r\n quello che non gli serve, sono <code>1</code> qui con <code>0</code> come minimo; col\r\n minimo a 1 sarebbe un modulo che <strong>obbliga</strong> a entrambe.\r\n </p>\r\n <p class=\"fb-field__hint\">\r\n Si applicano <strong>una volta sola</strong>, e solo se nessuno ha ancora scritto la\r\n collection: righe arrivate da un ciclo o da una schermata precedente <em>sono</em> le righe\r\n iniziali e non vengono diluite con righe vuote, e chi le toglie tutte non se le ritrova.\r\n </p>\r\n @if (repeaterInitialOutOfRange(); as issue) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">{{ issue }}</p>\r\n }\r\n </div>\r\n\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"selectedField()!.allowAdd !== false\"\r\n (change)=\"setRepeaterFlag('allowAdd', $any($event.target).checked)\"\r\n />\r\n L\u2019utente puo\u2019 aggiungere righe\r\n </label>\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"selectedField()!.allowRemove !== false\"\r\n (change)=\"setRepeaterFlag('allowRemove', $any($event.target).checked)\"\r\n />\r\n L\u2019utente puo\u2019 togliere righe\r\n </label>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Scritta del comando \u00ABaggiungi\u00BB</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"selectedField()!.addButtonLabel || ''\"\r\n (input)=\"setFieldProperty('addButtonLabel', $any($event.target).value)\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Scritta del comando \u00ABtogli\u00BB</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"selectedField()!.removeButtonLabel || ''\"\r\n (input)=\"setFieldProperty('removeButtonLabel', $any($event.target).value)\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Intestazione della singola riga</label>\r\n <!-- `commitOn=\"change\"`: mutare il documento ridisegna l'anteprima. -->\r\n <fb-merge-text-editor\r\n [text]=\"selectedField()!.itemLabel || ''\"\r\n [rows]=\"2\"\r\n commitOn=\"change\"\r\n placeholder=\"Punto {{ '{' }}!Cursore.PodId}\"\r\n ariaLabel=\"Intestazione della riga\"\r\n (textChange)=\"setFieldProperty('itemLabel', $event)\"\r\n >\r\n <p class=\"fb-field__hint\">\r\n Risolta <strong>sulla riga</strong>: i merge field qui nominano il cursore\r\n (<code>&#123;!{{ selectedField()!.itemReference || 'Cursore' }}.Membro&#125;</code>), non\r\n un campo della riga.\r\n </p>\r\n </fb-merge-text-editor>\r\n </div>\r\n\r\n @if (!selectedField()!.fields?.length) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Il modello della riga e\u2019 vuoto: la sezione non mostra niente (SCREEN_CONTAINER_EMPTY).\r\n </p>\r\n }\r\n </fieldset>\r\n }\r\n\r\n <!-- --------------------------------------------------- ComponentInstance -->\r\n @if (selectedField()!.fieldType === 'ComponentInstance') {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Componente</legend>\r\n <p class=\"fb-section__note\">\r\n Componenti e form sono la stessa domanda al frontend \u2014 cosa sa rendere, e con quali parametri\r\n \u2014 e passano dallo stesso catalogo. Un <code>ComponentInstance</code> non ha un valore proprio:\r\n lo hanno i suoi output.\r\n </p>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Componente</label>\r\n <fb-name-picker\r\n [value]=\"selectedField()!.extensionName\"\r\n [options]=\"componentOptions()\"\r\n [unusableOptions]=\"unusableComponents()\"\r\n label=\"Componente\"\r\n placeholder=\"Scrivi o scegli un componente\"\r\n [isMono]=\"false\"\r\n unknownMessage=\"Questo componente non esiste nel catalogo: e\u2019 SCREEN_COMPONENT_UNKNOWN.\"\r\n unknownSeverity=\"error\"\r\n unusableMessage=\"Questo nome e\u2019 una schermata intera, non un componente montabile qui (FORM_KIND_MISMATCH).\"\r\n emptyMessage=\"Il catalogo dei componenti non e\u2019 popolato: il nome non viene verificato.\"\r\n (valueChange)=\"setFieldProperty('extensionName', $event ?? '')\"\r\n />\r\n @if (!selectedField()!.extensionName) {\r\n <p class=\"fb-field__error\">Obbligatorio: senza, SCREEN_COMPONENT_MISSING.</p>\r\n }\r\n </div>\r\n\r\n <!--\r\n \u00A75.2 \u2014 dentro una riga la spunta **non c'e'**: gli output automatici si espongono come\r\n `<NomeCampo>.<NomeOutput>`, che sarebbe **una risorsa sola per tutte le righe** \u2014 l'ultima\r\n che ha girato. \u00C8 `REPEATING_SECTION_INVALID`, e la strada giusta e' `assignToReference` su\r\n un membro del cursore, che una riga ce l'ha per davvero.\r\n -->\r\n @if (!isInsideRepeater()) {\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"selectedField()!.storeOutputAutomatically === true\"\r\n (change)=\"setStoreOutputAutomatically($any($event.target).checked)\"\r\n />\r\n Rendi gli output referenziabili automaticamente\r\n </label>\r\n @if (selectedField()!.storeOutputAutomatically) {\r\n <p class=\"fb-field__hint\">\r\n Gli output si referenziano come\r\n <code>{{ selectedField()!.name || 'Campo' }}.nomeOutput</code>, senza dichiarare\r\n variabili.\r\n </p>\r\n }\r\n @if (hasOutputConflict()) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n Output automatici <strong>e</strong> parametri di uscita insieme:\r\n OUTPUT_CONFIGURATION_CONFLICT.\r\n </p>\r\n }\r\n } @else {\r\n <p class=\"fb-field__hint\">\r\n Gli output automatici qui non si possono usare: si esporrebbero come\r\n <code>{{ selectedField()!.name || 'Campo' }}.nomeOutput</code>, cioe\u2019 una risorsa sola per\r\n tutte le righe \u2014 l\u2019ultima che ha girato (REPEATING_SECTION_INVALID). Assegnali con\r\n <strong>assignToReference</strong> a un membro di\r\n <code>{{ rowCursor() || 'Cursore' }}</code>.\r\n </p>\r\n @if (selectedField()!.storeOutputAutomatically) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n Questo componente dichiara gli output automatici e sta dentro una riga:\r\n REPEATING_SECTION_INVALID.\r\n <button type=\"button\" class=\"fb-btn fb-btn--sm\" (click)=\"setStoreOutputAutomatically(false)\">\r\n Togli\r\n </button>\r\n </p>\r\n }\r\n }\r\n\r\n <fb-parameter-editor\r\n [holder]=\"$any(selectedField())\"\r\n [catalogParameters]=\"componentParameterList()\"\r\n [preferredRoots]=\"preferredRoots()\"\r\n inputTitle=\"Valori passati al componente\"\r\n outputTitle=\"Valori raccolti dal componente\"\r\n [showOutputs]=\"!selectedField()!.storeOutputAutomatically\"\r\n outputsDisabledReason=\"Gli output sono automatici: disattivalo per assegnarli a variabili.\"\r\n (changed)=\"onComponentParametersChanged($event)\"\r\n />\r\n </fieldset>\r\n }\r\n\r\n <!-- ------------------------------------------------------- visibilita' -->\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Visibilita\u2019</legend>\r\n <p class=\"fb-section__note\">\r\n Le regole si rivalutano <strong>sui valori appena inviati</strong>: e\u2019 cos\u00EC che un campo compare\r\n in funzione di un altro campo della stessa schermata. Un campo risultato nascosto viene\r\n <strong>azzerato</strong> e non viene validato.\r\n </p>\r\n <fb-condition-editor\r\n [holder]=\"visibilityRule()\"\r\n title=\"Mostra il campo quando\"\r\n [allowFormula]=\"false\"\r\n [preferredRoots]=\"preferredRoots()\"\r\n [issuePath]=\"'fields[' + (selectedField()!.name || '') + '].visibilityRule'\"\r\n (changed)=\"onVisibilityChanged($event)\"\r\n />\r\n </fieldset>\r\n }\r\n </aside>\r\n</div>\r\n\r\n<fb-connector-editor\r\n [nodeName]=\"name()\"\r\n [node]=\"node()\"\r\n [outlets]=\"outlets()\"\r\n (connectorChanged)=\"onConnectorChanged($event)\"\r\n/>\r\n\r\n<!--\r\n ============================================================== l'anteprima\r\n Due template ricorsivi. `listTpl` disegna i figli di un contenitore, e nient'altro: `data-item`\r\n marca cio' che conta come fratello nel calcolo del punto di rilascio, e il segnaposto del\r\n contenitore vuoto ne resta fuori. La barra di inserimento non e' qui \u2014 sta sopra l'anteprima,\r\n vedi il velo nel frame.\r\n-->\r\n<ng-template #listTpl let-fields let-parent=\"parent\" let-axis=\"axis\">\r\n @for (child of fields; track $index) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"fieldTpl\"\r\n [ngTemplateOutletContext]=\"{ $implicit: child, path: childPath(parent, $index), axis: axis }\"\r\n />\r\n }\r\n @if (!fields.length) {\r\n <p class=\"fb-scr__drop-hint\">Trascina qui un componente</p>\r\n }\r\n</ng-template>\r\n\r\n<ng-template #fieldTpl let-field let-path=\"path\" let-axis=\"axis\">\r\n <div\r\n class=\"fb-scr__item\"\r\n data-item=\"\"\r\n cdkDrag\r\n [style.grid-column]=\"'span ' + widthOf(field)\"\r\n [class.fb-scr__item--selected]=\"isSelected(path)\"\r\n [class.fb-scr__item--dragging]=\"isDragging(path)\"\r\n [class.fb-scr__item--container]=\"holdsFields(field)\"\r\n (cdkDragStarted)=\"onFieldDragStarted(path)\"\r\n (cdkDragMoved)=\"onFieldDragMoved($event)\"\r\n (cdkDragEnded)=\"onFieldDragEnded($event, path)\"\r\n (click)=\"select(path); $event.stopPropagation()\"\r\n >\r\n <span class=\"fb-scr__grip\" cdkDragHandle title=\"Trascina per spostare\" aria-hidden=\"true\">\u283F</span>\r\n @if (field.visibilityRule?.conditions?.length) {\r\n <span class=\"fb-scr__flag\" title=\"Ha una regola di visibilita\u2019\">\u25D0</span>\r\n }\r\n\r\n @switch (field.fieldType) {\r\n @case ('RegionContainer') {\r\n <div class=\"fb-scr__section\">\r\n @if (field.regionContainerType !== 'SectionWithoutHeader') {\r\n <header class=\"fb-scr__section-head\">{{ field.fieldText || field.name }}</header>\r\n }\r\n <div class=\"fb-scr__cols\" [attr.data-drop]=\"dropId(path)\" data-axis=\"row\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"listTpl\"\r\n [ngTemplateOutletContext]=\"{ $implicit: field.fields || [], parent: path, axis: 'row' }\"\r\n />\r\n </div>\r\n </div>\r\n }\r\n @case ('Region') {\r\n <div class=\"fb-scr__region\">\r\n <span class=\"fb-scr__region-tag\">{{ field.name }} \u00B7 {{ widthOf(field) }}/12</span>\r\n <div class=\"fb-scr__region-body\" [attr.data-drop]=\"dropId(path)\" data-axis=\"column\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"listTpl\"\r\n [ngTemplateOutletContext]=\"{ $implicit: field.fields || [], parent: path, axis: 'column' }\"\r\n />\r\n </div>\r\n </div>\r\n }\r\n @case ('RepeatingSection') {\r\n <!--\r\n \u00A75.2 \u2014 si disegna **una** riga, ed e' il modello: a runtime la schermata ne rende una copia\r\n per elemento della collection. Disegnarne due sarebbe finto \u2014 quante siano lo decide chi\r\n compila \u2014 e disegnarla senza cornice farebbe credere che i campi siano della schermata.\r\n -->\r\n <div class=\"fb-scr__repeat\">\r\n <header class=\"fb-scr__repeat-head\">\r\n <span>{{ field.fieldText || field.name }}</span>\r\n <span class=\"fb-scr__repeat-meta\">\r\n {{ field.collectionReference || 'collection non indicata' }}\r\n @if (field.minInstances !== undefined || field.maxInstances !== undefined) {\r\n \u00B7 {{ field.minInstances ?? 0 }}\u2013{{ field.maxInstances ?? '\u221E' }}\r\n }\r\n </span>\r\n </header>\r\n <div class=\"fb-scr__repeat-row\">\r\n <span class=\"fb-scr__repeat-tag\">\r\n {{ field.itemLabel || 'Riga 1' }}\r\n @if (field.allowRemove !== false) {\r\n <span class=\"fb-scr__repeat-x\" aria-hidden=\"true\">\u00D7</span>\r\n }\r\n </span>\r\n <div class=\"fb-scr__repeat-body\" [attr.data-drop]=\"dropId(path)\" data-axis=\"column\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"listTpl\"\r\n [ngTemplateOutletContext]=\"{ $implicit: field.fields || [], parent: path, axis: 'column' }\"\r\n />\r\n </div>\r\n </div>\r\n @if (field.allowAdd !== false) {\r\n <span class=\"fb-scr__repeat-add\">+ {{ field.addButtonLabel || 'Aggiungi' }}</span>\r\n }\r\n </div>\r\n }\r\n @case ('DisplayText') {\r\n <p class=\"fb-scr__display\">{{ field.fieldText || '(testo vuoto)' }}</p>\r\n }\r\n @case ('LargeTextArea') {\r\n <label class=\"fb-scr__label\">\r\n {{ captionOf(field) }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__control fb-scr__control--area\">{{ placeholderOf(field) }}</div>\r\n }\r\n @case ('PasswordField') {\r\n <label class=\"fb-scr__label\">\r\n {{ captionOf(field) }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__control\">\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022</div>\r\n }\r\n @case ('DropdownBox') {\r\n <label class=\"fb-scr__label\">\r\n {{ captionOf(field) }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__control fb-scr__control--select\">\r\n <span>{{ field.defaultSelectedChoiceReference || choiceLabelsOf(field)[0] || '\u2014 scegli \u2014' }}</span>\r\n <span aria-hidden=\"true\">\u25BE</span>\r\n </div>\r\n }\r\n @case ('MultiSelectPicklist') {\r\n <label class=\"fb-scr__label\">\r\n {{ captionOf(field) }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__control fb-scr__control--select\">\r\n <span>{{ choiceLabelsOf(field).join(', ') || '\u2014 scegli \u2014' }}</span>\r\n <span aria-hidden=\"true\">\u2261</span>\r\n </div>\r\n }\r\n @case ('RadioButtons') {\r\n <label class=\"fb-scr__label\">\r\n {{ captionOf(field) }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__options\">\r\n @for (option of choiceLabelsOf(field); track $index) {\r\n <span class=\"fb-scr__option\">\u25EF {{ option }}</span>\r\n }\r\n @if (!choiceLabelsOf(field).length) {\r\n <span class=\"fb-scr__option fb-scr__option--missing\">Nessuna opzione</span>\r\n }\r\n </div>\r\n }\r\n @case ('MultiSelectCheckboxes') {\r\n <label class=\"fb-scr__label\">\r\n {{ captionOf(field) }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__options\">\r\n @for (option of choiceLabelsOf(field); track $index) {\r\n <span class=\"fb-scr__option\">\u2610 {{ option }}</span>\r\n }\r\n @if (!choiceLabelsOf(field).length) {\r\n <span class=\"fb-scr__option fb-scr__option--missing\">Nessuna opzione</span>\r\n }\r\n </div>\r\n }\r\n @case ('ComponentInstance') {\r\n <!--\r\n L\u2019etichetta si disegna se c\u2019e\u2019: il runtime la consegna in `label` come per gli altri\r\n campi (\u00A75.2). Nessun asterisco: un componente non raccoglie un valore, quindi\r\n \u00ABobbligatorio\u00BB non ha niente su cui applicarsi.\r\n -->\r\n @if (field.fieldText) {\r\n <label class=\"fb-scr__label\">{{ field.fieldText }}</label>\r\n }\r\n <div class=\"fb-scr__component\">\r\n <span aria-hidden=\"true\">\u2B21</span>\r\n {{ field.extensionName || 'Componente non indicato' }}\r\n </div>\r\n }\r\n @case ('ObjectProvided') {\r\n <label class=\"fb-scr__label\">\r\n {{ field.fieldText || field.objectFieldReference || field.name }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__control\">{{ placeholderOf(field) }}</div>\r\n <span class=\"fb-scr__tag\">{{ field.objectFieldReference || 'campo non indicato' }}</span>\r\n }\r\n @default {\r\n <label class=\"fb-scr__label\">\r\n {{ captionOf(field) }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__control\">{{ placeholderOf(field) }}</div>\r\n @if (isUnknownType(field)) {\r\n <span class=\"fb-scr__tag fb-scr__tag--warn\">{{ field.fieldType }}: tipo non nel dizionario</span>\r\n }\r\n }\r\n }\r\n <!--\r\n Il formato sta **fuori** dal @switch perche' non e' del tipo di campo: e' di cio' che il campo\r\n raccoglie, e va visto anche dove non si applica \u2014 e\u2019 l\u00EC che si riconosce un\r\n SCREEN_FIELD_FORMAT_NOT_APPLICABLE senza aprire le proprieta\u2019.\r\n -->\r\n @if (field.format) {\r\n <span class=\"fb-scr__tag\">{{ field.format }}</span>\r\n }\r\n </div>\r\n</ng-template>\r\n", styles: [".fb-scr{display:grid;grid-template-columns:220px minmax(0,1fr) 340px;gap:12px;align-items:start;margin-bottom:14px}@media(max-width:1100px){.fb-scr{grid-template-columns:minmax(0,1fr)}}.fb-scr__side,.fb-scr__props{min-width:0;padding:10px;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius, 10px);background:var(--fb-surface-alt, #f7f8fa)}.fb-scr__tabs{display:flex;gap:4px;margin-bottom:8px}.fb-scr__tab{flex:1;padding:5px 8px;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-xs, 6px);background:var(--fb-surface, #fff);color:var(--fb-text-muted, #6b7086);font:inherit;font-size:11px;font-weight:600;cursor:pointer}.fb-scr__tab--active{border-color:var(--fb-accent, #4f6ef7);background:color-mix(in srgb,var(--fb-accent, #4f6ef7) 10%,transparent);color:var(--fb-accent-strong, #3d59e0)}.fb-scr__side-hint,.fb-scr__side-empty{margin:6px 0;font-size:11px;color:var(--fb-text-muted, #6b7086)}.fb-scr__palette{display:flex;flex-direction:column;gap:4px;margin:8px 0 0;padding:0;max-height:320px;overflow-y:auto;overscroll-behavior:contain;list-style:none}.fb-scr__chip{display:flex;align-items:center;gap:8px;width:100%;padding:6px 8px;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-xs, 6px);background:var(--fb-surface, #fff);color:var(--fb-text, #1a1c23);font:inherit;font-size:12px;text-align:left;cursor:grab}.fb-scr__chip:hover{border-color:var(--fb-accent, #4f6ef7)}.fb-scr__chip-icon{flex:none;width:20px;text-align:center;color:var(--fb-text-muted, #6b7086)}.fb-scr__chip-label{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-scr__chip-label small{margin-left:4px;color:var(--fb-text-subtle, #98a2b3);font-size:10px}.fb-scr__canvas{min-width:0;padding:14px;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius, 10px);background:var(--fb-canvas-bg, #f4f5f7);max-height:60vh;overflow-y:auto}.fb-scr__frame{position:relative;display:flex;flex-direction:column;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-sm, 8px);background:var(--fb-surface, #fff);box-shadow:var(--fb-shadow-sm, 0 1px 2px rgb(16 24 40 / 6%));overflow:hidden}.fb-scr__frame-head{display:flex;align-items:center;gap:6px;padding:8px 12px;border-bottom:1px solid var(--fb-border-subtle, #eef0f4);font-size:13px;font-weight:600}.fb-scr__help{display:inline-flex;align-items:center;justify-content:center;width:15px;height:15px;border-radius:50%;background:var(--fb-surface-sunken, #eef0f4);color:var(--fb-text-muted, #6b7086);font-size:10px}.fb-scr__body,.fb-scr__region-body{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));align-content:start;gap:8px 0;padding:12px;min-height:90px}.fb-scr__region-body{padding:8px;min-height:60px}.fb-scr__cols{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));align-items:stretch;gap:0;padding:8px;min-height:60px}.fb-scr__cols>.fb-scr__item{display:flex;align-self:stretch}.fb-scr__cols>.fb-scr__item>.fb-scr__region{flex:1;min-width:0}.fb-scr__drop-hint{grid-column:1 / -1;margin:0;padding:10px;border:1px dashed var(--fb-border-strong, #cfd4de);border-radius:var(--fb-radius-xs, 6px);color:var(--fb-text-subtle, #98a2b3);font-size:11px;text-align:center}.fb-scr__marker{position:absolute;z-index:2;border-radius:2px;background:var(--fb-accent, #4f6ef7);pointer-events:none}.fb-scr__item{position:relative;box-sizing:border-box;padding:6px 8px;border:1px solid transparent;border-radius:var(--fb-radius-xs, 6px);cursor:pointer}.fb-scr__item:hover{border-color:var(--fb-border-strong, #cfd4de);background:color-mix(in srgb,var(--fb-accent, #4f6ef7) 4%,transparent)}.fb-scr__item--selected{border-color:var(--fb-accent, #4f6ef7);box-shadow:inset 0 0 0 1px var(--fb-accent, #4f6ef7)}.fb-scr__item--dragging{opacity:.45;pointer-events:none}.fb-scr__grip{position:absolute;top:2px;left:-2px;padding:0 3px;color:var(--fb-text-subtle, #98a2b3);font-size:11px;line-height:1;opacity:0;cursor:grab}.fb-scr__item:hover>.fb-scr__grip,.fb-scr__item--selected>.fb-scr__grip{opacity:1}.fb-scr__flag{position:absolute;top:2px;right:4px;color:var(--fb-text-muted, #6b7086);font-size:11px}.fb-scr__label{display:block;margin-bottom:3px;font-size:11px;font-weight:600;color:var(--fb-text, #1a1c23)}.fb-scr__req{color:var(--fb-error, #dc2626)}.fb-scr__control{display:flex;align-items:center;justify-content:space-between;gap:6px;min-height:26px;padding:4px 8px;border:1px solid var(--fb-border-strong, #cfd4de);border-radius:var(--fb-radius-xs, 6px);background:var(--fb-surface, #fff);color:var(--fb-text-subtle, #98a2b3);font-size:12px;pointer-events:none}.fb-scr__control--area{min-height:54px;align-items:flex-start}.fb-scr__display{margin:0;font-size:12px;color:var(--fb-text, #1a1c23)}.fb-scr__options{display:flex;flex-direction:column;gap:2px;pointer-events:none}.fb-scr__option{font-size:12px;color:var(--fb-text-muted, #6b7086)}.fb-scr__option--missing{color:var(--fb-warning, #b7791f)}.fb-scr__component{display:flex;align-items:center;gap:6px;padding:10px;border:1px dashed var(--fb-accent, #4f6ef7);border-radius:var(--fb-radius-xs, 6px);background:color-mix(in srgb,var(--fb-accent, #4f6ef7) 5%,transparent);font-size:12px;color:var(--fb-text-muted, #6b7086)}.fb-scr__tag{display:inline-block;margin-top:3px;font-size:10px;color:var(--fb-text-subtle, #98a2b3)}.fb-scr__tag--warn{color:var(--fb-warning, #b7791f)}.fb-scr__section{border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-xs, 6px);background:var(--fb-surface-alt, #f7f8fa)}.fb-scr__section-head{padding:5px 10px;border-bottom:1px solid var(--fb-border-subtle, #eef0f4);font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--fb-text-muted, #6b7086)}.fb-scr__region{border:1px dashed var(--fb-border-strong, #cfd4de);border-radius:var(--fb-radius-xs, 6px);background:var(--fb-surface, #fff)}.fb-scr__region-tag{display:block;padding:3px 6px 0;font-size:10px;color:var(--fb-text-subtle, #98a2b3)}.fb-scr__repeat{border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-xs, 6px);background:var(--fb-surface-alt, #f7f8fa)}.fb-scr__repeat-head{display:flex;align-items:baseline;gap:8px;padding:5px 10px;border-bottom:1px solid var(--fb-border-subtle, #eef0f4);font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--fb-text-muted, #6b7086)}.fb-scr__repeat-meta{margin-left:auto;font-size:10px;font-weight:400;text-transform:none;letter-spacing:0;color:var(--fb-text-subtle, #98a2b3)}.fb-scr__repeat-row{margin:8px;border:1px dashed var(--fb-border-strong, #cfd4de);border-radius:var(--fb-radius-xs, 6px);background:var(--fb-surface, #fff)}.fb-scr__repeat-tag{display:flex;align-items:center;gap:6px;padding:3px 6px 0;font-size:10px;color:var(--fb-text-subtle, #98a2b3)}.fb-scr__repeat-x{margin-left:auto}.fb-scr__repeat-body{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));align-content:start;gap:8px 0;padding:8px;min-height:60px}.fb-scr__repeat-add{display:inline-block;margin:0 8px 8px;padding:3px 10px;border:1px dashed var(--fb-border-strong, #cfd4de);border-radius:var(--fb-radius-xs, 6px);font-size:11px;color:var(--fb-text-muted, #6b7086)}.fb-scr__frame-foot{display:flex;align-items:center;gap:6px;padding:8px 12px;border-top:1px solid var(--fb-border-subtle, #eef0f4);background:var(--fb-surface-alt, #f7f8fa)}.fb-scr__spacer{flex:1}.fb-scr__btn{padding:3px 10px;border:1px solid var(--fb-border-strong, #cfd4de);border-radius:var(--fb-radius-xs, 6px);font-size:11px;color:var(--fb-text-muted, #6b7086)}.fb-scr__btn--primary{border-color:var(--fb-accent, #4f6ef7);background:var(--fb-accent, #4f6ef7);color:var(--fb-accent-contrast, #fff)}.fb-scr__canvas-note{margin-top:10px}.fb-scr__props{background:var(--fb-surface, #fff)}.fb-scr__props-head{display:flex;align-items:center;gap:8px;margin-bottom:8px}.fb-scr__props-title{flex:1;min-width:0;margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--fb-text-muted, #6b7086)}.fb-scr__props-actions{display:flex;flex-wrap:wrap;gap:3px}.fb-btn--sm{padding:3px 7px;font-size:11px}.fb-scr__width{display:flex;align-items:center;gap:8px}.fb-scr__width input[type=range]{flex:1;min-width:0}.fb-scr__width-value{font-size:11px;color:var(--fb-text-muted, #6b7086)}\n"], dependencies: [{ kind: "directive", type: CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "component", type: ConditionEditorComponent, selector: "fb-condition-editor", inputs: ["holder", "title", "allowFormula", "preferredRoots", "allowLogic", "issuePath"], outputs: ["changed"] }, { kind: "component", type: ConnectorEditorComponent, selector: "fb-connector-editor", inputs: ["nodeName", "node", "outlets", "title"], outputs: ["connectorChanged"] }, { 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", "preferredRoots", "commitOn"], outputs: ["expressionChange"] }, { kind: "component", type: MergeTextEditorComponent, selector: "fb-merge-text-editor", inputs: ["text", "usage", "placeholder", "ariaLabel", "disabled", "rows", "commitOn"], outputs: ["textChange"] }, { kind: "component", type: NamePickerComponent, selector: "fb-name-picker", inputs: ["value", "options", "label", "placeholder", "disabled", "unknownMessage", "unknownSeverity", "unusableOptions", "unusableMessage", "emptyMessage", "isMono", "commitOn"], outputs: ["valueChange"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: ObjectPickerComponent, selector: "fb-object-picker", inputs: ["value", "label", "placeholder", "disabled"], outputs: ["valueChange"] }, { kind: "component", type: ParameterEditorComponent, selector: "fb-parameter-editor", inputs: ["holder", "catalogParameters", "inputTitle", "outputTitle", "showInputs", "showOutputs", "outputsDisabledReason", "extraTargets", "preferredRoots"], outputs: ["changed"] }, { kind: "component", type: ReferencePickerComponent, selector: "fb-reference-picker", inputs: ["value", "label", "placeholder", "disabled", "dataType", "isCollection", "objectType", "writableOnly", "elementsOnly", "extraReferences", "preferredRoots"], outputs: ["valueChange"] }, { kind: "directive", type: SelectValueDirective, selector: "select[fbValue]", inputs: ["fbValue"] }, { kind: "component", type: StructureMemberPickerComponent, selector: "fb-structure-member-picker", inputs: ["value", "className", "usage", "label", "placeholder", "disabled", "unverified"], outputs: ["valueChange"] }, { 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", "preferredRoots"], outputs: ["valueChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
13862
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.32", type: DynamicScreenInspectorComponent, isStandalone: true, selector: "fb-dynamic-screen-inspector", inputs: { initialSelection: { classPropertyName: "initialSelection", publicName: "initialSelection", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "composer", first: true, predicate: ["composer"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<div class=\"fb-scr\" #composer>\r\n <!-- ============================================================ sinistra -->\r\n <aside class=\"fb-scr__side\" aria-label=\"Componenti e campi\">\r\n <div class=\"fb-scr__tabs\" role=\"tablist\">\r\n <button\r\n type=\"button\"\r\n role=\"tab\"\r\n class=\"fb-scr__tab\"\r\n [class.fb-scr__tab--active]=\"paletteTab() === 'components'\"\r\n [attr.aria-selected]=\"paletteTab() === 'components'\"\r\n (click)=\"setPaletteTab('components')\"\r\n >\r\n Componenti\r\n </button>\r\n <button\r\n type=\"button\"\r\n role=\"tab\"\r\n class=\"fb-scr__tab\"\r\n [class.fb-scr__tab--active]=\"paletteTab() === 'fields'\"\r\n [attr.aria-selected]=\"paletteTab() === 'fields'\"\r\n (click)=\"setPaletteTab('fields')\"\r\n >\r\n Campi\r\n </button>\r\n </div>\r\n\r\n @if (paletteTab() === 'components') {\r\n @if (!dictionary.screenFieldTypes().length) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Il dizionario <code>screenFieldTypes</code> non e\u2019 disponibile: senza i suoi flag l\u2019editor non\r\n sa quali campi ammette ciascun tipo, e non li inventa.\r\n </p>\r\n }\r\n <p class=\"fb-scr__side-hint\">Trascina sulla schermata, oppure clicca per aggiungere.</p>\r\n <ul class=\"fb-scr__palette\">\r\n @for (entry of dictionary.screenFieldTypes(); track entry.value) {\r\n <li>\r\n <!--\r\n \u00A75.2 \u2014 l'unico chip che si spegne: una sezione ripetibile dentro un'altra. Non si\r\n annidano, e il cursore e' una variabile sola per sezione.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-scr__chip\"\r\n cdkDrag\r\n [disabled]=\"!canAddAtSelection(entry.value)\"\r\n [title]=\"\r\n canAddAtSelection(entry.value)\r\n ? entry.description || entry.label\r\n : 'Le sezioni ripetibili non si annidano: seleziona un punto fuori dalla riga.'\r\n \"\r\n (cdkDragMoved)=\"onPaletteDragMoved($event)\"\r\n (cdkDragEnded)=\"onComponentDragEnded($event, entry.value)\"\r\n (click)=\"addComponentAtSelection(entry.value)\"\r\n >\r\n <span class=\"fb-scr__chip-icon\" aria-hidden=\"true\">{{ iconOf(entry.value) }}</span>\r\n <span class=\"fb-scr__chip-label\">{{ entry.label }}</span>\r\n </button>\r\n </li>\r\n }\r\n </ul>\r\n } @else {\r\n <p class=\"fb-scr__side-hint\">\r\n Un campo dell\u2019entita\u2019 porta con se\u2019 tipo, etichetta, obbligatorieta\u2019 e opzioni: arrivano dallo\r\n schema dati, qui non si ridichiarano.\r\n </p>\r\n <!-- Il catalogo non e' un dizionario chiuso: si sceglie o si scrive, come ovunque si\r\n indichi un'entita' (vedi `object-picker`). -->\r\n <fb-object-picker\r\n [value]=\"fieldsObject()\"\r\n label=\"Entita\u2019\"\r\n placeholder=\"Scrivi o scegli un\u2019entita\u2019\"\r\n (valueChange)=\"setFieldsObject($event)\"\r\n />\r\n @if (fieldsObject() && !fieldsOfObject().length) {\r\n <p class=\"fb-scr__side-empty\">\r\n Nessun campo dichiarato per questa entita\u2019: il catalogo non li espone, non significa che non\r\n ci siano.\r\n </p>\r\n }\r\n <ul class=\"fb-scr__palette\">\r\n @for (field of fieldsOfObject(); track $index) {\r\n <li>\r\n <button\r\n type=\"button\"\r\n class=\"fb-scr__chip\"\r\n cdkDrag\r\n [title]=\"field.name + (field.dataType ? ' \u00B7 ' + field.dataType : '')\"\r\n (cdkDragMoved)=\"onPaletteDragMoved($event)\"\r\n (cdkDragEnded)=\"onObjectFieldDragEnded($event, field)\"\r\n (click)=\"addObjectFieldAtSelection(field)\"\r\n >\r\n <span class=\"fb-scr__chip-icon\" aria-hidden=\"true\">\u2317</span>\r\n <span class=\"fb-scr__chip-label\">\r\n {{ field.label || field.name }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n <small>{{ field.dataType }}</small>\r\n </span>\r\n </button>\r\n </li>\r\n }\r\n </ul>\r\n }\r\n </aside>\r\n\r\n <!-- ============================================================== centro -->\r\n <!-- Cliccare fuori da un campo riporta a destra le proprieta' della schermata. -->\r\n <section class=\"fb-scr__canvas\" aria-label=\"Anteprima della schermata\" (click)=\"selectScreen()\">\r\n <div class=\"fb-scr__frame\">\r\n @if (showHeader()) {\r\n <header class=\"fb-scr__frame-head\">\r\n {{ $any(node()).label || name() }}\r\n @if (screen().helpText) {\r\n <span class=\"fb-scr__help\" [title]=\"screen().helpText || ''\">?</span>\r\n }\r\n </header>\r\n }\r\n\r\n <div\r\n class=\"fb-scr__body\"\r\n [attr.data-drop]=\"''\"\r\n data-axis=\"column\"\r\n [class.fb-scr__body--empty]=\"isEmpty()\"\r\n >\r\n <ng-container\r\n [ngTemplateOutlet]=\"listTpl\"\r\n [ngTemplateOutletContext]=\"{ $implicit: screen().fields || [], parent: [], axis: 'column' }\"\r\n />\r\n </div>\r\n\r\n <!--\r\n La barra di inserimento e' un **velo** sopra l'anteprima, non un figlio del contenitore di\r\n rilascio: dentro la griglia occupava una riga intera e spostava in basso proprio la sezione\r\n che si stava puntando, che allora usciva da sotto il puntatore e il bersaglio oscillava.\r\n -->\r\n @if (dropMarker(); as bar) {\r\n <div\r\n class=\"fb-scr__marker\"\r\n [style.left.px]=\"bar.left\"\r\n [style.top.px]=\"bar.top\"\r\n [style.width.px]=\"bar.width\"\r\n [style.height.px]=\"bar.height\"\r\n ></div>\r\n }\r\n\r\n @if (showFooter()) {\r\n <footer class=\"fb-scr__frame-foot\">\r\n @if (allowPause()) {\r\n <span class=\"fb-scr__btn fb-scr__btn--ghost\">{{ screen().pauseButtonLabel || 'Pausa' }}</span>\r\n }\r\n <span class=\"fb-scr__spacer\"></span>\r\n @if (allowBack()) {\r\n <span class=\"fb-scr__btn fb-scr__btn--ghost\">{{ screen().backButtonLabel || 'Indietro' }}</span>\r\n }\r\n <span class=\"fb-scr__btn fb-scr__btn--primary\">\r\n {{ screen().nextOrFinishButtonLabel || (allowFinish() ? 'Fine' : 'Avanti') }}\r\n </span>\r\n </footer>\r\n }\r\n </div>\r\n\r\n @if (isEmpty()) {\r\n <p class=\"fb-callout fb-callout--warn fb-scr__canvas-note\">\r\n La schermata non ha campi: non c\u2019e\u2019 niente da mostrare all\u2019utente (SCREEN_WITHOUT_FIELDS).\r\n </p>\r\n }\r\n </section>\r\n\r\n <!-- ============================================================== destra -->\r\n <aside class=\"fb-scr__props\" aria-label=\"Proprieta\u2019\">\r\n @if (!selectedField()) {\r\n <!-- ------------------------------------------------ proprieta' della schermata -->\r\n <header class=\"fb-scr__props-head\">\r\n <h3 class=\"fb-scr__props-title\">Schermata</h3>\r\n </header>\r\n <p class=\"fb-scr__side-hint\">Seleziona un campo nell\u2019anteprima per configurarlo.</p>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Testo di aiuto</label>\r\n <textarea\r\n class=\"fb-textarea\"\r\n [value]=\"screen().helpText || ''\"\r\n (input)=\"setScreenText('helpText', $any($event.target).value)\"\r\n ></textarea>\r\n </div>\r\n\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Navigazione</legend>\r\n <p class=\"fb-section__note\">\r\n Questi flag sono un\u2019intenzione, non la verita\u2019 finale: a runtime il motore comunica\r\n <code>canGoBack</code>, <code>canFinish</code> e <code>canPause</code> nella richiesta della\r\n schermata.\r\n </p>\r\n <label class=\"fb-check\">\r\n <input type=\"checkbox\" [checked]=\"allowBack()\" (change)=\"setScreenFlag('allowBack', $any($event.target).checked)\" />\r\n Consenti \u00ABindietro\u00BB\r\n </label>\r\n <label class=\"fb-check\">\r\n <input type=\"checkbox\" [checked]=\"allowFinish()\" (change)=\"setScreenFlag('allowFinish', $any($event.target).checked)\" />\r\n Consenti \u00ABfine\u00BB\r\n </label>\r\n <label class=\"fb-check\">\r\n <input type=\"checkbox\" [checked]=\"allowPause()\" (change)=\"setScreenFlag('allowPause', $any($event.target).checked)\" />\r\n Consenti \u00ABpausa\u00BB\r\n </label>\r\n <label class=\"fb-check\">\r\n <input type=\"checkbox\" [checked]=\"showHeader()\" (change)=\"setScreenFlag('showHeader', $any($event.target).checked)\" />\r\n Mostra l\u2019intestazione\r\n </label>\r\n <label class=\"fb-check\">\r\n <input type=\"checkbox\" [checked]=\"showFooter()\" (change)=\"setScreenFlag('showFooter', $any($event.target).checked)\" />\r\n Mostra il piede\r\n </label>\r\n\r\n @if (allowPause()) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Testo mostrato alla pausa</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"screen().pausedText || ''\"\r\n (input)=\"setScreenText('pausedText', $any($event.target).value)\"\r\n />\r\n </div>\r\n }\r\n\r\n @if (isDeadEnd()) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n Questa schermata non ha una destinazione e non consente \u00ABfine\u00BB: e\u2019 un vicolo cieco, e\r\n l\u2019utente resterebbe bloccato (SCREEN_DEAD_END).\r\n </p>\r\n }\r\n </fieldset>\r\n\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Pulsanti</legend>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Etichetta \u00ABindietro\u00BB</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"screen().backButtonLabel || ''\"\r\n (input)=\"setScreenText('backButtonLabel', $any($event.target).value)\"\r\n />\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Etichetta \u00ABavanti / fine\u00BB</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"screen().nextOrFinishButtonLabel || ''\"\r\n (input)=\"setScreenText('nextOrFinishButtonLabel', $any($event.target).value)\"\r\n />\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Etichetta \u00ABpausa\u00BB</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"screen().pauseButtonLabel || ''\"\r\n (input)=\"setScreenText('pauseButtonLabel', $any($event.target).value)\"\r\n />\r\n </div>\r\n </fieldset>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Stage mostrato nell\u2019avanzamento</label>\r\n <fb-name-picker\r\n [value]=\"screen().stageReference\"\r\n [options]=\"stageOptions()\"\r\n label=\"Stage\"\r\n placeholder=\"Scegli uno stage\"\r\n unknownMessage=\"Questo stage non e\u2019 dichiarato dal flow.\"\r\n emptyMessage=\"Il flow non dichiara stage: creali nel pannello delle risorse.\"\r\n (valueChange)=\"setScreenText('stageReference', $event ?? '')\"\r\n />\r\n </div>\r\n\r\n <!--\r\n Screen action (\u00A75.2). Due elenchi e non uno perche' il contratto li separa: piu' trigger\r\n possono invocare la stessa action, quindi \u00ABchi la chiama\u00BB non e' una sua proprieta'. Le\r\n action si aprono una alla volta: i cataloghi dipendono da tipo e nome, e tenerne N in volo\r\n sarebbe N volte la corsa fra risposte.\r\n -->\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Azioni della schermata</legend>\r\n <p class=\"fb-section__note\">\r\n Un campo che l\u2019utente compila puo\u2019 innescare un\u2019action i cui risultati finiscono negli\r\n <strong>altri campi di questa schermata</strong>: e\u2019 il caso \u00ABscrivi il codice fiscale e nome\r\n e cognome compaiono da soli\u00BB. L\u2019alternativa sarebbe spezzare la schermata in due con un\r\n elemento Action in mezzo.\r\n </p>\r\n\r\n <div class=\"fb-list\">\r\n @for (action of screenActions(); track $index) {\r\n <div class=\"fb-list__item\">\r\n <div class=\"fb-list__header\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost\"\r\n [attr.aria-expanded]=\"selectedActionIndex() === $index\"\r\n (click)=\"selectAction(selectedActionIndex() === $index ? null : $index)\"\r\n >\r\n {{ selectedActionIndex() === $index ? '\u25BE' : '\u25B8' }}\r\n {{ action.name || '(senza nome)' }}\r\n </button>\r\n <span class=\"fb-list__spacer\"></span>\r\n <span class=\"fb-scr__side-hint\">{{ action.actionName || 'action non scelta' }}</span>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n aria-label=\"Rimuovi l\u2019action\"\r\n (click)=\"removeScreenAction($index)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n\r\n @if (actionLosesResult(action)) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Questa action viene invocata ma il suo risultato si perde: aggiungi un parametro di\r\n uscita, oppure accendi l\u2019output automatico (SCREEN_ACTION_WITHOUT_OUTPUTS).\r\n </p>\r\n }\r\n @if (actionHasNoTrigger(action)) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Nessun trigger la invoca: senza, non girera\u2019 mai.\r\n </p>\r\n }\r\n <!--\r\n \u00A75.2, \u00ABUna screen action per ogni riga\u00BB \u2014 le destinazioni dipendono da chi invoca\r\n l'action, e il rilievo nomina il trigger: la stessa destinazione e' giusta per un\r\n innesco e sbagliata per un altro. Visibili anche con l'action chiusa, come gli avvisi\r\n qui sopra: dicono quali righe dell'elenco hanno qualcosa che non va.\r\n -->\r\n @for (issue of actionRowContext($index).issues; track $index) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n {{ issue.message }} (SCREEN_ACTION_ROW_TARGET_INVALID)\r\n </p>\r\n }\r\n\r\n @if (selectedActionIndex() === $index) {\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]=\"action.name || ''\"\r\n (change)=\"setActionName($index, $any($event.target).value)\"\r\n />\r\n @if (actionNameError(action); as error) {\r\n <p class=\"fb-field__error\">{{ error }}</p>\r\n }\r\n <p class=\"fb-field__hint\">\r\n E\u2019 il nome con cui la citano i trigger, e vive nello spazio dei nomi del flow. Con\r\n l\u2019output automatico e\u2019 anche la radice del riferimento:\r\n <code>{{ action.name || 'Cerca' }}.NomeOutput</code>.\r\n </p>\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Tipo di action</label>\r\n <fb-name-picker\r\n [value]=\"action.actionType\"\r\n [options]=\"actionTypeOptions()\"\r\n label=\"Tipo di action\"\r\n placeholder=\"Scrivi o scegli un tipo\"\r\n [isMono]=\"false\"\r\n unknownMessage=\"Questo tipo di action non e\u2019 fra quelli dichiarati dal sistema ospite.\"\r\n emptyMessage=\"Catalogo dei tipi di action non disponibile: puoi scrivere il nome a mano.\"\r\n (valueChange)=\"setActionType($index, $event ?? '')\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Action</label>\r\n <fb-name-picker\r\n [value]=\"action.actionName\"\r\n [options]=\"actionOptions()\"\r\n label=\"Action\"\r\n placeholder=\"Scrivi o scegli un\u2019action\"\r\n [isMono]=\"false\"\r\n unknownMessage=\"Questa action non esiste nel catalogo del tipo scelto: e\u2019 ACTION_UNKNOWN.\"\r\n unknownSeverity=\"error\"\r\n emptyMessage=\"Scegli prima il tipo di action, oppure scrivi il nome a mano.\"\r\n (valueChange)=\"setActionTarget($index, $event ?? '')\"\r\n />\r\n @if (!action.actionName) {\r\n <p class=\"fb-field__error\">Obbligatoria: senza, ACTION_NAME_MISSING.</p>\r\n }\r\n </div>\r\n\r\n @if (actionRowContext($index).sections.length) {\r\n <p class=\"fb-field__hint\">\r\n Gira <strong>per ogni riga</strong> di\r\n @for (section of actionRowContext($index).sections; track $index) {\r\n @if (!$first) { e di }\r\n <code>{{ section.name }}</code>\r\n }\r\n in cui il campo che la innesca cambia. Input e destinazioni nominano la riga\r\n attraverso il cursore \u2014\r\n @for (root of actionRowContext($index).preferredRoots; track $index) {\r\n @if (!$first) { o }\r\n <code>{{ root }}.&lt;membro&gt;</code>\r\n }\r\n \u2014 e non con i nomi dei campi della riga. Gli output possono andare anche in una\r\n variabile del flow o in un campo fuori dalla sezione.\r\n </p>\r\n <!-- Senza colore d'avviso: e' una premessa, non un difetto \u2014 i difetti, se ci sono,\r\n li dicono i rilievi sopra. -->\r\n @if (actionRowContext($index).runsForScreen) {\r\n <p class=\"fb-field__hint\">\r\n La invoca anche un trigger su un campo fuori dalla sezione: l\u00EC il cursore non\r\n porta alcuna riga, quindi le destinazioni giuste per l\u2019uno sono sbagliate per\r\n l\u2019altro. Le sole che valgono per entrambi sono le variabili e i campi fuori dalla\r\n sezione.\r\n </p>\r\n }\r\n }\r\n\r\n <!--\r\n Su un'action che gira per riga la spunta non si propone: sarebbe una risorsa sola\r\n per tutte le righe (SCREEN_ACTION_ROW_TARGET_INVALID). Resta se e' gia' accesa,\r\n perche' e' da qui che la si spegne.\r\n -->\r\n @if (actionOffersAutomaticOutput($index, action)) {\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"action.storeOutputAutomatically === true\"\r\n (change)=\"setActionStoreOutput($index, $any($event.target).checked)\"\r\n />\r\n Output automatico\r\n </label>\r\n <p class=\"fb-field__hint\">\r\n L\u2019output automatico rende il risultato leggibile come\r\n <code>{{ action.name || 'Cerca' }}.NomeOutput</code>, ma\r\n <strong>non scrive in nessun campo</strong>: e\u2019 la forma da usare quando il risultato\r\n serve a una condizione, non a precompilare.\r\n </p>\r\n }\r\n\r\n <!--\r\n L'unico posto in cui un campo di schermata e' una destinazione (\u00A75.2):\r\n `POST /flows/references/writable` continua a escluderlo, e un Assignment che ci\r\n scrive resta TARGET_NOT_WRITABLE. I campi di una riga restano fuori anche qui:\r\n un'action di riga scrive nei membri del cursore, che il picker propone per primo.\r\n -->\r\n <fb-parameter-editor\r\n [holder]=\"action\"\r\n [catalogParameters]=\"actionParameterCatalog()\"\r\n [showOutputs]=\"action.storeOutputAutomatically !== true\"\r\n outputTitle=\"Campi da riempire\"\r\n [extraTargets]=\"screenFieldTargets()\"\r\n [preferredRoots]=\"actionRowContext($index).preferredRoots\"\r\n [outputsDisabledReason]=\"\r\n action.storeOutputAutomatically === true\r\n ? 'Con l\u2019output automatico il risultato non viene scritto nei campi.'\r\n : null\r\n \"\r\n (changed)=\"onActionParametersChanged($index, $event)\"\r\n />\r\n }\r\n </div>\r\n } @empty {\r\n <p class=\"fb-empty\">Nessuna action: i campi di questa schermata li compila solo l\u2019utente.</p>\r\n }\r\n </div>\r\n\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"addScreenAction()\">Aggiungi action</button>\r\n </fieldset>\r\n\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Trigger</legend>\r\n <p class=\"fb-section__note\">\r\n Quale campo invoca quale action. Senza condizioni l\u2019action gira a ogni cambio del campo.\r\n </p>\r\n\r\n <div class=\"fb-list\">\r\n @for (trigger of screenTriggers(); 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 trigger\"\r\n (click)=\"removeScreenTrigger($index)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Action da invocare</label>\r\n <fb-name-picker\r\n [value]=\"trigger.screenActionName\"\r\n [options]=\"screenActionOptions()\"\r\n label=\"Action della schermata\"\r\n placeholder=\"Scegli un\u2019action\"\r\n unknownMessage=\"Questa schermata non dichiara un\u2019action con questo nome: e\u2019 SCREEN_ACTION_UNKNOWN.\"\r\n unknownSeverity=\"error\"\r\n emptyMessage=\"Aggiungi prima un\u2019action qui sopra.\"\r\n (valueChange)=\"setTriggerProperty($index, 'screenActionName', $event ?? '')\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Campo che la innesca</label>\r\n <!-- I campi di **questa** schermata, non i riferimenti in generale (\u00A75.2). -->\r\n <fb-name-picker\r\n [value]=\"trigger.triggerFieldName\"\r\n [options]=\"triggerFieldOptions()\"\r\n label=\"Campo della schermata\"\r\n placeholder=\"Scegli un campo\"\r\n unknownMessage=\"Questo non e\u2019 un campo di questa schermata: e\u2019 SCREEN_TRIGGER_FIELD_UNKNOWN.\"\r\n unknownSeverity=\"error\"\r\n emptyMessage=\"La schermata non ha ancora campi che raccolgono un valore.\"\r\n (valueChange)=\"setTriggerProperty($index, 'triggerFieldName', $event ?? '')\"\r\n />\r\n @if (triggerRowSection($index); as section) {\r\n <p class=\"fb-field__hint\">\r\n \u00C8 un campo della riga di <code>{{ section.name }}</code>: l\u2019action gira\r\n <strong>per ogni riga</strong> in cui cambia, e per quella che nasce col campo gi\u00E0\r\n precompilato. Condizioni, input e output leggono e scrivono quella riga attraverso\r\n il cursore \u2014 <code>{{ section.itemReference || 'itemReference' }}.&lt;membro&gt;</code>.\r\n </p>\r\n }\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">All\u2019arrivo sulla schermata</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"trigger.initBehavior || ''\"\r\n (change)=\"setTriggerProperty($index, 'initBehavior', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 non invocare \u2014</option>\r\n <option value=\"runOnLoad\">Invoca al caricamento (runOnLoad)</option>\r\n </select>\r\n @if (triggerInitIsUnknown(trigger)) {\r\n <p class=\"fb-field__error\">\r\n \u00AB{{ trigger.initBehavior }}\u00BB non e\u2019 ammesso: l\u2019unico valore e\u2019 <code>runOnLoad</code>\r\n (SCREEN_TRIGGER_INIT_BEHAVIOR_UNKNOWN).\r\n </p>\r\n }\r\n @if (triggerFiresOnLoadByDefault(trigger)) {\r\n <p class=\"fb-field__hint\">\r\n Non serve: \u00AB{{ trigger.triggerFieldName }}\u00BB ha un valore di default, e un campo\r\n precompilato dal flow innesca l\u2019action gi\u00E0 all\u2019arrivo sulla schermata.\r\n </p>\r\n }\r\n </div>\r\n\r\n @if (triggerHasNoCause(trigger)) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Senza un campo che la innesca e senza l\u2019invocazione al caricamento, questo trigger non\r\n scattera\u2019 mai (SCREEN_TRIGGER_WITHOUT_CAUSE).\r\n </p>\r\n }\r\n\r\n <fb-condition-editor\r\n [holder]=\"triggerConditions(trigger)\"\r\n title=\"Invoca l\u2019action quando\"\r\n [allowFormula]=\"false\"\r\n [preferredRoots]=\"triggerPreferredRoots($index)\"\r\n [issuePath]=\"'triggers[' + $index + ']'\"\r\n (changed)=\"onTriggerConditionsChanged($index, $event)\"\r\n />\r\n </div>\r\n } @empty {\r\n <p class=\"fb-empty\">Nessun trigger: le action dichiarate non verranno invocate.</p>\r\n }\r\n </div>\r\n\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"addScreenTrigger()\">Aggiungi trigger</button>\r\n </fieldset>\r\n\r\n <!-- --------------------------------------------- regole della schermata (\u00A75.2) -->\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Regole della schermata</legend>\r\n <p class=\"fb-section__note\">\r\n Le verifiche che <strong>non appartengono a nessun campo</strong>: \u00ABalmeno un punto fra le\r\n due sezioni\u00BB, \u00ABla somma delle percentuali fa cento\u00BB. Si valutano all\u2019invio, dopo i campi e\r\n dopo le righe, e una regola non soddisfatta <strong>impedisce alla schermata di\r\n avanzare</strong>. La <code>validationRule</code> di un campo non basta \u2014 giudica un solo\r\n valore, e su una sezione ripetibile e\u2019 addirittura inerte.\r\n </p>\r\n\r\n <div class=\"fb-list\">\r\n @for (rule of screenRules(); 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__title\">{{ rule.errorMessage || '(senza messaggio)' }}</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--sm\"\r\n aria-label=\"Rimuovi la regola\"\r\n (click)=\"removeScreenRule($index)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n\r\n <!--\r\n \u00A75.2 \u2014 qui `Formula` **e'** un `conditionLogic` ammesso, a differenza di un\r\n `filterLogic`: la regola puo' essere un'espressione booleana. Resta il consiglio,\r\n scritto sotto, di preferire le condizioni.\r\n -->\r\n <fb-condition-editor\r\n [holder]=\"rule\"\r\n title=\"La schermata e\u2019 valida quando\"\r\n [allowFormula]=\"true\"\r\n [issuePath]=\"'validationRules[' + $index + ']'\"\r\n (changed)=\"onScreenRuleConditionsChanged($index, $event)\"\r\n />\r\n @if (screenRuleIsEmpty(rule)) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n La regola non verifica niente: senza condizioni non impedisce mai l\u2019invio.\r\n </p>\r\n }\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Messaggio</label>\r\n <fb-merge-text-editor\r\n [text]=\"rule.errorMessage || ''\"\r\n [rows]=\"2\"\r\n commitOn=\"change\"\r\n placeholder=\"Indica almeno un punto di fornitura, luce o gas.\"\r\n ariaLabel=\"Messaggio della regola\"\r\n (textChange)=\"setScreenRuleProperty($index, 'errorMessage', $event)\"\r\n >\r\n <p class=\"fb-field__hint\">\r\n \u00C8 il testo dell\u2019autore e non nomina nessun campo: il runtime non ne costruisce\r\n uno, perche\u2019 un campo da nominare non c\u2019e\u2019.\r\n </p>\r\n </fb-merge-text-editor>\r\n @if (screenRuleHasNoMessage(rule)) {\r\n <p class=\"fb-field__error\">\r\n Obbligatorio: senza, la schermata si rifiuta di avanzare senza dire perche\u2019\r\n (SCREEN_RULE_INVALID).\r\n </p>\r\n }\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Aggancia il messaggio a un campo</label>\r\n <!--\r\n I campi della schermata **senza** quelli dentro una riga, che esistono in una copia\r\n per elemento e non individuano un controllo (\u00A75.2). Non e' l'elenco degli inneschi:\r\n quello i campi di riga li comprende, perche' un'action puo' girare per riga.\r\n -->\r\n <fb-name-picker\r\n [value]=\"rule.fieldName\"\r\n [options]=\"ruleFieldOptions()\"\r\n label=\"Campo\"\r\n placeholder=\"Nessuno: il messaggio e\u2019 della schermata\"\r\n unknownMessage=\"Questo nome non e\u2019 un campo di questa schermata.\"\r\n unknownSeverity=\"error\"\r\n emptyMessage=\"La schermata non ha campi a cui agganciare il messaggio.\"\r\n (valueChange)=\"setScreenRuleProperty($index, 'fieldName', $event ?? '')\"\r\n />\r\n <p class=\"fb-field__hint\">\r\n Facoltativo. Senza, il rilievo e\u2019 <strong>della schermata</strong> e il frontend lo\r\n mostra dove mostra i messaggi che non hanno un controllo \u2014 in testa al form, o\r\n accanto ad Avanti.\r\n </p>\r\n </div>\r\n </div>\r\n } @empty {\r\n <p class=\"fb-empty\">Nessuna regola: la schermata avanza se i campi sono a posto.</p>\r\n }\r\n </div>\r\n\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"addScreenRule()\">Aggiungi regola</button>\r\n <p class=\"fb-field__hint\">\r\n Scrivile con le <strong>condizioni</strong>, non con la formula: <code>IsEmpty</code> su una\r\n collection e\u2019 un operatore che il runtime valuta, mentre la stessa domanda in una formula\r\n chiede al motore una funzione che potrebbe non avere. Vale soprattutto per la verifica piu\u2019\r\n frequente \u2014 che una sezione ripetibile abbia almeno una riga.\r\n </p>\r\n </fieldset>\r\n } @else {\r\n <!-- ---------------------------------------------------- proprieta' del campo -->\r\n <header class=\"fb-scr__props-head\">\r\n <h3 class=\"fb-scr__props-title\">{{ captionOf(selectedField()!) }}</h3>\r\n <div class=\"fb-scr__props-actions\">\r\n <button type=\"button\" class=\"fb-btn fb-btn--sm\" title=\"Sposta su\" (click)=\"moveSelected(-1)\">\u2191</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--sm\" title=\"Sposta giu\u2019\" (click)=\"moveSelected(1)\">\u2193</button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--sm\"\r\n title=\"Porta fuori dal contenitore\"\r\n [disabled]=\"!canOutdent()\"\r\n (click)=\"outdentSelected()\"\r\n >\r\n \u21E4\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--sm\" title=\"Duplica\" (click)=\"duplicateSelected()\">\u29C9</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--danger fb-btn--sm\" title=\"Elimina\" (click)=\"removeSelected()\">\r\n \u00D7\r\n </button>\r\n </div>\r\n </header>\r\n\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]=\"selectedField()!.fieldType || ''\"\r\n (change)=\"setFieldType($any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 scegli \u2014</option>\r\n <!--\r\n \u00A75.2 \u2014 dentro una riga la sezione ripetibile non c'e': **non si annidano**, perche' il\r\n cursore e' una variabile sola per sezione. La validazione lo rifiuterebbe comunque, ma\r\n lasciar comporre e poi accusare fa perdere tempo due volte.\r\n -->\r\n @for (entry of fieldTypeOptions(); track entry.value) {\r\n <option [value]=\"entry.value\">{{ entry.label }}</option>\r\n }\r\n </select>\r\n @if (isInsideRepeater()) {\r\n <p class=\"fb-field__hint\">\r\n Questo campo sta dentro la riga di <code>{{ selectedRepeater()!.name }}</code>: esiste una\r\n volta per ogni elemento della collection, quindi\r\n <strong>non e\u2019 una risorsa del flow</strong> e non si nomina per nome. Dentro una riga si\r\n nomina il cursore \u2014\r\n <code>{{ rowCursor() || 'itemReference' }}.&lt;membro&gt;</code> \u2014 e scrivere\r\n <code>{{ selectedField()!.name }}</code> in una condizione, in una formula o in un parametro\r\n e\u2019 <code>SCREEN_FIELD_IN_REPEATER</code>. Puo\u2019 invece <strong>innescare una screen\r\n action</strong>, che gira per ogni riga in cui il campo cambia.\r\n </p>\r\n }\r\n @if (!selectedField()!.fieldType) {\r\n <p class=\"fb-field__error\">Il tipo e\u2019 obbligatorio: senza, SCREEN_FIELD_TYPE_MISSING.</p>\r\n } @else if (isUnknownType(selectedField())) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n Questo tipo non e\u2019 nel dizionario: l\u2019editor non sa quali campi ammetta e mostra solo i comuni.\r\n </p>\r\n } @else if (selectedType()?.description) {\r\n <p class=\"fb-field__hint\">{{ selectedType()?.description }}</p>\r\n }\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Nome</label>\r\n <!-- Sul `change` e non sull\u2019`input`: la rinomina riscrive i riferimenti nel documento,\r\n e farlo a ogni tasto significherebbe riscriverlo per ogni lettera. -->\r\n <input\r\n class=\"fb-input fb-input--mono\"\r\n [value]=\"selectedField()!.name || ''\"\r\n (change)=\"setFieldName($any($event.target).value)\"\r\n />\r\n @if (selectedIsResource() && !isInsideRepeater()) {\r\n <p class=\"fb-field__hint\">\r\n Questo campo e\u2019 una <strong>risorsa</strong>: lo referenzi come\r\n <code>{{ selectedField()!.name || 'Nome' }}</code> in condizioni, formule e parametri. \u00C8 di\r\n sola lettura per il flow \u2014 un Assignment che ci scrive e\u2019 TARGET_NOT_WRITABLE.\r\n </p>\r\n } @else if (isInsideRepeater()) {\r\n <p class=\"fb-field__hint\">\r\n Il nome resta comunque <strong>unico</strong>: e\u2019 l\u2019indirizzo con cui il frontend rende il\r\n campo, e un omonimo di una variabile e\u2019 <code>NAME_DUPLICATED</code> come sempre.\r\n </p>\r\n }\r\n </div>\r\n\r\n <!--\r\n \u00A75.2 \u2014 `memberName` e il riferimento con cui gli altri campi lo nominano sono la **stessa\r\n domanda posta due volte**: `memberName: \"PodId\"` e `PuntoCorrente.PodId` sono lo stesso\r\n membro, e la classe da cui vengono e' l'`objectType` della variabile in `collectionReference`.\r\n -->\r\n @if (isInsideRepeater() && selectedType()?.storesValue) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Membro della riga</label>\r\n @if (rowClass()) {\r\n <fb-structure-member-picker\r\n [value]=\"selectedField()!.memberName\"\r\n [className]=\"rowClass()\"\r\n usage=\"writable\"\r\n label=\"Membro della riga\"\r\n (valueChange)=\"setFieldProperty('memberName', $event ?? '')\"\r\n />\r\n } @else {\r\n <input\r\n class=\"fb-input fb-input--mono\"\r\n [value]=\"selectedField()!.memberName || ''\"\r\n (change)=\"setFieldProperty('memberName', $any($event.target).value)\"\r\n />\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n La classe delle righe non e\u2019 nota: la sezione non dichiara una <code>collectionReference</code>\r\n valida, quindi i membri non si possono proporre.\r\n </p>\r\n }\r\n <p class=\"fb-field__hint\">\r\n Il membro della riga in cui finisce il valore. Senza, vale il nome del campo. Dentro una\r\n riga lo si legge come\r\n <code>{{ rowCursor() || 'itemReference' }}.{{ selectedField()!.memberName || selectedField()!.name }}</code>.\r\n </p>\r\n </div>\r\n }\r\n\r\n <!--\r\n Il ramo dei contenitori vale anche per una sezione ripetibile: l\u00EC `fieldText` e'\r\n l'intestazione del blocco, non l'etichetta di un valore \u2014 l'etichetta della **riga** e'\r\n `itemLabel`, ed e' un campo suo, piu' sotto (\u00A75.2).\r\n -->\r\n @if (!holdsFields(selectedField())) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">\r\n {{ selectedField()!.fieldType === 'DisplayText' ? 'Testo' : 'Etichetta' }}\r\n </label>\r\n <textarea\r\n class=\"fb-textarea\"\r\n [value]=\"selectedField()!.fieldText || ''\"\r\n (input)=\"setFieldProperty('fieldText', $any($event.target).value)\"\r\n ></textarea>\r\n <p class=\"fb-field__hint\">\r\n Supporta i merge field <code>&#123;!Riferimento&#125;</code>.\r\n @if (selectedField()!.fieldType === 'ComponentInstance') {\r\n <!--\r\n \u00A75.2 \u2014 su un componente l\u2019etichetta e\u2019 **ammessa** e viene consegnata al frontend\r\n in `label`, ma la sua assenza non e\u2019 un rilievo: un componente di solito disegna\r\n la propria intestazione. Nasconderla, come faceva prima l\u2019editor, toglieva un\r\n campo che il contratto prevede.\r\n -->\r\n Facoltativa: un componente di solito disegna la propria intestazione.\r\n }\r\n </p>\r\n </div>\r\n } @else {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Intestazione</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"selectedField()!.fieldText || ''\"\r\n (input)=\"setFieldProperty('fieldText', $any($event.target).value)\"\r\n />\r\n </div>\r\n }\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Testo di aiuto</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"selectedField()!.helpText || ''\"\r\n (input)=\"setFieldProperty('helpText', $any($event.target).value)\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Larghezza</label>\r\n <div class=\"fb-scr__width\">\r\n <input\r\n type=\"range\"\r\n min=\"1\"\r\n max=\"12\"\r\n step=\"1\"\r\n [value]=\"widthOf(selectedField()!)\"\r\n (input)=\"setNumberProperty('width', $any($event.target).value)\"\r\n />\r\n <span class=\"fb-scr__width-value\">{{ widthOf(selectedField()!) }}/12</span>\r\n </div>\r\n <p class=\"fb-field__hint\">\r\n Colonne della griglia della schermata. \u00C8 un\u2019indicazione: il frontend puo\u2019 ignorarla.\r\n </p>\r\n </div>\r\n\r\n @if (selectedField()!.fieldType === 'ComponentInstance') {\r\n <!--\r\n \u00A75.2 \u2014 perche\u2019 qui non c\u2019e\u2019 \u00ABobbligatorio\u00BB. La domanda arriva sempre, e la risposta non\r\n si indovina guardando l\u2019interfaccia: un componente non ha un valore proprio, ce l\u2019hanno\r\n i suoi output, che vanno in **variabili** scelte dall\u2019autore. \u00ABObbligatorio\u00BB dovrebbe\r\n dire *quale output* deve essere valorizzato, e il metadata non ha modo di dirlo \u2014\r\n dichiararlo qui sarebbe `SCREEN_FIELD_CONFIGURATION_INVALID` e il runtime lo\r\n ignorerebbe. Le due strade vere stanno nel contratto e sono queste.\r\n -->\r\n <p class=\"fb-field__hint\">\r\n Un componente non raccoglie un valore \u2014 lo fanno i suoi output \u2014 quindi obbligatorieta\u2019, valore\r\n predefinito e regola di validazione non si applicano: dichiararli e\u2019\r\n <code>SCREEN_FIELD_CONFIGURATION_INVALID</code> e il runtime li ignora. Per pretendere una\r\n compilazione: la validazione la fa il componente stesso, oppure si controlla la variabile di\r\n destinazione con una Decision dopo la schermata.\r\n </p>\r\n }\r\n\r\n <!-- ------------------------------------------------ campi che raccolgono un valore -->\r\n @if (selectedType()?.storesValue) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Valore</legend>\r\n\r\n @if (selectedField()!.fieldType !== 'ObjectProvided') {\r\n <div class=\"fb-field\">\r\n <label\r\n class=\"fb-field__label\"\r\n [class.fb-field__label--required]=\"!!selectedType()?.requiresDataType\"\r\n >\r\n Tipo di dato\r\n </label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"selectedField()!.dataType || ''\"\r\n (change)=\"setFieldProperty('dataType', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 scegli \u2014</option>\r\n @for (entry of dictionary.dataTypes(); track entry.value) {\r\n <option [value]=\"entry.value\">{{ entry.label }}</option>\r\n }\r\n </select>\r\n @if (selectedType()?.requiresDataType && !selectedField()!.dataType) {\r\n <p class=\"fb-field__error\">Obbligatorio per questo tipo di campo (DATA_TYPE_MISSING).</p>\r\n }\r\n @if (selectedType()?.isCollection) {\r\n <p class=\"fb-field__hint\">\r\n Il valore raccolto e\u2019 una <strong>collection</strong>, non una stringa con i valori\r\n separati: nelle condizioni si usano gli operatori di collection.\r\n </p>\r\n }\r\n </div>\r\n\r\n @if (requiresObjectType()) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\" [class.fb-field__label--required]=\"objectTypeIsStructure()\">\r\n {{\r\n objectTypeIsStructure()\r\n ? 'Classe'\r\n : selectedField()!.dataType === 'Enum'\r\n ? 'Tipo di enumerazione'\r\n : 'Oggetto'\r\n }}\r\n </label>\r\n @if (selectedField()!.dataType === 'Enum') {\r\n <!-- Dizionario chiuso: si sceglie, non si scrive. -->\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"selectedField()!.objectType || ''\"\r\n (change)=\"setFieldProperty('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 @if (!selectedField()!.objectType) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n Senza il tipo concreto l\u2019editor non puo\u2019 proporre i valori dell\u2019enumerazione.\r\n </p>\r\n }\r\n } @else if (objectTypeIsStructure()) {\r\n <fb-structure-picker\r\n [value]=\"selectedField()!.objectType\"\r\n label=\"Classe\"\r\n (valueChange)=\"setFieldProperty('objectType', $event ?? '')\"\r\n />\r\n @if (!selectedField()!.objectType) {\r\n <p class=\"fb-field__error\">\r\n La classe e\u2019 obbligatoria: senza, il runtime non ha nulla da istanziare\r\n (OBJECT_TYPE_MISSING).\r\n </p>\r\n }\r\n } @else {\r\n <fb-object-picker\r\n [value]=\"selectedField()!.objectType\"\r\n label=\"Oggetto\"\r\n (valueChange)=\"setFieldProperty('objectType', $event ?? '')\"\r\n />\r\n }\r\n </div>\r\n }\r\n } @else {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Campo dell\u2019entita\u2019</label>\r\n <p class=\"fb-field__hint\">\r\n Tipo, label, obbligatorieta\u2019, scala e \u2014 se il campo e\u2019 una picklist \u2014 le opzioni arrivano\r\n dallo schema dati: qui non si ridichiarano.\r\n </p>\r\n <fb-object-picker\r\n [value]=\"providedObject() || undefined\"\r\n label=\"Oggetto\"\r\n (valueChange)=\"setProvidedObject($event)\"\r\n />\r\n <fb-field-picker\r\n [value]=\"providedField() || undefined\"\r\n [object]=\"providedObject() || undefined\"\r\n label=\"Campo\"\r\n (valueChange)=\"setProvidedField($event)\"\r\n />\r\n </div>\r\n }\r\n\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"selectedIsRequired()\"\r\n (change)=\"setRequired($any($event.target).checked)\"\r\n />\r\n Obbligatorio\r\n </label>\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"selectedIsEditable()\"\r\n (change)=\"setEditable($any($event.target).checked)\"\r\n />\r\n Modificabile\r\n </label>\r\n @if (!selectedIsEditable()) {\r\n <p class=\"fb-field__hint\">\r\n A <code>false</code> il runtime <strong>ignora</strong> cio\u2019 che il client rimanda indietro:\r\n e\u2019 l\u2019unico modo di rendere un campo davvero di sola lettura.\r\n </p>\r\n }\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Valore di default</label>\r\n <fb-value-editor\r\n [value]=\"selectedField()!.defaultValue\"\r\n [dataType]=\"selectedField()!.dataType\"\r\n [objectType]=\"selectedField()!.objectType\"\r\n [isCollection]=\"selectedType()?.isCollection\"\r\n [preferredRoots]=\"preferredRoots()\"\r\n label=\"Valore di default\"\r\n (valueChange)=\"setDefaultValue($event)\"\r\n />\r\n </div>\r\n\r\n @if (scaleApplies()) {\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]=\"selectedField()!.scale ?? ''\"\r\n (change)=\"setNumberProperty('scale', $any($event.target).value)\"\r\n />\r\n </div>\r\n } @else if (selectedField()!.scale !== undefined) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n <code>scale</code> su un campo non numerico e\u2019 SCALE_NOT_APPLICABLE.\r\n </p>\r\n }\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Lunghezza massima</label>\r\n <input\r\n class=\"fb-input\"\r\n type=\"number\"\r\n min=\"1\"\r\n [value]=\"selectedField()!.maxLength ?? ''\"\r\n (change)=\"setNumberProperty('maxLength', $any($event.target).value)\"\r\n />\r\n <p class=\"fb-field__hint\">Verificata anche dal runtime (TOO_LONG).</p>\r\n </div>\r\n\r\n @if (formatApplies()) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Formato</label>\r\n <fb-name-picker\r\n [value]=\"selectedField()!.format\"\r\n [options]=\"formatOptions()\"\r\n label=\"Formato\"\r\n placeholder=\"Nessuno: un testo qualunque\"\r\n unknownSeverity=\"note\"\r\n unknownMessage=\"Non e\u2019 fra i formati proposti: se l\u2019host ha registrato il suo pattern va bene cosi\u2019, altrimenti il campo non verifica niente (SCREEN_FIELD_FORMAT_UNKNOWN).\"\r\n emptyMessage=\"Nessun formato dichiarato: puoi scriverne uno registrato dall\u2019host.\"\r\n (valueChange)=\"setFieldProperty('format', $event)\"\r\n />\r\n <p class=\"fb-field__hint\">\r\n <strong>Che cosa</strong> il campo raccoglie, oltre al fatto di essere un testo: il\r\n frontend ne ricava il tipo di input, e il runtime rifiuta l\u2019invio sbagliato\r\n (FORMAT_INVALID). L\u2019elenco non e\u2019 chiuso \u2014 i pattern li registra l\u2019host, non la\r\n libreria \u2014 quindi un nome fuori elenco non e\u2019 un difetto.\r\n </p>\r\n <p class=\"fb-field__hint\">\r\n Non e\u2019 un\u2019etichetta che il flow puo\u2019 leggere: se a valle serve sapere che un numero e\u2019\r\n un cellulare, quella distinzione sta nel dato (due campi, o due membri di una\r\n <code>Structure</code>). E non riscrive il valore \u2014 togliere spazi o aggiungere il\r\n prefisso e\u2019 una trasformazione, e la fa una screen action.\r\n </p>\r\n </div>\r\n } @else if (selectedField()!.format) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n <code>format</code> su un campo che non raccoglie un solo testo non ha effetto\r\n (SCREEN_FIELD_FORMAT_NOT_APPLICABLE).\r\n </p>\r\n }\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Tornando sulla schermata</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"selectedField()!.inputsOnNextNavToAssocScrn || ''\"\r\n (change)=\"setFieldProperty('inputsOnNextNavToAssocScrn', $any($event.target).value)\"\r\n >\r\n <option value=\"\">Predefinito (mantieni i valori)</option>\r\n @for (entry of dictionary.screenFieldInputsRevisited(); track entry.value) {\r\n <option [value]=\"entry.value\">{{ entry.label }}</option>\r\n }\r\n </select>\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Regola di validazione</label>\r\n <!--\r\n `commitOn=\"change\"`: qui mutare il documento ridisegna l'anteprima, e riscriverla a\r\n ogni tasto costa. La verifica parte lo stesso mentre si digita (\u00A76.3).\r\n -->\r\n <fb-formula-editor\r\n [expression]=\"selectedField()!.validationRule?.formulaExpression || ''\"\r\n usage=\"ValidationRule\"\r\n expectedDataType=\"Boolean\"\r\n commitOn=\"change\"\r\n [preferredRoots]=\"preferredRoots()\"\r\n [rows]=\"2\"\r\n placeholder=\"Espressione, es. LEN(Nome) > 3\"\r\n ariaLabel=\"Regola di validazione\"\r\n (expressionChange)=\"setValidationRule('formulaExpression', $event)\"\r\n />\r\n <input\r\n class=\"fb-input\"\r\n placeholder=\"Messaggio mostrato quando l\u2019espressione e\u2019 falsa\"\r\n [value]=\"selectedField()!.validationRule?.errorMessage || ''\"\r\n (change)=\"setValidationRule('errorMessage', $any($event.target).value)\"\r\n />\r\n <p class=\"fb-field__hint\">\r\n L\u2019espressione la valuta il motore di regole: il backend non la interpreta, la fa\r\n verificare al motore.\r\n </p>\r\n </div>\r\n </fieldset>\r\n }\r\n\r\n <!-- --------------------------------------------------------------- choice -->\r\n @if (selectedType()?.acceptsChoices) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Opzioni</legend>\r\n <p class=\"fb-section__note\">\r\n Accetta <strong>Choice</strong> e <strong>Dynamic choice set</strong>, nell\u2019ordine in cui le\r\n opzioni compaiono. Puntare a una variabile e\u2019 SCREEN_FIELD_CHOICE_UNKNOWN.\r\n </p>\r\n\r\n @if (!selectedField()!.choiceReferences?.length) {\r\n @if (selectedField()!.fieldType === 'ObjectProvided') {\r\n <!-- Le opzioni di una picklist arrivano dallo schema: qui si aggiungono solo se\r\n si vuole sostituirle, e non dichiararne nessuna e' il caso normale. -->\r\n <p class=\"fb-field__hint\">\r\n Se il campo dello schema e\u2019 una picklist, le opzioni arrivano da l\u00EC: dichiararle qui\r\n serve solo a sostituirle.\r\n </p>\r\n } @else {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Nessuna opzione: il campo non ha niente da mostrare (SCREEN_FIELD_CHOICES_MISSING).\r\n </p>\r\n }\r\n }\r\n\r\n <div class=\"fb-list\">\r\n @for (choice of selectedField()!.choiceReferences || []; 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__title\">{{ choice }}</span>\r\n <span class=\"fb-list__spacer\"></span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--sm\" (click)=\"moveChoice($index, -1)\">\u2191</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--sm\" (click)=\"moveChoice($index, 1)\">\u2193</button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--sm\"\r\n aria-label=\"Rimuovi\"\r\n (click)=\"removeChoice($index)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Aggiungi un\u2019opzione</label>\r\n <!--\r\n `commitOn=\"enter\"`: qui ogni emissione e\u2019 una riga in piu\u2019, non un campo che cambia\r\n valore \u2014 a tasto, scrivere \u00ABAlta\u00BB aggiungeva \u00ABA\u00BB, \u00ABAl\u00BB, \u00ABAlt\u00BB e \u00ABAlta\u00BB.\r\n -->\r\n <fb-name-picker\r\n [options]=\"choiceOptions()\"\r\n label=\"Choice\"\r\n commitOn=\"enter\"\r\n placeholder=\"Scegli una choice o un choice set\"\r\n unknownMessage=\"Questo nome non e\u2019 una choice ne\u2019 un choice set: e\u2019 SCREEN_FIELD_CHOICE_UNKNOWN.\"\r\n unknownSeverity=\"error\"\r\n emptyMessage=\"Il flow non dichiara nessuna choice: creale nel pannello delle risorse.\"\r\n (valueChange)=\"addChoice($event)\"\r\n />\r\n <p class=\"fb-field__hint\">\r\n Scegli dall\u2019elenco, oppure scrivi il nome e premi Invio.\r\n </p>\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Opzione selezionata all\u2019apertura</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"selectedField()!.defaultSelectedChoiceReference || ''\"\r\n (change)=\"setFieldProperty('defaultSelectedChoiceReference', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 nessuna \u2014</option>\r\n @for (choice of selectedField()!.choiceReferences || []; track $index) {\r\n <option [value]=\"choice\">{{ choice }}</option>\r\n }\r\n </select>\r\n @if (defaultChoiceIsForeign()) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n \u00AB{{ selectedField()!.defaultSelectedChoiceReference }}\u00BB non e\u2019 fra le opzioni elencate qui\r\n sopra.\r\n </p>\r\n }\r\n </div>\r\n\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"selectedAutoSelectsSingleChoice()\"\r\n (change)=\"setAutoSelectSingleChoice($any($event.target).checked)\"\r\n />\r\n Scegli da sola l\u2019unica opzione\r\n </label>\r\n <p class=\"fb-field__hint\">\r\n Quando le opzioni risolte sono <strong>esattamente una</strong>, il campo la prende da se\u2019.\r\n Serve alle tendine che dipendono da un\u2019altra tendina \u2014 i CAP di un comune \u2014 dove chiedere\r\n di scegliere l\u2019unica voce non decide niente.\r\n </p>\r\n </fieldset>\r\n }\r\n\r\n <!-- ------------------------------------------------------- contenitori -->\r\n @if (selectedType()?.isContainer) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Contenitore</legend>\r\n @if (selectedField()!.fieldType === 'RegionContainer') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Tipo di sezione</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"selectedField()!.regionContainerType || ''\"\r\n (change)=\"setFieldProperty('regionContainerType', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 predefinito \u2014</option>\r\n @for (entry of dictionary.regionContainerTypes(); track entry.value) {\r\n <option [value]=\"entry.value\">{{ entry.label }}</option>\r\n }\r\n </select>\r\n </div>\r\n }\r\n @if (!selectedField()!.fields?.length) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Il contenitore e\u2019 vuoto: non produce niente sulla schermata (SCREEN_CONTAINER_EMPTY).\r\n </p>\r\n }\r\n </fieldset>\r\n }\r\n\r\n <!-- ------------------------------------------------ sezione ripetibile (\u00A75.2) -->\r\n @if (selectedIsRepeatingSection()) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Righe</legend>\r\n <p class=\"fb-section__note\">\r\n I campi qui dentro sono il <strong>modello di una riga</strong>: la schermata ne rende una\r\n copia per ogni elemento della collection, e l\u2019utente decide quante con un tasto. I due\r\n riferimenti vanno pensati insieme \u2014 la <strong>collection</strong> e\u2019 dove le righe vivono,\r\n il <strong>cursore</strong> e\u2019 la riga corrente, come\r\n l\u2019<code>assignNextValueToReference</code> di un Loop.\r\n </p>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Collection delle righe</label>\r\n <fb-reference-picker\r\n [value]=\"selectedField()!.collectionReference\"\r\n dataType=\"Structure\"\r\n [isCollection]=\"true\"\r\n [writableOnly]=\"true\"\r\n label=\"Collection delle righe\"\r\n placeholder=\"Una variabile collection di tipo Structure\"\r\n (valueChange)=\"setFieldProperty('collectionReference', $event ?? '')\"\r\n />\r\n <p class=\"fb-field__hint\">\r\n \u00C8 quella che il resto del flow attraversa con un Loop: la sezione non introduce un posto\r\n nuovo in cui i dati stanno. Se la riga e\u2019 il DTO che poi finisce in una action, conviene\r\n che sia una collection <strong>di quel DTO</strong>.\r\n </p>\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Cursore della riga</label>\r\n <fb-reference-picker\r\n [value]=\"selectedField()!.itemReference\"\r\n dataType=\"Structure\"\r\n [isCollection]=\"false\"\r\n [objectType]=\"rowClass()\"\r\n [writableOnly]=\"true\"\r\n label=\"Cursore della riga\"\r\n placeholder=\"Una variabile Structure della stessa classe\"\r\n (valueChange)=\"setFieldProperty('itemReference', $event ?? '')\"\r\n />\r\n <p class=\"fb-field__hint\">\r\n \u00C8 il nome che si scrive <strong>dentro</strong> la riga: visibilita\u2019, regole di\r\n validazione, filtri delle tendine, parametri dei componenti e\r\n <code>itemLabel</code> nominano\r\n <code>{{ selectedField()!.itemReference || 'Cursore' }}.&lt;membro&gt;</code>, mai il nome\r\n di un campo della riga.\r\n </p>\r\n </div>\r\n\r\n @if (repeaterIssue(); as issue) {\r\n <p class=\"fb-callout fb-callout--error\">{{ issue }} (REPEATING_SECTION_INVALID)</p>\r\n }\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Righe minime</label>\r\n <input\r\n class=\"fb-input\"\r\n type=\"number\"\r\n min=\"0\"\r\n [value]=\"selectedField()!.minInstances ?? ''\"\r\n (change)=\"setNumberProperty('minInstances', $any($event.target).value)\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Righe massime</label>\r\n <input\r\n class=\"fb-input\"\r\n type=\"number\"\r\n min=\"1\"\r\n [value]=\"selectedField()!.maxInstances ?? ''\"\r\n (change)=\"setNumberProperty('maxInstances', $any($event.target).value)\"\r\n />\r\n @if (repeaterHasNoLimit()) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n Senza un massimo l\u2019utente puo\u2019 aggiungere righe all\u2019infinito\r\n (REPEATING_SECTION_WITHOUT_LIMIT).\r\n </p>\r\n }\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Righe gia\u2019 aperte</label>\r\n <input\r\n class=\"fb-input\"\r\n type=\"number\"\r\n min=\"0\"\r\n [value]=\"selectedField()!.initialInstances ?? ''\"\r\n (change)=\"setNumberProperty('initialInstances', $any($event.target).value)\"\r\n />\r\n <p class=\"fb-field__hint\">\r\n Quante righe trovare <strong>gia\u2019 aperte</strong> arrivando sulla schermata, ed e\u2019 un\u2019altra\r\n cosa dal minimo: il minimo e\u2019 un vincolo, queste sono una comodita\u2019 e si possono togliere\r\n tutte. Un punto luce e un punto gas gia\u2019 aperti, dove chi ha una sola commodity toglie\r\n quello che non gli serve, sono <code>1</code> qui con <code>0</code> come minimo; col\r\n minimo a 1 sarebbe un modulo che <strong>obbliga</strong> a entrambe.\r\n </p>\r\n <p class=\"fb-field__hint\">\r\n Si applicano <strong>una volta sola</strong>, e solo se nessuno ha ancora scritto la\r\n collection: righe arrivate da un ciclo o da una schermata precedente <em>sono</em> le righe\r\n iniziali e non vengono diluite con righe vuote, e chi le toglie tutte non se le ritrova.\r\n </p>\r\n @if (repeaterInitialOutOfRange(); as issue) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">{{ issue }}</p>\r\n }\r\n </div>\r\n\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"selectedField()!.allowAdd !== false\"\r\n (change)=\"setRepeaterFlag('allowAdd', $any($event.target).checked)\"\r\n />\r\n L\u2019utente puo\u2019 aggiungere righe\r\n </label>\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"selectedField()!.allowRemove !== false\"\r\n (change)=\"setRepeaterFlag('allowRemove', $any($event.target).checked)\"\r\n />\r\n L\u2019utente puo\u2019 togliere righe\r\n </label>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Scritta del comando \u00ABaggiungi\u00BB</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"selectedField()!.addButtonLabel || ''\"\r\n (input)=\"setFieldProperty('addButtonLabel', $any($event.target).value)\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Scritta del comando \u00ABtogli\u00BB</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"selectedField()!.removeButtonLabel || ''\"\r\n (input)=\"setFieldProperty('removeButtonLabel', $any($event.target).value)\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Intestazione della singola riga</label>\r\n <!-- `commitOn=\"change\"`: mutare il documento ridisegna l'anteprima. -->\r\n <fb-merge-text-editor\r\n [text]=\"selectedField()!.itemLabel || ''\"\r\n [rows]=\"2\"\r\n commitOn=\"change\"\r\n placeholder=\"Punto {{ '{' }}!Cursore.PodId}\"\r\n ariaLabel=\"Intestazione della riga\"\r\n (textChange)=\"setFieldProperty('itemLabel', $event)\"\r\n >\r\n <p class=\"fb-field__hint\">\r\n Risolta <strong>sulla riga</strong>: i merge field qui nominano il cursore\r\n (<code>&#123;!{{ selectedField()!.itemReference || 'Cursore' }}.Membro&#125;</code>), non\r\n un campo della riga.\r\n </p>\r\n </fb-merge-text-editor>\r\n </div>\r\n\r\n @if (!selectedField()!.fields?.length) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Il modello della riga e\u2019 vuoto: la sezione non mostra niente (SCREEN_CONTAINER_EMPTY).\r\n </p>\r\n }\r\n </fieldset>\r\n }\r\n\r\n <!-- --------------------------------------------------- ComponentInstance -->\r\n @if (selectedField()!.fieldType === 'ComponentInstance') {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Componente</legend>\r\n <p class=\"fb-section__note\">\r\n Componenti e form sono la stessa domanda al frontend \u2014 cosa sa rendere, e con quali parametri\r\n \u2014 e passano dallo stesso catalogo. Un <code>ComponentInstance</code> non ha un valore proprio:\r\n lo hanno i suoi output.\r\n </p>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Componente</label>\r\n <fb-name-picker\r\n [value]=\"selectedField()!.extensionName\"\r\n [options]=\"componentOptions()\"\r\n [unusableOptions]=\"unusableComponents()\"\r\n label=\"Componente\"\r\n placeholder=\"Scrivi o scegli un componente\"\r\n [isMono]=\"false\"\r\n unknownMessage=\"Questo componente non esiste nel catalogo: e\u2019 SCREEN_COMPONENT_UNKNOWN.\"\r\n unknownSeverity=\"error\"\r\n unusableMessage=\"Questo nome e\u2019 una schermata intera, non un componente montabile qui (FORM_KIND_MISMATCH).\"\r\n emptyMessage=\"Il catalogo dei componenti non e\u2019 popolato: il nome non viene verificato.\"\r\n (valueChange)=\"setFieldProperty('extensionName', $event ?? '')\"\r\n />\r\n @if (!selectedField()!.extensionName) {\r\n <p class=\"fb-field__error\">Obbligatorio: senza, SCREEN_COMPONENT_MISSING.</p>\r\n }\r\n </div>\r\n\r\n <!--\r\n \u00A75.2 \u2014 dentro una riga la spunta **non c'e'**: gli output automatici si espongono come\r\n `<NomeCampo>.<NomeOutput>`, che sarebbe **una risorsa sola per tutte le righe** \u2014 l'ultima\r\n che ha girato. \u00C8 `REPEATING_SECTION_INVALID`, e la strada giusta e' `assignToReference` su\r\n un membro del cursore, che una riga ce l'ha per davvero.\r\n -->\r\n @if (!isInsideRepeater()) {\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"selectedField()!.storeOutputAutomatically === true\"\r\n (change)=\"setStoreOutputAutomatically($any($event.target).checked)\"\r\n />\r\n Rendi gli output referenziabili automaticamente\r\n </label>\r\n @if (selectedField()!.storeOutputAutomatically) {\r\n <p class=\"fb-field__hint\">\r\n Gli output si referenziano come\r\n <code>{{ selectedField()!.name || 'Campo' }}.nomeOutput</code>, senza dichiarare\r\n variabili.\r\n </p>\r\n }\r\n @if (hasOutputConflict()) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n Output automatici <strong>e</strong> parametri di uscita insieme:\r\n OUTPUT_CONFIGURATION_CONFLICT.\r\n </p>\r\n }\r\n } @else {\r\n <p class=\"fb-field__hint\">\r\n Gli output automatici qui non si possono usare: si esporrebbero come\r\n <code>{{ selectedField()!.name || 'Campo' }}.nomeOutput</code>, cioe\u2019 una risorsa sola per\r\n tutte le righe \u2014 l\u2019ultima che ha girato (REPEATING_SECTION_INVALID). Assegnali con\r\n <strong>assignToReference</strong> a un membro di\r\n <code>{{ rowCursor() || 'Cursore' }}</code>.\r\n </p>\r\n @if (selectedField()!.storeOutputAutomatically) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n Questo componente dichiara gli output automatici e sta dentro una riga:\r\n REPEATING_SECTION_INVALID.\r\n <button type=\"button\" class=\"fb-btn fb-btn--sm\" (click)=\"setStoreOutputAutomatically(false)\">\r\n Togli\r\n </button>\r\n </p>\r\n }\r\n }\r\n\r\n <fb-parameter-editor\r\n [holder]=\"$any(selectedField())\"\r\n [catalogParameters]=\"componentParameterList()\"\r\n [preferredRoots]=\"preferredRoots()\"\r\n inputTitle=\"Valori passati al componente\"\r\n outputTitle=\"Valori raccolti dal componente\"\r\n [showOutputs]=\"!selectedField()!.storeOutputAutomatically\"\r\n outputsDisabledReason=\"Gli output sono automatici: disattivalo per assegnarli a variabili.\"\r\n (changed)=\"onComponentParametersChanged($event)\"\r\n />\r\n </fieldset>\r\n }\r\n\r\n <!-- ------------------------------------------------------- visibilita' -->\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Visibilita\u2019</legend>\r\n <p class=\"fb-section__note\">\r\n Le regole si rivalutano <strong>sui valori appena inviati</strong>: e\u2019 cos\u00EC che un campo compare\r\n in funzione di un altro campo della stessa schermata. Un campo risultato nascosto viene\r\n <strong>azzerato</strong> e non viene validato.\r\n </p>\r\n <fb-condition-editor\r\n [holder]=\"visibilityRule()\"\r\n title=\"Mostra il campo quando\"\r\n [allowFormula]=\"false\"\r\n [preferredRoots]=\"preferredRoots()\"\r\n [issuePath]=\"'fields[' + (selectedField()!.name || '') + '].visibilityRule'\"\r\n (changed)=\"onVisibilityChanged($event)\"\r\n />\r\n </fieldset>\r\n }\r\n </aside>\r\n</div>\r\n\r\n<fb-connector-editor\r\n [nodeName]=\"name()\"\r\n [node]=\"node()\"\r\n [outlets]=\"outlets()\"\r\n (connectorChanged)=\"onConnectorChanged($event)\"\r\n/>\r\n\r\n<!--\r\n ============================================================== l'anteprima\r\n Due template ricorsivi. `listTpl` disegna i figli di un contenitore, e nient'altro: `data-item`\r\n marca cio' che conta come fratello nel calcolo del punto di rilascio, e il segnaposto del\r\n contenitore vuoto ne resta fuori. La barra di inserimento non e' qui \u2014 sta sopra l'anteprima,\r\n vedi il velo nel frame.\r\n-->\r\n<ng-template #listTpl let-fields let-parent=\"parent\" let-axis=\"axis\">\r\n @for (child of fields; track $index) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"fieldTpl\"\r\n [ngTemplateOutletContext]=\"{ $implicit: child, path: childPath(parent, $index), axis: axis }\"\r\n />\r\n }\r\n @if (!fields.length) {\r\n <p class=\"fb-scr__drop-hint\">Trascina qui un componente</p>\r\n }\r\n</ng-template>\r\n\r\n<ng-template #fieldTpl let-field let-path=\"path\" let-axis=\"axis\">\r\n <div\r\n class=\"fb-scr__item\"\r\n data-item=\"\"\r\n cdkDrag\r\n [style.grid-column]=\"'span ' + widthOf(field)\"\r\n [class.fb-scr__item--selected]=\"isSelected(path)\"\r\n [class.fb-scr__item--dragging]=\"isDragging(path)\"\r\n [class.fb-scr__item--container]=\"holdsFields(field)\"\r\n (cdkDragStarted)=\"onFieldDragStarted(path)\"\r\n (cdkDragMoved)=\"onFieldDragMoved($event)\"\r\n (cdkDragEnded)=\"onFieldDragEnded($event, path)\"\r\n (click)=\"select(path); $event.stopPropagation()\"\r\n >\r\n <span class=\"fb-scr__grip\" cdkDragHandle title=\"Trascina per spostare\" aria-hidden=\"true\">\u283F</span>\r\n @if (field.visibilityRule?.conditions?.length) {\r\n <span class=\"fb-scr__flag\" title=\"Ha una regola di visibilita\u2019\">\u25D0</span>\r\n }\r\n\r\n @switch (field.fieldType) {\r\n @case ('RegionContainer') {\r\n <div class=\"fb-scr__section\">\r\n @if (field.regionContainerType !== 'SectionWithoutHeader') {\r\n <header class=\"fb-scr__section-head\">{{ field.fieldText || field.name }}</header>\r\n }\r\n <div class=\"fb-scr__cols\" [attr.data-drop]=\"dropId(path)\" data-axis=\"row\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"listTpl\"\r\n [ngTemplateOutletContext]=\"{ $implicit: field.fields || [], parent: path, axis: 'row' }\"\r\n />\r\n </div>\r\n </div>\r\n }\r\n @case ('Region') {\r\n <div class=\"fb-scr__region\">\r\n <span class=\"fb-scr__region-tag\">{{ field.name }} \u00B7 {{ widthOf(field) }}/12</span>\r\n <div class=\"fb-scr__region-body\" [attr.data-drop]=\"dropId(path)\" data-axis=\"column\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"listTpl\"\r\n [ngTemplateOutletContext]=\"{ $implicit: field.fields || [], parent: path, axis: 'column' }\"\r\n />\r\n </div>\r\n </div>\r\n }\r\n @case ('RepeatingSection') {\r\n <!--\r\n \u00A75.2 \u2014 si disegna **una** riga, ed e' il modello: a runtime la schermata ne rende una copia\r\n per elemento della collection. Disegnarne due sarebbe finto \u2014 quante siano lo decide chi\r\n compila \u2014 e disegnarla senza cornice farebbe credere che i campi siano della schermata.\r\n -->\r\n <div class=\"fb-scr__repeat\">\r\n <header class=\"fb-scr__repeat-head\">\r\n <span>{{ field.fieldText || field.name }}</span>\r\n <span class=\"fb-scr__repeat-meta\">\r\n {{ field.collectionReference || 'collection non indicata' }}\r\n @if (field.minInstances !== undefined || field.maxInstances !== undefined) {\r\n \u00B7 {{ field.minInstances ?? 0 }}\u2013{{ field.maxInstances ?? '\u221E' }}\r\n }\r\n </span>\r\n </header>\r\n <div class=\"fb-scr__repeat-row\">\r\n <span class=\"fb-scr__repeat-tag\">\r\n {{ field.itemLabel || 'Riga 1' }}\r\n @if (field.allowRemove !== false) {\r\n <span class=\"fb-scr__repeat-x\" aria-hidden=\"true\">\u00D7</span>\r\n }\r\n </span>\r\n <div class=\"fb-scr__repeat-body\" [attr.data-drop]=\"dropId(path)\" data-axis=\"column\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"listTpl\"\r\n [ngTemplateOutletContext]=\"{ $implicit: field.fields || [], parent: path, axis: 'column' }\"\r\n />\r\n </div>\r\n </div>\r\n @if (field.allowAdd !== false) {\r\n <span class=\"fb-scr__repeat-add\">+ {{ field.addButtonLabel || 'Aggiungi' }}</span>\r\n }\r\n </div>\r\n }\r\n @case ('DisplayText') {\r\n <p class=\"fb-scr__display\">{{ field.fieldText || '(testo vuoto)' }}</p>\r\n }\r\n @case ('LargeTextArea') {\r\n <label class=\"fb-scr__label\">\r\n {{ captionOf(field) }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__control fb-scr__control--area\">{{ placeholderOf(field) }}</div>\r\n }\r\n @case ('PasswordField') {\r\n <label class=\"fb-scr__label\">\r\n {{ captionOf(field) }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__control\">\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022</div>\r\n }\r\n @case ('DropdownBox') {\r\n <label class=\"fb-scr__label\">\r\n {{ captionOf(field) }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__control fb-scr__control--select\">\r\n <span>{{ field.defaultSelectedChoiceReference || choiceLabelsOf(field)[0] || '\u2014 scegli \u2014' }}</span>\r\n <span aria-hidden=\"true\">\u25BE</span>\r\n </div>\r\n }\r\n @case ('MultiSelectPicklist') {\r\n <label class=\"fb-scr__label\">\r\n {{ captionOf(field) }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__control fb-scr__control--select\">\r\n <span>{{ choiceLabelsOf(field).join(', ') || '\u2014 scegli \u2014' }}</span>\r\n <span aria-hidden=\"true\">\u2261</span>\r\n </div>\r\n }\r\n @case ('RadioButtons') {\r\n <label class=\"fb-scr__label\">\r\n {{ captionOf(field) }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__options\">\r\n @for (option of choiceLabelsOf(field); track $index) {\r\n <span class=\"fb-scr__option\">\u25EF {{ option }}</span>\r\n }\r\n @if (!choiceLabelsOf(field).length) {\r\n <span class=\"fb-scr__option fb-scr__option--missing\">Nessuna opzione</span>\r\n }\r\n </div>\r\n }\r\n @case ('MultiSelectCheckboxes') {\r\n <label class=\"fb-scr__label\">\r\n {{ captionOf(field) }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__options\">\r\n @for (option of choiceLabelsOf(field); track $index) {\r\n <span class=\"fb-scr__option\">\u2610 {{ option }}</span>\r\n }\r\n @if (!choiceLabelsOf(field).length) {\r\n <span class=\"fb-scr__option fb-scr__option--missing\">Nessuna opzione</span>\r\n }\r\n </div>\r\n }\r\n @case ('ComponentInstance') {\r\n <!--\r\n L\u2019etichetta si disegna se c\u2019e\u2019: il runtime la consegna in `label` come per gli altri\r\n campi (\u00A75.2). Nessun asterisco: un componente non raccoglie un valore, quindi\r\n \u00ABobbligatorio\u00BB non ha niente su cui applicarsi.\r\n -->\r\n @if (field.fieldText) {\r\n <label class=\"fb-scr__label\">{{ field.fieldText }}</label>\r\n }\r\n <div class=\"fb-scr__component\">\r\n <span aria-hidden=\"true\">\u2B21</span>\r\n {{ field.extensionName || 'Componente non indicato' }}\r\n </div>\r\n }\r\n @case ('ObjectProvided') {\r\n <label class=\"fb-scr__label\">\r\n {{ field.fieldText || field.objectFieldReference || field.name }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__control\">{{ placeholderOf(field) }}</div>\r\n <span class=\"fb-scr__tag\">{{ field.objectFieldReference || 'campo non indicato' }}</span>\r\n }\r\n @default {\r\n <label class=\"fb-scr__label\">\r\n {{ captionOf(field) }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__control\">{{ placeholderOf(field) }}</div>\r\n @if (isUnknownType(field)) {\r\n <span class=\"fb-scr__tag fb-scr__tag--warn\">{{ field.fieldType }}: tipo non nel dizionario</span>\r\n }\r\n }\r\n }\r\n <!--\r\n Il formato sta **fuori** dal @switch perche' non e' del tipo di campo: e' di cio' che il campo\r\n raccoglie, e va visto anche dove non si applica \u2014 e\u2019 l\u00EC che si riconosce un\r\n SCREEN_FIELD_FORMAT_NOT_APPLICABLE senza aprire le proprieta\u2019.\r\n -->\r\n @if (field.format) {\r\n <span class=\"fb-scr__tag\">{{ field.format }}</span>\r\n }\r\n </div>\r\n</ng-template>\r\n", styles: [".fb-scr{display:grid;grid-template-columns:220px minmax(0,1fr) 340px;gap:12px;align-items:start;margin-bottom:14px}@media(max-width:1100px){.fb-scr{grid-template-columns:minmax(0,1fr)}}.fb-scr__side,.fb-scr__props{min-width:0;padding:10px;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius, 10px);background:var(--fb-surface-alt, #f7f8fa)}.fb-scr__tabs{display:flex;gap:4px;margin-bottom:8px}.fb-scr__tab{flex:1;padding:5px 8px;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-xs, 6px);background:var(--fb-surface, #fff);color:var(--fb-text-muted, #6b7086);font:inherit;font-size:11px;font-weight:600;cursor:pointer}.fb-scr__tab--active{border-color:var(--fb-accent, #4f6ef7);background:color-mix(in srgb,var(--fb-accent, #4f6ef7) 10%,transparent);color:var(--fb-accent-strong, #3d59e0)}.fb-scr__side-hint,.fb-scr__side-empty{margin:6px 0;font-size:11px;color:var(--fb-text-muted, #6b7086)}.fb-scr__palette{display:flex;flex-direction:column;gap:4px;margin:8px 0 0;padding:0;max-height:320px;overflow-y:auto;overscroll-behavior:contain;list-style:none}.fb-scr__chip{display:flex;align-items:center;gap:8px;width:100%;padding:6px 8px;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-xs, 6px);background:var(--fb-surface, #fff);color:var(--fb-text, #1a1c23);font:inherit;font-size:12px;text-align:left;cursor:grab}.fb-scr__chip:hover{border-color:var(--fb-accent, #4f6ef7)}.fb-scr__chip-icon{flex:none;width:20px;text-align:center;color:var(--fb-text-muted, #6b7086)}.fb-scr__chip-label{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-scr__chip-label small{margin-left:4px;color:var(--fb-text-subtle, #98a2b3);font-size:10px}.fb-scr__canvas{min-width:0;padding:14px;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius, 10px);background:var(--fb-canvas-bg, #f4f5f7);max-height:60vh;overflow-y:auto}.fb-scr__frame{position:relative;display:flex;flex-direction:column;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-sm, 8px);background:var(--fb-surface, #fff);box-shadow:var(--fb-shadow-sm, 0 1px 2px rgb(16 24 40 / 6%));overflow:hidden}.fb-scr__frame-head{display:flex;align-items:center;gap:6px;padding:8px 12px;border-bottom:1px solid var(--fb-border-subtle, #eef0f4);font-size:13px;font-weight:600}.fb-scr__help{display:inline-flex;align-items:center;justify-content:center;width:15px;height:15px;border-radius:50%;background:var(--fb-surface-sunken, #eef0f4);color:var(--fb-text-muted, #6b7086);font-size:10px}.fb-scr__body,.fb-scr__region-body{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));align-content:start;gap:8px 0;padding:12px;min-height:90px}.fb-scr__region-body{padding:8px;min-height:60px}.fb-scr__cols{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));align-items:stretch;gap:0;padding:8px;min-height:60px}.fb-scr__cols>.fb-scr__item{display:flex;align-self:stretch}.fb-scr__cols>.fb-scr__item>.fb-scr__region{flex:1;min-width:0}.fb-scr__drop-hint{grid-column:1 / -1;margin:0;padding:10px;border:1px dashed var(--fb-border-strong, #cfd4de);border-radius:var(--fb-radius-xs, 6px);color:var(--fb-text-subtle, #98a2b3);font-size:11px;text-align:center}.fb-scr__marker{position:absolute;z-index:2;border-radius:2px;background:var(--fb-accent, #4f6ef7);pointer-events:none}.fb-scr__item{position:relative;box-sizing:border-box;padding:6px 8px;border:1px solid transparent;border-radius:var(--fb-radius-xs, 6px);cursor:pointer}.fb-scr__item:hover{border-color:var(--fb-border-strong, #cfd4de);background:color-mix(in srgb,var(--fb-accent, #4f6ef7) 4%,transparent)}.fb-scr__item--selected{border-color:var(--fb-accent, #4f6ef7);box-shadow:inset 0 0 0 1px var(--fb-accent, #4f6ef7)}.fb-scr__item--dragging{opacity:.45;pointer-events:none}.fb-scr__grip{position:absolute;top:2px;left:-2px;padding:0 3px;color:var(--fb-text-subtle, #98a2b3);font-size:11px;line-height:1;opacity:0;cursor:grab}.fb-scr__item:hover>.fb-scr__grip,.fb-scr__item--selected>.fb-scr__grip{opacity:1}.fb-scr__flag{position:absolute;top:2px;right:4px;color:var(--fb-text-muted, #6b7086);font-size:11px}.fb-scr__label{display:block;margin-bottom:3px;font-size:11px;font-weight:600;color:var(--fb-text, #1a1c23)}.fb-scr__req{color:var(--fb-error, #dc2626)}.fb-scr__control{display:flex;align-items:center;justify-content:space-between;gap:6px;min-height:26px;padding:4px 8px;border:1px solid var(--fb-border-strong, #cfd4de);border-radius:var(--fb-radius-xs, 6px);background:var(--fb-surface, #fff);color:var(--fb-text-subtle, #98a2b3);font-size:12px;pointer-events:none}.fb-scr__control--area{min-height:54px;align-items:flex-start}.fb-scr__display{margin:0;font-size:12px;color:var(--fb-text, #1a1c23)}.fb-scr__options{display:flex;flex-direction:column;gap:2px;pointer-events:none}.fb-scr__option{font-size:12px;color:var(--fb-text-muted, #6b7086)}.fb-scr__option--missing{color:var(--fb-warning, #b7791f)}.fb-scr__component{display:flex;align-items:center;gap:6px;padding:10px;border:1px dashed var(--fb-accent, #4f6ef7);border-radius:var(--fb-radius-xs, 6px);background:color-mix(in srgb,var(--fb-accent, #4f6ef7) 5%,transparent);font-size:12px;color:var(--fb-text-muted, #6b7086)}.fb-scr__tag{display:inline-block;margin-top:3px;font-size:10px;color:var(--fb-text-subtle, #98a2b3)}.fb-scr__tag--warn{color:var(--fb-warning, #b7791f)}.fb-scr__section{border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-xs, 6px);background:var(--fb-surface-alt, #f7f8fa)}.fb-scr__section-head{padding:5px 10px;border-bottom:1px solid var(--fb-border-subtle, #eef0f4);font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--fb-text-muted, #6b7086)}.fb-scr__region{border:1px dashed var(--fb-border-strong, #cfd4de);border-radius:var(--fb-radius-xs, 6px);background:var(--fb-surface, #fff)}.fb-scr__region-tag{display:block;padding:3px 6px 0;font-size:10px;color:var(--fb-text-subtle, #98a2b3)}.fb-scr__repeat{border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-xs, 6px);background:var(--fb-surface-alt, #f7f8fa)}.fb-scr__repeat-head{display:flex;align-items:baseline;gap:8px;padding:5px 10px;border-bottom:1px solid var(--fb-border-subtle, #eef0f4);font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--fb-text-muted, #6b7086)}.fb-scr__repeat-meta{margin-left:auto;font-size:10px;font-weight:400;text-transform:none;letter-spacing:0;color:var(--fb-text-subtle, #98a2b3)}.fb-scr__repeat-row{margin:8px;border:1px dashed var(--fb-border-strong, #cfd4de);border-radius:var(--fb-radius-xs, 6px);background:var(--fb-surface, #fff)}.fb-scr__repeat-tag{display:flex;align-items:center;gap:6px;padding:3px 6px 0;font-size:10px;color:var(--fb-text-subtle, #98a2b3)}.fb-scr__repeat-x{margin-left:auto}.fb-scr__repeat-body{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));align-content:start;gap:8px 0;padding:8px;min-height:60px}.fb-scr__repeat-add{display:inline-block;margin:0 8px 8px;padding:3px 10px;border:1px dashed var(--fb-border-strong, #cfd4de);border-radius:var(--fb-radius-xs, 6px);font-size:11px;color:var(--fb-text-muted, #6b7086)}.fb-scr__frame-foot{display:flex;align-items:center;gap:6px;padding:8px 12px;border-top:1px solid var(--fb-border-subtle, #eef0f4);background:var(--fb-surface-alt, #f7f8fa)}.fb-scr__spacer{flex:1}.fb-scr__btn{padding:3px 10px;border:1px solid var(--fb-border-strong, #cfd4de);border-radius:var(--fb-radius-xs, 6px);font-size:11px;color:var(--fb-text-muted, #6b7086)}.fb-scr__btn--primary{border-color:var(--fb-accent, #4f6ef7);background:var(--fb-accent, #4f6ef7);color:var(--fb-accent-contrast, #fff)}.fb-scr__canvas-note{margin-top:10px}.fb-scr__props{background:var(--fb-surface, #fff)}.fb-scr__props-head{display:flex;align-items:center;gap:8px;margin-bottom:8px}.fb-scr__props-title{flex:1;min-width:0;margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--fb-text-muted, #6b7086)}.fb-scr__props-actions{display:flex;flex-wrap:wrap;gap:3px}.fb-btn--sm{padding:3px 7px;font-size:11px}.fb-scr__width{display:flex;align-items:center;gap:8px}.fb-scr__width input[type=range]{flex:1;min-width:0}.fb-scr__width-value{font-size:11px;color:var(--fb-text-muted, #6b7086)}\n"], dependencies: [{ kind: "directive", type: CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "component", type: ConditionEditorComponent, selector: "fb-condition-editor", inputs: ["holder", "title", "allowFormula", "preferredRoots", "allowLogic", "issuePath"], outputs: ["changed"] }, { kind: "component", type: ConnectorEditorComponent, selector: "fb-connector-editor", inputs: ["nodeName", "node", "outlets", "title"], outputs: ["connectorChanged"] }, { 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", "preferredRoots", "commitOn"], outputs: ["expressionChange"] }, { kind: "component", type: MergeTextEditorComponent, selector: "fb-merge-text-editor", inputs: ["text", "usage", "placeholder", "ariaLabel", "disabled", "rows", "commitOn"], outputs: ["textChange"] }, { kind: "component", type: NamePickerComponent, selector: "fb-name-picker", inputs: ["value", "options", "label", "placeholder", "disabled", "unknownMessage", "unknownSeverity", "unusableOptions", "unusableMessage", "emptyMessage", "isMono", "commitOn"], outputs: ["valueChange"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: ObjectPickerComponent, selector: "fb-object-picker", inputs: ["value", "label", "placeholder", "disabled"], outputs: ["valueChange"] }, { kind: "component", type: ParameterEditorComponent, selector: "fb-parameter-editor", inputs: ["holder", "catalogParameters", "inputTitle", "outputTitle", "showInputs", "showOutputs", "outputsDisabledReason", "extraTargets", "preferredRoots"], outputs: ["changed"] }, { kind: "component", type: ReferencePickerComponent, selector: "fb-reference-picker", inputs: ["value", "label", "placeholder", "disabled", "dataType", "isCollection", "objectType", "writableOnly", "elementsOnly", "extraReferences", "preferredRoots"], outputs: ["valueChange"] }, { kind: "directive", type: SelectValueDirective, selector: "select[fbValue]", inputs: ["fbValue"] }, { kind: "component", type: StructureMemberPickerComponent, selector: "fb-structure-member-picker", inputs: ["value", "className", "usage", "label", "placeholder", "disabled", "unverified"], outputs: ["valueChange"] }, { 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", "preferredRoots"], outputs: ["valueChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
13527
13863
  }
13528
13864
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.32", ngImport: i0, type: DynamicScreenInspectorComponent, decorators: [{
13529
13865
  type: Component,
@@ -13544,7 +13880,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.32", ngImpo
13544
13880
  StructureMemberPickerComponent,
13545
13881
  StructurePickerComponent,
13546
13882
  ValueEditorComponent,
13547
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"fb-scr\" #composer>\r\n <!-- ============================================================ sinistra -->\r\n <aside class=\"fb-scr__side\" aria-label=\"Componenti e campi\">\r\n <div class=\"fb-scr__tabs\" role=\"tablist\">\r\n <button\r\n type=\"button\"\r\n role=\"tab\"\r\n class=\"fb-scr__tab\"\r\n [class.fb-scr__tab--active]=\"paletteTab() === 'components'\"\r\n [attr.aria-selected]=\"paletteTab() === 'components'\"\r\n (click)=\"setPaletteTab('components')\"\r\n >\r\n Componenti\r\n </button>\r\n <button\r\n type=\"button\"\r\n role=\"tab\"\r\n class=\"fb-scr__tab\"\r\n [class.fb-scr__tab--active]=\"paletteTab() === 'fields'\"\r\n [attr.aria-selected]=\"paletteTab() === 'fields'\"\r\n (click)=\"setPaletteTab('fields')\"\r\n >\r\n Campi\r\n </button>\r\n </div>\r\n\r\n @if (paletteTab() === 'components') {\r\n @if (!dictionary.screenFieldTypes().length) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Il dizionario <code>screenFieldTypes</code> non e\u2019 disponibile: senza i suoi flag l\u2019editor non\r\n sa quali campi ammette ciascun tipo, e non li inventa.\r\n </p>\r\n }\r\n <p class=\"fb-scr__side-hint\">Trascina sulla schermata, oppure clicca per aggiungere.</p>\r\n <ul class=\"fb-scr__palette\">\r\n @for (entry of dictionary.screenFieldTypes(); track entry.value) {\r\n <li>\r\n <!--\r\n \u00A75.2 \u2014 l'unico chip che si spegne: una sezione ripetibile dentro un'altra. Non si\r\n annidano, e il cursore e' una variabile sola per sezione.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-scr__chip\"\r\n cdkDrag\r\n [disabled]=\"!canAddAtSelection(entry.value)\"\r\n [title]=\"\r\n canAddAtSelection(entry.value)\r\n ? entry.description || entry.label\r\n : 'Le sezioni ripetibili non si annidano: seleziona un punto fuori dalla riga.'\r\n \"\r\n (cdkDragMoved)=\"onPaletteDragMoved($event)\"\r\n (cdkDragEnded)=\"onComponentDragEnded($event, entry.value)\"\r\n (click)=\"addComponentAtSelection(entry.value)\"\r\n >\r\n <span class=\"fb-scr__chip-icon\" aria-hidden=\"true\">{{ iconOf(entry.value) }}</span>\r\n <span class=\"fb-scr__chip-label\">{{ entry.label }}</span>\r\n </button>\r\n </li>\r\n }\r\n </ul>\r\n } @else {\r\n <p class=\"fb-scr__side-hint\">\r\n Un campo dell\u2019entita\u2019 porta con se\u2019 tipo, etichetta, obbligatorieta\u2019 e opzioni: arrivano dallo\r\n schema dati, qui non si ridichiarano.\r\n </p>\r\n <!-- Il catalogo non e' un dizionario chiuso: si sceglie o si scrive, come ovunque si\r\n indichi un'entita' (vedi `object-picker`). -->\r\n <fb-object-picker\r\n [value]=\"fieldsObject()\"\r\n label=\"Entita\u2019\"\r\n placeholder=\"Scrivi o scegli un\u2019entita\u2019\"\r\n (valueChange)=\"setFieldsObject($event)\"\r\n />\r\n @if (fieldsObject() && !fieldsOfObject().length) {\r\n <p class=\"fb-scr__side-empty\">\r\n Nessun campo dichiarato per questa entita\u2019: il catalogo non li espone, non significa che non\r\n ci siano.\r\n </p>\r\n }\r\n <ul class=\"fb-scr__palette\">\r\n @for (field of fieldsOfObject(); track $index) {\r\n <li>\r\n <button\r\n type=\"button\"\r\n class=\"fb-scr__chip\"\r\n cdkDrag\r\n [title]=\"field.name + (field.dataType ? ' \u00B7 ' + field.dataType : '')\"\r\n (cdkDragMoved)=\"onPaletteDragMoved($event)\"\r\n (cdkDragEnded)=\"onObjectFieldDragEnded($event, field)\"\r\n (click)=\"addObjectFieldAtSelection(field)\"\r\n >\r\n <span class=\"fb-scr__chip-icon\" aria-hidden=\"true\">\u2317</span>\r\n <span class=\"fb-scr__chip-label\">\r\n {{ field.label || field.name }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n <small>{{ field.dataType }}</small>\r\n </span>\r\n </button>\r\n </li>\r\n }\r\n </ul>\r\n }\r\n </aside>\r\n\r\n <!-- ============================================================== centro -->\r\n <!-- Cliccare fuori da un campo riporta a destra le proprieta' della schermata. -->\r\n <section class=\"fb-scr__canvas\" aria-label=\"Anteprima della schermata\" (click)=\"selectScreen()\">\r\n <div class=\"fb-scr__frame\">\r\n @if (showHeader()) {\r\n <header class=\"fb-scr__frame-head\">\r\n {{ $any(node()).label || name() }}\r\n @if (screen().helpText) {\r\n <span class=\"fb-scr__help\" [title]=\"screen().helpText || ''\">?</span>\r\n }\r\n </header>\r\n }\r\n\r\n <div\r\n class=\"fb-scr__body\"\r\n [attr.data-drop]=\"''\"\r\n data-axis=\"column\"\r\n [class.fb-scr__body--empty]=\"isEmpty()\"\r\n >\r\n <ng-container\r\n [ngTemplateOutlet]=\"listTpl\"\r\n [ngTemplateOutletContext]=\"{ $implicit: screen().fields || [], parent: [], axis: 'column' }\"\r\n />\r\n </div>\r\n\r\n <!--\r\n La barra di inserimento e' un **velo** sopra l'anteprima, non un figlio del contenitore di\r\n rilascio: dentro la griglia occupava una riga intera e spostava in basso proprio la sezione\r\n che si stava puntando, che allora usciva da sotto il puntatore e il bersaglio oscillava.\r\n -->\r\n @if (dropMarker(); as bar) {\r\n <div\r\n class=\"fb-scr__marker\"\r\n [style.left.px]=\"bar.left\"\r\n [style.top.px]=\"bar.top\"\r\n [style.width.px]=\"bar.width\"\r\n [style.height.px]=\"bar.height\"\r\n ></div>\r\n }\r\n\r\n @if (showFooter()) {\r\n <footer class=\"fb-scr__frame-foot\">\r\n @if (allowPause()) {\r\n <span class=\"fb-scr__btn fb-scr__btn--ghost\">{{ screen().pauseButtonLabel || 'Pausa' }}</span>\r\n }\r\n <span class=\"fb-scr__spacer\"></span>\r\n @if (allowBack()) {\r\n <span class=\"fb-scr__btn fb-scr__btn--ghost\">{{ screen().backButtonLabel || 'Indietro' }}</span>\r\n }\r\n <span class=\"fb-scr__btn fb-scr__btn--primary\">\r\n {{ screen().nextOrFinishButtonLabel || (allowFinish() ? 'Fine' : 'Avanti') }}\r\n </span>\r\n </footer>\r\n }\r\n </div>\r\n\r\n @if (isEmpty()) {\r\n <p class=\"fb-callout fb-callout--warn fb-scr__canvas-note\">\r\n La schermata non ha campi: non c\u2019e\u2019 niente da mostrare all\u2019utente (SCREEN_WITHOUT_FIELDS).\r\n </p>\r\n }\r\n </section>\r\n\r\n <!-- ============================================================== destra -->\r\n <aside class=\"fb-scr__props\" aria-label=\"Proprieta\u2019\">\r\n @if (!selectedField()) {\r\n <!-- ------------------------------------------------ proprieta' della schermata -->\r\n <header class=\"fb-scr__props-head\">\r\n <h3 class=\"fb-scr__props-title\">Schermata</h3>\r\n </header>\r\n <p class=\"fb-scr__side-hint\">Seleziona un campo nell\u2019anteprima per configurarlo.</p>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Testo di aiuto</label>\r\n <textarea\r\n class=\"fb-textarea\"\r\n [value]=\"screen().helpText || ''\"\r\n (input)=\"setScreenText('helpText', $any($event.target).value)\"\r\n ></textarea>\r\n </div>\r\n\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Navigazione</legend>\r\n <p class=\"fb-section__note\">\r\n Questi flag sono un\u2019intenzione, non la verita\u2019 finale: a runtime il motore comunica\r\n <code>canGoBack</code>, <code>canFinish</code> e <code>canPause</code> nella richiesta della\r\n schermata.\r\n </p>\r\n <label class=\"fb-check\">\r\n <input type=\"checkbox\" [checked]=\"allowBack()\" (change)=\"setScreenFlag('allowBack', $any($event.target).checked)\" />\r\n Consenti \u00ABindietro\u00BB\r\n </label>\r\n <label class=\"fb-check\">\r\n <input type=\"checkbox\" [checked]=\"allowFinish()\" (change)=\"setScreenFlag('allowFinish', $any($event.target).checked)\" />\r\n Consenti \u00ABfine\u00BB\r\n </label>\r\n <label class=\"fb-check\">\r\n <input type=\"checkbox\" [checked]=\"allowPause()\" (change)=\"setScreenFlag('allowPause', $any($event.target).checked)\" />\r\n Consenti \u00ABpausa\u00BB\r\n </label>\r\n <label class=\"fb-check\">\r\n <input type=\"checkbox\" [checked]=\"showHeader()\" (change)=\"setScreenFlag('showHeader', $any($event.target).checked)\" />\r\n Mostra l\u2019intestazione\r\n </label>\r\n <label class=\"fb-check\">\r\n <input type=\"checkbox\" [checked]=\"showFooter()\" (change)=\"setScreenFlag('showFooter', $any($event.target).checked)\" />\r\n Mostra il piede\r\n </label>\r\n\r\n @if (allowPause()) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Testo mostrato alla pausa</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"screen().pausedText || ''\"\r\n (input)=\"setScreenText('pausedText', $any($event.target).value)\"\r\n />\r\n </div>\r\n }\r\n\r\n @if (isDeadEnd()) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n Questa schermata non ha una destinazione e non consente \u00ABfine\u00BB: e\u2019 un vicolo cieco, e\r\n l\u2019utente resterebbe bloccato (SCREEN_DEAD_END).\r\n </p>\r\n }\r\n </fieldset>\r\n\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Pulsanti</legend>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Etichetta \u00ABindietro\u00BB</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"screen().backButtonLabel || ''\"\r\n (input)=\"setScreenText('backButtonLabel', $any($event.target).value)\"\r\n />\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Etichetta \u00ABavanti / fine\u00BB</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"screen().nextOrFinishButtonLabel || ''\"\r\n (input)=\"setScreenText('nextOrFinishButtonLabel', $any($event.target).value)\"\r\n />\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Etichetta \u00ABpausa\u00BB</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"screen().pauseButtonLabel || ''\"\r\n (input)=\"setScreenText('pauseButtonLabel', $any($event.target).value)\"\r\n />\r\n </div>\r\n </fieldset>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Stage mostrato nell\u2019avanzamento</label>\r\n <fb-name-picker\r\n [value]=\"screen().stageReference\"\r\n [options]=\"stageOptions()\"\r\n label=\"Stage\"\r\n placeholder=\"Scegli uno stage\"\r\n unknownMessage=\"Questo stage non e\u2019 dichiarato dal flow.\"\r\n emptyMessage=\"Il flow non dichiara stage: creali nel pannello delle risorse.\"\r\n (valueChange)=\"setScreenText('stageReference', $event ?? '')\"\r\n />\r\n </div>\r\n\r\n <!--\r\n Screen action (\u00A75.2). Due elenchi e non uno perche' il contratto li separa: piu' trigger\r\n possono invocare la stessa action, quindi \u00ABchi la chiama\u00BB non e' una sua proprieta'. Le\r\n action si aprono una alla volta: i cataloghi dipendono da tipo e nome, e tenerne N in volo\r\n sarebbe N volte la corsa fra risposte.\r\n -->\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Azioni della schermata</legend>\r\n <p class=\"fb-section__note\">\r\n Un campo che l\u2019utente compila puo\u2019 innescare un\u2019action i cui risultati finiscono negli\r\n <strong>altri campi di questa schermata</strong>: e\u2019 il caso \u00ABscrivi il codice fiscale e nome\r\n e cognome compaiono da soli\u00BB. L\u2019alternativa sarebbe spezzare la schermata in due con un\r\n elemento Action in mezzo.\r\n </p>\r\n\r\n <div class=\"fb-list\">\r\n @for (action of screenActions(); track $index) {\r\n <div class=\"fb-list__item\">\r\n <div class=\"fb-list__header\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost\"\r\n [attr.aria-expanded]=\"selectedActionIndex() === $index\"\r\n (click)=\"selectAction(selectedActionIndex() === $index ? null : $index)\"\r\n >\r\n {{ selectedActionIndex() === $index ? '\u25BE' : '\u25B8' }}\r\n {{ action.name || '(senza nome)' }}\r\n </button>\r\n <span class=\"fb-list__spacer\"></span>\r\n <span class=\"fb-scr__side-hint\">{{ action.actionName || 'action non scelta' }}</span>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n aria-label=\"Rimuovi l\u2019action\"\r\n (click)=\"removeScreenAction($index)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n\r\n @if (actionLosesResult(action)) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Questa action viene invocata ma il suo risultato si perde: aggiungi un parametro di\r\n uscita, oppure accendi l\u2019output automatico (SCREEN_ACTION_WITHOUT_OUTPUTS).\r\n </p>\r\n }\r\n @if (actionHasNoTrigger(action)) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Nessun trigger la invoca: senza, non girera\u2019 mai.\r\n </p>\r\n }\r\n\r\n @if (selectedActionIndex() === $index) {\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]=\"action.name || ''\"\r\n (change)=\"setActionName($index, $any($event.target).value)\"\r\n />\r\n @if (actionNameError(action); as error) {\r\n <p class=\"fb-field__error\">{{ error }}</p>\r\n }\r\n <p class=\"fb-field__hint\">\r\n E\u2019 il nome con cui la citano i trigger, e vive nello spazio dei nomi del flow. Con\r\n l\u2019output automatico e\u2019 anche la radice del riferimento:\r\n <code>{{ action.name || 'Cerca' }}.NomeOutput</code>.\r\n </p>\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Tipo di action</label>\r\n <fb-name-picker\r\n [value]=\"action.actionType\"\r\n [options]=\"actionTypeOptions()\"\r\n label=\"Tipo di action\"\r\n placeholder=\"Scrivi o scegli un tipo\"\r\n [isMono]=\"false\"\r\n unknownMessage=\"Questo tipo di action non e\u2019 fra quelli dichiarati dal sistema ospite.\"\r\n emptyMessage=\"Catalogo dei tipi di action non disponibile: puoi scrivere il nome a mano.\"\r\n (valueChange)=\"setActionType($index, $event ?? '')\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Action</label>\r\n <fb-name-picker\r\n [value]=\"action.actionName\"\r\n [options]=\"actionOptions()\"\r\n label=\"Action\"\r\n placeholder=\"Scrivi o scegli un\u2019action\"\r\n [isMono]=\"false\"\r\n unknownMessage=\"Questa action non esiste nel catalogo del tipo scelto: e\u2019 ACTION_UNKNOWN.\"\r\n unknownSeverity=\"error\"\r\n emptyMessage=\"Scegli prima il tipo di action, oppure scrivi il nome a mano.\"\r\n (valueChange)=\"setActionTarget($index, $event ?? '')\"\r\n />\r\n @if (!action.actionName) {\r\n <p class=\"fb-field__error\">Obbligatoria: senza, ACTION_NAME_MISSING.</p>\r\n }\r\n </div>\r\n\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"action.storeOutputAutomatically === true\"\r\n (change)=\"setActionStoreOutput($index, $any($event.target).checked)\"\r\n />\r\n Output automatico\r\n </label>\r\n <p class=\"fb-field__hint\">\r\n L\u2019output automatico rende il risultato leggibile come\r\n <code>{{ action.name || 'Cerca' }}.NomeOutput</code>, ma\r\n <strong>non scrive in nessun campo</strong>: e\u2019 la forma da usare quando il risultato\r\n serve a una condizione, non a precompilare.\r\n </p>\r\n\r\n <!--\r\n L'unico posto in cui un campo di schermata e' una destinazione (\u00A75.2):\r\n `POST /flows/references/writable` continua a escluderlo, e un Assignment che ci\r\n scrive resta TARGET_NOT_WRITABLE.\r\n -->\r\n <fb-parameter-editor\r\n [holder]=\"action\"\r\n [catalogParameters]=\"actionParameterCatalog()\"\r\n [showOutputs]=\"action.storeOutputAutomatically !== true\"\r\n outputTitle=\"Campi da riempire\"\r\n [extraTargets]=\"screenFieldTargets()\"\r\n [outputsDisabledReason]=\"\r\n action.storeOutputAutomatically === true\r\n ? 'Con l\u2019output automatico il risultato non viene scritto nei campi.'\r\n : null\r\n \"\r\n (changed)=\"onActionParametersChanged($index, $event)\"\r\n />\r\n }\r\n </div>\r\n } @empty {\r\n <p class=\"fb-empty\">Nessuna action: i campi di questa schermata li compila solo l\u2019utente.</p>\r\n }\r\n </div>\r\n\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"addScreenAction()\">Aggiungi action</button>\r\n </fieldset>\r\n\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Trigger</legend>\r\n <p class=\"fb-section__note\">\r\n Quale campo invoca quale action. Senza condizioni l\u2019action gira a ogni cambio del campo.\r\n </p>\r\n\r\n <div class=\"fb-list\">\r\n @for (trigger of screenTriggers(); 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 trigger\"\r\n (click)=\"removeScreenTrigger($index)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Action da invocare</label>\r\n <fb-name-picker\r\n [value]=\"trigger.screenActionName\"\r\n [options]=\"screenActionOptions()\"\r\n label=\"Action della schermata\"\r\n placeholder=\"Scegli un\u2019action\"\r\n unknownMessage=\"Questa schermata non dichiara un\u2019action con questo nome: e\u2019 SCREEN_ACTION_UNKNOWN.\"\r\n unknownSeverity=\"error\"\r\n emptyMessage=\"Aggiungi prima un\u2019action qui sopra.\"\r\n (valueChange)=\"setTriggerProperty($index, 'screenActionName', $event ?? '')\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Campo che la innesca</label>\r\n <!-- I campi di **questa** schermata, non i riferimenti in generale (\u00A75.2). -->\r\n <fb-name-picker\r\n [value]=\"trigger.triggerFieldName\"\r\n [options]=\"triggerFieldOptions()\"\r\n label=\"Campo della schermata\"\r\n placeholder=\"Scegli un campo\"\r\n unknownMessage=\"Questo non e\u2019 un campo di questa schermata: e\u2019 SCREEN_TRIGGER_FIELD_UNKNOWN.\"\r\n unknownSeverity=\"error\"\r\n emptyMessage=\"La schermata non ha ancora campi che raccolgono un valore.\"\r\n (valueChange)=\"setTriggerProperty($index, 'triggerFieldName', $event ?? '')\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">All\u2019arrivo sulla schermata</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"trigger.initBehavior || ''\"\r\n (change)=\"setTriggerProperty($index, 'initBehavior', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 non invocare \u2014</option>\r\n <option value=\"runOnLoad\">Invoca al caricamento (runOnLoad)</option>\r\n </select>\r\n @if (triggerInitIsUnknown(trigger)) {\r\n <p class=\"fb-field__error\">\r\n \u00AB{{ trigger.initBehavior }}\u00BB non e\u2019 ammesso: l\u2019unico valore e\u2019 <code>runOnLoad</code>\r\n (SCREEN_TRIGGER_INIT_BEHAVIOR_UNKNOWN).\r\n </p>\r\n }\r\n @if (triggerFiresOnLoadByDefault(trigger)) {\r\n <p class=\"fb-field__hint\">\r\n Non serve: \u00AB{{ trigger.triggerFieldName }}\u00BB ha un valore di default, e un campo\r\n precompilato dal flow innesca l\u2019action gi\u00E0 all\u2019arrivo sulla schermata.\r\n </p>\r\n }\r\n </div>\r\n\r\n @if (triggerHasNoCause(trigger)) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Senza un campo che la innesca e senza l\u2019invocazione al caricamento, questo trigger non\r\n scattera\u2019 mai (SCREEN_TRIGGER_WITHOUT_CAUSE).\r\n </p>\r\n }\r\n\r\n <fb-condition-editor\r\n [holder]=\"triggerConditions(trigger)\"\r\n title=\"Invoca l\u2019action quando\"\r\n [allowFormula]=\"false\"\r\n [issuePath]=\"'triggers[' + $index + ']'\"\r\n (changed)=\"onTriggerConditionsChanged($index, $event)\"\r\n />\r\n </div>\r\n } @empty {\r\n <p class=\"fb-empty\">Nessun trigger: le action dichiarate non verranno invocate.</p>\r\n }\r\n </div>\r\n\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"addScreenTrigger()\">Aggiungi trigger</button>\r\n </fieldset>\r\n\r\n <!-- --------------------------------------------- regole della schermata (\u00A75.2) -->\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Regole della schermata</legend>\r\n <p class=\"fb-section__note\">\r\n Le verifiche che <strong>non appartengono a nessun campo</strong>: \u00ABalmeno un punto fra le\r\n due sezioni\u00BB, \u00ABla somma delle percentuali fa cento\u00BB. Si valutano all\u2019invio, dopo i campi e\r\n dopo le righe, e una regola non soddisfatta <strong>impedisce alla schermata di\r\n avanzare</strong>. La <code>validationRule</code> di un campo non basta \u2014 giudica un solo\r\n valore, e su una sezione ripetibile e\u2019 addirittura inerte.\r\n </p>\r\n\r\n <div class=\"fb-list\">\r\n @for (rule of screenRules(); 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__title\">{{ rule.errorMessage || '(senza messaggio)' }}</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--sm\"\r\n aria-label=\"Rimuovi la regola\"\r\n (click)=\"removeScreenRule($index)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n\r\n <!--\r\n \u00A75.2 \u2014 qui `Formula` **e'** un `conditionLogic` ammesso, a differenza di un\r\n `filterLogic`: la regola puo' essere un'espressione booleana. Resta il consiglio,\r\n scritto sotto, di preferire le condizioni.\r\n -->\r\n <fb-condition-editor\r\n [holder]=\"rule\"\r\n title=\"La schermata e\u2019 valida quando\"\r\n [allowFormula]=\"true\"\r\n [issuePath]=\"'validationRules[' + $index + ']'\"\r\n (changed)=\"onScreenRuleConditionsChanged($index, $event)\"\r\n />\r\n @if (screenRuleIsEmpty(rule)) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n La regola non verifica niente: senza condizioni non impedisce mai l\u2019invio.\r\n </p>\r\n }\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Messaggio</label>\r\n <fb-merge-text-editor\r\n [text]=\"rule.errorMessage || ''\"\r\n [rows]=\"2\"\r\n commitOn=\"change\"\r\n placeholder=\"Indica almeno un punto di fornitura, luce o gas.\"\r\n ariaLabel=\"Messaggio della regola\"\r\n (textChange)=\"setScreenRuleProperty($index, 'errorMessage', $event)\"\r\n >\r\n <p class=\"fb-field__hint\">\r\n \u00C8 il testo dell\u2019autore e non nomina nessun campo: il runtime non ne costruisce\r\n uno, perche\u2019 un campo da nominare non c\u2019e\u2019.\r\n </p>\r\n </fb-merge-text-editor>\r\n @if (screenRuleHasNoMessage(rule)) {\r\n <p class=\"fb-field__error\">\r\n Obbligatorio: senza, la schermata si rifiuta di avanzare senza dire perche\u2019\r\n (SCREEN_RULE_INVALID).\r\n </p>\r\n }\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Aggancia il messaggio a un campo</label>\r\n <!--\r\n Stesso elenco degli inneschi: i campi della schermata **senza** quelli dentro una\r\n riga, che esistono in una copia per elemento e non individuano un controllo (\u00A75.2).\r\n -->\r\n <fb-name-picker\r\n [value]=\"rule.fieldName\"\r\n [options]=\"triggerFieldOptions()\"\r\n label=\"Campo\"\r\n placeholder=\"Nessuno: il messaggio e\u2019 della schermata\"\r\n unknownMessage=\"Questo nome non e\u2019 un campo di questa schermata.\"\r\n unknownSeverity=\"error\"\r\n emptyMessage=\"La schermata non ha campi a cui agganciare il messaggio.\"\r\n (valueChange)=\"setScreenRuleProperty($index, 'fieldName', $event ?? '')\"\r\n />\r\n <p class=\"fb-field__hint\">\r\n Facoltativo. Senza, il rilievo e\u2019 <strong>della schermata</strong> e il frontend lo\r\n mostra dove mostra i messaggi che non hanno un controllo \u2014 in testa al form, o\r\n accanto ad Avanti.\r\n </p>\r\n </div>\r\n </div>\r\n } @empty {\r\n <p class=\"fb-empty\">Nessuna regola: la schermata avanza se i campi sono a posto.</p>\r\n }\r\n </div>\r\n\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"addScreenRule()\">Aggiungi regola</button>\r\n <p class=\"fb-field__hint\">\r\n Scrivile con le <strong>condizioni</strong>, non con la formula: <code>IsEmpty</code> su una\r\n collection e\u2019 un operatore che il runtime valuta, mentre la stessa domanda in una formula\r\n chiede al motore una funzione che potrebbe non avere. Vale soprattutto per la verifica piu\u2019\r\n frequente \u2014 che una sezione ripetibile abbia almeno una riga.\r\n </p>\r\n </fieldset>\r\n } @else {\r\n <!-- ---------------------------------------------------- proprieta' del campo -->\r\n <header class=\"fb-scr__props-head\">\r\n <h3 class=\"fb-scr__props-title\">{{ captionOf(selectedField()!) }}</h3>\r\n <div class=\"fb-scr__props-actions\">\r\n <button type=\"button\" class=\"fb-btn fb-btn--sm\" title=\"Sposta su\" (click)=\"moveSelected(-1)\">\u2191</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--sm\" title=\"Sposta giu\u2019\" (click)=\"moveSelected(1)\">\u2193</button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--sm\"\r\n title=\"Porta fuori dal contenitore\"\r\n [disabled]=\"!canOutdent()\"\r\n (click)=\"outdentSelected()\"\r\n >\r\n \u21E4\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--sm\" title=\"Duplica\" (click)=\"duplicateSelected()\">\u29C9</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--danger fb-btn--sm\" title=\"Elimina\" (click)=\"removeSelected()\">\r\n \u00D7\r\n </button>\r\n </div>\r\n </header>\r\n\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]=\"selectedField()!.fieldType || ''\"\r\n (change)=\"setFieldType($any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 scegli \u2014</option>\r\n <!--\r\n \u00A75.2 \u2014 dentro una riga la sezione ripetibile non c'e': **non si annidano**, perche' il\r\n cursore e' una variabile sola per sezione. La validazione lo rifiuterebbe comunque, ma\r\n lasciar comporre e poi accusare fa perdere tempo due volte.\r\n -->\r\n @for (entry of fieldTypeOptions(); track entry.value) {\r\n <option [value]=\"entry.value\">{{ entry.label }}</option>\r\n }\r\n </select>\r\n @if (isInsideRepeater()) {\r\n <p class=\"fb-field__hint\">\r\n Questo campo sta dentro la riga di <code>{{ selectedRepeater()!.name }}</code>: esiste una\r\n volta per ogni elemento della collection, quindi\r\n <strong>non e\u2019 una risorsa del flow</strong> e non si nomina per nome. Dentro una riga si\r\n nomina il cursore \u2014\r\n <code>{{ rowCursor() || 'itemReference' }}.&lt;membro&gt;</code> \u2014 e scrivere\r\n <code>{{ selectedField()!.name }}</code> in una condizione, in una formula o in un parametro\r\n e\u2019 <code>SCREEN_FIELD_IN_REPEATER</code>.\r\n </p>\r\n }\r\n @if (!selectedField()!.fieldType) {\r\n <p class=\"fb-field__error\">Il tipo e\u2019 obbligatorio: senza, SCREEN_FIELD_TYPE_MISSING.</p>\r\n } @else if (isUnknownType(selectedField())) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n Questo tipo non e\u2019 nel dizionario: l\u2019editor non sa quali campi ammetta e mostra solo i comuni.\r\n </p>\r\n } @else if (selectedType()?.description) {\r\n <p class=\"fb-field__hint\">{{ selectedType()?.description }}</p>\r\n }\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Nome</label>\r\n <!-- Sul `change` e non sull\u2019`input`: la rinomina riscrive i riferimenti nel documento,\r\n e farlo a ogni tasto significherebbe riscriverlo per ogni lettera. -->\r\n <input\r\n class=\"fb-input fb-input--mono\"\r\n [value]=\"selectedField()!.name || ''\"\r\n (change)=\"setFieldName($any($event.target).value)\"\r\n />\r\n @if (selectedIsResource() && !isInsideRepeater()) {\r\n <p class=\"fb-field__hint\">\r\n Questo campo e\u2019 una <strong>risorsa</strong>: lo referenzi come\r\n <code>{{ selectedField()!.name || 'Nome' }}</code> in condizioni, formule e parametri. \u00C8 di\r\n sola lettura per il flow \u2014 un Assignment che ci scrive e\u2019 TARGET_NOT_WRITABLE.\r\n </p>\r\n } @else if (isInsideRepeater()) {\r\n <p class=\"fb-field__hint\">\r\n Il nome resta comunque <strong>unico</strong>: e\u2019 l\u2019indirizzo con cui il frontend rende il\r\n campo, e un omonimo di una variabile e\u2019 <code>NAME_DUPLICATED</code> come sempre.\r\n </p>\r\n }\r\n </div>\r\n\r\n <!--\r\n \u00A75.2 \u2014 `memberName` e il riferimento con cui gli altri campi lo nominano sono la **stessa\r\n domanda posta due volte**: `memberName: \"PodId\"` e `PuntoCorrente.PodId` sono lo stesso\r\n membro, e la classe da cui vengono e' l'`objectType` della variabile in `collectionReference`.\r\n -->\r\n @if (isInsideRepeater() && selectedType()?.storesValue) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Membro della riga</label>\r\n @if (rowClass()) {\r\n <fb-structure-member-picker\r\n [value]=\"selectedField()!.memberName\"\r\n [className]=\"rowClass()\"\r\n usage=\"writable\"\r\n label=\"Membro della riga\"\r\n (valueChange)=\"setFieldProperty('memberName', $event ?? '')\"\r\n />\r\n } @else {\r\n <input\r\n class=\"fb-input fb-input--mono\"\r\n [value]=\"selectedField()!.memberName || ''\"\r\n (change)=\"setFieldProperty('memberName', $any($event.target).value)\"\r\n />\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n La classe delle righe non e\u2019 nota: la sezione non dichiara una <code>collectionReference</code>\r\n valida, quindi i membri non si possono proporre.\r\n </p>\r\n }\r\n <p class=\"fb-field__hint\">\r\n Il membro della riga in cui finisce il valore. Senza, vale il nome del campo. Dentro una\r\n riga lo si legge come\r\n <code>{{ rowCursor() || 'itemReference' }}.{{ selectedField()!.memberName || selectedField()!.name }}</code>.\r\n </p>\r\n </div>\r\n }\r\n\r\n <!--\r\n Il ramo dei contenitori vale anche per una sezione ripetibile: l\u00EC `fieldText` e'\r\n l'intestazione del blocco, non l'etichetta di un valore \u2014 l'etichetta della **riga** e'\r\n `itemLabel`, ed e' un campo suo, piu' sotto (\u00A75.2).\r\n -->\r\n @if (!holdsFields(selectedField())) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">\r\n {{ selectedField()!.fieldType === 'DisplayText' ? 'Testo' : 'Etichetta' }}\r\n </label>\r\n <textarea\r\n class=\"fb-textarea\"\r\n [value]=\"selectedField()!.fieldText || ''\"\r\n (input)=\"setFieldProperty('fieldText', $any($event.target).value)\"\r\n ></textarea>\r\n <p class=\"fb-field__hint\">\r\n Supporta i merge field <code>&#123;!Riferimento&#125;</code>.\r\n @if (selectedField()!.fieldType === 'ComponentInstance') {\r\n <!--\r\n \u00A75.2 \u2014 su un componente l\u2019etichetta e\u2019 **ammessa** e viene consegnata al frontend\r\n in `label`, ma la sua assenza non e\u2019 un rilievo: un componente di solito disegna\r\n la propria intestazione. Nasconderla, come faceva prima l\u2019editor, toglieva un\r\n campo che il contratto prevede.\r\n -->\r\n Facoltativa: un componente di solito disegna la propria intestazione.\r\n }\r\n </p>\r\n </div>\r\n } @else {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Intestazione</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"selectedField()!.fieldText || ''\"\r\n (input)=\"setFieldProperty('fieldText', $any($event.target).value)\"\r\n />\r\n </div>\r\n }\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Testo di aiuto</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"selectedField()!.helpText || ''\"\r\n (input)=\"setFieldProperty('helpText', $any($event.target).value)\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Larghezza</label>\r\n <div class=\"fb-scr__width\">\r\n <input\r\n type=\"range\"\r\n min=\"1\"\r\n max=\"12\"\r\n step=\"1\"\r\n [value]=\"widthOf(selectedField()!)\"\r\n (input)=\"setNumberProperty('width', $any($event.target).value)\"\r\n />\r\n <span class=\"fb-scr__width-value\">{{ widthOf(selectedField()!) }}/12</span>\r\n </div>\r\n <p class=\"fb-field__hint\">\r\n Colonne della griglia della schermata. \u00C8 un\u2019indicazione: il frontend puo\u2019 ignorarla.\r\n </p>\r\n </div>\r\n\r\n @if (selectedField()!.fieldType === 'ComponentInstance') {\r\n <!--\r\n \u00A75.2 \u2014 perche\u2019 qui non c\u2019e\u2019 \u00ABobbligatorio\u00BB. La domanda arriva sempre, e la risposta non\r\n si indovina guardando l\u2019interfaccia: un componente non ha un valore proprio, ce l\u2019hanno\r\n i suoi output, che vanno in **variabili** scelte dall\u2019autore. \u00ABObbligatorio\u00BB dovrebbe\r\n dire *quale output* deve essere valorizzato, e il metadata non ha modo di dirlo \u2014\r\n dichiararlo qui sarebbe `SCREEN_FIELD_CONFIGURATION_INVALID` e il runtime lo\r\n ignorerebbe. Le due strade vere stanno nel contratto e sono queste.\r\n -->\r\n <p class=\"fb-field__hint\">\r\n Un componente non raccoglie un valore \u2014 lo fanno i suoi output \u2014 quindi obbligatorieta\u2019, valore\r\n predefinito e regola di validazione non si applicano: dichiararli e\u2019\r\n <code>SCREEN_FIELD_CONFIGURATION_INVALID</code> e il runtime li ignora. Per pretendere una\r\n compilazione: la validazione la fa il componente stesso, oppure si controlla la variabile di\r\n destinazione con una Decision dopo la schermata.\r\n </p>\r\n }\r\n\r\n <!-- ------------------------------------------------ campi che raccolgono un valore -->\r\n @if (selectedType()?.storesValue) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Valore</legend>\r\n\r\n @if (selectedField()!.fieldType !== 'ObjectProvided') {\r\n <div class=\"fb-field\">\r\n <label\r\n class=\"fb-field__label\"\r\n [class.fb-field__label--required]=\"!!selectedType()?.requiresDataType\"\r\n >\r\n Tipo di dato\r\n </label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"selectedField()!.dataType || ''\"\r\n (change)=\"setFieldProperty('dataType', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 scegli \u2014</option>\r\n @for (entry of dictionary.dataTypes(); track entry.value) {\r\n <option [value]=\"entry.value\">{{ entry.label }}</option>\r\n }\r\n </select>\r\n @if (selectedType()?.requiresDataType && !selectedField()!.dataType) {\r\n <p class=\"fb-field__error\">Obbligatorio per questo tipo di campo (DATA_TYPE_MISSING).</p>\r\n }\r\n @if (selectedType()?.isCollection) {\r\n <p class=\"fb-field__hint\">\r\n Il valore raccolto e\u2019 una <strong>collection</strong>, non una stringa con i valori\r\n separati: nelle condizioni si usano gli operatori di collection.\r\n </p>\r\n }\r\n </div>\r\n\r\n @if (requiresObjectType()) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\" [class.fb-field__label--required]=\"objectTypeIsStructure()\">\r\n {{\r\n objectTypeIsStructure()\r\n ? 'Classe'\r\n : selectedField()!.dataType === 'Enum'\r\n ? 'Tipo di enumerazione'\r\n : 'Oggetto'\r\n }}\r\n </label>\r\n @if (selectedField()!.dataType === 'Enum') {\r\n <!-- Dizionario chiuso: si sceglie, non si scrive. -->\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"selectedField()!.objectType || ''\"\r\n (change)=\"setFieldProperty('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 @if (!selectedField()!.objectType) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n Senza il tipo concreto l\u2019editor non puo\u2019 proporre i valori dell\u2019enumerazione.\r\n </p>\r\n }\r\n } @else if (objectTypeIsStructure()) {\r\n <fb-structure-picker\r\n [value]=\"selectedField()!.objectType\"\r\n label=\"Classe\"\r\n (valueChange)=\"setFieldProperty('objectType', $event ?? '')\"\r\n />\r\n @if (!selectedField()!.objectType) {\r\n <p class=\"fb-field__error\">\r\n La classe e\u2019 obbligatoria: senza, il runtime non ha nulla da istanziare\r\n (OBJECT_TYPE_MISSING).\r\n </p>\r\n }\r\n } @else {\r\n <fb-object-picker\r\n [value]=\"selectedField()!.objectType\"\r\n label=\"Oggetto\"\r\n (valueChange)=\"setFieldProperty('objectType', $event ?? '')\"\r\n />\r\n }\r\n </div>\r\n }\r\n } @else {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Campo dell\u2019entita\u2019</label>\r\n <p class=\"fb-field__hint\">\r\n Tipo, label, obbligatorieta\u2019, scala e \u2014 se il campo e\u2019 una picklist \u2014 le opzioni arrivano\r\n dallo schema dati: qui non si ridichiarano.\r\n </p>\r\n <fb-object-picker\r\n [value]=\"providedObject() || undefined\"\r\n label=\"Oggetto\"\r\n (valueChange)=\"setProvidedObject($event)\"\r\n />\r\n <fb-field-picker\r\n [value]=\"providedField() || undefined\"\r\n [object]=\"providedObject() || undefined\"\r\n label=\"Campo\"\r\n (valueChange)=\"setProvidedField($event)\"\r\n />\r\n </div>\r\n }\r\n\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"selectedIsRequired()\"\r\n (change)=\"setRequired($any($event.target).checked)\"\r\n />\r\n Obbligatorio\r\n </label>\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"selectedIsEditable()\"\r\n (change)=\"setEditable($any($event.target).checked)\"\r\n />\r\n Modificabile\r\n </label>\r\n @if (!selectedIsEditable()) {\r\n <p class=\"fb-field__hint\">\r\n A <code>false</code> il runtime <strong>ignora</strong> cio\u2019 che il client rimanda indietro:\r\n e\u2019 l\u2019unico modo di rendere un campo davvero di sola lettura.\r\n </p>\r\n }\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Valore di default</label>\r\n <fb-value-editor\r\n [value]=\"selectedField()!.defaultValue\"\r\n [dataType]=\"selectedField()!.dataType\"\r\n [objectType]=\"selectedField()!.objectType\"\r\n [isCollection]=\"selectedType()?.isCollection\"\r\n [preferredRoots]=\"preferredRoots()\"\r\n label=\"Valore di default\"\r\n (valueChange)=\"setDefaultValue($event)\"\r\n />\r\n </div>\r\n\r\n @if (scaleApplies()) {\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]=\"selectedField()!.scale ?? ''\"\r\n (change)=\"setNumberProperty('scale', $any($event.target).value)\"\r\n />\r\n </div>\r\n } @else if (selectedField()!.scale !== undefined) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n <code>scale</code> su un campo non numerico e\u2019 SCALE_NOT_APPLICABLE.\r\n </p>\r\n }\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Lunghezza massima</label>\r\n <input\r\n class=\"fb-input\"\r\n type=\"number\"\r\n min=\"1\"\r\n [value]=\"selectedField()!.maxLength ?? ''\"\r\n (change)=\"setNumberProperty('maxLength', $any($event.target).value)\"\r\n />\r\n <p class=\"fb-field__hint\">Verificata anche dal runtime (TOO_LONG).</p>\r\n </div>\r\n\r\n @if (formatApplies()) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Formato</label>\r\n <fb-name-picker\r\n [value]=\"selectedField()!.format\"\r\n [options]=\"formatOptions()\"\r\n label=\"Formato\"\r\n placeholder=\"Nessuno: un testo qualunque\"\r\n unknownSeverity=\"note\"\r\n unknownMessage=\"Non e\u2019 fra i formati proposti: se l\u2019host ha registrato il suo pattern va bene cosi\u2019, altrimenti il campo non verifica niente (SCREEN_FIELD_FORMAT_UNKNOWN).\"\r\n emptyMessage=\"Nessun formato dichiarato: puoi scriverne uno registrato dall\u2019host.\"\r\n (valueChange)=\"setFieldProperty('format', $event)\"\r\n />\r\n <p class=\"fb-field__hint\">\r\n <strong>Che cosa</strong> il campo raccoglie, oltre al fatto di essere un testo: il\r\n frontend ne ricava il tipo di input, e il runtime rifiuta l\u2019invio sbagliato\r\n (FORMAT_INVALID). L\u2019elenco non e\u2019 chiuso \u2014 i pattern li registra l\u2019host, non la\r\n libreria \u2014 quindi un nome fuori elenco non e\u2019 un difetto.\r\n </p>\r\n <p class=\"fb-field__hint\">\r\n Non e\u2019 un\u2019etichetta che il flow puo\u2019 leggere: se a valle serve sapere che un numero e\u2019\r\n un cellulare, quella distinzione sta nel dato (due campi, o due membri di una\r\n <code>Structure</code>). E non riscrive il valore \u2014 togliere spazi o aggiungere il\r\n prefisso e\u2019 una trasformazione, e la fa una screen action.\r\n </p>\r\n </div>\r\n } @else if (selectedField()!.format) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n <code>format</code> su un campo che non raccoglie un solo testo non ha effetto\r\n (SCREEN_FIELD_FORMAT_NOT_APPLICABLE).\r\n </p>\r\n }\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Tornando sulla schermata</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"selectedField()!.inputsOnNextNavToAssocScrn || ''\"\r\n (change)=\"setFieldProperty('inputsOnNextNavToAssocScrn', $any($event.target).value)\"\r\n >\r\n <option value=\"\">Predefinito (mantieni i valori)</option>\r\n @for (entry of dictionary.screenFieldInputsRevisited(); track entry.value) {\r\n <option [value]=\"entry.value\">{{ entry.label }}</option>\r\n }\r\n </select>\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Regola di validazione</label>\r\n <!--\r\n `commitOn=\"change\"`: qui mutare il documento ridisegna l'anteprima, e riscriverla a\r\n ogni tasto costa. La verifica parte lo stesso mentre si digita (\u00A76.3).\r\n -->\r\n <fb-formula-editor\r\n [expression]=\"selectedField()!.validationRule?.formulaExpression || ''\"\r\n usage=\"ValidationRule\"\r\n expectedDataType=\"Boolean\"\r\n commitOn=\"change\"\r\n [preferredRoots]=\"preferredRoots()\"\r\n [rows]=\"2\"\r\n placeholder=\"Espressione, es. LEN(Nome) > 3\"\r\n ariaLabel=\"Regola di validazione\"\r\n (expressionChange)=\"setValidationRule('formulaExpression', $event)\"\r\n />\r\n <input\r\n class=\"fb-input\"\r\n placeholder=\"Messaggio mostrato quando l\u2019espressione e\u2019 falsa\"\r\n [value]=\"selectedField()!.validationRule?.errorMessage || ''\"\r\n (change)=\"setValidationRule('errorMessage', $any($event.target).value)\"\r\n />\r\n <p class=\"fb-field__hint\">\r\n L\u2019espressione la valuta il motore di regole: il backend non la interpreta, la fa\r\n verificare al motore.\r\n </p>\r\n </div>\r\n </fieldset>\r\n }\r\n\r\n <!-- --------------------------------------------------------------- choice -->\r\n @if (selectedType()?.acceptsChoices) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Opzioni</legend>\r\n <p class=\"fb-section__note\">\r\n Accetta <strong>Choice</strong> e <strong>Dynamic choice set</strong>, nell\u2019ordine in cui le\r\n opzioni compaiono. Puntare a una variabile e\u2019 SCREEN_FIELD_CHOICE_UNKNOWN.\r\n </p>\r\n\r\n @if (!selectedField()!.choiceReferences?.length) {\r\n @if (selectedField()!.fieldType === 'ObjectProvided') {\r\n <!-- Le opzioni di una picklist arrivano dallo schema: qui si aggiungono solo se\r\n si vuole sostituirle, e non dichiararne nessuna e' il caso normale. -->\r\n <p class=\"fb-field__hint\">\r\n Se il campo dello schema e\u2019 una picklist, le opzioni arrivano da l\u00EC: dichiararle qui\r\n serve solo a sostituirle.\r\n </p>\r\n } @else {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Nessuna opzione: il campo non ha niente da mostrare (SCREEN_FIELD_CHOICES_MISSING).\r\n </p>\r\n }\r\n }\r\n\r\n <div class=\"fb-list\">\r\n @for (choice of selectedField()!.choiceReferences || []; 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__title\">{{ choice }}</span>\r\n <span class=\"fb-list__spacer\"></span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--sm\" (click)=\"moveChoice($index, -1)\">\u2191</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--sm\" (click)=\"moveChoice($index, 1)\">\u2193</button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--sm\"\r\n aria-label=\"Rimuovi\"\r\n (click)=\"removeChoice($index)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Aggiungi un\u2019opzione</label>\r\n <!--\r\n `commitOn=\"enter\"`: qui ogni emissione e\u2019 una riga in piu\u2019, non un campo che cambia\r\n valore \u2014 a tasto, scrivere \u00ABAlta\u00BB aggiungeva \u00ABA\u00BB, \u00ABAl\u00BB, \u00ABAlt\u00BB e \u00ABAlta\u00BB.\r\n -->\r\n <fb-name-picker\r\n [options]=\"choiceOptions()\"\r\n label=\"Choice\"\r\n commitOn=\"enter\"\r\n placeholder=\"Scegli una choice o un choice set\"\r\n unknownMessage=\"Questo nome non e\u2019 una choice ne\u2019 un choice set: e\u2019 SCREEN_FIELD_CHOICE_UNKNOWN.\"\r\n unknownSeverity=\"error\"\r\n emptyMessage=\"Il flow non dichiara nessuna choice: creale nel pannello delle risorse.\"\r\n (valueChange)=\"addChoice($event)\"\r\n />\r\n <p class=\"fb-field__hint\">\r\n Scegli dall\u2019elenco, oppure scrivi il nome e premi Invio.\r\n </p>\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Opzione selezionata all\u2019apertura</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"selectedField()!.defaultSelectedChoiceReference || ''\"\r\n (change)=\"setFieldProperty('defaultSelectedChoiceReference', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 nessuna \u2014</option>\r\n @for (choice of selectedField()!.choiceReferences || []; track $index) {\r\n <option [value]=\"choice\">{{ choice }}</option>\r\n }\r\n </select>\r\n @if (defaultChoiceIsForeign()) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n \u00AB{{ selectedField()!.defaultSelectedChoiceReference }}\u00BB non e\u2019 fra le opzioni elencate qui\r\n sopra.\r\n </p>\r\n }\r\n </div>\r\n\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"selectedAutoSelectsSingleChoice()\"\r\n (change)=\"setAutoSelectSingleChoice($any($event.target).checked)\"\r\n />\r\n Scegli da sola l\u2019unica opzione\r\n </label>\r\n <p class=\"fb-field__hint\">\r\n Quando le opzioni risolte sono <strong>esattamente una</strong>, il campo la prende da se\u2019.\r\n Serve alle tendine che dipendono da un\u2019altra tendina \u2014 i CAP di un comune \u2014 dove chiedere\r\n di scegliere l\u2019unica voce non decide niente.\r\n </p>\r\n </fieldset>\r\n }\r\n\r\n <!-- ------------------------------------------------------- contenitori -->\r\n @if (selectedType()?.isContainer) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Contenitore</legend>\r\n @if (selectedField()!.fieldType === 'RegionContainer') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Tipo di sezione</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"selectedField()!.regionContainerType || ''\"\r\n (change)=\"setFieldProperty('regionContainerType', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 predefinito \u2014</option>\r\n @for (entry of dictionary.regionContainerTypes(); track entry.value) {\r\n <option [value]=\"entry.value\">{{ entry.label }}</option>\r\n }\r\n </select>\r\n </div>\r\n }\r\n @if (!selectedField()!.fields?.length) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Il contenitore e\u2019 vuoto: non produce niente sulla schermata (SCREEN_CONTAINER_EMPTY).\r\n </p>\r\n }\r\n </fieldset>\r\n }\r\n\r\n <!-- ------------------------------------------------ sezione ripetibile (\u00A75.2) -->\r\n @if (selectedIsRepeatingSection()) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Righe</legend>\r\n <p class=\"fb-section__note\">\r\n I campi qui dentro sono il <strong>modello di una riga</strong>: la schermata ne rende una\r\n copia per ogni elemento della collection, e l\u2019utente decide quante con un tasto. I due\r\n riferimenti vanno pensati insieme \u2014 la <strong>collection</strong> e\u2019 dove le righe vivono,\r\n il <strong>cursore</strong> e\u2019 la riga corrente, come\r\n l\u2019<code>assignNextValueToReference</code> di un Loop.\r\n </p>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Collection delle righe</label>\r\n <fb-reference-picker\r\n [value]=\"selectedField()!.collectionReference\"\r\n dataType=\"Structure\"\r\n [isCollection]=\"true\"\r\n [writableOnly]=\"true\"\r\n label=\"Collection delle righe\"\r\n placeholder=\"Una variabile collection di tipo Structure\"\r\n (valueChange)=\"setFieldProperty('collectionReference', $event ?? '')\"\r\n />\r\n <p class=\"fb-field__hint\">\r\n \u00C8 quella che il resto del flow attraversa con un Loop: la sezione non introduce un posto\r\n nuovo in cui i dati stanno. Se la riga e\u2019 il DTO che poi finisce in una action, conviene\r\n che sia una collection <strong>di quel DTO</strong>.\r\n </p>\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Cursore della riga</label>\r\n <fb-reference-picker\r\n [value]=\"selectedField()!.itemReference\"\r\n dataType=\"Structure\"\r\n [isCollection]=\"false\"\r\n [objectType]=\"rowClass()\"\r\n [writableOnly]=\"true\"\r\n label=\"Cursore della riga\"\r\n placeholder=\"Una variabile Structure della stessa classe\"\r\n (valueChange)=\"setFieldProperty('itemReference', $event ?? '')\"\r\n />\r\n <p class=\"fb-field__hint\">\r\n \u00C8 il nome che si scrive <strong>dentro</strong> la riga: visibilita\u2019, regole di\r\n validazione, filtri delle tendine, parametri dei componenti e\r\n <code>itemLabel</code> nominano\r\n <code>{{ selectedField()!.itemReference || 'Cursore' }}.&lt;membro&gt;</code>, mai il nome\r\n di un campo della riga.\r\n </p>\r\n </div>\r\n\r\n @if (repeaterIssue(); as issue) {\r\n <p class=\"fb-callout fb-callout--error\">{{ issue }} (REPEATING_SECTION_INVALID)</p>\r\n }\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Righe minime</label>\r\n <input\r\n class=\"fb-input\"\r\n type=\"number\"\r\n min=\"0\"\r\n [value]=\"selectedField()!.minInstances ?? ''\"\r\n (change)=\"setNumberProperty('minInstances', $any($event.target).value)\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Righe massime</label>\r\n <input\r\n class=\"fb-input\"\r\n type=\"number\"\r\n min=\"1\"\r\n [value]=\"selectedField()!.maxInstances ?? ''\"\r\n (change)=\"setNumberProperty('maxInstances', $any($event.target).value)\"\r\n />\r\n @if (repeaterHasNoLimit()) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n Senza un massimo l\u2019utente puo\u2019 aggiungere righe all\u2019infinito\r\n (REPEATING_SECTION_WITHOUT_LIMIT).\r\n </p>\r\n }\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Righe gia\u2019 aperte</label>\r\n <input\r\n class=\"fb-input\"\r\n type=\"number\"\r\n min=\"0\"\r\n [value]=\"selectedField()!.initialInstances ?? ''\"\r\n (change)=\"setNumberProperty('initialInstances', $any($event.target).value)\"\r\n />\r\n <p class=\"fb-field__hint\">\r\n Quante righe trovare <strong>gia\u2019 aperte</strong> arrivando sulla schermata, ed e\u2019 un\u2019altra\r\n cosa dal minimo: il minimo e\u2019 un vincolo, queste sono una comodita\u2019 e si possono togliere\r\n tutte. Un punto luce e un punto gas gia\u2019 aperti, dove chi ha una sola commodity toglie\r\n quello che non gli serve, sono <code>1</code> qui con <code>0</code> come minimo; col\r\n minimo a 1 sarebbe un modulo che <strong>obbliga</strong> a entrambe.\r\n </p>\r\n <p class=\"fb-field__hint\">\r\n Si applicano <strong>una volta sola</strong>, e solo se nessuno ha ancora scritto la\r\n collection: righe arrivate da un ciclo o da una schermata precedente <em>sono</em> le righe\r\n iniziali e non vengono diluite con righe vuote, e chi le toglie tutte non se le ritrova.\r\n </p>\r\n @if (repeaterInitialOutOfRange(); as issue) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">{{ issue }}</p>\r\n }\r\n </div>\r\n\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"selectedField()!.allowAdd !== false\"\r\n (change)=\"setRepeaterFlag('allowAdd', $any($event.target).checked)\"\r\n />\r\n L\u2019utente puo\u2019 aggiungere righe\r\n </label>\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"selectedField()!.allowRemove !== false\"\r\n (change)=\"setRepeaterFlag('allowRemove', $any($event.target).checked)\"\r\n />\r\n L\u2019utente puo\u2019 togliere righe\r\n </label>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Scritta del comando \u00ABaggiungi\u00BB</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"selectedField()!.addButtonLabel || ''\"\r\n (input)=\"setFieldProperty('addButtonLabel', $any($event.target).value)\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Scritta del comando \u00ABtogli\u00BB</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"selectedField()!.removeButtonLabel || ''\"\r\n (input)=\"setFieldProperty('removeButtonLabel', $any($event.target).value)\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Intestazione della singola riga</label>\r\n <!-- `commitOn=\"change\"`: mutare il documento ridisegna l'anteprima. -->\r\n <fb-merge-text-editor\r\n [text]=\"selectedField()!.itemLabel || ''\"\r\n [rows]=\"2\"\r\n commitOn=\"change\"\r\n placeholder=\"Punto {{ '{' }}!Cursore.PodId}\"\r\n ariaLabel=\"Intestazione della riga\"\r\n (textChange)=\"setFieldProperty('itemLabel', $event)\"\r\n >\r\n <p class=\"fb-field__hint\">\r\n Risolta <strong>sulla riga</strong>: i merge field qui nominano il cursore\r\n (<code>&#123;!{{ selectedField()!.itemReference || 'Cursore' }}.Membro&#125;</code>), non\r\n un campo della riga.\r\n </p>\r\n </fb-merge-text-editor>\r\n </div>\r\n\r\n @if (!selectedField()!.fields?.length) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Il modello della riga e\u2019 vuoto: la sezione non mostra niente (SCREEN_CONTAINER_EMPTY).\r\n </p>\r\n }\r\n </fieldset>\r\n }\r\n\r\n <!-- --------------------------------------------------- ComponentInstance -->\r\n @if (selectedField()!.fieldType === 'ComponentInstance') {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Componente</legend>\r\n <p class=\"fb-section__note\">\r\n Componenti e form sono la stessa domanda al frontend \u2014 cosa sa rendere, e con quali parametri\r\n \u2014 e passano dallo stesso catalogo. Un <code>ComponentInstance</code> non ha un valore proprio:\r\n lo hanno i suoi output.\r\n </p>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Componente</label>\r\n <fb-name-picker\r\n [value]=\"selectedField()!.extensionName\"\r\n [options]=\"componentOptions()\"\r\n [unusableOptions]=\"unusableComponents()\"\r\n label=\"Componente\"\r\n placeholder=\"Scrivi o scegli un componente\"\r\n [isMono]=\"false\"\r\n unknownMessage=\"Questo componente non esiste nel catalogo: e\u2019 SCREEN_COMPONENT_UNKNOWN.\"\r\n unknownSeverity=\"error\"\r\n unusableMessage=\"Questo nome e\u2019 una schermata intera, non un componente montabile qui (FORM_KIND_MISMATCH).\"\r\n emptyMessage=\"Il catalogo dei componenti non e\u2019 popolato: il nome non viene verificato.\"\r\n (valueChange)=\"setFieldProperty('extensionName', $event ?? '')\"\r\n />\r\n @if (!selectedField()!.extensionName) {\r\n <p class=\"fb-field__error\">Obbligatorio: senza, SCREEN_COMPONENT_MISSING.</p>\r\n }\r\n </div>\r\n\r\n <!--\r\n \u00A75.2 \u2014 dentro una riga la spunta **non c'e'**: gli output automatici si espongono come\r\n `<NomeCampo>.<NomeOutput>`, che sarebbe **una risorsa sola per tutte le righe** \u2014 l'ultima\r\n che ha girato. \u00C8 `REPEATING_SECTION_INVALID`, e la strada giusta e' `assignToReference` su\r\n un membro del cursore, che una riga ce l'ha per davvero.\r\n -->\r\n @if (!isInsideRepeater()) {\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"selectedField()!.storeOutputAutomatically === true\"\r\n (change)=\"setStoreOutputAutomatically($any($event.target).checked)\"\r\n />\r\n Rendi gli output referenziabili automaticamente\r\n </label>\r\n @if (selectedField()!.storeOutputAutomatically) {\r\n <p class=\"fb-field__hint\">\r\n Gli output si referenziano come\r\n <code>{{ selectedField()!.name || 'Campo' }}.nomeOutput</code>, senza dichiarare\r\n variabili.\r\n </p>\r\n }\r\n @if (hasOutputConflict()) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n Output automatici <strong>e</strong> parametri di uscita insieme:\r\n OUTPUT_CONFIGURATION_CONFLICT.\r\n </p>\r\n }\r\n } @else {\r\n <p class=\"fb-field__hint\">\r\n Gli output automatici qui non si possono usare: si esporrebbero come\r\n <code>{{ selectedField()!.name || 'Campo' }}.nomeOutput</code>, cioe\u2019 una risorsa sola per\r\n tutte le righe \u2014 l\u2019ultima che ha girato (REPEATING_SECTION_INVALID). Assegnali con\r\n <strong>assignToReference</strong> a un membro di\r\n <code>{{ rowCursor() || 'Cursore' }}</code>.\r\n </p>\r\n @if (selectedField()!.storeOutputAutomatically) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n Questo componente dichiara gli output automatici e sta dentro una riga:\r\n REPEATING_SECTION_INVALID.\r\n <button type=\"button\" class=\"fb-btn fb-btn--sm\" (click)=\"setStoreOutputAutomatically(false)\">\r\n Togli\r\n </button>\r\n </p>\r\n }\r\n }\r\n\r\n <fb-parameter-editor\r\n [holder]=\"$any(selectedField())\"\r\n [catalogParameters]=\"componentParameterList()\"\r\n [preferredRoots]=\"preferredRoots()\"\r\n inputTitle=\"Valori passati al componente\"\r\n outputTitle=\"Valori raccolti dal componente\"\r\n [showOutputs]=\"!selectedField()!.storeOutputAutomatically\"\r\n outputsDisabledReason=\"Gli output sono automatici: disattivalo per assegnarli a variabili.\"\r\n (changed)=\"onComponentParametersChanged($event)\"\r\n />\r\n </fieldset>\r\n }\r\n\r\n <!-- ------------------------------------------------------- visibilita' -->\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Visibilita\u2019</legend>\r\n <p class=\"fb-section__note\">\r\n Le regole si rivalutano <strong>sui valori appena inviati</strong>: e\u2019 cos\u00EC che un campo compare\r\n in funzione di un altro campo della stessa schermata. Un campo risultato nascosto viene\r\n <strong>azzerato</strong> e non viene validato.\r\n </p>\r\n <fb-condition-editor\r\n [holder]=\"visibilityRule()\"\r\n title=\"Mostra il campo quando\"\r\n [allowFormula]=\"false\"\r\n [preferredRoots]=\"preferredRoots()\"\r\n [issuePath]=\"'fields[' + (selectedField()!.name || '') + '].visibilityRule'\"\r\n (changed)=\"onVisibilityChanged($event)\"\r\n />\r\n </fieldset>\r\n }\r\n </aside>\r\n</div>\r\n\r\n<fb-connector-editor\r\n [nodeName]=\"name()\"\r\n [node]=\"node()\"\r\n [outlets]=\"outlets()\"\r\n (connectorChanged)=\"onConnectorChanged($event)\"\r\n/>\r\n\r\n<!--\r\n ============================================================== l'anteprima\r\n Due template ricorsivi. `listTpl` disegna i figli di un contenitore, e nient'altro: `data-item`\r\n marca cio' che conta come fratello nel calcolo del punto di rilascio, e il segnaposto del\r\n contenitore vuoto ne resta fuori. La barra di inserimento non e' qui \u2014 sta sopra l'anteprima,\r\n vedi il velo nel frame.\r\n-->\r\n<ng-template #listTpl let-fields let-parent=\"parent\" let-axis=\"axis\">\r\n @for (child of fields; track $index) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"fieldTpl\"\r\n [ngTemplateOutletContext]=\"{ $implicit: child, path: childPath(parent, $index), axis: axis }\"\r\n />\r\n }\r\n @if (!fields.length) {\r\n <p class=\"fb-scr__drop-hint\">Trascina qui un componente</p>\r\n }\r\n</ng-template>\r\n\r\n<ng-template #fieldTpl let-field let-path=\"path\" let-axis=\"axis\">\r\n <div\r\n class=\"fb-scr__item\"\r\n data-item=\"\"\r\n cdkDrag\r\n [style.grid-column]=\"'span ' + widthOf(field)\"\r\n [class.fb-scr__item--selected]=\"isSelected(path)\"\r\n [class.fb-scr__item--dragging]=\"isDragging(path)\"\r\n [class.fb-scr__item--container]=\"holdsFields(field)\"\r\n (cdkDragStarted)=\"onFieldDragStarted(path)\"\r\n (cdkDragMoved)=\"onFieldDragMoved($event)\"\r\n (cdkDragEnded)=\"onFieldDragEnded($event, path)\"\r\n (click)=\"select(path); $event.stopPropagation()\"\r\n >\r\n <span class=\"fb-scr__grip\" cdkDragHandle title=\"Trascina per spostare\" aria-hidden=\"true\">\u283F</span>\r\n @if (field.visibilityRule?.conditions?.length) {\r\n <span class=\"fb-scr__flag\" title=\"Ha una regola di visibilita\u2019\">\u25D0</span>\r\n }\r\n\r\n @switch (field.fieldType) {\r\n @case ('RegionContainer') {\r\n <div class=\"fb-scr__section\">\r\n @if (field.regionContainerType !== 'SectionWithoutHeader') {\r\n <header class=\"fb-scr__section-head\">{{ field.fieldText || field.name }}</header>\r\n }\r\n <div class=\"fb-scr__cols\" [attr.data-drop]=\"dropId(path)\" data-axis=\"row\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"listTpl\"\r\n [ngTemplateOutletContext]=\"{ $implicit: field.fields || [], parent: path, axis: 'row' }\"\r\n />\r\n </div>\r\n </div>\r\n }\r\n @case ('Region') {\r\n <div class=\"fb-scr__region\">\r\n <span class=\"fb-scr__region-tag\">{{ field.name }} \u00B7 {{ widthOf(field) }}/12</span>\r\n <div class=\"fb-scr__region-body\" [attr.data-drop]=\"dropId(path)\" data-axis=\"column\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"listTpl\"\r\n [ngTemplateOutletContext]=\"{ $implicit: field.fields || [], parent: path, axis: 'column' }\"\r\n />\r\n </div>\r\n </div>\r\n }\r\n @case ('RepeatingSection') {\r\n <!--\r\n \u00A75.2 \u2014 si disegna **una** riga, ed e' il modello: a runtime la schermata ne rende una copia\r\n per elemento della collection. Disegnarne due sarebbe finto \u2014 quante siano lo decide chi\r\n compila \u2014 e disegnarla senza cornice farebbe credere che i campi siano della schermata.\r\n -->\r\n <div class=\"fb-scr__repeat\">\r\n <header class=\"fb-scr__repeat-head\">\r\n <span>{{ field.fieldText || field.name }}</span>\r\n <span class=\"fb-scr__repeat-meta\">\r\n {{ field.collectionReference || 'collection non indicata' }}\r\n @if (field.minInstances !== undefined || field.maxInstances !== undefined) {\r\n \u00B7 {{ field.minInstances ?? 0 }}\u2013{{ field.maxInstances ?? '\u221E' }}\r\n }\r\n </span>\r\n </header>\r\n <div class=\"fb-scr__repeat-row\">\r\n <span class=\"fb-scr__repeat-tag\">\r\n {{ field.itemLabel || 'Riga 1' }}\r\n @if (field.allowRemove !== false) {\r\n <span class=\"fb-scr__repeat-x\" aria-hidden=\"true\">\u00D7</span>\r\n }\r\n </span>\r\n <div class=\"fb-scr__repeat-body\" [attr.data-drop]=\"dropId(path)\" data-axis=\"column\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"listTpl\"\r\n [ngTemplateOutletContext]=\"{ $implicit: field.fields || [], parent: path, axis: 'column' }\"\r\n />\r\n </div>\r\n </div>\r\n @if (field.allowAdd !== false) {\r\n <span class=\"fb-scr__repeat-add\">+ {{ field.addButtonLabel || 'Aggiungi' }}</span>\r\n }\r\n </div>\r\n }\r\n @case ('DisplayText') {\r\n <p class=\"fb-scr__display\">{{ field.fieldText || '(testo vuoto)' }}</p>\r\n }\r\n @case ('LargeTextArea') {\r\n <label class=\"fb-scr__label\">\r\n {{ captionOf(field) }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__control fb-scr__control--area\">{{ placeholderOf(field) }}</div>\r\n }\r\n @case ('PasswordField') {\r\n <label class=\"fb-scr__label\">\r\n {{ captionOf(field) }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__control\">\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022</div>\r\n }\r\n @case ('DropdownBox') {\r\n <label class=\"fb-scr__label\">\r\n {{ captionOf(field) }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__control fb-scr__control--select\">\r\n <span>{{ field.defaultSelectedChoiceReference || choiceLabelsOf(field)[0] || '\u2014 scegli \u2014' }}</span>\r\n <span aria-hidden=\"true\">\u25BE</span>\r\n </div>\r\n }\r\n @case ('MultiSelectPicklist') {\r\n <label class=\"fb-scr__label\">\r\n {{ captionOf(field) }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__control fb-scr__control--select\">\r\n <span>{{ choiceLabelsOf(field).join(', ') || '\u2014 scegli \u2014' }}</span>\r\n <span aria-hidden=\"true\">\u2261</span>\r\n </div>\r\n }\r\n @case ('RadioButtons') {\r\n <label class=\"fb-scr__label\">\r\n {{ captionOf(field) }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__options\">\r\n @for (option of choiceLabelsOf(field); track $index) {\r\n <span class=\"fb-scr__option\">\u25EF {{ option }}</span>\r\n }\r\n @if (!choiceLabelsOf(field).length) {\r\n <span class=\"fb-scr__option fb-scr__option--missing\">Nessuna opzione</span>\r\n }\r\n </div>\r\n }\r\n @case ('MultiSelectCheckboxes') {\r\n <label class=\"fb-scr__label\">\r\n {{ captionOf(field) }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__options\">\r\n @for (option of choiceLabelsOf(field); track $index) {\r\n <span class=\"fb-scr__option\">\u2610 {{ option }}</span>\r\n }\r\n @if (!choiceLabelsOf(field).length) {\r\n <span class=\"fb-scr__option fb-scr__option--missing\">Nessuna opzione</span>\r\n }\r\n </div>\r\n }\r\n @case ('ComponentInstance') {\r\n <!--\r\n L\u2019etichetta si disegna se c\u2019e\u2019: il runtime la consegna in `label` come per gli altri\r\n campi (\u00A75.2). Nessun asterisco: un componente non raccoglie un valore, quindi\r\n \u00ABobbligatorio\u00BB non ha niente su cui applicarsi.\r\n -->\r\n @if (field.fieldText) {\r\n <label class=\"fb-scr__label\">{{ field.fieldText }}</label>\r\n }\r\n <div class=\"fb-scr__component\">\r\n <span aria-hidden=\"true\">\u2B21</span>\r\n {{ field.extensionName || 'Componente non indicato' }}\r\n </div>\r\n }\r\n @case ('ObjectProvided') {\r\n <label class=\"fb-scr__label\">\r\n {{ field.fieldText || field.objectFieldReference || field.name }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__control\">{{ placeholderOf(field) }}</div>\r\n <span class=\"fb-scr__tag\">{{ field.objectFieldReference || 'campo non indicato' }}</span>\r\n }\r\n @default {\r\n <label class=\"fb-scr__label\">\r\n {{ captionOf(field) }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__control\">{{ placeholderOf(field) }}</div>\r\n @if (isUnknownType(field)) {\r\n <span class=\"fb-scr__tag fb-scr__tag--warn\">{{ field.fieldType }}: tipo non nel dizionario</span>\r\n }\r\n }\r\n }\r\n <!--\r\n Il formato sta **fuori** dal @switch perche' non e' del tipo di campo: e' di cio' che il campo\r\n raccoglie, e va visto anche dove non si applica \u2014 e\u2019 l\u00EC che si riconosce un\r\n SCREEN_FIELD_FORMAT_NOT_APPLICABLE senza aprire le proprieta\u2019.\r\n -->\r\n @if (field.format) {\r\n <span class=\"fb-scr__tag\">{{ field.format }}</span>\r\n }\r\n </div>\r\n</ng-template>\r\n", styles: [".fb-scr{display:grid;grid-template-columns:220px minmax(0,1fr) 340px;gap:12px;align-items:start;margin-bottom:14px}@media(max-width:1100px){.fb-scr{grid-template-columns:minmax(0,1fr)}}.fb-scr__side,.fb-scr__props{min-width:0;padding:10px;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius, 10px);background:var(--fb-surface-alt, #f7f8fa)}.fb-scr__tabs{display:flex;gap:4px;margin-bottom:8px}.fb-scr__tab{flex:1;padding:5px 8px;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-xs, 6px);background:var(--fb-surface, #fff);color:var(--fb-text-muted, #6b7086);font:inherit;font-size:11px;font-weight:600;cursor:pointer}.fb-scr__tab--active{border-color:var(--fb-accent, #4f6ef7);background:color-mix(in srgb,var(--fb-accent, #4f6ef7) 10%,transparent);color:var(--fb-accent-strong, #3d59e0)}.fb-scr__side-hint,.fb-scr__side-empty{margin:6px 0;font-size:11px;color:var(--fb-text-muted, #6b7086)}.fb-scr__palette{display:flex;flex-direction:column;gap:4px;margin:8px 0 0;padding:0;max-height:320px;overflow-y:auto;overscroll-behavior:contain;list-style:none}.fb-scr__chip{display:flex;align-items:center;gap:8px;width:100%;padding:6px 8px;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-xs, 6px);background:var(--fb-surface, #fff);color:var(--fb-text, #1a1c23);font:inherit;font-size:12px;text-align:left;cursor:grab}.fb-scr__chip:hover{border-color:var(--fb-accent, #4f6ef7)}.fb-scr__chip-icon{flex:none;width:20px;text-align:center;color:var(--fb-text-muted, #6b7086)}.fb-scr__chip-label{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-scr__chip-label small{margin-left:4px;color:var(--fb-text-subtle, #98a2b3);font-size:10px}.fb-scr__canvas{min-width:0;padding:14px;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius, 10px);background:var(--fb-canvas-bg, #f4f5f7);max-height:60vh;overflow-y:auto}.fb-scr__frame{position:relative;display:flex;flex-direction:column;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-sm, 8px);background:var(--fb-surface, #fff);box-shadow:var(--fb-shadow-sm, 0 1px 2px rgb(16 24 40 / 6%));overflow:hidden}.fb-scr__frame-head{display:flex;align-items:center;gap:6px;padding:8px 12px;border-bottom:1px solid var(--fb-border-subtle, #eef0f4);font-size:13px;font-weight:600}.fb-scr__help{display:inline-flex;align-items:center;justify-content:center;width:15px;height:15px;border-radius:50%;background:var(--fb-surface-sunken, #eef0f4);color:var(--fb-text-muted, #6b7086);font-size:10px}.fb-scr__body,.fb-scr__region-body{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));align-content:start;gap:8px 0;padding:12px;min-height:90px}.fb-scr__region-body{padding:8px;min-height:60px}.fb-scr__cols{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));align-items:stretch;gap:0;padding:8px;min-height:60px}.fb-scr__cols>.fb-scr__item{display:flex;align-self:stretch}.fb-scr__cols>.fb-scr__item>.fb-scr__region{flex:1;min-width:0}.fb-scr__drop-hint{grid-column:1 / -1;margin:0;padding:10px;border:1px dashed var(--fb-border-strong, #cfd4de);border-radius:var(--fb-radius-xs, 6px);color:var(--fb-text-subtle, #98a2b3);font-size:11px;text-align:center}.fb-scr__marker{position:absolute;z-index:2;border-radius:2px;background:var(--fb-accent, #4f6ef7);pointer-events:none}.fb-scr__item{position:relative;box-sizing:border-box;padding:6px 8px;border:1px solid transparent;border-radius:var(--fb-radius-xs, 6px);cursor:pointer}.fb-scr__item:hover{border-color:var(--fb-border-strong, #cfd4de);background:color-mix(in srgb,var(--fb-accent, #4f6ef7) 4%,transparent)}.fb-scr__item--selected{border-color:var(--fb-accent, #4f6ef7);box-shadow:inset 0 0 0 1px var(--fb-accent, #4f6ef7)}.fb-scr__item--dragging{opacity:.45;pointer-events:none}.fb-scr__grip{position:absolute;top:2px;left:-2px;padding:0 3px;color:var(--fb-text-subtle, #98a2b3);font-size:11px;line-height:1;opacity:0;cursor:grab}.fb-scr__item:hover>.fb-scr__grip,.fb-scr__item--selected>.fb-scr__grip{opacity:1}.fb-scr__flag{position:absolute;top:2px;right:4px;color:var(--fb-text-muted, #6b7086);font-size:11px}.fb-scr__label{display:block;margin-bottom:3px;font-size:11px;font-weight:600;color:var(--fb-text, #1a1c23)}.fb-scr__req{color:var(--fb-error, #dc2626)}.fb-scr__control{display:flex;align-items:center;justify-content:space-between;gap:6px;min-height:26px;padding:4px 8px;border:1px solid var(--fb-border-strong, #cfd4de);border-radius:var(--fb-radius-xs, 6px);background:var(--fb-surface, #fff);color:var(--fb-text-subtle, #98a2b3);font-size:12px;pointer-events:none}.fb-scr__control--area{min-height:54px;align-items:flex-start}.fb-scr__display{margin:0;font-size:12px;color:var(--fb-text, #1a1c23)}.fb-scr__options{display:flex;flex-direction:column;gap:2px;pointer-events:none}.fb-scr__option{font-size:12px;color:var(--fb-text-muted, #6b7086)}.fb-scr__option--missing{color:var(--fb-warning, #b7791f)}.fb-scr__component{display:flex;align-items:center;gap:6px;padding:10px;border:1px dashed var(--fb-accent, #4f6ef7);border-radius:var(--fb-radius-xs, 6px);background:color-mix(in srgb,var(--fb-accent, #4f6ef7) 5%,transparent);font-size:12px;color:var(--fb-text-muted, #6b7086)}.fb-scr__tag{display:inline-block;margin-top:3px;font-size:10px;color:var(--fb-text-subtle, #98a2b3)}.fb-scr__tag--warn{color:var(--fb-warning, #b7791f)}.fb-scr__section{border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-xs, 6px);background:var(--fb-surface-alt, #f7f8fa)}.fb-scr__section-head{padding:5px 10px;border-bottom:1px solid var(--fb-border-subtle, #eef0f4);font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--fb-text-muted, #6b7086)}.fb-scr__region{border:1px dashed var(--fb-border-strong, #cfd4de);border-radius:var(--fb-radius-xs, 6px);background:var(--fb-surface, #fff)}.fb-scr__region-tag{display:block;padding:3px 6px 0;font-size:10px;color:var(--fb-text-subtle, #98a2b3)}.fb-scr__repeat{border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-xs, 6px);background:var(--fb-surface-alt, #f7f8fa)}.fb-scr__repeat-head{display:flex;align-items:baseline;gap:8px;padding:5px 10px;border-bottom:1px solid var(--fb-border-subtle, #eef0f4);font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--fb-text-muted, #6b7086)}.fb-scr__repeat-meta{margin-left:auto;font-size:10px;font-weight:400;text-transform:none;letter-spacing:0;color:var(--fb-text-subtle, #98a2b3)}.fb-scr__repeat-row{margin:8px;border:1px dashed var(--fb-border-strong, #cfd4de);border-radius:var(--fb-radius-xs, 6px);background:var(--fb-surface, #fff)}.fb-scr__repeat-tag{display:flex;align-items:center;gap:6px;padding:3px 6px 0;font-size:10px;color:var(--fb-text-subtle, #98a2b3)}.fb-scr__repeat-x{margin-left:auto}.fb-scr__repeat-body{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));align-content:start;gap:8px 0;padding:8px;min-height:60px}.fb-scr__repeat-add{display:inline-block;margin:0 8px 8px;padding:3px 10px;border:1px dashed var(--fb-border-strong, #cfd4de);border-radius:var(--fb-radius-xs, 6px);font-size:11px;color:var(--fb-text-muted, #6b7086)}.fb-scr__frame-foot{display:flex;align-items:center;gap:6px;padding:8px 12px;border-top:1px solid var(--fb-border-subtle, #eef0f4);background:var(--fb-surface-alt, #f7f8fa)}.fb-scr__spacer{flex:1}.fb-scr__btn{padding:3px 10px;border:1px solid var(--fb-border-strong, #cfd4de);border-radius:var(--fb-radius-xs, 6px);font-size:11px;color:var(--fb-text-muted, #6b7086)}.fb-scr__btn--primary{border-color:var(--fb-accent, #4f6ef7);background:var(--fb-accent, #4f6ef7);color:var(--fb-accent-contrast, #fff)}.fb-scr__canvas-note{margin-top:10px}.fb-scr__props{background:var(--fb-surface, #fff)}.fb-scr__props-head{display:flex;align-items:center;gap:8px;margin-bottom:8px}.fb-scr__props-title{flex:1;min-width:0;margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--fb-text-muted, #6b7086)}.fb-scr__props-actions{display:flex;flex-wrap:wrap;gap:3px}.fb-btn--sm{padding:3px 7px;font-size:11px}.fb-scr__width{display:flex;align-items:center;gap:8px}.fb-scr__width input[type=range]{flex:1;min-width:0}.fb-scr__width-value{font-size:11px;color:var(--fb-text-muted, #6b7086)}\n"] }]
13883
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"fb-scr\" #composer>\r\n <!-- ============================================================ sinistra -->\r\n <aside class=\"fb-scr__side\" aria-label=\"Componenti e campi\">\r\n <div class=\"fb-scr__tabs\" role=\"tablist\">\r\n <button\r\n type=\"button\"\r\n role=\"tab\"\r\n class=\"fb-scr__tab\"\r\n [class.fb-scr__tab--active]=\"paletteTab() === 'components'\"\r\n [attr.aria-selected]=\"paletteTab() === 'components'\"\r\n (click)=\"setPaletteTab('components')\"\r\n >\r\n Componenti\r\n </button>\r\n <button\r\n type=\"button\"\r\n role=\"tab\"\r\n class=\"fb-scr__tab\"\r\n [class.fb-scr__tab--active]=\"paletteTab() === 'fields'\"\r\n [attr.aria-selected]=\"paletteTab() === 'fields'\"\r\n (click)=\"setPaletteTab('fields')\"\r\n >\r\n Campi\r\n </button>\r\n </div>\r\n\r\n @if (paletteTab() === 'components') {\r\n @if (!dictionary.screenFieldTypes().length) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Il dizionario <code>screenFieldTypes</code> non e\u2019 disponibile: senza i suoi flag l\u2019editor non\r\n sa quali campi ammette ciascun tipo, e non li inventa.\r\n </p>\r\n }\r\n <p class=\"fb-scr__side-hint\">Trascina sulla schermata, oppure clicca per aggiungere.</p>\r\n <ul class=\"fb-scr__palette\">\r\n @for (entry of dictionary.screenFieldTypes(); track entry.value) {\r\n <li>\r\n <!--\r\n \u00A75.2 \u2014 l'unico chip che si spegne: una sezione ripetibile dentro un'altra. Non si\r\n annidano, e il cursore e' una variabile sola per sezione.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-scr__chip\"\r\n cdkDrag\r\n [disabled]=\"!canAddAtSelection(entry.value)\"\r\n [title]=\"\r\n canAddAtSelection(entry.value)\r\n ? entry.description || entry.label\r\n : 'Le sezioni ripetibili non si annidano: seleziona un punto fuori dalla riga.'\r\n \"\r\n (cdkDragMoved)=\"onPaletteDragMoved($event)\"\r\n (cdkDragEnded)=\"onComponentDragEnded($event, entry.value)\"\r\n (click)=\"addComponentAtSelection(entry.value)\"\r\n >\r\n <span class=\"fb-scr__chip-icon\" aria-hidden=\"true\">{{ iconOf(entry.value) }}</span>\r\n <span class=\"fb-scr__chip-label\">{{ entry.label }}</span>\r\n </button>\r\n </li>\r\n }\r\n </ul>\r\n } @else {\r\n <p class=\"fb-scr__side-hint\">\r\n Un campo dell\u2019entita\u2019 porta con se\u2019 tipo, etichetta, obbligatorieta\u2019 e opzioni: arrivano dallo\r\n schema dati, qui non si ridichiarano.\r\n </p>\r\n <!-- Il catalogo non e' un dizionario chiuso: si sceglie o si scrive, come ovunque si\r\n indichi un'entita' (vedi `object-picker`). -->\r\n <fb-object-picker\r\n [value]=\"fieldsObject()\"\r\n label=\"Entita\u2019\"\r\n placeholder=\"Scrivi o scegli un\u2019entita\u2019\"\r\n (valueChange)=\"setFieldsObject($event)\"\r\n />\r\n @if (fieldsObject() && !fieldsOfObject().length) {\r\n <p class=\"fb-scr__side-empty\">\r\n Nessun campo dichiarato per questa entita\u2019: il catalogo non li espone, non significa che non\r\n ci siano.\r\n </p>\r\n }\r\n <ul class=\"fb-scr__palette\">\r\n @for (field of fieldsOfObject(); track $index) {\r\n <li>\r\n <button\r\n type=\"button\"\r\n class=\"fb-scr__chip\"\r\n cdkDrag\r\n [title]=\"field.name + (field.dataType ? ' \u00B7 ' + field.dataType : '')\"\r\n (cdkDragMoved)=\"onPaletteDragMoved($event)\"\r\n (cdkDragEnded)=\"onObjectFieldDragEnded($event, field)\"\r\n (click)=\"addObjectFieldAtSelection(field)\"\r\n >\r\n <span class=\"fb-scr__chip-icon\" aria-hidden=\"true\">\u2317</span>\r\n <span class=\"fb-scr__chip-label\">\r\n {{ field.label || field.name }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n <small>{{ field.dataType }}</small>\r\n </span>\r\n </button>\r\n </li>\r\n }\r\n </ul>\r\n }\r\n </aside>\r\n\r\n <!-- ============================================================== centro -->\r\n <!-- Cliccare fuori da un campo riporta a destra le proprieta' della schermata. -->\r\n <section class=\"fb-scr__canvas\" aria-label=\"Anteprima della schermata\" (click)=\"selectScreen()\">\r\n <div class=\"fb-scr__frame\">\r\n @if (showHeader()) {\r\n <header class=\"fb-scr__frame-head\">\r\n {{ $any(node()).label || name() }}\r\n @if (screen().helpText) {\r\n <span class=\"fb-scr__help\" [title]=\"screen().helpText || ''\">?</span>\r\n }\r\n </header>\r\n }\r\n\r\n <div\r\n class=\"fb-scr__body\"\r\n [attr.data-drop]=\"''\"\r\n data-axis=\"column\"\r\n [class.fb-scr__body--empty]=\"isEmpty()\"\r\n >\r\n <ng-container\r\n [ngTemplateOutlet]=\"listTpl\"\r\n [ngTemplateOutletContext]=\"{ $implicit: screen().fields || [], parent: [], axis: 'column' }\"\r\n />\r\n </div>\r\n\r\n <!--\r\n La barra di inserimento e' un **velo** sopra l'anteprima, non un figlio del contenitore di\r\n rilascio: dentro la griglia occupava una riga intera e spostava in basso proprio la sezione\r\n che si stava puntando, che allora usciva da sotto il puntatore e il bersaglio oscillava.\r\n -->\r\n @if (dropMarker(); as bar) {\r\n <div\r\n class=\"fb-scr__marker\"\r\n [style.left.px]=\"bar.left\"\r\n [style.top.px]=\"bar.top\"\r\n [style.width.px]=\"bar.width\"\r\n [style.height.px]=\"bar.height\"\r\n ></div>\r\n }\r\n\r\n @if (showFooter()) {\r\n <footer class=\"fb-scr__frame-foot\">\r\n @if (allowPause()) {\r\n <span class=\"fb-scr__btn fb-scr__btn--ghost\">{{ screen().pauseButtonLabel || 'Pausa' }}</span>\r\n }\r\n <span class=\"fb-scr__spacer\"></span>\r\n @if (allowBack()) {\r\n <span class=\"fb-scr__btn fb-scr__btn--ghost\">{{ screen().backButtonLabel || 'Indietro' }}</span>\r\n }\r\n <span class=\"fb-scr__btn fb-scr__btn--primary\">\r\n {{ screen().nextOrFinishButtonLabel || (allowFinish() ? 'Fine' : 'Avanti') }}\r\n </span>\r\n </footer>\r\n }\r\n </div>\r\n\r\n @if (isEmpty()) {\r\n <p class=\"fb-callout fb-callout--warn fb-scr__canvas-note\">\r\n La schermata non ha campi: non c\u2019e\u2019 niente da mostrare all\u2019utente (SCREEN_WITHOUT_FIELDS).\r\n </p>\r\n }\r\n </section>\r\n\r\n <!-- ============================================================== destra -->\r\n <aside class=\"fb-scr__props\" aria-label=\"Proprieta\u2019\">\r\n @if (!selectedField()) {\r\n <!-- ------------------------------------------------ proprieta' della schermata -->\r\n <header class=\"fb-scr__props-head\">\r\n <h3 class=\"fb-scr__props-title\">Schermata</h3>\r\n </header>\r\n <p class=\"fb-scr__side-hint\">Seleziona un campo nell\u2019anteprima per configurarlo.</p>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Testo di aiuto</label>\r\n <textarea\r\n class=\"fb-textarea\"\r\n [value]=\"screen().helpText || ''\"\r\n (input)=\"setScreenText('helpText', $any($event.target).value)\"\r\n ></textarea>\r\n </div>\r\n\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Navigazione</legend>\r\n <p class=\"fb-section__note\">\r\n Questi flag sono un\u2019intenzione, non la verita\u2019 finale: a runtime il motore comunica\r\n <code>canGoBack</code>, <code>canFinish</code> e <code>canPause</code> nella richiesta della\r\n schermata.\r\n </p>\r\n <label class=\"fb-check\">\r\n <input type=\"checkbox\" [checked]=\"allowBack()\" (change)=\"setScreenFlag('allowBack', $any($event.target).checked)\" />\r\n Consenti \u00ABindietro\u00BB\r\n </label>\r\n <label class=\"fb-check\">\r\n <input type=\"checkbox\" [checked]=\"allowFinish()\" (change)=\"setScreenFlag('allowFinish', $any($event.target).checked)\" />\r\n Consenti \u00ABfine\u00BB\r\n </label>\r\n <label class=\"fb-check\">\r\n <input type=\"checkbox\" [checked]=\"allowPause()\" (change)=\"setScreenFlag('allowPause', $any($event.target).checked)\" />\r\n Consenti \u00ABpausa\u00BB\r\n </label>\r\n <label class=\"fb-check\">\r\n <input type=\"checkbox\" [checked]=\"showHeader()\" (change)=\"setScreenFlag('showHeader', $any($event.target).checked)\" />\r\n Mostra l\u2019intestazione\r\n </label>\r\n <label class=\"fb-check\">\r\n <input type=\"checkbox\" [checked]=\"showFooter()\" (change)=\"setScreenFlag('showFooter', $any($event.target).checked)\" />\r\n Mostra il piede\r\n </label>\r\n\r\n @if (allowPause()) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Testo mostrato alla pausa</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"screen().pausedText || ''\"\r\n (input)=\"setScreenText('pausedText', $any($event.target).value)\"\r\n />\r\n </div>\r\n }\r\n\r\n @if (isDeadEnd()) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n Questa schermata non ha una destinazione e non consente \u00ABfine\u00BB: e\u2019 un vicolo cieco, e\r\n l\u2019utente resterebbe bloccato (SCREEN_DEAD_END).\r\n </p>\r\n }\r\n </fieldset>\r\n\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Pulsanti</legend>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Etichetta \u00ABindietro\u00BB</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"screen().backButtonLabel || ''\"\r\n (input)=\"setScreenText('backButtonLabel', $any($event.target).value)\"\r\n />\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Etichetta \u00ABavanti / fine\u00BB</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"screen().nextOrFinishButtonLabel || ''\"\r\n (input)=\"setScreenText('nextOrFinishButtonLabel', $any($event.target).value)\"\r\n />\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Etichetta \u00ABpausa\u00BB</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"screen().pauseButtonLabel || ''\"\r\n (input)=\"setScreenText('pauseButtonLabel', $any($event.target).value)\"\r\n />\r\n </div>\r\n </fieldset>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Stage mostrato nell\u2019avanzamento</label>\r\n <fb-name-picker\r\n [value]=\"screen().stageReference\"\r\n [options]=\"stageOptions()\"\r\n label=\"Stage\"\r\n placeholder=\"Scegli uno stage\"\r\n unknownMessage=\"Questo stage non e\u2019 dichiarato dal flow.\"\r\n emptyMessage=\"Il flow non dichiara stage: creali nel pannello delle risorse.\"\r\n (valueChange)=\"setScreenText('stageReference', $event ?? '')\"\r\n />\r\n </div>\r\n\r\n <!--\r\n Screen action (\u00A75.2). Due elenchi e non uno perche' il contratto li separa: piu' trigger\r\n possono invocare la stessa action, quindi \u00ABchi la chiama\u00BB non e' una sua proprieta'. Le\r\n action si aprono una alla volta: i cataloghi dipendono da tipo e nome, e tenerne N in volo\r\n sarebbe N volte la corsa fra risposte.\r\n -->\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Azioni della schermata</legend>\r\n <p class=\"fb-section__note\">\r\n Un campo che l\u2019utente compila puo\u2019 innescare un\u2019action i cui risultati finiscono negli\r\n <strong>altri campi di questa schermata</strong>: e\u2019 il caso \u00ABscrivi il codice fiscale e nome\r\n e cognome compaiono da soli\u00BB. L\u2019alternativa sarebbe spezzare la schermata in due con un\r\n elemento Action in mezzo.\r\n </p>\r\n\r\n <div class=\"fb-list\">\r\n @for (action of screenActions(); track $index) {\r\n <div class=\"fb-list__item\">\r\n <div class=\"fb-list__header\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost\"\r\n [attr.aria-expanded]=\"selectedActionIndex() === $index\"\r\n (click)=\"selectAction(selectedActionIndex() === $index ? null : $index)\"\r\n >\r\n {{ selectedActionIndex() === $index ? '\u25BE' : '\u25B8' }}\r\n {{ action.name || '(senza nome)' }}\r\n </button>\r\n <span class=\"fb-list__spacer\"></span>\r\n <span class=\"fb-scr__side-hint\">{{ action.actionName || 'action non scelta' }}</span>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n aria-label=\"Rimuovi l\u2019action\"\r\n (click)=\"removeScreenAction($index)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n\r\n @if (actionLosesResult(action)) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Questa action viene invocata ma il suo risultato si perde: aggiungi un parametro di\r\n uscita, oppure accendi l\u2019output automatico (SCREEN_ACTION_WITHOUT_OUTPUTS).\r\n </p>\r\n }\r\n @if (actionHasNoTrigger(action)) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Nessun trigger la invoca: senza, non girera\u2019 mai.\r\n </p>\r\n }\r\n <!--\r\n \u00A75.2, \u00ABUna screen action per ogni riga\u00BB \u2014 le destinazioni dipendono da chi invoca\r\n l'action, e il rilievo nomina il trigger: la stessa destinazione e' giusta per un\r\n innesco e sbagliata per un altro. Visibili anche con l'action chiusa, come gli avvisi\r\n qui sopra: dicono quali righe dell'elenco hanno qualcosa che non va.\r\n -->\r\n @for (issue of actionRowContext($index).issues; track $index) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n {{ issue.message }} (SCREEN_ACTION_ROW_TARGET_INVALID)\r\n </p>\r\n }\r\n\r\n @if (selectedActionIndex() === $index) {\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]=\"action.name || ''\"\r\n (change)=\"setActionName($index, $any($event.target).value)\"\r\n />\r\n @if (actionNameError(action); as error) {\r\n <p class=\"fb-field__error\">{{ error }}</p>\r\n }\r\n <p class=\"fb-field__hint\">\r\n E\u2019 il nome con cui la citano i trigger, e vive nello spazio dei nomi del flow. Con\r\n l\u2019output automatico e\u2019 anche la radice del riferimento:\r\n <code>{{ action.name || 'Cerca' }}.NomeOutput</code>.\r\n </p>\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Tipo di action</label>\r\n <fb-name-picker\r\n [value]=\"action.actionType\"\r\n [options]=\"actionTypeOptions()\"\r\n label=\"Tipo di action\"\r\n placeholder=\"Scrivi o scegli un tipo\"\r\n [isMono]=\"false\"\r\n unknownMessage=\"Questo tipo di action non e\u2019 fra quelli dichiarati dal sistema ospite.\"\r\n emptyMessage=\"Catalogo dei tipi di action non disponibile: puoi scrivere il nome a mano.\"\r\n (valueChange)=\"setActionType($index, $event ?? '')\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Action</label>\r\n <fb-name-picker\r\n [value]=\"action.actionName\"\r\n [options]=\"actionOptions()\"\r\n label=\"Action\"\r\n placeholder=\"Scrivi o scegli un\u2019action\"\r\n [isMono]=\"false\"\r\n unknownMessage=\"Questa action non esiste nel catalogo del tipo scelto: e\u2019 ACTION_UNKNOWN.\"\r\n unknownSeverity=\"error\"\r\n emptyMessage=\"Scegli prima il tipo di action, oppure scrivi il nome a mano.\"\r\n (valueChange)=\"setActionTarget($index, $event ?? '')\"\r\n />\r\n @if (!action.actionName) {\r\n <p class=\"fb-field__error\">Obbligatoria: senza, ACTION_NAME_MISSING.</p>\r\n }\r\n </div>\r\n\r\n @if (actionRowContext($index).sections.length) {\r\n <p class=\"fb-field__hint\">\r\n Gira <strong>per ogni riga</strong> di\r\n @for (section of actionRowContext($index).sections; track $index) {\r\n @if (!$first) { e di }\r\n <code>{{ section.name }}</code>\r\n }\r\n in cui il campo che la innesca cambia. Input e destinazioni nominano la riga\r\n attraverso il cursore \u2014\r\n @for (root of actionRowContext($index).preferredRoots; track $index) {\r\n @if (!$first) { o }\r\n <code>{{ root }}.&lt;membro&gt;</code>\r\n }\r\n \u2014 e non con i nomi dei campi della riga. Gli output possono andare anche in una\r\n variabile del flow o in un campo fuori dalla sezione.\r\n </p>\r\n <!-- Senza colore d'avviso: e' una premessa, non un difetto \u2014 i difetti, se ci sono,\r\n li dicono i rilievi sopra. -->\r\n @if (actionRowContext($index).runsForScreen) {\r\n <p class=\"fb-field__hint\">\r\n La invoca anche un trigger su un campo fuori dalla sezione: l\u00EC il cursore non\r\n porta alcuna riga, quindi le destinazioni giuste per l\u2019uno sono sbagliate per\r\n l\u2019altro. Le sole che valgono per entrambi sono le variabili e i campi fuori dalla\r\n sezione.\r\n </p>\r\n }\r\n }\r\n\r\n <!--\r\n Su un'action che gira per riga la spunta non si propone: sarebbe una risorsa sola\r\n per tutte le righe (SCREEN_ACTION_ROW_TARGET_INVALID). Resta se e' gia' accesa,\r\n perche' e' da qui che la si spegne.\r\n -->\r\n @if (actionOffersAutomaticOutput($index, action)) {\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"action.storeOutputAutomatically === true\"\r\n (change)=\"setActionStoreOutput($index, $any($event.target).checked)\"\r\n />\r\n Output automatico\r\n </label>\r\n <p class=\"fb-field__hint\">\r\n L\u2019output automatico rende il risultato leggibile come\r\n <code>{{ action.name || 'Cerca' }}.NomeOutput</code>, ma\r\n <strong>non scrive in nessun campo</strong>: e\u2019 la forma da usare quando il risultato\r\n serve a una condizione, non a precompilare.\r\n </p>\r\n }\r\n\r\n <!--\r\n L'unico posto in cui un campo di schermata e' una destinazione (\u00A75.2):\r\n `POST /flows/references/writable` continua a escluderlo, e un Assignment che ci\r\n scrive resta TARGET_NOT_WRITABLE. I campi di una riga restano fuori anche qui:\r\n un'action di riga scrive nei membri del cursore, che il picker propone per primo.\r\n -->\r\n <fb-parameter-editor\r\n [holder]=\"action\"\r\n [catalogParameters]=\"actionParameterCatalog()\"\r\n [showOutputs]=\"action.storeOutputAutomatically !== true\"\r\n outputTitle=\"Campi da riempire\"\r\n [extraTargets]=\"screenFieldTargets()\"\r\n [preferredRoots]=\"actionRowContext($index).preferredRoots\"\r\n [outputsDisabledReason]=\"\r\n action.storeOutputAutomatically === true\r\n ? 'Con l\u2019output automatico il risultato non viene scritto nei campi.'\r\n : null\r\n \"\r\n (changed)=\"onActionParametersChanged($index, $event)\"\r\n />\r\n }\r\n </div>\r\n } @empty {\r\n <p class=\"fb-empty\">Nessuna action: i campi di questa schermata li compila solo l\u2019utente.</p>\r\n }\r\n </div>\r\n\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"addScreenAction()\">Aggiungi action</button>\r\n </fieldset>\r\n\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Trigger</legend>\r\n <p class=\"fb-section__note\">\r\n Quale campo invoca quale action. Senza condizioni l\u2019action gira a ogni cambio del campo.\r\n </p>\r\n\r\n <div class=\"fb-list\">\r\n @for (trigger of screenTriggers(); 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 trigger\"\r\n (click)=\"removeScreenTrigger($index)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Action da invocare</label>\r\n <fb-name-picker\r\n [value]=\"trigger.screenActionName\"\r\n [options]=\"screenActionOptions()\"\r\n label=\"Action della schermata\"\r\n placeholder=\"Scegli un\u2019action\"\r\n unknownMessage=\"Questa schermata non dichiara un\u2019action con questo nome: e\u2019 SCREEN_ACTION_UNKNOWN.\"\r\n unknownSeverity=\"error\"\r\n emptyMessage=\"Aggiungi prima un\u2019action qui sopra.\"\r\n (valueChange)=\"setTriggerProperty($index, 'screenActionName', $event ?? '')\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Campo che la innesca</label>\r\n <!-- I campi di **questa** schermata, non i riferimenti in generale (\u00A75.2). -->\r\n <fb-name-picker\r\n [value]=\"trigger.triggerFieldName\"\r\n [options]=\"triggerFieldOptions()\"\r\n label=\"Campo della schermata\"\r\n placeholder=\"Scegli un campo\"\r\n unknownMessage=\"Questo non e\u2019 un campo di questa schermata: e\u2019 SCREEN_TRIGGER_FIELD_UNKNOWN.\"\r\n unknownSeverity=\"error\"\r\n emptyMessage=\"La schermata non ha ancora campi che raccolgono un valore.\"\r\n (valueChange)=\"setTriggerProperty($index, 'triggerFieldName', $event ?? '')\"\r\n />\r\n @if (triggerRowSection($index); as section) {\r\n <p class=\"fb-field__hint\">\r\n \u00C8 un campo della riga di <code>{{ section.name }}</code>: l\u2019action gira\r\n <strong>per ogni riga</strong> in cui cambia, e per quella che nasce col campo gi\u00E0\r\n precompilato. Condizioni, input e output leggono e scrivono quella riga attraverso\r\n il cursore \u2014 <code>{{ section.itemReference || 'itemReference' }}.&lt;membro&gt;</code>.\r\n </p>\r\n }\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">All\u2019arrivo sulla schermata</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"trigger.initBehavior || ''\"\r\n (change)=\"setTriggerProperty($index, 'initBehavior', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 non invocare \u2014</option>\r\n <option value=\"runOnLoad\">Invoca al caricamento (runOnLoad)</option>\r\n </select>\r\n @if (triggerInitIsUnknown(trigger)) {\r\n <p class=\"fb-field__error\">\r\n \u00AB{{ trigger.initBehavior }}\u00BB non e\u2019 ammesso: l\u2019unico valore e\u2019 <code>runOnLoad</code>\r\n (SCREEN_TRIGGER_INIT_BEHAVIOR_UNKNOWN).\r\n </p>\r\n }\r\n @if (triggerFiresOnLoadByDefault(trigger)) {\r\n <p class=\"fb-field__hint\">\r\n Non serve: \u00AB{{ trigger.triggerFieldName }}\u00BB ha un valore di default, e un campo\r\n precompilato dal flow innesca l\u2019action gi\u00E0 all\u2019arrivo sulla schermata.\r\n </p>\r\n }\r\n </div>\r\n\r\n @if (triggerHasNoCause(trigger)) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Senza un campo che la innesca e senza l\u2019invocazione al caricamento, questo trigger non\r\n scattera\u2019 mai (SCREEN_TRIGGER_WITHOUT_CAUSE).\r\n </p>\r\n }\r\n\r\n <fb-condition-editor\r\n [holder]=\"triggerConditions(trigger)\"\r\n title=\"Invoca l\u2019action quando\"\r\n [allowFormula]=\"false\"\r\n [preferredRoots]=\"triggerPreferredRoots($index)\"\r\n [issuePath]=\"'triggers[' + $index + ']'\"\r\n (changed)=\"onTriggerConditionsChanged($index, $event)\"\r\n />\r\n </div>\r\n } @empty {\r\n <p class=\"fb-empty\">Nessun trigger: le action dichiarate non verranno invocate.</p>\r\n }\r\n </div>\r\n\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"addScreenTrigger()\">Aggiungi trigger</button>\r\n </fieldset>\r\n\r\n <!-- --------------------------------------------- regole della schermata (\u00A75.2) -->\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Regole della schermata</legend>\r\n <p class=\"fb-section__note\">\r\n Le verifiche che <strong>non appartengono a nessun campo</strong>: \u00ABalmeno un punto fra le\r\n due sezioni\u00BB, \u00ABla somma delle percentuali fa cento\u00BB. Si valutano all\u2019invio, dopo i campi e\r\n dopo le righe, e una regola non soddisfatta <strong>impedisce alla schermata di\r\n avanzare</strong>. La <code>validationRule</code> di un campo non basta \u2014 giudica un solo\r\n valore, e su una sezione ripetibile e\u2019 addirittura inerte.\r\n </p>\r\n\r\n <div class=\"fb-list\">\r\n @for (rule of screenRules(); 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__title\">{{ rule.errorMessage || '(senza messaggio)' }}</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--sm\"\r\n aria-label=\"Rimuovi la regola\"\r\n (click)=\"removeScreenRule($index)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n\r\n <!--\r\n \u00A75.2 \u2014 qui `Formula` **e'** un `conditionLogic` ammesso, a differenza di un\r\n `filterLogic`: la regola puo' essere un'espressione booleana. Resta il consiglio,\r\n scritto sotto, di preferire le condizioni.\r\n -->\r\n <fb-condition-editor\r\n [holder]=\"rule\"\r\n title=\"La schermata e\u2019 valida quando\"\r\n [allowFormula]=\"true\"\r\n [issuePath]=\"'validationRules[' + $index + ']'\"\r\n (changed)=\"onScreenRuleConditionsChanged($index, $event)\"\r\n />\r\n @if (screenRuleIsEmpty(rule)) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n La regola non verifica niente: senza condizioni non impedisce mai l\u2019invio.\r\n </p>\r\n }\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Messaggio</label>\r\n <fb-merge-text-editor\r\n [text]=\"rule.errorMessage || ''\"\r\n [rows]=\"2\"\r\n commitOn=\"change\"\r\n placeholder=\"Indica almeno un punto di fornitura, luce o gas.\"\r\n ariaLabel=\"Messaggio della regola\"\r\n (textChange)=\"setScreenRuleProperty($index, 'errorMessage', $event)\"\r\n >\r\n <p class=\"fb-field__hint\">\r\n \u00C8 il testo dell\u2019autore e non nomina nessun campo: il runtime non ne costruisce\r\n uno, perche\u2019 un campo da nominare non c\u2019e\u2019.\r\n </p>\r\n </fb-merge-text-editor>\r\n @if (screenRuleHasNoMessage(rule)) {\r\n <p class=\"fb-field__error\">\r\n Obbligatorio: senza, la schermata si rifiuta di avanzare senza dire perche\u2019\r\n (SCREEN_RULE_INVALID).\r\n </p>\r\n }\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Aggancia il messaggio a un campo</label>\r\n <!--\r\n I campi della schermata **senza** quelli dentro una riga, che esistono in una copia\r\n per elemento e non individuano un controllo (\u00A75.2). Non e' l'elenco degli inneschi:\r\n quello i campi di riga li comprende, perche' un'action puo' girare per riga.\r\n -->\r\n <fb-name-picker\r\n [value]=\"rule.fieldName\"\r\n [options]=\"ruleFieldOptions()\"\r\n label=\"Campo\"\r\n placeholder=\"Nessuno: il messaggio e\u2019 della schermata\"\r\n unknownMessage=\"Questo nome non e\u2019 un campo di questa schermata.\"\r\n unknownSeverity=\"error\"\r\n emptyMessage=\"La schermata non ha campi a cui agganciare il messaggio.\"\r\n (valueChange)=\"setScreenRuleProperty($index, 'fieldName', $event ?? '')\"\r\n />\r\n <p class=\"fb-field__hint\">\r\n Facoltativo. Senza, il rilievo e\u2019 <strong>della schermata</strong> e il frontend lo\r\n mostra dove mostra i messaggi che non hanno un controllo \u2014 in testa al form, o\r\n accanto ad Avanti.\r\n </p>\r\n </div>\r\n </div>\r\n } @empty {\r\n <p class=\"fb-empty\">Nessuna regola: la schermata avanza se i campi sono a posto.</p>\r\n }\r\n </div>\r\n\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"addScreenRule()\">Aggiungi regola</button>\r\n <p class=\"fb-field__hint\">\r\n Scrivile con le <strong>condizioni</strong>, non con la formula: <code>IsEmpty</code> su una\r\n collection e\u2019 un operatore che il runtime valuta, mentre la stessa domanda in una formula\r\n chiede al motore una funzione che potrebbe non avere. Vale soprattutto per la verifica piu\u2019\r\n frequente \u2014 che una sezione ripetibile abbia almeno una riga.\r\n </p>\r\n </fieldset>\r\n } @else {\r\n <!-- ---------------------------------------------------- proprieta' del campo -->\r\n <header class=\"fb-scr__props-head\">\r\n <h3 class=\"fb-scr__props-title\">{{ captionOf(selectedField()!) }}</h3>\r\n <div class=\"fb-scr__props-actions\">\r\n <button type=\"button\" class=\"fb-btn fb-btn--sm\" title=\"Sposta su\" (click)=\"moveSelected(-1)\">\u2191</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--sm\" title=\"Sposta giu\u2019\" (click)=\"moveSelected(1)\">\u2193</button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--sm\"\r\n title=\"Porta fuori dal contenitore\"\r\n [disabled]=\"!canOutdent()\"\r\n (click)=\"outdentSelected()\"\r\n >\r\n \u21E4\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--sm\" title=\"Duplica\" (click)=\"duplicateSelected()\">\u29C9</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--danger fb-btn--sm\" title=\"Elimina\" (click)=\"removeSelected()\">\r\n \u00D7\r\n </button>\r\n </div>\r\n </header>\r\n\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]=\"selectedField()!.fieldType || ''\"\r\n (change)=\"setFieldType($any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 scegli \u2014</option>\r\n <!--\r\n \u00A75.2 \u2014 dentro una riga la sezione ripetibile non c'e': **non si annidano**, perche' il\r\n cursore e' una variabile sola per sezione. La validazione lo rifiuterebbe comunque, ma\r\n lasciar comporre e poi accusare fa perdere tempo due volte.\r\n -->\r\n @for (entry of fieldTypeOptions(); track entry.value) {\r\n <option [value]=\"entry.value\">{{ entry.label }}</option>\r\n }\r\n </select>\r\n @if (isInsideRepeater()) {\r\n <p class=\"fb-field__hint\">\r\n Questo campo sta dentro la riga di <code>{{ selectedRepeater()!.name }}</code>: esiste una\r\n volta per ogni elemento della collection, quindi\r\n <strong>non e\u2019 una risorsa del flow</strong> e non si nomina per nome. Dentro una riga si\r\n nomina il cursore \u2014\r\n <code>{{ rowCursor() || 'itemReference' }}.&lt;membro&gt;</code> \u2014 e scrivere\r\n <code>{{ selectedField()!.name }}</code> in una condizione, in una formula o in un parametro\r\n e\u2019 <code>SCREEN_FIELD_IN_REPEATER</code>. Puo\u2019 invece <strong>innescare una screen\r\n action</strong>, che gira per ogni riga in cui il campo cambia.\r\n </p>\r\n }\r\n @if (!selectedField()!.fieldType) {\r\n <p class=\"fb-field__error\">Il tipo e\u2019 obbligatorio: senza, SCREEN_FIELD_TYPE_MISSING.</p>\r\n } @else if (isUnknownType(selectedField())) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n Questo tipo non e\u2019 nel dizionario: l\u2019editor non sa quali campi ammetta e mostra solo i comuni.\r\n </p>\r\n } @else if (selectedType()?.description) {\r\n <p class=\"fb-field__hint\">{{ selectedType()?.description }}</p>\r\n }\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Nome</label>\r\n <!-- Sul `change` e non sull\u2019`input`: la rinomina riscrive i riferimenti nel documento,\r\n e farlo a ogni tasto significherebbe riscriverlo per ogni lettera. -->\r\n <input\r\n class=\"fb-input fb-input--mono\"\r\n [value]=\"selectedField()!.name || ''\"\r\n (change)=\"setFieldName($any($event.target).value)\"\r\n />\r\n @if (selectedIsResource() && !isInsideRepeater()) {\r\n <p class=\"fb-field__hint\">\r\n Questo campo e\u2019 una <strong>risorsa</strong>: lo referenzi come\r\n <code>{{ selectedField()!.name || 'Nome' }}</code> in condizioni, formule e parametri. \u00C8 di\r\n sola lettura per il flow \u2014 un Assignment che ci scrive e\u2019 TARGET_NOT_WRITABLE.\r\n </p>\r\n } @else if (isInsideRepeater()) {\r\n <p class=\"fb-field__hint\">\r\n Il nome resta comunque <strong>unico</strong>: e\u2019 l\u2019indirizzo con cui il frontend rende il\r\n campo, e un omonimo di una variabile e\u2019 <code>NAME_DUPLICATED</code> come sempre.\r\n </p>\r\n }\r\n </div>\r\n\r\n <!--\r\n \u00A75.2 \u2014 `memberName` e il riferimento con cui gli altri campi lo nominano sono la **stessa\r\n domanda posta due volte**: `memberName: \"PodId\"` e `PuntoCorrente.PodId` sono lo stesso\r\n membro, e la classe da cui vengono e' l'`objectType` della variabile in `collectionReference`.\r\n -->\r\n @if (isInsideRepeater() && selectedType()?.storesValue) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Membro della riga</label>\r\n @if (rowClass()) {\r\n <fb-structure-member-picker\r\n [value]=\"selectedField()!.memberName\"\r\n [className]=\"rowClass()\"\r\n usage=\"writable\"\r\n label=\"Membro della riga\"\r\n (valueChange)=\"setFieldProperty('memberName', $event ?? '')\"\r\n />\r\n } @else {\r\n <input\r\n class=\"fb-input fb-input--mono\"\r\n [value]=\"selectedField()!.memberName || ''\"\r\n (change)=\"setFieldProperty('memberName', $any($event.target).value)\"\r\n />\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n La classe delle righe non e\u2019 nota: la sezione non dichiara una <code>collectionReference</code>\r\n valida, quindi i membri non si possono proporre.\r\n </p>\r\n }\r\n <p class=\"fb-field__hint\">\r\n Il membro della riga in cui finisce il valore. Senza, vale il nome del campo. Dentro una\r\n riga lo si legge come\r\n <code>{{ rowCursor() || 'itemReference' }}.{{ selectedField()!.memberName || selectedField()!.name }}</code>.\r\n </p>\r\n </div>\r\n }\r\n\r\n <!--\r\n Il ramo dei contenitori vale anche per una sezione ripetibile: l\u00EC `fieldText` e'\r\n l'intestazione del blocco, non l'etichetta di un valore \u2014 l'etichetta della **riga** e'\r\n `itemLabel`, ed e' un campo suo, piu' sotto (\u00A75.2).\r\n -->\r\n @if (!holdsFields(selectedField())) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">\r\n {{ selectedField()!.fieldType === 'DisplayText' ? 'Testo' : 'Etichetta' }}\r\n </label>\r\n <textarea\r\n class=\"fb-textarea\"\r\n [value]=\"selectedField()!.fieldText || ''\"\r\n (input)=\"setFieldProperty('fieldText', $any($event.target).value)\"\r\n ></textarea>\r\n <p class=\"fb-field__hint\">\r\n Supporta i merge field <code>&#123;!Riferimento&#125;</code>.\r\n @if (selectedField()!.fieldType === 'ComponentInstance') {\r\n <!--\r\n \u00A75.2 \u2014 su un componente l\u2019etichetta e\u2019 **ammessa** e viene consegnata al frontend\r\n in `label`, ma la sua assenza non e\u2019 un rilievo: un componente di solito disegna\r\n la propria intestazione. Nasconderla, come faceva prima l\u2019editor, toglieva un\r\n campo che il contratto prevede.\r\n -->\r\n Facoltativa: un componente di solito disegna la propria intestazione.\r\n }\r\n </p>\r\n </div>\r\n } @else {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Intestazione</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"selectedField()!.fieldText || ''\"\r\n (input)=\"setFieldProperty('fieldText', $any($event.target).value)\"\r\n />\r\n </div>\r\n }\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Testo di aiuto</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"selectedField()!.helpText || ''\"\r\n (input)=\"setFieldProperty('helpText', $any($event.target).value)\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Larghezza</label>\r\n <div class=\"fb-scr__width\">\r\n <input\r\n type=\"range\"\r\n min=\"1\"\r\n max=\"12\"\r\n step=\"1\"\r\n [value]=\"widthOf(selectedField()!)\"\r\n (input)=\"setNumberProperty('width', $any($event.target).value)\"\r\n />\r\n <span class=\"fb-scr__width-value\">{{ widthOf(selectedField()!) }}/12</span>\r\n </div>\r\n <p class=\"fb-field__hint\">\r\n Colonne della griglia della schermata. \u00C8 un\u2019indicazione: il frontend puo\u2019 ignorarla.\r\n </p>\r\n </div>\r\n\r\n @if (selectedField()!.fieldType === 'ComponentInstance') {\r\n <!--\r\n \u00A75.2 \u2014 perche\u2019 qui non c\u2019e\u2019 \u00ABobbligatorio\u00BB. La domanda arriva sempre, e la risposta non\r\n si indovina guardando l\u2019interfaccia: un componente non ha un valore proprio, ce l\u2019hanno\r\n i suoi output, che vanno in **variabili** scelte dall\u2019autore. \u00ABObbligatorio\u00BB dovrebbe\r\n dire *quale output* deve essere valorizzato, e il metadata non ha modo di dirlo \u2014\r\n dichiararlo qui sarebbe `SCREEN_FIELD_CONFIGURATION_INVALID` e il runtime lo\r\n ignorerebbe. Le due strade vere stanno nel contratto e sono queste.\r\n -->\r\n <p class=\"fb-field__hint\">\r\n Un componente non raccoglie un valore \u2014 lo fanno i suoi output \u2014 quindi obbligatorieta\u2019, valore\r\n predefinito e regola di validazione non si applicano: dichiararli e\u2019\r\n <code>SCREEN_FIELD_CONFIGURATION_INVALID</code> e il runtime li ignora. Per pretendere una\r\n compilazione: la validazione la fa il componente stesso, oppure si controlla la variabile di\r\n destinazione con una Decision dopo la schermata.\r\n </p>\r\n }\r\n\r\n <!-- ------------------------------------------------ campi che raccolgono un valore -->\r\n @if (selectedType()?.storesValue) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Valore</legend>\r\n\r\n @if (selectedField()!.fieldType !== 'ObjectProvided') {\r\n <div class=\"fb-field\">\r\n <label\r\n class=\"fb-field__label\"\r\n [class.fb-field__label--required]=\"!!selectedType()?.requiresDataType\"\r\n >\r\n Tipo di dato\r\n </label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"selectedField()!.dataType || ''\"\r\n (change)=\"setFieldProperty('dataType', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 scegli \u2014</option>\r\n @for (entry of dictionary.dataTypes(); track entry.value) {\r\n <option [value]=\"entry.value\">{{ entry.label }}</option>\r\n }\r\n </select>\r\n @if (selectedType()?.requiresDataType && !selectedField()!.dataType) {\r\n <p class=\"fb-field__error\">Obbligatorio per questo tipo di campo (DATA_TYPE_MISSING).</p>\r\n }\r\n @if (selectedType()?.isCollection) {\r\n <p class=\"fb-field__hint\">\r\n Il valore raccolto e\u2019 una <strong>collection</strong>, non una stringa con i valori\r\n separati: nelle condizioni si usano gli operatori di collection.\r\n </p>\r\n }\r\n </div>\r\n\r\n @if (requiresObjectType()) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\" [class.fb-field__label--required]=\"objectTypeIsStructure()\">\r\n {{\r\n objectTypeIsStructure()\r\n ? 'Classe'\r\n : selectedField()!.dataType === 'Enum'\r\n ? 'Tipo di enumerazione'\r\n : 'Oggetto'\r\n }}\r\n </label>\r\n @if (selectedField()!.dataType === 'Enum') {\r\n <!-- Dizionario chiuso: si sceglie, non si scrive. -->\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"selectedField()!.objectType || ''\"\r\n (change)=\"setFieldProperty('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 @if (!selectedField()!.objectType) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n Senza il tipo concreto l\u2019editor non puo\u2019 proporre i valori dell\u2019enumerazione.\r\n </p>\r\n }\r\n } @else if (objectTypeIsStructure()) {\r\n <fb-structure-picker\r\n [value]=\"selectedField()!.objectType\"\r\n label=\"Classe\"\r\n (valueChange)=\"setFieldProperty('objectType', $event ?? '')\"\r\n />\r\n @if (!selectedField()!.objectType) {\r\n <p class=\"fb-field__error\">\r\n La classe e\u2019 obbligatoria: senza, il runtime non ha nulla da istanziare\r\n (OBJECT_TYPE_MISSING).\r\n </p>\r\n }\r\n } @else {\r\n <fb-object-picker\r\n [value]=\"selectedField()!.objectType\"\r\n label=\"Oggetto\"\r\n (valueChange)=\"setFieldProperty('objectType', $event ?? '')\"\r\n />\r\n }\r\n </div>\r\n }\r\n } @else {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Campo dell\u2019entita\u2019</label>\r\n <p class=\"fb-field__hint\">\r\n Tipo, label, obbligatorieta\u2019, scala e \u2014 se il campo e\u2019 una picklist \u2014 le opzioni arrivano\r\n dallo schema dati: qui non si ridichiarano.\r\n </p>\r\n <fb-object-picker\r\n [value]=\"providedObject() || undefined\"\r\n label=\"Oggetto\"\r\n (valueChange)=\"setProvidedObject($event)\"\r\n />\r\n <fb-field-picker\r\n [value]=\"providedField() || undefined\"\r\n [object]=\"providedObject() || undefined\"\r\n label=\"Campo\"\r\n (valueChange)=\"setProvidedField($event)\"\r\n />\r\n </div>\r\n }\r\n\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"selectedIsRequired()\"\r\n (change)=\"setRequired($any($event.target).checked)\"\r\n />\r\n Obbligatorio\r\n </label>\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"selectedIsEditable()\"\r\n (change)=\"setEditable($any($event.target).checked)\"\r\n />\r\n Modificabile\r\n </label>\r\n @if (!selectedIsEditable()) {\r\n <p class=\"fb-field__hint\">\r\n A <code>false</code> il runtime <strong>ignora</strong> cio\u2019 che il client rimanda indietro:\r\n e\u2019 l\u2019unico modo di rendere un campo davvero di sola lettura.\r\n </p>\r\n }\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Valore di default</label>\r\n <fb-value-editor\r\n [value]=\"selectedField()!.defaultValue\"\r\n [dataType]=\"selectedField()!.dataType\"\r\n [objectType]=\"selectedField()!.objectType\"\r\n [isCollection]=\"selectedType()?.isCollection\"\r\n [preferredRoots]=\"preferredRoots()\"\r\n label=\"Valore di default\"\r\n (valueChange)=\"setDefaultValue($event)\"\r\n />\r\n </div>\r\n\r\n @if (scaleApplies()) {\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]=\"selectedField()!.scale ?? ''\"\r\n (change)=\"setNumberProperty('scale', $any($event.target).value)\"\r\n />\r\n </div>\r\n } @else if (selectedField()!.scale !== undefined) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n <code>scale</code> su un campo non numerico e\u2019 SCALE_NOT_APPLICABLE.\r\n </p>\r\n }\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Lunghezza massima</label>\r\n <input\r\n class=\"fb-input\"\r\n type=\"number\"\r\n min=\"1\"\r\n [value]=\"selectedField()!.maxLength ?? ''\"\r\n (change)=\"setNumberProperty('maxLength', $any($event.target).value)\"\r\n />\r\n <p class=\"fb-field__hint\">Verificata anche dal runtime (TOO_LONG).</p>\r\n </div>\r\n\r\n @if (formatApplies()) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Formato</label>\r\n <fb-name-picker\r\n [value]=\"selectedField()!.format\"\r\n [options]=\"formatOptions()\"\r\n label=\"Formato\"\r\n placeholder=\"Nessuno: un testo qualunque\"\r\n unknownSeverity=\"note\"\r\n unknownMessage=\"Non e\u2019 fra i formati proposti: se l\u2019host ha registrato il suo pattern va bene cosi\u2019, altrimenti il campo non verifica niente (SCREEN_FIELD_FORMAT_UNKNOWN).\"\r\n emptyMessage=\"Nessun formato dichiarato: puoi scriverne uno registrato dall\u2019host.\"\r\n (valueChange)=\"setFieldProperty('format', $event)\"\r\n />\r\n <p class=\"fb-field__hint\">\r\n <strong>Che cosa</strong> il campo raccoglie, oltre al fatto di essere un testo: il\r\n frontend ne ricava il tipo di input, e il runtime rifiuta l\u2019invio sbagliato\r\n (FORMAT_INVALID). L\u2019elenco non e\u2019 chiuso \u2014 i pattern li registra l\u2019host, non la\r\n libreria \u2014 quindi un nome fuori elenco non e\u2019 un difetto.\r\n </p>\r\n <p class=\"fb-field__hint\">\r\n Non e\u2019 un\u2019etichetta che il flow puo\u2019 leggere: se a valle serve sapere che un numero e\u2019\r\n un cellulare, quella distinzione sta nel dato (due campi, o due membri di una\r\n <code>Structure</code>). E non riscrive il valore \u2014 togliere spazi o aggiungere il\r\n prefisso e\u2019 una trasformazione, e la fa una screen action.\r\n </p>\r\n </div>\r\n } @else if (selectedField()!.format) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n <code>format</code> su un campo che non raccoglie un solo testo non ha effetto\r\n (SCREEN_FIELD_FORMAT_NOT_APPLICABLE).\r\n </p>\r\n }\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Tornando sulla schermata</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"selectedField()!.inputsOnNextNavToAssocScrn || ''\"\r\n (change)=\"setFieldProperty('inputsOnNextNavToAssocScrn', $any($event.target).value)\"\r\n >\r\n <option value=\"\">Predefinito (mantieni i valori)</option>\r\n @for (entry of dictionary.screenFieldInputsRevisited(); track entry.value) {\r\n <option [value]=\"entry.value\">{{ entry.label }}</option>\r\n }\r\n </select>\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Regola di validazione</label>\r\n <!--\r\n `commitOn=\"change\"`: qui mutare il documento ridisegna l'anteprima, e riscriverla a\r\n ogni tasto costa. La verifica parte lo stesso mentre si digita (\u00A76.3).\r\n -->\r\n <fb-formula-editor\r\n [expression]=\"selectedField()!.validationRule?.formulaExpression || ''\"\r\n usage=\"ValidationRule\"\r\n expectedDataType=\"Boolean\"\r\n commitOn=\"change\"\r\n [preferredRoots]=\"preferredRoots()\"\r\n [rows]=\"2\"\r\n placeholder=\"Espressione, es. LEN(Nome) > 3\"\r\n ariaLabel=\"Regola di validazione\"\r\n (expressionChange)=\"setValidationRule('formulaExpression', $event)\"\r\n />\r\n <input\r\n class=\"fb-input\"\r\n placeholder=\"Messaggio mostrato quando l\u2019espressione e\u2019 falsa\"\r\n [value]=\"selectedField()!.validationRule?.errorMessage || ''\"\r\n (change)=\"setValidationRule('errorMessage', $any($event.target).value)\"\r\n />\r\n <p class=\"fb-field__hint\">\r\n L\u2019espressione la valuta il motore di regole: il backend non la interpreta, la fa\r\n verificare al motore.\r\n </p>\r\n </div>\r\n </fieldset>\r\n }\r\n\r\n <!-- --------------------------------------------------------------- choice -->\r\n @if (selectedType()?.acceptsChoices) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Opzioni</legend>\r\n <p class=\"fb-section__note\">\r\n Accetta <strong>Choice</strong> e <strong>Dynamic choice set</strong>, nell\u2019ordine in cui le\r\n opzioni compaiono. Puntare a una variabile e\u2019 SCREEN_FIELD_CHOICE_UNKNOWN.\r\n </p>\r\n\r\n @if (!selectedField()!.choiceReferences?.length) {\r\n @if (selectedField()!.fieldType === 'ObjectProvided') {\r\n <!-- Le opzioni di una picklist arrivano dallo schema: qui si aggiungono solo se\r\n si vuole sostituirle, e non dichiararne nessuna e' il caso normale. -->\r\n <p class=\"fb-field__hint\">\r\n Se il campo dello schema e\u2019 una picklist, le opzioni arrivano da l\u00EC: dichiararle qui\r\n serve solo a sostituirle.\r\n </p>\r\n } @else {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Nessuna opzione: il campo non ha niente da mostrare (SCREEN_FIELD_CHOICES_MISSING).\r\n </p>\r\n }\r\n }\r\n\r\n <div class=\"fb-list\">\r\n @for (choice of selectedField()!.choiceReferences || []; 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__title\">{{ choice }}</span>\r\n <span class=\"fb-list__spacer\"></span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--sm\" (click)=\"moveChoice($index, -1)\">\u2191</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--sm\" (click)=\"moveChoice($index, 1)\">\u2193</button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--sm\"\r\n aria-label=\"Rimuovi\"\r\n (click)=\"removeChoice($index)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Aggiungi un\u2019opzione</label>\r\n <!--\r\n `commitOn=\"enter\"`: qui ogni emissione e\u2019 una riga in piu\u2019, non un campo che cambia\r\n valore \u2014 a tasto, scrivere \u00ABAlta\u00BB aggiungeva \u00ABA\u00BB, \u00ABAl\u00BB, \u00ABAlt\u00BB e \u00ABAlta\u00BB.\r\n -->\r\n <fb-name-picker\r\n [options]=\"choiceOptions()\"\r\n label=\"Choice\"\r\n commitOn=\"enter\"\r\n placeholder=\"Scegli una choice o un choice set\"\r\n unknownMessage=\"Questo nome non e\u2019 una choice ne\u2019 un choice set: e\u2019 SCREEN_FIELD_CHOICE_UNKNOWN.\"\r\n unknownSeverity=\"error\"\r\n emptyMessage=\"Il flow non dichiara nessuna choice: creale nel pannello delle risorse.\"\r\n (valueChange)=\"addChoice($event)\"\r\n />\r\n <p class=\"fb-field__hint\">\r\n Scegli dall\u2019elenco, oppure scrivi il nome e premi Invio.\r\n </p>\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Opzione selezionata all\u2019apertura</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"selectedField()!.defaultSelectedChoiceReference || ''\"\r\n (change)=\"setFieldProperty('defaultSelectedChoiceReference', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 nessuna \u2014</option>\r\n @for (choice of selectedField()!.choiceReferences || []; track $index) {\r\n <option [value]=\"choice\">{{ choice }}</option>\r\n }\r\n </select>\r\n @if (defaultChoiceIsForeign()) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n \u00AB{{ selectedField()!.defaultSelectedChoiceReference }}\u00BB non e\u2019 fra le opzioni elencate qui\r\n sopra.\r\n </p>\r\n }\r\n </div>\r\n\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"selectedAutoSelectsSingleChoice()\"\r\n (change)=\"setAutoSelectSingleChoice($any($event.target).checked)\"\r\n />\r\n Scegli da sola l\u2019unica opzione\r\n </label>\r\n <p class=\"fb-field__hint\">\r\n Quando le opzioni risolte sono <strong>esattamente una</strong>, il campo la prende da se\u2019.\r\n Serve alle tendine che dipendono da un\u2019altra tendina \u2014 i CAP di un comune \u2014 dove chiedere\r\n di scegliere l\u2019unica voce non decide niente.\r\n </p>\r\n </fieldset>\r\n }\r\n\r\n <!-- ------------------------------------------------------- contenitori -->\r\n @if (selectedType()?.isContainer) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Contenitore</legend>\r\n @if (selectedField()!.fieldType === 'RegionContainer') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Tipo di sezione</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"selectedField()!.regionContainerType || ''\"\r\n (change)=\"setFieldProperty('regionContainerType', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 predefinito \u2014</option>\r\n @for (entry of dictionary.regionContainerTypes(); track entry.value) {\r\n <option [value]=\"entry.value\">{{ entry.label }}</option>\r\n }\r\n </select>\r\n </div>\r\n }\r\n @if (!selectedField()!.fields?.length) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Il contenitore e\u2019 vuoto: non produce niente sulla schermata (SCREEN_CONTAINER_EMPTY).\r\n </p>\r\n }\r\n </fieldset>\r\n }\r\n\r\n <!-- ------------------------------------------------ sezione ripetibile (\u00A75.2) -->\r\n @if (selectedIsRepeatingSection()) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Righe</legend>\r\n <p class=\"fb-section__note\">\r\n I campi qui dentro sono il <strong>modello di una riga</strong>: la schermata ne rende una\r\n copia per ogni elemento della collection, e l\u2019utente decide quante con un tasto. I due\r\n riferimenti vanno pensati insieme \u2014 la <strong>collection</strong> e\u2019 dove le righe vivono,\r\n il <strong>cursore</strong> e\u2019 la riga corrente, come\r\n l\u2019<code>assignNextValueToReference</code> di un Loop.\r\n </p>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Collection delle righe</label>\r\n <fb-reference-picker\r\n [value]=\"selectedField()!.collectionReference\"\r\n dataType=\"Structure\"\r\n [isCollection]=\"true\"\r\n [writableOnly]=\"true\"\r\n label=\"Collection delle righe\"\r\n placeholder=\"Una variabile collection di tipo Structure\"\r\n (valueChange)=\"setFieldProperty('collectionReference', $event ?? '')\"\r\n />\r\n <p class=\"fb-field__hint\">\r\n \u00C8 quella che il resto del flow attraversa con un Loop: la sezione non introduce un posto\r\n nuovo in cui i dati stanno. Se la riga e\u2019 il DTO che poi finisce in una action, conviene\r\n che sia una collection <strong>di quel DTO</strong>.\r\n </p>\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Cursore della riga</label>\r\n <fb-reference-picker\r\n [value]=\"selectedField()!.itemReference\"\r\n dataType=\"Structure\"\r\n [isCollection]=\"false\"\r\n [objectType]=\"rowClass()\"\r\n [writableOnly]=\"true\"\r\n label=\"Cursore della riga\"\r\n placeholder=\"Una variabile Structure della stessa classe\"\r\n (valueChange)=\"setFieldProperty('itemReference', $event ?? '')\"\r\n />\r\n <p class=\"fb-field__hint\">\r\n \u00C8 il nome che si scrive <strong>dentro</strong> la riga: visibilita\u2019, regole di\r\n validazione, filtri delle tendine, parametri dei componenti e\r\n <code>itemLabel</code> nominano\r\n <code>{{ selectedField()!.itemReference || 'Cursore' }}.&lt;membro&gt;</code>, mai il nome\r\n di un campo della riga.\r\n </p>\r\n </div>\r\n\r\n @if (repeaterIssue(); as issue) {\r\n <p class=\"fb-callout fb-callout--error\">{{ issue }} (REPEATING_SECTION_INVALID)</p>\r\n }\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Righe minime</label>\r\n <input\r\n class=\"fb-input\"\r\n type=\"number\"\r\n min=\"0\"\r\n [value]=\"selectedField()!.minInstances ?? ''\"\r\n (change)=\"setNumberProperty('minInstances', $any($event.target).value)\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Righe massime</label>\r\n <input\r\n class=\"fb-input\"\r\n type=\"number\"\r\n min=\"1\"\r\n [value]=\"selectedField()!.maxInstances ?? ''\"\r\n (change)=\"setNumberProperty('maxInstances', $any($event.target).value)\"\r\n />\r\n @if (repeaterHasNoLimit()) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n Senza un massimo l\u2019utente puo\u2019 aggiungere righe all\u2019infinito\r\n (REPEATING_SECTION_WITHOUT_LIMIT).\r\n </p>\r\n }\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Righe gia\u2019 aperte</label>\r\n <input\r\n class=\"fb-input\"\r\n type=\"number\"\r\n min=\"0\"\r\n [value]=\"selectedField()!.initialInstances ?? ''\"\r\n (change)=\"setNumberProperty('initialInstances', $any($event.target).value)\"\r\n />\r\n <p class=\"fb-field__hint\">\r\n Quante righe trovare <strong>gia\u2019 aperte</strong> arrivando sulla schermata, ed e\u2019 un\u2019altra\r\n cosa dal minimo: il minimo e\u2019 un vincolo, queste sono una comodita\u2019 e si possono togliere\r\n tutte. Un punto luce e un punto gas gia\u2019 aperti, dove chi ha una sola commodity toglie\r\n quello che non gli serve, sono <code>1</code> qui con <code>0</code> come minimo; col\r\n minimo a 1 sarebbe un modulo che <strong>obbliga</strong> a entrambe.\r\n </p>\r\n <p class=\"fb-field__hint\">\r\n Si applicano <strong>una volta sola</strong>, e solo se nessuno ha ancora scritto la\r\n collection: righe arrivate da un ciclo o da una schermata precedente <em>sono</em> le righe\r\n iniziali e non vengono diluite con righe vuote, e chi le toglie tutte non se le ritrova.\r\n </p>\r\n @if (repeaterInitialOutOfRange(); as issue) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">{{ issue }}</p>\r\n }\r\n </div>\r\n\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"selectedField()!.allowAdd !== false\"\r\n (change)=\"setRepeaterFlag('allowAdd', $any($event.target).checked)\"\r\n />\r\n L\u2019utente puo\u2019 aggiungere righe\r\n </label>\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"selectedField()!.allowRemove !== false\"\r\n (change)=\"setRepeaterFlag('allowRemove', $any($event.target).checked)\"\r\n />\r\n L\u2019utente puo\u2019 togliere righe\r\n </label>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Scritta del comando \u00ABaggiungi\u00BB</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"selectedField()!.addButtonLabel || ''\"\r\n (input)=\"setFieldProperty('addButtonLabel', $any($event.target).value)\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Scritta del comando \u00ABtogli\u00BB</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"selectedField()!.removeButtonLabel || ''\"\r\n (input)=\"setFieldProperty('removeButtonLabel', $any($event.target).value)\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Intestazione della singola riga</label>\r\n <!-- `commitOn=\"change\"`: mutare il documento ridisegna l'anteprima. -->\r\n <fb-merge-text-editor\r\n [text]=\"selectedField()!.itemLabel || ''\"\r\n [rows]=\"2\"\r\n commitOn=\"change\"\r\n placeholder=\"Punto {{ '{' }}!Cursore.PodId}\"\r\n ariaLabel=\"Intestazione della riga\"\r\n (textChange)=\"setFieldProperty('itemLabel', $event)\"\r\n >\r\n <p class=\"fb-field__hint\">\r\n Risolta <strong>sulla riga</strong>: i merge field qui nominano il cursore\r\n (<code>&#123;!{{ selectedField()!.itemReference || 'Cursore' }}.Membro&#125;</code>), non\r\n un campo della riga.\r\n </p>\r\n </fb-merge-text-editor>\r\n </div>\r\n\r\n @if (!selectedField()!.fields?.length) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Il modello della riga e\u2019 vuoto: la sezione non mostra niente (SCREEN_CONTAINER_EMPTY).\r\n </p>\r\n }\r\n </fieldset>\r\n }\r\n\r\n <!-- --------------------------------------------------- ComponentInstance -->\r\n @if (selectedField()!.fieldType === 'ComponentInstance') {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Componente</legend>\r\n <p class=\"fb-section__note\">\r\n Componenti e form sono la stessa domanda al frontend \u2014 cosa sa rendere, e con quali parametri\r\n \u2014 e passano dallo stesso catalogo. Un <code>ComponentInstance</code> non ha un valore proprio:\r\n lo hanno i suoi output.\r\n </p>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Componente</label>\r\n <fb-name-picker\r\n [value]=\"selectedField()!.extensionName\"\r\n [options]=\"componentOptions()\"\r\n [unusableOptions]=\"unusableComponents()\"\r\n label=\"Componente\"\r\n placeholder=\"Scrivi o scegli un componente\"\r\n [isMono]=\"false\"\r\n unknownMessage=\"Questo componente non esiste nel catalogo: e\u2019 SCREEN_COMPONENT_UNKNOWN.\"\r\n unknownSeverity=\"error\"\r\n unusableMessage=\"Questo nome e\u2019 una schermata intera, non un componente montabile qui (FORM_KIND_MISMATCH).\"\r\n emptyMessage=\"Il catalogo dei componenti non e\u2019 popolato: il nome non viene verificato.\"\r\n (valueChange)=\"setFieldProperty('extensionName', $event ?? '')\"\r\n />\r\n @if (!selectedField()!.extensionName) {\r\n <p class=\"fb-field__error\">Obbligatorio: senza, SCREEN_COMPONENT_MISSING.</p>\r\n }\r\n </div>\r\n\r\n <!--\r\n \u00A75.2 \u2014 dentro una riga la spunta **non c'e'**: gli output automatici si espongono come\r\n `<NomeCampo>.<NomeOutput>`, che sarebbe **una risorsa sola per tutte le righe** \u2014 l'ultima\r\n che ha girato. \u00C8 `REPEATING_SECTION_INVALID`, e la strada giusta e' `assignToReference` su\r\n un membro del cursore, che una riga ce l'ha per davvero.\r\n -->\r\n @if (!isInsideRepeater()) {\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"selectedField()!.storeOutputAutomatically === true\"\r\n (change)=\"setStoreOutputAutomatically($any($event.target).checked)\"\r\n />\r\n Rendi gli output referenziabili automaticamente\r\n </label>\r\n @if (selectedField()!.storeOutputAutomatically) {\r\n <p class=\"fb-field__hint\">\r\n Gli output si referenziano come\r\n <code>{{ selectedField()!.name || 'Campo' }}.nomeOutput</code>, senza dichiarare\r\n variabili.\r\n </p>\r\n }\r\n @if (hasOutputConflict()) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n Output automatici <strong>e</strong> parametri di uscita insieme:\r\n OUTPUT_CONFIGURATION_CONFLICT.\r\n </p>\r\n }\r\n } @else {\r\n <p class=\"fb-field__hint\">\r\n Gli output automatici qui non si possono usare: si esporrebbero come\r\n <code>{{ selectedField()!.name || 'Campo' }}.nomeOutput</code>, cioe\u2019 una risorsa sola per\r\n tutte le righe \u2014 l\u2019ultima che ha girato (REPEATING_SECTION_INVALID). Assegnali con\r\n <strong>assignToReference</strong> a un membro di\r\n <code>{{ rowCursor() || 'Cursore' }}</code>.\r\n </p>\r\n @if (selectedField()!.storeOutputAutomatically) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n Questo componente dichiara gli output automatici e sta dentro una riga:\r\n REPEATING_SECTION_INVALID.\r\n <button type=\"button\" class=\"fb-btn fb-btn--sm\" (click)=\"setStoreOutputAutomatically(false)\">\r\n Togli\r\n </button>\r\n </p>\r\n }\r\n }\r\n\r\n <fb-parameter-editor\r\n [holder]=\"$any(selectedField())\"\r\n [catalogParameters]=\"componentParameterList()\"\r\n [preferredRoots]=\"preferredRoots()\"\r\n inputTitle=\"Valori passati al componente\"\r\n outputTitle=\"Valori raccolti dal componente\"\r\n [showOutputs]=\"!selectedField()!.storeOutputAutomatically\"\r\n outputsDisabledReason=\"Gli output sono automatici: disattivalo per assegnarli a variabili.\"\r\n (changed)=\"onComponentParametersChanged($event)\"\r\n />\r\n </fieldset>\r\n }\r\n\r\n <!-- ------------------------------------------------------- visibilita' -->\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Visibilita\u2019</legend>\r\n <p class=\"fb-section__note\">\r\n Le regole si rivalutano <strong>sui valori appena inviati</strong>: e\u2019 cos\u00EC che un campo compare\r\n in funzione di un altro campo della stessa schermata. Un campo risultato nascosto viene\r\n <strong>azzerato</strong> e non viene validato.\r\n </p>\r\n <fb-condition-editor\r\n [holder]=\"visibilityRule()\"\r\n title=\"Mostra il campo quando\"\r\n [allowFormula]=\"false\"\r\n [preferredRoots]=\"preferredRoots()\"\r\n [issuePath]=\"'fields[' + (selectedField()!.name || '') + '].visibilityRule'\"\r\n (changed)=\"onVisibilityChanged($event)\"\r\n />\r\n </fieldset>\r\n }\r\n </aside>\r\n</div>\r\n\r\n<fb-connector-editor\r\n [nodeName]=\"name()\"\r\n [node]=\"node()\"\r\n [outlets]=\"outlets()\"\r\n (connectorChanged)=\"onConnectorChanged($event)\"\r\n/>\r\n\r\n<!--\r\n ============================================================== l'anteprima\r\n Due template ricorsivi. `listTpl` disegna i figli di un contenitore, e nient'altro: `data-item`\r\n marca cio' che conta come fratello nel calcolo del punto di rilascio, e il segnaposto del\r\n contenitore vuoto ne resta fuori. La barra di inserimento non e' qui \u2014 sta sopra l'anteprima,\r\n vedi il velo nel frame.\r\n-->\r\n<ng-template #listTpl let-fields let-parent=\"parent\" let-axis=\"axis\">\r\n @for (child of fields; track $index) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"fieldTpl\"\r\n [ngTemplateOutletContext]=\"{ $implicit: child, path: childPath(parent, $index), axis: axis }\"\r\n />\r\n }\r\n @if (!fields.length) {\r\n <p class=\"fb-scr__drop-hint\">Trascina qui un componente</p>\r\n }\r\n</ng-template>\r\n\r\n<ng-template #fieldTpl let-field let-path=\"path\" let-axis=\"axis\">\r\n <div\r\n class=\"fb-scr__item\"\r\n data-item=\"\"\r\n cdkDrag\r\n [style.grid-column]=\"'span ' + widthOf(field)\"\r\n [class.fb-scr__item--selected]=\"isSelected(path)\"\r\n [class.fb-scr__item--dragging]=\"isDragging(path)\"\r\n [class.fb-scr__item--container]=\"holdsFields(field)\"\r\n (cdkDragStarted)=\"onFieldDragStarted(path)\"\r\n (cdkDragMoved)=\"onFieldDragMoved($event)\"\r\n (cdkDragEnded)=\"onFieldDragEnded($event, path)\"\r\n (click)=\"select(path); $event.stopPropagation()\"\r\n >\r\n <span class=\"fb-scr__grip\" cdkDragHandle title=\"Trascina per spostare\" aria-hidden=\"true\">\u283F</span>\r\n @if (field.visibilityRule?.conditions?.length) {\r\n <span class=\"fb-scr__flag\" title=\"Ha una regola di visibilita\u2019\">\u25D0</span>\r\n }\r\n\r\n @switch (field.fieldType) {\r\n @case ('RegionContainer') {\r\n <div class=\"fb-scr__section\">\r\n @if (field.regionContainerType !== 'SectionWithoutHeader') {\r\n <header class=\"fb-scr__section-head\">{{ field.fieldText || field.name }}</header>\r\n }\r\n <div class=\"fb-scr__cols\" [attr.data-drop]=\"dropId(path)\" data-axis=\"row\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"listTpl\"\r\n [ngTemplateOutletContext]=\"{ $implicit: field.fields || [], parent: path, axis: 'row' }\"\r\n />\r\n </div>\r\n </div>\r\n }\r\n @case ('Region') {\r\n <div class=\"fb-scr__region\">\r\n <span class=\"fb-scr__region-tag\">{{ field.name }} \u00B7 {{ widthOf(field) }}/12</span>\r\n <div class=\"fb-scr__region-body\" [attr.data-drop]=\"dropId(path)\" data-axis=\"column\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"listTpl\"\r\n [ngTemplateOutletContext]=\"{ $implicit: field.fields || [], parent: path, axis: 'column' }\"\r\n />\r\n </div>\r\n </div>\r\n }\r\n @case ('RepeatingSection') {\r\n <!--\r\n \u00A75.2 \u2014 si disegna **una** riga, ed e' il modello: a runtime la schermata ne rende una copia\r\n per elemento della collection. Disegnarne due sarebbe finto \u2014 quante siano lo decide chi\r\n compila \u2014 e disegnarla senza cornice farebbe credere che i campi siano della schermata.\r\n -->\r\n <div class=\"fb-scr__repeat\">\r\n <header class=\"fb-scr__repeat-head\">\r\n <span>{{ field.fieldText || field.name }}</span>\r\n <span class=\"fb-scr__repeat-meta\">\r\n {{ field.collectionReference || 'collection non indicata' }}\r\n @if (field.minInstances !== undefined || field.maxInstances !== undefined) {\r\n \u00B7 {{ field.minInstances ?? 0 }}\u2013{{ field.maxInstances ?? '\u221E' }}\r\n }\r\n </span>\r\n </header>\r\n <div class=\"fb-scr__repeat-row\">\r\n <span class=\"fb-scr__repeat-tag\">\r\n {{ field.itemLabel || 'Riga 1' }}\r\n @if (field.allowRemove !== false) {\r\n <span class=\"fb-scr__repeat-x\" aria-hidden=\"true\">\u00D7</span>\r\n }\r\n </span>\r\n <div class=\"fb-scr__repeat-body\" [attr.data-drop]=\"dropId(path)\" data-axis=\"column\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"listTpl\"\r\n [ngTemplateOutletContext]=\"{ $implicit: field.fields || [], parent: path, axis: 'column' }\"\r\n />\r\n </div>\r\n </div>\r\n @if (field.allowAdd !== false) {\r\n <span class=\"fb-scr__repeat-add\">+ {{ field.addButtonLabel || 'Aggiungi' }}</span>\r\n }\r\n </div>\r\n }\r\n @case ('DisplayText') {\r\n <p class=\"fb-scr__display\">{{ field.fieldText || '(testo vuoto)' }}</p>\r\n }\r\n @case ('LargeTextArea') {\r\n <label class=\"fb-scr__label\">\r\n {{ captionOf(field) }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__control fb-scr__control--area\">{{ placeholderOf(field) }}</div>\r\n }\r\n @case ('PasswordField') {\r\n <label class=\"fb-scr__label\">\r\n {{ captionOf(field) }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__control\">\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022</div>\r\n }\r\n @case ('DropdownBox') {\r\n <label class=\"fb-scr__label\">\r\n {{ captionOf(field) }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__control fb-scr__control--select\">\r\n <span>{{ field.defaultSelectedChoiceReference || choiceLabelsOf(field)[0] || '\u2014 scegli \u2014' }}</span>\r\n <span aria-hidden=\"true\">\u25BE</span>\r\n </div>\r\n }\r\n @case ('MultiSelectPicklist') {\r\n <label class=\"fb-scr__label\">\r\n {{ captionOf(field) }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__control fb-scr__control--select\">\r\n <span>{{ choiceLabelsOf(field).join(', ') || '\u2014 scegli \u2014' }}</span>\r\n <span aria-hidden=\"true\">\u2261</span>\r\n </div>\r\n }\r\n @case ('RadioButtons') {\r\n <label class=\"fb-scr__label\">\r\n {{ captionOf(field) }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__options\">\r\n @for (option of choiceLabelsOf(field); track $index) {\r\n <span class=\"fb-scr__option\">\u25EF {{ option }}</span>\r\n }\r\n @if (!choiceLabelsOf(field).length) {\r\n <span class=\"fb-scr__option fb-scr__option--missing\">Nessuna opzione</span>\r\n }\r\n </div>\r\n }\r\n @case ('MultiSelectCheckboxes') {\r\n <label class=\"fb-scr__label\">\r\n {{ captionOf(field) }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__options\">\r\n @for (option of choiceLabelsOf(field); track $index) {\r\n <span class=\"fb-scr__option\">\u2610 {{ option }}</span>\r\n }\r\n @if (!choiceLabelsOf(field).length) {\r\n <span class=\"fb-scr__option fb-scr__option--missing\">Nessuna opzione</span>\r\n }\r\n </div>\r\n }\r\n @case ('ComponentInstance') {\r\n <!--\r\n L\u2019etichetta si disegna se c\u2019e\u2019: il runtime la consegna in `label` come per gli altri\r\n campi (\u00A75.2). Nessun asterisco: un componente non raccoglie un valore, quindi\r\n \u00ABobbligatorio\u00BB non ha niente su cui applicarsi.\r\n -->\r\n @if (field.fieldText) {\r\n <label class=\"fb-scr__label\">{{ field.fieldText }}</label>\r\n }\r\n <div class=\"fb-scr__component\">\r\n <span aria-hidden=\"true\">\u2B21</span>\r\n {{ field.extensionName || 'Componente non indicato' }}\r\n </div>\r\n }\r\n @case ('ObjectProvided') {\r\n <label class=\"fb-scr__label\">\r\n {{ field.fieldText || field.objectFieldReference || field.name }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__control\">{{ placeholderOf(field) }}</div>\r\n <span class=\"fb-scr__tag\">{{ field.objectFieldReference || 'campo non indicato' }}</span>\r\n }\r\n @default {\r\n <label class=\"fb-scr__label\">\r\n {{ captionOf(field) }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__control\">{{ placeholderOf(field) }}</div>\r\n @if (isUnknownType(field)) {\r\n <span class=\"fb-scr__tag fb-scr__tag--warn\">{{ field.fieldType }}: tipo non nel dizionario</span>\r\n }\r\n }\r\n }\r\n <!--\r\n Il formato sta **fuori** dal @switch perche' non e' del tipo di campo: e' di cio' che il campo\r\n raccoglie, e va visto anche dove non si applica \u2014 e\u2019 l\u00EC che si riconosce un\r\n SCREEN_FIELD_FORMAT_NOT_APPLICABLE senza aprire le proprieta\u2019.\r\n -->\r\n @if (field.format) {\r\n <span class=\"fb-scr__tag\">{{ field.format }}</span>\r\n }\r\n </div>\r\n</ng-template>\r\n", styles: [".fb-scr{display:grid;grid-template-columns:220px minmax(0,1fr) 340px;gap:12px;align-items:start;margin-bottom:14px}@media(max-width:1100px){.fb-scr{grid-template-columns:minmax(0,1fr)}}.fb-scr__side,.fb-scr__props{min-width:0;padding:10px;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius, 10px);background:var(--fb-surface-alt, #f7f8fa)}.fb-scr__tabs{display:flex;gap:4px;margin-bottom:8px}.fb-scr__tab{flex:1;padding:5px 8px;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-xs, 6px);background:var(--fb-surface, #fff);color:var(--fb-text-muted, #6b7086);font:inherit;font-size:11px;font-weight:600;cursor:pointer}.fb-scr__tab--active{border-color:var(--fb-accent, #4f6ef7);background:color-mix(in srgb,var(--fb-accent, #4f6ef7) 10%,transparent);color:var(--fb-accent-strong, #3d59e0)}.fb-scr__side-hint,.fb-scr__side-empty{margin:6px 0;font-size:11px;color:var(--fb-text-muted, #6b7086)}.fb-scr__palette{display:flex;flex-direction:column;gap:4px;margin:8px 0 0;padding:0;max-height:320px;overflow-y:auto;overscroll-behavior:contain;list-style:none}.fb-scr__chip{display:flex;align-items:center;gap:8px;width:100%;padding:6px 8px;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-xs, 6px);background:var(--fb-surface, #fff);color:var(--fb-text, #1a1c23);font:inherit;font-size:12px;text-align:left;cursor:grab}.fb-scr__chip:hover{border-color:var(--fb-accent, #4f6ef7)}.fb-scr__chip-icon{flex:none;width:20px;text-align:center;color:var(--fb-text-muted, #6b7086)}.fb-scr__chip-label{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-scr__chip-label small{margin-left:4px;color:var(--fb-text-subtle, #98a2b3);font-size:10px}.fb-scr__canvas{min-width:0;padding:14px;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius, 10px);background:var(--fb-canvas-bg, #f4f5f7);max-height:60vh;overflow-y:auto}.fb-scr__frame{position:relative;display:flex;flex-direction:column;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-sm, 8px);background:var(--fb-surface, #fff);box-shadow:var(--fb-shadow-sm, 0 1px 2px rgb(16 24 40 / 6%));overflow:hidden}.fb-scr__frame-head{display:flex;align-items:center;gap:6px;padding:8px 12px;border-bottom:1px solid var(--fb-border-subtle, #eef0f4);font-size:13px;font-weight:600}.fb-scr__help{display:inline-flex;align-items:center;justify-content:center;width:15px;height:15px;border-radius:50%;background:var(--fb-surface-sunken, #eef0f4);color:var(--fb-text-muted, #6b7086);font-size:10px}.fb-scr__body,.fb-scr__region-body{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));align-content:start;gap:8px 0;padding:12px;min-height:90px}.fb-scr__region-body{padding:8px;min-height:60px}.fb-scr__cols{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));align-items:stretch;gap:0;padding:8px;min-height:60px}.fb-scr__cols>.fb-scr__item{display:flex;align-self:stretch}.fb-scr__cols>.fb-scr__item>.fb-scr__region{flex:1;min-width:0}.fb-scr__drop-hint{grid-column:1 / -1;margin:0;padding:10px;border:1px dashed var(--fb-border-strong, #cfd4de);border-radius:var(--fb-radius-xs, 6px);color:var(--fb-text-subtle, #98a2b3);font-size:11px;text-align:center}.fb-scr__marker{position:absolute;z-index:2;border-radius:2px;background:var(--fb-accent, #4f6ef7);pointer-events:none}.fb-scr__item{position:relative;box-sizing:border-box;padding:6px 8px;border:1px solid transparent;border-radius:var(--fb-radius-xs, 6px);cursor:pointer}.fb-scr__item:hover{border-color:var(--fb-border-strong, #cfd4de);background:color-mix(in srgb,var(--fb-accent, #4f6ef7) 4%,transparent)}.fb-scr__item--selected{border-color:var(--fb-accent, #4f6ef7);box-shadow:inset 0 0 0 1px var(--fb-accent, #4f6ef7)}.fb-scr__item--dragging{opacity:.45;pointer-events:none}.fb-scr__grip{position:absolute;top:2px;left:-2px;padding:0 3px;color:var(--fb-text-subtle, #98a2b3);font-size:11px;line-height:1;opacity:0;cursor:grab}.fb-scr__item:hover>.fb-scr__grip,.fb-scr__item--selected>.fb-scr__grip{opacity:1}.fb-scr__flag{position:absolute;top:2px;right:4px;color:var(--fb-text-muted, #6b7086);font-size:11px}.fb-scr__label{display:block;margin-bottom:3px;font-size:11px;font-weight:600;color:var(--fb-text, #1a1c23)}.fb-scr__req{color:var(--fb-error, #dc2626)}.fb-scr__control{display:flex;align-items:center;justify-content:space-between;gap:6px;min-height:26px;padding:4px 8px;border:1px solid var(--fb-border-strong, #cfd4de);border-radius:var(--fb-radius-xs, 6px);background:var(--fb-surface, #fff);color:var(--fb-text-subtle, #98a2b3);font-size:12px;pointer-events:none}.fb-scr__control--area{min-height:54px;align-items:flex-start}.fb-scr__display{margin:0;font-size:12px;color:var(--fb-text, #1a1c23)}.fb-scr__options{display:flex;flex-direction:column;gap:2px;pointer-events:none}.fb-scr__option{font-size:12px;color:var(--fb-text-muted, #6b7086)}.fb-scr__option--missing{color:var(--fb-warning, #b7791f)}.fb-scr__component{display:flex;align-items:center;gap:6px;padding:10px;border:1px dashed var(--fb-accent, #4f6ef7);border-radius:var(--fb-radius-xs, 6px);background:color-mix(in srgb,var(--fb-accent, #4f6ef7) 5%,transparent);font-size:12px;color:var(--fb-text-muted, #6b7086)}.fb-scr__tag{display:inline-block;margin-top:3px;font-size:10px;color:var(--fb-text-subtle, #98a2b3)}.fb-scr__tag--warn{color:var(--fb-warning, #b7791f)}.fb-scr__section{border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-xs, 6px);background:var(--fb-surface-alt, #f7f8fa)}.fb-scr__section-head{padding:5px 10px;border-bottom:1px solid var(--fb-border-subtle, #eef0f4);font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--fb-text-muted, #6b7086)}.fb-scr__region{border:1px dashed var(--fb-border-strong, #cfd4de);border-radius:var(--fb-radius-xs, 6px);background:var(--fb-surface, #fff)}.fb-scr__region-tag{display:block;padding:3px 6px 0;font-size:10px;color:var(--fb-text-subtle, #98a2b3)}.fb-scr__repeat{border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-xs, 6px);background:var(--fb-surface-alt, #f7f8fa)}.fb-scr__repeat-head{display:flex;align-items:baseline;gap:8px;padding:5px 10px;border-bottom:1px solid var(--fb-border-subtle, #eef0f4);font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--fb-text-muted, #6b7086)}.fb-scr__repeat-meta{margin-left:auto;font-size:10px;font-weight:400;text-transform:none;letter-spacing:0;color:var(--fb-text-subtle, #98a2b3)}.fb-scr__repeat-row{margin:8px;border:1px dashed var(--fb-border-strong, #cfd4de);border-radius:var(--fb-radius-xs, 6px);background:var(--fb-surface, #fff)}.fb-scr__repeat-tag{display:flex;align-items:center;gap:6px;padding:3px 6px 0;font-size:10px;color:var(--fb-text-subtle, #98a2b3)}.fb-scr__repeat-x{margin-left:auto}.fb-scr__repeat-body{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));align-content:start;gap:8px 0;padding:8px;min-height:60px}.fb-scr__repeat-add{display:inline-block;margin:0 8px 8px;padding:3px 10px;border:1px dashed var(--fb-border-strong, #cfd4de);border-radius:var(--fb-radius-xs, 6px);font-size:11px;color:var(--fb-text-muted, #6b7086)}.fb-scr__frame-foot{display:flex;align-items:center;gap:6px;padding:8px 12px;border-top:1px solid var(--fb-border-subtle, #eef0f4);background:var(--fb-surface-alt, #f7f8fa)}.fb-scr__spacer{flex:1}.fb-scr__btn{padding:3px 10px;border:1px solid var(--fb-border-strong, #cfd4de);border-radius:var(--fb-radius-xs, 6px);font-size:11px;color:var(--fb-text-muted, #6b7086)}.fb-scr__btn--primary{border-color:var(--fb-accent, #4f6ef7);background:var(--fb-accent, #4f6ef7);color:var(--fb-accent-contrast, #fff)}.fb-scr__canvas-note{margin-top:10px}.fb-scr__props{background:var(--fb-surface, #fff)}.fb-scr__props-head{display:flex;align-items:center;gap:8px;margin-bottom:8px}.fb-scr__props-title{flex:1;min-width:0;margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--fb-text-muted, #6b7086)}.fb-scr__props-actions{display:flex;flex-wrap:wrap;gap:3px}.fb-btn--sm{padding:3px 7px;font-size:11px}.fb-scr__width{display:flex;align-items:center;gap:8px}.fb-scr__width input[type=range]{flex:1;min-width:0}.fb-scr__width-value{font-size:11px;color:var(--fb-text-muted, #6b7086)}\n"] }]
13548
13884
  }], ctorParameters: () => [], propDecorators: { initialSelection: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialSelection", required: false }] }], composer: [{ type: i0.ViewChild, args: ['composer', { isSignal: true }] }] } });
13549
13885
 
13550
13886
  /**
@@ -13999,8 +14335,25 @@ class RecordLookupInspectorComponent extends NodeInspectorBase {
13999
14335
  * ordinamento, campi di output) li carica il picker che li mostra.
14000
14336
  */
14001
14337
  allFields = signal([], ...(ngDevMode ? [{ debugName: "allFields" }] : []));
14338
+ /**
14339
+ * La modalita' scelta col radio, finche' il documento non la rende evidente da se'.
14340
+ *
14341
+ * Vale solo per le due modalita' che possono restare senza dati: «in una variabile» appena
14342
+ * scelta non ha ancora la variabile, e «campo per campo» resta senza righe quando si toglie
14343
+ * l'ultima. Dedotte dal solo documento ricadevano su «scarta», e il radio tornava indietro
14344
+ * prima che si potesse scegliere la variabile. Scrivere `outputReference: ''` per
14345
+ * ricordarsene e' una chiave vuota nel documento (§2), e non bastava nemmeno: e' falsa.
14346
+ * Automatico e scarta il documento li esprime per intero, quindi qui non entrano — un
14347
+ * annulla deve poterli riportare indietro.
14348
+ */
14349
+ pendingOutputMode = signal(null, ...(ngDevMode ? [{ debugName: "pendingOutputMode" }] : []));
14002
14350
  constructor() {
14003
14351
  super();
14352
+ // Cambiare elemento azzera la scelta locale: e' di questo node, non del prossimo.
14353
+ effect(() => {
14354
+ this.name();
14355
+ this.pendingOutputMode.set(null);
14356
+ });
14004
14357
  effect(() => {
14005
14358
  const object = this.lookup().object;
14006
14359
  if (!object) {
@@ -14015,7 +14368,7 @@ class RecordLookupInspectorComponent extends NodeInspectorBase {
14015
14368
  }
14016
14369
  fieldOptions = computed(() => this.allFields(), ...(ngDevMode ? [{ debugName: "fieldOptions" }] : []));
14017
14370
  queriedFields = computed(() => this.lookup().queriedFields ?? [], ...(ngDevMode ? [{ debugName: "queriedFields" }] : []));
14018
- /** La modalita' di output dedotta dai campi valorizzati. */
14371
+ /** La modalita' di output dedotta dai campi valorizzati, poi dalla scelta ancora vuota. */
14019
14372
  outputMode = computed(() => {
14020
14373
  const lookup = this.lookup();
14021
14374
  if (lookup.storeOutputAutomatically) {
@@ -14027,7 +14380,7 @@ class RecordLookupInspectorComponent extends NodeInspectorBase {
14027
14380
  if (lookup.outputReference) {
14028
14381
  return 'variable';
14029
14382
  }
14030
- return 'discard';
14383
+ return this.pendingOutputMode() ?? 'discard';
14031
14384
  }, ...(ngDevMode ? [{ debugName: "outputMode" }] : []));
14032
14385
  /** Entrambi dichiarati: capita su documenti arrivati da altri editor. */
14033
14386
  hasOutputConflict = computed(() => {
@@ -14096,6 +14449,7 @@ class RecordLookupInspectorComponent extends NodeInspectorBase {
14096
14449
  }
14097
14450
  /** Il passaggio fra modalita' azzera i campi delle altre: il conflitto non e' scrivibile. */
14098
14451
  setOutputMode(mode) {
14452
+ this.pendingOutputMode.set(mode === 'variable' || mode === 'assignments' ? mode : null);
14099
14453
  this.patch((node) => {
14100
14454
  const lookup = node;
14101
14455
  delete lookup.storeOutputAutomatically;
@@ -14105,18 +14459,18 @@ class RecordLookupInspectorComponent extends NodeInspectorBase {
14105
14459
  case 'automatic':
14106
14460
  lookup.storeOutputAutomatically = true;
14107
14461
  break;
14108
- case 'variable':
14109
- lookup.outputReference = '';
14110
- break;
14111
14462
  case 'assignments':
14112
14463
  lookup.outputAssignments = [{}];
14113
14464
  break;
14465
+ case 'variable':
14114
14466
  case 'discard':
14115
14467
  break;
14116
14468
  }
14117
14469
  });
14118
14470
  }
14119
14471
  setOutputReference(reference) {
14472
+ // Svuotare il picker non e' scegliere «scarta»: il picker deve restare dov'e'.
14473
+ this.pendingOutputMode.set('variable');
14120
14474
  this.setField('outputReference', reference);
14121
14475
  }
14122
14476
  addOutputAssignment() {
@@ -14127,6 +14481,8 @@ class RecordLookupInspectorComponent extends NodeInspectorBase {
14127
14481
  });
14128
14482
  }
14129
14483
  removeOutputAssignment(index) {
14484
+ // Togliere l'ultima riga lascia la modalita' com'e', con «Aggiungi campo» a portata.
14485
+ this.pendingOutputMode.set('assignments');
14130
14486
  this.patch((node) => {
14131
14487
  const lookup = node;
14132
14488
  lookup.outputAssignments?.splice(index, 1);
@@ -14157,7 +14513,7 @@ class RecordLookupInspectorComponent extends NodeInspectorBase {
14157
14513
  /** `relatedRecords` e' modellato ma non tradotto in query: se c'e', si avvisa (§5.7). */
14158
14514
  hasRelatedRecords = computed(() => (this.lookup().relatedRecords?.length ?? 0) > 0, ...(ngDevMode ? [{ debugName: "hasRelatedRecords" }] : []));
14159
14515
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.32", ngImport: i0, type: RecordLookupInspectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
14160
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.32", type: RecordLookupInspectorComponent, isStandalone: true, selector: "fb-record-lookup-inspector", usesInheritance: true, ngImport: i0, template: "<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]=\"lookup().object\"\r\n placeholder=\"Scrivi o scegli un oggetto\"\r\n (valueChange)=\"setObject($event ?? '')\"\r\n />\r\n</div>\r\n\r\n<fb-record-filter-editor\r\n [holder]=\"lookup()\"\r\n [object]=\"lookup().object\"\r\n title=\"Quali record leggere\"\r\n usage=\"filterable\"\r\n [supportsLogic]=\"true\"\r\n [supportsFormula]=\"true\"\r\n emptyWarning=\"Senza filtri legge tutti i record dell\u2019oggetto.\"\r\n (changed)=\"onFiltersChanged($event)\"\r\n/>\r\n\r\n<fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Quanti e in che ordine</legend>\r\n\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"lookup().getFirstRecordOnly === true\"\r\n (change)=\"setFirstOnly($any($event.target).checked)\"\r\n />\r\n Solo il primo record\r\n </label>\r\n <p class=\"fb-field__hint\">\r\n @if (returnsCollection()) {\r\n Il risultato e\u2019 una <strong>collection</strong>: puo\u2019 essere iterata da un Loop.\r\n } @else {\r\n Il risultato e\u2019 un <strong>record singolo</strong>: non e\u2019 iterabile da un Loop.\r\n }\r\n </p>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Ordina per</label>\r\n <fb-field-picker\r\n [value]=\"lookup().sortField\"\r\n [object]=\"lookup().object\"\r\n usage=\"sortable\"\r\n label=\"Campo di ordinamento\"\r\n placeholder=\"Nessun ordinamento\"\r\n (valueChange)=\"setSortField($event ?? '')\"\r\n />\r\n </div>\r\n\r\n @if (lookup().sortField) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Direzione</label>\r\n <select class=\"fb-select\" [fbValue]=\"lookup().sortOrder || ''\" (change)=\"setSortOrder($any($event.target).value)\">\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 }\r\n\r\n @if (returnsCollection()) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Numero massimo di record</label>\r\n <input\r\n class=\"fb-input\"\r\n type=\"number\"\r\n min=\"1\"\r\n [value]=\"lookup().limit ?? ''\"\r\n (input)=\"setLimit($any($event.target).value)\"\r\n />\r\n </div>\r\n }\r\n</fieldset>\r\n\r\n@if (fieldOptions().length) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Campi da leggere</legend>\r\n <p class=\"fb-section__note\">Nessuna selezione = tutti i campi disponibili.</p>\r\n <div class=\"fb-fields-grid\">\r\n @for (field of fieldOptions(); track field.name) {\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"isQueried(field.name)\"\r\n (change)=\"toggleQueriedField(field.name, $any($event.target).checked)\"\r\n />\r\n {{ field.label || field.name }}\r\n </label>\r\n }\r\n </div>\r\n </fieldset>\r\n}\r\n\r\n<fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Dove finisce il risultato</legend>\r\n\r\n @if (hasOutputConflict()) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n Sono dichiarati insieme l\u2019output automatico e una destinazione esplicita: e\u2019 un conflitto\r\n (OUTPUT_CONFIGURATION_CONFLICT). Scegli una sola modalita\u2019 qui sotto.\r\n </p>\r\n }\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"radio\"\r\n name=\"output-mode\"\r\n [checked]=\"outputMode() === 'automatic'\"\r\n (change)=\"setOutputMode('automatic')\"\r\n />\r\n Output automatico <em>(consigliato)</em>\r\n </label>\r\n @if (outputMode() === 'automatic') {\r\n <p class=\"fb-field__hint\">\r\n Il risultato si referenzia con il nome dell\u2019elemento: <code>{{ name() }}</code>,\r\n <code>{{ name() }}.Campo</code>. Non serve dichiarare nessuna variabile.\r\n </p>\r\n }\r\n\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"radio\"\r\n name=\"output-mode\"\r\n [checked]=\"outputMode() === 'variable'\"\r\n (change)=\"setOutputMode('variable')\"\r\n />\r\n In una variabile\r\n </label>\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"radio\"\r\n name=\"output-mode\"\r\n [checked]=\"outputMode() === 'assignments'\"\r\n (change)=\"setOutputMode('assignments')\"\r\n />\r\n Campo per campo\r\n </label>\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"radio\"\r\n name=\"output-mode\"\r\n [checked]=\"outputMode() === 'discard'\"\r\n (change)=\"setOutputMode('discard')\"\r\n />\r\n Scarta il risultato\r\n </label>\r\n </div>\r\n\r\n @if (outputMode() === 'variable') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Variabile di destinazione</label>\r\n <fb-reference-picker\r\n [value]=\"lookup().outputReference\"\r\n [writableOnly]=\"true\"\r\n [isCollection]=\"returnsCollection()\"\r\n [objectType]=\"lookup().object\"\r\n placeholder=\"Scegli una variabile\"\r\n (valueChange)=\"setOutputReference($event)\"\r\n />\r\n </div>\r\n }\r\n\r\n @if (outputMode() === 'assignments') {\r\n <div class=\"fb-list\">\r\n @for (assignment of outputAssignments(); 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\"\r\n (click)=\"removeOutputAssignment($index)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Campo del record</label>\r\n <fb-field-picker\r\n [value]=\"assignment.field\"\r\n [object]=\"lookup().object\"\r\n usage=\"any\"\r\n placeholder=\"Scrivi o scegli un campo\"\r\n (valueChange)=\"setOutputAssignmentField($index, $event ?? '')\"\r\n />\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Assegna a</label>\r\n <fb-reference-picker\r\n [value]=\"assignment.assignToReference\"\r\n [writableOnly]=\"true\"\r\n placeholder=\"Scegli una variabile\"\r\n (valueChange)=\"setOutputAssignmentTarget($index, $event)\"\r\n />\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"addOutputAssignment()\">Aggiungi campo</button>\r\n }\r\n\r\n @if (outputMode() === 'discard') {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n L\u2019elemento interroga il database e butta via il risultato (LOOKUP_RESULT_DISCARDED).\r\n </p>\r\n }\r\n</fieldset>\r\n\r\n@if (hasRelatedRecords()) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Questo elemento dichiara <code>relatedRecords</code>: il campo e\u2019 modellato ma non viene tradotto in\r\n query, quindi non ha effetto.\r\n </p>\r\n}\r\n\r\n<fb-connector-editor\r\n [nodeName]=\"name()\"\r\n [node]=\"node()\"\r\n [outlets]=\"outlets()\"\r\n (connectorChanged)=\"onConnectorChanged($event)\"\r\n/>\r\n", dependencies: [{ kind: "component", type: ConnectorEditorComponent, selector: "fb-connector-editor", inputs: ["nodeName", "node", "outlets", "title"], outputs: ["connectorChanged"] }, { kind: "component", type: FieldPickerComponent, selector: "fb-field-picker", inputs: ["value", "object", "usage", "label", "placeholder", "disabled"], outputs: ["valueChange"] }, { kind: "component", type: ObjectPickerComponent, selector: "fb-object-picker", inputs: ["value", "label", "placeholder", "disabled"], 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: ReferencePickerComponent, selector: "fb-reference-picker", inputs: ["value", "label", "placeholder", "disabled", "dataType", "isCollection", "objectType", "writableOnly", "elementsOnly", "extraReferences", "preferredRoots"], outputs: ["valueChange"] }, { kind: "directive", type: SelectValueDirective, selector: "select[fbValue]", inputs: ["fbValue"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
14516
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.32", type: RecordLookupInspectorComponent, isStandalone: true, selector: "fb-record-lookup-inspector", usesInheritance: true, ngImport: i0, template: "<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]=\"lookup().object\"\r\n placeholder=\"Scrivi o scegli un oggetto\"\r\n (valueChange)=\"setObject($event ?? '')\"\r\n />\r\n</div>\r\n\r\n<fb-record-filter-editor\r\n [holder]=\"lookup()\"\r\n [object]=\"lookup().object\"\r\n title=\"Quali record leggere\"\r\n usage=\"filterable\"\r\n [supportsLogic]=\"true\"\r\n [supportsFormula]=\"true\"\r\n emptyWarning=\"Senza filtri legge tutti i record dell\u2019oggetto.\"\r\n (changed)=\"onFiltersChanged($event)\"\r\n/>\r\n\r\n<fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Quanti e in che ordine</legend>\r\n\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"lookup().getFirstRecordOnly === true\"\r\n (change)=\"setFirstOnly($any($event.target).checked)\"\r\n />\r\n Solo il primo record\r\n </label>\r\n <p class=\"fb-field__hint\">\r\n @if (returnsCollection()) {\r\n Il risultato e\u2019 una <strong>collection</strong>: puo\u2019 essere iterata da un Loop.\r\n } @else {\r\n Il risultato e\u2019 un <strong>record singolo</strong>: non e\u2019 iterabile da un Loop.\r\n }\r\n </p>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Ordina per</label>\r\n <fb-field-picker\r\n [value]=\"lookup().sortField\"\r\n [object]=\"lookup().object\"\r\n usage=\"sortable\"\r\n label=\"Campo di ordinamento\"\r\n placeholder=\"Nessun ordinamento\"\r\n (valueChange)=\"setSortField($event ?? '')\"\r\n />\r\n </div>\r\n\r\n @if (lookup().sortField) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Direzione</label>\r\n <select class=\"fb-select\" [fbValue]=\"lookup().sortOrder || ''\" (change)=\"setSortOrder($any($event.target).value)\">\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 }\r\n\r\n @if (returnsCollection()) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Numero massimo di record</label>\r\n <input\r\n class=\"fb-input\"\r\n type=\"number\"\r\n min=\"1\"\r\n [value]=\"lookup().limit ?? ''\"\r\n (input)=\"setLimit($any($event.target).value)\"\r\n />\r\n </div>\r\n }\r\n</fieldset>\r\n\r\n@if (fieldOptions().length) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Campi da leggere</legend>\r\n <p class=\"fb-section__note\">Nessuna selezione = tutti i campi disponibili.</p>\r\n <div class=\"fb-fields-grid\">\r\n @for (field of fieldOptions(); track field.name) {\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"isQueried(field.name)\"\r\n (change)=\"toggleQueriedField(field.name, $any($event.target).checked)\"\r\n />\r\n {{ field.label || field.name }}\r\n </label>\r\n }\r\n </div>\r\n </fieldset>\r\n}\r\n\r\n<fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Dove finisce il risultato</legend>\r\n\r\n @if (hasOutputConflict()) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n Sono dichiarati insieme l\u2019output automatico e una destinazione esplicita: e\u2019 un conflitto\r\n (OUTPUT_CONFIGURATION_CONFLICT). Scegli una sola modalita\u2019 qui sotto.\r\n </p>\r\n }\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"radio\"\r\n name=\"output-mode\"\r\n [checked]=\"outputMode() === 'automatic'\"\r\n (change)=\"setOutputMode('automatic')\"\r\n />\r\n Output automatico <em>(consigliato)</em>\r\n </label>\r\n @if (outputMode() === 'automatic') {\r\n <p class=\"fb-field__hint\">\r\n Il risultato si referenzia con il nome dell\u2019elemento: <code>{{ name() }}</code>,\r\n <code>{{ name() }}.Campo</code>. Non serve dichiarare nessuna variabile.\r\n </p>\r\n }\r\n\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"radio\"\r\n name=\"output-mode\"\r\n [checked]=\"outputMode() === 'variable'\"\r\n (change)=\"setOutputMode('variable')\"\r\n />\r\n In una variabile\r\n </label>\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"radio\"\r\n name=\"output-mode\"\r\n [checked]=\"outputMode() === 'assignments'\"\r\n (change)=\"setOutputMode('assignments')\"\r\n />\r\n Campo per campo\r\n </label>\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"radio\"\r\n name=\"output-mode\"\r\n [checked]=\"outputMode() === 'discard'\"\r\n (change)=\"setOutputMode('discard')\"\r\n />\r\n Scarta il risultato\r\n </label>\r\n </div>\r\n\r\n @if (outputMode() === 'variable') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Variabile di destinazione</label>\r\n <fb-reference-picker\r\n [value]=\"lookup().outputReference\"\r\n [writableOnly]=\"true\"\r\n dataType=\"Object\"\r\n [isCollection]=\"returnsCollection()\"\r\n [objectType]=\"lookup().object\"\r\n placeholder=\"Scegli una variabile\"\r\n (valueChange)=\"setOutputReference($event)\"\r\n />\r\n </div>\r\n }\r\n\r\n @if (outputMode() === 'assignments') {\r\n <div class=\"fb-list\">\r\n @for (assignment of outputAssignments(); 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\"\r\n (click)=\"removeOutputAssignment($index)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Campo del record</label>\r\n <fb-field-picker\r\n [value]=\"assignment.field\"\r\n [object]=\"lookup().object\"\r\n usage=\"any\"\r\n placeholder=\"Scrivi o scegli un campo\"\r\n (valueChange)=\"setOutputAssignmentField($index, $event ?? '')\"\r\n />\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Assegna a</label>\r\n <fb-reference-picker\r\n [value]=\"assignment.assignToReference\"\r\n [writableOnly]=\"true\"\r\n placeholder=\"Scegli una variabile\"\r\n (valueChange)=\"setOutputAssignmentTarget($index, $event)\"\r\n />\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"addOutputAssignment()\">Aggiungi campo</button>\r\n }\r\n\r\n @if (outputMode() === 'discard') {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n L\u2019elemento interroga il database e butta via il risultato (LOOKUP_RESULT_DISCARDED).\r\n </p>\r\n }\r\n</fieldset>\r\n\r\n@if (hasRelatedRecords()) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Questo elemento dichiara <code>relatedRecords</code>: il campo e\u2019 modellato ma non viene tradotto in\r\n query, quindi non ha effetto.\r\n </p>\r\n}\r\n\r\n<fb-connector-editor\r\n [nodeName]=\"name()\"\r\n [node]=\"node()\"\r\n [outlets]=\"outlets()\"\r\n (connectorChanged)=\"onConnectorChanged($event)\"\r\n/>\r\n", dependencies: [{ kind: "component", type: ConnectorEditorComponent, selector: "fb-connector-editor", inputs: ["nodeName", "node", "outlets", "title"], outputs: ["connectorChanged"] }, { kind: "component", type: FieldPickerComponent, selector: "fb-field-picker", inputs: ["value", "object", "usage", "label", "placeholder", "disabled"], outputs: ["valueChange"] }, { kind: "component", type: ObjectPickerComponent, selector: "fb-object-picker", inputs: ["value", "label", "placeholder", "disabled"], 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: ReferencePickerComponent, selector: "fb-reference-picker", inputs: ["value", "label", "placeholder", "disabled", "dataType", "isCollection", "objectType", "writableOnly", "elementsOnly", "extraReferences", "preferredRoots"], outputs: ["valueChange"] }, { kind: "directive", type: SelectValueDirective, selector: "select[fbValue]", inputs: ["fbValue"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
14161
14517
  }
14162
14518
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.32", ngImport: i0, type: RecordLookupInspectorComponent, decorators: [{
14163
14519
  type: Component,
@@ -14168,7 +14524,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.32", ngImpo
14168
14524
  RecordFilterEditorComponent,
14169
14525
  ReferencePickerComponent,
14170
14526
  SelectValueDirective,
14171
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<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]=\"lookup().object\"\r\n placeholder=\"Scrivi o scegli un oggetto\"\r\n (valueChange)=\"setObject($event ?? '')\"\r\n />\r\n</div>\r\n\r\n<fb-record-filter-editor\r\n [holder]=\"lookup()\"\r\n [object]=\"lookup().object\"\r\n title=\"Quali record leggere\"\r\n usage=\"filterable\"\r\n [supportsLogic]=\"true\"\r\n [supportsFormula]=\"true\"\r\n emptyWarning=\"Senza filtri legge tutti i record dell\u2019oggetto.\"\r\n (changed)=\"onFiltersChanged($event)\"\r\n/>\r\n\r\n<fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Quanti e in che ordine</legend>\r\n\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"lookup().getFirstRecordOnly === true\"\r\n (change)=\"setFirstOnly($any($event.target).checked)\"\r\n />\r\n Solo il primo record\r\n </label>\r\n <p class=\"fb-field__hint\">\r\n @if (returnsCollection()) {\r\n Il risultato e\u2019 una <strong>collection</strong>: puo\u2019 essere iterata da un Loop.\r\n } @else {\r\n Il risultato e\u2019 un <strong>record singolo</strong>: non e\u2019 iterabile da un Loop.\r\n }\r\n </p>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Ordina per</label>\r\n <fb-field-picker\r\n [value]=\"lookup().sortField\"\r\n [object]=\"lookup().object\"\r\n usage=\"sortable\"\r\n label=\"Campo di ordinamento\"\r\n placeholder=\"Nessun ordinamento\"\r\n (valueChange)=\"setSortField($event ?? '')\"\r\n />\r\n </div>\r\n\r\n @if (lookup().sortField) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Direzione</label>\r\n <select class=\"fb-select\" [fbValue]=\"lookup().sortOrder || ''\" (change)=\"setSortOrder($any($event.target).value)\">\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 }\r\n\r\n @if (returnsCollection()) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Numero massimo di record</label>\r\n <input\r\n class=\"fb-input\"\r\n type=\"number\"\r\n min=\"1\"\r\n [value]=\"lookup().limit ?? ''\"\r\n (input)=\"setLimit($any($event.target).value)\"\r\n />\r\n </div>\r\n }\r\n</fieldset>\r\n\r\n@if (fieldOptions().length) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Campi da leggere</legend>\r\n <p class=\"fb-section__note\">Nessuna selezione = tutti i campi disponibili.</p>\r\n <div class=\"fb-fields-grid\">\r\n @for (field of fieldOptions(); track field.name) {\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"isQueried(field.name)\"\r\n (change)=\"toggleQueriedField(field.name, $any($event.target).checked)\"\r\n />\r\n {{ field.label || field.name }}\r\n </label>\r\n }\r\n </div>\r\n </fieldset>\r\n}\r\n\r\n<fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Dove finisce il risultato</legend>\r\n\r\n @if (hasOutputConflict()) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n Sono dichiarati insieme l\u2019output automatico e una destinazione esplicita: e\u2019 un conflitto\r\n (OUTPUT_CONFIGURATION_CONFLICT). Scegli una sola modalita\u2019 qui sotto.\r\n </p>\r\n }\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"radio\"\r\n name=\"output-mode\"\r\n [checked]=\"outputMode() === 'automatic'\"\r\n (change)=\"setOutputMode('automatic')\"\r\n />\r\n Output automatico <em>(consigliato)</em>\r\n </label>\r\n @if (outputMode() === 'automatic') {\r\n <p class=\"fb-field__hint\">\r\n Il risultato si referenzia con il nome dell\u2019elemento: <code>{{ name() }}</code>,\r\n <code>{{ name() }}.Campo</code>. Non serve dichiarare nessuna variabile.\r\n </p>\r\n }\r\n\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"radio\"\r\n name=\"output-mode\"\r\n [checked]=\"outputMode() === 'variable'\"\r\n (change)=\"setOutputMode('variable')\"\r\n />\r\n In una variabile\r\n </label>\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"radio\"\r\n name=\"output-mode\"\r\n [checked]=\"outputMode() === 'assignments'\"\r\n (change)=\"setOutputMode('assignments')\"\r\n />\r\n Campo per campo\r\n </label>\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"radio\"\r\n name=\"output-mode\"\r\n [checked]=\"outputMode() === 'discard'\"\r\n (change)=\"setOutputMode('discard')\"\r\n />\r\n Scarta il risultato\r\n </label>\r\n </div>\r\n\r\n @if (outputMode() === 'variable') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Variabile di destinazione</label>\r\n <fb-reference-picker\r\n [value]=\"lookup().outputReference\"\r\n [writableOnly]=\"true\"\r\n [isCollection]=\"returnsCollection()\"\r\n [objectType]=\"lookup().object\"\r\n placeholder=\"Scegli una variabile\"\r\n (valueChange)=\"setOutputReference($event)\"\r\n />\r\n </div>\r\n }\r\n\r\n @if (outputMode() === 'assignments') {\r\n <div class=\"fb-list\">\r\n @for (assignment of outputAssignments(); 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\"\r\n (click)=\"removeOutputAssignment($index)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Campo del record</label>\r\n <fb-field-picker\r\n [value]=\"assignment.field\"\r\n [object]=\"lookup().object\"\r\n usage=\"any\"\r\n placeholder=\"Scrivi o scegli un campo\"\r\n (valueChange)=\"setOutputAssignmentField($index, $event ?? '')\"\r\n />\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Assegna a</label>\r\n <fb-reference-picker\r\n [value]=\"assignment.assignToReference\"\r\n [writableOnly]=\"true\"\r\n placeholder=\"Scegli una variabile\"\r\n (valueChange)=\"setOutputAssignmentTarget($index, $event)\"\r\n />\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"addOutputAssignment()\">Aggiungi campo</button>\r\n }\r\n\r\n @if (outputMode() === 'discard') {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n L\u2019elemento interroga il database e butta via il risultato (LOOKUP_RESULT_DISCARDED).\r\n </p>\r\n }\r\n</fieldset>\r\n\r\n@if (hasRelatedRecords()) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Questo elemento dichiara <code>relatedRecords</code>: il campo e\u2019 modellato ma non viene tradotto in\r\n query, quindi non ha effetto.\r\n </p>\r\n}\r\n\r\n<fb-connector-editor\r\n [nodeName]=\"name()\"\r\n [node]=\"node()\"\r\n [outlets]=\"outlets()\"\r\n (connectorChanged)=\"onConnectorChanged($event)\"\r\n/>\r\n" }]
14527
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<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]=\"lookup().object\"\r\n placeholder=\"Scrivi o scegli un oggetto\"\r\n (valueChange)=\"setObject($event ?? '')\"\r\n />\r\n</div>\r\n\r\n<fb-record-filter-editor\r\n [holder]=\"lookup()\"\r\n [object]=\"lookup().object\"\r\n title=\"Quali record leggere\"\r\n usage=\"filterable\"\r\n [supportsLogic]=\"true\"\r\n [supportsFormula]=\"true\"\r\n emptyWarning=\"Senza filtri legge tutti i record dell\u2019oggetto.\"\r\n (changed)=\"onFiltersChanged($event)\"\r\n/>\r\n\r\n<fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Quanti e in che ordine</legend>\r\n\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"lookup().getFirstRecordOnly === true\"\r\n (change)=\"setFirstOnly($any($event.target).checked)\"\r\n />\r\n Solo il primo record\r\n </label>\r\n <p class=\"fb-field__hint\">\r\n @if (returnsCollection()) {\r\n Il risultato e\u2019 una <strong>collection</strong>: puo\u2019 essere iterata da un Loop.\r\n } @else {\r\n Il risultato e\u2019 un <strong>record singolo</strong>: non e\u2019 iterabile da un Loop.\r\n }\r\n </p>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Ordina per</label>\r\n <fb-field-picker\r\n [value]=\"lookup().sortField\"\r\n [object]=\"lookup().object\"\r\n usage=\"sortable\"\r\n label=\"Campo di ordinamento\"\r\n placeholder=\"Nessun ordinamento\"\r\n (valueChange)=\"setSortField($event ?? '')\"\r\n />\r\n </div>\r\n\r\n @if (lookup().sortField) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Direzione</label>\r\n <select class=\"fb-select\" [fbValue]=\"lookup().sortOrder || ''\" (change)=\"setSortOrder($any($event.target).value)\">\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 }\r\n\r\n @if (returnsCollection()) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Numero massimo di record</label>\r\n <input\r\n class=\"fb-input\"\r\n type=\"number\"\r\n min=\"1\"\r\n [value]=\"lookup().limit ?? ''\"\r\n (input)=\"setLimit($any($event.target).value)\"\r\n />\r\n </div>\r\n }\r\n</fieldset>\r\n\r\n@if (fieldOptions().length) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Campi da leggere</legend>\r\n <p class=\"fb-section__note\">Nessuna selezione = tutti i campi disponibili.</p>\r\n <div class=\"fb-fields-grid\">\r\n @for (field of fieldOptions(); track field.name) {\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"isQueried(field.name)\"\r\n (change)=\"toggleQueriedField(field.name, $any($event.target).checked)\"\r\n />\r\n {{ field.label || field.name }}\r\n </label>\r\n }\r\n </div>\r\n </fieldset>\r\n}\r\n\r\n<fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Dove finisce il risultato</legend>\r\n\r\n @if (hasOutputConflict()) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n Sono dichiarati insieme l\u2019output automatico e una destinazione esplicita: e\u2019 un conflitto\r\n (OUTPUT_CONFIGURATION_CONFLICT). Scegli una sola modalita\u2019 qui sotto.\r\n </p>\r\n }\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"radio\"\r\n name=\"output-mode\"\r\n [checked]=\"outputMode() === 'automatic'\"\r\n (change)=\"setOutputMode('automatic')\"\r\n />\r\n Output automatico <em>(consigliato)</em>\r\n </label>\r\n @if (outputMode() === 'automatic') {\r\n <p class=\"fb-field__hint\">\r\n Il risultato si referenzia con il nome dell\u2019elemento: <code>{{ name() }}</code>,\r\n <code>{{ name() }}.Campo</code>. Non serve dichiarare nessuna variabile.\r\n </p>\r\n }\r\n\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"radio\"\r\n name=\"output-mode\"\r\n [checked]=\"outputMode() === 'variable'\"\r\n (change)=\"setOutputMode('variable')\"\r\n />\r\n In una variabile\r\n </label>\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"radio\"\r\n name=\"output-mode\"\r\n [checked]=\"outputMode() === 'assignments'\"\r\n (change)=\"setOutputMode('assignments')\"\r\n />\r\n Campo per campo\r\n </label>\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"radio\"\r\n name=\"output-mode\"\r\n [checked]=\"outputMode() === 'discard'\"\r\n (change)=\"setOutputMode('discard')\"\r\n />\r\n Scarta il risultato\r\n </label>\r\n </div>\r\n\r\n @if (outputMode() === 'variable') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Variabile di destinazione</label>\r\n <fb-reference-picker\r\n [value]=\"lookup().outputReference\"\r\n [writableOnly]=\"true\"\r\n dataType=\"Object\"\r\n [isCollection]=\"returnsCollection()\"\r\n [objectType]=\"lookup().object\"\r\n placeholder=\"Scegli una variabile\"\r\n (valueChange)=\"setOutputReference($event)\"\r\n />\r\n </div>\r\n }\r\n\r\n @if (outputMode() === 'assignments') {\r\n <div class=\"fb-list\">\r\n @for (assignment of outputAssignments(); 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\"\r\n (click)=\"removeOutputAssignment($index)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Campo del record</label>\r\n <fb-field-picker\r\n [value]=\"assignment.field\"\r\n [object]=\"lookup().object\"\r\n usage=\"any\"\r\n placeholder=\"Scrivi o scegli un campo\"\r\n (valueChange)=\"setOutputAssignmentField($index, $event ?? '')\"\r\n />\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Assegna a</label>\r\n <fb-reference-picker\r\n [value]=\"assignment.assignToReference\"\r\n [writableOnly]=\"true\"\r\n placeholder=\"Scegli una variabile\"\r\n (valueChange)=\"setOutputAssignmentTarget($index, $event)\"\r\n />\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"addOutputAssignment()\">Aggiungi campo</button>\r\n }\r\n\r\n @if (outputMode() === 'discard') {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n L\u2019elemento interroga il database e butta via il risultato (LOOKUP_RESULT_DISCARDED).\r\n </p>\r\n }\r\n</fieldset>\r\n\r\n@if (hasRelatedRecords()) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Questo elemento dichiara <code>relatedRecords</code>: il campo e\u2019 modellato ma non viene tradotto in\r\n query, quindi non ha effetto.\r\n </p>\r\n}\r\n\r\n<fb-connector-editor\r\n [nodeName]=\"name()\"\r\n [node]=\"node()\"\r\n [outlets]=\"outlets()\"\r\n (connectorChanged)=\"onConnectorChanged($event)\"\r\n/>\r\n" }]
14172
14528
  }], ctorParameters: () => [] });
14173
14529
 
14174
14530
  /**
@@ -14224,12 +14580,19 @@ class RecordWriteInspectorComponent extends NodeInspectorBase {
14224
14580
  * campi per oggetto restavano visibili.
14225
14581
  */
14226
14582
  chosenMode = signal(null, ...(ngDevMode ? [{ debugName: "chosenMode" }] : []));
14583
+ /**
14584
+ * Stessa ragione di {@link chosenMode}, sull'upsert: scelto «dall'id standard», il campo
14585
+ * non c'e' ancora, e il vuoto scritto per ricordarsene era falso — il radio tornava su
14586
+ * «id esterno».
14587
+ */
14588
+ chosenUpsertMode = signal(null, ...(ngDevMode ? [{ debugName: "chosenUpsertMode" }] : []));
14227
14589
  constructor() {
14228
14590
  super();
14229
14591
  // Cambiare elemento azzera le scelte locali al form: sono di questo node, non del prossimo.
14230
14592
  effect(() => {
14231
14593
  this.name();
14232
14594
  this.chosenMode.set(null);
14595
+ this.chosenUpsertMode.set(null);
14233
14596
  this.bulkUpdateConfirmed.set(false);
14234
14597
  });
14235
14598
  }
@@ -14268,7 +14631,13 @@ class RecordWriteInspectorComponent extends NodeInspectorBase {
14268
14631
  if (!record.doesUpsert) {
14269
14632
  return 'none';
14270
14633
  }
14271
- return record.upsertStandardIdField ? 'standard' : 'external';
14634
+ if (record.upsertStandardIdField) {
14635
+ return 'standard';
14636
+ }
14637
+ if (record.upsertExternalIdField) {
14638
+ return 'external';
14639
+ }
14640
+ return this.chosenUpsertMode() ?? 'external';
14272
14641
  }, ...(ngDevMode ? [{ debugName: "upsertMode" }] : []));
14273
14642
  /** Entrambi i campi di upsert insieme e' `UPSERT_CONFIGURATION_INVALID`. */
14274
14643
  hasUpsertConflict = computed(() => {
@@ -14330,24 +14699,25 @@ class RecordWriteInspectorComponent extends NodeInspectorBase {
14330
14699
  });
14331
14700
  }
14332
14701
  setUpsertMode(mode) {
14702
+ this.chosenUpsertMode.set(mode);
14333
14703
  this.patch((node) => {
14334
14704
  const record = node;
14335
14705
  record.doesUpsert = true;
14336
14706
  // Mai entrambi: scegliere l'uno cancella l'altro.
14337
14707
  if (mode === 'external') {
14338
14708
  delete record.upsertStandardIdField;
14339
- record.upsertExternalIdField ??= '';
14340
14709
  }
14341
14710
  else {
14342
14711
  delete record.upsertExternalIdField;
14343
- record.upsertStandardIdField ??= '';
14344
14712
  }
14345
14713
  });
14346
14714
  }
14347
14715
  setUpsertExternalField(value) {
14716
+ this.chosenUpsertMode.set('external');
14348
14717
  this.setField('upsertExternalIdField', value);
14349
14718
  }
14350
14719
  setUpsertStandardField(value) {
14720
+ this.chosenUpsertMode.set('standard');
14351
14721
  this.setField('upsertStandardIdField', value);
14352
14722
  }
14353
14723
  setStoreOutputAutomatically(value) {
@@ -17874,8 +18244,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.32", ngImpo
17874
18244
  * Versioni e ciclo di vita — FRONTEND.md §8, §9.
17875
18245
  *
17876
18246
  * Le quattro regole che l'interfaccia deve rispettare, tutte visibili qui:
17877
- * 1. **si modifica una bozza**: su una versione non modificabile il comando e' "Nuova
17878
- * versione", non "Salva" — `isEditable` arriva dal backend e non si ricalcola;
18247
+ * 1. **si modifica tutto tranne la versione attiva**: lì il comando e' "Nuova versione", non
18248
+ * "Salva" — `isEditable` arriva dal backend e non si ricalcola. Una superata si modifica, e
18249
+ * salvata torna bozza: la riga lo dice prima di aprirla, la conferma sta nel builder;
17879
18250
  * 2. una bozza si salva anche con errori, e finisce in `InvalidDraft`;
17880
18251
  * 3. **attivare esige zero errori**;
17881
18252
  * 4. una sola versione attiva: attivarne una rende `Obsolete` la precedente.
@@ -17988,11 +18359,11 @@ class VersionPanelComponent {
17988
18359
  this.closed.emit();
17989
18360
  }
17990
18361
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.32", ngImport: i0, type: VersionPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
17991
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.32", type: VersionPanelComponent, isStandalone: true, selector: "fb-version-panel", outputs: { closed: "closed", versionOpened: "versionOpened", notice: "notice" }, ngImport: i0, template: "<header class=\"fb-ver__header\">\r\n <h2 class=\"fb-ver__title\">Versioni</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<div class=\"fb-ver__body\">\r\n <div class=\"fb-field__row\">\r\n <button type=\"button\" class=\"fb-btn\" [disabled]=\"isBusy()\" (click)=\"createNewVersion()\">\r\n Nuova versione dalla corrente\r\n </button>\r\n @if (hasActiveVersion()) {\r\n <button type=\"button\" class=\"fb-btn fb-btn--danger\" [disabled]=\"isBusy()\" (click)=\"deactivate()\">\r\n Disattiva\r\n </button>\r\n }\r\n </div>\r\n\r\n <ul class=\"fb-ver__list\">\r\n @for (version of versions(); track version.version) {\r\n <li class=\"fb-ver__item\" [class.fb-ver__item--current]=\"isCurrent(version)\">\r\n <div class=\"fb-ver__row\">\r\n <button type=\"button\" class=\"fb-ver__open\" (click)=\"open(version)\">\r\n <span class=\"fb-ver__number\">v{{ version.version }}</span>\r\n <span\r\n class=\"fb-ver__status\"\r\n [class.fb-ver__status--active]=\"isActive(version)\"\r\n [class.fb-ver__status--invalid]=\"version.status === 'InvalidDraft'\"\r\n >\r\n {{ statusLabel(version) }}\r\n </span>\r\n @if (isCurrent(version)) {\r\n <span class=\"fb-ver__badge\">aperta</span>\r\n }\r\n </button>\r\n </div>\r\n\r\n <p class=\"fb-ver__meta\">\r\n @if (version.updatedAt) {\r\n aggiornata {{ version.updatedAt }}\r\n }\r\n @if (version.updatedBy) {\r\n da {{ version.updatedBy }}\r\n }\r\n </p>\r\n\r\n <div class=\"fb-ver__actions\">\r\n @if (version.isEditable) {\r\n <span class=\"fb-ver__hint\">Modificabile</span>\r\n } @else {\r\n <!-- Salvare su una versione Active o Obsolete verrebbe rifiutato con NotEditable. -->\r\n <span class=\"fb-ver__hint\">Sola lettura: per modificarla, creane una nuova</span>\r\n }\r\n\r\n @if (!isActive(version) && isCurrent(version)) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--primary fb-btn--icon\"\r\n [disabled]=\"isBusy() || !canActivate()\"\r\n [title]=\"canActivate() ? 'Attiva questa versione' : 'Ci sono errori da correggere prima di attivare'\"\r\n (click)=\"activate(version)\"\r\n >\r\n Attiva\r\n </button>\r\n }\r\n\r\n @if (!isActive(version)) {\r\n @if (pendingDelete() === version.version) {\r\n <span class=\"fb-ver__confirm\">\r\n Le esecuzioni sospese nate su questo numero riprenderebbero su una definizione diversa.\r\n <button type=\"button\" class=\"fb-btn fb-btn--danger fb-btn--icon\" (click)=\"confirmDelete(version.version)\">\r\n Elimina comunque\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" (click)=\"cancelDelete()\">\r\n Annulla\r\n </button>\r\n </span>\r\n } @else {\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n [disabled]=\"isBusy()\"\r\n (click)=\"requestDelete(version)\"\r\n >\r\n Elimina\r\n </button>\r\n }\r\n }\r\n </div>\r\n </li>\r\n } @empty {\r\n <li class=\"fb-empty\">Nessuna versione: il flow non e\u2019 ancora stato salvato.</li>\r\n }\r\n </ul>\r\n\r\n <p class=\"fb-ver__note\">\r\n Una sola versione e\u2019 attiva: attivarne una rende superata la precedente, senza doverla disattivare prima.\r\n Le versioni superate costano poco \u2014 lo stato \u00ABSuperata\u00BB esiste per questo.\r\n </p>\r\n</div>\r\n", styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0;background:var(--fb-surface, #fff)}.fb-ver__header{display:flex;align-items:center;justify-content:space-between;padding:10px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-ver__title{margin:0;font-size:14px;font-weight:600;color:var(--fb-text, #1d2939)}.fb-ver__body{flex:1;min-height:0;overflow-y:auto;padding:10px 12px}.fb-ver__list{margin:12px 0 0;padding:0;list-style:none}.fb-ver__item{margin-bottom:6px;padding:8px;border:1px solid var(--fb-border-subtle, #e6e9ee);border-radius:6px;background:var(--fb-surface-alt, #f8f9fb)}.fb-ver__item--current{border-color:var(--fb-accent, #2f6feb);background:color-mix(in srgb,var(--fb-accent, #2f6feb) 5%,transparent)}.fb-ver__open{display:flex;align-items:center;gap:6px;width:100%;padding:0;border:0;background:transparent;color:var(--fb-text, #1d2939);font:inherit;text-align:left;cursor:pointer}.fb-ver__number{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:13px;font-weight:700}.fb-ver__status{padding:1px 6px;border-radius:8px;background:var(--fb-border, #d6dae1);font-size:10px;font-weight:600;color:var(--fb-text, #1d2939)}.fb-ver__status--active{background:color-mix(in srgb,var(--fb-success, #3f8f5f) 18%,transparent);color:var(--fb-success, #3f8f5f)}.fb-ver__status--invalid{background:color-mix(in srgb,var(--fb-warning, #b7791f) 16%,transparent);color:var(--fb-warning, #b7791f)}.fb-ver__badge{font-size:9px;color:var(--fb-accent, #2f6feb);text-transform:uppercase;letter-spacing:.05em}.fb-ver__meta{margin:3px 0 0;font-size:10px;color:var(--fb-text-muted, #667085)}.fb-ver__actions{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-top:6px}.fb-ver__hint{font-size:10px;color:var(--fb-text-subtle, #98a2b3)}.fb-ver__confirm{display:flex;flex-wrap:wrap;align-items:center;gap:4px;padding:5px 7px;border-radius:5px;background:color-mix(in srgb,var(--fb-error, #c9372c) 8%,transparent);font-size:10px;color:var(--fb-error, #c9372c)}.fb-ver__note{margin:12px 0 0;font-size:10px;line-height:1.45;color:var(--fb-text-subtle, #98a2b3)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
18362
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.32", type: VersionPanelComponent, isStandalone: true, selector: "fb-version-panel", outputs: { closed: "closed", versionOpened: "versionOpened", notice: "notice" }, ngImport: i0, template: "<header class=\"fb-ver__header\">\r\n <h2 class=\"fb-ver__title\">Versioni</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<div class=\"fb-ver__body\">\r\n <div class=\"fb-field__row\">\r\n <button type=\"button\" class=\"fb-btn\" [disabled]=\"isBusy()\" (click)=\"createNewVersion()\">\r\n Nuova versione dalla corrente\r\n </button>\r\n @if (hasActiveVersion()) {\r\n <button type=\"button\" class=\"fb-btn fb-btn--danger\" [disabled]=\"isBusy()\" (click)=\"deactivate()\">\r\n Disattiva\r\n </button>\r\n }\r\n </div>\r\n\r\n <ul class=\"fb-ver__list\">\r\n @for (version of versions(); track version.version) {\r\n <li class=\"fb-ver__item\" [class.fb-ver__item--current]=\"isCurrent(version)\">\r\n <div class=\"fb-ver__row\">\r\n <button type=\"button\" class=\"fb-ver__open\" (click)=\"open(version)\">\r\n <span class=\"fb-ver__number\">v{{ version.version }}</span>\r\n <span\r\n class=\"fb-ver__status\"\r\n [class.fb-ver__status--active]=\"isActive(version)\"\r\n [class.fb-ver__status--invalid]=\"version.status === 'InvalidDraft'\"\r\n >\r\n {{ statusLabel(version) }}\r\n </span>\r\n @if (isCurrent(version)) {\r\n <span class=\"fb-ver__badge\">aperta</span>\r\n }\r\n </button>\r\n </div>\r\n\r\n <p class=\"fb-ver__meta\">\r\n @if (version.updatedAt) {\r\n aggiornata {{ version.updatedAt }}\r\n }\r\n @if (version.updatedBy) {\r\n da {{ version.updatedBy }}\r\n }\r\n </p>\r\n\r\n <div class=\"fb-ver__actions\">\r\n @if (!version.isEditable) {\r\n <!-- Salvare sulla versione attiva verrebbe rifiutato con NotEditable (\u00A78, regola 1). -->\r\n <span class=\"fb-ver__hint\">Sola lettura: per modificarla, creane una nuova</span>\r\n } @else if (version.status === 'Obsolete') {\r\n <!--\r\n Modificabile come una bozza, ma non e\u2019 la stessa cosa: salvata torna bozza, e le\r\n esecuzioni sospese nate su di lei riprendono con le modifiche. Detto qui perche\u2019 e\u2019 il\r\n posto in cui si sceglie quale versione aprire.\r\n -->\r\n <span class=\"fb-ver__hint\">Modificabile: salvata torna bozza, e va riattivata</span>\r\n } @else {\r\n <span class=\"fb-ver__hint\">Modificabile</span>\r\n }\r\n\r\n @if (!isActive(version) && isCurrent(version)) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--primary fb-btn--icon\"\r\n [disabled]=\"isBusy() || !canActivate()\"\r\n [title]=\"canActivate() ? 'Attiva questa versione' : 'Ci sono errori da correggere prima di attivare'\"\r\n (click)=\"activate(version)\"\r\n >\r\n Attiva\r\n </button>\r\n }\r\n\r\n @if (!isActive(version)) {\r\n @if (pendingDelete() === version.version) {\r\n <span class=\"fb-ver__confirm\">\r\n Le esecuzioni sospese nate su questo numero riprenderebbero su una definizione diversa.\r\n <button type=\"button\" class=\"fb-btn fb-btn--danger fb-btn--icon\" (click)=\"confirmDelete(version.version)\">\r\n Elimina comunque\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" (click)=\"cancelDelete()\">\r\n Annulla\r\n </button>\r\n </span>\r\n } @else {\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n [disabled]=\"isBusy()\"\r\n (click)=\"requestDelete(version)\"\r\n >\r\n Elimina\r\n </button>\r\n }\r\n }\r\n </div>\r\n </li>\r\n } @empty {\r\n <li class=\"fb-empty\">Nessuna versione: il flow non e\u2019 ancora stato salvato.</li>\r\n }\r\n </ul>\r\n\r\n <p class=\"fb-ver__note\">\r\n Una sola versione e\u2019 attiva: attivarne una rende superata la precedente, senza doverla disattivare prima.\r\n Le versioni superate costano poco \u2014 lo stato \u00ABSuperata\u00BB esiste per questo \u2014 e restano modificabili:\r\n l\u2019unica che non si tocca e\u2019 quella attiva.\r\n </p>\r\n</div>\r\n", styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0;background:var(--fb-surface, #fff)}.fb-ver__header{display:flex;align-items:center;justify-content:space-between;padding:10px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-ver__title{margin:0;font-size:14px;font-weight:600;color:var(--fb-text, #1d2939)}.fb-ver__body{flex:1;min-height:0;overflow-y:auto;padding:10px 12px}.fb-ver__list{margin:12px 0 0;padding:0;list-style:none}.fb-ver__item{margin-bottom:6px;padding:8px;border:1px solid var(--fb-border-subtle, #e6e9ee);border-radius:6px;background:var(--fb-surface-alt, #f8f9fb)}.fb-ver__item--current{border-color:var(--fb-accent, #2f6feb);background:color-mix(in srgb,var(--fb-accent, #2f6feb) 5%,transparent)}.fb-ver__open{display:flex;align-items:center;gap:6px;width:100%;padding:0;border:0;background:transparent;color:var(--fb-text, #1d2939);font:inherit;text-align:left;cursor:pointer}.fb-ver__number{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:13px;font-weight:700}.fb-ver__status{padding:1px 6px;border-radius:8px;background:var(--fb-border, #d6dae1);font-size:10px;font-weight:600;color:var(--fb-text, #1d2939)}.fb-ver__status--active{background:color-mix(in srgb,var(--fb-success, #3f8f5f) 18%,transparent);color:var(--fb-success, #3f8f5f)}.fb-ver__status--invalid{background:color-mix(in srgb,var(--fb-warning, #b7791f) 16%,transparent);color:var(--fb-warning, #b7791f)}.fb-ver__badge{font-size:9px;color:var(--fb-accent, #2f6feb);text-transform:uppercase;letter-spacing:.05em}.fb-ver__meta{margin:3px 0 0;font-size:10px;color:var(--fb-text-muted, #667085)}.fb-ver__actions{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-top:6px}.fb-ver__hint{font-size:10px;color:var(--fb-text-subtle, #98a2b3)}.fb-ver__confirm{display:flex;flex-wrap:wrap;align-items:center;gap:4px;padding:5px 7px;border-radius:5px;background:color-mix(in srgb,var(--fb-error, #c9372c) 8%,transparent);font-size:10px;color:var(--fb-error, #c9372c)}.fb-ver__note{margin:12px 0 0;font-size:10px;line-height:1.45;color:var(--fb-text-subtle, #98a2b3)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
17992
18363
  }
17993
18364
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.32", ngImport: i0, type: VersionPanelComponent, decorators: [{
17994
18365
  type: Component,
17995
- args: [{ selector: 'fb-version-panel', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<header class=\"fb-ver__header\">\r\n <h2 class=\"fb-ver__title\">Versioni</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<div class=\"fb-ver__body\">\r\n <div class=\"fb-field__row\">\r\n <button type=\"button\" class=\"fb-btn\" [disabled]=\"isBusy()\" (click)=\"createNewVersion()\">\r\n Nuova versione dalla corrente\r\n </button>\r\n @if (hasActiveVersion()) {\r\n <button type=\"button\" class=\"fb-btn fb-btn--danger\" [disabled]=\"isBusy()\" (click)=\"deactivate()\">\r\n Disattiva\r\n </button>\r\n }\r\n </div>\r\n\r\n <ul class=\"fb-ver__list\">\r\n @for (version of versions(); track version.version) {\r\n <li class=\"fb-ver__item\" [class.fb-ver__item--current]=\"isCurrent(version)\">\r\n <div class=\"fb-ver__row\">\r\n <button type=\"button\" class=\"fb-ver__open\" (click)=\"open(version)\">\r\n <span class=\"fb-ver__number\">v{{ version.version }}</span>\r\n <span\r\n class=\"fb-ver__status\"\r\n [class.fb-ver__status--active]=\"isActive(version)\"\r\n [class.fb-ver__status--invalid]=\"version.status === 'InvalidDraft'\"\r\n >\r\n {{ statusLabel(version) }}\r\n </span>\r\n @if (isCurrent(version)) {\r\n <span class=\"fb-ver__badge\">aperta</span>\r\n }\r\n </button>\r\n </div>\r\n\r\n <p class=\"fb-ver__meta\">\r\n @if (version.updatedAt) {\r\n aggiornata {{ version.updatedAt }}\r\n }\r\n @if (version.updatedBy) {\r\n da {{ version.updatedBy }}\r\n }\r\n </p>\r\n\r\n <div class=\"fb-ver__actions\">\r\n @if (version.isEditable) {\r\n <span class=\"fb-ver__hint\">Modificabile</span>\r\n } @else {\r\n <!-- Salvare su una versione Active o Obsolete verrebbe rifiutato con NotEditable. -->\r\n <span class=\"fb-ver__hint\">Sola lettura: per modificarla, creane una nuova</span>\r\n }\r\n\r\n @if (!isActive(version) && isCurrent(version)) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--primary fb-btn--icon\"\r\n [disabled]=\"isBusy() || !canActivate()\"\r\n [title]=\"canActivate() ? 'Attiva questa versione' : 'Ci sono errori da correggere prima di attivare'\"\r\n (click)=\"activate(version)\"\r\n >\r\n Attiva\r\n </button>\r\n }\r\n\r\n @if (!isActive(version)) {\r\n @if (pendingDelete() === version.version) {\r\n <span class=\"fb-ver__confirm\">\r\n Le esecuzioni sospese nate su questo numero riprenderebbero su una definizione diversa.\r\n <button type=\"button\" class=\"fb-btn fb-btn--danger fb-btn--icon\" (click)=\"confirmDelete(version.version)\">\r\n Elimina comunque\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" (click)=\"cancelDelete()\">\r\n Annulla\r\n </button>\r\n </span>\r\n } @else {\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n [disabled]=\"isBusy()\"\r\n (click)=\"requestDelete(version)\"\r\n >\r\n Elimina\r\n </button>\r\n }\r\n }\r\n </div>\r\n </li>\r\n } @empty {\r\n <li class=\"fb-empty\">Nessuna versione: il flow non e\u2019 ancora stato salvato.</li>\r\n }\r\n </ul>\r\n\r\n <p class=\"fb-ver__note\">\r\n Una sola versione e\u2019 attiva: attivarne una rende superata la precedente, senza doverla disattivare prima.\r\n Le versioni superate costano poco \u2014 lo stato \u00ABSuperata\u00BB esiste per questo.\r\n </p>\r\n</div>\r\n", styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0;background:var(--fb-surface, #fff)}.fb-ver__header{display:flex;align-items:center;justify-content:space-between;padding:10px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-ver__title{margin:0;font-size:14px;font-weight:600;color:var(--fb-text, #1d2939)}.fb-ver__body{flex:1;min-height:0;overflow-y:auto;padding:10px 12px}.fb-ver__list{margin:12px 0 0;padding:0;list-style:none}.fb-ver__item{margin-bottom:6px;padding:8px;border:1px solid var(--fb-border-subtle, #e6e9ee);border-radius:6px;background:var(--fb-surface-alt, #f8f9fb)}.fb-ver__item--current{border-color:var(--fb-accent, #2f6feb);background:color-mix(in srgb,var(--fb-accent, #2f6feb) 5%,transparent)}.fb-ver__open{display:flex;align-items:center;gap:6px;width:100%;padding:0;border:0;background:transparent;color:var(--fb-text, #1d2939);font:inherit;text-align:left;cursor:pointer}.fb-ver__number{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:13px;font-weight:700}.fb-ver__status{padding:1px 6px;border-radius:8px;background:var(--fb-border, #d6dae1);font-size:10px;font-weight:600;color:var(--fb-text, #1d2939)}.fb-ver__status--active{background:color-mix(in srgb,var(--fb-success, #3f8f5f) 18%,transparent);color:var(--fb-success, #3f8f5f)}.fb-ver__status--invalid{background:color-mix(in srgb,var(--fb-warning, #b7791f) 16%,transparent);color:var(--fb-warning, #b7791f)}.fb-ver__badge{font-size:9px;color:var(--fb-accent, #2f6feb);text-transform:uppercase;letter-spacing:.05em}.fb-ver__meta{margin:3px 0 0;font-size:10px;color:var(--fb-text-muted, #667085)}.fb-ver__actions{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-top:6px}.fb-ver__hint{font-size:10px;color:var(--fb-text-subtle, #98a2b3)}.fb-ver__confirm{display:flex;flex-wrap:wrap;align-items:center;gap:4px;padding:5px 7px;border-radius:5px;background:color-mix(in srgb,var(--fb-error, #c9372c) 8%,transparent);font-size:10px;color:var(--fb-error, #c9372c)}.fb-ver__note{margin:12px 0 0;font-size:10px;line-height:1.45;color:var(--fb-text-subtle, #98a2b3)}\n"] }]
18366
+ args: [{ selector: 'fb-version-panel', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<header class=\"fb-ver__header\">\r\n <h2 class=\"fb-ver__title\">Versioni</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<div class=\"fb-ver__body\">\r\n <div class=\"fb-field__row\">\r\n <button type=\"button\" class=\"fb-btn\" [disabled]=\"isBusy()\" (click)=\"createNewVersion()\">\r\n Nuova versione dalla corrente\r\n </button>\r\n @if (hasActiveVersion()) {\r\n <button type=\"button\" class=\"fb-btn fb-btn--danger\" [disabled]=\"isBusy()\" (click)=\"deactivate()\">\r\n Disattiva\r\n </button>\r\n }\r\n </div>\r\n\r\n <ul class=\"fb-ver__list\">\r\n @for (version of versions(); track version.version) {\r\n <li class=\"fb-ver__item\" [class.fb-ver__item--current]=\"isCurrent(version)\">\r\n <div class=\"fb-ver__row\">\r\n <button type=\"button\" class=\"fb-ver__open\" (click)=\"open(version)\">\r\n <span class=\"fb-ver__number\">v{{ version.version }}</span>\r\n <span\r\n class=\"fb-ver__status\"\r\n [class.fb-ver__status--active]=\"isActive(version)\"\r\n [class.fb-ver__status--invalid]=\"version.status === 'InvalidDraft'\"\r\n >\r\n {{ statusLabel(version) }}\r\n </span>\r\n @if (isCurrent(version)) {\r\n <span class=\"fb-ver__badge\">aperta</span>\r\n }\r\n </button>\r\n </div>\r\n\r\n <p class=\"fb-ver__meta\">\r\n @if (version.updatedAt) {\r\n aggiornata {{ version.updatedAt }}\r\n }\r\n @if (version.updatedBy) {\r\n da {{ version.updatedBy }}\r\n }\r\n </p>\r\n\r\n <div class=\"fb-ver__actions\">\r\n @if (!version.isEditable) {\r\n <!-- Salvare sulla versione attiva verrebbe rifiutato con NotEditable (\u00A78, regola 1). -->\r\n <span class=\"fb-ver__hint\">Sola lettura: per modificarla, creane una nuova</span>\r\n } @else if (version.status === 'Obsolete') {\r\n <!--\r\n Modificabile come una bozza, ma non e\u2019 la stessa cosa: salvata torna bozza, e le\r\n esecuzioni sospese nate su di lei riprendono con le modifiche. Detto qui perche\u2019 e\u2019 il\r\n posto in cui si sceglie quale versione aprire.\r\n -->\r\n <span class=\"fb-ver__hint\">Modificabile: salvata torna bozza, e va riattivata</span>\r\n } @else {\r\n <span class=\"fb-ver__hint\">Modificabile</span>\r\n }\r\n\r\n @if (!isActive(version) && isCurrent(version)) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--primary fb-btn--icon\"\r\n [disabled]=\"isBusy() || !canActivate()\"\r\n [title]=\"canActivate() ? 'Attiva questa versione' : 'Ci sono errori da correggere prima di attivare'\"\r\n (click)=\"activate(version)\"\r\n >\r\n Attiva\r\n </button>\r\n }\r\n\r\n @if (!isActive(version)) {\r\n @if (pendingDelete() === version.version) {\r\n <span class=\"fb-ver__confirm\">\r\n Le esecuzioni sospese nate su questo numero riprenderebbero su una definizione diversa.\r\n <button type=\"button\" class=\"fb-btn fb-btn--danger fb-btn--icon\" (click)=\"confirmDelete(version.version)\">\r\n Elimina comunque\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" (click)=\"cancelDelete()\">\r\n Annulla\r\n </button>\r\n </span>\r\n } @else {\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n [disabled]=\"isBusy()\"\r\n (click)=\"requestDelete(version)\"\r\n >\r\n Elimina\r\n </button>\r\n }\r\n }\r\n </div>\r\n </li>\r\n } @empty {\r\n <li class=\"fb-empty\">Nessuna versione: il flow non e\u2019 ancora stato salvato.</li>\r\n }\r\n </ul>\r\n\r\n <p class=\"fb-ver__note\">\r\n Una sola versione e\u2019 attiva: attivarne una rende superata la precedente, senza doverla disattivare prima.\r\n Le versioni superate costano poco \u2014 lo stato \u00ABSuperata\u00BB esiste per questo \u2014 e restano modificabili:\r\n l\u2019unica che non si tocca e\u2019 quella attiva.\r\n </p>\r\n</div>\r\n", styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0;background:var(--fb-surface, #fff)}.fb-ver__header{display:flex;align-items:center;justify-content:space-between;padding:10px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-ver__title{margin:0;font-size:14px;font-weight:600;color:var(--fb-text, #1d2939)}.fb-ver__body{flex:1;min-height:0;overflow-y:auto;padding:10px 12px}.fb-ver__list{margin:12px 0 0;padding:0;list-style:none}.fb-ver__item{margin-bottom:6px;padding:8px;border:1px solid var(--fb-border-subtle, #e6e9ee);border-radius:6px;background:var(--fb-surface-alt, #f8f9fb)}.fb-ver__item--current{border-color:var(--fb-accent, #2f6feb);background:color-mix(in srgb,var(--fb-accent, #2f6feb) 5%,transparent)}.fb-ver__open{display:flex;align-items:center;gap:6px;width:100%;padding:0;border:0;background:transparent;color:var(--fb-text, #1d2939);font:inherit;text-align:left;cursor:pointer}.fb-ver__number{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:13px;font-weight:700}.fb-ver__status{padding:1px 6px;border-radius:8px;background:var(--fb-border, #d6dae1);font-size:10px;font-weight:600;color:var(--fb-text, #1d2939)}.fb-ver__status--active{background:color-mix(in srgb,var(--fb-success, #3f8f5f) 18%,transparent);color:var(--fb-success, #3f8f5f)}.fb-ver__status--invalid{background:color-mix(in srgb,var(--fb-warning, #b7791f) 16%,transparent);color:var(--fb-warning, #b7791f)}.fb-ver__badge{font-size:9px;color:var(--fb-accent, #2f6feb);text-transform:uppercase;letter-spacing:.05em}.fb-ver__meta{margin:3px 0 0;font-size:10px;color:var(--fb-text-muted, #667085)}.fb-ver__actions{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-top:6px}.fb-ver__hint{font-size:10px;color:var(--fb-text-subtle, #98a2b3)}.fb-ver__confirm{display:flex;flex-wrap:wrap;align-items:center;gap:4px;padding:5px 7px;border-radius:5px;background:color-mix(in srgb,var(--fb-error, #c9372c) 8%,transparent);font-size:10px;color:var(--fb-error, #c9372c)}.fb-ver__note{margin:12px 0 0;font-size:10px;line-height:1.45;color:var(--fb-text-subtle, #98a2b3)}\n"] }]
17996
18367
  }], propDecorators: { closed: [{ type: i0.Output, args: ["closed"] }], versionOpened: [{ type: i0.Output, args: ["versionOpened"] }], notice: [{ type: i0.Output, args: ["notice"] }] } });
17997
18368
 
17998
18369
  /**
@@ -18906,9 +19277,11 @@ class FlowBuilderComponent {
18906
19277
  }
18907
19278
  this.isLoading.set(true);
18908
19279
  this.notice.set(null);
18909
- // Si sta cambiando documento: una dialog aperta guarderebbe un elemento di un altro flow.
19280
+ // Si sta cambiando documento: una dialog aperta guarderebbe un elemento di un altro flow, e una
19281
+ // conferma aperta parlerebbe di una versione che non e' piu' quella in mano.
18910
19282
  this.isDialogOpen.set(false);
18911
19283
  this.editingResource.set(null);
19284
+ this.isObsoleteSaveOpen.set(false);
18912
19285
  try {
18913
19286
  if (flowName) {
18914
19287
  await this.session.open(flowName, version ?? undefined);
@@ -19646,12 +20019,66 @@ class FlowBuilderComponent {
19646
20019
  // -------------------------------------------------------------------------
19647
20020
  // Salvataggio e ciclo di vita
19648
20021
  // -------------------------------------------------------------------------
20022
+ /**
20023
+ * §8, regola 1 — il salvataggio su una versione **superata** aspetta una conferma. Si modifica come
20024
+ * una bozza, ma le esecuzioni sospese nate quando era attiva riprendono sulla definizione nuova, e
20025
+ * se il node su cui erano ferme non c'e' piu' falliscono: e' una domanda a cui il backend non puo'
20026
+ * rispondere al posto di chi salva. La barra resta finche' non si sceglie, come quella del duplicato,
20027
+ * e un secondo «Salva» non vale come conferma: la conferma e' un bottone che dice cosa accadra'.
20028
+ */
20029
+ isObsoleteSaveOpen = signal(false, ...(ngDevMode ? [{ debugName: "isObsoleteSaveOpen" }] : []));
19649
20030
  async save() {
19650
20031
  const definition = this.store.document();
19651
20032
  if (!definition.fullName) {
19652
20033
  this.notice.set({ kind: 'error', message: 'Dai un nome tecnico al flow prima di salvare.' });
19653
20034
  return;
19654
20035
  }
20036
+ if (this.session.isObsoleteVersion()) {
20037
+ this.isObsoleteSaveOpen.set(true);
20038
+ return;
20039
+ }
20040
+ await this.writeCurrentVersion();
20041
+ }
20042
+ /** «Salva comunque»: la superata si sovrascrive e torna bozza (§8). */
20043
+ async confirmObsoleteSave() {
20044
+ this.isObsoleteSaveOpen.set(false);
20045
+ const version = this.session.version();
20046
+ const saved = await this.writeCurrentVersion();
20047
+ if (saved) {
20048
+ // Detto qui perche' e' l'unico momento in cui e' una notizia: la versione non e' piu' una
20049
+ // superata, e non tornera' in produzione finche' qualcuno non la riattiva.
20050
+ const errors = saved.validation?.errorCount ?? 0;
20051
+ this.notice.set({
20052
+ kind: 'info',
20053
+ message: `Versione ${version} salvata: e’ tornata bozza${errors ? `, con ${errors} ${errors === 1 ? 'errore' : 'errori'} da correggere` : ''}. ` +
20054
+ 'Per rimetterla in produzione va riattivata.',
20055
+ });
20056
+ }
20057
+ }
20058
+ /** L'alternativa che non tocca le esecuzioni sospese: la superata resta com'era. */
20059
+ async saveObsoleteAsNewVersion() {
20060
+ this.isObsoleteSaveOpen.set(false);
20061
+ try {
20062
+ const result = await this.session.saveAsNewVersion();
20063
+ this.saved.emit(result);
20064
+ const version = this.session.version();
20065
+ this.notice.set({
20066
+ kind: 'info',
20067
+ message: version != null
20068
+ ? `Le modifiche sono nella nuova versione ${version}; la superata resta com’era.`
20069
+ : 'Le modifiche sono in una nuova versione; la superata resta com’era.',
20070
+ });
20071
+ }
20072
+ catch (error) {
20073
+ this.showError(error);
20074
+ }
20075
+ }
20076
+ cancelObsoleteSave() {
20077
+ this.isObsoleteSaveOpen.set(false);
20078
+ }
20079
+ /** Il salvataggio vero, con la notifica dell'esito; `undefined` se non e' riuscito. */
20080
+ async writeCurrentVersion() {
20081
+ const definition = this.store.document();
19655
20082
  try {
19656
20083
  const result = await this.session.save({ flowName: definition.fullName });
19657
20084
  this.saved.emit(result);
@@ -19668,9 +20095,11 @@ class FlowBuilderComponent {
19668
20095
  ? `${saved}, con ${errors} ${errors === 1 ? 'errore' : 'errori'} da correggere.`
19669
20096
  : `${saved}.`,
19670
20097
  });
20098
+ return result;
19671
20099
  }
19672
20100
  catch (error) {
19673
20101
  this.showError(error);
20102
+ return undefined;
19674
20103
  }
19675
20104
  }
19676
20105
  // ----------------------------------------------------------------- duplica
@@ -19958,6 +20387,7 @@ class FlowBuilderComponent {
19958
20387
  this.isLoading.set(true);
19959
20388
  this.isDialogOpen.set(false);
19960
20389
  this.editingResource.set(null);
20390
+ this.isObsoleteSaveOpen.set(false);
19961
20391
  try {
19962
20392
  await this.session.open(flowName, version);
19963
20393
  this.selectedName.set(START_NODE_NAME);
@@ -20005,7 +20435,7 @@ class FlowBuilderComponent {
20005
20435
  FormulaValidationService,
20006
20436
  FlowEditorSession,
20007
20437
  FlowLayoutService,
20008
- ], 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 \u00A73.4 \u2014 cio\u2019 che si sceglie e\u2019 il **tipo di flow** (`flowTypes`), non il `processType`:\r\n quello si ricava, insieme al `triggerType`. Dove il dizionario non dichiara i tipi si\r\n torna alla vecchia tendina dei soli `processType`: e\u2019 un ripiego, non un difetto.\r\n -->\r\n @if (session.isNew() && flowTypes().length) {\r\n <select\r\n class=\"fb-top__process\"\r\n [fbValue]=\"flowType()?.value || ''\"\r\n aria-label=\"Tipo di flow\"\r\n [disabled]=\"!isEditable()\"\r\n (change)=\"setFlowType($any($event.target).value)\"\r\n >\r\n @if (!flowType()) {\r\n <option value=\"\" disabled>\u2014 tipo di flow \u2014</option>\r\n }\r\n @for (type of flowTypes(); track type.value) {\r\n <option [value]=\"type.value\" [title]=\"type.description || type.label\">{{ type.label }}</option>\r\n }\r\n </select>\r\n } @else 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 {{ flowTypeLabel() }}\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\r\n [processType]=\"document().processType\"\r\n [triggerType]=\"document().start?.[0]?.triggerType\"\r\n (elementPicked)=\"onElementPicked($event)\"\r\n />\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%;max-height:100vh;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", "triggerType"], 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 });
20438
+ ], 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 \u00A73.4 \u2014 cio\u2019 che si sceglie e\u2019 il **tipo di flow** (`flowTypes`), non il `processType`:\r\n quello si ricava, insieme al `triggerType`. Dove il dizionario non dichiara i tipi si\r\n torna alla vecchia tendina dei soli `processType`: e\u2019 un ripiego, non un difetto.\r\n -->\r\n @if (session.isNew() && flowTypes().length) {\r\n <select\r\n class=\"fb-top__process\"\r\n [fbValue]=\"flowType()?.value || ''\"\r\n aria-label=\"Tipo di flow\"\r\n [disabled]=\"!isEditable()\"\r\n (change)=\"setFlowType($any($event.target).value)\"\r\n >\r\n @if (!flowType()) {\r\n <option value=\"\" disabled>\u2014 tipo di flow \u2014</option>\r\n }\r\n @for (type of flowTypes(); track type.value) {\r\n <option [value]=\"type.value\" [title]=\"type.description || type.label\">{{ type.label }}</option>\r\n }\r\n </select>\r\n } @else 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 {{ flowTypeLabel() }}\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 <!-- Sulla versione attiva il comando primario e' \"Nuova versione\" (\u00A78, regola 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 (isObsoleteSaveOpen() && session.isObsoleteVersion()) {\r\n <!--\r\n \u00A78, regola 1 \u2014 una superata si salva, ma il primo salvataggio si conferma: le esecuzioni\r\n sospese nate quando era attiva riprendono con le modifiche. La terza strada lascia la\r\n superata com\u2019era e mette le modifiche in una versione nuova.\r\n -->\r\n <div class=\"fb-banner fb-banner--warn\" role=\"alertdialog\" aria-label=\"Salvataggio su una versione superata\">\r\n <span>\r\n Questa versione e\u2019 superata. Le esecuzioni ancora in corso su questa versione riprenderanno con\r\n le modifiche, e se il punto in cui sono ferme non c\u2019e\u2019 piu\u2019 falliranno. Salvata, torna bozza:\r\n per rimetterla in produzione andra\u2019 riattivata.\r\n </span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary fb-btn--icon\" [disabled]=\"isBusy()\" (click)=\"confirmObsoleteSave()\">\r\n Salva comunque\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--icon\" [disabled]=\"isBusy()\" (click)=\"saveObsoleteAsNewVersion()\">\r\n Salva come nuova versione\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" (click)=\"cancelObsoleteSave()\">Annulla</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 <!-- \u00A78, regola 1: la sola versione che non si modifica e\u2019 quella da cui partono le esecuzioni. -->\r\n <div class=\"fb-banner\">\r\n Questa e\u2019 la versione attiva, ed 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\r\n [processType]=\"document().processType\"\r\n [triggerType]=\"document().start?.[0]?.triggerType\"\r\n (elementPicked)=\"onElementPicked($event)\"\r\n />\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%;max-height:100vh;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", "triggerType"], 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 });
20009
20439
  }
20010
20440
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.32", ngImport: i0, type: FlowBuilderComponent, decorators: [{
20011
20441
  type: Component,
@@ -20031,12 +20461,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.32", ngImpo
20031
20461
  FormulaValidationService,
20032
20462
  FlowEditorSession,
20033
20463
  FlowLayoutService,
20034
- ], 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 \u00A73.4 \u2014 cio\u2019 che si sceglie e\u2019 il **tipo di flow** (`flowTypes`), non il `processType`:\r\n quello si ricava, insieme al `triggerType`. Dove il dizionario non dichiara i tipi si\r\n torna alla vecchia tendina dei soli `processType`: e\u2019 un ripiego, non un difetto.\r\n -->\r\n @if (session.isNew() && flowTypes().length) {\r\n <select\r\n class=\"fb-top__process\"\r\n [fbValue]=\"flowType()?.value || ''\"\r\n aria-label=\"Tipo di flow\"\r\n [disabled]=\"!isEditable()\"\r\n (change)=\"setFlowType($any($event.target).value)\"\r\n >\r\n @if (!flowType()) {\r\n <option value=\"\" disabled>\u2014 tipo di flow \u2014</option>\r\n }\r\n @for (type of flowTypes(); track type.value) {\r\n <option [value]=\"type.value\" [title]=\"type.description || type.label\">{{ type.label }}</option>\r\n }\r\n </select>\r\n } @else 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 {{ flowTypeLabel() }}\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\r\n [processType]=\"document().processType\"\r\n [triggerType]=\"document().start?.[0]?.triggerType\"\r\n (elementPicked)=\"onElementPicked($event)\"\r\n />\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%;max-height:100vh;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"] }]
20464
+ ], 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 \u00A73.4 \u2014 cio\u2019 che si sceglie e\u2019 il **tipo di flow** (`flowTypes`), non il `processType`:\r\n quello si ricava, insieme al `triggerType`. Dove il dizionario non dichiara i tipi si\r\n torna alla vecchia tendina dei soli `processType`: e\u2019 un ripiego, non un difetto.\r\n -->\r\n @if (session.isNew() && flowTypes().length) {\r\n <select\r\n class=\"fb-top__process\"\r\n [fbValue]=\"flowType()?.value || ''\"\r\n aria-label=\"Tipo di flow\"\r\n [disabled]=\"!isEditable()\"\r\n (change)=\"setFlowType($any($event.target).value)\"\r\n >\r\n @if (!flowType()) {\r\n <option value=\"\" disabled>\u2014 tipo di flow \u2014</option>\r\n }\r\n @for (type of flowTypes(); track type.value) {\r\n <option [value]=\"type.value\" [title]=\"type.description || type.label\">{{ type.label }}</option>\r\n }\r\n </select>\r\n } @else 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 {{ flowTypeLabel() }}\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 <!-- Sulla versione attiva il comando primario e' \"Nuova versione\" (\u00A78, regola 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 (isObsoleteSaveOpen() && session.isObsoleteVersion()) {\r\n <!--\r\n \u00A78, regola 1 \u2014 una superata si salva, ma il primo salvataggio si conferma: le esecuzioni\r\n sospese nate quando era attiva riprendono con le modifiche. La terza strada lascia la\r\n superata com\u2019era e mette le modifiche in una versione nuova.\r\n -->\r\n <div class=\"fb-banner fb-banner--warn\" role=\"alertdialog\" aria-label=\"Salvataggio su una versione superata\">\r\n <span>\r\n Questa versione e\u2019 superata. Le esecuzioni ancora in corso su questa versione riprenderanno con\r\n le modifiche, e se il punto in cui sono ferme non c\u2019e\u2019 piu\u2019 falliranno. Salvata, torna bozza:\r\n per rimetterla in produzione andra\u2019 riattivata.\r\n </span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary fb-btn--icon\" [disabled]=\"isBusy()\" (click)=\"confirmObsoleteSave()\">\r\n Salva comunque\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--icon\" [disabled]=\"isBusy()\" (click)=\"saveObsoleteAsNewVersion()\">\r\n Salva come nuova versione\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" (click)=\"cancelObsoleteSave()\">Annulla</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 <!-- \u00A78, regola 1: la sola versione che non si modifica e\u2019 quella da cui partono le esecuzioni. -->\r\n <div class=\"fb-banner\">\r\n Questa e\u2019 la versione attiva, ed 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\r\n [processType]=\"document().processType\"\r\n [triggerType]=\"document().start?.[0]?.triggerType\"\r\n (elementPicked)=\"onElementPicked($event)\"\r\n />\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%;max-height:100vh;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"] }]
20035
20465
  }], 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 }] }] } });
20036
20466
 
20037
20467
  /**
20038
20468
  * Generated bundle index. Do not edit.
20039
20469
  */
20040
20470
 
20041
- export { ConditionEditorComponent, ConnectorEditorComponent, DebugPanelComponent, DynamicScreenInspectorComponent, ENUM_CHOICE_SET_DEFAULT_DISPLAY_FIELD, ENUM_CHOICE_SET_DEFAULT_VALUE_FIELD, ENUM_CHOICE_SET_FIELDS, ElementDialogComponent, ElementInspectorComponent, ElementPaletteComponent, EnumValuePickerComponent, FALLBACK_COLLECTION_BY_TYPE, FALLBACK_TYPE_LABEL, FLOW_BUILDER_HTTP_CONFIG, FLOW_CLIPBOARD_KIND, FLOW_CLIPBOARD_VERSION, FLOW_ELEMENT_ICONS, FLOW_ELEMENT_VARIANT_FIELDS, FLOW_ELEMENT_VARIANT_ICONS, FLOW_ERROR_FALLBACK_MESSAGE, FLOW_ERROR_HTTP_STATUS, FLOW_FOLD_HEIGHT, FLOW_FOLD_WIDTH, FLOW_GROUP_COLORS, FLOW_NAME_PATTERN, FLOW_NODE_COLLECTIONS, FLOW_NODE_HEIGHT, FLOW_NODE_WIDTH, FLOW_REFERENCE_FIELDS, FLOW_RESOURCE_COLLECTIONS, FLOW_VALUE_FIELDS, FLOW_VALUE_LITERAL_FIELDS, FieldAssignmentEditorComponent, FieldPickerComponent, FieldValuePickerComponent, FlowApiError, FlowBuilderApi, FlowBuilderComponent, FlowCanvasComponent, FlowCatalogStore, FlowClipboardService, FlowDictionaryStore, FlowDocumentStore, FlowEditorSession, FlowLayoutService, FlowValidationStore, FormulaEditorComponent, FormulaValidationService, GROUP_HEADER_HEIGHT, GROUP_MIN_HEIGHT, GROUP_MIN_WIDTH, GROUP_PADDING, GroupPanelComponent, HttpFlowBuilderApi, MergeTextEditorComponent, NamePickerComponent, NodeInspectorBase, ORCHESTRATION_CONDITION_OUTPUT, ObjectPickerComponent, OrchestratedStageInspectorComponent, PRIOR_VALUES_OPERATOR, PRIOR_VALUES_SCOPE, ParameterEditorComponent, PasteDialogComponent, ProblemsPanelComponent, RECORD_TRIGGERS, RecordFilterEditorComponent, ReferencePickerComponent, ResourceDialogComponent, ResourceFormComponent, ResourcePanelComponent, RunDialogComponent, SEVERITY_BUCKETS, SEVERITY_ICON, SEVERITY_LABEL, START_NODE_NAME, SelectValueDirective, StartInspectorComponent, StructureMemberPickerComponent, StructurePickerComponent, TYPES_WITH_AUTOMATIC_OUTPUT, TYPE_BY_COLLECTION, UNSUPPORTED_TYPES, ValueEditorComponent, VersionPanelComponent, allFieldsOf, applyPaste, areTypesComparable, boundsAround, buildClipboardPayload, canvasFoldId, canvasGroupId, canvasNodeId, checkConditionLogic, checkFlowName, choiceSetSourceOf, declaredChoiceSetSources, describeActivationError, describeFieldPath, describePathEntry, describePriorValues, duplicateField, elementIcon, emptyFlowDefinition, enumChoiceSetFieldType, fieldAt, filterReferences, flattenFields, flowNodeWidth, flowNodeWidthClass, foldSourceConnectorId, foldTargetConnectorId, groupColorClass, groupRect, hasAutoSize, innerNamesOf, insertField, isClipboardPayload, isCustomConditionLogic, isEmptyReferenceFilter, isFieldResource, isFoldReference, isGlobalReference, isNumericType, isPathInside, isRecordTrigger, isRegistrationError, isTypeCheckedOperator, isUnknownEnumChoiceSetField, isValidFlowName, isValued, isVersionActive, loadPathLevel, matchesReferenceFilter, membersInside, moveCondition, moveField, navigatePath, otherSourceFieldsOf, outletByKey, outletsOf, parseCanvasFoldId, parseCanvasGroupId, parseCanvasNodeId, parseFieldPath, parseInvariantNumber, parseSourceConnectorId, parseTargetConnectorId, pathAvailableNames, pathContainerLabel, pathKey, pathNotVerifiableMessage, planPaste, priorCriteriaSeverity, priorValuesAvailability, priorValuesOfDefinition, priorValuesProposable, priorValuesSeverity, readsPriorValues, recomputeMembers, referenceRoot, referencedRootsOf, remapConditionLogic, removeCondition, removeField, repeatedFieldNames, repeatingSectionOf, resolvePath, resourceFieldsOf, rewriteReferences, sameMembers, samePath, screenActionNames, screenFieldNames, severityBucket, slugifyFlowName, sourceConnectorId, stageStepNames, stageStepOutputReferenced, stepsOf, targetConnectorId, typeOfCollection, uniqueFlowName, valuedFieldOf, valuedFieldsOf, variantFieldOf, variantOf, variantPresetOf };
20471
+ export { ConditionEditorComponent, ConnectorEditorComponent, DebugPanelComponent, DynamicScreenInspectorComponent, ENUM_CHOICE_SET_DEFAULT_DISPLAY_FIELD, ENUM_CHOICE_SET_DEFAULT_VALUE_FIELD, ENUM_CHOICE_SET_FIELDS, ElementDialogComponent, ElementInspectorComponent, ElementPaletteComponent, EnumValuePickerComponent, FALLBACK_COLLECTION_BY_TYPE, FALLBACK_TYPE_LABEL, FLOW_BUILDER_HTTP_CONFIG, FLOW_CLIPBOARD_KIND, FLOW_CLIPBOARD_VERSION, FLOW_ELEMENT_ICONS, FLOW_ELEMENT_VARIANT_FIELDS, FLOW_ELEMENT_VARIANT_ICONS, FLOW_ERROR_FALLBACK_MESSAGE, FLOW_ERROR_HTTP_STATUS, FLOW_FOLD_HEIGHT, FLOW_FOLD_WIDTH, FLOW_GROUP_COLORS, FLOW_NAME_PATTERN, FLOW_NODE_COLLECTIONS, FLOW_NODE_HEIGHT, FLOW_NODE_WIDTH, FLOW_REFERENCE_FIELDS, FLOW_RESOURCE_COLLECTIONS, FLOW_VALUE_FIELDS, FLOW_VALUE_LITERAL_FIELDS, FieldAssignmentEditorComponent, FieldPickerComponent, FieldValuePickerComponent, FlowApiError, FlowBuilderApi, FlowBuilderComponent, FlowCanvasComponent, FlowCatalogStore, FlowClipboardService, FlowDictionaryStore, FlowDocumentStore, FlowEditorSession, FlowLayoutService, FlowValidationStore, FormulaEditorComponent, FormulaValidationService, GROUP_HEADER_HEIGHT, GROUP_MIN_HEIGHT, GROUP_MIN_WIDTH, GROUP_PADDING, GroupPanelComponent, HttpFlowBuilderApi, MergeTextEditorComponent, NamePickerComponent, NodeInspectorBase, ORCHESTRATION_CONDITION_OUTPUT, ObjectPickerComponent, OrchestratedStageInspectorComponent, PRIOR_VALUES_OPERATOR, PRIOR_VALUES_SCOPE, ParameterEditorComponent, PasteDialogComponent, ProblemsPanelComponent, RECORD_TRIGGERS, RecordFilterEditorComponent, ReferencePickerComponent, ResourceDialogComponent, ResourceFormComponent, ResourcePanelComponent, RunDialogComponent, SEVERITY_BUCKETS, SEVERITY_ICON, SEVERITY_LABEL, START_NODE_NAME, SelectValueDirective, StartInspectorComponent, StructureMemberPickerComponent, StructurePickerComponent, TYPES_WITH_AUTOMATIC_OUTPUT, TYPE_BY_COLLECTION, UNSUPPORTED_TYPES, ValueEditorComponent, VersionPanelComponent, allFieldsOf, applyPaste, areTypesComparable, boundsAround, buildClipboardPayload, canvasFoldId, canvasGroupId, canvasNodeId, checkConditionLogic, checkFlowName, choiceSetSourceOf, declaredChoiceSetSources, describeActivationError, describeFieldPath, describePathEntry, describePriorValues, duplicateField, elementIcon, emptyFlowDefinition, enumChoiceSetFieldType, fieldAt, filterReferences, flattenFields, flowNodeWidth, flowNodeWidthClass, foldSourceConnectorId, foldTargetConnectorId, groupColorClass, groupRect, hasAutoSize, innerNamesOf, insertField, isClipboardPayload, isCustomConditionLogic, isEmptyReferenceFilter, isFieldResource, isFoldReference, isGlobalReference, isNumericType, isPathInside, isRecordTrigger, isRegistrationError, isTypeCheckedOperator, isUnknownEnumChoiceSetField, isValidFlowName, isValued, isVersionActive, loadPathLevel, matchesReferenceFilter, membersInside, moveCondition, moveField, navigatePath, otherSourceFieldsOf, outletByKey, outletsOf, parseCanvasFoldId, parseCanvasGroupId, parseCanvasNodeId, parseFieldPath, parseInvariantNumber, parseSourceConnectorId, parseTargetConnectorId, pathAvailableNames, pathContainerLabel, pathKey, pathNotVerifiableMessage, planPaste, priorCriteriaSeverity, priorValuesAvailability, priorValuesOfDefinition, priorValuesProposable, priorValuesSeverity, readsPriorValues, recomputeMembers, referenceRoot, referencedRootsOf, remapConditionLogic, removeCondition, removeField, repeatedFieldNames, repeatingSectionOf, repeatingSectionsOf, resolvePath, resourceFieldsOf, rewriteReferences, sameMembers, samePath, screenActionNames, screenActionRowTargetIssues, screenActionScopes, screenFieldNames, severityBucket, slugifyFlowName, sourceConnectorId, stageStepNames, stageStepOutputReferenced, stepsOf, targetConnectorId, triggerScopeOf, triggerableFieldsOf, typeOfCollection, uniqueFlowName, valuedFieldOf, valuedFieldsOf, variantFieldOf, variantOf, variantPresetOf };
20042
20472
  //# sourceMappingURL=esfaenza-flow-builder.mjs.map