@esfaenza/flow-builder 20.3.13 → 20.3.15

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.
@@ -5,6 +5,7 @@ import { firstValueFrom } from 'rxjs';
5
5
  import dagre from 'dagre';
6
6
  import * as i1 from '@foblex/flow';
7
7
  import { EFMarkerType, EFConnectableSide, FCanvasComponent, FFlowModule, provideFFlow, withA11y } from '@foblex/flow';
8
+ import { CdkDrag, CdkDragHandle, CdkDropList } from '@angular/cdk/drag-drop';
8
9
 
9
10
  /**
10
11
  * Modello del documento Flow — FRONTEND.md §3, §4, §5.
@@ -22,6 +23,7 @@ import { EFMarkerType, EFConnectableSide, FCanvasComponent, FFlowModule, provide
22
23
  /** Le collection di node: `metadataProperty` del dizionario `elementTypes` (§3.2). */
23
24
  const FLOW_NODE_COLLECTIONS = [
24
25
  'screens',
26
+ 'dynamicScreens',
25
27
  'assignments',
26
28
  'decisions',
27
29
  'loops',
@@ -712,6 +714,7 @@ function outletsOf(type, node) {
712
714
  case 'CustomError':
713
715
  return [];
714
716
  case 'Screen':
717
+ case 'DynamicScreen':
715
718
  case 'Assignment':
716
719
  case 'CollectionProcessor':
717
720
  case 'RecordRollback':
@@ -733,6 +736,7 @@ function outletByKey(type, node, key) {
733
736
  */
734
737
  const FALLBACK_COLLECTION_BY_TYPE = {
735
738
  Screen: 'screens',
739
+ DynamicScreen: 'dynamicScreens',
736
740
  Assignment: 'assignments',
737
741
  Decision: 'decisions',
738
742
  Loop: 'loops',
@@ -757,6 +761,7 @@ const TYPE_BY_COLLECTION = Object.fromEntries(Object.entries(FALLBACK_COLLECTION
757
761
  /** Etichette di fallback, se il dizionario non e' caricato. */
758
762
  const FALLBACK_TYPE_LABEL = {
759
763
  Screen: 'Screen',
764
+ DynamicScreen: 'Screen dinamico',
760
765
  Assignment: 'Assegnazione',
761
766
  Decision: 'Decisione',
762
767
  Loop: 'Ciclo',
@@ -799,6 +804,9 @@ const TYPES_WITH_AUTOMATIC_OUTPUT = new Set([
799
804
  const FLOW_ELEMENT_ICONS = {
800
805
  Start: '▶',
801
806
  Screen: '▤',
807
+ // Diverso da quello dello Screen a form: sul canvas i due elementi devono distinguersi
808
+ // a colpo d'occhio, perche' chi decide il layout e' l'unica differenza fra loro (§5.2).
809
+ DynamicScreen: '▦',
802
810
  Decision: '◆',
803
811
  Assignment: '=',
804
812
  Loop: '↻',
@@ -1205,6 +1213,67 @@ function parseInvariantNumber(raw) {
1205
1213
  return Number.isFinite(parsed) ? parsed : undefined;
1206
1214
  }
1207
1215
 
1216
+ /**
1217
+ * Quale campo di un `FlowElementReferenceOrValue` porta davvero il valore — FRONTEND.md §4.2.
1218
+ *
1219
+ * La regola del contratto e' "uno e un solo campo valorizzato", e la §2 dice che cio' che non c'e'
1220
+ * si **omette**. Un backend che serializza tutte le proprieta' — `System.Text.Json` senza
1221
+ * `IgnoreNullValues` lo fa di default — rispetta il contratto nella sostanza e lo viola nella forma:
1222
+ * arriva `{"enumValue": "InIstruttoria", "formulaExpression": null, ...}`, cioe' un solo valore e
1223
+ * sette caselle piene di niente.
1224
+ *
1225
+ * Il campo si sceglieva con `!== undefined`, e un `null` passava: la prima casella letta —
1226
+ * `formulaExpression` — vinceva, l'editor apriva la formula (vuota) e il valore, pur presente nel
1227
+ * documento, spariva dalla vista. Da qui questa funzione sola: **`null` e' assente quanto
1228
+ * `undefined`**, e chi interpreta un valore la usa invece di confrontare con `undefined`.
1229
+ *
1230
+ * Sta in `core/` perche' i chiamanti sono piu' d'uno — l'editor del valore, l'editor delle
1231
+ * condizioni — e la regola non deve divergere fra loro.
1232
+ */
1233
+ /**
1234
+ * I campi che possono portare il valore, **nell'ordine in cui si interpretano**: `formulaExpression`
1235
+ * per primo perche' e' l'unico a due campi, poi il riferimento, poi i letterali. `formulaDataType`
1236
+ * non c'e': accompagna l'espressione, non e' un valore da solo.
1237
+ */
1238
+ const FLOW_VALUE_FIELDS = [
1239
+ 'formulaExpression',
1240
+ 'elementReference',
1241
+ 'stringValue',
1242
+ 'integerValue',
1243
+ 'numberValue',
1244
+ 'dateValue',
1245
+ 'booleanValue',
1246
+ 'enumValue',
1247
+ ];
1248
+ /** I soli campi letterali: quelli che l'editor mostra sotto la modalita' «Valore». */
1249
+ const FLOW_VALUE_LITERAL_FIELDS = [
1250
+ 'stringValue',
1251
+ 'integerValue',
1252
+ 'numberValue',
1253
+ 'dateValue',
1254
+ 'booleanValue',
1255
+ 'enumValue',
1256
+ ];
1257
+ /**
1258
+ * `true` se il campo porta un valore. Attenzione ai falsi legittimi: `booleanValue: false`,
1259
+ * `numberValue: 0` e `stringValue: ''` sono valori, quindi il confronto e' con `null`/`undefined`
1260
+ * e non una verifica di verita'.
1261
+ */
1262
+ function isValued(field) {
1263
+ return field !== undefined && field !== null;
1264
+ }
1265
+ /** I campi valorizzati, nell'ordine di interpretazione: piu' di uno e' `VALUE_AMBIGUOUS` (§4.2). */
1266
+ function valuedFieldsOf(value) {
1267
+ if (!value) {
1268
+ return [];
1269
+ }
1270
+ return FLOW_VALUE_FIELDS.filter((field) => isValued(value[field]));
1271
+ }
1272
+ /** Il campo che l'editor considera **il** valore, cioe' il primo valorizzato nell'ordine sopra. */
1273
+ function valuedFieldOf(value) {
1274
+ return valuedFieldsOf(value)[0];
1275
+ }
1276
+
1208
1277
  /**
1209
1278
  * Gli step di uno stage di orchestrazione — FRONTEND.md §5.13.
1210
1279
  *
@@ -1254,6 +1323,198 @@ function stageStepOutputReferenced(reference, steps) {
1254
1323
  */
1255
1324
  const ORCHESTRATION_CONDITION_OUTPUT = 'isOrchestrationConditionMet';
1256
1325
 
1326
+ /**
1327
+ * L'albero dei campi di uno screen dinamico — FRONTEND.md §5.2.
1328
+ *
1329
+ * Un campo puo' contenerne altri (`Region` dentro `RegionContainer`), quindi «il campo
1330
+ * selezionato» non e' un indice ma un **percorso di indici**: `[1, 0, 2]` e' il terzo campo
1331
+ * della prima colonna della seconda sezione. Tutto cio' che cammina l'albero sta qui, perche'
1332
+ * inspector, store e outline lo fanno con la stessa regola e sbagliarne una copia significa
1333
+ * scrivere in un campo diverso da quello che l'utente vede selezionato.
1334
+ *
1335
+ * L'altra ragione per cui questo file esiste: **i campi che raccolgono un valore sono risorse
1336
+ * del flow** (§5.2). I loro nomi vivono nello stesso spazio dei nomi di node e risorse (§3.3),
1337
+ * quindi `usedNames` dello store deve comprenderli — un campo omonimo di una variabile e'
1338
+ * `NAME_DUPLICATED`, non una stranezza tollerata.
1339
+ */
1340
+ /** Uguaglianza fra percorsi: due array diversi con gli stessi indici sono lo stesso campo. */
1341
+ function samePath(a, b) {
1342
+ if (!a || !b || a.length !== b.length) {
1343
+ return false;
1344
+ }
1345
+ return a.every((value, index) => value === b[index]);
1346
+ }
1347
+ /** `true` se `parent` e' un antenato di `path` (o lo stesso campo). */
1348
+ function isPathInside(parent, path) {
1349
+ return parent.length <= path.length && parent.every((value, index) => value === path[index]);
1350
+ }
1351
+ /** Chiave stabile di un percorso: serve al `track` e agli id delle liste di trascinamento. */
1352
+ function pathKey(path) {
1353
+ return path.join('.');
1354
+ }
1355
+ /** Il campo a un percorso, o `undefined` se il percorso non esiste piu'. */
1356
+ function fieldAt(fields, path) {
1357
+ let list = fields;
1358
+ let field;
1359
+ for (const index of path) {
1360
+ field = list?.[index];
1361
+ if (!field) {
1362
+ return undefined;
1363
+ }
1364
+ list = field.fields;
1365
+ }
1366
+ return field;
1367
+ }
1368
+ /** La lista che **contiene** un percorso, creandola se serve: serve a inserire e rimuovere. */
1369
+ function listOf(screen, parentPath, create) {
1370
+ if (parentPath.length === 0) {
1371
+ if (create) {
1372
+ screen.fields ??= [];
1373
+ }
1374
+ return screen.fields;
1375
+ }
1376
+ const parent = fieldAt(screen.fields, parentPath);
1377
+ if (!parent) {
1378
+ return undefined;
1379
+ }
1380
+ if (create) {
1381
+ parent.fields ??= [];
1382
+ }
1383
+ return parent.fields;
1384
+ }
1385
+ /** L'albero appiattito in ordine di visita, con profondita': e' l'elenco che si disegna. */
1386
+ function flattenFields(fields, parentPath = []) {
1387
+ const result = [];
1388
+ (fields ?? []).forEach((field, index) => {
1389
+ const path = [...parentPath, index];
1390
+ result.push({ field, path, depth: parentPath.length });
1391
+ if (field.fields?.length) {
1392
+ result.push(...flattenFields(field.fields, path));
1393
+ }
1394
+ });
1395
+ return result;
1396
+ }
1397
+ /** Tutti i campi di uno screen, in ordine di visita. */
1398
+ function allFieldsOf(screen) {
1399
+ return flattenFields(screen?.fields).map((node) => node.field);
1400
+ }
1401
+ /**
1402
+ * I nomi dei campi di tutti gli screen dinamici del documento.
1403
+ *
1404
+ * Contenitori compresi: un `Region` non e' una risorsa — non raccoglie un valore — ma il suo
1405
+ * nome resta un nome, e due campi omonimi nella stessa schermata sono comunque un problema.
1406
+ * Chi vuole i **soli** riferimenti proponibili usa `isFieldResource`.
1407
+ */
1408
+ function screenFieldNames(definition) {
1409
+ const names = [];
1410
+ for (const screen of definition?.dynamicScreens ?? []) {
1411
+ for (const field of allFieldsOf(screen)) {
1412
+ if (field.name) {
1413
+ names.push(field.name);
1414
+ }
1415
+ }
1416
+ }
1417
+ return names;
1418
+ }
1419
+ /**
1420
+ * §5.2 — il campo e' una risorsa del flow (`kind: "ScreenField"`), quindi referenziabile
1421
+ * per nome ovunque e **di sola lettura**.
1422
+ *
1423
+ * Un `ComponentInstance` non lo e' pur essendo un campo: non ha un valore proprio, ce l'hanno
1424
+ * i suoi output. Il flag `storesValue` arriva dal dizionario, che e' l'unica fonte autoritativa:
1425
+ * senza la voce del dizionario si risponde `false`, cioe' "non lo so" — proporre un riferimento
1426
+ * che il backend non conosce e' peggio che non proporlo.
1427
+ */
1428
+ function isFieldResource(field, entry) {
1429
+ return !!field?.name && entry?.storesValue === true;
1430
+ }
1431
+ // ---------------------------------------------------------------------------
1432
+ // Mutazioni (lavorano sulla copia di lavoro dello store)
1433
+ // ---------------------------------------------------------------------------
1434
+ /** Inserisce un campo dentro `parentPath` alla posizione indicata (in fondo se assente). */
1435
+ function insertField(screen, parentPath, field, index) {
1436
+ const list = listOf(screen, parentPath, true);
1437
+ if (!list) {
1438
+ return undefined;
1439
+ }
1440
+ const position = index === undefined || index > list.length ? list.length : Math.max(0, index);
1441
+ list.splice(position, 0, field);
1442
+ return [...parentPath, position];
1443
+ }
1444
+ /** Rimuove il campo a un percorso, e con esso tutto cio' che conteneva. */
1445
+ function removeField(screen, path) {
1446
+ if (path.length === 0) {
1447
+ return;
1448
+ }
1449
+ const parentPath = path.slice(0, -1);
1450
+ const list = listOf(screen, parentPath, false);
1451
+ const index = path[path.length - 1];
1452
+ list?.splice(index, 1);
1453
+ if (list && list.length === 0) {
1454
+ // Liste vuote omesse, non scritte come `[]` (§2).
1455
+ if (parentPath.length === 0) {
1456
+ delete screen.fields;
1457
+ }
1458
+ else {
1459
+ delete fieldAt(screen.fields, parentPath)?.fields;
1460
+ }
1461
+ }
1462
+ }
1463
+ /**
1464
+ * Sposta un campo dentro un'altra lista, a una posizione precisa.
1465
+ *
1466
+ * `toIndex` e' l'indice **dopo** l'estrazione, cioe' la stessa convenzione di
1467
+ * `moveItemInArray` del CDK: e' l'indice che il drop del trascinamento consegna, e riallinearlo
1468
+ * qui sposterebbe di uno ogni trascinamento verso il basso nella stessa lista.
1469
+ *
1470
+ * Uno spostamento dentro se stesso e' rifiutato: sposterebbe un contenitore dentro il proprio
1471
+ * sottoalbero, e il sottoalbero sparirebbe con lui.
1472
+ */
1473
+ function moveField(screen, from, toParent, toIndex) {
1474
+ if (from.length === 0 || isPathInside(from, toParent)) {
1475
+ return undefined;
1476
+ }
1477
+ const field = fieldAt(screen.fields, from);
1478
+ if (!field) {
1479
+ return undefined;
1480
+ }
1481
+ removeField(screen, from);
1482
+ return insertField(screen, shiftAfterRemoval(from, toParent), field, toIndex);
1483
+ }
1484
+ /**
1485
+ * Il percorso della destinazione **dopo** aver estratto il campo di partenza.
1486
+ *
1487
+ * Estrarre `[0]` fa scalare di uno i fratelli che seguivano: la sezione che era `[1]` ora e'
1488
+ * `[0]`, e reinserire nel percorso vecchio significa scrivere dentro un contenitore diverso da
1489
+ * quello su cui l'utente ha rilasciato. Riguarda solo i percorsi che **passano** dalla lista da
1490
+ * cui si estrae, e solo gli indici successivi a quello estratto.
1491
+ */
1492
+ function shiftAfterRemoval(from, toParent) {
1493
+ const fromParent = from.slice(0, -1);
1494
+ const fromIndex = from[from.length - 1];
1495
+ if (toParent.length <= fromParent.length || !samePath(fromParent, toParent.slice(0, fromParent.length))) {
1496
+ return toParent;
1497
+ }
1498
+ const shifted = [...toParent];
1499
+ if (shifted[fromParent.length] > fromIndex) {
1500
+ shifted[fromParent.length] -= 1;
1501
+ }
1502
+ return shifted;
1503
+ }
1504
+ /** Duplica un campo accanto all'originale, rinominandolo: i nomi restano unici (§3.3). */
1505
+ function duplicateField(screen, path, newName) {
1506
+ const field = fieldAt(screen.fields, path);
1507
+ if (!field) {
1508
+ return undefined;
1509
+ }
1510
+ const copy = JSON.parse(JSON.stringify(field));
1511
+ copy.name = newName;
1512
+ // I figli non si rinominano: sarebbero nomi duplicati. Copiare un contenitore e' quindi un
1513
+ // gesto da completare a mano, e l'editor lo dice invece di inventare dieci nomi.
1514
+ delete copy.fields;
1515
+ return insertField(screen, path.slice(0, -1), copy, path[path.length - 1] + 1);
1516
+ }
1517
+
1257
1518
  /**
1258
1519
  * La gravita' di un rilievo, ridotta a tre secchi — FRONTEND.md §7.
1259
1520
  *
@@ -1750,15 +2011,18 @@ class FlowDocumentStore {
1750
2011
  return result;
1751
2012
  }, ...(ngDevMode ? [{ debugName: "resources" }] : []));
1752
2013
  /**
1753
- * §3.3 — l'insieme dei nomi già usati: node, risorse **e** step di orchestrazione, un unico
1754
- * spazio di nomi. Il controllo di unicita' che guarda solo le variabili lascia passare una
1755
- * variabile omonima di un node (§13.5); quello che dimentica gli step lascia passare uno
1756
- * step omonimo di una variabile, che e' `NAME_DUPLICATED` allo stesso modo (§5.13).
2014
+ * §3.3 — l'insieme dei nomi già usati: node, risorse, step di orchestrazione **e campi di
2015
+ * screen dinamico**, un unico spazio di nomi. Il controllo di unicita' che guarda solo le
2016
+ * variabili lascia passare una variabile omonima di un node (§13.5); quello che dimentica gli
2017
+ * step lascia passare uno step omonimo di una variabile (§5.13); quello che dimentica i campi
2018
+ * lascia passare un campo omonimo di una variabile, che e' `NAME_DUPLICATED` come gli altri
2019
+ * — e lì il danno e' peggiore, perche' un campo **e' un riferimento** (§5.2).
1757
2020
  */
1758
2021
  usedNames = computed(() => [
1759
2022
  ...this.nodes().map((reference) => reference.name),
1760
2023
  ...this.resources().map((reference) => reference.name),
1761
2024
  ...stageStepNames(this._document()),
2025
+ ...screenFieldNames(this._document()),
1762
2026
  ], ...(ngDevMode ? [{ debugName: "usedNames" }] : []));
1763
2027
  /** Gli archi derivati dal documento, Start incluso. */
1764
2028
  edges = computed(() => {
@@ -1990,6 +2254,37 @@ class FlowDocumentStore {
1990
2254
  FlowDocumentStore.rewriteReferences(draft, oldName, newName);
1991
2255
  });
1992
2256
  }
2257
+ /**
2258
+ * §5.2 — rinomina un campo di screen dinamico riscrivendo i riferimenti che lo usano.
2259
+ *
2260
+ * Un campo che raccoglie un valore **e' una risorsa** referenziabile per nome: rinominarlo
2261
+ * senza riscrivere le condizioni e le assegnazioni che lo citano lascia dei
2262
+ * `REFERENCE_UNKNOWN` che si scoprono solo alla validazione. Vale la stessa regola dei node
2263
+ * (§13.8): nessuna primitiva del backend lo fa, lo fa l'editor.
2264
+ */
2265
+ renameScreenField(nodeName, path, newName) {
2266
+ const reference = this.nodeByName().get(nodeName);
2267
+ if (!reference || !newName) {
2268
+ return;
2269
+ }
2270
+ // Una sola `update`, non `updateNode` piu' una riscrittura: annidare due update farebbe
2271
+ // ripartire la seconda dal documento **precedente**, buttando via la rinomina.
2272
+ this.update((draft) => {
2273
+ const list = draft[reference.collection];
2274
+ const screen = list?.[reference.index];
2275
+ const field = fieldAt(screen?.fields, path);
2276
+ if (!field || field.name === newName) {
2277
+ return;
2278
+ }
2279
+ const oldName = field.name;
2280
+ field.name = newName;
2281
+ if (oldName) {
2282
+ // La riscrittura cammina l'intero documento: il campo puo' essere referenziato da
2283
+ // qualunque elemento successivo, non solo da questo screen.
2284
+ FlowDocumentStore.rewriteReferences(draft, oldName, newName);
2285
+ }
2286
+ });
2287
+ }
1993
2288
  /** Rinomina una risorsa, riscrivendo i riferimenti che la usano. */
1994
2289
  renameResource(collection, index, newName) {
1995
2290
  this.update((draft) => {
@@ -2225,6 +2520,42 @@ class FlowDictionaryStore {
2225
2520
  stageStepTypes = computed(() => this._dictionaries().stageStepTypes ?? [], ...(ngDevMode ? [{ debugName: "stageStepTypes" }] : []));
2226
2521
  assigneeTypes = computed(() => this._dictionaries().assigneeTypes ?? [], ...(ngDevMode ? [{ debugName: "assigneeTypes" }] : []));
2227
2522
  conditionLogicModes = computed(() => this._dictionaries().conditionLogicModes ?? [], ...(ngDevMode ? [{ debugName: "conditionLogicModes" }] : []));
2523
+ regionContainerTypes = computed(() => this._dictionaries().regionContainerTypes ?? [], ...(ngDevMode ? [{ debugName: "regionContainerTypes" }] : []));
2524
+ screenFieldInputsRevisited = computed(() => this._dictionaries().screenFieldInputsRevisited ?? [], ...(ngDevMode ? [{ debugName: "screenFieldInputsRevisited" }] : []));
2525
+ /** §5.2 — i tipi di campo di uno screen dinamico, con i flag che guidano il form. */
2526
+ screenFieldTypes = computed(() => this._dictionaries().screenFieldTypes ?? [], ...(ngDevMode ? [{ debugName: "screenFieldTypes" }] : []));
2527
+ /**
2528
+ * La voce di un tipo di campo. **Non c'e' un fallback cablato**, di proposito: i flag sono gli
2529
+ * stessi che applica il runtime (§5.2, §6.4), e indovinarli qui vorrebbe dire proporre una
2530
+ * configurazione che il motore rifiuta. Voce assente = nessun flag, cioe' il form mostra i soli
2531
+ * campi comuni e lascia decidere al backend.
2532
+ */
2533
+ screenFieldType(value) {
2534
+ if (!value) {
2535
+ return undefined;
2536
+ }
2537
+ return this.screenFieldTypes().find((entry) => entry.value === value);
2538
+ }
2539
+ /** §5.2 — il campo raccoglie un valore: e' una risorsa, e vuole tipo, default, … */
2540
+ screenFieldStoresValue(value) {
2541
+ return this.screenFieldType(value)?.storesValue ?? false;
2542
+ }
2543
+ /** §5.2 — il campo contiene altri campi: `Region`, `RegionContainer`. */
2544
+ screenFieldIsContainer(value) {
2545
+ return this.screenFieldType(value)?.isContainer ?? false;
2546
+ }
2547
+ /** §5.2 — il campo si configura con `choiceReferences`. */
2548
+ screenFieldAcceptsChoices(value) {
2549
+ return this.screenFieldType(value)?.acceptsChoices ?? false;
2550
+ }
2551
+ /** §5.2 — `dataType` obbligatorio: senza, `DATA_TYPE_MISSING`. */
2552
+ screenFieldRequiresDataType(value) {
2553
+ return this.screenFieldType(value)?.requiresDataType ?? false;
2554
+ }
2555
+ /** §5.2 — il valore raccolto e' una collection: contano gli operatori di collection. */
2556
+ screenFieldIsCollection(value) {
2557
+ return this.screenFieldType(value)?.isCollection ?? false;
2558
+ }
2228
2559
  elementTypes = computed(() => this._dictionaries().elementTypes ?? [], ...(ngDevMode ? [{ debugName: "elementTypes" }] : []));
2229
2560
  /**
2230
2561
  * I tipi mostrabili nella palette: `isSupported: false` va nascosto (§3.2, §13.9).
@@ -3720,9 +4051,14 @@ class ElementPaletteComponent {
3720
4051
  pick(item) {
3721
4052
  this.elementPicked.emit(item.data);
3722
4053
  }
3723
- /** Uno screen in un flow che non mostra nulla: si segnala, non si nasconde (§3.1). */
4054
+ /**
4055
+ * Uno screen in un flow che non mostra nulla: si segnala, non si nasconde (§3.1). Vale per
4056
+ * entrambi i tipi di screen — quello a form e quello dinamico sono la stessa interazione con
4057
+ * l'utente, e in un flow `AutoLaunched` non c'e' nessuno a cui mostrarli.
4058
+ */
3724
4059
  isIncompatible(item) {
3725
- return item.entry.value === 'Screen' && this.processType() === 'AutoLaunched';
4060
+ const isScreen = item.entry.value === 'Screen' || item.entry.value === 'DynamicScreen';
4061
+ return isScreen && this.processType() === 'AutoLaunched';
3726
4062
  }
3727
4063
  /**
3728
4064
  * Il testo del tooltip. Su una voce che nasce da una variante ci va anche il nome del tipo:
@@ -4314,6 +4650,9 @@ class ReferencePickerComponent {
4314
4650
  return 'Dynamic choice set';
4315
4651
  case 'Stage':
4316
4652
  return 'Stage';
4653
+ // §5.2 — nasce dichiarando un campo di screen dinamico, non da una collection di risorse.
4654
+ case 'ScreenField':
4655
+ return 'Campi di schermata';
4317
4656
  case 'ElementOutput':
4318
4657
  return 'Output di elementi';
4319
4658
  case 'StructureMember':
@@ -5406,18 +5745,16 @@ class ValueEditorComponent {
5406
5745
  // valore che il modello non sa portare (§4.2, §4.7).
5407
5746
  return this.dataType() && this.literalAllowed() ? 'literal' : 'reference';
5408
5747
  }
5409
- if (value.formulaExpression !== undefined) {
5748
+ // `isValued` e non `!== undefined`: un backend che serializza anche cio' che non c'e' manda
5749
+ // `formulaExpression: null`, e il primo controllo apriva la formula (vuota) su un valore che nel
5750
+ // documento c'era — un letterale che sparisce dalla vista senza che nulla lo segnali (§4.2).
5751
+ if (isValued(value.formulaExpression)) {
5410
5752
  return 'formula';
5411
5753
  }
5412
- if (value.elementReference !== undefined) {
5754
+ if (isValued(value.elementReference)) {
5413
5755
  return value.elementReference.startsWith('$GlobalConstant.') ? 'globalConstant' : 'reference';
5414
5756
  }
5415
- if (value.stringValue !== undefined ||
5416
- value.integerValue !== undefined ||
5417
- value.numberValue !== undefined ||
5418
- value.dateValue !== undefined ||
5419
- value.booleanValue !== undefined ||
5420
- value.enumValue !== undefined) {
5757
+ if (FLOW_VALUE_LITERAL_FIELDS.some((field) => isValued(value[field]))) {
5421
5758
  return 'literal';
5422
5759
  }
5423
5760
  return 'empty';
@@ -5427,23 +5764,7 @@ class ValueEditorComponent {
5427
5764
  * Puo' capitare su un documento arrivato da un altro editor: si segnala e si offre
5428
5765
  * di normalizzarlo, invece di nasconderlo.
5429
5766
  */
5430
- filledFieldCount = computed(() => {
5431
- const value = this.value();
5432
- if (!value) {
5433
- return 0;
5434
- }
5435
- const keys = [
5436
- 'elementReference',
5437
- 'stringValue',
5438
- 'integerValue',
5439
- 'numberValue',
5440
- 'dateValue',
5441
- 'booleanValue',
5442
- 'enumValue',
5443
- 'formulaExpression',
5444
- ];
5445
- return keys.filter((key) => value[key] !== undefined).length;
5446
- }, ...(ngDevMode ? [{ debugName: "filledFieldCount" }] : []));
5767
+ filledFieldCount = computed(() => valuedFieldsOf(this.value()).length, ...(ngDevMode ? [{ debugName: "filledFieldCount" }] : []));
5447
5768
  isAmbiguous = computed(() => this.filledFieldCount() > 1, ...(ngDevMode ? [{ debugName: "isAmbiguous" }] : []));
5448
5769
  /**
5449
5770
  * Il campo del valore che corrisponde al `dataType` (§4.2, tabella degli otto tipi). `Object` e
@@ -5498,7 +5819,7 @@ class ValueEditorComponent {
5498
5819
  return '';
5499
5820
  }
5500
5821
  const field = this.literalField();
5501
- if (field === 'dateValue' && value.dateValue !== undefined) {
5822
+ if (field === 'dateValue' && isValued(value.dateValue)) {
5502
5823
  // `datetime-local` rifiuta un valore col fuso e resterebbe **vuoto** pur avendo un
5503
5824
  // valore: il suffisso si toglie qui e si riscrive in uscita. Se il documento porta un
5504
5825
  // campo diverso da quello atteso si passa al fallback qui sotto, che lo mostra comunque.
@@ -5658,7 +5979,13 @@ class ValueEditorComponent {
5658
5979
  this.emit({ elementReference: value.elementReference });
5659
5980
  return;
5660
5981
  default: {
5661
- const field = this.literalField();
5982
+ // Il campo del tipo atteso, ma solo se e' quello che porta il valore: dove il tipo non si
5983
+ // sa — o il documento usa un campo diverso — «normalizza» scriverebbe un valore vuoto e
5984
+ // butterebbe via l'unico che c'era.
5985
+ const expected = this.literalField();
5986
+ const field = isValued(value[expected])
5987
+ ? expected
5988
+ : (FLOW_VALUE_LITERAL_FIELDS.find((candidate) => isValued(value[candidate])) ?? expected);
5662
5989
  this.emit({ [field]: value[field] });
5663
5990
  }
5664
5991
  }
@@ -5780,31 +6107,29 @@ class ConditionEditorComponent {
5780
6107
  if (!value) {
5781
6108
  return undefined;
5782
6109
  }
5783
- if (value.formulaExpression !== undefined) {
5784
- return value.formulaDataType;
5785
- }
5786
- if (value.elementReference !== undefined) {
5787
- return this.typeOfReference(value.elementReference);
5788
- }
5789
- if (value.stringValue !== undefined) {
5790
- return 'String';
5791
- }
5792
- if (value.integerValue !== undefined) {
5793
- return 'Integer';
5794
- }
5795
- if (value.numberValue !== undefined) {
5796
- return 'Number';
5797
- }
5798
- if (value.dateValue !== undefined) {
5799
- return 'Date';
5800
- }
5801
- if (value.booleanValue !== undefined) {
5802
- return 'Boolean';
5803
- }
5804
- if (value.enumValue !== undefined) {
5805
- return 'Enum';
6110
+ // Stessa regola dell'editor del valore: un campo a `null` e' assente, non valorizzato — con
6111
+ // `!== undefined` un backend che serializza tutto faceva risultare **formula** ogni letterale,
6112
+ // e il tipo dedotto era quello sbagliato (§4.2).
6113
+ switch (valuedFieldOf(value)) {
6114
+ case 'formulaExpression':
6115
+ return value.formulaDataType;
6116
+ case 'elementReference':
6117
+ return this.typeOfReference(value.elementReference);
6118
+ case 'stringValue':
6119
+ return 'String';
6120
+ case 'integerValue':
6121
+ return 'Integer';
6122
+ case 'numberValue':
6123
+ return 'Number';
6124
+ case 'dateValue':
6125
+ return 'Date';
6126
+ case 'booleanValue':
6127
+ return 'Boolean';
6128
+ case 'enumValue':
6129
+ return 'Enum';
6130
+ default:
6131
+ return undefined;
5806
6132
  }
5807
- return undefined;
5808
6133
  }
5809
6134
  /**
5810
6135
  * `true` quando i due lati sono incompatibili: e' `CONDITION_TYPE_MISMATCH`, un errore che
@@ -5975,7 +6300,7 @@ class ConditionEditorComponent {
5975
6300
  // "confronta con questo" ma "l'esito atteso e' questo": va riscritto, non tenuto.
5976
6301
  condition.rightValue = { booleanValue: true };
5977
6302
  }
5978
- else if (condition.rightValue?.booleanValue !== undefined && operator !== 'EqualTo') {
6303
+ else if (isValued(condition.rightValue?.booleanValue) && operator !== 'EqualTo') {
5979
6304
  condition.rightValue = undefined;
5980
6305
  }
5981
6306
  });
@@ -7192,6 +7517,585 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImpo
7192
7517
  args: [{ selector: 'fb-decision-inspector', standalone: true, imports: [ConditionEditorComponent, ConnectorEditorComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<p class=\"fb-callout\">\r\n Le regole sono valutate <strong>dall\u2019alto verso il basso</strong> e si ferma alla prima vera. L\u2019ordine e\u2019\r\n parte del significato del flow: usa le frecce per cambiarlo.\r\n</p>\r\n\r\n<div class=\"fb-list\">\r\n @for (rule of rules(); track $index) {\r\n <div class=\"fb-list__item\">\r\n <div class=\"fb-list__header\">\r\n <span class=\"fb-list__index\" [title]=\"'Valutata per ' + ($index + 1) + '\u00AA'\">{{ $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=\"Valuta prima\"\r\n [disabled]=\"$first\"\r\n (click)=\"moveRule($index, -1)\"\r\n >\r\n \u2191\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n aria-label=\"Valuta dopo\"\r\n [disabled]=\"$last\"\r\n (click)=\"moveRule($index, 1)\"\r\n >\r\n \u2193\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n aria-label=\"Rimuovi la regola\"\r\n (click)=\"removeRule($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\">Etichetta del ramo</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"rule.label || ''\"\r\n placeholder=\"Approvato\"\r\n (input)=\"setRuleLabel($index, $any($event.target).value)\"\r\n />\r\n @if (!rule.label) {\r\n <p class=\"fb-field__hint\">Senza etichetta l\u2019arco resta senza nome sul canvas.</p>\r\n }\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Nome tecnico</label>\r\n <input\r\n class=\"fb-input fb-input--mono\"\r\n [class.fb-input--invalid]=\"!!ruleNameError($index)\"\r\n [value]=\"rule.name || ''\"\r\n (input)=\"setRuleName($index, $any($event.target).value)\"\r\n />\r\n @if (ruleNameError($index)) {\r\n <p class=\"fb-field__error\">{{ ruleNameError($index) }}</p>\r\n }\r\n </div>\r\n\r\n <fb-condition-editor\r\n [holder]=\"rule\"\r\n title=\"Quando prendere questo ramo\"\r\n [issuePath]=\"'rules[' + (rule.name || $index) + ']'\"\r\n (changed)=\"onRuleConditionsChanged($index, $event)\"\r\n />\r\n </div>\r\n } @empty {\r\n <p class=\"fb-empty\">\r\n Nessuna regola: una Decision senza regole prende sempre il ramo di default\r\n (DECISION_WITHOUT_RULES).\r\n </p>\r\n }\r\n</div>\r\n\r\n<button type=\"button\" class=\"fb-btn\" (click)=\"addRule()\">Aggiungi regola</button>\r\n\r\n<fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Se nessuna regola e\u2019 vera</legend>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Etichetta del ramo di default</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"defaultLabel()\"\r\n placeholder=\"Rifiutato\"\r\n (input)=\"setDefaultLabel($any($event.target).value)\"\r\n />\r\n </div>\r\n @if (!hasDefaultTarget()) {\r\n <p class=\"fb-field__hint\">\r\n Nessuna destinazione di default: se nessuna regola e\u2019 vera l\u2019esecuzione finisce qui.\r\n </p>\r\n }\r\n</fieldset>\r\n\r\n<fb-connector-editor\r\n [nodeName]=\"name()\"\r\n [node]=\"node()\"\r\n [outlets]=\"outlets()\"\r\n title=\"Rami\"\r\n (connectorChanged)=\"onConnectorChanged($event)\"\r\n/>\r\n" }]
7193
7518
  }] });
7194
7519
 
7520
+ /**
7521
+ * Screen dinamico — FRONTEND.md §5.2.
7522
+ *
7523
+ * A differenza dello Screen a form (§5.1), qui la schermata **la compone il flow**: l'inspector
7524
+ * e' quindi un compositore, non un form di quattro campi. Due pannelli: a sinistra l'albero dei
7525
+ * campi con la griglia, a destra le proprieta' del campo selezionato.
7526
+ *
7527
+ * Le cose che questo componente fa di proposito, e che un editor piu' ingenuo sbaglia:
7528
+ *
7529
+ * 1. **Quali campi mostrare lo decide il dizionario, non il codice.** `screenFieldTypes` porta
7530
+ * `storesValue`, `isCollection`, `acceptsChoices`, `isContainer`, `requiresDataType` (§6.4),
7531
+ * e sono gli **stessi** flag che applica il runtime: reimplementarli qui vorrebbe dire
7532
+ * proporre configurazioni che il motore rifiuta.
7533
+ * 2. **Un campo che raccoglie un valore e' una risorsa del flow** (§5.2): il suo nome vive
7534
+ * nello spazio dei nomi comune (§3.3) e rinominarlo riscrive i riferimenti — se ne occupa
7535
+ * `FlowDocumentStore.renameScreenField`, perche' nessuna primitiva del backend lo fa (§13.8).
7536
+ * Per questo il nome si applica sul `change` (uscita dal campo), non a ogni tasto: una
7537
+ * riscrittura per lettera digitata sarebbe una riscrittura del documento per lettera.
7538
+ * 3. **I campi sono di sola lettura per il flow**: non si offre nessuna destinazione su di
7539
+ * essi, e il default e' un valore, non un'assegnazione.
7540
+ * 4. **`choiceReferences` accetta solo `Choice` e `DynamicChoiceSet`** (§5.2): puntare a una
7541
+ * variabile e' `SCREEN_FIELD_CHOICE_UNKNOWN`, quindi l'elenco proposto e' filtrato sulle due
7542
+ * collection e non sui riferimenti in generale.
7543
+ * 5. **Il trascinamento e' del CDK.** L'annidamento previsto e' sezione → colonne → campi, e
7544
+ * lo spostamento dentro il proprio sottoalbero e' rifiutato da `moveField`: un contenitore
7545
+ * trascinato dentro se stesso porterebbe via tutto quello che contiene.
7546
+ */
7547
+ class DynamicScreenInspectorComponent extends NodeInspectorBase {
7548
+ catalog = inject(FlowCatalogStore);
7549
+ dictionary = inject(FlowDictionaryStore);
7550
+ elementType = 'DynamicScreen';
7551
+ screen = computed(() => this.node(), ...(ngDevMode ? [{ debugName: "screen" }] : []));
7552
+ /** Il percorso del campo selezionato: `null` = nessuno, e il pannello destro lo dice. */
7553
+ selection = signal(null, ...(ngDevMode ? [{ debugName: "selection" }] : []));
7554
+ /** L'albero appiattito: e' anche cio' che serve per capire quali percorsi esistono ancora. */
7555
+ tree = computed(() => flattenFields(this.screen().fields), ...(ngDevMode ? [{ debugName: "tree" }] : []));
7556
+ selectedPath = computed(() => {
7557
+ const path = this.selection();
7558
+ if (!path) {
7559
+ return null;
7560
+ }
7561
+ // Il campo puo' essere sparito (annulla, eliminazione altrove): una selezione che punta a
7562
+ // un percorso inesistente mostrerebbe le proprieta' di un altro campo.
7563
+ return fieldAt(this.screen().fields, path) ? path : null;
7564
+ }, ...(ngDevMode ? [{ debugName: "selectedPath" }] : []));
7565
+ selectedField = computed(() => {
7566
+ const path = this.selectedPath();
7567
+ return path ? fieldAt(this.screen().fields, path) : undefined;
7568
+ }, ...(ngDevMode ? [{ debugName: "selectedField" }] : []));
7569
+ /** La voce di dizionario del campo selezionato: e' lei a decidere il form di destra. */
7570
+ selectedType = computed(() => this.dictionary.screenFieldType(this.selectedField()?.fieldType), ...(ngDevMode ? [{ debugName: "selectedType" }] : []));
7571
+ // -------------------------------------------------------------------------
7572
+ // Cataloghi
7573
+ // -------------------------------------------------------------------------
7574
+ /** Componenti e form sono la stessa domanda al frontend, e lo stesso catalogo (§5.2). */
7575
+ components = signal([], ...(ngDevMode ? [{ debugName: "components" }] : []));
7576
+ componentParameters = signal([], ...(ngDevMode ? [{ debugName: "componentParameters" }] : []));
7577
+ /** I tipi di enumerazione: dizionario chiuso, quindi `<select>` e non combo (§4.6). */
7578
+ enumTypes = signal([], ...(ngDevMode ? [{ debugName: "enumTypes" }] : []));
7579
+ constructor() {
7580
+ super();
7581
+ void this.catalog
7582
+ .listForms()
7583
+ .then((list) => this.components.set(list ?? []))
7584
+ .catch(() => this.components.set([]));
7585
+ void this.catalog
7586
+ .listEnumTypes()
7587
+ .then((list) => this.enumTypes.set(list ?? []))
7588
+ .catch(() => this.enumTypes.set([]));
7589
+ effect(() => {
7590
+ const extensionName = this.selectedField()?.extensionName;
7591
+ if (!extensionName) {
7592
+ this.componentParameters.set([]);
7593
+ return;
7594
+ }
7595
+ void this.catalog
7596
+ .listFormParameters(extensionName)
7597
+ .then((list) => this.componentParameters.set(list ?? []))
7598
+ .catch(() => this.componentParameters.set([]));
7599
+ });
7600
+ }
7601
+ componentOptions = computed(() => this.components(), ...(ngDevMode ? [{ debugName: "componentOptions" }] : []));
7602
+ enumOptions = computed(() => this.enumTypes(), ...(ngDevMode ? [{ debugName: "enumOptions" }] : []));
7603
+ componentParameterList = computed(() => this.componentParameters(), ...(ngDevMode ? [{ debugName: "componentParameterList" }] : []));
7604
+ /**
7605
+ * Le choice proponibili: **solo** `choices` e `dynamicChoiceSets` (§5.2). Un elenco costruito
7606
+ * dai riferimenti in generale farebbe scegliere una variabile, che e'
7607
+ * `SCREEN_FIELD_CHOICE_UNKNOWN`.
7608
+ */
7609
+ choiceOptions = computed(() => {
7610
+ const document = this.store.document();
7611
+ const options = [];
7612
+ for (const choice of document.choices ?? []) {
7613
+ options.push({ name: choice.name ?? '', label: choice.choiceText ?? null, description: 'Choice' });
7614
+ }
7615
+ for (const set of document.dynamicChoiceSets ?? []) {
7616
+ options.push({
7617
+ name: set.name ?? '',
7618
+ label: set.object ? `da ${set.object}` : null,
7619
+ description: 'Dynamic choice set',
7620
+ });
7621
+ }
7622
+ return options.filter((option) => option.name);
7623
+ }, ...(ngDevMode ? [{ debugName: "choiceOptions" }] : []));
7624
+ /** Gli stage dichiarati: `stageReference` alimenta l'indicatore di avanzamento (§5.2). */
7625
+ stageOptions = computed(() => (this.store.document().stages ?? [])
7626
+ .filter((stage) => stage.name)
7627
+ .map((stage) => ({ name: stage.name, label: stage.label ?? null })), ...(ngDevMode ? [{ debugName: "stageOptions" }] : []));
7628
+ // -------------------------------------------------------------------------
7629
+ // Lettura dell'albero
7630
+ // -------------------------------------------------------------------------
7631
+ icon = elementIcon('DynamicScreen');
7632
+ isSelected(path) {
7633
+ return samePath(this.selectedPath(), path);
7634
+ }
7635
+ isContainer(field) {
7636
+ return this.dictionary.screenFieldIsContainer(field?.fieldType);
7637
+ }
7638
+ /** L'etichetta mostrata nell'albero: `fieldText`, poi il nome, poi un segnaposto. */
7639
+ captionOf(field) {
7640
+ return field.fieldText || field.name || '(senza nome)';
7641
+ }
7642
+ typeLabelOf(field) {
7643
+ return this.dictionary.screenFieldType(field.fieldType)?.label ?? field.fieldType ?? '—';
7644
+ }
7645
+ /** La larghezza in dodicesimi: assente = tutta la riga, che e' il comportamento di default. */
7646
+ widthOf(field) {
7647
+ return field.width ?? 12;
7648
+ }
7649
+ /** `true` dove il tipo non e' nel dizionario: il form di destra lo dice invece di indovinare. */
7650
+ isUnknownType(field) {
7651
+ return !!field?.fieldType && !this.dictionary.screenFieldType(field.fieldType);
7652
+ }
7653
+ /** Il campo raccoglie un valore, quindi e' referenziabile per nome nel resto del flow (§5.2). */
7654
+ selectedIsResource = computed(() => this.selectedType()?.storesValue === true, ...(ngDevMode ? [{ debugName: "selectedIsResource" }] : []));
7655
+ /**
7656
+ * §5.2 — uno screen senza campi non ha niente da mostrare (`SCREEN_WITHOUT_FIELDS`), e uno
7657
+ * senza destinazione che non consente «fine» e' un vicolo cieco (`SCREEN_DEAD_END`). Si dicono
7658
+ * qui prima che lo dica la validazione.
7659
+ */
7660
+ isEmpty = computed(() => !this.screen().fields?.length, ...(ngDevMode ? [{ debugName: "isEmpty" }] : []));
7661
+ isDeadEnd = computed(() => {
7662
+ const screen = this.screen();
7663
+ return !screen.connector?.targetReference && screen.allowFinish === false;
7664
+ }, ...(ngDevMode ? [{ debugName: "isDeadEnd" }] : []));
7665
+ allowBack = computed(() => this.screen().allowBack !== false, ...(ngDevMode ? [{ debugName: "allowBack" }] : []));
7666
+ allowFinish = computed(() => this.screen().allowFinish !== false, ...(ngDevMode ? [{ debugName: "allowFinish" }] : []));
7667
+ allowPause = computed(() => this.screen().allowPause !== false, ...(ngDevMode ? [{ debugName: "allowPause" }] : []));
7668
+ showHeader = computed(() => this.screen().showHeader !== false, ...(ngDevMode ? [{ debugName: "showHeader" }] : []));
7669
+ showFooter = computed(() => this.screen().showFooter !== false, ...(ngDevMode ? [{ debugName: "showFooter" }] : []));
7670
+ /** §5.2 — `isEditable` ha default `true`: a `false` il runtime ignora cio' che torna. */
7671
+ selectedIsEditable = computed(() => this.selectedField()?.isEditable !== false, ...(ngDevMode ? [{ debugName: "selectedIsEditable" }] : []));
7672
+ selectedIsRequired = computed(() => this.selectedField()?.isRequired === true, ...(ngDevMode ? [{ debugName: "selectedIsRequired" }] : []));
7673
+ /**
7674
+ * §5.2 — `storeOutputAutomatically` **insieme** a `outputParameters` e'
7675
+ * `OUTPUT_CONFIGURATION_CONFLICT`: la coppia si mostra come esclusiva, non come due caselle.
7676
+ */
7677
+ hasOutputConflict = computed(() => {
7678
+ const field = this.selectedField();
7679
+ return field?.storeOutputAutomatically === true && (field.outputParameters?.length ?? 0) > 0;
7680
+ }, ...(ngDevMode ? [{ debugName: "hasOutputConflict" }] : []));
7681
+ /** §5.2 — la choice di default fuori dall'elenco e' un avviso, non un errore. */
7682
+ defaultChoiceIsForeign = computed(() => {
7683
+ const field = this.selectedField();
7684
+ const value = field?.defaultSelectedChoiceReference;
7685
+ return !!value && !(field?.choiceReferences ?? []).includes(value);
7686
+ }, ...(ngDevMode ? [{ debugName: "defaultChoiceIsForeign" }] : []));
7687
+ /** §5.2 — `scale` su un campo non numerico e' `SCALE_NOT_APPLICABLE`. */
7688
+ scaleApplies = computed(() => this.dictionary.supportsScale(this.selectedField()?.dataType), ...(ngDevMode ? [{ debugName: "scaleApplies" }] : []));
7689
+ requiresObjectType = computed(() => this.dictionary.requiresObjectType(this.selectedField()?.dataType), ...(ngDevMode ? [{ debugName: "requiresObjectType" }] : []));
7690
+ objectTypeIsStructure = computed(() => this.dictionary.isStructure(this.selectedField()?.dataType), ...(ngDevMode ? [{ debugName: "objectTypeIsStructure" }] : []));
7691
+ /** L'oggetto di un `ObjectProvided`, cioe' la parte prima del punto (§5.2). */
7692
+ providedObject = computed(() => {
7693
+ const reference = this.selectedField()?.objectFieldReference ?? '';
7694
+ const dot = reference.indexOf('.');
7695
+ return dot < 0 ? reference : reference.slice(0, dot);
7696
+ }, ...(ngDevMode ? [{ debugName: "providedObject" }] : []));
7697
+ providedField = computed(() => {
7698
+ const reference = this.selectedField()?.objectFieldReference ?? '';
7699
+ const dot = reference.indexOf('.');
7700
+ return dot < 0 ? '' : reference.slice(dot + 1);
7701
+ }, ...(ngDevMode ? [{ debugName: "providedField" }] : []));
7702
+ // -------------------------------------------------------------------------
7703
+ // Mutazioni sull'albero
7704
+ // -------------------------------------------------------------------------
7705
+ select(path) {
7706
+ this.selection.set([...path]);
7707
+ }
7708
+ /** Aggiunge un campo dentro il contenitore selezionato, o in fondo alla schermata. */
7709
+ addField(type) {
7710
+ const parent = this.parentForNewField();
7711
+ const base = this.dictionary.screenFieldType(type)?.label ?? type;
7712
+ const name = uniqueFlowName(base, this.store.usedNames());
7713
+ const field = { name, fieldType: type };
7714
+ if (this.dictionary.screenFieldStoresValue(type) && this.dictionary.screenFieldRequiresDataType(type)) {
7715
+ // Il tipo e' obbligatorio (`DATA_TYPE_MISSING`): nascere con `String` evita di
7716
+ // presentare come completo un campo che non lo e'. Resta cambiabile.
7717
+ field.dataType = 'String';
7718
+ }
7719
+ if (!this.dictionary.screenFieldIsContainer(type)) {
7720
+ field.fieldText = base;
7721
+ }
7722
+ let created;
7723
+ this.patch((node) => {
7724
+ created = insertField(node, parent, field);
7725
+ });
7726
+ if (created) {
7727
+ this.selection.set(created);
7728
+ }
7729
+ }
7730
+ /**
7731
+ * Dove finisce un campo nuovo: dentro il contenitore selezionato (o dentro il contenitore
7732
+ * che contiene il campo selezionato), altrimenti in fondo alla schermata. È il gesto che
7733
+ * l'utente si aspetta dopo aver cliccato una colonna.
7734
+ */
7735
+ parentForNewField() {
7736
+ const path = this.selectedPath();
7737
+ if (!path) {
7738
+ return [];
7739
+ }
7740
+ return this.isContainer(this.selectedField()) ? path : path.slice(0, -1);
7741
+ }
7742
+ removeSelected() {
7743
+ const path = this.selectedPath();
7744
+ if (!path) {
7745
+ return;
7746
+ }
7747
+ this.patch((node) => removeField(node, path));
7748
+ this.selection.set(null);
7749
+ }
7750
+ duplicateSelected() {
7751
+ const path = this.selectedPath();
7752
+ const field = this.selectedField();
7753
+ if (!path || !field) {
7754
+ return;
7755
+ }
7756
+ const name = uniqueFlowName(field.name || 'Campo', this.store.usedNames());
7757
+ let created;
7758
+ this.patch((node) => {
7759
+ created = duplicateField(node, path, name);
7760
+ });
7761
+ if (created) {
7762
+ this.selection.set(created);
7763
+ }
7764
+ }
7765
+ /**
7766
+ * Spostamento con i tasti: la stessa operazione del trascinamento, per chi non trascina e per
7767
+ * i casi in cui la lista e' piu' alta del pannello.
7768
+ */
7769
+ moveSelected(direction) {
7770
+ const path = this.selectedPath();
7771
+ if (!path) {
7772
+ return;
7773
+ }
7774
+ const parent = path.slice(0, -1);
7775
+ const index = path[path.length - 1];
7776
+ const siblings = parent.length
7777
+ ? (fieldAt(this.screen().fields, parent)?.fields ?? [])
7778
+ : (this.screen().fields ?? []);
7779
+ const target = index + direction;
7780
+ if (target < 0 || target >= siblings.length) {
7781
+ return;
7782
+ }
7783
+ this.applyMove(path, parent, target);
7784
+ }
7785
+ /**
7786
+ * Sposta il campo dentro il contenitore che lo precede. È la controparte esplicita del
7787
+ * trascinamento: dove l'albero e' lungo, mirare la riga giusta costa piu' di un clic.
7788
+ */
7789
+ indentSelected() {
7790
+ const target = this.indentTarget();
7791
+ const path = this.selectedPath();
7792
+ if (!path || !target) {
7793
+ return;
7794
+ }
7795
+ this.applyMove(path, target, (fieldAt(this.screen().fields, target)?.fields?.length ?? 0));
7796
+ }
7797
+ /** Il contenitore in cui «porta dentro» metterebbe il campo, o `null` se non ce n'e' uno. */
7798
+ indentTarget = computed(() => {
7799
+ const path = this.selectedPath();
7800
+ if (!path || path[path.length - 1] === 0) {
7801
+ // Primo della sua lista: non ha un fratello precedente in cui entrare.
7802
+ return null;
7803
+ }
7804
+ const previous = [...path.slice(0, -1), path[path.length - 1] - 1];
7805
+ return this.isContainer(fieldAt(this.screen().fields, previous)) ? previous : null;
7806
+ }, ...(ngDevMode ? [{ debugName: "indentTarget" }] : []));
7807
+ /** Estrae il campo dal proprio contenitore e lo mette subito dopo di esso. */
7808
+ outdentSelected() {
7809
+ const path = this.selectedPath();
7810
+ if (!path || path.length < 2) {
7811
+ return;
7812
+ }
7813
+ const grandParent = path.slice(0, -2);
7814
+ const parentIndex = path[path.length - 2];
7815
+ this.applyMove(path, grandParent, parentIndex + 1);
7816
+ }
7817
+ /**
7818
+ * Rilascio del trascinamento sulla lista **piatta**.
7819
+ *
7820
+ * Il contenitore di arrivo non lo dice il CDK — c'e' una sola lista — ma si deduce dalla riga
7821
+ * che **precede** il punto di rilascio, che e' anche cio' che l'utente vede: rilasciare subito
7822
+ * sotto una sezione o una colonna significa «mettilo dentro»; rilasciare sotto un campo
7823
+ * normale significa «mettilo accanto». Sono le due sole intenzioni possibili.
7824
+ */
7825
+ onDrop(event) {
7826
+ const nodes = this.tree();
7827
+ const dragged = nodes[event.previousIndex];
7828
+ if (!dragged) {
7829
+ return;
7830
+ }
7831
+ const from = dragged.path;
7832
+ // L'elenco come lo vede il CDK al momento del rilascio: senza la riga trascinata.
7833
+ const withoutDragged = nodes.filter((_, index) => index !== event.previousIndex);
7834
+ let before;
7835
+ for (let index = event.currentIndex - 1; index >= 0; index -= 1) {
7836
+ const candidate = withoutDragged[index];
7837
+ // Le righe del proprio sottoalbero non sono un ancoraggio valido: e' lì che il campo
7838
+ // finirebbe dentro se stesso.
7839
+ if (candidate && !isPathInside(from, candidate.path)) {
7840
+ before = candidate;
7841
+ break;
7842
+ }
7843
+ }
7844
+ if (!before) {
7845
+ // Rilasciato in cima: primo campo della schermata.
7846
+ this.applyMove(from, [], 0, true);
7847
+ return;
7848
+ }
7849
+ if (this.isContainer(before.field)) {
7850
+ this.applyMove(from, before.path, 0, true);
7851
+ return;
7852
+ }
7853
+ this.applyMove(from, before.path.slice(0, -1), before.path[before.path.length - 1] + 1, true);
7854
+ }
7855
+ /**
7856
+ * `preRemoval` distingue le due convenzioni sull'indice di arrivo: i comandi (su, giu',
7857
+ * porta fuori) ragionano sulla lista **dopo** l'estrazione, il rilascio ragiona su cio' che
7858
+ * si vedeva **prima**. Confonderle sposta di uno ogni trascinamento verso il basso.
7859
+ */
7860
+ applyMove(from, toParent, toIndex, preRemoval = false) {
7861
+ const fromParent = from.slice(0, -1);
7862
+ const target = preRemoval && samePath(fromParent, toParent) && from[from.length - 1] < toIndex
7863
+ ? toIndex - 1
7864
+ : toIndex;
7865
+ let moved;
7866
+ this.patch((node) => {
7867
+ moved = moveField(node, from, toParent, target);
7868
+ });
7869
+ if (moved) {
7870
+ this.selection.set(moved);
7871
+ }
7872
+ }
7873
+ // -------------------------------------------------------------------------
7874
+ // Mutazioni sul campo selezionato
7875
+ // -------------------------------------------------------------------------
7876
+ /** Scrive una proprieta' del campo selezionato; valore vuoto → chiave omessa (§2). */
7877
+ patchField(mutate) {
7878
+ const path = this.selectedPath();
7879
+ if (!path) {
7880
+ return;
7881
+ }
7882
+ this.patch((node) => {
7883
+ const field = fieldAt(node.fields, path);
7884
+ if (field) {
7885
+ mutate(field);
7886
+ }
7887
+ });
7888
+ }
7889
+ setFieldProperty(property, value) {
7890
+ this.patchField((field) => {
7891
+ if (value === undefined || value === null || value === '') {
7892
+ delete field[property];
7893
+ }
7894
+ else {
7895
+ field[property] = value;
7896
+ }
7897
+ });
7898
+ }
7899
+ /**
7900
+ * Il nome si applica sull'uscita dal campo e passa da `renameScreenField`: il campo e' una
7901
+ * risorsa, quindi la rinomina deve riscrivere i riferimenti che lo citano (§5.2, §13.8).
7902
+ */
7903
+ setFieldName(value) {
7904
+ const path = this.selectedPath();
7905
+ const trimmed = value.trim();
7906
+ if (!path || !trimmed || trimmed === this.selectedField()?.name) {
7907
+ return;
7908
+ }
7909
+ this.store.renameScreenField(this.name(), path, trimmed);
7910
+ }
7911
+ /**
7912
+ * Cambiare tipo **ripulisce** cio' che il tipo nuovo non prevede: choice su una textarea o un
7913
+ * `dataType` su un `DisplayText` sono `SCREEN_FIELD_CONFIGURATION_INVALID`, e lasciarli nel
7914
+ * documento significa lasciare un errore invisibile nell'editor.
7915
+ */
7916
+ setFieldType(type) {
7917
+ const entry = this.dictionary.screenFieldType(type);
7918
+ this.patchField((field) => {
7919
+ field.fieldType = type;
7920
+ if (!entry?.storesValue) {
7921
+ delete field.dataType;
7922
+ delete field.objectType;
7923
+ delete field.isRequired;
7924
+ delete field.isEditable;
7925
+ delete field.defaultValue;
7926
+ delete field.scale;
7927
+ delete field.maxLength;
7928
+ delete field.validationRule;
7929
+ delete field.inputsOnNextNavToAssocScrn;
7930
+ }
7931
+ if (!entry?.acceptsChoices) {
7932
+ delete field.choiceReferences;
7933
+ delete field.defaultSelectedChoiceReference;
7934
+ }
7935
+ if (!entry?.isContainer) {
7936
+ delete field.fields;
7937
+ delete field.regionContainerType;
7938
+ }
7939
+ if (type !== 'ComponentInstance') {
7940
+ delete field.extensionName;
7941
+ delete field.inputParameters;
7942
+ delete field.outputParameters;
7943
+ delete field.storeOutputAutomatically;
7944
+ }
7945
+ if (type !== 'ObjectProvided') {
7946
+ delete field.objectFieldReference;
7947
+ }
7948
+ });
7949
+ }
7950
+ setDefaultValue(value) {
7951
+ this.patchField((field) => {
7952
+ field.defaultValue = value;
7953
+ });
7954
+ }
7955
+ setNumberProperty(property, raw) {
7956
+ const parsed = Number(raw);
7957
+ this.setFieldProperty(property, raw === '' || Number.isNaN(parsed) ? undefined : parsed);
7958
+ }
7959
+ /** `isRequired` si scrive solo quando e' vero: il default e' facoltativo. */
7960
+ setRequired(value) {
7961
+ this.patchField((field) => {
7962
+ if (value) {
7963
+ field.isRequired = true;
7964
+ }
7965
+ else {
7966
+ delete field.isRequired;
7967
+ }
7968
+ });
7969
+ }
7970
+ /** `isEditable` ha default `true`: si scrive solo il `false`, che e' la scelta significativa. */
7971
+ setEditable(value) {
7972
+ this.patchField((field) => {
7973
+ if (value) {
7974
+ delete field.isEditable;
7975
+ }
7976
+ else {
7977
+ field.isEditable = false;
7978
+ }
7979
+ });
7980
+ }
7981
+ addChoice(name) {
7982
+ if (!name) {
7983
+ return;
7984
+ }
7985
+ this.patchField((field) => {
7986
+ field.choiceReferences ??= [];
7987
+ if (!field.choiceReferences.includes(name)) {
7988
+ // L'ordine e' quello di presentazione delle opzioni (§5.2): si aggiunge in fondo.
7989
+ field.choiceReferences.push(name);
7990
+ }
7991
+ });
7992
+ }
7993
+ removeChoice(index) {
7994
+ this.patchField((field) => {
7995
+ field.choiceReferences?.splice(index, 1);
7996
+ if (!field.choiceReferences?.length) {
7997
+ delete field.choiceReferences;
7998
+ }
7999
+ });
8000
+ }
8001
+ moveChoice(index, direction) {
8002
+ const target = index + direction;
8003
+ this.patchField((field) => {
8004
+ const list = field.choiceReferences;
8005
+ if (!list || target < 0 || target >= list.length) {
8006
+ return;
8007
+ }
8008
+ const [entry] = list.splice(index, 1);
8009
+ list.splice(target, 0, entry);
8010
+ });
8011
+ }
8012
+ setValidationRule(part, value) {
8013
+ this.patchField((field) => {
8014
+ const rule = { ...(field.validationRule ?? {}), [part]: value || undefined };
8015
+ if (!rule.formulaExpression && !rule.errorMessage) {
8016
+ delete field.validationRule;
8017
+ }
8018
+ else {
8019
+ field.validationRule = rule;
8020
+ }
8021
+ });
8022
+ }
8023
+ /** La regola di visibilita' e' un blocco condizioni come quello di una Decision (§5.2). */
8024
+ onVisibilityChanged(mutate) {
8025
+ this.patchField((field) => {
8026
+ field.visibilityRule ??= {};
8027
+ mutate(field.visibilityRule);
8028
+ if (!field.visibilityRule.conditions?.length && !field.visibilityRule.formula) {
8029
+ // Nessuna condizione = campo sempre visibile: meglio omettere la regola che scrivere
8030
+ // un contenitore vuoto (§2).
8031
+ delete field.visibilityRule;
8032
+ }
8033
+ });
8034
+ }
8035
+ /** Il blocco passato all'editor delle condizioni: mai `undefined`, altrimenti non si apre. */
8036
+ visibilityRule = computed(() => this.selectedField()?.visibilityRule ?? {}, ...(ngDevMode ? [{ debugName: "visibilityRule" }] : []));
8037
+ onComponentParametersChanged(mutate) {
8038
+ this.patchField((field) => mutate(field));
8039
+ }
8040
+ setStoreOutputAutomatically(value) {
8041
+ this.patchField((field) => {
8042
+ if (value) {
8043
+ field.storeOutputAutomatically = true;
8044
+ // Esclusivo con gli output espliciti (§5.2): scriverli entrambi e'
8045
+ // `OUTPUT_CONFIGURATION_CONFLICT`, quindi la scelta ne cancella l'altra.
8046
+ delete field.outputParameters;
8047
+ }
8048
+ else {
8049
+ delete field.storeOutputAutomatically;
8050
+ }
8051
+ });
8052
+ }
8053
+ /** `ObjectProvided`: oggetto e campo si scrivono in un unico `<Oggetto>.<Campo>` (§5.2). */
8054
+ setProvidedObject(value) {
8055
+ this.setFieldProperty('objectFieldReference', value ? `${value}.${this.providedField()}` : undefined);
8056
+ }
8057
+ setProvidedField(value) {
8058
+ const object = this.providedObject();
8059
+ this.setFieldProperty('objectFieldReference', object && value ? `${object}.${value}` : object || undefined);
8060
+ }
8061
+ // -------------------------------------------------------------------------
8062
+ // Schermata
8063
+ // -------------------------------------------------------------------------
8064
+ setScreenText(property, value) {
8065
+ this.setField(property, value);
8066
+ }
8067
+ /** I flag con default `true` si scrivono solo quando si nega, per non sporcare il diff. */
8068
+ setScreenFlag(flag, value) {
8069
+ this.patch((node) => {
8070
+ if (value) {
8071
+ delete node[flag];
8072
+ }
8073
+ else {
8074
+ node[flag] = false;
8075
+ }
8076
+ });
8077
+ }
8078
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.27", ngImport: i0, type: DynamicScreenInspectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8079
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.27", type: DynamicScreenInspectorComponent, isStandalone: true, selector: "fb-dynamic-screen-inspector", usesInheritance: true, ngImport: i0, template: "<p class=\"fb-callout\">\r\n Lo screen dinamico <strong>descrive i propri campi</strong>: la schermata la compone il flow, non un\r\n componente scritto a mano. Un campo che raccoglie un valore diventa una <strong>risorsa</strong> del flow,\r\n referenziabile per nome ovunque e di sola lettura.\r\n</p>\r\n\r\n<div class=\"fb-dyn\">\r\n <!-- ------------------------------------------------------------- albero -->\r\n <section class=\"fb-dyn__pane fb-dyn__pane--tree\" aria-label=\"Campi della schermata\">\r\n <header class=\"fb-dyn__pane-head\">\r\n <h3 class=\"fb-dyn__pane-title\">Campi</h3>\r\n <div class=\"fb-dyn__add\">\r\n <select class=\"fb-select fb-select--sm\" #newType [disabled]=\"!dictionary.screenFieldTypes().length\">\r\n @for (entry of dictionary.screenFieldTypes(); track entry.value) {\r\n <option [value]=\"entry.value\">{{ entry.label }}</option>\r\n }\r\n </select>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--primary fb-btn--sm\"\r\n [disabled]=\"!dictionary.screenFieldTypes().length\"\r\n (click)=\"addField(newType.value)\"\r\n >\r\n Aggiungi\r\n </button>\r\n </div>\r\n </header>\r\n\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 sa\r\n quali campi ammette ciascun tipo, e non li inventa.\r\n </p>\r\n }\r\n\r\n <p class=\"fb-dyn__hint\">\r\n Trascina per riordinare e per spostare un campo dentro una sezione o una colonna. L\u2019annidamento\r\n previsto e\u2019 <strong>sezione \u2192 colonne \u2192 campi</strong>.\r\n </p>\r\n\r\n <!--\r\n **Una sola** lista di rilascio sull'albero appiattito, non una per contenitore.\r\n Le drop list annidate del CDK sono ambigue proprio qui: il rettangolo di una sezione\r\n contiene quello delle sue colonne, e chi riceve il rilascio e' la lista registrata per\r\n prima \u2014 cioe' sempre la radice. Con la lista piatta il contenitore di arrivo si deduce\r\n dalla riga che precede il punto di rilascio, che e' anche cio' che si vede.\r\n -->\r\n <div\r\n cdkDropList\r\n class=\"fb-dyn__tree\"\r\n [cdkDropListData]=\"tree()\"\r\n (cdkDropListDropped)=\"onDrop($event)\"\r\n >\r\n @for (item of tree(); track $index) {\r\n <div cdkDrag class=\"fb-dyn__item\" [style.margin-left.px]=\"item.depth * 16\">\r\n <div\r\n class=\"fb-dyn__row\"\r\n [class.fb-dyn__row--selected]=\"isSelected(item.path)\"\r\n [class.fb-dyn__row--container]=\"isContainer(item.field)\"\r\n role=\"button\"\r\n tabindex=\"0\"\r\n (click)=\"select(item.path)\"\r\n (keydown.enter)=\"select(item.path)\"\r\n (keydown.space)=\"select(item.path)\"\r\n >\r\n <span class=\"fb-dyn__grip\" cdkDragHandle aria-hidden=\"true\">\u283F</span>\r\n <span class=\"fb-dyn__caption\">{{ captionOf(item.field) }}</span>\r\n <span class=\"fb-dyn__type\">{{ typeLabelOf(item.field) }}</span>\r\n @if (item.field.isRequired) {\r\n <span class=\"fb-dyn__badge fb-dyn__badge--required\" title=\"Obbligatorio\">*</span>\r\n }\r\n @if (item.field.visibilityRule?.conditions?.length) {\r\n <span class=\"fb-dyn__badge\" title=\"Ha una regola di visibilita\u2019\">\u25D0</span>\r\n }\r\n @if (!isContainer(item.field)) {\r\n <!-- La larghezza in dodicesimi, disegnata: e' l'unica parte di layout che il\r\n documento porta, e leggerla come numero non dice niente. -->\r\n <span class=\"fb-dyn__width\" [title]=\"widthOf(item.field) + '/12'\">\r\n <span class=\"fb-dyn__width-fill\" [style.width.%]=\"(widthOf(item.field) / 12) * 100\"></span>\r\n </span>\r\n }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n\r\n @if (isEmpty()) {\r\n <p class=\"fb-callout fb-callout--warn\">\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 <!-- --------------------------------------------------------- proprieta' -->\r\n <section class=\"fb-dyn__pane fb-dyn__pane--props\" aria-label=\"Proprieta\u2019 del campo\">\r\n @if (!selectedField()) {\r\n <p class=\"fb-empty\">Seleziona un campo a sinistra per configurarlo.</p>\r\n } @else {\r\n <header class=\"fb-dyn__pane-head\">\r\n <h3 class=\"fb-dyn__pane-title\">{{ captionOf(selectedField()!) }}</h3>\r\n <div class=\"fb-dyn__pane-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 dentro il contenitore precedente\"\r\n [disabled]=\"!indentTarget()\"\r\n (click)=\"indentSelected()\"\r\n >\r\n \u21E5\r\n </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]=\"(selectedPath()?.length || 0) < 2\"\r\n (click)=\"outdentSelected()\"\r\n >\r\n \u21E4\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--sm\" (click)=\"duplicateSelected()\">Duplica</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--danger fb-btn--sm\" (click)=\"removeSelected()\">Elimina</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 @for (entry of dictionary.screenFieldTypes(); track entry.value) {\r\n <option [value]=\"entry.value\">{{ entry.label }}</option>\r\n }\r\n </select>\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()) {\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 sola\r\n lettura per il flow \u2014 un Assignment che ci scrive e\u2019 TARGET_NOT_WRITABLE.\r\n </p>\r\n }\r\n </div>\r\n\r\n @if (!selectedType()?.isContainer && selectedField()!.fieldType !== 'ComponentInstance') {\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\">Supporta i merge field <code>&#123;!Riferimento&#125;</code>.</p>\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-dyn__width-editor\">\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-dyn__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 <!-- ------------------------------------------------ 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\r\n class=\"fb-field__label\"\r\n [class.fb-field__label--required]=\"objectTypeIsStructure()\"\r\n >\r\n {{ objectTypeIsStructure() ? 'Classe' : selectedField()!.dataType === 'Enum' ? 'Tipo di enumerazione' : 'Oggetto' }}\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 label=\"Valore di default\"\r\n (valueChange)=\"setDefaultValue($event)\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-dyn__grid\">\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 <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 </div>\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 <input\r\n class=\"fb-input fb-input--mono\"\r\n placeholder=\"Espressione, es. LEN(Nome) > 3\"\r\n [value]=\"selectedField()!.validationRule?.formulaExpression || ''\"\r\n (change)=\"setValidationRule('formulaExpression', $any($event.target).value)\"\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 e non la verifica.\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 <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 <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 <fb-name-picker\r\n [options]=\"choiceOptions()\"\r\n label=\"Choice\"\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 </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 </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 <!-- --------------------------------------------------- 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 \u2014\r\n e passano dallo stesso catalogo. Un <code>ComponentInstance</code> non ha un valore proprio: lo\r\n 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 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 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 <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 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\r\n <fb-parameter-editor\r\n [holder]=\"$any(selectedField())\"\r\n [catalogParameters]=\"componentParameterList()\"\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 in\r\n 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 [issuePath]=\"'fields[' + (selectedField()!.name || '') + '].visibilityRule'\"\r\n (changed)=\"onVisibilityChanged($event)\"\r\n />\r\n </fieldset>\r\n }\r\n </section>\r\n</div>\r\n\r\n<!-- --------------------------------------------------------------- schermata -->\r\n<fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Schermata</legend>\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 <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 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 <div class=\"fb-dyn__grid\">\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 </div>\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 @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 l\u2019utente\r\n resterebbe bloccato (SCREEN_DEAD_END).\r\n </p>\r\n }\r\n</fieldset>\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", styles: [".fb-dyn{display:grid;grid-template-columns:minmax(240px,320px) minmax(0,1fr);gap:14px;align-items:start;margin-bottom:14px}@media(max-width:900px){.fb-dyn{grid-template-columns:minmax(0,1fr)}}.fb-dyn__pane{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-dyn__pane--tree{position:sticky;top:0}.fb-dyn__pane--props{background:var(--fb-surface, #fff)}.fb-dyn__pane-head{display:flex;align-items:center;gap:8px;margin-bottom:8px}.fb-dyn__pane-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-dyn__pane-actions,.fb-dyn__add{display:flex;flex-wrap:wrap;gap:4px}.fb-dyn__add .fb-select--sm{width:auto;max-width:150px}.fb-btn--sm{padding:3px 7px;font-size:11px}.fb-select--sm{padding:3px 6px;font-size:11px}.fb-dyn__hint{margin:0 0 8px;font-size:11px;color:var(--fb-text-muted, #6b7086)}.fb-dyn__tree{max-height:420px;overflow-y:auto;overscroll-behavior:contain}.fb-dyn__item{background:transparent}.fb-dyn__row{display:flex;align-items:center;gap:6px;padding:4px 6px;margin-bottom:3px;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-xs, 6px);background:var(--fb-surface, #fff);cursor:pointer}.fb-dyn__row:hover{border-color:var(--fb-border-strong, #cfd4de)}.fb-dyn__row--selected{border-color:var(--fb-accent, #4f6ef7);box-shadow:inset 0 0 0 1px var(--fb-accent, #4f6ef7)}.fb-dyn__row--container{background:var(--fb-surface-sunken, #eef0f4);font-weight:600}.fb-dyn__grip{color:var(--fb-text-subtle, #98a2b3);cursor:grab}.fb-dyn__caption{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px;color:var(--fb-text, #1a1c23)}.fb-dyn__type{font-size:10px;color:var(--fb-text-subtle, #98a2b3)}.fb-dyn__badge{font-size:11px;color:var(--fb-text-muted, #6b7086)}.fb-dyn__badge--required{color:var(--fb-error, #dc2626);font-weight:700}.fb-dyn__width{flex:none;width:34px;height:6px;border-radius:3px;background:var(--fb-surface-sunken, #eef0f4);overflow:hidden}.fb-dyn__width-fill{display:block;height:100%;background:var(--fb-accent, #4f6ef7)}.fb-dyn__grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:0 10px}.fb-dyn__width-editor{display:flex;align-items:center;gap:8px}.fb-dyn__width-editor input[type=range]{flex:1;min-width:0}.fb-dyn__width-value{font-size:11px;color:var(--fb-text-muted, #6b7086)}.fb-dyn__item.cdk-drag-placeholder{opacity:.4}.fb-dyn__tree.cdk-drop-list-dragging .fb-dyn__row{transition:transform .12s cubic-bezier(0,0,.2,1)}\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: "directive", type: CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "component", type: ConditionEditorComponent, selector: "fb-condition-editor", inputs: ["holder", "title", "allowFormula", "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: NamePickerComponent, selector: "fb-name-picker", inputs: ["value", "options", "label", "placeholder", "disabled", "unknownMessage", "unknownSeverity", "emptyMessage", "isMono"], outputs: ["valueChange"] }, { 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"], outputs: ["changed"] }, { kind: "directive", type: SelectValueDirective, selector: "select[fbValue]", inputs: ["fbValue"] }, { 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", "disabled", "allowFormula"], outputs: ["valueChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
8080
+ }
8081
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImport: i0, type: DynamicScreenInspectorComponent, decorators: [{
8082
+ type: Component,
8083
+ args: [{ selector: 'fb-dynamic-screen-inspector', standalone: true, imports: [
8084
+ CdkDrag,
8085
+ CdkDragHandle,
8086
+ CdkDropList,
8087
+ ConditionEditorComponent,
8088
+ ConnectorEditorComponent,
8089
+ FieldPickerComponent,
8090
+ NamePickerComponent,
8091
+ ObjectPickerComponent,
8092
+ ParameterEditorComponent,
8093
+ SelectValueDirective,
8094
+ StructurePickerComponent,
8095
+ ValueEditorComponent,
8096
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<p class=\"fb-callout\">\r\n Lo screen dinamico <strong>descrive i propri campi</strong>: la schermata la compone il flow, non un\r\n componente scritto a mano. Un campo che raccoglie un valore diventa una <strong>risorsa</strong> del flow,\r\n referenziabile per nome ovunque e di sola lettura.\r\n</p>\r\n\r\n<div class=\"fb-dyn\">\r\n <!-- ------------------------------------------------------------- albero -->\r\n <section class=\"fb-dyn__pane fb-dyn__pane--tree\" aria-label=\"Campi della schermata\">\r\n <header class=\"fb-dyn__pane-head\">\r\n <h3 class=\"fb-dyn__pane-title\">Campi</h3>\r\n <div class=\"fb-dyn__add\">\r\n <select class=\"fb-select fb-select--sm\" #newType [disabled]=\"!dictionary.screenFieldTypes().length\">\r\n @for (entry of dictionary.screenFieldTypes(); track entry.value) {\r\n <option [value]=\"entry.value\">{{ entry.label }}</option>\r\n }\r\n </select>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--primary fb-btn--sm\"\r\n [disabled]=\"!dictionary.screenFieldTypes().length\"\r\n (click)=\"addField(newType.value)\"\r\n >\r\n Aggiungi\r\n </button>\r\n </div>\r\n </header>\r\n\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 sa\r\n quali campi ammette ciascun tipo, e non li inventa.\r\n </p>\r\n }\r\n\r\n <p class=\"fb-dyn__hint\">\r\n Trascina per riordinare e per spostare un campo dentro una sezione o una colonna. L\u2019annidamento\r\n previsto e\u2019 <strong>sezione \u2192 colonne \u2192 campi</strong>.\r\n </p>\r\n\r\n <!--\r\n **Una sola** lista di rilascio sull'albero appiattito, non una per contenitore.\r\n Le drop list annidate del CDK sono ambigue proprio qui: il rettangolo di una sezione\r\n contiene quello delle sue colonne, e chi riceve il rilascio e' la lista registrata per\r\n prima \u2014 cioe' sempre la radice. Con la lista piatta il contenitore di arrivo si deduce\r\n dalla riga che precede il punto di rilascio, che e' anche cio' che si vede.\r\n -->\r\n <div\r\n cdkDropList\r\n class=\"fb-dyn__tree\"\r\n [cdkDropListData]=\"tree()\"\r\n (cdkDropListDropped)=\"onDrop($event)\"\r\n >\r\n @for (item of tree(); track $index) {\r\n <div cdkDrag class=\"fb-dyn__item\" [style.margin-left.px]=\"item.depth * 16\">\r\n <div\r\n class=\"fb-dyn__row\"\r\n [class.fb-dyn__row--selected]=\"isSelected(item.path)\"\r\n [class.fb-dyn__row--container]=\"isContainer(item.field)\"\r\n role=\"button\"\r\n tabindex=\"0\"\r\n (click)=\"select(item.path)\"\r\n (keydown.enter)=\"select(item.path)\"\r\n (keydown.space)=\"select(item.path)\"\r\n >\r\n <span class=\"fb-dyn__grip\" cdkDragHandle aria-hidden=\"true\">\u283F</span>\r\n <span class=\"fb-dyn__caption\">{{ captionOf(item.field) }}</span>\r\n <span class=\"fb-dyn__type\">{{ typeLabelOf(item.field) }}</span>\r\n @if (item.field.isRequired) {\r\n <span class=\"fb-dyn__badge fb-dyn__badge--required\" title=\"Obbligatorio\">*</span>\r\n }\r\n @if (item.field.visibilityRule?.conditions?.length) {\r\n <span class=\"fb-dyn__badge\" title=\"Ha una regola di visibilita\u2019\">\u25D0</span>\r\n }\r\n @if (!isContainer(item.field)) {\r\n <!-- La larghezza in dodicesimi, disegnata: e' l'unica parte di layout che il\r\n documento porta, e leggerla come numero non dice niente. -->\r\n <span class=\"fb-dyn__width\" [title]=\"widthOf(item.field) + '/12'\">\r\n <span class=\"fb-dyn__width-fill\" [style.width.%]=\"(widthOf(item.field) / 12) * 100\"></span>\r\n </span>\r\n }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n\r\n @if (isEmpty()) {\r\n <p class=\"fb-callout fb-callout--warn\">\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 <!-- --------------------------------------------------------- proprieta' -->\r\n <section class=\"fb-dyn__pane fb-dyn__pane--props\" aria-label=\"Proprieta\u2019 del campo\">\r\n @if (!selectedField()) {\r\n <p class=\"fb-empty\">Seleziona un campo a sinistra per configurarlo.</p>\r\n } @else {\r\n <header class=\"fb-dyn__pane-head\">\r\n <h3 class=\"fb-dyn__pane-title\">{{ captionOf(selectedField()!) }}</h3>\r\n <div class=\"fb-dyn__pane-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 dentro il contenitore precedente\"\r\n [disabled]=\"!indentTarget()\"\r\n (click)=\"indentSelected()\"\r\n >\r\n \u21E5\r\n </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]=\"(selectedPath()?.length || 0) < 2\"\r\n (click)=\"outdentSelected()\"\r\n >\r\n \u21E4\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--sm\" (click)=\"duplicateSelected()\">Duplica</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--danger fb-btn--sm\" (click)=\"removeSelected()\">Elimina</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 @for (entry of dictionary.screenFieldTypes(); track entry.value) {\r\n <option [value]=\"entry.value\">{{ entry.label }}</option>\r\n }\r\n </select>\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()) {\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 sola\r\n lettura per il flow \u2014 un Assignment che ci scrive e\u2019 TARGET_NOT_WRITABLE.\r\n </p>\r\n }\r\n </div>\r\n\r\n @if (!selectedType()?.isContainer && selectedField()!.fieldType !== 'ComponentInstance') {\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\">Supporta i merge field <code>&#123;!Riferimento&#125;</code>.</p>\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-dyn__width-editor\">\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-dyn__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 <!-- ------------------------------------------------ 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\r\n class=\"fb-field__label\"\r\n [class.fb-field__label--required]=\"objectTypeIsStructure()\"\r\n >\r\n {{ objectTypeIsStructure() ? 'Classe' : selectedField()!.dataType === 'Enum' ? 'Tipo di enumerazione' : 'Oggetto' }}\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 label=\"Valore di default\"\r\n (valueChange)=\"setDefaultValue($event)\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-dyn__grid\">\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 <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 </div>\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 <input\r\n class=\"fb-input fb-input--mono\"\r\n placeholder=\"Espressione, es. LEN(Nome) > 3\"\r\n [value]=\"selectedField()!.validationRule?.formulaExpression || ''\"\r\n (change)=\"setValidationRule('formulaExpression', $any($event.target).value)\"\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 e non la verifica.\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 <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 <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 <fb-name-picker\r\n [options]=\"choiceOptions()\"\r\n label=\"Choice\"\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 </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 </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 <!-- --------------------------------------------------- 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 \u2014\r\n e passano dallo stesso catalogo. Un <code>ComponentInstance</code> non ha un valore proprio: lo\r\n 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 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 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 <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 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\r\n <fb-parameter-editor\r\n [holder]=\"$any(selectedField())\"\r\n [catalogParameters]=\"componentParameterList()\"\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 in\r\n 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 [issuePath]=\"'fields[' + (selectedField()!.name || '') + '].visibilityRule'\"\r\n (changed)=\"onVisibilityChanged($event)\"\r\n />\r\n </fieldset>\r\n }\r\n </section>\r\n</div>\r\n\r\n<!-- --------------------------------------------------------------- schermata -->\r\n<fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Schermata</legend>\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 <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 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 <div class=\"fb-dyn__grid\">\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 </div>\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 @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 l\u2019utente\r\n resterebbe bloccato (SCREEN_DEAD_END).\r\n </p>\r\n }\r\n</fieldset>\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", styles: [".fb-dyn{display:grid;grid-template-columns:minmax(240px,320px) minmax(0,1fr);gap:14px;align-items:start;margin-bottom:14px}@media(max-width:900px){.fb-dyn{grid-template-columns:minmax(0,1fr)}}.fb-dyn__pane{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-dyn__pane--tree{position:sticky;top:0}.fb-dyn__pane--props{background:var(--fb-surface, #fff)}.fb-dyn__pane-head{display:flex;align-items:center;gap:8px;margin-bottom:8px}.fb-dyn__pane-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-dyn__pane-actions,.fb-dyn__add{display:flex;flex-wrap:wrap;gap:4px}.fb-dyn__add .fb-select--sm{width:auto;max-width:150px}.fb-btn--sm{padding:3px 7px;font-size:11px}.fb-select--sm{padding:3px 6px;font-size:11px}.fb-dyn__hint{margin:0 0 8px;font-size:11px;color:var(--fb-text-muted, #6b7086)}.fb-dyn__tree{max-height:420px;overflow-y:auto;overscroll-behavior:contain}.fb-dyn__item{background:transparent}.fb-dyn__row{display:flex;align-items:center;gap:6px;padding:4px 6px;margin-bottom:3px;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-xs, 6px);background:var(--fb-surface, #fff);cursor:pointer}.fb-dyn__row:hover{border-color:var(--fb-border-strong, #cfd4de)}.fb-dyn__row--selected{border-color:var(--fb-accent, #4f6ef7);box-shadow:inset 0 0 0 1px var(--fb-accent, #4f6ef7)}.fb-dyn__row--container{background:var(--fb-surface-sunken, #eef0f4);font-weight:600}.fb-dyn__grip{color:var(--fb-text-subtle, #98a2b3);cursor:grab}.fb-dyn__caption{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px;color:var(--fb-text, #1a1c23)}.fb-dyn__type{font-size:10px;color:var(--fb-text-subtle, #98a2b3)}.fb-dyn__badge{font-size:11px;color:var(--fb-text-muted, #6b7086)}.fb-dyn__badge--required{color:var(--fb-error, #dc2626);font-weight:700}.fb-dyn__width{flex:none;width:34px;height:6px;border-radius:3px;background:var(--fb-surface-sunken, #eef0f4);overflow:hidden}.fb-dyn__width-fill{display:block;height:100%;background:var(--fb-accent, #4f6ef7)}.fb-dyn__grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:0 10px}.fb-dyn__width-editor{display:flex;align-items:center;gap:8px}.fb-dyn__width-editor input[type=range]{flex:1;min-width:0}.fb-dyn__width-value{font-size:11px;color:var(--fb-text-muted, #6b7086)}.fb-dyn__item.cdk-drag-placeholder{opacity:.4}.fb-dyn__tree.cdk-drop-list-dragging .fb-dyn__row{transition:transform .12s cubic-bezier(0,0,.2,1)}\n"] }]
8097
+ }], ctorParameters: () => [] });
8098
+
7195
8099
  /**
7196
8100
  * Loop — FRONTEND.md §5.4.
7197
8101
  *
@@ -9010,7 +9914,7 @@ class ElementInspectorComponent {
9010
9914
  return { x: node?.locationX ?? 0, y: node?.locationY ?? 0 };
9011
9915
  }, ...(ngDevMode ? [{ debugName: "position" }] : []));
9012
9916
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.27", ngImport: i0, type: ElementInspectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
9013
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.27", type: ElementInspectorComponent, isStandalone: true, selector: "fb-element-inspector", inputs: { selectedName: { classPropertyName: "selectedName", publicName: "selectedName", isSignal: true, isRequired: false, transformFunction: null }, showHeader: { classPropertyName: "showHeader", publicName: "showHeader", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closed: "closed", removeRequested: "removeRequested", duplicateRequested: "duplicateRequested" }, ngImport: i0, template: "@if (!selectedName()) {\r\n <p class=\"fb-inspector__empty\">\r\n Seleziona un elemento sul canvas per modificarlo, oppure trascina un elemento dalla palette.\r\n </p>\r\n} @else {\r\n @if (showHeader()) {\r\n <header class=\"fb-inspector__header\">\r\n <div>\r\n <span class=\"fb-inspector__type\">{{ typeLabel() }}</span>\r\n <h2 class=\"fb-inspector__title\">\r\n {{ isStart() ? 'Avvio del flow' : node()?.label || selectedName() }}\r\n </h2>\r\n </div>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" aria-label=\"Chiudi\" (click)=\"close()\">\r\n \u00D7\r\n </button>\r\n </header>\r\n }\r\n\r\n <div class=\"fb-inspector__body\">\r\n @if (issues().length) {\r\n <ul class=\"fb-inspector__issues\">\r\n <!-- `$index`: due rilievi con lo stesso codice e lo stesso path sono possibili. -->\r\n @for (issue of issues(); track $index) {\r\n <li\r\n class=\"fb-inspector__issue\"\r\n [class.fb-inspector__issue--error]=\"issue.severity === 'Error'\"\r\n [class.fb-inspector__issue--warning]=\"issue.severity === 'Warning'\"\r\n >\r\n <span class=\"fb-inspector__issue-code\">{{ issue.code }}</span>\r\n {{ issue.message }}\r\n @if (issue.path) {\r\n <span class=\"fb-inspector__issue-path\">{{ issue.path }}</span>\r\n }\r\n </li>\r\n }\r\n </ul>\r\n }\r\n\r\n @if (isUnsupported()) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n Questo tipo di elemento non e\u2019 supportato dal motore: il flow che lo contiene non parte\r\n (ELEMENT_NOT_SUPPORTED). Non e\u2019 creabile dalla palette; se e\u2019 arrivato da un documento importato,\r\n va rimosso.\r\n </p>\r\n }\r\n\r\n @if (!isStart()) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Etichetta</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"$any(node()?.label) || ''\"\r\n placeholder=\"Nome mostrato sul canvas\"\r\n (input)=\"setLabel($any($event.target).value)\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Nome tecnico</label>\r\n @if (pendingName() === null) {\r\n <div class=\"fb-field__row\">\r\n <input class=\"fb-input fb-input--mono\" [value]=\"selectedName() || ''\" readonly />\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"startRename()\">Rinomina</button>\r\n </div>\r\n <p class=\"fb-field__hint\">\r\n \u00C8 l\u2019identificatore con cui i riferimenti raggiungono questo elemento.\r\n @if (referenceCount() > 1) {\r\n Compare {{ referenceCount() }} volte nel documento.\r\n }\r\n </p>\r\n } @else {\r\n <input\r\n class=\"fb-input fb-input--mono\"\r\n [class.fb-input--invalid]=\"!!nameError()\"\r\n [value]=\"pendingName() || ''\"\r\n (input)=\"onPendingNameInput($any($event.target).value)\"\r\n />\r\n @if (nameError()) {\r\n <p class=\"fb-field__error\">{{ nameError() }}</p>\r\n } @else {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n La rinomina riscrive tutti i riferimenti che puntano a questo elemento\r\n ({{ referenceCount() }} occorrenze): nessuna primitiva del backend lo fa, lo fa l\u2019editor.\r\n </p>\r\n }\r\n <div class=\"fb-field__row\">\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" [disabled]=\"!canRename()\" (click)=\"applyRename()\">\r\n Applica\r\n </button>\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"suggestNameFromLabel()\">Genera dalla label</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost\" (click)=\"cancelRename()\">Annulla</button>\r\n </div>\r\n }\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Descrizione</label>\r\n <textarea\r\n class=\"fb-textarea\"\r\n [value]=\"$any(node()?.description) || ''\"\r\n (input)=\"setDescription($any($event.target).value)\"\r\n ></textarea>\r\n </div>\r\n }\r\n\r\n <p class=\"fb-inspector__position\">\r\n Posizione sul canvas: {{ position().x }}, {{ position().y }}\r\n </p>\r\n\r\n <hr class=\"fb-inspector__divider\" />\r\n\r\n @if (isStart()) {\r\n <fb-start-inspector />\r\n } @else if (node()) {\r\n @switch (type()) {\r\n @case ('Screen') {\r\n <fb-screen-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('Assignment') {\r\n <fb-assignment-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('Decision') {\r\n <fb-decision-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('Loop') {\r\n <fb-loop-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('CollectionProcessor') {\r\n <fb-collection-processor-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('CustomError') {\r\n <fb-custom-error-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('Wait') {\r\n <fb-wait-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('RecordLookup') {\r\n <fb-record-lookup-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('RecordCreate') {\r\n <fb-record-write-inspector [name]=\"selectedName()!\" [node]=\"node()!\" type=\"RecordCreate\" />\r\n }\r\n @case ('RecordUpdate') {\r\n <fb-record-write-inspector [name]=\"selectedName()!\" [node]=\"node()!\" type=\"RecordUpdate\" />\r\n }\r\n @case ('RecordDelete') {\r\n <fb-record-write-inspector [name]=\"selectedName()!\" [node]=\"node()!\" type=\"RecordDelete\" />\r\n }\r\n @case ('RecordRollback') {\r\n <fb-record-rollback-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('ActionCall') {\r\n <fb-action-call-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('ScriptCall') {\r\n <fb-script-call-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('Subflow') {\r\n <fb-subflow-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('Transform') {\r\n <fb-transform-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('OrchestratedStage') {\r\n <fb-orchestrated-stage-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @default {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Nessun form specifico per il tipo \u00AB{{ type() }}\u00BB: i campi comuni sono modificabili qui sopra.\r\n </p>\r\n }\r\n }\r\n }\r\n\r\n @if (!isStart()) {\r\n <hr class=\"fb-inspector__divider\" />\r\n <div class=\"fb-field__row\">\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"requestDuplicate()\">Duplica</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--danger\" (click)=\"requestRemove()\">Elimina</button>\r\n </div>\r\n }\r\n </div>\r\n}\r\n", styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0;background:var(--fb-surface, #fff)}.fb-inspector__empty{margin:16px 12px;font-size:12px;line-height:1.5;color:var(--fb-text-muted, #667085)}.fb-inspector__header{display:flex;align-items:flex-start;justify-content:space-between;gap:8px;padding:10px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-inspector__type{font-size:10px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;color:var(--fb-text-subtle, #98a2b3)}.fb-inspector__title{margin:2px 0 0;font-size:14px;font-weight:600;color:var(--fb-text, #1d2939)}.fb-inspector__body{flex:1;min-height:0;overflow-y:auto;padding:14px}.fb-inspector__issues{margin:0 0 12px;padding:0;list-style:none}.fb-inspector__issue{margin-bottom:4px;padding:6px 8px;border-left:3px solid var(--fb-text-subtle, #98a2b3);border-radius:3px;background:var(--fb-surface-alt, #f8f9fb);font-size:11px;line-height:1.4;color:var(--fb-text, #1d2939)}.fb-inspector__issue--error{border-left-color:var(--fb-error, #c9372c)}.fb-inspector__issue--warning{border-left-color:var(--fb-warning, #b7791f)}.fb-inspector__issue-code{display:block;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:9px;color:var(--fb-text-subtle, #98a2b3)}.fb-inspector__issue-path{display:block;margin-top:2px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:9px;color:var(--fb-text-muted, #667085)}.fb-inspector__position{margin:0;font-size:10px;color:var(--fb-text-subtle, #98a2b3)}.fb-inspector__divider{margin:12px 0;border:0;border-top:1px solid var(--fb-border-subtle, #e6e9ee)}\n"], dependencies: [{ kind: "component", type: ActionCallInspectorComponent, selector: "fb-action-call-inspector" }, { kind: "component", type: AssignmentInspectorComponent, selector: "fb-assignment-inspector" }, { kind: "component", type: CollectionProcessorInspectorComponent, selector: "fb-collection-processor-inspector" }, { kind: "component", type: CustomErrorInspectorComponent, selector: "fb-custom-error-inspector" }, { kind: "component", type: DecisionInspectorComponent, selector: "fb-decision-inspector" }, { kind: "component", type: LoopInspectorComponent, selector: "fb-loop-inspector" }, { kind: "component", type: OrchestratedStageInspectorComponent, selector: "fb-orchestrated-stage-inspector" }, { kind: "component", type: RecordLookupInspectorComponent, selector: "fb-record-lookup-inspector" }, { kind: "component", type: RecordRollbackInspectorComponent, selector: "fb-record-rollback-inspector" }, { kind: "component", type: RecordWriteInspectorComponent, selector: "fb-record-write-inspector", inputs: ["type"] }, { kind: "component", type: ScreenInspectorComponent, selector: "fb-screen-inspector" }, { kind: "component", type: ScriptCallInspectorComponent, selector: "fb-script-call-inspector" }, { kind: "component", type: StartInspectorComponent, selector: "fb-start-inspector" }, { kind: "component", type: SubflowInspectorComponent, selector: "fb-subflow-inspector" }, { kind: "component", type: TransformInspectorComponent, selector: "fb-transform-inspector" }, { kind: "component", type: WaitInspectorComponent, selector: "fb-wait-inspector" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
9917
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.27", type: ElementInspectorComponent, isStandalone: true, selector: "fb-element-inspector", inputs: { selectedName: { classPropertyName: "selectedName", publicName: "selectedName", isSignal: true, isRequired: false, transformFunction: null }, showHeader: { classPropertyName: "showHeader", publicName: "showHeader", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closed: "closed", removeRequested: "removeRequested", duplicateRequested: "duplicateRequested" }, ngImport: i0, template: "@if (!selectedName()) {\r\n <p class=\"fb-inspector__empty\">\r\n Seleziona un elemento sul canvas per modificarlo, oppure trascina un elemento dalla palette.\r\n </p>\r\n} @else {\r\n @if (showHeader()) {\r\n <header class=\"fb-inspector__header\">\r\n <div>\r\n <span class=\"fb-inspector__type\">{{ typeLabel() }}</span>\r\n <h2 class=\"fb-inspector__title\">\r\n {{ isStart() ? 'Avvio del flow' : node()?.label || selectedName() }}\r\n </h2>\r\n </div>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" aria-label=\"Chiudi\" (click)=\"close()\">\r\n \u00D7\r\n </button>\r\n </header>\r\n }\r\n\r\n <div class=\"fb-inspector__body\">\r\n @if (issues().length) {\r\n <ul class=\"fb-inspector__issues\">\r\n <!-- `$index`: due rilievi con lo stesso codice e lo stesso path sono possibili. -->\r\n @for (issue of issues(); track $index) {\r\n <li\r\n class=\"fb-inspector__issue\"\r\n [class.fb-inspector__issue--error]=\"issue.severity === 'Error'\"\r\n [class.fb-inspector__issue--warning]=\"issue.severity === 'Warning'\"\r\n >\r\n <span class=\"fb-inspector__issue-code\">{{ issue.code }}</span>\r\n {{ issue.message }}\r\n @if (issue.path) {\r\n <span class=\"fb-inspector__issue-path\">{{ issue.path }}</span>\r\n }\r\n </li>\r\n }\r\n </ul>\r\n }\r\n\r\n @if (isUnsupported()) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n Questo tipo di elemento non e\u2019 supportato dal motore: il flow che lo contiene non parte\r\n (ELEMENT_NOT_SUPPORTED). Non e\u2019 creabile dalla palette; se e\u2019 arrivato da un documento importato,\r\n va rimosso.\r\n </p>\r\n }\r\n\r\n @if (!isStart()) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Etichetta</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"$any(node()?.label) || ''\"\r\n placeholder=\"Nome mostrato sul canvas\"\r\n (input)=\"setLabel($any($event.target).value)\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Nome tecnico</label>\r\n @if (pendingName() === null) {\r\n <div class=\"fb-field__row\">\r\n <input class=\"fb-input fb-input--mono\" [value]=\"selectedName() || ''\" readonly />\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"startRename()\">Rinomina</button>\r\n </div>\r\n <p class=\"fb-field__hint\">\r\n \u00C8 l\u2019identificatore con cui i riferimenti raggiungono questo elemento.\r\n @if (referenceCount() > 1) {\r\n Compare {{ referenceCount() }} volte nel documento.\r\n }\r\n </p>\r\n } @else {\r\n <input\r\n class=\"fb-input fb-input--mono\"\r\n [class.fb-input--invalid]=\"!!nameError()\"\r\n [value]=\"pendingName() || ''\"\r\n (input)=\"onPendingNameInput($any($event.target).value)\"\r\n />\r\n @if (nameError()) {\r\n <p class=\"fb-field__error\">{{ nameError() }}</p>\r\n } @else {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n La rinomina riscrive tutti i riferimenti che puntano a questo elemento\r\n ({{ referenceCount() }} occorrenze): nessuna primitiva del backend lo fa, lo fa l\u2019editor.\r\n </p>\r\n }\r\n <div class=\"fb-field__row\">\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" [disabled]=\"!canRename()\" (click)=\"applyRename()\">\r\n Applica\r\n </button>\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"suggestNameFromLabel()\">Genera dalla label</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost\" (click)=\"cancelRename()\">Annulla</button>\r\n </div>\r\n }\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Descrizione</label>\r\n <textarea\r\n class=\"fb-textarea\"\r\n [value]=\"$any(node()?.description) || ''\"\r\n (input)=\"setDescription($any($event.target).value)\"\r\n ></textarea>\r\n </div>\r\n }\r\n\r\n <p class=\"fb-inspector__position\">\r\n Posizione sul canvas: {{ position().x }}, {{ position().y }}\r\n </p>\r\n\r\n <hr class=\"fb-inspector__divider\" />\r\n\r\n @if (isStart()) {\r\n <fb-start-inspector />\r\n } @else if (node()) {\r\n @switch (type()) {\r\n @case ('Screen') {\r\n <fb-screen-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('DynamicScreen') {\r\n <fb-dynamic-screen-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('Assignment') {\r\n <fb-assignment-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('Decision') {\r\n <fb-decision-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('Loop') {\r\n <fb-loop-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('CollectionProcessor') {\r\n <fb-collection-processor-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('CustomError') {\r\n <fb-custom-error-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('Wait') {\r\n <fb-wait-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('RecordLookup') {\r\n <fb-record-lookup-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('RecordCreate') {\r\n <fb-record-write-inspector [name]=\"selectedName()!\" [node]=\"node()!\" type=\"RecordCreate\" />\r\n }\r\n @case ('RecordUpdate') {\r\n <fb-record-write-inspector [name]=\"selectedName()!\" [node]=\"node()!\" type=\"RecordUpdate\" />\r\n }\r\n @case ('RecordDelete') {\r\n <fb-record-write-inspector [name]=\"selectedName()!\" [node]=\"node()!\" type=\"RecordDelete\" />\r\n }\r\n @case ('RecordRollback') {\r\n <fb-record-rollback-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('ActionCall') {\r\n <fb-action-call-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('ScriptCall') {\r\n <fb-script-call-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('Subflow') {\r\n <fb-subflow-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('Transform') {\r\n <fb-transform-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('OrchestratedStage') {\r\n <fb-orchestrated-stage-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @default {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Nessun form specifico per il tipo \u00AB{{ type() }}\u00BB: i campi comuni sono modificabili qui sopra.\r\n </p>\r\n }\r\n }\r\n }\r\n\r\n @if (!isStart()) {\r\n <hr class=\"fb-inspector__divider\" />\r\n <div class=\"fb-field__row\">\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"requestDuplicate()\">Duplica</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--danger\" (click)=\"requestRemove()\">Elimina</button>\r\n </div>\r\n }\r\n </div>\r\n}\r\n", styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0;background:var(--fb-surface, #fff)}.fb-inspector__empty{margin:16px 12px;font-size:12px;line-height:1.5;color:var(--fb-text-muted, #667085)}.fb-inspector__header{display:flex;align-items:flex-start;justify-content:space-between;gap:8px;padding:10px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-inspector__type{font-size:10px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;color:var(--fb-text-subtle, #98a2b3)}.fb-inspector__title{margin:2px 0 0;font-size:14px;font-weight:600;color:var(--fb-text, #1d2939)}.fb-inspector__body{flex:1;min-height:0;overflow-y:auto;padding:14px}.fb-inspector__issues{margin:0 0 12px;padding:0;list-style:none}.fb-inspector__issue{margin-bottom:4px;padding:6px 8px;border-left:3px solid var(--fb-text-subtle, #98a2b3);border-radius:3px;background:var(--fb-surface-alt, #f8f9fb);font-size:11px;line-height:1.4;color:var(--fb-text, #1d2939)}.fb-inspector__issue--error{border-left-color:var(--fb-error, #c9372c)}.fb-inspector__issue--warning{border-left-color:var(--fb-warning, #b7791f)}.fb-inspector__issue-code{display:block;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:9px;color:var(--fb-text-subtle, #98a2b3)}.fb-inspector__issue-path{display:block;margin-top:2px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:9px;color:var(--fb-text-muted, #667085)}.fb-inspector__position{margin:0;font-size:10px;color:var(--fb-text-subtle, #98a2b3)}.fb-inspector__divider{margin:12px 0;border:0;border-top:1px solid var(--fb-border-subtle, #e6e9ee)}\n"], dependencies: [{ kind: "component", type: ActionCallInspectorComponent, selector: "fb-action-call-inspector" }, { kind: "component", type: AssignmentInspectorComponent, selector: "fb-assignment-inspector" }, { kind: "component", type: CollectionProcessorInspectorComponent, selector: "fb-collection-processor-inspector" }, { kind: "component", type: CustomErrorInspectorComponent, selector: "fb-custom-error-inspector" }, { kind: "component", type: DecisionInspectorComponent, selector: "fb-decision-inspector" }, { kind: "component", type: DynamicScreenInspectorComponent, selector: "fb-dynamic-screen-inspector" }, { kind: "component", type: LoopInspectorComponent, selector: "fb-loop-inspector" }, { kind: "component", type: OrchestratedStageInspectorComponent, selector: "fb-orchestrated-stage-inspector" }, { kind: "component", type: RecordLookupInspectorComponent, selector: "fb-record-lookup-inspector" }, { kind: "component", type: RecordRollbackInspectorComponent, selector: "fb-record-rollback-inspector" }, { kind: "component", type: RecordWriteInspectorComponent, selector: "fb-record-write-inspector", inputs: ["type"] }, { kind: "component", type: ScreenInspectorComponent, selector: "fb-screen-inspector" }, { kind: "component", type: ScriptCallInspectorComponent, selector: "fb-script-call-inspector" }, { kind: "component", type: StartInspectorComponent, selector: "fb-start-inspector" }, { kind: "component", type: SubflowInspectorComponent, selector: "fb-subflow-inspector" }, { kind: "component", type: TransformInspectorComponent, selector: "fb-transform-inspector" }, { kind: "component", type: WaitInspectorComponent, selector: "fb-wait-inspector" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
9014
9918
  }
9015
9919
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImport: i0, type: ElementInspectorComponent, decorators: [{
9016
9920
  type: Component,
@@ -9020,6 +9924,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImpo
9020
9924
  CollectionProcessorInspectorComponent,
9021
9925
  CustomErrorInspectorComponent,
9022
9926
  DecisionInspectorComponent,
9927
+ DynamicScreenInspectorComponent,
9023
9928
  LoopInspectorComponent,
9024
9929
  OrchestratedStageInspectorComponent,
9025
9930
  RecordLookupInspectorComponent,
@@ -9031,7 +9936,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImpo
9031
9936
  SubflowInspectorComponent,
9032
9937
  TransformInspectorComponent,
9033
9938
  WaitInspectorComponent,
9034
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (!selectedName()) {\r\n <p class=\"fb-inspector__empty\">\r\n Seleziona un elemento sul canvas per modificarlo, oppure trascina un elemento dalla palette.\r\n </p>\r\n} @else {\r\n @if (showHeader()) {\r\n <header class=\"fb-inspector__header\">\r\n <div>\r\n <span class=\"fb-inspector__type\">{{ typeLabel() }}</span>\r\n <h2 class=\"fb-inspector__title\">\r\n {{ isStart() ? 'Avvio del flow' : node()?.label || selectedName() }}\r\n </h2>\r\n </div>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" aria-label=\"Chiudi\" (click)=\"close()\">\r\n \u00D7\r\n </button>\r\n </header>\r\n }\r\n\r\n <div class=\"fb-inspector__body\">\r\n @if (issues().length) {\r\n <ul class=\"fb-inspector__issues\">\r\n <!-- `$index`: due rilievi con lo stesso codice e lo stesso path sono possibili. -->\r\n @for (issue of issues(); track $index) {\r\n <li\r\n class=\"fb-inspector__issue\"\r\n [class.fb-inspector__issue--error]=\"issue.severity === 'Error'\"\r\n [class.fb-inspector__issue--warning]=\"issue.severity === 'Warning'\"\r\n >\r\n <span class=\"fb-inspector__issue-code\">{{ issue.code }}</span>\r\n {{ issue.message }}\r\n @if (issue.path) {\r\n <span class=\"fb-inspector__issue-path\">{{ issue.path }}</span>\r\n }\r\n </li>\r\n }\r\n </ul>\r\n }\r\n\r\n @if (isUnsupported()) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n Questo tipo di elemento non e\u2019 supportato dal motore: il flow che lo contiene non parte\r\n (ELEMENT_NOT_SUPPORTED). Non e\u2019 creabile dalla palette; se e\u2019 arrivato da un documento importato,\r\n va rimosso.\r\n </p>\r\n }\r\n\r\n @if (!isStart()) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Etichetta</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"$any(node()?.label) || ''\"\r\n placeholder=\"Nome mostrato sul canvas\"\r\n (input)=\"setLabel($any($event.target).value)\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Nome tecnico</label>\r\n @if (pendingName() === null) {\r\n <div class=\"fb-field__row\">\r\n <input class=\"fb-input fb-input--mono\" [value]=\"selectedName() || ''\" readonly />\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"startRename()\">Rinomina</button>\r\n </div>\r\n <p class=\"fb-field__hint\">\r\n \u00C8 l\u2019identificatore con cui i riferimenti raggiungono questo elemento.\r\n @if (referenceCount() > 1) {\r\n Compare {{ referenceCount() }} volte nel documento.\r\n }\r\n </p>\r\n } @else {\r\n <input\r\n class=\"fb-input fb-input--mono\"\r\n [class.fb-input--invalid]=\"!!nameError()\"\r\n [value]=\"pendingName() || ''\"\r\n (input)=\"onPendingNameInput($any($event.target).value)\"\r\n />\r\n @if (nameError()) {\r\n <p class=\"fb-field__error\">{{ nameError() }}</p>\r\n } @else {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n La rinomina riscrive tutti i riferimenti che puntano a questo elemento\r\n ({{ referenceCount() }} occorrenze): nessuna primitiva del backend lo fa, lo fa l\u2019editor.\r\n </p>\r\n }\r\n <div class=\"fb-field__row\">\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" [disabled]=\"!canRename()\" (click)=\"applyRename()\">\r\n Applica\r\n </button>\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"suggestNameFromLabel()\">Genera dalla label</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost\" (click)=\"cancelRename()\">Annulla</button>\r\n </div>\r\n }\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Descrizione</label>\r\n <textarea\r\n class=\"fb-textarea\"\r\n [value]=\"$any(node()?.description) || ''\"\r\n (input)=\"setDescription($any($event.target).value)\"\r\n ></textarea>\r\n </div>\r\n }\r\n\r\n <p class=\"fb-inspector__position\">\r\n Posizione sul canvas: {{ position().x }}, {{ position().y }}\r\n </p>\r\n\r\n <hr class=\"fb-inspector__divider\" />\r\n\r\n @if (isStart()) {\r\n <fb-start-inspector />\r\n } @else if (node()) {\r\n @switch (type()) {\r\n @case ('Screen') {\r\n <fb-screen-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('Assignment') {\r\n <fb-assignment-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('Decision') {\r\n <fb-decision-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('Loop') {\r\n <fb-loop-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('CollectionProcessor') {\r\n <fb-collection-processor-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('CustomError') {\r\n <fb-custom-error-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('Wait') {\r\n <fb-wait-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('RecordLookup') {\r\n <fb-record-lookup-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('RecordCreate') {\r\n <fb-record-write-inspector [name]=\"selectedName()!\" [node]=\"node()!\" type=\"RecordCreate\" />\r\n }\r\n @case ('RecordUpdate') {\r\n <fb-record-write-inspector [name]=\"selectedName()!\" [node]=\"node()!\" type=\"RecordUpdate\" />\r\n }\r\n @case ('RecordDelete') {\r\n <fb-record-write-inspector [name]=\"selectedName()!\" [node]=\"node()!\" type=\"RecordDelete\" />\r\n }\r\n @case ('RecordRollback') {\r\n <fb-record-rollback-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('ActionCall') {\r\n <fb-action-call-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('ScriptCall') {\r\n <fb-script-call-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('Subflow') {\r\n <fb-subflow-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('Transform') {\r\n <fb-transform-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('OrchestratedStage') {\r\n <fb-orchestrated-stage-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @default {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Nessun form specifico per il tipo \u00AB{{ type() }}\u00BB: i campi comuni sono modificabili qui sopra.\r\n </p>\r\n }\r\n }\r\n }\r\n\r\n @if (!isStart()) {\r\n <hr class=\"fb-inspector__divider\" />\r\n <div class=\"fb-field__row\">\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"requestDuplicate()\">Duplica</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--danger\" (click)=\"requestRemove()\">Elimina</button>\r\n </div>\r\n }\r\n </div>\r\n}\r\n", styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0;background:var(--fb-surface, #fff)}.fb-inspector__empty{margin:16px 12px;font-size:12px;line-height:1.5;color:var(--fb-text-muted, #667085)}.fb-inspector__header{display:flex;align-items:flex-start;justify-content:space-between;gap:8px;padding:10px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-inspector__type{font-size:10px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;color:var(--fb-text-subtle, #98a2b3)}.fb-inspector__title{margin:2px 0 0;font-size:14px;font-weight:600;color:var(--fb-text, #1d2939)}.fb-inspector__body{flex:1;min-height:0;overflow-y:auto;padding:14px}.fb-inspector__issues{margin:0 0 12px;padding:0;list-style:none}.fb-inspector__issue{margin-bottom:4px;padding:6px 8px;border-left:3px solid var(--fb-text-subtle, #98a2b3);border-radius:3px;background:var(--fb-surface-alt, #f8f9fb);font-size:11px;line-height:1.4;color:var(--fb-text, #1d2939)}.fb-inspector__issue--error{border-left-color:var(--fb-error, #c9372c)}.fb-inspector__issue--warning{border-left-color:var(--fb-warning, #b7791f)}.fb-inspector__issue-code{display:block;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:9px;color:var(--fb-text-subtle, #98a2b3)}.fb-inspector__issue-path{display:block;margin-top:2px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:9px;color:var(--fb-text-muted, #667085)}.fb-inspector__position{margin:0;font-size:10px;color:var(--fb-text-subtle, #98a2b3)}.fb-inspector__divider{margin:12px 0;border:0;border-top:1px solid var(--fb-border-subtle, #e6e9ee)}\n"] }]
9939
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (!selectedName()) {\r\n <p class=\"fb-inspector__empty\">\r\n Seleziona un elemento sul canvas per modificarlo, oppure trascina un elemento dalla palette.\r\n </p>\r\n} @else {\r\n @if (showHeader()) {\r\n <header class=\"fb-inspector__header\">\r\n <div>\r\n <span class=\"fb-inspector__type\">{{ typeLabel() }}</span>\r\n <h2 class=\"fb-inspector__title\">\r\n {{ isStart() ? 'Avvio del flow' : node()?.label || selectedName() }}\r\n </h2>\r\n </div>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" aria-label=\"Chiudi\" (click)=\"close()\">\r\n \u00D7\r\n </button>\r\n </header>\r\n }\r\n\r\n <div class=\"fb-inspector__body\">\r\n @if (issues().length) {\r\n <ul class=\"fb-inspector__issues\">\r\n <!-- `$index`: due rilievi con lo stesso codice e lo stesso path sono possibili. -->\r\n @for (issue of issues(); track $index) {\r\n <li\r\n class=\"fb-inspector__issue\"\r\n [class.fb-inspector__issue--error]=\"issue.severity === 'Error'\"\r\n [class.fb-inspector__issue--warning]=\"issue.severity === 'Warning'\"\r\n >\r\n <span class=\"fb-inspector__issue-code\">{{ issue.code }}</span>\r\n {{ issue.message }}\r\n @if (issue.path) {\r\n <span class=\"fb-inspector__issue-path\">{{ issue.path }}</span>\r\n }\r\n </li>\r\n }\r\n </ul>\r\n }\r\n\r\n @if (isUnsupported()) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n Questo tipo di elemento non e\u2019 supportato dal motore: il flow che lo contiene non parte\r\n (ELEMENT_NOT_SUPPORTED). Non e\u2019 creabile dalla palette; se e\u2019 arrivato da un documento importato,\r\n va rimosso.\r\n </p>\r\n }\r\n\r\n @if (!isStart()) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Etichetta</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"$any(node()?.label) || ''\"\r\n placeholder=\"Nome mostrato sul canvas\"\r\n (input)=\"setLabel($any($event.target).value)\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Nome tecnico</label>\r\n @if (pendingName() === null) {\r\n <div class=\"fb-field__row\">\r\n <input class=\"fb-input fb-input--mono\" [value]=\"selectedName() || ''\" readonly />\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"startRename()\">Rinomina</button>\r\n </div>\r\n <p class=\"fb-field__hint\">\r\n \u00C8 l\u2019identificatore con cui i riferimenti raggiungono questo elemento.\r\n @if (referenceCount() > 1) {\r\n Compare {{ referenceCount() }} volte nel documento.\r\n }\r\n </p>\r\n } @else {\r\n <input\r\n class=\"fb-input fb-input--mono\"\r\n [class.fb-input--invalid]=\"!!nameError()\"\r\n [value]=\"pendingName() || ''\"\r\n (input)=\"onPendingNameInput($any($event.target).value)\"\r\n />\r\n @if (nameError()) {\r\n <p class=\"fb-field__error\">{{ nameError() }}</p>\r\n } @else {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n La rinomina riscrive tutti i riferimenti che puntano a questo elemento\r\n ({{ referenceCount() }} occorrenze): nessuna primitiva del backend lo fa, lo fa l\u2019editor.\r\n </p>\r\n }\r\n <div class=\"fb-field__row\">\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" [disabled]=\"!canRename()\" (click)=\"applyRename()\">\r\n Applica\r\n </button>\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"suggestNameFromLabel()\">Genera dalla label</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost\" (click)=\"cancelRename()\">Annulla</button>\r\n </div>\r\n }\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Descrizione</label>\r\n <textarea\r\n class=\"fb-textarea\"\r\n [value]=\"$any(node()?.description) || ''\"\r\n (input)=\"setDescription($any($event.target).value)\"\r\n ></textarea>\r\n </div>\r\n }\r\n\r\n <p class=\"fb-inspector__position\">\r\n Posizione sul canvas: {{ position().x }}, {{ position().y }}\r\n </p>\r\n\r\n <hr class=\"fb-inspector__divider\" />\r\n\r\n @if (isStart()) {\r\n <fb-start-inspector />\r\n } @else if (node()) {\r\n @switch (type()) {\r\n @case ('Screen') {\r\n <fb-screen-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('DynamicScreen') {\r\n <fb-dynamic-screen-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('Assignment') {\r\n <fb-assignment-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('Decision') {\r\n <fb-decision-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('Loop') {\r\n <fb-loop-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('CollectionProcessor') {\r\n <fb-collection-processor-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('CustomError') {\r\n <fb-custom-error-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('Wait') {\r\n <fb-wait-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('RecordLookup') {\r\n <fb-record-lookup-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('RecordCreate') {\r\n <fb-record-write-inspector [name]=\"selectedName()!\" [node]=\"node()!\" type=\"RecordCreate\" />\r\n }\r\n @case ('RecordUpdate') {\r\n <fb-record-write-inspector [name]=\"selectedName()!\" [node]=\"node()!\" type=\"RecordUpdate\" />\r\n }\r\n @case ('RecordDelete') {\r\n <fb-record-write-inspector [name]=\"selectedName()!\" [node]=\"node()!\" type=\"RecordDelete\" />\r\n }\r\n @case ('RecordRollback') {\r\n <fb-record-rollback-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('ActionCall') {\r\n <fb-action-call-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('ScriptCall') {\r\n <fb-script-call-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('Subflow') {\r\n <fb-subflow-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('Transform') {\r\n <fb-transform-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('OrchestratedStage') {\r\n <fb-orchestrated-stage-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @default {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Nessun form specifico per il tipo \u00AB{{ type() }}\u00BB: i campi comuni sono modificabili qui sopra.\r\n </p>\r\n }\r\n }\r\n }\r\n\r\n @if (!isStart()) {\r\n <hr class=\"fb-inspector__divider\" />\r\n <div class=\"fb-field__row\">\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"requestDuplicate()\">Duplica</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--danger\" (click)=\"requestRemove()\">Elimina</button>\r\n </div>\r\n }\r\n </div>\r\n}\r\n", styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0;background:var(--fb-surface, #fff)}.fb-inspector__empty{margin:16px 12px;font-size:12px;line-height:1.5;color:var(--fb-text-muted, #667085)}.fb-inspector__header{display:flex;align-items:flex-start;justify-content:space-between;gap:8px;padding:10px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-inspector__type{font-size:10px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;color:var(--fb-text-subtle, #98a2b3)}.fb-inspector__title{margin:2px 0 0;font-size:14px;font-weight:600;color:var(--fb-text, #1d2939)}.fb-inspector__body{flex:1;min-height:0;overflow-y:auto;padding:14px}.fb-inspector__issues{margin:0 0 12px;padding:0;list-style:none}.fb-inspector__issue{margin-bottom:4px;padding:6px 8px;border-left:3px solid var(--fb-text-subtle, #98a2b3);border-radius:3px;background:var(--fb-surface-alt, #f8f9fb);font-size:11px;line-height:1.4;color:var(--fb-text, #1d2939)}.fb-inspector__issue--error{border-left-color:var(--fb-error, #c9372c)}.fb-inspector__issue--warning{border-left-color:var(--fb-warning, #b7791f)}.fb-inspector__issue-code{display:block;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:9px;color:var(--fb-text-subtle, #98a2b3)}.fb-inspector__issue-path{display:block;margin-top:2px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:9px;color:var(--fb-text-muted, #667085)}.fb-inspector__position{margin:0;font-size:10px;color:var(--fb-text-subtle, #98a2b3)}.fb-inspector__divider{margin:12px 0;border:0;border-top:1px solid var(--fb-border-subtle, #e6e9ee)}\n"] }]
9035
9940
  }], propDecorators: { selectedName: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedName", required: false }] }], showHeader: [{ type: i0.Input, args: [{ isSignal: true, alias: "showHeader", required: false }] }], closed: [{ type: i0.Output, args: ["closed"] }], removeRequested: [{ type: i0.Output, args: ["removeRequested"] }], duplicateRequested: [{ type: i0.Output, args: ["duplicateRequested"] }] } });
9036
9941
 
9037
9942
  /**
@@ -9095,11 +10000,11 @@ class ElementDialogComponent {
9095
10000
  this.duplicateRequested.emit(name);
9096
10001
  }
9097
10002
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.27", ngImport: i0, type: ElementDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
9098
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "20.3.27", type: ElementDialogComponent, isStandalone: true, selector: "fb-element-dialog", inputs: { selectedName: { classPropertyName: "selectedName", publicName: "selectedName", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closed: "closed", removeRequested: "removeRequested", duplicateRequested: "duplicateRequested" }, viewQueries: [{ propertyName: "panel", first: true, predicate: ["panel"], descendants: true, isSignal: true }], ngImport: i0, template: "<!--\r\n Il backdrop chiude: e' il gesto che tutti si aspettano. Il pannello ferma la propagazione\r\n del click, altrimenti configurare un campo chiuderebbe la dialog.\r\n-->\r\n<div class=\"fb-dialog__backdrop\" (click)=\"close()\"></div>\r\n\r\n<div\r\n #panel\r\n class=\"fb-dialog__panel\"\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n [attr.aria-label]=\"typeLabel() + ': ' + title()\"\r\n tabindex=\"-1\"\r\n (click)=\"$event.stopPropagation()\"\r\n (keydown.escape)=\"close()\"\r\n>\r\n <header class=\"fb-dialog__head\">\r\n <div class=\"fb-dialog__identity\">\r\n <span class=\"fb-dialog__type\">{{ typeLabel() }}</span>\r\n <h2 class=\"fb-dialog__title\">{{ title() }}</h2>\r\n </div>\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-dialog__body\">\r\n <!-- L'intestazione dell'inspector qui e' quella della dialog: non si ripete. -->\r\n <fb-element-inspector\r\n [selectedName]=\"selectedName()\"\r\n [showHeader]=\"false\"\r\n (removeRequested)=\"onRemoveRequested($event)\"\r\n (duplicateRequested)=\"onDuplicateRequested($event)\"\r\n (closed)=\"close()\"\r\n />\r\n </div>\r\n\r\n <footer class=\"fb-dialog__foot\">\r\n <span class=\"fb-dialog__note\">\r\n Le modifiche sono gi\u00E0 nel documento: per tornare indietro c\u2019\u00E8 l\u2019annulla dell\u2019editor.\r\n </span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" (click)=\"close()\">Fatto</button>\r\n </footer>\r\n</div>\r\n", styles: [":host{position:absolute;inset:0;z-index:30;display:grid;place-items:center;padding:24px}.fb-dialog__backdrop{position:absolute;inset:0;background:#10182852;-webkit-backdrop-filter:blur(1px);backdrop-filter:blur(1px)}.fb-dialog__panel{position:relative;display:flex;flex-direction:column;width:min(760px,100%);max-height:100%;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-lg, 12px);background:var(--fb-surface, #fff);box-shadow:var(--fb-shadow-lg, 0 18px 44px rgb(16 24 40 / 18%));outline:none;overflow:hidden}.fb-dialog__head{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 14px;border-bottom:1px solid var(--fb-border-subtle, #eef0f4)}.fb-dialog__identity{min-width:0}.fb-dialog__type{display:block;font-size:10px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--fb-text-subtle, #98a2b3)}.fb-dialog__title{margin:1px 0 0;font-size:15px;font-weight:600;color:var(--fb-text, #1a1c23);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-dialog__body{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain}.fb-dialog__foot{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 14px;border-top:1px solid var(--fb-border-subtle, #eef0f4);background:var(--fb-surface-alt, #f7f8fa)}.fb-dialog__note{font-size:11px;color:var(--fb-text-muted, #6b7086)}@media(max-height:620px){:host{padding:10px}}\n"], dependencies: [{ kind: "component", type: ElementInspectorComponent, selector: "fb-element-inspector", inputs: ["selectedName", "showHeader"], outputs: ["closed", "removeRequested", "duplicateRequested"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
10003
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "20.3.27", type: ElementDialogComponent, isStandalone: true, selector: "fb-element-dialog", inputs: { selectedName: { classPropertyName: "selectedName", publicName: "selectedName", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closed: "closed", removeRequested: "removeRequested", duplicateRequested: "duplicateRequested" }, viewQueries: [{ propertyName: "panel", first: true, predicate: ["panel"], descendants: true, isSignal: true }], ngImport: i0, template: "<!--\r\n Il backdrop chiude: e' il gesto che tutti si aspettano. Il pannello ferma la propagazione\r\n del click, altrimenti configurare un campo chiuderebbe la dialog.\r\n-->\r\n<div class=\"fb-dialog__backdrop\" (click)=\"close()\"></div>\r\n\r\n<div\r\n #panel\r\n class=\"fb-dialog__panel\"\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n [attr.aria-label]=\"typeLabel() + ': ' + title()\"\r\n tabindex=\"-1\"\r\n (click)=\"$event.stopPropagation()\"\r\n (keydown.escape)=\"close()\"\r\n>\r\n <header class=\"fb-dialog__head\">\r\n <div class=\"fb-dialog__identity\">\r\n <span class=\"fb-dialog__type\">{{ typeLabel() }}</span>\r\n <h2 class=\"fb-dialog__title\">{{ title() }}</h2>\r\n </div>\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-dialog__body\">\r\n <!-- L'intestazione dell'inspector qui e' quella della dialog: non si ripete. -->\r\n <fb-element-inspector\r\n [selectedName]=\"selectedName()\"\r\n [showHeader]=\"false\"\r\n (removeRequested)=\"onRemoveRequested($event)\"\r\n (duplicateRequested)=\"onDuplicateRequested($event)\"\r\n (closed)=\"close()\"\r\n />\r\n </div>\r\n\r\n <footer class=\"fb-dialog__foot\">\r\n <span class=\"fb-dialog__note\">\r\n Le modifiche sono gi\u00E0 nel documento: per tornare indietro c\u2019\u00E8 l\u2019annulla dell\u2019editor.\r\n </span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" (click)=\"close()\">Fatto</button>\r\n </footer>\r\n</div>\r\n", styles: [":host{position:absolute;inset:0;z-index:30;display:grid;place-items:center;padding:24px}.fb-dialog__backdrop{position:absolute;inset:0;background:#10182852;-webkit-backdrop-filter:blur(1px);backdrop-filter:blur(1px)}.fb-dialog__panel{position:relative;display:flex;flex-direction:column;width:min(var(--fb-dialog-width, 1400px),100%);height:min(var(--fb-dialog-height, 860px),100%);border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-lg, 12px);background:var(--fb-surface, #fff);box-shadow:var(--fb-shadow-lg, 0 18px 44px rgb(16 24 40 / 18%));outline:none;overflow:hidden}.fb-dialog__head{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 14px;border-bottom:1px solid var(--fb-border-subtle, #eef0f4)}.fb-dialog__identity{min-width:0}.fb-dialog__type{display:block;font-size:10px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--fb-text-subtle, #98a2b3)}.fb-dialog__title{margin:1px 0 0;font-size:15px;font-weight:600;color:var(--fb-text, #1a1c23);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-dialog__body{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain}.fb-dialog__foot{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 14px;border-top:1px solid var(--fb-border-subtle, #eef0f4);background:var(--fb-surface-alt, #f7f8fa)}.fb-dialog__note{font-size:11px;color:var(--fb-text-muted, #6b7086)}@media(max-height:620px){:host{padding:10px}}\n"], dependencies: [{ kind: "component", type: ElementInspectorComponent, selector: "fb-element-inspector", inputs: ["selectedName", "showHeader"], outputs: ["closed", "removeRequested", "duplicateRequested"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
9099
10004
  }
9100
10005
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImport: i0, type: ElementDialogComponent, decorators: [{
9101
10006
  type: Component,
9102
- args: [{ selector: 'fb-element-dialog', standalone: true, imports: [ElementInspectorComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<!--\r\n Il backdrop chiude: e' il gesto che tutti si aspettano. Il pannello ferma la propagazione\r\n del click, altrimenti configurare un campo chiuderebbe la dialog.\r\n-->\r\n<div class=\"fb-dialog__backdrop\" (click)=\"close()\"></div>\r\n\r\n<div\r\n #panel\r\n class=\"fb-dialog__panel\"\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n [attr.aria-label]=\"typeLabel() + ': ' + title()\"\r\n tabindex=\"-1\"\r\n (click)=\"$event.stopPropagation()\"\r\n (keydown.escape)=\"close()\"\r\n>\r\n <header class=\"fb-dialog__head\">\r\n <div class=\"fb-dialog__identity\">\r\n <span class=\"fb-dialog__type\">{{ typeLabel() }}</span>\r\n <h2 class=\"fb-dialog__title\">{{ title() }}</h2>\r\n </div>\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-dialog__body\">\r\n <!-- L'intestazione dell'inspector qui e' quella della dialog: non si ripete. -->\r\n <fb-element-inspector\r\n [selectedName]=\"selectedName()\"\r\n [showHeader]=\"false\"\r\n (removeRequested)=\"onRemoveRequested($event)\"\r\n (duplicateRequested)=\"onDuplicateRequested($event)\"\r\n (closed)=\"close()\"\r\n />\r\n </div>\r\n\r\n <footer class=\"fb-dialog__foot\">\r\n <span class=\"fb-dialog__note\">\r\n Le modifiche sono gi\u00E0 nel documento: per tornare indietro c\u2019\u00E8 l\u2019annulla dell\u2019editor.\r\n </span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" (click)=\"close()\">Fatto</button>\r\n </footer>\r\n</div>\r\n", styles: [":host{position:absolute;inset:0;z-index:30;display:grid;place-items:center;padding:24px}.fb-dialog__backdrop{position:absolute;inset:0;background:#10182852;-webkit-backdrop-filter:blur(1px);backdrop-filter:blur(1px)}.fb-dialog__panel{position:relative;display:flex;flex-direction:column;width:min(760px,100%);max-height:100%;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-lg, 12px);background:var(--fb-surface, #fff);box-shadow:var(--fb-shadow-lg, 0 18px 44px rgb(16 24 40 / 18%));outline:none;overflow:hidden}.fb-dialog__head{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 14px;border-bottom:1px solid var(--fb-border-subtle, #eef0f4)}.fb-dialog__identity{min-width:0}.fb-dialog__type{display:block;font-size:10px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--fb-text-subtle, #98a2b3)}.fb-dialog__title{margin:1px 0 0;font-size:15px;font-weight:600;color:var(--fb-text, #1a1c23);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-dialog__body{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain}.fb-dialog__foot{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 14px;border-top:1px solid var(--fb-border-subtle, #eef0f4);background:var(--fb-surface-alt, #f7f8fa)}.fb-dialog__note{font-size:11px;color:var(--fb-text-muted, #6b7086)}@media(max-height:620px){:host{padding:10px}}\n"] }]
10007
+ args: [{ selector: 'fb-element-dialog', standalone: true, imports: [ElementInspectorComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<!--\r\n Il backdrop chiude: e' il gesto che tutti si aspettano. Il pannello ferma la propagazione\r\n del click, altrimenti configurare un campo chiuderebbe la dialog.\r\n-->\r\n<div class=\"fb-dialog__backdrop\" (click)=\"close()\"></div>\r\n\r\n<div\r\n #panel\r\n class=\"fb-dialog__panel\"\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n [attr.aria-label]=\"typeLabel() + ': ' + title()\"\r\n tabindex=\"-1\"\r\n (click)=\"$event.stopPropagation()\"\r\n (keydown.escape)=\"close()\"\r\n>\r\n <header class=\"fb-dialog__head\">\r\n <div class=\"fb-dialog__identity\">\r\n <span class=\"fb-dialog__type\">{{ typeLabel() }}</span>\r\n <h2 class=\"fb-dialog__title\">{{ title() }}</h2>\r\n </div>\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-dialog__body\">\r\n <!-- L'intestazione dell'inspector qui e' quella della dialog: non si ripete. -->\r\n <fb-element-inspector\r\n [selectedName]=\"selectedName()\"\r\n [showHeader]=\"false\"\r\n (removeRequested)=\"onRemoveRequested($event)\"\r\n (duplicateRequested)=\"onDuplicateRequested($event)\"\r\n (closed)=\"close()\"\r\n />\r\n </div>\r\n\r\n <footer class=\"fb-dialog__foot\">\r\n <span class=\"fb-dialog__note\">\r\n Le modifiche sono gi\u00E0 nel documento: per tornare indietro c\u2019\u00E8 l\u2019annulla dell\u2019editor.\r\n </span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" (click)=\"close()\">Fatto</button>\r\n </footer>\r\n</div>\r\n", styles: [":host{position:absolute;inset:0;z-index:30;display:grid;place-items:center;padding:24px}.fb-dialog__backdrop{position:absolute;inset:0;background:#10182852;-webkit-backdrop-filter:blur(1px);backdrop-filter:blur(1px)}.fb-dialog__panel{position:relative;display:flex;flex-direction:column;width:min(var(--fb-dialog-width, 1400px),100%);height:min(var(--fb-dialog-height, 860px),100%);border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-lg, 12px);background:var(--fb-surface, #fff);box-shadow:var(--fb-shadow-lg, 0 18px 44px rgb(16 24 40 / 18%));outline:none;overflow:hidden}.fb-dialog__head{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 14px;border-bottom:1px solid var(--fb-border-subtle, #eef0f4)}.fb-dialog__identity{min-width:0}.fb-dialog__type{display:block;font-size:10px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--fb-text-subtle, #98a2b3)}.fb-dialog__title{margin:1px 0 0;font-size:15px;font-weight:600;color:var(--fb-text, #1a1c23);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-dialog__body{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain}.fb-dialog__foot{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 14px;border-top:1px solid var(--fb-border-subtle, #eef0f4);background:var(--fb-surface-alt, #f7f8fa)}.fb-dialog__note{font-size:11px;color:var(--fb-text-muted, #6b7086)}@media(max-height:620px){:host{padding:10px}}\n"] }]
9103
10008
  }], ctorParameters: () => [], propDecorators: { selectedName: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedName", required: false }] }], closed: [{ type: i0.Output, args: ["closed"] }], removeRequested: [{ type: i0.Output, args: ["removeRequested"] }], duplicateRequested: [{ type: i0.Output, args: ["duplicateRequested"] }], panel: [{ type: i0.ViewChild, args: ['panel', { isSignal: true }] }] } });
9104
10009
 
9105
10010
  /**
@@ -10218,11 +11123,19 @@ class FlowBuilderComponent {
10218
11123
  const current = this.store.document().processType;
10219
11124
  return !!current && !this.processTypes().some((type) => type.value === current);
10220
11125
  }, ...(ngDevMode ? [{ debugName: "isProcessTypeOutOfCatalog" }] : []));
11126
+ /**
11127
+ * Quante schermate ha il flow. **Entrambi** i tipi di screen contano: quello a form e quello
11128
+ * dinamico sono la stessa interazione con l'utente, e contarne uno solo faceva comparire
11129
+ * «questo flow Screen non ha screen» su un flow fatto di soli screen dinamici (§5.2).
11130
+ */
11131
+ screenCount = computed(() => {
11132
+ const document = this.store.document();
11133
+ return (document.screens?.length ?? 0) + (document.dynamicScreens?.length ?? 0);
11134
+ }, ...(ngDevMode ? [{ debugName: "screenCount" }] : []));
10221
11135
  /** `AutoLaunched` + screen e' un errore di validazione: si segnala subito (§13.6). */
10222
- hasScreensInAutoLaunched = computed(() => this.store.document().processType === 'AutoLaunched' &&
10223
- (this.store.document().screens?.length ?? 0) > 0, ...(ngDevMode ? [{ debugName: "hasScreensInAutoLaunched" }] : []));
11136
+ hasScreensInAutoLaunched = computed(() => this.store.document().processType === 'AutoLaunched' && this.screenCount() > 0, ...(ngDevMode ? [{ debugName: "hasScreensInAutoLaunched" }] : []));
10224
11137
  /** Un flow `Screen` senza screen e' `SCREEN_FLOW_WITHOUT_SCREENS`. */
10225
- hasNoScreensInScreenFlow = computed(() => this.store.document().processType === 'Screen' && (this.store.document().screens?.length ?? 0) === 0, ...(ngDevMode ? [{ debugName: "hasNoScreensInScreenFlow" }] : []));
11138
+ hasNoScreensInScreenFlow = computed(() => this.store.document().processType === 'Screen' && this.screenCount() === 0, ...(ngDevMode ? [{ debugName: "hasNoScreensInScreenFlow" }] : []));
10226
11139
  /**
10227
11140
  * Un flow `Orchestration` senza stage di orchestrazione e' un **avviso**
10228
11141
  * (`ORCHESTRATION_WITHOUT_STAGES`): il tipo dichiara un'orchestrazione che non c'e' (§3.1).
@@ -10585,5 +11498,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImpo
10585
11498
  * Generated bundle index. Do not edit.
10586
11499
  */
10587
11500
 
10588
- export { ConditionEditorComponent, ConnectorEditorComponent, DebugPanelComponent, ElementDialogComponent, ElementInspectorComponent, ElementPaletteComponent, EnumValuePickerComponent, FALLBACK_COLLECTION_BY_TYPE, FALLBACK_TYPE_LABEL, FLOW_BUILDER_HTTP_CONFIG, FLOW_ELEMENT_ICONS, FLOW_ELEMENT_VARIANT_FIELDS, FLOW_ELEMENT_VARIANT_ICONS, FLOW_ERROR_FALLBACK_MESSAGE, FLOW_ERROR_HTTP_STATUS, FLOW_NAME_PATTERN, FLOW_NODE_COLLECTIONS, FLOW_NODE_HEIGHT, FLOW_NODE_WIDTH, FLOW_RESOURCE_COLLECTIONS, FieldAssignmentEditorComponent, FieldPickerComponent, FlowApiError, FlowBuilderApi, FlowBuilderComponent, FlowCanvasComponent, FlowCatalogStore, FlowDictionaryStore, FlowDocumentStore, FlowEditorSession, FlowLayoutService, FlowValidationStore, HttpFlowBuilderApi, NamePickerComponent, NodeInspectorBase, ORCHESTRATION_CONDITION_OUTPUT, ObjectPickerComponent, OrchestratedStageInspectorComponent, ParameterEditorComponent, ProblemsPanelComponent, RecordFilterEditorComponent, ReferencePickerComponent, ResourcePanelComponent, SEVERITY_BUCKETS, SEVERITY_ICON, SEVERITY_LABEL, START_NODE_NAME, SelectValueDirective, StartInspectorComponent, StructureMemberPickerComponent, StructurePickerComponent, TYPES_WITH_AUTOMATIC_OUTPUT, TYPE_BY_COLLECTION, UNSUPPORTED_TYPES, ValueEditorComponent, VersionPanelComponent, areTypesComparable, canvasNodeId, checkConditionLogic, checkFlowName, describePathEntry, elementIcon, emptyFlowDefinition, filterReferences, flowNodeWidth, flowNodeWidthClass, isCustomConditionLogic, isEmptyReferenceFilter, isGlobalReference, isNumericType, isTypeCheckedOperator, isValidFlowName, loadPathLevel, matchesReferenceFilter, moveCondition, navigatePath, outletByKey, outletsOf, parseCanvasNodeId, parseInvariantNumber, parseSourceConnectorId, parseTargetConnectorId, pathAvailableNames, pathContainerLabel, pathNotVerifiableMessage, referenceRoot, remapConditionLogic, removeCondition, resolvePath, severityBucket, slugifyFlowName, sourceConnectorId, stageStepNames, stageStepOutputReferenced, stepsOf, targetConnectorId, uniqueFlowName, variantFieldOf, variantOf, variantPresetOf };
11501
+ export { ConditionEditorComponent, ConnectorEditorComponent, DebugPanelComponent, DynamicScreenInspectorComponent, ElementDialogComponent, ElementInspectorComponent, ElementPaletteComponent, EnumValuePickerComponent, FALLBACK_COLLECTION_BY_TYPE, FALLBACK_TYPE_LABEL, FLOW_BUILDER_HTTP_CONFIG, FLOW_ELEMENT_ICONS, FLOW_ELEMENT_VARIANT_FIELDS, FLOW_ELEMENT_VARIANT_ICONS, FLOW_ERROR_FALLBACK_MESSAGE, FLOW_ERROR_HTTP_STATUS, FLOW_NAME_PATTERN, FLOW_NODE_COLLECTIONS, FLOW_NODE_HEIGHT, FLOW_NODE_WIDTH, FLOW_RESOURCE_COLLECTIONS, FLOW_VALUE_FIELDS, FLOW_VALUE_LITERAL_FIELDS, FieldAssignmentEditorComponent, FieldPickerComponent, FlowApiError, FlowBuilderApi, FlowBuilderComponent, FlowCanvasComponent, FlowCatalogStore, FlowDictionaryStore, FlowDocumentStore, FlowEditorSession, FlowLayoutService, FlowValidationStore, HttpFlowBuilderApi, NamePickerComponent, NodeInspectorBase, ORCHESTRATION_CONDITION_OUTPUT, ObjectPickerComponent, OrchestratedStageInspectorComponent, ParameterEditorComponent, ProblemsPanelComponent, RecordFilterEditorComponent, ReferencePickerComponent, ResourcePanelComponent, 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, areTypesComparable, canvasNodeId, checkConditionLogic, checkFlowName, describePathEntry, duplicateField, elementIcon, emptyFlowDefinition, fieldAt, filterReferences, flattenFields, flowNodeWidth, flowNodeWidthClass, insertField, isCustomConditionLogic, isEmptyReferenceFilter, isFieldResource, isGlobalReference, isNumericType, isPathInside, isTypeCheckedOperator, isValidFlowName, isValued, loadPathLevel, matchesReferenceFilter, moveCondition, moveField, navigatePath, outletByKey, outletsOf, parseCanvasNodeId, parseInvariantNumber, parseSourceConnectorId, parseTargetConnectorId, pathAvailableNames, pathContainerLabel, pathKey, pathNotVerifiableMessage, referenceRoot, remapConditionLogic, removeCondition, removeField, resolvePath, samePath, screenFieldNames, severityBucket, slugifyFlowName, sourceConnectorId, stageStepNames, stageStepOutputReferenced, stepsOf, targetConnectorId, uniqueFlowName, valuedFieldOf, valuedFieldsOf, variantFieldOf, variantOf, variantPresetOf };
10589
11502
  //# sourceMappingURL=esfaenza-flow-builder.mjs.map