@esfaenza/flow-builder 20.3.4 → 20.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,10 +1,10 @@
1
1
  import { HttpClient, HttpErrorResponse, HttpParams } from '@angular/common/http';
2
2
  import * as i0 from '@angular/core';
3
- import { InjectionToken, inject, signal, computed, Injectable, DestroyRef, input, output, ChangeDetectionStrategy, Component, effect, untracked, ElementRef, Directive, viewChild } from '@angular/core';
3
+ import { InjectionToken, inject, signal, effect, computed, Injectable, DestroyRef, input, output, viewChild, ChangeDetectionStrategy, Component, untracked, ElementRef, Directive } from '@angular/core';
4
4
  import { firstValueFrom } from 'rxjs';
5
5
  import dagre from 'dagre';
6
6
  import * as i1 from '@foblex/flow';
7
- import { EFMarkerType, EFConnectableSide, FFlowModule } from '@foblex/flow';
7
+ import { EFMarkerType, EFConnectableSide, FCanvasComponent, FFlowModule, provideFFlow, withA11y } from '@foblex/flow';
8
8
 
9
9
  /**
10
10
  * Modello del documento Flow — FRONTEND.md §3, §4, §5.
@@ -1218,6 +1218,327 @@ function stageStepOutputReferenced(reference, steps) {
1218
1218
  */
1219
1219
  const ORCHESTRATION_CONDITION_OUTPUT = 'isOrchestrationConditionMet';
1220
1220
 
1221
+ /**
1222
+ * La gravita' di un rilievo, ridotta a tre secchi — FRONTEND.md §7.
1223
+ *
1224
+ * Perche' esiste: `FlowIssueSeverity` e' un tipo **aperto** (`'Error' | 'Warning' | 'Info' |
1225
+ * string`) perche' il backend puo' aggiungere livelli senza toccare il frontend (§13.11). La
1226
+ * conseguenza e' che confrontare la stringa cosi' com'e' e' un fallimento silenzioso: un
1227
+ * `error` minuscolo, un `WARN`, un `Log` non corrispondono a niente, e allora il filtro del
1228
+ * pannello non seleziona nulla, il conteggio resta a zero e il node sul canvas non si colora —
1229
+ * senza un errore da nessuna parte. Era il difetto dei filtri della §7.
1230
+ *
1231
+ * Da qui in avanti si confronta il **secchio**, non la stringa. I livelli che non conosciamo
1232
+ * cadono in `Info`: sono note, non problemi, e restano visibili invece di sparire.
1233
+ */
1234
+ /**
1235
+ * Sinonimi visti o plausibili. Il confronto e' minuscolo e senza spazi, quindi qui bastano
1236
+ * le forme canoniche: `Fatal`, `FATAL` e ` fatal ` finiscono tutte sulla stessa chiave.
1237
+ */
1238
+ const BUCKET_BY_NAME = new Map([
1239
+ ['error', 'Error'],
1240
+ ['errore', 'Error'],
1241
+ ['errors', 'Error'],
1242
+ ['fatal', 'Error'],
1243
+ ['critical', 'Error'],
1244
+ ['severe', 'Error'],
1245
+ ['blocker', 'Error'],
1246
+ ['warning', 'Warning'],
1247
+ ['warn', 'Warning'],
1248
+ ['warnings', 'Warning'],
1249
+ ['avviso', 'Warning'],
1250
+ ['caution', 'Warning'],
1251
+ ['info', 'Info'],
1252
+ ['information', 'Info'],
1253
+ ['informational', 'Info'],
1254
+ ['note', 'Info'],
1255
+ ['nota', 'Info'],
1256
+ ['hint', 'Info'],
1257
+ ['log', 'Info'],
1258
+ ['debug', 'Info'],
1259
+ ['trace', 'Info'],
1260
+ ['verbose', 'Info'],
1261
+ ]);
1262
+ /** Un livello sconosciuto e' una nota: si mostra, non si nasconde. */
1263
+ function severityBucket(severity) {
1264
+ if (!severity) {
1265
+ return 'Info';
1266
+ }
1267
+ return BUCKET_BY_NAME.get(severity.trim().toLowerCase()) ?? 'Info';
1268
+ }
1269
+ /** L'ordine in cui si presentano: prima cio' che blocca l'attivazione. */
1270
+ const SEVERITY_BUCKETS = ['Error', 'Warning', 'Info'];
1271
+ const SEVERITY_LABEL = {
1272
+ Error: 'Errore',
1273
+ Warning: 'Avviso',
1274
+ Info: 'Nota',
1275
+ };
1276
+ /**
1277
+ * Caratteri Unicode, non icone: la libreria non porta asset e non dipende dal font di icone
1278
+ * dell'applicazione ospite — la stessa scelta di `element-icons.ts`. Sono glifi che rendono
1279
+ * bene dentro un pastiglia colorata a 14px e non hanno un fallback emoji che cambia forma da
1280
+ * un sistema all'altro.
1281
+ */
1282
+ const SEVERITY_ICON = {
1283
+ Error: '✕',
1284
+ Warning: '!',
1285
+ Info: 'i',
1286
+ };
1287
+
1288
+ /**
1289
+ * Navigazione dei percorsi puntati — FRONTEND.md §4.4, §4.7, §4.7.1, §5.2, §5.7.
1290
+ *
1291
+ * La specifica e' cambiata su questo punto: i percorsi con il punto **si verificano**. Un percorso
1292
+ * di relazione (`Cliente.Citta`) si naviga un segmento alla volta e l'ultimo si verifica come un
1293
+ * campo semplice; dentro un'istanza di classe i membri annidati (`Richiesta.Cliente.Nome`) si
1294
+ * verificano **fino in fondo**, seguendo `objectType` di classe in classe e passando ai campi
1295
+ * dello schema quando il membro porta un record. Dove la catena si interrompe il rilievo e'
1296
+ * l'avviso `PATH_NOT_VERIFIABLE`, che significa "non lo so" e non "sbagliato".
1297
+ *
1298
+ * Perche' sta qui e non in tre pickers: la catena e' la stessa per campi e membri — cambiano solo
1299
+ * la primitiva che descrive la tappa (`listFields` contro `describeStructure`) e le parole
1300
+ * dell'avviso. Tenerla in un posto solo e' anche il modo di non far divergere l'editor dal
1301
+ * validatore, che naviga la stessa catena.
1302
+ *
1303
+ * Le due cose da non confondere:
1304
+ *
1305
+ * - **`objectType` di un membro** porta il nome di una classe se il membro e' `Structure`, il nome
1306
+ * di un'**entita' dello schema** se il membro e' `Object` — e da lì in poi si continua con i
1307
+ * campi, non con i membri (§4.7.1). Un `Enum` non e' un contenitore: `objectType` lì e' un tipo
1308
+ * di enumerazione e il percorso finisce.
1309
+ * - **scrivibile** non vuol dire la stessa cosa nei due mondi: un membro porta il proprio
1310
+ * `isWritable`, un campo no — fuori da un Create o da un Update il contesto non c'e', quindi
1311
+ * basta che sia `isCreateable` **oppure** `isUpdateable`, e un campo che non e' ne' l'uno ne'
1312
+ * l'altro e' `TARGET_NOT_WRITABLE` in qualunque contesto (§5.7).
1313
+ */
1314
+ /** Il `dataType` porta un contenitore solo se e' `Object` o `Structure`, e non su una collection. */
1315
+ function containerOf(dataType, objectType, isCollection) {
1316
+ if (isCollection || !objectType) {
1317
+ return undefined;
1318
+ }
1319
+ if (dataType === 'Structure') {
1320
+ return { kind: 'structure', name: objectType };
1321
+ }
1322
+ if (dataType === 'Object') {
1323
+ return { kind: 'object', name: objectType };
1324
+ }
1325
+ // `Enum` porta un tipo di enumerazione in `objectType`: non e' una tappa navigabile.
1326
+ return undefined;
1327
+ }
1328
+ function fieldEntry(field) {
1329
+ return {
1330
+ name: field.name,
1331
+ label: field.label ?? null,
1332
+ dataType: field.dataType,
1333
+ objectType: field.objectType ?? null,
1334
+ // §5.7 — fuori da un Create o da un Update il contesto non c'e': basta scrivibile in un modo.
1335
+ // Ne' createable ne' updateable = non scrivibile in nessun contesto (`TARGET_NOT_WRITABLE`).
1336
+ isWritable: field.isCreateable !== false || field.isUpdateable !== false,
1337
+ next: containerOf(field.dataType, field.objectType, false),
1338
+ description: field.isRequired ? 'obbligatorio' : null,
1339
+ };
1340
+ }
1341
+ function memberEntry(member) {
1342
+ return {
1343
+ name: member.name,
1344
+ label: member.label ?? null,
1345
+ description: member.description ?? null,
1346
+ dataType: member.dataType,
1347
+ objectType: member.objectType ?? null,
1348
+ isCollection: member.isCollection ?? false,
1349
+ isWritable: member.isWritable !== false,
1350
+ next: containerOf(member.dataType, member.objectType, member.isCollection),
1351
+ };
1352
+ }
1353
+ /**
1354
+ * Descrive una tappa. `usage` vale solo per le entita' e solo dove serve: i segmenti **intermedi**
1355
+ * si chiedono sempre con `any`, perche' l'uso riguarda l'ultimo — un filtro su `Cliente.Citta`
1356
+ * esige che `Citta` sia filtrabile, non che lo sia la relazione (§4.4).
1357
+ */
1358
+ async function loadPathLevel(catalog, container, usage = 'any') {
1359
+ if (!container.name) {
1360
+ return { container, status: 'unknown-catalog', entries: [] };
1361
+ }
1362
+ if (container.kind === 'structure') {
1363
+ // `describeStructure` non rifiuta: gli esiti sono dati, non errori (§4.7.1).
1364
+ const described = await catalog.describeStructure(container.name);
1365
+ return {
1366
+ container,
1367
+ label: described.label ?? null,
1368
+ status: described.status === 'unknown-class' ? 'unknown-container' : described.status,
1369
+ entries: described.members.map(memberEntry),
1370
+ };
1371
+ }
1372
+ try {
1373
+ const fields = (await catalog.listFields(container.name, usage)) ?? [];
1374
+ // Lista vuota = "non lo so", non "nessun campo": lì non si accusa nessuno (§7).
1375
+ return { container, status: fields.length ? 'declared' : 'undeclared', entries: fields.map(fieldEntry) };
1376
+ }
1377
+ catch {
1378
+ return { container, status: 'unknown-catalog', entries: [] };
1379
+ }
1380
+ }
1381
+ /** Da uno stato di tappa non autorevole al motivo per cui il percorso non e' verificabile. */
1382
+ function reasonOf(status) {
1383
+ return status === 'declared' ? 'unknown-segment' : status;
1384
+ }
1385
+ /**
1386
+ * Naviga `path` a partire da `root`, un segmento alla volta. Non rifiuta mai: un catalogo assente
1387
+ * e' un esito (`unverifiable`), non un errore da gestire in ogni chiamante.
1388
+ */
1389
+ async function resolvePath(catalog, root, path, options) {
1390
+ const trimmed = (path ?? '').trim();
1391
+ const segments = trimmed.split('.').map((segment) => segment.trim());
1392
+ const levels = [];
1393
+ const resolved = [];
1394
+ const base = { root, path: trimmed, segments, levels, resolved };
1395
+ let container = root;
1396
+ for (let index = 0; index < segments.length; index += 1) {
1397
+ const segment = segments[index];
1398
+ const isLast = index === segments.length - 1;
1399
+ const prefix = segments.slice(0, index).join('.');
1400
+ const level = await loadPathLevel(catalog, container, isLast ? (options?.usage ?? 'any') : 'any');
1401
+ levels.push(level);
1402
+ if (isLast) {
1403
+ const tail = { level, prefix, segment };
1404
+ if (!segment) {
1405
+ // Percorso vuoto, o appena aperto col punto: non c'e' niente da verificare, c'e' da proporre.
1406
+ return { ...base, status: 'empty', tail };
1407
+ }
1408
+ if (level.status !== 'declared') {
1409
+ return {
1410
+ ...base,
1411
+ status: 'unverifiable',
1412
+ reason: reasonOf(level.status),
1413
+ stoppedAt: { index, segment },
1414
+ tail,
1415
+ };
1416
+ }
1417
+ const entry = level.entries.find((candidate) => candidate.name === segment);
1418
+ if (!entry) {
1419
+ return { ...base, status: 'unknown', reason: 'unknown-segment', stoppedAt: { index, segment }, tail };
1420
+ }
1421
+ resolved.push(entry);
1422
+ return { ...base, status: 'resolved', last: entry, tail };
1423
+ }
1424
+ if (!segment) {
1425
+ // Un segmento intermedio vuoto (`Cliente..Citta`) non designa niente e non si propone.
1426
+ return { ...base, status: 'empty' };
1427
+ }
1428
+ if (level.status !== 'declared') {
1429
+ return { ...base, status: 'unverifiable', reason: reasonOf(level.status), stoppedAt: { index, segment } };
1430
+ }
1431
+ const entry = level.entries.find((candidate) => candidate.name === segment);
1432
+ if (!entry) {
1433
+ return { ...base, status: 'unknown', reason: 'unknown-segment', stoppedAt: { index, segment } };
1434
+ }
1435
+ resolved.push(entry);
1436
+ const next = entry.isCollection ? undefined : entry.next;
1437
+ if (!next) {
1438
+ // Il resto del percorso resta non verificato: e' un "non lo so", non un errore (§4.4).
1439
+ return {
1440
+ ...base,
1441
+ status: 'unverifiable',
1442
+ reason: entry.isCollection ? 'collection' : 'no-object-type',
1443
+ stoppedAt: { index: index + 1, segment: segments[index + 1] },
1444
+ };
1445
+ }
1446
+ container = next;
1447
+ }
1448
+ // Irraggiungibile: `split` restituisce sempre almeno un segmento.
1449
+ return { ...base, status: 'empty' };
1450
+ }
1451
+ /** Come chiamare una tappa in un messaggio: la classe ha una label, l'entita' il proprio nome. */
1452
+ function pathContainerLabel(level) {
1453
+ if (!level) {
1454
+ return '';
1455
+ }
1456
+ return level.label || level.container.name;
1457
+ }
1458
+ /**
1459
+ * Il testo dell'avviso `PATH_NOT_VERIFIABLE`, con il **motivo**: "non verificabile" senza dire
1460
+ * perche' e' indistinguibile da un difetto dell'editor.
1461
+ */
1462
+ function pathNotVerifiableMessage(resolution) {
1463
+ const level = resolution.levels[resolution.levels.length - 1];
1464
+ const stopped = resolution.stoppedAt?.segment;
1465
+ const before = resolution.resolved[resolution.resolved.length - 1]?.name;
1466
+ const tail = stopped ? `«${stopped}»` : 'il resto del percorso';
1467
+ switch (resolution.reason) {
1468
+ case 'collection':
1469
+ return `«${before}» e’ una collection: dentro una collection il percorso non si verifica, ${tail} resta non verificato (PATH_NOT_VERIFIABLE).`;
1470
+ case 'no-object-type':
1471
+ return `«${before}» non dichiara dove il percorso continua: ${tail} resta non verificato (PATH_NOT_VERIFIABLE).`;
1472
+ case 'unknown-container':
1473
+ return `La classe ${level?.container.name} non e’ registrata: correggi la classe, non il percorso (STRUCTURE_TYPE_UNKNOWN).`;
1474
+ case 'undeclared':
1475
+ return level?.container.kind === 'structure'
1476
+ ? `${pathContainerLabel(level)} non dichiara i suoi membri: ${tail} non e’ verificabile (PATH_NOT_VERIFIABLE).`
1477
+ : `Il catalogo dei campi di ${pathContainerLabel(level)} non e’ popolato: ${tail} non e’ verificabile (PATH_NOT_VERIFIABLE).`;
1478
+ case 'unknown-catalog':
1479
+ return `Catalogo non disponibile: ${tail} non e’ verificabile (PATH_NOT_VERIFIABLE).`;
1480
+ default:
1481
+ return `${tail} non e’ verificabile (PATH_NOT_VERIFIABLE).`;
1482
+ }
1483
+ }
1484
+ /** I nomi proponibili nella tappa dove un segmento non e' stato trovato: il messaggio li elenca. */
1485
+ function pathAvailableNames(resolution) {
1486
+ const level = resolution.levels[resolution.levels.length - 1];
1487
+ return (level?.entries ?? []).map((entry) => entry.name);
1488
+ }
1489
+ /**
1490
+ * La riga di descrizione di un segmento proponibile. `showWritability` la chiede chi sta
1491
+ * compilando una **destinazione**: in un filtro "sola lettura" sarebbe rumore, in un campo di
1492
+ * destinazione e' l'informazione che evita `TARGET_NOT_WRITABLE`.
1493
+ */
1494
+ function describePathEntry(entry, options) {
1495
+ const parts = [];
1496
+ if (entry.label && entry.label !== entry.name) {
1497
+ parts.push(entry.label);
1498
+ }
1499
+ if (entry.dataType) {
1500
+ const type = entry.isCollection ? `${entry.dataType}[]` : entry.dataType;
1501
+ parts.push(entry.objectType ? `${type} · ${entry.objectType}` : type);
1502
+ }
1503
+ if (options?.showWritability && !entry.isWritable) {
1504
+ parts.push('sola lettura');
1505
+ }
1506
+ if (entry.description) {
1507
+ parts.push(entry.description);
1508
+ }
1509
+ return parts.join(' · ');
1510
+ }
1511
+ /**
1512
+ * La navigazione come coppia di signal, per i pickers. Da chiamare in un **contesto di
1513
+ * iniezione** (il costruttore del componente), perche' dentro crea un `effect`.
1514
+ *
1515
+ * Il token di sequenza non e' cosmetico: le tappe si caricano una alla volta e una risposta
1516
+ * superata che arriva dopo mostrerebbe l'avviso di un percorso che l'utente ha già cambiato.
1517
+ */
1518
+ function navigatePath(catalog, request) {
1519
+ const resolution = signal(null, ...(ngDevMode ? [{ debugName: "resolution" }] : []));
1520
+ const isPending = signal(false, ...(ngDevMode ? [{ debugName: "isPending" }] : []));
1521
+ let sequence = 0;
1522
+ effect(() => {
1523
+ const current = request();
1524
+ const token = ++sequence;
1525
+ if (!current?.root?.name) {
1526
+ resolution.set(null);
1527
+ isPending.set(false);
1528
+ return;
1529
+ }
1530
+ isPending.set(true);
1531
+ void resolvePath(catalog, current.root, current.path, { usage: current.usage }).then((result) => {
1532
+ if (token !== sequence) {
1533
+ return;
1534
+ }
1535
+ resolution.set(result);
1536
+ isPending.set(false);
1537
+ });
1538
+ });
1539
+ return { resolution: resolution.asReadonly(), isPending: isPending.asReadonly() };
1540
+ }
1541
+
1221
1542
  /**
1222
1543
  * Lo store del documento in lavorazione.
1223
1544
  *
@@ -2124,7 +2445,22 @@ class FlowValidationStore {
2124
2445
  issues = computed(() => this._result()?.issues ?? [], ...(ngDevMode ? [{ debugName: "issues" }] : []));
2125
2446
  errorCount = computed(() => this._result()?.errorCount ?? 0, ...(ngDevMode ? [{ debugName: "errorCount" }] : []));
2126
2447
  warningCount = computed(() => this._result()?.warningCount ?? 0, ...(ngDevMode ? [{ debugName: "warningCount" }] : []));
2127
- infoCount = computed(() => this.issues().filter((issue) => issue.severity === 'Info').length, ...(ngDevMode ? [{ debugName: "infoCount" }] : []));
2448
+ /**
2449
+ * I conteggi per gravita' **ricavati dai rilievi**, non quelli del risultato.
2450
+ *
2451
+ * Servono a etichettare i filtri del pannello, e un'etichetta di filtro deve contare
2452
+ * esattamente cio' che il filtro mostrera': `errorCount` del backend e' l'unica verita' per
2453
+ * l'attivazione (§8.3) ma non e' allineato ai secchi, quindi usarlo sul chip produrrebbe un
2454
+ * «Errori 3» che apre una lista di due. Il livello si normalizza (`severityBucket`) perche'
2455
+ * la stringa e' aperta.
2456
+ */
2457
+ issueCounts = computed(() => {
2458
+ const counts = { Error: 0, Warning: 0, Info: 0 };
2459
+ for (const issue of this.issues()) {
2460
+ counts[severityBucket(issue.severity)] += 1;
2461
+ }
2462
+ return counts;
2463
+ }, ...(ngDevMode ? [{ debugName: "issueCounts" }] : []));
2128
2464
  /**
2129
2465
  * `false` finche' non e' arrivata una validazione: non presentare l'assenza di rilievi
2130
2466
  * come garanzia di correttezza (§7, ultimo capoverso).
@@ -2154,13 +2490,9 @@ class FlowValidationStore {
2154
2490
  if (!issues?.length) {
2155
2491
  return null;
2156
2492
  }
2157
- if (issues.some((issue) => issue.severity === 'Error')) {
2158
- return 'Error';
2159
- }
2160
- if (issues.some((issue) => issue.severity === 'Warning')) {
2161
- return 'Warning';
2162
- }
2163
- return 'Info';
2493
+ const buckets = new Set(issues.map((issue) => severityBucket(issue.severity)));
2494
+ // `SEVERITY_BUCKETS` e' ordinato per gravita' decrescente: il primo che c'e' vince.
2495
+ return SEVERITY_BUCKETS.find((bucket) => buckets.has(bucket)) ?? 'Info';
2164
2496
  }
2165
2497
  /**
2166
2498
  * I rilievi che riguardano un campo dentro un elemento.
@@ -2762,6 +3094,12 @@ const FLOW_NODE_HEIGHT = 116;
2762
3094
  * restare uguali: la stessa misura serve a disegnare il node e a stimarlo per dagre.
2763
3095
  */
2764
3096
  const OUTLET_WIDTHS = [240, 240, 240, 304, 380, 456, 520];
3097
+ /**
3098
+ * Margine attorno al flow quando lo si inquadra: senza, i node di bordo toccano il bordo
3099
+ * della viewport e sembrano tagliati. In basso a destra ci sono minimappa e pulsante, ma il
3100
+ * padding e' simmetrico: sbilanciarlo sposta il flow fuori centro.
3101
+ */
3102
+ const FIT_PADDING = { x: 120, y: 120 };
2765
3103
  /** Oltre questo numero di rami il node non cresce piu': le etichette si troncano. */
2766
3104
  const MAX_OUTLET_BUCKET = OUTLET_WIDTHS.length - 1;
2767
3105
  function outletBucket(outletCount) {
@@ -2797,12 +3135,56 @@ class FlowCanvasComponent {
2797
3135
  * Assente → si usa il calcolo locale.
2798
3136
  */
2799
3137
  outline = input(null, ...(ngDevMode ? [{ debugName: "outline" }] : []));
3138
+ /**
3139
+ * Lo decide il backend, non il client (§8): su una versione non modificabile i comandi che
3140
+ * mutano il documento non si offrono. Arriva come input invece di leggere la sessione,
3141
+ * perche' il canvas resti montabile da solo.
3142
+ */
3143
+ isEditable = input(true, ...(ngDevMode ? [{ debugName: "isEditable" }] : []));
2800
3144
  selectionChange = output();
2801
3145
  nodeOpened = output();
2802
3146
  nodeRemoveRequested = output();
2803
3147
  nodeDuplicateRequested = output();
2804
3148
  /** Rilascio di un elemento dalla palette: tipo + punto nel sistema del canvas. */
2805
3149
  elementDropped = output();
3150
+ // -------------------------------------------------------------------------
3151
+ // Viewport
3152
+ // -------------------------------------------------------------------------
3153
+ canvas = viewChild.required(FCanvasComponent);
3154
+ _hasMovedViewport = signal(false, ...(ngDevMode ? [{ debugName: "_hasMovedViewport" }] : []));
3155
+ /** L'utente ha zoomato o spostato la vista: il comando per rimetterla a posto ha senso. */
3156
+ hasMovedViewport = this._hasMovedViewport.asReadonly();
3157
+ /** Il primo `fNodesRendered` inquadra, i successivi no: rifarlo combatterebbe con l'utente. */
3158
+ hasFramedOnce = false;
3159
+ /**
3160
+ * `fitToScreen` va chiamato **dopo** che i node sono stati renderizzati: prima, la libreria
3161
+ * non conosce ancora le loro dimensioni e inquadra la vista sbagliata (FF1009).
3162
+ *
3163
+ * Con un solo node (un flow appena creato ha solo lo Start) non si inquadra niente: la
3164
+ * vista si spalancherebbe su una card sola.
3165
+ */
3166
+ onNodesRendered() {
3167
+ if (this.hasFramedOnce || this.nodes().length < 2) {
3168
+ return;
3169
+ }
3170
+ this.hasFramedOnce = true;
3171
+ this.frameFlow(false);
3172
+ }
3173
+ /**
3174
+ * Qualunque zoom o panoramica accende il pulsante. `frameFlow` passa
3175
+ * `emitCanvasChange: false`, quindi il ritorno alla vista iniziale non rientra da qui e il
3176
+ * pulsante si spegne invece di restare acceso per sempre.
3177
+ */
3178
+ onCanvasChange() {
3179
+ this._hasMovedViewport.set(true);
3180
+ }
3181
+ resetViewport() {
3182
+ this.frameFlow(true);
3183
+ this._hasMovedViewport.set(false);
3184
+ }
3185
+ frameFlow(animated) {
3186
+ this.canvas().fitToScreen(FIT_PADDING, animated, false);
3187
+ }
2806
3188
  /** Raggiungibilita': backend se c'e', locale altrimenti. */
2807
3189
  reachability = computed(() => {
2808
3190
  const outline = this.outline();
@@ -3018,6 +3400,9 @@ class FlowCanvasComponent {
3018
3400
  }
3019
3401
  /** `Delete` sulla selezione: lo Start non e' cancellabile, e' uno per flow (§3.4). */
3020
3402
  onDeleteSelected(event) {
3403
+ if (!this.isEditable()) {
3404
+ return;
3405
+ }
3021
3406
  for (const id of event.nodeIds) {
3022
3407
  const name = parseCanvasNodeId(id);
3023
3408
  if (name && name !== START_NODE_NAME) {
@@ -3055,6 +3440,15 @@ class FlowCanvasComponent {
3055
3440
  event.stopPropagation();
3056
3441
  this.nodeOpened.emit(name);
3057
3442
  }
3443
+ /**
3444
+ * `stopPropagation` come per ✎: senza, il clic arriva anche al node e la selezione cambia
3445
+ * mentre l'elemento sta sparendo. La rimozione la esegue il documento, non il canvas:
3446
+ * `removeNode` toglie anche i connector che lo puntavano.
3447
+ */
3448
+ onRemoveClick(event, name) {
3449
+ event.stopPropagation();
3450
+ this.nodeRemoveRequested.emit(name);
3451
+ }
3058
3452
  connectorIdOf(node, outletKey) {
3059
3453
  return sourceConnectorId(node.name, outletKey);
3060
3454
  }
@@ -3092,12 +3486,12 @@ class FlowCanvasComponent {
3092
3486
  return 'cat-other';
3093
3487
  }
3094
3488
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.27", ngImport: i0, type: FlowCanvasComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3095
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.27", type: FlowCanvasComponent, isStandalone: true, selector: "fb-flow-canvas", inputs: { selectedName: { classPropertyName: "selectedName", publicName: "selectedName", isSignal: true, isRequired: false, transformFunction: null }, outline: { classPropertyName: "outline", publicName: "outline", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectionChange: "selectionChange", nodeOpened: "nodeOpened", nodeRemoveRequested: "nodeRemoveRequested", nodeDuplicateRequested: "nodeDuplicateRequested", elementDropped: "elementDropped" }, ngImport: i0, template: "<!--\r\n Gerarchia obbligatoria: f-flow > f-canvas > fNode / f-connection.\r\n I `@for` sono direttamente dentro <f-canvas>: nessun wrapper, quindi non serve\r\n `ngProjectAs` (che sarebbe indispensabile con blocchi annidati).\r\n-->\r\n<f-flow\r\n fDraggable\r\n (fCreateConnection)=\"onCreateConnection($event)\"\r\n (fReassignConnection)=\"onReassignConnection($event)\"\r\n (fMoveNodes)=\"onMoveNodes($event)\"\r\n (fSelectionChange)=\"onSelectionChange($event)\"\r\n (fDeleteSelected)=\"onDeleteSelected($event)\"\r\n (fCreateNode)=\"onCreateNode($event)\"\r\n>\r\n <f-canvas fZoom>\r\n <f-background>\r\n <f-circle-pattern />\r\n </f-background>\r\n\r\n @for (edge of edges(); track edge.id) {\r\n <f-connection\r\n [fConnectionId]=\"edge.id\"\r\n [fSourceId]=\"edge.sourceId\"\r\n [fTargetId]=\"edge.targetId\"\r\n fBehavior=\"floating\"\r\n [fType]=\"edge.isGoTo ? 'segment' : 'bezier'\"\r\n [class]=\"'fb-edge fb-edge--' + edge.kind + (edge.isGoTo ? ' fb-edge--goto' : '')\"\r\n >\r\n <f-connection-marker-arrow [type]=\"markerEnd\" />\r\n @if (edge.label) {\r\n <div fConnectionContent class=\"fb-edge-label\">{{ edge.label }}</div>\r\n }\r\n </f-connection>\r\n }\r\n\r\n @for (node of nodes(); track node.id) {\r\n <div\r\n fNode\r\n fDragHandle\r\n [fNodeId]=\"node.id\"\r\n [fNodePosition]=\"node.position\"\r\n [class]=\"'fb-node ' + categoryClass(node) + ' ' + node.widthClass\"\r\n [class.fb-node--start]=\"node.isStart\"\r\n [class.fb-node--selected]=\"isSelected(node)\"\r\n [class.fb-node--unreachable]=\"!node.isReachable\"\r\n [class.fb-node--error]=\"node.severity === 'Error'\"\r\n [class.fb-node--warning]=\"node.severity === 'Warning'\"\r\n (dblclick)=\"onNodeDoubleClick(node.name)\"\r\n >\r\n <!--\r\n Lo Start non ha ingresso: e' il punto di partenza (\u00A73.4).\r\n `fConnectorConnectableSide` e' cio' che fa entrare l'arco dall'alto: senza, foblex\r\n calcola il lato e un arco che scende dal node sopra potrebbe agganciarsi di fianco.\r\n -->\r\n @if (!node.isStart) {\r\n <div\r\n fConnector\r\n fConnectorType=\"target\"\r\n [fConnectorId]=\"targetIdOf(node)\"\r\n [fConnectorConnectableSide]=\"sides.TOP\"\r\n fConnectorMultiple=\"true\"\r\n class=\"fb-connector fb-connector--in\"\r\n title=\"Ingresso\"\r\n ></div>\r\n }\r\n\r\n <div class=\"fb-node__head\">\r\n <span class=\"fb-node__icon\" aria-hidden=\"true\">{{ node.icon }}</span>\r\n <div class=\"fb-node__text\">\r\n <span class=\"fb-node__title\" [title]=\"node.description || node.label\">{{ node.label }}</span>\r\n <span class=\"fb-node__sub\">\r\n {{ node.typeLabel }}\r\n @if (node.subtitle) {\r\n <span class=\"fb-node__sub-dot\">\u00B7</span>{{ node.subtitle }}\r\n }\r\n </span>\r\n </div>\r\n @if (node.hasAutomaticOutput) {\r\n <!-- L'elemento espone il proprio risultato sotto il proprio nome (\u00A74.5). -->\r\n <span class=\"fb-node__auto\" title=\"Espone un output automatico referenziabile come \u00AB{{ node.name }}\u00BB\">\r\n \u0192\r\n </span>\r\n }\r\n <!--\r\n `fDragBlocker` impedisce che il pointerdown sul bottone diventi un trascinamento\r\n del node: senza, aprire il dettaglio sposterebbe l'elemento di qualche pixel.\r\n -->\r\n <button\r\n type=\"button\"\r\n fDragBlocker\r\n class=\"fb-node__edit\"\r\n [attr.aria-label]=\"'Apri il dettaglio di ' + node.label\"\r\n title=\"Apri il dettaglio\"\r\n (click)=\"onEditClick($event, node.name)\"\r\n >\r\n \u270E\r\n </button>\r\n </div>\r\n\r\n <div class=\"fb-node__meta\">\r\n @if (!node.isStart) {\r\n <span class=\"fb-node__name\" [title]=\"'Nome tecnico: ' + node.name\">{{ node.name }}</span>\r\n }\r\n @if (!node.isReachable) {\r\n <span class=\"fb-badge fb-badge--unreachable\" title=\"Nessun percorso raggiunge questo elemento dallo Start\">\r\n scollegato\r\n </span>\r\n }\r\n @if (node.issueCount > 0) {\r\n <span\r\n class=\"fb-badge\"\r\n [class.fb-badge--error]=\"node.severity === 'Error'\"\r\n [class.fb-badge--warning]=\"node.severity === 'Warning'\"\r\n [class.fb-badge--info]=\"node.severity === 'Info'\"\r\n [title]=\"node.issueCount + ' rilievi di validazione'\"\r\n >\r\n {{ node.issueCount }}\r\n </span>\r\n }\r\n @if (node.danglingOutlets.length > 0) {\r\n <span\r\n class=\"fb-badge fb-badge--dangling\"\r\n [title]=\"'Rami dichiarati senza destinazione: ' + danglingLabels(node)\"\r\n >\r\n ramo incompleto\r\n </span>\r\n }\r\n </div>\r\n\r\n <!--\r\n Le uscite stanno sul bordo **inferiore**, una per ramo, nell'ordine in cui il modello\r\n le dichiara: per una Decision e' l'ordine di valutazione delle regole, che e'\r\n semantico (\u00A75.3), e da sinistra a destra si legge come la lista nell'inspector.\r\n L'etichetta si mostra solo quando i rami sono piu' di uno: su un `next` unico\r\n direbbe soltanto \u00ABSuccessivo\u00BB.\r\n -->\r\n <div class=\"fb-node__outlets\" [class.fb-node__outlets--labelled]=\"node.showsOutletLabels\">\r\n @for (outlet of node.outlets; track outlet.key) {\r\n <div class=\"fb-outlet\">\r\n @if (node.showsOutletLabels) {\r\n <span class=\"fb-outlet__label\" [title]=\"outlet.label\">{{ outlet.label }}</span>\r\n }\r\n <div\r\n fConnector\r\n fConnectorType=\"source\"\r\n [fConnectorId]=\"connectorIdOf(node, outlet.key)\"\r\n [fConnectorConnectableSide]=\"sides.BOTTOM\"\r\n [class]=\"'fb-connector fb-connector--out fb-connector--' + outlet.kind\"\r\n [title]=\"outlet.label\"\r\n ></div>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- Anteprima dell'arco durante il trascinamento. -->\r\n <f-connection-for-create fBehavior=\"floating\" fType=\"bezier\" class=\"fb-edge fb-edge--creating\">\r\n <f-connection-marker-arrow [type]=\"markerEnd\" />\r\n </f-connection-for-create>\r\n\r\n <f-selection-area />\r\n </f-canvas>\r\n\r\n <f-minimap [fMinSize]=\"1200\" class=\"fb-minimap\" />\r\n</f-flow>\r\n", styles: [":host{display:block;position:relative;width:100%;height:100%;overflow:hidden;background:var(--fb-canvas-bg, #f4f5f7)}f-flow{display:block;width:100%;height:100%}.fb-minimap{position:absolute;right:14px;bottom:14px;width:190px;height:130px;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius, 10px);background:var(--fb-surface, #fff);box-shadow:var(--fb-shadow-md, 0 6px 18px rgb(16 24 40 / 10%));overflow:hidden}.fb-node{position:absolute;display:flex;flex-direction:column;box-sizing:border-box;width:240px;padding:0;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-lg, 12px);background:var(--fb-surface, #fff);box-shadow:var(--fb-shadow-sm, 0 1px 2px rgb(16 24 40 / 6%));font:inherit;cursor:grab;-webkit-user-select:none;user-select:none;transition:box-shadow .12s ease,border-color .12s ease}.fb-node--outlets-3{width:304px}.fb-node--outlets-4{width:380px}.fb-node--outlets-5{width:456px}.fb-node--outlets-6{width:520px}.fb-node:hover{box-shadow:var(--fb-shadow-md, 0 6px 18px rgb(16 24 40 / 10%))}.fb-node--selected{border-color:var(--fb-accent, #4f6ef7);box-shadow:0 0 0 3px color-mix(in srgb,var(--fb-accent, #4f6ef7) 22%,transparent)}.fb-node--unreachable{border-style:dashed;opacity:.8}.fb-node--error{border-color:var(--fb-error, #c9372c)}.fb-node--warning{border-color:var(--fb-warning, #b7791f)}.fb-node__head{display:flex;align-items:center;gap:8px;padding:10px 10px 6px 12px}.fb-node__icon{display:grid;place-items:center;flex:0 0 auto;width:28px;height:28px;border-radius:var(--fb-radius-sm, 8px);background:var(--fb-node-accent, #667085);color:#fff;font-size:14px;line-height:1}.cat-start{--fb-node-accent: #22a06b}.cat-screen{--fb-node-accent: #3b82f6}.cat-logic{--fb-node-accent: #8b5cf6}.cat-data{--fb-node-accent: #06b6d4}.cat-action{--fb-node-accent: #f59e0b}.cat-flow{--fb-node-accent: #14b8a6}.cat-other{--fb-node-accent: #667085}.fb-node__text{flex:1;min-width:0}.fb-node__title{display:block;font-size:13px;font-weight:600;line-height:17px;color:var(--fb-text, #1a1c23);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-node__sub{display:block;margin-top:1px;font-size:11px;line-height:14px;color:var(--fb-text-muted, #6b7086);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-node__sub-dot{margin:0 4px;color:var(--fb-text-subtle, #98a2b3)}.fb-node__auto{flex:0 0 auto;font-size:12px;font-weight:700;color:var(--fb-accent, #4f6ef7);cursor:help}.fb-node__edit{display:grid;place-items:center;flex:0 0 auto;width:22px;height:22px;padding:0;border:0;border-radius:var(--fb-radius-xs, 6px);background:transparent;color:var(--fb-text-subtle, #98a2b3);font:inherit;font-size:12px;cursor:pointer;opacity:0;transition:opacity .12s ease,background .12s ease}.fb-node:hover .fb-node__edit,.fb-node--selected .fb-node__edit,.fb-node__edit:focus-visible{opacity:1}.fb-node__edit:hover{background:var(--fb-surface-alt, #f7f8fa);color:var(--fb-text, #1a1c23)}.fb-node__meta{display:flex;flex-wrap:wrap;align-items:center;gap:4px;min-height:14px;padding:0 12px 6px}.fb-node__name{flex:0 1 auto;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:10px;color:var(--fb-text-subtle, #98a2b3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-badge{padding:1px 6px;border-radius:10px;background:var(--fb-badge-bg, #eef0f4);font-size:10px;font-weight:600;color:var(--fb-text-muted, #6b7086);white-space:nowrap;cursor:help}.fb-badge--error{background:color-mix(in srgb,var(--fb-error, #c9372c) 14%,transparent);color:var(--fb-error, #c9372c)}.fb-badge--warning{background:color-mix(in srgb,var(--fb-warning, #b7791f) 16%,transparent);color:var(--fb-warning, #b7791f)}.fb-badge--info{background:color-mix(in srgb,var(--fb-accent, #4f6ef7) 12%,transparent);color:var(--fb-accent, #4f6ef7)}.fb-badge--unreachable,.fb-badge--dangling{background:color-mix(in srgb,var(--fb-warning, #b7791f) 12%,transparent);color:var(--fb-warning, #b7791f)}.fb-node__outlets{display:flex;align-items:flex-end;justify-content:space-evenly;gap:4px;padding:0 8px 4px}.fb-node__outlets--labelled{padding-top:5px;border-top:1px solid var(--fb-border-subtle, #eef0f4)}.fb-outlet{display:flex;flex:1 1 0;flex-direction:column;align-items:center;gap:2px;min-width:0}.fb-outlet__label{max-width:100%;font-size:10px;line-height:13px;color:var(--fb-text-muted, #6b7086);text-align:center;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-connector{box-sizing:border-box;width:12px;height:12px;flex:0 0 auto;border:2px solid var(--fb-surface, #fff);border-radius:50%;background:var(--fb-connector, #98a2b3);cursor:crosshair;transition:transform .12s ease,box-shadow .12s ease}f-flow .fb-connector--out{position:relative;inset:auto;margin-bottom:-12px}.fb-connector--out:hover{transform:scale(1.2)}f-flow .fb-connector--in{position:absolute;inset:-2px auto auto 50%;width:24px;height:4px;border:0;border-radius:2px;transform:translate(-50%);background:var(--fb-border-strong, #cfd4de)}.fb-connector--out{--ff-connector-connected-color: var(--fb-connector, #98a2b3)}.fb-connector--Next,.fb-connector--Start,.fb-connector--LoopNext{--fb-connector: var(--fb-edge-next, #98a2b3)}.fb-connector--Rule,.fb-connector--WaitEvent{--fb-connector: var(--fb-edge-rule, #8b5cf6)}.fb-connector--Default,.fb-connector--LoopEnd{--fb-connector: var(--fb-edge-default, #06b6d4)}.fb-connector--Fault{--fb-connector: var(--fb-edge-fault, #dc2626)}.fb-connector--Timeout,.fb-connector--ScheduledPath{--fb-connector: var(--fb-edge-timeout, #f59e0b)}.fb-connector.f-connector-connectable{box-shadow:0 0 0 4px color-mix(in srgb,var(--fb-accent, #4f6ef7) 28%,transparent)}\n"], dependencies: [{ kind: "ngmodule", type: FFlowModule }, { kind: "component", type: i1.FFlowComponent, selector: "f-flow", inputs: ["fFlowId", "fCache"], outputs: ["fNodesRendered", "fFullRendered", "fLoaded"] }, { kind: "component", type: i1.FCanvasComponent, selector: "f-canvas", inputs: ["position", "scale", "debounceTime", "fLayers"], outputs: ["fCanvasChange"] }, { kind: "component", type: i1.FBackgroundComponent, selector: "f-background" }, { kind: "component", type: i1.FCirclePatternComponent, selector: "f-circle-pattern", inputs: ["id", "color", "radius"] }, { kind: "directive", type: i1.FZoomDirective, selector: "f-canvas[fZoom]", inputs: ["fZoom", "fWheelTrigger", "fDblClickTrigger", "fZoomMinimum", "fZoomMaximum", "fZoomStep", "fPinchStep", "fZoomDblClickStep"] }, { kind: "component", type: i1.FSelectionArea, selector: "f-selection-area", inputs: ["fTrigger"] }, { kind: "directive", type: i1.FConnectionContent, selector: "[fConnectionContent]", inputs: ["position", "offset", "align"] }, { kind: "component", type: i1.FConnectionMarkerArrow, selector: "f-connection-marker-arrow", inputs: ["type"] }, { kind: "component", type: i1.FConnectionComponent, selector: "f-connection", inputs: ["fConnectionId", "fSourceId", "fTargetId", "fOutputId", "fInputId", "fRadius", "fOffset", "fBehavior", "fType", "fSelectionDisabled", "fReassignableStart", "fReassignDisabled", "fSourceSide", "fTargetSide", "fInputSide", "fOutputSide"], exportAs: ["fComponent"] }, { kind: "component", type: i1.FConnectionForCreateComponent, selector: "f-connection-for-create", inputs: ["fRadius", "fOffset", "fBehavior", "fType", "fInputSide", "fOutputSide"] }, { kind: "directive", type: i1.FConnectorDirective, selector: "[fConnector]", inputs: ["fConnectorId", "fConnectorType", "fConnectorDisabled", "fConnectorMultiple", "fConnectorCategory", "fConnectorConnectableSide", "fConnectorSelfConnectable", "fCanBeConnectedTo", "fConnectionFromOutlet"], exportAs: ["fConnector"] }, { kind: "component", type: i1.FMinimapComponent, selector: "f-minimap", inputs: ["fMinSize", "fNodeRenderLimit"], exportAs: ["fComponent"] }, { kind: "directive", type: i1.FNodeDirective, selector: "[fNode]", inputs: ["fNodeId", "fNodeParentId", "fNodePosition", "fNodeSize", "fNodeRotate", "fConnectOnNode", "fMinimapClass", "fNodeDraggingDisabled", "fNodeSelectionDisabled", "fIncludePadding", "fAutoExpandOnChildHit", "fAutoSizeToFitChildren"], outputs: ["fNodePositionChange", "fNodeSizeChange", "fNodeRotateChange"], exportAs: ["fComponent"] }, { kind: "directive", type: i1.FDragHandleDirective, selector: "[fDragHandle]" }, { kind: "directive", type: i1.FDragBlockerDirective, selector: "[fDragBlocker]" }, { kind: "directive", type: i1.FDraggableDirective, selector: "f-flow[fDraggable]", inputs: ["fDraggableDisabled", "fDropToGroup", "fMultiSelectTrigger", "fReassignConnectionTrigger", "fCreateConnectionTrigger", "fConnectionWaypointsTrigger", "fMoveControlPointTrigger", "fNodeResizeTrigger", "fNodeRotateTrigger", "fNodeMoveTrigger", "fCanvasMoveTrigger", "fExternalItemTrigger", "fEmitOnNodeIntersect", "vCellSize", "hCellSize", "fCellSizeWhileDragging"], outputs: ["fSelectionChange", "fDeleteSelected", "fNodeIntersectedWithConnections", "fNodeConnectionsIntersection", "fCreateNode", "fMoveNodes", "fReassignConnection", "fCreateConnection", "fConnectionWaypointsChanged", "fDropToGroup", "fDragStarted", "fDragEnded"], exportAs: ["fDraggable"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3489
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.27", type: FlowCanvasComponent, isStandalone: true, selector: "fb-flow-canvas", inputs: { selectedName: { classPropertyName: "selectedName", publicName: "selectedName", isSignal: true, isRequired: false, transformFunction: null }, outline: { classPropertyName: "outline", publicName: "outline", isSignal: true, isRequired: false, transformFunction: null }, isEditable: { classPropertyName: "isEditable", publicName: "isEditable", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectionChange: "selectionChange", nodeOpened: "nodeOpened", nodeRemoveRequested: "nodeRemoveRequested", nodeDuplicateRequested: "nodeDuplicateRequested", elementDropped: "elementDropped" }, providers: [provideFFlow(withA11y())], viewQueries: [{ propertyName: "canvas", first: true, predicate: FCanvasComponent, descendants: true, isSignal: true }], ngImport: i0, template: "<!--\r\n Gerarchia obbligatoria: f-flow > f-canvas > fNode / f-connection.\r\n I `@for` sono direttamente dentro <f-canvas>: nessun wrapper, quindi non serve\r\n `ngProjectAs` (che sarebbe indispensabile con blocchi annidati).\r\n-->\r\n<f-flow\r\n fDraggable\r\n (fCreateConnection)=\"onCreateConnection($event)\"\r\n (fReassignConnection)=\"onReassignConnection($event)\"\r\n (fMoveNodes)=\"onMoveNodes($event)\"\r\n (fSelectionChange)=\"onSelectionChange($event)\"\r\n (fDeleteSelected)=\"onDeleteSelected($event)\"\r\n (fCreateNode)=\"onCreateNode($event)\"\r\n (fNodesRendered)=\"onNodesRendered()\"\r\n>\r\n <!--\r\n `#canvas` + `(fCanvasChange)`: il primo serve per chiamare `fitToScreen()`, il secondo per\r\n sapere che l'utente ha mosso la vista. `[debounceTime]` non si imposta: l'evento serve solo\r\n ad accendere un flag, non a ricalcolare niente.\r\n -->\r\n <f-canvas fZoom #canvas (fCanvasChange)=\"onCanvasChange()\">\r\n <f-background>\r\n <f-circle-pattern />\r\n </f-background>\r\n\r\n @for (edge of edges(); track edge.id) {\r\n <f-connection\r\n [fConnectionId]=\"edge.id\"\r\n [fSourceId]=\"edge.sourceId\"\r\n [fTargetId]=\"edge.targetId\"\r\n fBehavior=\"floating\"\r\n [fType]=\"edge.isGoTo ? 'segment' : 'bezier'\"\r\n [class]=\"'fb-edge fb-edge--' + edge.kind + (edge.isGoTo ? ' fb-edge--goto' : '')\"\r\n >\r\n <f-connection-marker-arrow [type]=\"markerEnd\" />\r\n @if (edge.label) {\r\n <div fConnectionContent class=\"fb-edge-label\">{{ edge.label }}</div>\r\n }\r\n </f-connection>\r\n }\r\n\r\n @for (node of nodes(); track node.id) {\r\n <div\r\n fNode\r\n fDragHandle\r\n [fNodeId]=\"node.id\"\r\n [fNodePosition]=\"node.position\"\r\n [class]=\"'fb-node ' + categoryClass(node) + ' ' + node.widthClass\"\r\n [class.fb-node--start]=\"node.isStart\"\r\n [class.fb-node--selected]=\"isSelected(node)\"\r\n [class.fb-node--unreachable]=\"!node.isReachable\"\r\n [class.fb-node--error]=\"node.severity === 'Error'\"\r\n [class.fb-node--warning]=\"node.severity === 'Warning'\"\r\n (dblclick)=\"onNodeDoubleClick(node.name)\"\r\n >\r\n <!--\r\n Lo Start non ha ingresso: e' il punto di partenza (\u00A73.4).\r\n `fConnectorConnectableSide` e' cio' che fa entrare l'arco dall'alto: senza, foblex\r\n calcola il lato e un arco che scende dal node sopra potrebbe agganciarsi di fianco.\r\n -->\r\n @if (!node.isStart) {\r\n <div\r\n fConnector\r\n fConnectorType=\"target\"\r\n [fConnectorId]=\"targetIdOf(node)\"\r\n [fConnectorConnectableSide]=\"sides.TOP\"\r\n fConnectorMultiple=\"true\"\r\n class=\"fb-connector fb-connector--in\"\r\n title=\"Ingresso\"\r\n ></div>\r\n }\r\n\r\n <div class=\"fb-node__head\">\r\n <span class=\"fb-node__icon\" aria-hidden=\"true\">{{ node.icon }}</span>\r\n <div class=\"fb-node__text\">\r\n <span class=\"fb-node__title\" [title]=\"node.description || node.label\">{{ node.label }}</span>\r\n <span class=\"fb-node__sub\">\r\n {{ node.typeLabel }}\r\n @if (node.subtitle) {\r\n <span class=\"fb-node__sub-dot\">\u00B7</span>{{ node.subtitle }}\r\n }\r\n </span>\r\n </div>\r\n @if (node.hasAutomaticOutput) {\r\n <!-- L'elemento espone il proprio risultato sotto il proprio nome (\u00A74.5). -->\r\n <span class=\"fb-node__auto\" title=\"Espone un output automatico referenziabile come \u00AB{{ node.name }}\u00BB\">\r\n \u0192\r\n </span>\r\n }\r\n <!--\r\n `fDragBlocker` impedisce che il pointerdown sul bottone diventi un trascinamento\r\n del node: senza, aprire il dettaglio sposterebbe l'elemento di qualche pixel.\r\n -->\r\n <button\r\n type=\"button\"\r\n fDragBlocker\r\n class=\"fb-node__edit\"\r\n [attr.aria-label]=\"'Apri il dettaglio di ' + node.label\"\r\n title=\"Apri il dettaglio\"\r\n (click)=\"onEditClick($event, node.name)\"\r\n >\r\n \u270E\r\n </button>\r\n @if (!node.isStart && isEditable()) {\r\n <!--\r\n Si mostra solo sul node **selezionato**, non al passaggio del mouse come \u270E:\r\n cancellare non e' reversibile con un altro clic, e un comando distruttivo che\r\n appare sotto il puntatore mentre si attraversa il grafo si preme per sbaglio.\r\n Lo Start non lo espone: un flow senza ingresso non esisterebbe.\r\n -->\r\n <button\r\n type=\"button\"\r\n fDragBlocker\r\n class=\"fb-node__remove\"\r\n [attr.aria-label]=\"'Elimina ' + node.label\"\r\n title=\"Elimina questo elemento (si annulla con \u21B6)\"\r\n (click)=\"onRemoveClick($event, node.name)\"\r\n >\r\n \u00D7\r\n </button>\r\n }\r\n </div>\r\n\r\n <div class=\"fb-node__meta\">\r\n @if (!node.isStart) {\r\n <span class=\"fb-node__name\" [title]=\"'Nome tecnico: ' + node.name\">{{ node.name }}</span>\r\n }\r\n @if (!node.isReachable) {\r\n <span class=\"fb-badge fb-badge--unreachable\" title=\"Nessun percorso raggiunge questo elemento dallo Start\">\r\n scollegato\r\n </span>\r\n }\r\n @if (node.issueCount > 0) {\r\n <span\r\n class=\"fb-badge\"\r\n [class.fb-badge--error]=\"node.severity === 'Error'\"\r\n [class.fb-badge--warning]=\"node.severity === 'Warning'\"\r\n [class.fb-badge--info]=\"node.severity === 'Info'\"\r\n [title]=\"node.issueCount + ' rilievi di validazione'\"\r\n >\r\n {{ node.issueCount }}\r\n </span>\r\n }\r\n @if (node.danglingOutlets.length > 0) {\r\n <span\r\n class=\"fb-badge fb-badge--dangling\"\r\n [title]=\"'Rami dichiarati senza destinazione: ' + danglingLabels(node)\"\r\n >\r\n ramo incompleto\r\n </span>\r\n }\r\n </div>\r\n\r\n <!--\r\n Le uscite stanno sul bordo **inferiore**, una per ramo, nell'ordine in cui il modello\r\n le dichiara: per una Decision e' l'ordine di valutazione delle regole, che e'\r\n semantico (\u00A75.3), e da sinistra a destra si legge come la lista nell'inspector.\r\n L'etichetta si mostra solo quando i rami sono piu' di uno: su un `next` unico\r\n direbbe soltanto \u00ABSuccessivo\u00BB.\r\n -->\r\n <div class=\"fb-node__outlets\" [class.fb-node__outlets--labelled]=\"node.showsOutletLabels\">\r\n @for (outlet of node.outlets; track outlet.key) {\r\n <div class=\"fb-outlet\">\r\n @if (node.showsOutletLabels) {\r\n <span class=\"fb-outlet__label\" [title]=\"outlet.label\">{{ outlet.label }}</span>\r\n }\r\n <div\r\n fConnector\r\n fConnectorType=\"source\"\r\n [fConnectorId]=\"connectorIdOf(node, outlet.key)\"\r\n [fConnectorConnectableSide]=\"sides.BOTTOM\"\r\n [class]=\"'fb-connector fb-connector--out fb-connector--' + outlet.kind\"\r\n [title]=\"outlet.label\"\r\n ></div>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- Anteprima dell'arco durante il trascinamento. -->\r\n <f-connection-for-create fBehavior=\"floating\" fType=\"bezier\" class=\"fb-edge fb-edge--creating\">\r\n <f-connection-marker-arrow [type]=\"markerEnd\" />\r\n </f-connection-for-create>\r\n\r\n <f-selection-area />\r\n </f-canvas>\r\n\r\n <!--\r\n Fuori da <f-canvas> come la minimappa: dentro, la trasformazione del canvas se lo\r\n porterebbe via insieme ai node. `fDragBlocker` perche' il pointerdown non inizi una\r\n panoramica invece di premere il bottone.\r\n -->\r\n <!--\r\n Sempre nel DOM, nascosto con una classe e non con `@if`: dentro una proiezione di contenuto\r\n un blocco di controllo e' una complicazione gratuita, e `visibility: hidden` lo toglie\r\n anche dall'ordine di tabulazione. Il costo e' un bottone in piu' nel DOM, non un rischio.\r\n -->\r\n <button\r\n type=\"button\"\r\n fDragBlocker\r\n class=\"fb-btn fb-viewport-reset\"\r\n [class.fb-viewport-reset--hidden]=\"!hasMovedViewport()\"\r\n title=\"Rimetti il flow interamente in vista, al centro\"\r\n (click)=\"resetViewport()\"\r\n >\r\n <span class=\"fb-viewport-reset__icon\" aria-hidden=\"true\">\u2922</span>\r\n Inquadra il flow\r\n </button>\r\n\r\n <f-minimap [fMinSize]=\"1200\" class=\"fb-minimap\" />\r\n</f-flow>\r\n", styles: [":host{display:block;position:relative;width:100%;height:100%;overflow:hidden;background:var(--fb-canvas-bg, #f4f5f7)}f-flow{display:block;width:100%;height:100%}.fb-minimap{position:absolute;right:14px;bottom:14px;width:190px;height:130px;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius, 10px);background:var(--fb-surface, #fff);box-shadow:var(--fb-shadow-md, 0 6px 18px rgb(16 24 40 / 10%));overflow:hidden}.fb-viewport-reset{position:absolute;right:14px;bottom:152px;z-index:1;border-radius:var(--fb-radius-xs, 6px);box-shadow:var(--fb-shadow-md, 0 6px 18px rgb(16 24 40 / 10%));font-size:11px;transition:opacity .15s ease,visibility .15s ease}.fb-viewport-reset--hidden{opacity:0;visibility:hidden}.fb-viewport-reset__icon{font-size:13px;line-height:1;color:var(--fb-text-muted, #667085)}.fb-node{position:absolute;display:flex;flex-direction:column;box-sizing:border-box;width:240px;padding:0;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-lg, 12px);background:var(--fb-surface, #fff);box-shadow:var(--fb-shadow-sm, 0 1px 2px rgb(16 24 40 / 6%));font:inherit;cursor:grab;-webkit-user-select:none;user-select:none;transition:box-shadow .12s ease,border-color .12s ease}.fb-node--outlets-3{width:304px}.fb-node--outlets-4{width:380px}.fb-node--outlets-5{width:456px}.fb-node--outlets-6{width:520px}.fb-node:hover{box-shadow:var(--fb-shadow-md, 0 6px 18px rgb(16 24 40 / 10%))}.fb-node--selected{border-color:var(--fb-accent, #4f6ef7);box-shadow:0 0 0 3px color-mix(in srgb,var(--fb-accent, #4f6ef7) 22%,transparent)}.fb-node--unreachable{border-style:dashed;opacity:.8}.fb-node--error{border-color:var(--fb-error, #c9372c)}.fb-node--warning{border-color:var(--fb-warning, #b7791f)}.fb-node__head{display:flex;align-items:center;gap:8px;padding:10px 10px 6px 12px}.fb-node__icon{display:grid;place-items:center;flex:0 0 auto;width:28px;height:28px;border-radius:var(--fb-radius-sm, 8px);background:var(--fb-node-accent, #667085);color:#fff;font-size:14px;line-height:1}.cat-start{--fb-node-accent: #22a06b}.cat-screen{--fb-node-accent: #3b82f6}.cat-logic{--fb-node-accent: #8b5cf6}.cat-data{--fb-node-accent: #06b6d4}.cat-action{--fb-node-accent: #f59e0b}.cat-flow{--fb-node-accent: #14b8a6}.cat-other{--fb-node-accent: #667085}.fb-node__text{flex:1;min-width:0}.fb-node__title{display:block;font-size:13px;font-weight:600;line-height:17px;color:var(--fb-text, #1a1c23);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-node__sub{display:block;margin-top:1px;font-size:11px;line-height:14px;color:var(--fb-text-muted, #6b7086);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-node__sub-dot{margin:0 4px;color:var(--fb-text-subtle, #98a2b3)}.fb-node__auto{flex:0 0 auto;font-size:12px;font-weight:700;color:var(--fb-accent, #4f6ef7);cursor:help}.fb-node__edit{display:grid;place-items:center;flex:0 0 auto;width:22px;height:22px;padding:0;border:0;border-radius:var(--fb-radius-xs, 6px);background:transparent;color:var(--fb-text-subtle, #98a2b3);font:inherit;font-size:12px;cursor:pointer;opacity:0;transition:opacity .12s ease,background .12s ease}.fb-node:hover .fb-node__edit,.fb-node--selected .fb-node__edit,.fb-node__edit:focus-visible{opacity:1}.fb-node__edit:hover{background:var(--fb-surface-alt, #f7f8fa);color:var(--fb-text, #1a1c23)}.fb-node__remove{display:grid;place-items:center;flex:0 0 auto;width:22px;height:22px;padding:0;border:0;border-radius:var(--fb-radius-xs, 6px);background:transparent;color:var(--fb-text-subtle, #98a2b3);font:inherit;font-size:15px;line-height:1;cursor:pointer;opacity:0;transition:opacity .12s ease,background .12s ease,color .12s ease}.fb-node--selected .fb-node__remove,.fb-node__remove:focus-visible{opacity:1}.fb-node__remove:hover{background:color-mix(in srgb,var(--fb-error, #c9372c) 12%,transparent);color:var(--fb-error, #c9372c)}.fb-node__meta{display:flex;flex-wrap:wrap;align-items:center;gap:4px;min-height:14px;padding:0 12px 6px}.fb-node__name{flex:0 1 auto;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:10px;color:var(--fb-text-subtle, #98a2b3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-badge{padding:1px 6px;border-radius:10px;background:var(--fb-badge-bg, #eef0f4);font-size:10px;font-weight:600;color:var(--fb-text-muted, #6b7086);white-space:nowrap;cursor:help}.fb-badge--error{background:color-mix(in srgb,var(--fb-error, #c9372c) 14%,transparent);color:var(--fb-error, #c9372c)}.fb-badge--warning{background:color-mix(in srgb,var(--fb-warning, #b7791f) 16%,transparent);color:var(--fb-warning, #b7791f)}.fb-badge--info{background:color-mix(in srgb,var(--fb-accent, #4f6ef7) 12%,transparent);color:var(--fb-accent, #4f6ef7)}.fb-badge--unreachable,.fb-badge--dangling{background:color-mix(in srgb,var(--fb-warning, #b7791f) 12%,transparent);color:var(--fb-warning, #b7791f)}.fb-node__outlets{display:flex;align-items:flex-end;justify-content:space-evenly;gap:4px;padding:0 8px 4px}.fb-node__outlets--labelled{padding-top:5px;border-top:1px solid var(--fb-border-subtle, #eef0f4)}.fb-outlet{display:flex;flex:1 1 0;flex-direction:column;align-items:center;gap:2px;min-width:0}.fb-outlet__label{max-width:100%;font-size:10px;line-height:13px;color:var(--fb-text-muted, #6b7086);text-align:center;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-connector{box-sizing:border-box;width:12px;height:12px;flex:0 0 auto;border:2px solid var(--fb-surface, #fff);border-radius:50%;background:var(--fb-connector, #98a2b3);cursor:crosshair;transition:transform .12s ease,box-shadow .12s ease}f-flow .fb-connector--out{position:relative;inset:auto;margin-bottom:-12px}.fb-connector--out:hover{transform:scale(1.2)}f-flow .fb-connector--in{position:absolute;inset:-2px auto auto 50%;width:24px;height:4px;border:0;border-radius:2px;transform:translate(-50%);background:var(--fb-border-strong, #cfd4de)}.fb-connector--out{--ff-connector-connected-color: var(--fb-connector, #98a2b3)}.fb-connector--Next,.fb-connector--Start,.fb-connector--LoopNext{--fb-connector: var(--fb-edge-next, #98a2b3)}.fb-connector--Rule,.fb-connector--WaitEvent{--fb-connector: var(--fb-edge-rule, #8b5cf6)}.fb-connector--Default,.fb-connector--LoopEnd{--fb-connector: var(--fb-edge-default, #06b6d4)}.fb-connector--Fault{--fb-connector: var(--fb-edge-fault, #dc2626)}.fb-connector--Timeout,.fb-connector--ScheduledPath{--fb-connector: var(--fb-edge-timeout, #f59e0b)}.fb-connector.f-connector-connectable{box-shadow:0 0 0 4px color-mix(in srgb,var(--fb-accent, #4f6ef7) 28%,transparent)}\n"], dependencies: [{ kind: "ngmodule", type: FFlowModule }, { kind: "component", type: i1.FFlowComponent, selector: "f-flow", inputs: ["fFlowId", "fCache"], outputs: ["fNodesRendered", "fFullRendered", "fLoaded"] }, { kind: "component", type: i1.FCanvasComponent, selector: "f-canvas", inputs: ["position", "scale", "debounceTime", "fLayers"], outputs: ["fCanvasChange"] }, { kind: "component", type: i1.FBackgroundComponent, selector: "f-background" }, { kind: "component", type: i1.FCirclePatternComponent, selector: "f-circle-pattern", inputs: ["id", "color", "radius"] }, { kind: "directive", type: i1.FZoomDirective, selector: "f-canvas[fZoom]", inputs: ["fZoom", "fWheelTrigger", "fDblClickTrigger", "fZoomMinimum", "fZoomMaximum", "fZoomStep", "fPinchStep", "fZoomDblClickStep"] }, { kind: "component", type: i1.FSelectionArea, selector: "f-selection-area", inputs: ["fTrigger"] }, { kind: "directive", type: i1.FConnectionContent, selector: "[fConnectionContent]", inputs: ["position", "offset", "align"] }, { kind: "component", type: i1.FConnectionMarkerArrow, selector: "f-connection-marker-arrow", inputs: ["type"] }, { kind: "component", type: i1.FConnectionComponent, selector: "f-connection", inputs: ["fConnectionId", "fSourceId", "fTargetId", "fOutputId", "fInputId", "fRadius", "fOffset", "fBehavior", "fType", "fSelectionDisabled", "fReassignableStart", "fReassignDisabled", "fSourceSide", "fTargetSide", "fInputSide", "fOutputSide"], exportAs: ["fComponent"] }, { kind: "component", type: i1.FConnectionForCreateComponent, selector: "f-connection-for-create", inputs: ["fRadius", "fOffset", "fBehavior", "fType", "fInputSide", "fOutputSide"] }, { kind: "directive", type: i1.FConnectorDirective, selector: "[fConnector]", inputs: ["fConnectorId", "fConnectorType", "fConnectorDisabled", "fConnectorMultiple", "fConnectorCategory", "fConnectorConnectableSide", "fConnectorSelfConnectable", "fCanBeConnectedTo", "fConnectionFromOutlet"], exportAs: ["fConnector"] }, { kind: "component", type: i1.FMinimapComponent, selector: "f-minimap", inputs: ["fMinSize", "fNodeRenderLimit"], exportAs: ["fComponent"] }, { kind: "directive", type: i1.FNodeDirective, selector: "[fNode]", inputs: ["fNodeId", "fNodeParentId", "fNodePosition", "fNodeSize", "fNodeRotate", "fConnectOnNode", "fMinimapClass", "fNodeDraggingDisabled", "fNodeSelectionDisabled", "fIncludePadding", "fAutoExpandOnChildHit", "fAutoSizeToFitChildren"], outputs: ["fNodePositionChange", "fNodeSizeChange", "fNodeRotateChange"], exportAs: ["fComponent"] }, { kind: "directive", type: i1.FDragHandleDirective, selector: "[fDragHandle]" }, { kind: "directive", type: i1.FDragBlockerDirective, selector: "[fDragBlocker]" }, { kind: "directive", type: i1.FDraggableDirective, selector: "f-flow[fDraggable]", inputs: ["fDraggableDisabled", "fDropToGroup", "fMultiSelectTrigger", "fReassignConnectionTrigger", "fCreateConnectionTrigger", "fConnectionWaypointsTrigger", "fMoveControlPointTrigger", "fNodeResizeTrigger", "fNodeRotateTrigger", "fNodeMoveTrigger", "fCanvasMoveTrigger", "fExternalItemTrigger", "fEmitOnNodeIntersect", "vCellSize", "hCellSize", "fCellSizeWhileDragging"], outputs: ["fSelectionChange", "fDeleteSelected", "fNodeIntersectedWithConnections", "fNodeConnectionsIntersection", "fCreateNode", "fMoveNodes", "fReassignConnection", "fCreateConnection", "fConnectionWaypointsChanged", "fDropToGroup", "fDragStarted", "fDragEnded"], exportAs: ["fDraggable"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3096
3490
  }
3097
3491
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImport: i0, type: FlowCanvasComponent, decorators: [{
3098
3492
  type: Component,
3099
- args: [{ selector: 'fb-flow-canvas', standalone: true, imports: [FFlowModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<!--\r\n Gerarchia obbligatoria: f-flow > f-canvas > fNode / f-connection.\r\n I `@for` sono direttamente dentro <f-canvas>: nessun wrapper, quindi non serve\r\n `ngProjectAs` (che sarebbe indispensabile con blocchi annidati).\r\n-->\r\n<f-flow\r\n fDraggable\r\n (fCreateConnection)=\"onCreateConnection($event)\"\r\n (fReassignConnection)=\"onReassignConnection($event)\"\r\n (fMoveNodes)=\"onMoveNodes($event)\"\r\n (fSelectionChange)=\"onSelectionChange($event)\"\r\n (fDeleteSelected)=\"onDeleteSelected($event)\"\r\n (fCreateNode)=\"onCreateNode($event)\"\r\n>\r\n <f-canvas fZoom>\r\n <f-background>\r\n <f-circle-pattern />\r\n </f-background>\r\n\r\n @for (edge of edges(); track edge.id) {\r\n <f-connection\r\n [fConnectionId]=\"edge.id\"\r\n [fSourceId]=\"edge.sourceId\"\r\n [fTargetId]=\"edge.targetId\"\r\n fBehavior=\"floating\"\r\n [fType]=\"edge.isGoTo ? 'segment' : 'bezier'\"\r\n [class]=\"'fb-edge fb-edge--' + edge.kind + (edge.isGoTo ? ' fb-edge--goto' : '')\"\r\n >\r\n <f-connection-marker-arrow [type]=\"markerEnd\" />\r\n @if (edge.label) {\r\n <div fConnectionContent class=\"fb-edge-label\">{{ edge.label }}</div>\r\n }\r\n </f-connection>\r\n }\r\n\r\n @for (node of nodes(); track node.id) {\r\n <div\r\n fNode\r\n fDragHandle\r\n [fNodeId]=\"node.id\"\r\n [fNodePosition]=\"node.position\"\r\n [class]=\"'fb-node ' + categoryClass(node) + ' ' + node.widthClass\"\r\n [class.fb-node--start]=\"node.isStart\"\r\n [class.fb-node--selected]=\"isSelected(node)\"\r\n [class.fb-node--unreachable]=\"!node.isReachable\"\r\n [class.fb-node--error]=\"node.severity === 'Error'\"\r\n [class.fb-node--warning]=\"node.severity === 'Warning'\"\r\n (dblclick)=\"onNodeDoubleClick(node.name)\"\r\n >\r\n <!--\r\n Lo Start non ha ingresso: e' il punto di partenza (\u00A73.4).\r\n `fConnectorConnectableSide` e' cio' che fa entrare l'arco dall'alto: senza, foblex\r\n calcola il lato e un arco che scende dal node sopra potrebbe agganciarsi di fianco.\r\n -->\r\n @if (!node.isStart) {\r\n <div\r\n fConnector\r\n fConnectorType=\"target\"\r\n [fConnectorId]=\"targetIdOf(node)\"\r\n [fConnectorConnectableSide]=\"sides.TOP\"\r\n fConnectorMultiple=\"true\"\r\n class=\"fb-connector fb-connector--in\"\r\n title=\"Ingresso\"\r\n ></div>\r\n }\r\n\r\n <div class=\"fb-node__head\">\r\n <span class=\"fb-node__icon\" aria-hidden=\"true\">{{ node.icon }}</span>\r\n <div class=\"fb-node__text\">\r\n <span class=\"fb-node__title\" [title]=\"node.description || node.label\">{{ node.label }}</span>\r\n <span class=\"fb-node__sub\">\r\n {{ node.typeLabel }}\r\n @if (node.subtitle) {\r\n <span class=\"fb-node__sub-dot\">\u00B7</span>{{ node.subtitle }}\r\n }\r\n </span>\r\n </div>\r\n @if (node.hasAutomaticOutput) {\r\n <!-- L'elemento espone il proprio risultato sotto il proprio nome (\u00A74.5). -->\r\n <span class=\"fb-node__auto\" title=\"Espone un output automatico referenziabile come \u00AB{{ node.name }}\u00BB\">\r\n \u0192\r\n </span>\r\n }\r\n <!--\r\n `fDragBlocker` impedisce che il pointerdown sul bottone diventi un trascinamento\r\n del node: senza, aprire il dettaglio sposterebbe l'elemento di qualche pixel.\r\n -->\r\n <button\r\n type=\"button\"\r\n fDragBlocker\r\n class=\"fb-node__edit\"\r\n [attr.aria-label]=\"'Apri il dettaglio di ' + node.label\"\r\n title=\"Apri il dettaglio\"\r\n (click)=\"onEditClick($event, node.name)\"\r\n >\r\n \u270E\r\n </button>\r\n </div>\r\n\r\n <div class=\"fb-node__meta\">\r\n @if (!node.isStart) {\r\n <span class=\"fb-node__name\" [title]=\"'Nome tecnico: ' + node.name\">{{ node.name }}</span>\r\n }\r\n @if (!node.isReachable) {\r\n <span class=\"fb-badge fb-badge--unreachable\" title=\"Nessun percorso raggiunge questo elemento dallo Start\">\r\n scollegato\r\n </span>\r\n }\r\n @if (node.issueCount > 0) {\r\n <span\r\n class=\"fb-badge\"\r\n [class.fb-badge--error]=\"node.severity === 'Error'\"\r\n [class.fb-badge--warning]=\"node.severity === 'Warning'\"\r\n [class.fb-badge--info]=\"node.severity === 'Info'\"\r\n [title]=\"node.issueCount + ' rilievi di validazione'\"\r\n >\r\n {{ node.issueCount }}\r\n </span>\r\n }\r\n @if (node.danglingOutlets.length > 0) {\r\n <span\r\n class=\"fb-badge fb-badge--dangling\"\r\n [title]=\"'Rami dichiarati senza destinazione: ' + danglingLabels(node)\"\r\n >\r\n ramo incompleto\r\n </span>\r\n }\r\n </div>\r\n\r\n <!--\r\n Le uscite stanno sul bordo **inferiore**, una per ramo, nell'ordine in cui il modello\r\n le dichiara: per una Decision e' l'ordine di valutazione delle regole, che e'\r\n semantico (\u00A75.3), e da sinistra a destra si legge come la lista nell'inspector.\r\n L'etichetta si mostra solo quando i rami sono piu' di uno: su un `next` unico\r\n direbbe soltanto \u00ABSuccessivo\u00BB.\r\n -->\r\n <div class=\"fb-node__outlets\" [class.fb-node__outlets--labelled]=\"node.showsOutletLabels\">\r\n @for (outlet of node.outlets; track outlet.key) {\r\n <div class=\"fb-outlet\">\r\n @if (node.showsOutletLabels) {\r\n <span class=\"fb-outlet__label\" [title]=\"outlet.label\">{{ outlet.label }}</span>\r\n }\r\n <div\r\n fConnector\r\n fConnectorType=\"source\"\r\n [fConnectorId]=\"connectorIdOf(node, outlet.key)\"\r\n [fConnectorConnectableSide]=\"sides.BOTTOM\"\r\n [class]=\"'fb-connector fb-connector--out fb-connector--' + outlet.kind\"\r\n [title]=\"outlet.label\"\r\n ></div>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- Anteprima dell'arco durante il trascinamento. -->\r\n <f-connection-for-create fBehavior=\"floating\" fType=\"bezier\" class=\"fb-edge fb-edge--creating\">\r\n <f-connection-marker-arrow [type]=\"markerEnd\" />\r\n </f-connection-for-create>\r\n\r\n <f-selection-area />\r\n </f-canvas>\r\n\r\n <f-minimap [fMinSize]=\"1200\" class=\"fb-minimap\" />\r\n</f-flow>\r\n", styles: [":host{display:block;position:relative;width:100%;height:100%;overflow:hidden;background:var(--fb-canvas-bg, #f4f5f7)}f-flow{display:block;width:100%;height:100%}.fb-minimap{position:absolute;right:14px;bottom:14px;width:190px;height:130px;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius, 10px);background:var(--fb-surface, #fff);box-shadow:var(--fb-shadow-md, 0 6px 18px rgb(16 24 40 / 10%));overflow:hidden}.fb-node{position:absolute;display:flex;flex-direction:column;box-sizing:border-box;width:240px;padding:0;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-lg, 12px);background:var(--fb-surface, #fff);box-shadow:var(--fb-shadow-sm, 0 1px 2px rgb(16 24 40 / 6%));font:inherit;cursor:grab;-webkit-user-select:none;user-select:none;transition:box-shadow .12s ease,border-color .12s ease}.fb-node--outlets-3{width:304px}.fb-node--outlets-4{width:380px}.fb-node--outlets-5{width:456px}.fb-node--outlets-6{width:520px}.fb-node:hover{box-shadow:var(--fb-shadow-md, 0 6px 18px rgb(16 24 40 / 10%))}.fb-node--selected{border-color:var(--fb-accent, #4f6ef7);box-shadow:0 0 0 3px color-mix(in srgb,var(--fb-accent, #4f6ef7) 22%,transparent)}.fb-node--unreachable{border-style:dashed;opacity:.8}.fb-node--error{border-color:var(--fb-error, #c9372c)}.fb-node--warning{border-color:var(--fb-warning, #b7791f)}.fb-node__head{display:flex;align-items:center;gap:8px;padding:10px 10px 6px 12px}.fb-node__icon{display:grid;place-items:center;flex:0 0 auto;width:28px;height:28px;border-radius:var(--fb-radius-sm, 8px);background:var(--fb-node-accent, #667085);color:#fff;font-size:14px;line-height:1}.cat-start{--fb-node-accent: #22a06b}.cat-screen{--fb-node-accent: #3b82f6}.cat-logic{--fb-node-accent: #8b5cf6}.cat-data{--fb-node-accent: #06b6d4}.cat-action{--fb-node-accent: #f59e0b}.cat-flow{--fb-node-accent: #14b8a6}.cat-other{--fb-node-accent: #667085}.fb-node__text{flex:1;min-width:0}.fb-node__title{display:block;font-size:13px;font-weight:600;line-height:17px;color:var(--fb-text, #1a1c23);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-node__sub{display:block;margin-top:1px;font-size:11px;line-height:14px;color:var(--fb-text-muted, #6b7086);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-node__sub-dot{margin:0 4px;color:var(--fb-text-subtle, #98a2b3)}.fb-node__auto{flex:0 0 auto;font-size:12px;font-weight:700;color:var(--fb-accent, #4f6ef7);cursor:help}.fb-node__edit{display:grid;place-items:center;flex:0 0 auto;width:22px;height:22px;padding:0;border:0;border-radius:var(--fb-radius-xs, 6px);background:transparent;color:var(--fb-text-subtle, #98a2b3);font:inherit;font-size:12px;cursor:pointer;opacity:0;transition:opacity .12s ease,background .12s ease}.fb-node:hover .fb-node__edit,.fb-node--selected .fb-node__edit,.fb-node__edit:focus-visible{opacity:1}.fb-node__edit:hover{background:var(--fb-surface-alt, #f7f8fa);color:var(--fb-text, #1a1c23)}.fb-node__meta{display:flex;flex-wrap:wrap;align-items:center;gap:4px;min-height:14px;padding:0 12px 6px}.fb-node__name{flex:0 1 auto;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:10px;color:var(--fb-text-subtle, #98a2b3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-badge{padding:1px 6px;border-radius:10px;background:var(--fb-badge-bg, #eef0f4);font-size:10px;font-weight:600;color:var(--fb-text-muted, #6b7086);white-space:nowrap;cursor:help}.fb-badge--error{background:color-mix(in srgb,var(--fb-error, #c9372c) 14%,transparent);color:var(--fb-error, #c9372c)}.fb-badge--warning{background:color-mix(in srgb,var(--fb-warning, #b7791f) 16%,transparent);color:var(--fb-warning, #b7791f)}.fb-badge--info{background:color-mix(in srgb,var(--fb-accent, #4f6ef7) 12%,transparent);color:var(--fb-accent, #4f6ef7)}.fb-badge--unreachable,.fb-badge--dangling{background:color-mix(in srgb,var(--fb-warning, #b7791f) 12%,transparent);color:var(--fb-warning, #b7791f)}.fb-node__outlets{display:flex;align-items:flex-end;justify-content:space-evenly;gap:4px;padding:0 8px 4px}.fb-node__outlets--labelled{padding-top:5px;border-top:1px solid var(--fb-border-subtle, #eef0f4)}.fb-outlet{display:flex;flex:1 1 0;flex-direction:column;align-items:center;gap:2px;min-width:0}.fb-outlet__label{max-width:100%;font-size:10px;line-height:13px;color:var(--fb-text-muted, #6b7086);text-align:center;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-connector{box-sizing:border-box;width:12px;height:12px;flex:0 0 auto;border:2px solid var(--fb-surface, #fff);border-radius:50%;background:var(--fb-connector, #98a2b3);cursor:crosshair;transition:transform .12s ease,box-shadow .12s ease}f-flow .fb-connector--out{position:relative;inset:auto;margin-bottom:-12px}.fb-connector--out:hover{transform:scale(1.2)}f-flow .fb-connector--in{position:absolute;inset:-2px auto auto 50%;width:24px;height:4px;border:0;border-radius:2px;transform:translate(-50%);background:var(--fb-border-strong, #cfd4de)}.fb-connector--out{--ff-connector-connected-color: var(--fb-connector, #98a2b3)}.fb-connector--Next,.fb-connector--Start,.fb-connector--LoopNext{--fb-connector: var(--fb-edge-next, #98a2b3)}.fb-connector--Rule,.fb-connector--WaitEvent{--fb-connector: var(--fb-edge-rule, #8b5cf6)}.fb-connector--Default,.fb-connector--LoopEnd{--fb-connector: var(--fb-edge-default, #06b6d4)}.fb-connector--Fault{--fb-connector: var(--fb-edge-fault, #dc2626)}.fb-connector--Timeout,.fb-connector--ScheduledPath{--fb-connector: var(--fb-edge-timeout, #f59e0b)}.fb-connector.f-connector-connectable{box-shadow:0 0 0 4px color-mix(in srgb,var(--fb-accent, #4f6ef7) 28%,transparent)}\n"] }]
3100
- }], propDecorators: { selectedName: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedName", required: false }] }], outline: [{ type: i0.Input, args: [{ isSignal: true, alias: "outline", required: false }] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], nodeOpened: [{ type: i0.Output, args: ["nodeOpened"] }], nodeRemoveRequested: [{ type: i0.Output, args: ["nodeRemoveRequested"] }], nodeDuplicateRequested: [{ type: i0.Output, args: ["nodeDuplicateRequested"] }], elementDropped: [{ type: i0.Output, args: ["elementDropped"] }] } });
3493
+ args: [{ selector: 'fb-flow-canvas', standalone: true, imports: [FFlowModule], changeDetection: ChangeDetectionStrategy.OnPush, providers: [provideFFlow(withA11y())], template: "<!--\r\n Gerarchia obbligatoria: f-flow > f-canvas > fNode / f-connection.\r\n I `@for` sono direttamente dentro <f-canvas>: nessun wrapper, quindi non serve\r\n `ngProjectAs` (che sarebbe indispensabile con blocchi annidati).\r\n-->\r\n<f-flow\r\n fDraggable\r\n (fCreateConnection)=\"onCreateConnection($event)\"\r\n (fReassignConnection)=\"onReassignConnection($event)\"\r\n (fMoveNodes)=\"onMoveNodes($event)\"\r\n (fSelectionChange)=\"onSelectionChange($event)\"\r\n (fDeleteSelected)=\"onDeleteSelected($event)\"\r\n (fCreateNode)=\"onCreateNode($event)\"\r\n (fNodesRendered)=\"onNodesRendered()\"\r\n>\r\n <!--\r\n `#canvas` + `(fCanvasChange)`: il primo serve per chiamare `fitToScreen()`, il secondo per\r\n sapere che l'utente ha mosso la vista. `[debounceTime]` non si imposta: l'evento serve solo\r\n ad accendere un flag, non a ricalcolare niente.\r\n -->\r\n <f-canvas fZoom #canvas (fCanvasChange)=\"onCanvasChange()\">\r\n <f-background>\r\n <f-circle-pattern />\r\n </f-background>\r\n\r\n @for (edge of edges(); track edge.id) {\r\n <f-connection\r\n [fConnectionId]=\"edge.id\"\r\n [fSourceId]=\"edge.sourceId\"\r\n [fTargetId]=\"edge.targetId\"\r\n fBehavior=\"floating\"\r\n [fType]=\"edge.isGoTo ? 'segment' : 'bezier'\"\r\n [class]=\"'fb-edge fb-edge--' + edge.kind + (edge.isGoTo ? ' fb-edge--goto' : '')\"\r\n >\r\n <f-connection-marker-arrow [type]=\"markerEnd\" />\r\n @if (edge.label) {\r\n <div fConnectionContent class=\"fb-edge-label\">{{ edge.label }}</div>\r\n }\r\n </f-connection>\r\n }\r\n\r\n @for (node of nodes(); track node.id) {\r\n <div\r\n fNode\r\n fDragHandle\r\n [fNodeId]=\"node.id\"\r\n [fNodePosition]=\"node.position\"\r\n [class]=\"'fb-node ' + categoryClass(node) + ' ' + node.widthClass\"\r\n [class.fb-node--start]=\"node.isStart\"\r\n [class.fb-node--selected]=\"isSelected(node)\"\r\n [class.fb-node--unreachable]=\"!node.isReachable\"\r\n [class.fb-node--error]=\"node.severity === 'Error'\"\r\n [class.fb-node--warning]=\"node.severity === 'Warning'\"\r\n (dblclick)=\"onNodeDoubleClick(node.name)\"\r\n >\r\n <!--\r\n Lo Start non ha ingresso: e' il punto di partenza (\u00A73.4).\r\n `fConnectorConnectableSide` e' cio' che fa entrare l'arco dall'alto: senza, foblex\r\n calcola il lato e un arco che scende dal node sopra potrebbe agganciarsi di fianco.\r\n -->\r\n @if (!node.isStart) {\r\n <div\r\n fConnector\r\n fConnectorType=\"target\"\r\n [fConnectorId]=\"targetIdOf(node)\"\r\n [fConnectorConnectableSide]=\"sides.TOP\"\r\n fConnectorMultiple=\"true\"\r\n class=\"fb-connector fb-connector--in\"\r\n title=\"Ingresso\"\r\n ></div>\r\n }\r\n\r\n <div class=\"fb-node__head\">\r\n <span class=\"fb-node__icon\" aria-hidden=\"true\">{{ node.icon }}</span>\r\n <div class=\"fb-node__text\">\r\n <span class=\"fb-node__title\" [title]=\"node.description || node.label\">{{ node.label }}</span>\r\n <span class=\"fb-node__sub\">\r\n {{ node.typeLabel }}\r\n @if (node.subtitle) {\r\n <span class=\"fb-node__sub-dot\">\u00B7</span>{{ node.subtitle }}\r\n }\r\n </span>\r\n </div>\r\n @if (node.hasAutomaticOutput) {\r\n <!-- L'elemento espone il proprio risultato sotto il proprio nome (\u00A74.5). -->\r\n <span class=\"fb-node__auto\" title=\"Espone un output automatico referenziabile come \u00AB{{ node.name }}\u00BB\">\r\n \u0192\r\n </span>\r\n }\r\n <!--\r\n `fDragBlocker` impedisce che il pointerdown sul bottone diventi un trascinamento\r\n del node: senza, aprire il dettaglio sposterebbe l'elemento di qualche pixel.\r\n -->\r\n <button\r\n type=\"button\"\r\n fDragBlocker\r\n class=\"fb-node__edit\"\r\n [attr.aria-label]=\"'Apri il dettaglio di ' + node.label\"\r\n title=\"Apri il dettaglio\"\r\n (click)=\"onEditClick($event, node.name)\"\r\n >\r\n \u270E\r\n </button>\r\n @if (!node.isStart && isEditable()) {\r\n <!--\r\n Si mostra solo sul node **selezionato**, non al passaggio del mouse come \u270E:\r\n cancellare non e' reversibile con un altro clic, e un comando distruttivo che\r\n appare sotto il puntatore mentre si attraversa il grafo si preme per sbaglio.\r\n Lo Start non lo espone: un flow senza ingresso non esisterebbe.\r\n -->\r\n <button\r\n type=\"button\"\r\n fDragBlocker\r\n class=\"fb-node__remove\"\r\n [attr.aria-label]=\"'Elimina ' + node.label\"\r\n title=\"Elimina questo elemento (si annulla con \u21B6)\"\r\n (click)=\"onRemoveClick($event, node.name)\"\r\n >\r\n \u00D7\r\n </button>\r\n }\r\n </div>\r\n\r\n <div class=\"fb-node__meta\">\r\n @if (!node.isStart) {\r\n <span class=\"fb-node__name\" [title]=\"'Nome tecnico: ' + node.name\">{{ node.name }}</span>\r\n }\r\n @if (!node.isReachable) {\r\n <span class=\"fb-badge fb-badge--unreachable\" title=\"Nessun percorso raggiunge questo elemento dallo Start\">\r\n scollegato\r\n </span>\r\n }\r\n @if (node.issueCount > 0) {\r\n <span\r\n class=\"fb-badge\"\r\n [class.fb-badge--error]=\"node.severity === 'Error'\"\r\n [class.fb-badge--warning]=\"node.severity === 'Warning'\"\r\n [class.fb-badge--info]=\"node.severity === 'Info'\"\r\n [title]=\"node.issueCount + ' rilievi di validazione'\"\r\n >\r\n {{ node.issueCount }}\r\n </span>\r\n }\r\n @if (node.danglingOutlets.length > 0) {\r\n <span\r\n class=\"fb-badge fb-badge--dangling\"\r\n [title]=\"'Rami dichiarati senza destinazione: ' + danglingLabels(node)\"\r\n >\r\n ramo incompleto\r\n </span>\r\n }\r\n </div>\r\n\r\n <!--\r\n Le uscite stanno sul bordo **inferiore**, una per ramo, nell'ordine in cui il modello\r\n le dichiara: per una Decision e' l'ordine di valutazione delle regole, che e'\r\n semantico (\u00A75.3), e da sinistra a destra si legge come la lista nell'inspector.\r\n L'etichetta si mostra solo quando i rami sono piu' di uno: su un `next` unico\r\n direbbe soltanto \u00ABSuccessivo\u00BB.\r\n -->\r\n <div class=\"fb-node__outlets\" [class.fb-node__outlets--labelled]=\"node.showsOutletLabels\">\r\n @for (outlet of node.outlets; track outlet.key) {\r\n <div class=\"fb-outlet\">\r\n @if (node.showsOutletLabels) {\r\n <span class=\"fb-outlet__label\" [title]=\"outlet.label\">{{ outlet.label }}</span>\r\n }\r\n <div\r\n fConnector\r\n fConnectorType=\"source\"\r\n [fConnectorId]=\"connectorIdOf(node, outlet.key)\"\r\n [fConnectorConnectableSide]=\"sides.BOTTOM\"\r\n [class]=\"'fb-connector fb-connector--out fb-connector--' + outlet.kind\"\r\n [title]=\"outlet.label\"\r\n ></div>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- Anteprima dell'arco durante il trascinamento. -->\r\n <f-connection-for-create fBehavior=\"floating\" fType=\"bezier\" class=\"fb-edge fb-edge--creating\">\r\n <f-connection-marker-arrow [type]=\"markerEnd\" />\r\n </f-connection-for-create>\r\n\r\n <f-selection-area />\r\n </f-canvas>\r\n\r\n <!--\r\n Fuori da <f-canvas> come la minimappa: dentro, la trasformazione del canvas se lo\r\n porterebbe via insieme ai node. `fDragBlocker` perche' il pointerdown non inizi una\r\n panoramica invece di premere il bottone.\r\n -->\r\n <!--\r\n Sempre nel DOM, nascosto con una classe e non con `@if`: dentro una proiezione di contenuto\r\n un blocco di controllo e' una complicazione gratuita, e `visibility: hidden` lo toglie\r\n anche dall'ordine di tabulazione. Il costo e' un bottone in piu' nel DOM, non un rischio.\r\n -->\r\n <button\r\n type=\"button\"\r\n fDragBlocker\r\n class=\"fb-btn fb-viewport-reset\"\r\n [class.fb-viewport-reset--hidden]=\"!hasMovedViewport()\"\r\n title=\"Rimetti il flow interamente in vista, al centro\"\r\n (click)=\"resetViewport()\"\r\n >\r\n <span class=\"fb-viewport-reset__icon\" aria-hidden=\"true\">\u2922</span>\r\n Inquadra il flow\r\n </button>\r\n\r\n <f-minimap [fMinSize]=\"1200\" class=\"fb-minimap\" />\r\n</f-flow>\r\n", styles: [":host{display:block;position:relative;width:100%;height:100%;overflow:hidden;background:var(--fb-canvas-bg, #f4f5f7)}f-flow{display:block;width:100%;height:100%}.fb-minimap{position:absolute;right:14px;bottom:14px;width:190px;height:130px;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius, 10px);background:var(--fb-surface, #fff);box-shadow:var(--fb-shadow-md, 0 6px 18px rgb(16 24 40 / 10%));overflow:hidden}.fb-viewport-reset{position:absolute;right:14px;bottom:152px;z-index:1;border-radius:var(--fb-radius-xs, 6px);box-shadow:var(--fb-shadow-md, 0 6px 18px rgb(16 24 40 / 10%));font-size:11px;transition:opacity .15s ease,visibility .15s ease}.fb-viewport-reset--hidden{opacity:0;visibility:hidden}.fb-viewport-reset__icon{font-size:13px;line-height:1;color:var(--fb-text-muted, #667085)}.fb-node{position:absolute;display:flex;flex-direction:column;box-sizing:border-box;width:240px;padding:0;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-lg, 12px);background:var(--fb-surface, #fff);box-shadow:var(--fb-shadow-sm, 0 1px 2px rgb(16 24 40 / 6%));font:inherit;cursor:grab;-webkit-user-select:none;user-select:none;transition:box-shadow .12s ease,border-color .12s ease}.fb-node--outlets-3{width:304px}.fb-node--outlets-4{width:380px}.fb-node--outlets-5{width:456px}.fb-node--outlets-6{width:520px}.fb-node:hover{box-shadow:var(--fb-shadow-md, 0 6px 18px rgb(16 24 40 / 10%))}.fb-node--selected{border-color:var(--fb-accent, #4f6ef7);box-shadow:0 0 0 3px color-mix(in srgb,var(--fb-accent, #4f6ef7) 22%,transparent)}.fb-node--unreachable{border-style:dashed;opacity:.8}.fb-node--error{border-color:var(--fb-error, #c9372c)}.fb-node--warning{border-color:var(--fb-warning, #b7791f)}.fb-node__head{display:flex;align-items:center;gap:8px;padding:10px 10px 6px 12px}.fb-node__icon{display:grid;place-items:center;flex:0 0 auto;width:28px;height:28px;border-radius:var(--fb-radius-sm, 8px);background:var(--fb-node-accent, #667085);color:#fff;font-size:14px;line-height:1}.cat-start{--fb-node-accent: #22a06b}.cat-screen{--fb-node-accent: #3b82f6}.cat-logic{--fb-node-accent: #8b5cf6}.cat-data{--fb-node-accent: #06b6d4}.cat-action{--fb-node-accent: #f59e0b}.cat-flow{--fb-node-accent: #14b8a6}.cat-other{--fb-node-accent: #667085}.fb-node__text{flex:1;min-width:0}.fb-node__title{display:block;font-size:13px;font-weight:600;line-height:17px;color:var(--fb-text, #1a1c23);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-node__sub{display:block;margin-top:1px;font-size:11px;line-height:14px;color:var(--fb-text-muted, #6b7086);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-node__sub-dot{margin:0 4px;color:var(--fb-text-subtle, #98a2b3)}.fb-node__auto{flex:0 0 auto;font-size:12px;font-weight:700;color:var(--fb-accent, #4f6ef7);cursor:help}.fb-node__edit{display:grid;place-items:center;flex:0 0 auto;width:22px;height:22px;padding:0;border:0;border-radius:var(--fb-radius-xs, 6px);background:transparent;color:var(--fb-text-subtle, #98a2b3);font:inherit;font-size:12px;cursor:pointer;opacity:0;transition:opacity .12s ease,background .12s ease}.fb-node:hover .fb-node__edit,.fb-node--selected .fb-node__edit,.fb-node__edit:focus-visible{opacity:1}.fb-node__edit:hover{background:var(--fb-surface-alt, #f7f8fa);color:var(--fb-text, #1a1c23)}.fb-node__remove{display:grid;place-items:center;flex:0 0 auto;width:22px;height:22px;padding:0;border:0;border-radius:var(--fb-radius-xs, 6px);background:transparent;color:var(--fb-text-subtle, #98a2b3);font:inherit;font-size:15px;line-height:1;cursor:pointer;opacity:0;transition:opacity .12s ease,background .12s ease,color .12s ease}.fb-node--selected .fb-node__remove,.fb-node__remove:focus-visible{opacity:1}.fb-node__remove:hover{background:color-mix(in srgb,var(--fb-error, #c9372c) 12%,transparent);color:var(--fb-error, #c9372c)}.fb-node__meta{display:flex;flex-wrap:wrap;align-items:center;gap:4px;min-height:14px;padding:0 12px 6px}.fb-node__name{flex:0 1 auto;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:10px;color:var(--fb-text-subtle, #98a2b3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-badge{padding:1px 6px;border-radius:10px;background:var(--fb-badge-bg, #eef0f4);font-size:10px;font-weight:600;color:var(--fb-text-muted, #6b7086);white-space:nowrap;cursor:help}.fb-badge--error{background:color-mix(in srgb,var(--fb-error, #c9372c) 14%,transparent);color:var(--fb-error, #c9372c)}.fb-badge--warning{background:color-mix(in srgb,var(--fb-warning, #b7791f) 16%,transparent);color:var(--fb-warning, #b7791f)}.fb-badge--info{background:color-mix(in srgb,var(--fb-accent, #4f6ef7) 12%,transparent);color:var(--fb-accent, #4f6ef7)}.fb-badge--unreachable,.fb-badge--dangling{background:color-mix(in srgb,var(--fb-warning, #b7791f) 12%,transparent);color:var(--fb-warning, #b7791f)}.fb-node__outlets{display:flex;align-items:flex-end;justify-content:space-evenly;gap:4px;padding:0 8px 4px}.fb-node__outlets--labelled{padding-top:5px;border-top:1px solid var(--fb-border-subtle, #eef0f4)}.fb-outlet{display:flex;flex:1 1 0;flex-direction:column;align-items:center;gap:2px;min-width:0}.fb-outlet__label{max-width:100%;font-size:10px;line-height:13px;color:var(--fb-text-muted, #6b7086);text-align:center;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-connector{box-sizing:border-box;width:12px;height:12px;flex:0 0 auto;border:2px solid var(--fb-surface, #fff);border-radius:50%;background:var(--fb-connector, #98a2b3);cursor:crosshair;transition:transform .12s ease,box-shadow .12s ease}f-flow .fb-connector--out{position:relative;inset:auto;margin-bottom:-12px}.fb-connector--out:hover{transform:scale(1.2)}f-flow .fb-connector--in{position:absolute;inset:-2px auto auto 50%;width:24px;height:4px;border:0;border-radius:2px;transform:translate(-50%);background:var(--fb-border-strong, #cfd4de)}.fb-connector--out{--ff-connector-connected-color: var(--fb-connector, #98a2b3)}.fb-connector--Next,.fb-connector--Start,.fb-connector--LoopNext{--fb-connector: var(--fb-edge-next, #98a2b3)}.fb-connector--Rule,.fb-connector--WaitEvent{--fb-connector: var(--fb-edge-rule, #8b5cf6)}.fb-connector--Default,.fb-connector--LoopEnd{--fb-connector: var(--fb-edge-default, #06b6d4)}.fb-connector--Fault{--fb-connector: var(--fb-edge-fault, #dc2626)}.fb-connector--Timeout,.fb-connector--ScheduledPath{--fb-connector: var(--fb-edge-timeout, #f59e0b)}.fb-connector.f-connector-connectable{box-shadow:0 0 0 4px color-mix(in srgb,var(--fb-accent, #4f6ef7) 28%,transparent)}\n"] }]
3494
+ }], propDecorators: { selectedName: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedName", required: false }] }], outline: [{ type: i0.Input, args: [{ isSignal: true, alias: "outline", required: false }] }], isEditable: [{ type: i0.Input, args: [{ isSignal: true, alias: "isEditable", required: false }] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], nodeOpened: [{ type: i0.Output, args: ["nodeOpened"] }], nodeRemoveRequested: [{ type: i0.Output, args: ["nodeRemoveRequested"] }], nodeDuplicateRequested: [{ type: i0.Output, args: ["nodeDuplicateRequested"] }], elementDropped: [{ type: i0.Output, args: ["elementDropped"] }], canvas: [{ type: i0.ViewChild, args: [i0.forwardRef(() => FCanvasComponent), { isSignal: true }] }] } });
3101
3495
 
3102
3496
  /**
3103
3497
  * La palette degli elementi — FRONTEND.md §3.2, §11 ("Aggiungere un elemento").
@@ -3235,7 +3629,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImpo
3235
3629
  }], propDecorators: { processType: [{ type: i0.Input, args: [{ isSignal: true, alias: "processType", required: false }] }], elementPicked: [{ type: i0.Output, args: ["elementPicked"] }] } });
3236
3630
 
3237
3631
  /**
3238
- * Selettore di riferimento — FRONTEND.md §4.1, §4.5, §6.4, §13.3.
3632
+ * Selettore di riferimento — FRONTEND.md §4.1, §4.5, §4.7, §5.2, §6.4, §13.3.
3239
3633
  *
3240
3634
  * Un riferimento e' testo libero, e un nome sbagliato e' indistinguibile da uno giusto se
3241
3635
  * non si conosce l'insieme dei nomi definiti. Questo componente chiede l'insieme a
@@ -3246,18 +3640,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImpo
3246
3640
  * pur non essendo dichiarati in nessuna collection di risorse: la primitiva li restituisce
3247
3641
  * già, ed e' il motivo per cui non si ricostruisce l'elenco lato client.
3248
3642
  *
3249
- * Resta possibile scrivere a mano: i percorsi di relazione e i campi di un record non sono
3250
- * enumerabili. Gli scope del sistema ospite (`$User.X`), invece, sono verificabili **quando
3251
- * l'host li dichiara**: se `globalVariables` porta almeno un percorso di quello scope, un
3252
- * percorso che non c'e' e' `GLOBAL_UNKNOWN`; se non ne porta nessuno, lo scope resta non
3253
- * verificabileche significa "non lo so", non "non esiste" (§4.1).
3254
- *
3255
- * I **membri di una classe** sono l'eccezione ai percorsi non enumerabili (§4.7): quando la
3256
- * radice e' una risorsa `Structure` i membri arrivano da
3257
- * `GET /catalog/structures/{className}/members`, quindi si propongono come `Richiesta.Ragione`
3258
- * e il **primo** segmento si verifica un membro che non c'e' e'
3259
- * `STRUCTURE_MEMBER_UNKNOWN`, e in un campo di destinazione un membro calcolato e'
3260
- * `TARGET_NOT_WRITABLE`. Oltre il primo segmento resta valida la regola generale: nessuno lo sa.
3643
+ * I **percorsi navigati** non sono piu' tutti uguali. Quando la radice e' una risorsa dichiarata
3644
+ * di cui si conosce la forma — una `Structure` con la sua classe, un record con la sua entita' — il
3645
+ * percorso si naviga **fino in fondo** (§4.4, §4.7.1): i segmenti si propongono un livello alla
3646
+ * volta, un nome che non c'e' e' `STRUCTURE_MEMBER_UNKNOWN` o `FIELD_UNKNOWN`, e in un campo di
3647
+ * destinazione un membro calcolato o un campo ne' `createable` ne' `updateable` e'
3648
+ * `TARGET_NOT_WRITABLE` (§5.7). Dove la catena si interrompe l'avviso e' `PATH_NOT_VERIFIABLE`.
3649
+ *
3650
+ * Restano non verificabili due cose, e per motivi diversi: i percorsi radicati sul **risultato
3651
+ * automatico** di un elemento (`Leggi_Contatti.Owner.Name`), di cui il documento non dichiara la
3652
+ * forma, e gli scope del sistema ospite che non dichiarano i propri percorsi (`$User.X`) — lì
3653
+ * "non lo so" non e' "non esiste" (§4.1).
3261
3654
  */
3262
3655
  class ReferencePickerComponent {
3263
3656
  api = inject(FlowBuilderApi);
@@ -3332,62 +3725,76 @@ class ReferencePickerComponent {
3332
3725
  })
3333
3726
  .finally(() => this.isLoading.set(false));
3334
3727
  });
3335
- // I membri della classe da cui si sta navigando: caricati solo quando servono, cioe' quando
3336
- // la radice del valore e' davvero una `Structure` (§4.7).
3337
- effect(() => {
3338
- const className = this.structureRoot()?.objectType;
3339
- if (!className) {
3340
- this.members.set([]);
3341
- return;
3342
- }
3343
- // §4.7.1 — solo un elenco `declared` autorizza a dire che un membro non esiste: membri non
3344
- // dichiarati, classe non registrata e catalogo assente danno tutti e tre nessuna proposta e
3345
- // nessuna accusa, ma per la classe non registrata l'errore e' già sulla risorsa.
3346
- void this.catalog
3347
- .describeStructure(className)
3348
- .then((result) => this.members.set(result.status === 'declared' ? result.members : []));
3349
- });
3350
3728
  }
3351
3729
  /**
3352
- * §4.7 — la risorsa `Structure` da cui il valore corrente sta navigando, se c'e'. È quella
3353
- * che rende i membri proponibili: senza classe non c'e' catalogo da interrogare.
3730
+ * La risorsa da cui il valore corrente sta navigando, quando la sua forma e' **dichiarata**: una
3731
+ * `Structure` con la classe, un record con l'entita'. È cio' che rende il percorso verificabile.
3732
+ *
3733
+ * Un output automatico e' escluso di proposito: la primitiva ne dichiara il tipo, ma la
3734
+ * specifica tiene i percorsi che ne partono fuori dalla validazione (§7, ultimo capoverso), e
3735
+ * l'editor non deve accusare cio' che il backend non verifica.
3354
3736
  */
3355
- structureRoot = computed(() => {
3356
- const typed = (this.query().trim() || this.value() || '').trim();
3357
- const root = typed ? referenceRoot(typed) : '';
3737
+ navigableRoot = computed(() => {
3738
+ const root = referenceRoot((this.value() ?? '').trim());
3358
3739
  if (!root) {
3359
3740
  return undefined;
3360
3741
  }
3361
- return this.references().find((reference) => reference.name === root && this.dictionaries.isStructure(reference.dataType) && !!reference.objectType);
3362
- }, ...(ngDevMode ? [{ debugName: "structureRoot" }] : []));
3363
- members = signal([], ...(ngDevMode ? [{ debugName: "members" }] : []));
3364
- /** I membri come riferimenti navigati: `Richiesta.Ragione`, col tipo del membro. */
3365
- memberReferences = computed(() => {
3366
- const root = this.structureRoot();
3367
- if (!root) {
3742
+ const reference = this.references().find((candidate) => candidate.name === root);
3743
+ if (!reference || reference.kind === 'ElementOutput' || !reference.objectType || reference.isCollection) {
3744
+ return undefined;
3745
+ }
3746
+ if (this.dictionaries.isStructure(reference.dataType)) {
3747
+ return { reference, container: { kind: 'structure', name: reference.objectType } };
3748
+ }
3749
+ return reference.dataType === 'Object'
3750
+ ? { reference, container: { kind: 'object', name: reference.objectType } }
3751
+ : undefined;
3752
+ }, ...(ngDevMode ? [{ debugName: "navigableRoot" }] : []));
3753
+ /** Il percorso dopo la radice: `Cliente.Email` di `Richiesta.Cliente.Email`. */
3754
+ navigatedPath = computed(() => {
3755
+ const root = this.navigableRoot()?.reference.name;
3756
+ const value = (this.value() ?? '').trim();
3757
+ return root && value.length > root.length ? value.slice(root.length + 1) : '';
3758
+ }, ...(ngDevMode ? [{ debugName: "navigatedPath" }] : []));
3759
+ navigation = navigatePath(this.catalog, () => {
3760
+ const root = this.navigableRoot();
3761
+ return root ? { root: root.container, path: this.navigatedPath() } : null;
3762
+ });
3763
+ resolution = this.navigation.resolution;
3764
+ /** La tappa in cui si sta scrivendo: da qui vengono le proposte dei segmenti. */
3765
+ tail = computed(() => this.resolution()?.tail, ...(ngDevMode ? [{ debugName: "tail" }] : []));
3766
+ /**
3767
+ * I segmenti come riferimenti navigati: `Richiesta.Cliente.Email`, col tipo del segmento. Un
3768
+ * segmento di sola lettura non si propone in un campo di destinazione, a meno che da lì il
3769
+ * percorso continui: `Richiesta.Cliente` non si assegna, ma `Richiesta.Cliente.Email` sì (§5.7).
3770
+ */
3771
+ pathReferences = computed(() => {
3772
+ const root = this.navigableRoot();
3773
+ const tail = this.tail();
3774
+ if (!root || !tail) {
3368
3775
  return [];
3369
3776
  }
3370
- return this.members()
3371
- // In un campo di destinazione un membro calcolato non si propone: assegnarlo e'
3372
- // `TARGET_NOT_WRITABLE`, e proporlo sarebbe un invito a sbagliare (§4.7).
3373
- .filter((member) => !this.writableOnly() || member.isWritable !== false)
3374
- .map((member) => ({
3375
- name: `${root.name}.${member.name}`,
3376
- label: member.label ?? null,
3377
- kind: 'StructureMember',
3378
- dataType: member.dataType ?? null,
3379
- isCollection: member.isCollection ?? false,
3380
- objectType: member.objectType ?? null,
3381
- isWritable: member.isWritable !== false,
3382
- description: member.isWritable === false ? 'sola lettura' : (member.description ?? null),
3777
+ const prefix = tail.prefix ? `${root.reference.name}.${tail.prefix}` : root.reference.name;
3778
+ const kind = tail.level.container.kind === 'object' ? 'RecordField' : 'StructureMember';
3779
+ return tail.level.entries
3780
+ .filter((entry) => !this.writableOnly() || entry.isWritable || !!entry.next)
3781
+ .map((entry) => ({
3782
+ name: `${prefix}.${entry.name}`,
3783
+ label: entry.label ?? null,
3784
+ kind,
3785
+ dataType: entry.dataType ?? null,
3786
+ isCollection: entry.isCollection ?? false,
3787
+ objectType: entry.objectType ?? null,
3788
+ isWritable: entry.isWritable,
3789
+ description: entry.isWritable ? (entry.description ?? null) : 'sola lettura',
3383
3790
  }));
3384
- }, ...(ngDevMode ? [{ debugName: "memberReferences" }] : []));
3791
+ }, ...(ngDevMode ? [{ debugName: "pathReferences" }] : []));
3385
3792
  options = computed(() => {
3386
3793
  const needle = this.query().trim().toLowerCase();
3387
3794
  // Le globali assegnabili — `$Flow.CurrentStage`, `$Flow.ActiveStages` e quelle che l'host
3388
3795
  // dichiara scrivibili — le comprende già `POST /flows/references/writable`: aggiungerle
3389
3796
  // qui le duplicherebbe (§5.2, §6.4).
3390
- const all = [...this.references(), ...this.memberReferences()];
3797
+ const all = [...this.references(), ...this.pathReferences()];
3391
3798
  if (!needle) {
3392
3799
  return all;
3393
3800
  }
@@ -3429,6 +3836,8 @@ class ReferencePickerComponent {
3429
3836
  return 'Output di elementi';
3430
3837
  case 'StructureMember':
3431
3838
  return 'Membri della classe';
3839
+ case 'RecordField':
3840
+ return 'Campi del record';
3432
3841
  case 'Global':
3433
3842
  return 'Variabili globali';
3434
3843
  default:
@@ -3438,15 +3847,15 @@ class ReferencePickerComponent {
3438
3847
  /**
3439
3848
  * Stato del valore corrente rispetto all'elenco.
3440
3849
  * - `known`: presente nell'elenco;
3441
- * - `navigated`: la radice e' nota ma c'e' un percorso dopo il punto (campo di record,
3442
- * output automatico navigato): la validazione non verifica i percorsi con il punto,
3443
- * quindi si accetta senza segnalare;
3850
+ * - `navigated`: la radice e' nota ma la sua forma non e' dichiarata (output automatico di un
3851
+ * elemento): il percorso resta fuori dalla validazione, quindi si accetta senza segnalare;
3444
3852
  * - `host`: scope del sistema ospite che **non dichiara percorsi**: non verificabile (§4.1);
3445
- * - `member`: membro di una classe, esistente: qui il percorso **e'** verificato (§4.7);
3446
- * - `memberUnknown`: la radice e' una `Structure` ma il membro non c'e': e'
3447
- * `STRUCTURE_MEMBER_UNKNOWN`, e a differenza di un campo di record si sa per certo;
3448
- * - `memberNotWritable`: il membro esiste, e' calcolato, e questo campo vuole una
3853
+ * - `member`: percorso dentro una forma dichiarata, verificato fino in fondo (§4.4, §4.7.1);
3854
+ * - `memberUnknown`: un segmento non esiste nella sua tappa: `STRUCTURE_MEMBER_UNKNOWN` su una
3855
+ * classe, `FIELD_UNKNOWN` su un'entita', e a differenza di prima si sa per certo;
3856
+ * - `memberNotWritable`: il segmento esiste, non e' scrivibile, e questo campo vuole una
3449
3857
  * destinazione: `TARGET_NOT_WRITABLE`;
3858
+ * - `pathUnverified`: la catena si interrompe (`PATH_NOT_VERIFIABLE`): avviso, non errore;
3450
3859
  * - `unknown`: nessuna corrispondenza: si avvisa, senza bloccare. Ci finisce anche il
3451
3860
  * percorso inesistente di uno scope che i percorsi li dichiara, perche' lì il backend
3452
3861
  * risponderebbe `GLOBAL_UNKNOWN`.
@@ -3464,19 +3873,20 @@ class ReferencePickerComponent {
3464
3873
  if (this.dictionaries.globalScope(root) && !this.dictionaries.isGlobalScopeVerifiable(root)) {
3465
3874
  return 'host';
3466
3875
  }
3467
- // Dentro una classe il primo segmento e' verificabile: dirlo "non verificato" sarebbe falso.
3468
- const structure = this.structureRoot();
3469
- if (structure && structure.name === root && this.members().length) {
3470
- const rest = value.slice(root.length + 1);
3471
- const segment = rest.includes('.') ? rest.slice(0, rest.indexOf('.')) : rest;
3472
- const member = this.members().find((candidate) => candidate.name === segment);
3473
- if (!member) {
3474
- return 'memberUnknown';
3876
+ // Dentro una forma dichiarata il percorso e' verificabile: dirlo "non verificato" sarebbe falso.
3877
+ if (this.navigableRoot()) {
3878
+ const resolution = this.resolution();
3879
+ switch (resolution?.status) {
3880
+ case 'unknown':
3881
+ return 'memberUnknown';
3882
+ case 'unverifiable':
3883
+ return 'pathUnverified';
3884
+ case 'resolved':
3885
+ return this.writableOnly() && !resolution.last?.isWritable ? 'memberNotWritable' : 'member';
3886
+ default:
3887
+ // Percorso ancora aperto (`Richiesta.`) o risposta non arrivata: non si accusa nessuno.
3888
+ return 'empty';
3475
3889
  }
3476
- if (this.writableOnly() && member.isWritable === false) {
3477
- return 'memberNotWritable';
3478
- }
3479
- return 'member';
3480
3890
  }
3481
3891
  if (all.some((reference) => reference.name === root)) {
3482
3892
  return 'navigated';
@@ -3484,22 +3894,43 @@ class ReferencePickerComponent {
3484
3894
  // Se l'elenco non e' arrivato, non si accusa nessuno.
3485
3895
  return all.length === 0 && this.isLoading() ? 'empty' : 'unknown';
3486
3896
  }, ...(ngDevMode ? [{ debugName: "valueState" }] : []));
3897
+ /** Le parole cambiano con la tappa: un campo di un'entita' non e' un membro di una classe. */
3898
+ tailIsObject = computed(() => {
3899
+ const resolution = this.resolution();
3900
+ return resolution?.levels[resolution.levels.length - 1]?.container.kind === 'object';
3901
+ }, ...(ngDevMode ? [{ debugName: "tailIsObject" }] : []));
3902
+ tailContainer = computed(() => {
3903
+ const resolution = this.resolution();
3904
+ return resolution?.levels[resolution.levels.length - 1]?.container.name ?? '';
3905
+ }, ...(ngDevMode ? [{ debugName: "tailContainer" }] : []));
3487
3906
  hint = computed(() => {
3907
+ const resolution = this.resolution();
3488
3908
  switch (this.valueState()) {
3489
3909
  case 'host':
3490
3910
  return 'Scope risolto dal sistema ospite, che non dichiara i suoi percorsi: il backend non puo’ verificarlo.';
3491
3911
  case 'navigated':
3492
- return 'Percorso su un riferimento noto: la validazione non verifica i campi dopo il punto.';
3493
- case 'member':
3494
- return `Membro di ${this.structureRoot()?.objectType}. Oltre il primo segmento il percorso non e’ verificato.`;
3912
+ return 'Percorso su un output automatico: la validazione non verifica i segmenti dopo il punto.';
3913
+ case 'member': {
3914
+ const what = this.tailIsObject() ? 'Campo' : 'Membro';
3915
+ return `${what} di ${this.tailContainer()}: il percorso e’ verificato fino in fondo.`;
3916
+ }
3495
3917
  case 'memberUnknown': {
3496
- const names = this.members()
3497
- .map((member) => member.name)
3498
- .join(', ');
3499
- return `Questo membro non esiste su ${this.structureRoot()?.objectType} (STRUCTURE_MEMBER_UNKNOWN). Disponibili: ${names}.`;
3918
+ if (!resolution) {
3919
+ return null;
3920
+ }
3921
+ const segment = resolution.stoppedAt?.segment ?? '';
3922
+ const names = pathAvailableNames(resolution).join(', ');
3923
+ const head = this.tailIsObject()
3924
+ ? `«${segment}» non e’ un campo di ${this.tailContainer()} (FIELD_UNKNOWN).`
3925
+ : `«${segment}» non e’ un membro di ${this.tailContainer()} (STRUCTURE_MEMBER_UNKNOWN).`;
3926
+ return names ? `${head} Disponibili: ${names}.` : head;
3500
3927
  }
3501
3928
  case 'memberNotWritable':
3502
- return 'Questo membro e’ calcolato: si legge, ma assegnarlo e’ TARGET_NOT_WRITABLE.';
3929
+ return this.tailIsObject()
3930
+ ? 'Questo campo non si scrive ne’ creando ne’ aggiornando un record: assegnarlo e’ TARGET_NOT_WRITABLE.'
3931
+ : 'Questo membro e’ calcolato: si legge, ma assegnarlo e’ TARGET_NOT_WRITABLE.';
3932
+ case 'pathUnverified':
3933
+ return resolution ? pathNotVerifiableMessage(resolution) : null;
3503
3934
  case 'unknown':
3504
3935
  return 'Questo nome non e’ fra i riferimenti disponibili: la validazione lo segnalerebbe.';
3505
3936
  default:
@@ -3529,7 +3960,23 @@ class ReferencePickerComponent {
3529
3960
  this.valueChange.emit(reference.name);
3530
3961
  this.close();
3531
3962
  }
3532
- /** Scrittura libera: indispensabile per i campi di un record e per gli scope dell'host. */
3963
+ /**
3964
+ * Scende di un livello dentro un percorso proponibile invece di scegliere il segmento: scrive il
3965
+ * punto, e l'elenco diventa quello della tappa successiva. Serve perche' `Richiesta.Cliente` puo'
3966
+ * essere il riferimento che si vuole **oppure** la tappa da cui continuare.
3967
+ */
3968
+ descend(reference) {
3969
+ this.valueChange.emit(`${reference.name}.`);
3970
+ this.query.set('');
3971
+ }
3972
+ /** `true` se da questo riferimento il percorso continua: la sua forma e' dichiarata. */
3973
+ canDescend(reference) {
3974
+ if (reference.isCollection || !reference.objectType || reference.kind === 'ElementOutput') {
3975
+ return false;
3976
+ }
3977
+ return this.dictionaries.isStructure(reference.dataType) || reference.dataType === 'Object';
3978
+ }
3979
+ /** Scrittura libera: indispensabile per gli scope dell'host e per i percorsi non enumerabili. */
3533
3980
  onManualInput(value) {
3534
3981
  this.valueChange.emit(value.trim() ? value.trim() : undefined);
3535
3982
  }
@@ -3550,11 +3997,11 @@ class ReferencePickerComponent {
3550
3997
  return parts.join(' · ');
3551
3998
  }
3552
3999
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.27", ngImport: i0, type: ReferencePickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3553
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.27", type: ReferencePickerComponent, isStandalone: true, selector: "fb-reference-picker", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, dataType: { classPropertyName: "dataType", publicName: "dataType", isSignal: true, isRequired: false, transformFunction: null }, isCollection: { classPropertyName: "isCollection", publicName: "isCollection", isSignal: true, isRequired: false, transformFunction: null }, objectType: { classPropertyName: "objectType", publicName: "objectType", isSignal: true, isRequired: false, transformFunction: null }, writableOnly: { classPropertyName: "writableOnly", publicName: "writableOnly", isSignal: true, isRequired: false, transformFunction: null }, elementsOnly: { classPropertyName: "elementsOnly", publicName: "elementsOnly", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange" }, ngImport: i0, template: "<div class=\"fb-ref\" [class.fb-ref--open]=\"isOpen()\">\r\n <div class=\"fb-ref__control\">\r\n <input\r\n class=\"fb-ref__input\"\r\n type=\"text\"\r\n [value]=\"value() || ''\"\r\n [placeholder]=\"placeholder()\"\r\n [disabled]=\"disabled()\"\r\n [attr.aria-label]=\"label()\"\r\n (input)=\"onManualInput($any($event.target).value)\"\r\n (focus)=\"open()\"\r\n />\r\n <button\r\n type=\"button\"\r\n class=\"fb-ref__toggle\"\r\n [disabled]=\"disabled()\"\r\n [attr.aria-expanded]=\"isOpen()\"\r\n aria-label=\"Mostra i riferimenti disponibili\"\r\n (click)=\"toggle()\"\r\n >\r\n \u25BE\r\n </button>\r\n @if (value()) {\r\n <button type=\"button\" class=\"fb-ref__clear\" aria-label=\"Svuota\" (click)=\"clear()\">\u00D7</button>\r\n }\r\n </div>\r\n\r\n @if (hint()) {\r\n <!-- Un membro inesistente o non scrivibile e' un **errore**: mostrarlo come avviso direbbe il falso. -->\r\n <p\r\n class=\"fb-ref__hint\"\r\n [class.fb-ref__hint--warn]=\"valueState() === 'unknown'\"\r\n [class.fb-ref__hint--error]=\"isHintError()\"\r\n >\r\n {{ hint() }}\r\n </p>\r\n }\r\n @if (errorMessage()) {\r\n <p class=\"fb-ref__hint fb-ref__hint--warn\">\r\n Elenco dei riferimenti non disponibile: puoi scrivere il nome a mano.\r\n </p>\r\n }\r\n\r\n @if (isOpen()) {\r\n <div class=\"fb-ref__panel\" role=\"listbox\">\r\n <input\r\n class=\"fb-ref__search\"\r\n type=\"search\"\r\n placeholder=\"Filtra\"\r\n aria-label=\"Filtra i riferimenti\"\r\n (input)=\"onQuery($any($event.target).value)\"\r\n />\r\n @if (groups().length === 0) {\r\n <p class=\"fb-ref__empty\">Nessun riferimento compatibile.</p>\r\n }\r\n @for (group of groups(); track group.kind) {\r\n <div class=\"fb-ref__group\">\r\n <span class=\"fb-ref__group-label\">{{ group.label }}</span>\r\n @for (item of group.items; track item.name) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-ref__option\"\r\n role=\"option\"\r\n [attr.aria-selected]=\"item.name === value()\"\r\n (click)=\"choose(item)\"\r\n >\r\n <span class=\"fb-ref__name\">{{ item.name }}</span>\r\n <span class=\"fb-ref__meta\">{{ describe(item) }}</span>\r\n </button>\r\n }\r\n </div>\r\n }\r\n <button type=\"button\" class=\"fb-ref__close\" (click)=\"close()\">Chiudi</button>\r\n </div>\r\n }\r\n</div>\r\n", styles: [":host{display:block}.fb-ref{position:relative}.fb-ref__control{display:flex;align-items:stretch;gap:0;border:1px solid var(--fb-border, #d6dae1);border-radius:6px;background:var(--fb-surface, #fff);overflow:hidden}.fb-ref--open .fb-ref__control{border-color:var(--fb-accent, #2f6feb)}.fb-ref__input{flex:1;min-width:0;padding:5px 7px;border:0;background:transparent;color:var(--fb-text, #1d2939);font:inherit;font-size:12px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.fb-ref__input:focus-visible{outline:none}.fb-ref__toggle,.fb-ref__clear{flex:0 0 auto;padding:0 7px;border:0;border-left:1px solid var(--fb-border-subtle, #e6e9ee);background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:12px;cursor:pointer}.fb-ref__toggle:hover,.fb-ref__clear:hover{background:var(--fb-surface-alt, #f8f9fb)}.fb-ref__hint{margin:3px 0 0;font-size:10px;line-height:1.35;color:var(--fb-text-subtle, #98a2b3)}.fb-ref__hint--warn{color:var(--fb-warning, #b7791f)}.fb-ref__hint--error{color:var(--fb-error, #c9372c)}.fb-ref__panel{position:absolute;z-index:30;top:calc(100% + 3px);left:0;right:0;max-height:260px;overflow-y:auto;padding:6px;border:1px solid var(--fb-border, #d6dae1);border-radius:6px;background:var(--fb-surface, #fff);box-shadow:0 6px 18px #10182829}.fb-ref__search{box-sizing:border-box;width:100%;margin-bottom:6px;padding:4px 6px;border:1px solid var(--fb-border, #d6dae1);border-radius:5px;font:inherit;font-size:12px}.fb-ref__group{margin-bottom:6px}.fb-ref__group-label{display:block;padding:2px 4px;font-size:9px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;color:var(--fb-text-subtle, #98a2b3)}.fb-ref__option{display:flex;flex-direction:column;width:100%;padding:4px 6px;border:0;border-radius:4px;background:transparent;color:var(--fb-text, #1d2939);font:inherit;text-align:left;cursor:pointer}.fb-ref__option:hover,.fb-ref__option[aria-selected=true]{background:var(--fb-surface-alt, #f8f9fb)}.fb-ref__name{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px}.fb-ref__meta{font-size:10px;color:var(--fb-text-muted, #667085)}.fb-ref__empty{margin:4px;font-size:11px;color:var(--fb-text-muted, #667085)}.fb-ref__close{width:100%;margin-top:4px;padding:4px;border:0;border-top:1px solid var(--fb-border-subtle, #e6e9ee);background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:11px;cursor:pointer}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4000
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.27", type: ReferencePickerComponent, isStandalone: true, selector: "fb-reference-picker", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, dataType: { classPropertyName: "dataType", publicName: "dataType", isSignal: true, isRequired: false, transformFunction: null }, isCollection: { classPropertyName: "isCollection", publicName: "isCollection", isSignal: true, isRequired: false, transformFunction: null }, objectType: { classPropertyName: "objectType", publicName: "objectType", isSignal: true, isRequired: false, transformFunction: null }, writableOnly: { classPropertyName: "writableOnly", publicName: "writableOnly", isSignal: true, isRequired: false, transformFunction: null }, elementsOnly: { classPropertyName: "elementsOnly", publicName: "elementsOnly", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange" }, ngImport: i0, template: "<div class=\"fb-ref\" [class.fb-ref--open]=\"isOpen()\">\r\n <div class=\"fb-ref__control\">\r\n <input\r\n class=\"fb-ref__input\"\r\n type=\"text\"\r\n [value]=\"value() || ''\"\r\n [placeholder]=\"placeholder()\"\r\n [disabled]=\"disabled()\"\r\n [attr.aria-label]=\"label()\"\r\n (input)=\"onManualInput($any($event.target).value)\"\r\n (focus)=\"open()\"\r\n />\r\n <button\r\n type=\"button\"\r\n class=\"fb-ref__toggle\"\r\n [disabled]=\"disabled()\"\r\n [attr.aria-expanded]=\"isOpen()\"\r\n aria-label=\"Mostra i riferimenti disponibili\"\r\n (click)=\"toggle()\"\r\n >\r\n \u25BE\r\n </button>\r\n @if (value()) {\r\n <button type=\"button\" class=\"fb-ref__clear\" aria-label=\"Svuota\" (click)=\"clear()\">\u00D7</button>\r\n }\r\n </div>\r\n\r\n @if (hint()) {\r\n <!-- Un membro inesistente o non scrivibile e' un **errore**: mostrarlo come avviso direbbe il falso. -->\r\n <p\r\n class=\"fb-ref__hint\"\r\n [class.fb-ref__hint--warn]=\"valueState() === 'unknown'\"\r\n [class.fb-ref__hint--error]=\"isHintError()\"\r\n >\r\n {{ hint() }}\r\n </p>\r\n }\r\n @if (errorMessage()) {\r\n <p class=\"fb-ref__hint fb-ref__hint--warn\">\r\n Elenco dei riferimenti non disponibile: puoi scrivere il nome a mano.\r\n </p>\r\n }\r\n\r\n @if (isOpen()) {\r\n <div class=\"fb-ref__panel\" role=\"listbox\">\r\n <input\r\n class=\"fb-ref__search\"\r\n type=\"search\"\r\n placeholder=\"Filtra\"\r\n aria-label=\"Filtra i riferimenti\"\r\n (input)=\"onQuery($any($event.target).value)\"\r\n />\r\n @if (groups().length === 0) {\r\n <p class=\"fb-ref__empty\">Nessun riferimento compatibile.</p>\r\n }\r\n @for (group of groups(); track group.kind) {\r\n <div class=\"fb-ref__group\">\r\n <span class=\"fb-ref__group-label\">{{ group.label }}</span>\r\n @for (item of group.items; track item.name) {\r\n <!-- Due bottoni dove il percorso continua: scegliere il riferimento e scendere di un\r\n livello sono cose diverse, e su una Structure o un record servono entrambe. -->\r\n <div class=\"fb-ref__row\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-ref__option\"\r\n role=\"option\"\r\n [attr.aria-selected]=\"item.name === value()\"\r\n (click)=\"choose(item)\"\r\n >\r\n <span class=\"fb-ref__name\">{{ item.name }}</span>\r\n <span class=\"fb-ref__meta\">{{ describe(item) }}</span>\r\n </button>\r\n @if (canDescend(item)) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-ref__into\"\r\n [attr.aria-label]=\"'Entra in ' + item.name\"\r\n title=\"Entra e proponi i segmenti\"\r\n (click)=\"descend(item)\"\r\n >\r\n \u203A\r\n </button>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n <button type=\"button\" class=\"fb-ref__close\" (click)=\"close()\">Chiudi</button>\r\n </div>\r\n }\r\n</div>\r\n", styles: [":host{display:block}.fb-ref{position:relative}.fb-ref__control{display:flex;align-items:stretch;gap:0;border:1px solid var(--fb-border, #d6dae1);border-radius:6px;background:var(--fb-surface, #fff);overflow:hidden}.fb-ref--open .fb-ref__control{border-color:var(--fb-accent, #2f6feb)}.fb-ref__input{flex:1;min-width:0;padding:5px 7px;border:0;background:transparent;color:var(--fb-text, #1d2939);font:inherit;font-size:12px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.fb-ref__input:focus-visible{outline:none}.fb-ref__toggle,.fb-ref__clear{flex:0 0 auto;padding:0 7px;border:0;border-left:1px solid var(--fb-border-subtle, #e6e9ee);background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:12px;cursor:pointer}.fb-ref__toggle:hover,.fb-ref__clear:hover{background:var(--fb-surface-alt, #f8f9fb)}.fb-ref__hint{margin:3px 0 0;font-size:10px;line-height:1.35;color:var(--fb-text-subtle, #98a2b3)}.fb-ref__hint--warn{color:var(--fb-warning, #b7791f)}.fb-ref__hint--error{color:var(--fb-error, #c9372c)}.fb-ref__panel{position:absolute;z-index:30;top:calc(100% + 3px);left:0;right:0;max-height:260px;overflow-y:auto;padding:6px;border:1px solid var(--fb-border, #d6dae1);border-radius:6px;background:var(--fb-surface, #fff);box-shadow:0 6px 18px #10182829}.fb-ref__search{box-sizing:border-box;width:100%;margin-bottom:6px;padding:4px 6px;border:1px solid var(--fb-border, #d6dae1);border-radius:5px;font:inherit;font-size:12px}.fb-ref__group{margin-bottom:6px}.fb-ref__group-label{display:block;padding:2px 4px;font-size:9px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;color:var(--fb-text-subtle, #98a2b3)}.fb-ref__row{display:flex;align-items:stretch;gap:2px}.fb-ref__into{flex:0 0 auto;padding:0 7px;border:0;border-radius:4px;background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:13px;cursor:pointer}.fb-ref__into:hover{background:var(--fb-surface-alt, #f8f9fb);color:var(--fb-accent, #2f6feb)}.fb-ref__option{display:flex;flex:1;flex-direction:column;min-width:0;width:100%;padding:4px 6px;border:0;border-radius:4px;background:transparent;color:var(--fb-text, #1d2939);font:inherit;text-align:left;cursor:pointer}.fb-ref__option:hover,.fb-ref__option[aria-selected=true]{background:var(--fb-surface-alt, #f8f9fb)}.fb-ref__name{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px}.fb-ref__meta{font-size:10px;color:var(--fb-text-muted, #667085)}.fb-ref__empty{margin:4px;font-size:11px;color:var(--fb-text-muted, #667085)}.fb-ref__close{width:100%;margin-top:4px;padding:4px;border:0;border-top:1px solid var(--fb-border-subtle, #e6e9ee);background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:11px;cursor:pointer}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3554
4001
  }
3555
4002
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImport: i0, type: ReferencePickerComponent, decorators: [{
3556
4003
  type: Component,
3557
- args: [{ selector: 'fb-reference-picker', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"fb-ref\" [class.fb-ref--open]=\"isOpen()\">\r\n <div class=\"fb-ref__control\">\r\n <input\r\n class=\"fb-ref__input\"\r\n type=\"text\"\r\n [value]=\"value() || ''\"\r\n [placeholder]=\"placeholder()\"\r\n [disabled]=\"disabled()\"\r\n [attr.aria-label]=\"label()\"\r\n (input)=\"onManualInput($any($event.target).value)\"\r\n (focus)=\"open()\"\r\n />\r\n <button\r\n type=\"button\"\r\n class=\"fb-ref__toggle\"\r\n [disabled]=\"disabled()\"\r\n [attr.aria-expanded]=\"isOpen()\"\r\n aria-label=\"Mostra i riferimenti disponibili\"\r\n (click)=\"toggle()\"\r\n >\r\n \u25BE\r\n </button>\r\n @if (value()) {\r\n <button type=\"button\" class=\"fb-ref__clear\" aria-label=\"Svuota\" (click)=\"clear()\">\u00D7</button>\r\n }\r\n </div>\r\n\r\n @if (hint()) {\r\n <!-- Un membro inesistente o non scrivibile e' un **errore**: mostrarlo come avviso direbbe il falso. -->\r\n <p\r\n class=\"fb-ref__hint\"\r\n [class.fb-ref__hint--warn]=\"valueState() === 'unknown'\"\r\n [class.fb-ref__hint--error]=\"isHintError()\"\r\n >\r\n {{ hint() }}\r\n </p>\r\n }\r\n @if (errorMessage()) {\r\n <p class=\"fb-ref__hint fb-ref__hint--warn\">\r\n Elenco dei riferimenti non disponibile: puoi scrivere il nome a mano.\r\n </p>\r\n }\r\n\r\n @if (isOpen()) {\r\n <div class=\"fb-ref__panel\" role=\"listbox\">\r\n <input\r\n class=\"fb-ref__search\"\r\n type=\"search\"\r\n placeholder=\"Filtra\"\r\n aria-label=\"Filtra i riferimenti\"\r\n (input)=\"onQuery($any($event.target).value)\"\r\n />\r\n @if (groups().length === 0) {\r\n <p class=\"fb-ref__empty\">Nessun riferimento compatibile.</p>\r\n }\r\n @for (group of groups(); track group.kind) {\r\n <div class=\"fb-ref__group\">\r\n <span class=\"fb-ref__group-label\">{{ group.label }}</span>\r\n @for (item of group.items; track item.name) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-ref__option\"\r\n role=\"option\"\r\n [attr.aria-selected]=\"item.name === value()\"\r\n (click)=\"choose(item)\"\r\n >\r\n <span class=\"fb-ref__name\">{{ item.name }}</span>\r\n <span class=\"fb-ref__meta\">{{ describe(item) }}</span>\r\n </button>\r\n }\r\n </div>\r\n }\r\n <button type=\"button\" class=\"fb-ref__close\" (click)=\"close()\">Chiudi</button>\r\n </div>\r\n }\r\n</div>\r\n", styles: [":host{display:block}.fb-ref{position:relative}.fb-ref__control{display:flex;align-items:stretch;gap:0;border:1px solid var(--fb-border, #d6dae1);border-radius:6px;background:var(--fb-surface, #fff);overflow:hidden}.fb-ref--open .fb-ref__control{border-color:var(--fb-accent, #2f6feb)}.fb-ref__input{flex:1;min-width:0;padding:5px 7px;border:0;background:transparent;color:var(--fb-text, #1d2939);font:inherit;font-size:12px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.fb-ref__input:focus-visible{outline:none}.fb-ref__toggle,.fb-ref__clear{flex:0 0 auto;padding:0 7px;border:0;border-left:1px solid var(--fb-border-subtle, #e6e9ee);background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:12px;cursor:pointer}.fb-ref__toggle:hover,.fb-ref__clear:hover{background:var(--fb-surface-alt, #f8f9fb)}.fb-ref__hint{margin:3px 0 0;font-size:10px;line-height:1.35;color:var(--fb-text-subtle, #98a2b3)}.fb-ref__hint--warn{color:var(--fb-warning, #b7791f)}.fb-ref__hint--error{color:var(--fb-error, #c9372c)}.fb-ref__panel{position:absolute;z-index:30;top:calc(100% + 3px);left:0;right:0;max-height:260px;overflow-y:auto;padding:6px;border:1px solid var(--fb-border, #d6dae1);border-radius:6px;background:var(--fb-surface, #fff);box-shadow:0 6px 18px #10182829}.fb-ref__search{box-sizing:border-box;width:100%;margin-bottom:6px;padding:4px 6px;border:1px solid var(--fb-border, #d6dae1);border-radius:5px;font:inherit;font-size:12px}.fb-ref__group{margin-bottom:6px}.fb-ref__group-label{display:block;padding:2px 4px;font-size:9px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;color:var(--fb-text-subtle, #98a2b3)}.fb-ref__option{display:flex;flex-direction:column;width:100%;padding:4px 6px;border:0;border-radius:4px;background:transparent;color:var(--fb-text, #1d2939);font:inherit;text-align:left;cursor:pointer}.fb-ref__option:hover,.fb-ref__option[aria-selected=true]{background:var(--fb-surface-alt, #f8f9fb)}.fb-ref__name{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px}.fb-ref__meta{font-size:10px;color:var(--fb-text-muted, #667085)}.fb-ref__empty{margin:4px;font-size:11px;color:var(--fb-text-muted, #667085)}.fb-ref__close{width:100%;margin-top:4px;padding:4px;border:0;border-top:1px solid var(--fb-border-subtle, #e6e9ee);background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:11px;cursor:pointer}\n"] }]
4004
+ args: [{ selector: 'fb-reference-picker', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"fb-ref\" [class.fb-ref--open]=\"isOpen()\">\r\n <div class=\"fb-ref__control\">\r\n <input\r\n class=\"fb-ref__input\"\r\n type=\"text\"\r\n [value]=\"value() || ''\"\r\n [placeholder]=\"placeholder()\"\r\n [disabled]=\"disabled()\"\r\n [attr.aria-label]=\"label()\"\r\n (input)=\"onManualInput($any($event.target).value)\"\r\n (focus)=\"open()\"\r\n />\r\n <button\r\n type=\"button\"\r\n class=\"fb-ref__toggle\"\r\n [disabled]=\"disabled()\"\r\n [attr.aria-expanded]=\"isOpen()\"\r\n aria-label=\"Mostra i riferimenti disponibili\"\r\n (click)=\"toggle()\"\r\n >\r\n \u25BE\r\n </button>\r\n @if (value()) {\r\n <button type=\"button\" class=\"fb-ref__clear\" aria-label=\"Svuota\" (click)=\"clear()\">\u00D7</button>\r\n }\r\n </div>\r\n\r\n @if (hint()) {\r\n <!-- Un membro inesistente o non scrivibile e' un **errore**: mostrarlo come avviso direbbe il falso. -->\r\n <p\r\n class=\"fb-ref__hint\"\r\n [class.fb-ref__hint--warn]=\"valueState() === 'unknown'\"\r\n [class.fb-ref__hint--error]=\"isHintError()\"\r\n >\r\n {{ hint() }}\r\n </p>\r\n }\r\n @if (errorMessage()) {\r\n <p class=\"fb-ref__hint fb-ref__hint--warn\">\r\n Elenco dei riferimenti non disponibile: puoi scrivere il nome a mano.\r\n </p>\r\n }\r\n\r\n @if (isOpen()) {\r\n <div class=\"fb-ref__panel\" role=\"listbox\">\r\n <input\r\n class=\"fb-ref__search\"\r\n type=\"search\"\r\n placeholder=\"Filtra\"\r\n aria-label=\"Filtra i riferimenti\"\r\n (input)=\"onQuery($any($event.target).value)\"\r\n />\r\n @if (groups().length === 0) {\r\n <p class=\"fb-ref__empty\">Nessun riferimento compatibile.</p>\r\n }\r\n @for (group of groups(); track group.kind) {\r\n <div class=\"fb-ref__group\">\r\n <span class=\"fb-ref__group-label\">{{ group.label }}</span>\r\n @for (item of group.items; track item.name) {\r\n <!-- Due bottoni dove il percorso continua: scegliere il riferimento e scendere di un\r\n livello sono cose diverse, e su una Structure o un record servono entrambe. -->\r\n <div class=\"fb-ref__row\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-ref__option\"\r\n role=\"option\"\r\n [attr.aria-selected]=\"item.name === value()\"\r\n (click)=\"choose(item)\"\r\n >\r\n <span class=\"fb-ref__name\">{{ item.name }}</span>\r\n <span class=\"fb-ref__meta\">{{ describe(item) }}</span>\r\n </button>\r\n @if (canDescend(item)) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-ref__into\"\r\n [attr.aria-label]=\"'Entra in ' + item.name\"\r\n title=\"Entra e proponi i segmenti\"\r\n (click)=\"descend(item)\"\r\n >\r\n \u203A\r\n </button>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n <button type=\"button\" class=\"fb-ref__close\" (click)=\"close()\">Chiudi</button>\r\n </div>\r\n }\r\n</div>\r\n", styles: [":host{display:block}.fb-ref{position:relative}.fb-ref__control{display:flex;align-items:stretch;gap:0;border:1px solid var(--fb-border, #d6dae1);border-radius:6px;background:var(--fb-surface, #fff);overflow:hidden}.fb-ref--open .fb-ref__control{border-color:var(--fb-accent, #2f6feb)}.fb-ref__input{flex:1;min-width:0;padding:5px 7px;border:0;background:transparent;color:var(--fb-text, #1d2939);font:inherit;font-size:12px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.fb-ref__input:focus-visible{outline:none}.fb-ref__toggle,.fb-ref__clear{flex:0 0 auto;padding:0 7px;border:0;border-left:1px solid var(--fb-border-subtle, #e6e9ee);background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:12px;cursor:pointer}.fb-ref__toggle:hover,.fb-ref__clear:hover{background:var(--fb-surface-alt, #f8f9fb)}.fb-ref__hint{margin:3px 0 0;font-size:10px;line-height:1.35;color:var(--fb-text-subtle, #98a2b3)}.fb-ref__hint--warn{color:var(--fb-warning, #b7791f)}.fb-ref__hint--error{color:var(--fb-error, #c9372c)}.fb-ref__panel{position:absolute;z-index:30;top:calc(100% + 3px);left:0;right:0;max-height:260px;overflow-y:auto;padding:6px;border:1px solid var(--fb-border, #d6dae1);border-radius:6px;background:var(--fb-surface, #fff);box-shadow:0 6px 18px #10182829}.fb-ref__search{box-sizing:border-box;width:100%;margin-bottom:6px;padding:4px 6px;border:1px solid var(--fb-border, #d6dae1);border-radius:5px;font:inherit;font-size:12px}.fb-ref__group{margin-bottom:6px}.fb-ref__group-label{display:block;padding:2px 4px;font-size:9px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;color:var(--fb-text-subtle, #98a2b3)}.fb-ref__row{display:flex;align-items:stretch;gap:2px}.fb-ref__into{flex:0 0 auto;padding:0 7px;border:0;border-radius:4px;background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:13px;cursor:pointer}.fb-ref__into:hover{background:var(--fb-surface-alt, #f8f9fb);color:var(--fb-accent, #2f6feb)}.fb-ref__option{display:flex;flex:1;flex-direction:column;min-width:0;width:100%;padding:4px 6px;border:0;border-radius:4px;background:transparent;color:var(--fb-text, #1d2939);font:inherit;text-align:left;cursor:pointer}.fb-ref__option:hover,.fb-ref__option[aria-selected=true]{background:var(--fb-surface-alt, #f8f9fb)}.fb-ref__name{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px}.fb-ref__meta{font-size:10px;color:var(--fb-text-muted, #667085)}.fb-ref__empty{margin:4px;font-size:11px;color:var(--fb-text-muted, #667085)}.fb-ref__close{width:100%;margin-top:4px;padding:4px;border:0;border-top:1px solid var(--fb-border-subtle, #e6e9ee);background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:11px;cursor:pointer}\n"] }]
3558
4005
  }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], dataType: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataType", required: false }] }], isCollection: [{ type: i0.Input, args: [{ isSignal: true, alias: "isCollection", required: false }] }], objectType: [{ type: i0.Input, args: [{ isSignal: true, alias: "objectType", required: false }] }], writableOnly: [{ type: i0.Input, args: [{ isSignal: true, alias: "writableOnly", required: false }] }], elementsOnly: [{ type: i0.Input, args: [{ isSignal: true, alias: "elementsOnly", required: false }] }], valueChange: [{ type: i0.Output, args: ["valueChange"] }] } });
3559
4006
 
3560
4007
  /**
@@ -3669,11 +4116,11 @@ class ObjectPickerComponent {
3669
4116
  return parts.join(' · ');
3670
4117
  }
3671
4118
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.27", ngImport: i0, type: ObjectPickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3672
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.27", type: ObjectPickerComponent, isStandalone: true, selector: "fb-object-picker", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange" }, ngImport: i0, template: "<div class=\"fb-pick\" [class.fb-pick--open]=\"isOpen()\">\r\n <div class=\"fb-pick__control\">\r\n <input\r\n class=\"fb-pick__input\"\r\n type=\"text\"\r\n role=\"combobox\"\r\n autocomplete=\"off\"\r\n [value]=\"text()\"\r\n [placeholder]=\"placeholder()\"\r\n [disabled]=\"disabled()\"\r\n [attr.aria-label]=\"label()\"\r\n [attr.aria-expanded]=\"isOpen()\"\r\n (input)=\"onInput($any($event.target).value)\"\r\n (focus)=\"open()\"\r\n (keydown.escape)=\"close()\"\r\n />\r\n <button\r\n type=\"button\"\r\n class=\"fb-pick__toggle\"\r\n [disabled]=\"disabled()\"\r\n [attr.aria-expanded]=\"isOpen()\"\r\n aria-label=\"Mostra gli oggetti disponibili\"\r\n (click)=\"toggle()\"\r\n >\r\n \u25BE\r\n </button>\r\n @if (value()) {\r\n <button type=\"button\" class=\"fb-pick__clear\" aria-label=\"Svuota\" (click)=\"clear()\">\u00D7</button>\r\n }\r\n </div>\r\n\r\n @if (isUnknown()) {\r\n <p class=\"fb-pick__hint fb-pick__hint--warn\">\r\n Questo nome non e\u2019 fra gli oggetti disponibili: la validazione lo segnalerebbe.\r\n </p>\r\n } @else if (labelOfValue()) {\r\n <p class=\"fb-pick__hint\">{{ labelOfValue() }}</p>\r\n } @else if (loadErrored()) {\r\n <p class=\"fb-pick__hint fb-pick__hint--warn\">\r\n Elenco degli oggetti non disponibile: puoi scrivere il nome a mano.\r\n </p>\r\n }\r\n\r\n @if (isOpen()) {\r\n <div class=\"fb-pick__panel\" role=\"listbox\">\r\n @if (options().length === 0) {\r\n <p class=\"fb-pick__empty\">\r\n {{ hasCatalog() ? 'Nessun oggetto corrisponde.' : 'Catalogo degli oggetti non disponibile.' }}\r\n </p>\r\n }\r\n @for (object of options(); track object.name) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-pick__option\"\r\n role=\"option\"\r\n [attr.aria-selected]=\"object.name === value()\"\r\n (click)=\"choose(object)\"\r\n >\r\n <span class=\"fb-pick__name\">{{ object.name }}</span>\r\n @if (describe(object)) {\r\n <span class=\"fb-pick__meta\">{{ describe(object) }}</span>\r\n }\r\n </button>\r\n }\r\n <button type=\"button\" class=\"fb-pick__close\" (click)=\"close()\">Chiudi</button>\r\n </div>\r\n }\r\n</div>\r\n", styles: [":host{display:block}.fb-pick{position:relative}.fb-pick__control{display:flex;align-items:stretch;border:1px solid var(--fb-border, #d6dae1);border-radius:6px;background:var(--fb-surface, #fff);overflow:hidden}.fb-pick--open .fb-pick__control{border-color:var(--fb-accent, #2f6feb)}.fb-pick__input{flex:1;min-width:0;padding:5px 7px;border:0;background:transparent;color:var(--fb-text, #1d2939);font:inherit;font-size:12px}.fb-pick__input:focus-visible{outline:none}.fb-pick__input--mono,.fb-pick__name--mono{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.fb-pick__toggle,.fb-pick__clear{flex:0 0 auto;padding:0 7px;border:0;border-left:1px solid var(--fb-border-subtle, #e6e9ee);background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:12px;cursor:pointer}.fb-pick__toggle:hover,.fb-pick__clear:hover{background:var(--fb-surface-alt, #f8f9fb)}.fb-pick__hint{margin:3px 0 0;font-size:10px;line-height:1.35;color:var(--fb-text-subtle, #98a2b3)}.fb-pick__hint--warn{color:var(--fb-warning, #b7791f)}.fb-pick__hint--error{color:var(--fb-error, #c9372c)}.fb-pick__panel{position:absolute;z-index:30;top:calc(100% + 3px);left:0;right:0;max-height:260px;overflow-y:auto;padding:6px;border:1px solid var(--fb-border, #d6dae1);border-radius:6px;background:var(--fb-surface, #fff);box-shadow:0 6px 18px #10182829}.fb-pick__option{display:flex;flex-direction:column;width:100%;padding:4px 6px;border:0;border-radius:4px;background:transparent;color:var(--fb-text, #1d2939);font:inherit;text-align:left;cursor:pointer}.fb-pick__option:hover,.fb-pick__option[aria-selected=true]{background:var(--fb-surface-alt, #f8f9fb)}.fb-pick__name{font-size:12px}.fb-pick__meta{font-size:10px;color:var(--fb-text-muted, #667085)}.fb-pick__group{margin:2px 4px 4px;font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--fb-text-muted, #667085)}.fb-pick__empty{margin:4px;font-size:11px;color:var(--fb-text-muted, #667085)}.fb-pick__close{width:100%;margin-top:4px;padding:4px;border:0;border-top:1px solid var(--fb-border-subtle, #e6e9ee);background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:11px;cursor:pointer}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4119
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.27", type: ObjectPickerComponent, isStandalone: true, selector: "fb-object-picker", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange" }, ngImport: i0, template: "<div class=\"fb-pick\" [class.fb-pick--open]=\"isOpen()\">\r\n <div class=\"fb-pick__control\">\r\n <input\r\n class=\"fb-pick__input\"\r\n type=\"text\"\r\n role=\"combobox\"\r\n autocomplete=\"off\"\r\n [value]=\"text()\"\r\n [placeholder]=\"placeholder()\"\r\n [disabled]=\"disabled()\"\r\n [attr.aria-label]=\"label()\"\r\n [attr.aria-expanded]=\"isOpen()\"\r\n (input)=\"onInput($any($event.target).value)\"\r\n (focus)=\"open()\"\r\n (keydown.escape)=\"close()\"\r\n />\r\n <button\r\n type=\"button\"\r\n class=\"fb-pick__toggle\"\r\n [disabled]=\"disabled()\"\r\n [attr.aria-expanded]=\"isOpen()\"\r\n aria-label=\"Mostra gli oggetti disponibili\"\r\n (click)=\"toggle()\"\r\n >\r\n \u25BE\r\n </button>\r\n @if (value()) {\r\n <button type=\"button\" class=\"fb-pick__clear\" aria-label=\"Svuota\" (click)=\"clear()\">\u00D7</button>\r\n }\r\n </div>\r\n\r\n @if (isUnknown()) {\r\n <p class=\"fb-pick__hint fb-pick__hint--warn\">\r\n Questo nome non e\u2019 fra gli oggetti disponibili: la validazione lo segnalerebbe.\r\n </p>\r\n } @else if (labelOfValue()) {\r\n <p class=\"fb-pick__hint\">{{ labelOfValue() }}</p>\r\n } @else if (loadErrored()) {\r\n <p class=\"fb-pick__hint fb-pick__hint--warn\">\r\n Elenco degli oggetti non disponibile: puoi scrivere il nome a mano.\r\n </p>\r\n }\r\n\r\n @if (isOpen()) {\r\n <div class=\"fb-pick__panel\" role=\"listbox\">\r\n @if (options().length === 0) {\r\n <p class=\"fb-pick__empty\">\r\n {{ hasCatalog() ? 'Nessun oggetto corrisponde.' : 'Catalogo degli oggetti non disponibile.' }}\r\n </p>\r\n }\r\n @for (object of options(); track object.name) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-pick__option\"\r\n role=\"option\"\r\n [attr.aria-selected]=\"object.name === value()\"\r\n (click)=\"choose(object)\"\r\n >\r\n <span class=\"fb-pick__name\">{{ object.name }}</span>\r\n @if (describe(object)) {\r\n <span class=\"fb-pick__meta\">{{ describe(object) }}</span>\r\n }\r\n </button>\r\n }\r\n <button type=\"button\" class=\"fb-pick__close\" (click)=\"close()\">Chiudi</button>\r\n </div>\r\n }\r\n</div>\r\n", styles: [":host{display:block}.fb-pick{position:relative}.fb-pick__control{display:flex;align-items:stretch;border:1px solid var(--fb-border, #d6dae1);border-radius:6px;background:var(--fb-surface, #fff);overflow:hidden}.fb-pick--open .fb-pick__control{border-color:var(--fb-accent, #2f6feb)}.fb-pick__input{flex:1;min-width:0;padding:5px 7px;border:0;background:transparent;color:var(--fb-text, #1d2939);font:inherit;font-size:12px}.fb-pick__input:focus-visible{outline:none}.fb-pick__input--mono,.fb-pick__name--mono{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.fb-pick__toggle,.fb-pick__clear{flex:0 0 auto;padding:0 7px;border:0;border-left:1px solid var(--fb-border-subtle, #e6e9ee);background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:12px;cursor:pointer}.fb-pick__toggle:hover,.fb-pick__clear:hover{background:var(--fb-surface-alt, #f8f9fb)}.fb-pick__hint{margin:3px 0 0;font-size:10px;line-height:1.35;color:var(--fb-text-subtle, #98a2b3)}.fb-pick__hint--warn{color:var(--fb-warning, #b7791f)}.fb-pick__hint--error{color:var(--fb-error, #c9372c)}.fb-pick__panel{position:absolute;z-index:30;top:calc(100% + 3px);left:0;right:0;max-height:260px;overflow-y:auto;padding:6px;border:1px solid var(--fb-border, #d6dae1);border-radius:6px;background:var(--fb-surface, #fff);box-shadow:0 6px 18px #10182829}.fb-pick__option{display:flex;flex-direction:column;width:100%;padding:4px 6px;border:0;border-radius:4px;background:transparent;color:var(--fb-text, #1d2939);font:inherit;text-align:left;cursor:pointer}.fb-pick__option:hover,.fb-pick__option[aria-selected=true]{background:var(--fb-surface-alt, #f8f9fb)}.fb-pick__row{display:flex;align-items:stretch;gap:2px}.fb-pick__row .fb-pick__option{flex:1;min-width:0}.fb-pick__into{flex:0 0 auto;padding:0 7px;border:0;border-radius:4px;background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:13px;cursor:pointer}.fb-pick__into:hover{background:var(--fb-surface-alt, #f8f9fb);color:var(--fb-accent, #2f6feb)}.fb-pick__name{font-size:12px}.fb-pick__meta{font-size:10px;color:var(--fb-text-muted, #667085)}.fb-pick__group{margin:2px 4px 4px;font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--fb-text-muted, #667085)}.fb-pick__empty{margin:4px;font-size:11px;color:var(--fb-text-muted, #667085)}.fb-pick__close{width:100%;margin-top:4px;padding:4px;border:0;border-top:1px solid var(--fb-border-subtle, #e6e9ee);background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:11px;cursor:pointer}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3673
4120
  }
3674
4121
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImport: i0, type: ObjectPickerComponent, decorators: [{
3675
4122
  type: Component,
3676
- args: [{ selector: 'fb-object-picker', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"fb-pick\" [class.fb-pick--open]=\"isOpen()\">\r\n <div class=\"fb-pick__control\">\r\n <input\r\n class=\"fb-pick__input\"\r\n type=\"text\"\r\n role=\"combobox\"\r\n autocomplete=\"off\"\r\n [value]=\"text()\"\r\n [placeholder]=\"placeholder()\"\r\n [disabled]=\"disabled()\"\r\n [attr.aria-label]=\"label()\"\r\n [attr.aria-expanded]=\"isOpen()\"\r\n (input)=\"onInput($any($event.target).value)\"\r\n (focus)=\"open()\"\r\n (keydown.escape)=\"close()\"\r\n />\r\n <button\r\n type=\"button\"\r\n class=\"fb-pick__toggle\"\r\n [disabled]=\"disabled()\"\r\n [attr.aria-expanded]=\"isOpen()\"\r\n aria-label=\"Mostra gli oggetti disponibili\"\r\n (click)=\"toggle()\"\r\n >\r\n \u25BE\r\n </button>\r\n @if (value()) {\r\n <button type=\"button\" class=\"fb-pick__clear\" aria-label=\"Svuota\" (click)=\"clear()\">\u00D7</button>\r\n }\r\n </div>\r\n\r\n @if (isUnknown()) {\r\n <p class=\"fb-pick__hint fb-pick__hint--warn\">\r\n Questo nome non e\u2019 fra gli oggetti disponibili: la validazione lo segnalerebbe.\r\n </p>\r\n } @else if (labelOfValue()) {\r\n <p class=\"fb-pick__hint\">{{ labelOfValue() }}</p>\r\n } @else if (loadErrored()) {\r\n <p class=\"fb-pick__hint fb-pick__hint--warn\">\r\n Elenco degli oggetti non disponibile: puoi scrivere il nome a mano.\r\n </p>\r\n }\r\n\r\n @if (isOpen()) {\r\n <div class=\"fb-pick__panel\" role=\"listbox\">\r\n @if (options().length === 0) {\r\n <p class=\"fb-pick__empty\">\r\n {{ hasCatalog() ? 'Nessun oggetto corrisponde.' : 'Catalogo degli oggetti non disponibile.' }}\r\n </p>\r\n }\r\n @for (object of options(); track object.name) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-pick__option\"\r\n role=\"option\"\r\n [attr.aria-selected]=\"object.name === value()\"\r\n (click)=\"choose(object)\"\r\n >\r\n <span class=\"fb-pick__name\">{{ object.name }}</span>\r\n @if (describe(object)) {\r\n <span class=\"fb-pick__meta\">{{ describe(object) }}</span>\r\n }\r\n </button>\r\n }\r\n <button type=\"button\" class=\"fb-pick__close\" (click)=\"close()\">Chiudi</button>\r\n </div>\r\n }\r\n</div>\r\n", styles: [":host{display:block}.fb-pick{position:relative}.fb-pick__control{display:flex;align-items:stretch;border:1px solid var(--fb-border, #d6dae1);border-radius:6px;background:var(--fb-surface, #fff);overflow:hidden}.fb-pick--open .fb-pick__control{border-color:var(--fb-accent, #2f6feb)}.fb-pick__input{flex:1;min-width:0;padding:5px 7px;border:0;background:transparent;color:var(--fb-text, #1d2939);font:inherit;font-size:12px}.fb-pick__input:focus-visible{outline:none}.fb-pick__input--mono,.fb-pick__name--mono{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.fb-pick__toggle,.fb-pick__clear{flex:0 0 auto;padding:0 7px;border:0;border-left:1px solid var(--fb-border-subtle, #e6e9ee);background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:12px;cursor:pointer}.fb-pick__toggle:hover,.fb-pick__clear:hover{background:var(--fb-surface-alt, #f8f9fb)}.fb-pick__hint{margin:3px 0 0;font-size:10px;line-height:1.35;color:var(--fb-text-subtle, #98a2b3)}.fb-pick__hint--warn{color:var(--fb-warning, #b7791f)}.fb-pick__hint--error{color:var(--fb-error, #c9372c)}.fb-pick__panel{position:absolute;z-index:30;top:calc(100% + 3px);left:0;right:0;max-height:260px;overflow-y:auto;padding:6px;border:1px solid var(--fb-border, #d6dae1);border-radius:6px;background:var(--fb-surface, #fff);box-shadow:0 6px 18px #10182829}.fb-pick__option{display:flex;flex-direction:column;width:100%;padding:4px 6px;border:0;border-radius:4px;background:transparent;color:var(--fb-text, #1d2939);font:inherit;text-align:left;cursor:pointer}.fb-pick__option:hover,.fb-pick__option[aria-selected=true]{background:var(--fb-surface-alt, #f8f9fb)}.fb-pick__name{font-size:12px}.fb-pick__meta{font-size:10px;color:var(--fb-text-muted, #667085)}.fb-pick__group{margin:2px 4px 4px;font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--fb-text-muted, #667085)}.fb-pick__empty{margin:4px;font-size:11px;color:var(--fb-text-muted, #667085)}.fb-pick__close{width:100%;margin-top:4px;padding:4px;border:0;border-top:1px solid var(--fb-border-subtle, #e6e9ee);background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:11px;cursor:pointer}\n"] }]
4123
+ args: [{ selector: 'fb-object-picker', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"fb-pick\" [class.fb-pick--open]=\"isOpen()\">\r\n <div class=\"fb-pick__control\">\r\n <input\r\n class=\"fb-pick__input\"\r\n type=\"text\"\r\n role=\"combobox\"\r\n autocomplete=\"off\"\r\n [value]=\"text()\"\r\n [placeholder]=\"placeholder()\"\r\n [disabled]=\"disabled()\"\r\n [attr.aria-label]=\"label()\"\r\n [attr.aria-expanded]=\"isOpen()\"\r\n (input)=\"onInput($any($event.target).value)\"\r\n (focus)=\"open()\"\r\n (keydown.escape)=\"close()\"\r\n />\r\n <button\r\n type=\"button\"\r\n class=\"fb-pick__toggle\"\r\n [disabled]=\"disabled()\"\r\n [attr.aria-expanded]=\"isOpen()\"\r\n aria-label=\"Mostra gli oggetti disponibili\"\r\n (click)=\"toggle()\"\r\n >\r\n \u25BE\r\n </button>\r\n @if (value()) {\r\n <button type=\"button\" class=\"fb-pick__clear\" aria-label=\"Svuota\" (click)=\"clear()\">\u00D7</button>\r\n }\r\n </div>\r\n\r\n @if (isUnknown()) {\r\n <p class=\"fb-pick__hint fb-pick__hint--warn\">\r\n Questo nome non e\u2019 fra gli oggetti disponibili: la validazione lo segnalerebbe.\r\n </p>\r\n } @else if (labelOfValue()) {\r\n <p class=\"fb-pick__hint\">{{ labelOfValue() }}</p>\r\n } @else if (loadErrored()) {\r\n <p class=\"fb-pick__hint fb-pick__hint--warn\">\r\n Elenco degli oggetti non disponibile: puoi scrivere il nome a mano.\r\n </p>\r\n }\r\n\r\n @if (isOpen()) {\r\n <div class=\"fb-pick__panel\" role=\"listbox\">\r\n @if (options().length === 0) {\r\n <p class=\"fb-pick__empty\">\r\n {{ hasCatalog() ? 'Nessun oggetto corrisponde.' : 'Catalogo degli oggetti non disponibile.' }}\r\n </p>\r\n }\r\n @for (object of options(); track object.name) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-pick__option\"\r\n role=\"option\"\r\n [attr.aria-selected]=\"object.name === value()\"\r\n (click)=\"choose(object)\"\r\n >\r\n <span class=\"fb-pick__name\">{{ object.name }}</span>\r\n @if (describe(object)) {\r\n <span class=\"fb-pick__meta\">{{ describe(object) }}</span>\r\n }\r\n </button>\r\n }\r\n <button type=\"button\" class=\"fb-pick__close\" (click)=\"close()\">Chiudi</button>\r\n </div>\r\n }\r\n</div>\r\n", styles: [":host{display:block}.fb-pick{position:relative}.fb-pick__control{display:flex;align-items:stretch;border:1px solid var(--fb-border, #d6dae1);border-radius:6px;background:var(--fb-surface, #fff);overflow:hidden}.fb-pick--open .fb-pick__control{border-color:var(--fb-accent, #2f6feb)}.fb-pick__input{flex:1;min-width:0;padding:5px 7px;border:0;background:transparent;color:var(--fb-text, #1d2939);font:inherit;font-size:12px}.fb-pick__input:focus-visible{outline:none}.fb-pick__input--mono,.fb-pick__name--mono{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.fb-pick__toggle,.fb-pick__clear{flex:0 0 auto;padding:0 7px;border:0;border-left:1px solid var(--fb-border-subtle, #e6e9ee);background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:12px;cursor:pointer}.fb-pick__toggle:hover,.fb-pick__clear:hover{background:var(--fb-surface-alt, #f8f9fb)}.fb-pick__hint{margin:3px 0 0;font-size:10px;line-height:1.35;color:var(--fb-text-subtle, #98a2b3)}.fb-pick__hint--warn{color:var(--fb-warning, #b7791f)}.fb-pick__hint--error{color:var(--fb-error, #c9372c)}.fb-pick__panel{position:absolute;z-index:30;top:calc(100% + 3px);left:0;right:0;max-height:260px;overflow-y:auto;padding:6px;border:1px solid var(--fb-border, #d6dae1);border-radius:6px;background:var(--fb-surface, #fff);box-shadow:0 6px 18px #10182829}.fb-pick__option{display:flex;flex-direction:column;width:100%;padding:4px 6px;border:0;border-radius:4px;background:transparent;color:var(--fb-text, #1d2939);font:inherit;text-align:left;cursor:pointer}.fb-pick__option:hover,.fb-pick__option[aria-selected=true]{background:var(--fb-surface-alt, #f8f9fb)}.fb-pick__row{display:flex;align-items:stretch;gap:2px}.fb-pick__row .fb-pick__option{flex:1;min-width:0}.fb-pick__into{flex:0 0 auto;padding:0 7px;border:0;border-radius:4px;background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:13px;cursor:pointer}.fb-pick__into:hover{background:var(--fb-surface-alt, #f8f9fb);color:var(--fb-accent, #2f6feb)}.fb-pick__name{font-size:12px}.fb-pick__meta{font-size:10px;color:var(--fb-text-muted, #667085)}.fb-pick__group{margin:2px 4px 4px;font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--fb-text-muted, #667085)}.fb-pick__empty{margin:4px;font-size:11px;color:var(--fb-text-muted, #667085)}.fb-pick__close{width:100%;margin-top:4px;padding:4px;border:0;border-top:1px solid var(--fb-border-subtle, #e6e9ee);background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:11px;cursor:pointer}\n"] }]
3677
4124
  }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], valueChange: [{ type: i0.Output, args: ["valueChange"] }] } });
3678
4125
 
3679
4126
  /**
@@ -3689,9 +4136,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImpo
3689
4136
  * porta il flow che crea il record e non si cambia dopo, un identificativo generato dal database
3690
4137
  * e' l'opposto (§5.7).
3691
4138
  *
3692
- * La scrittura libera resta possibile perche' i percorsi di relazione (`Cliente.Citta`) non
3693
- * sono enumerabili: su un valore col punto non si accusa nessuno, si avvisa che la
3694
- * validazione non lo verifica.
4139
+ * I **percorsi di relazione** (`Cliente.Citta`) non sono piu' un buco nero: la validazione li
4140
+ * segue un segmento alla volta (§4.4), e quindi li segue anche il picker `core/path-navigation`
4141
+ * naviga la catena, l'elenco propone **il livello in cui si sta scrivendo** e l'ultimo segmento si
4142
+ * verifica con l'`usage` richiesto. Dove la catena si interrompe — una relazione che non dichiara
4143
+ * l'oggetto referenziato, un oggetto non descritto, una collection — l'avviso e'
4144
+ * `PATH_NOT_VERIFIABLE`, che vuol dire "non lo so" e non "sbagliato": la scrittura libera resta.
3695
4145
  */
3696
4146
  class FieldPickerComponent {
3697
4147
  catalog = inject(FlowCatalogStore);
@@ -3703,29 +4153,20 @@ class FieldPickerComponent {
3703
4153
  placeholder = input('Scrivi o scegli un campo', ...(ngDevMode ? [{ debugName: "placeholder" }] : []));
3704
4154
  disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
3705
4155
  valueChange = output();
3706
- fields = signal([], ...(ngDevMode ? [{ debugName: "fields" }] : []));
3707
- loadFailed = signal(false, ...(ngDevMode ? [{ debugName: "loadFailed" }] : []));
3708
4156
  isOpen = signal(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : []));
3709
4157
  /** Il testo digitato mentre il pannello e' aperto: filtra l'elenco. */
3710
4158
  query = signal(null, ...(ngDevMode ? [{ debugName: "query" }] : []));
4159
+ /**
4160
+ * La navigazione del percorso corrente. Il testo digitato conta quanto il valore: mentre si
4161
+ * scrive `Cliente.Cit` l'elenco deve già essere quello dei campi di `Cliente`.
4162
+ */
4163
+ navigation = navigatePath(this.catalog, () => ({
4164
+ root: this.object() ? { kind: 'object', name: this.object() } : null,
4165
+ path: this.query() ?? this.value() ?? '',
4166
+ usage: this.usage(),
4167
+ }));
4168
+ resolution = this.navigation.resolution;
3711
4169
  constructor() {
3712
- effect(() => {
3713
- const object = this.object();
3714
- const usage = this.usage();
3715
- this.loadFailed.set(false);
3716
- if (!object) {
3717
- this.fields.set([]);
3718
- return;
3719
- }
3720
- void this.catalog
3721
- .listFields(object, usage)
3722
- .then((list) => this.fields.set(list ?? []))
3723
- // Catalogo non disponibile: il campo resta scrivibile a mano, non bloccato.
3724
- .catch(() => {
3725
- this.fields.set([]);
3726
- this.loadFailed.set(true);
3727
- });
3728
- });
3729
4170
  // Vedi ObjectPickerComponent: il filtro sopravvive alla propria scrittura, non a un
3730
4171
  // valore che arriva da fuori (un'altra riga della lista, un altro elemento).
3731
4172
  effect(() => {
@@ -3736,47 +4177,70 @@ class FieldPickerComponent {
3736
4177
  });
3737
4178
  }
3738
4179
  text = computed(() => this.query() ?? this.value() ?? '', ...(ngDevMode ? [{ debugName: "text" }] : []));
4180
+ /** La tappa in cui si sta scrivendo: da qui vengono le proposte, non dalla radice. */
4181
+ tail = computed(() => this.resolution()?.tail, ...(ngDevMode ? [{ debugName: "tail" }] : []));
4182
+ /** Cio' che si premette al nome scelto: `''` sul primo segmento, `Cliente.` sul secondo. */
4183
+ prefix = computed(() => {
4184
+ const prefix = this.tail()?.prefix ?? '';
4185
+ return prefix ? `${prefix}.` : '';
4186
+ }, ...(ngDevMode ? [{ debugName: "prefix" }] : []));
3739
4187
  options = computed(() => {
3740
- const needle = (this.query() ?? '').trim().toLowerCase();
3741
- const all = this.fields();
4188
+ const tail = this.tail();
4189
+ if (!tail) {
4190
+ return [];
4191
+ }
4192
+ const needle = tail.segment.trim().toLowerCase();
3742
4193
  if (!needle) {
3743
- return all;
4194
+ return tail.level.entries;
3744
4195
  }
3745
- return all.filter((field) => `${field.name} ${field.label ?? ''}`.toLowerCase().includes(needle));
4196
+ return tail.level.entries.filter((entry) => `${entry.name} ${entry.label ?? ''}`.toLowerCase().includes(needle));
3746
4197
  }, ...(ngDevMode ? [{ debugName: "options" }] : []));
3747
- hasCatalog = computed(() => this.fields().length > 0, ...(ngDevMode ? [{ debugName: "hasCatalog" }] : []));
3748
- /** `true` se il valore e' un percorso di relazione: non enumerabile, non verificato (§4.4). */
3749
- isRelationPath = computed(() => (this.value() ?? '').includes('.'), ...(ngDevMode ? [{ debugName: "isRelationPath" }] : []));
3750
- isUnknown = computed(() => {
3751
- const value = (this.value() ?? '').trim();
3752
- if (!value || !this.hasCatalog() || this.isRelationPath()) {
3753
- return false;
3754
- }
3755
- return !this.fields().some((field) => field.name === value);
3756
- }, ...(ngDevMode ? [{ debugName: "isUnknown" }] : []));
3757
- /** L'avviso dipende dall'uso: un campo puo' esistere e non essere filtrabile. */
4198
+ /** `true` se la tappa in cui si scrive ha un elenco **autorevole** (§4.7.1). */
4199
+ hasCatalog = computed(() => this.tail()?.level.status === 'declared', ...(ngDevMode ? [{ debugName: "hasCatalog" }] : []));
4200
+ /** L'etichetta della tappa: serve nell'intestazione dell'elenco quando non e' la radice. */
4201
+ levelLabel = computed(() => this.tail()?.level.container.name ?? '', ...(ngDevMode ? [{ debugName: "levelLabel" }] : []));
4202
+ /** `true` se il valore e' un percorso navigato: cambia il testo dei messaggi, non la severita'. */
4203
+ isPath = computed(() => (this.resolution()?.segments.length ?? 1) > 1, ...(ngDevMode ? [{ debugName: "isPath" }] : []));
4204
+ isUnknown = computed(() => this.resolution()?.status === 'unknown', ...(ngDevMode ? [{ debugName: "isUnknown" }] : []));
4205
+ /** §4.4 — la catena si e' interrotta: avviso, non errore. Il percorso puo' essere giusto. */
4206
+ isNotVerifiable = computed(() => this.resolution()?.status === 'unverifiable', ...(ngDevMode ? [{ debugName: "isNotVerifiable" }] : []));
4207
+ /**
4208
+ * L'avviso dipende dall'uso un campo puo' esistere e non essere filtrabile — e dalla **tappa**:
4209
+ * su `Cliente.Citta` il campo che non esiste e' di `Cliente`, non dell'oggetto di partenza.
4210
+ */
3758
4211
  unknownMessage = computed(() => {
4212
+ const resolution = this.resolution();
4213
+ const segment = resolution?.stoppedAt?.segment ?? '';
4214
+ const container = this.levelLabel();
4215
+ const names = resolution ? pathAvailableNames(resolution).join(', ') : '';
4216
+ const what = `«${segment}»`;
4217
+ const where = this.isPath() ? ` di ${container}` : '';
4218
+ const suffix = names ? ` Disponibili: ${names}.` : '';
3759
4219
  switch (this.usage()) {
3760
4220
  case 'filterable':
3761
- return 'Questo campo non e’ fra quelli filtrabili dell’oggetto.';
4221
+ return `${what} non e’ fra i campi filtrabili${where}.${suffix}`;
3762
4222
  case 'sortable':
3763
- return 'Questo campo non e’ fra quelli su cui si puo’ ordinare.';
4223
+ return `${what} non e’ fra i campi su cui si puo’ ordinare${where}.${suffix}`;
3764
4224
  case 'updateable':
3765
- return 'Questo campo non e’ fra quelli aggiornabili dell’oggetto.';
4225
+ return `${what} non e’ fra i campi aggiornabili${where}.${suffix}`;
3766
4226
  case 'createable':
3767
4227
  // Non e' lo stesso insieme di `updateable`: la colonna di una chiave naturale si scrive
3768
4228
  // creando il record e non si cambia dopo (§5.7).
3769
- return 'Questo campo non e’ fra quelli valorizzabili alla creazione.';
4229
+ return `${what} non e’ fra i campi valorizzabili alla creazione${where}.${suffix}`;
3770
4230
  default:
3771
- return 'Questo campo non e’ fra quelli dell’oggetto.';
4231
+ return `${what} non e’ un campo${where || ' dell’oggetto'}.${suffix}`;
3772
4232
  }
3773
4233
  }, ...(ngDevMode ? [{ debugName: "unknownMessage" }] : []));
4234
+ notVerifiableMessage = computed(() => {
4235
+ const resolution = this.resolution();
4236
+ return resolution ? pathNotVerifiableMessage(resolution) : '';
4237
+ }, ...(ngDevMode ? [{ debugName: "notVerifiableMessage" }] : []));
3774
4238
  describeValue = computed(() => {
3775
- const value = (this.value() ?? '').trim();
3776
- const found = this.fields().find((field) => field.name === value);
3777
- return found ? this.describe(found) || null : null;
4239
+ const last = this.resolution()?.last;
4240
+ return last ? describePathEntry(last) || null : null;
3778
4241
  }, ...(ngDevMode ? [{ debugName: "describeValue" }] : []));
3779
- loadErrored = computed(() => this.loadFailed(), ...(ngDevMode ? [{ debugName: "loadErrored" }] : []));
4242
+ /** Catalogo non raggiungibile sulla **radice**: il campo resta scrivibile a mano, non bloccato. */
4243
+ loadErrored = computed(() => this.resolution()?.levels[0]?.status === 'unknown-catalog', ...(ngDevMode ? [{ debugName: "loadErrored" }] : []));
3780
4244
  open() {
3781
4245
  if (this.disabled()) {
3782
4246
  return;
@@ -3795,33 +4259,38 @@ class FieldPickerComponent {
3795
4259
  this.open();
3796
4260
  this.valueChange.emit(text.trim() ? text.trim() : undefined);
3797
4261
  }
3798
- choose(field) {
3799
- this.valueChange.emit(field.name);
4262
+ choose(entry) {
4263
+ this.valueChange.emit(`${this.prefix()}${entry.name}`);
3800
4264
  this.close();
3801
4265
  }
4266
+ /**
4267
+ * Scende dentro una relazione invece di scegliere il campo: il pannello resta aperto e l'elenco
4268
+ * diventa quello dell'oggetto referenziato. È l'equivalente di scrivere il punto, e infatti
4269
+ * scrive il punto: il valore intermedio e' quello che si vedrebbe digitando.
4270
+ */
4271
+ descend(entry) {
4272
+ const path = `${this.prefix()}${entry.name}.`;
4273
+ this.query.set(path);
4274
+ this.valueChange.emit(path);
4275
+ this.open();
4276
+ }
4277
+ /** `true` se da questo campo il percorso continua: e' una relazione con l'oggetto dichiarato. */
4278
+ canDescend(entry) {
4279
+ return !!entry.next;
4280
+ }
3802
4281
  clear() {
3803
4282
  this.valueChange.emit(undefined);
3804
4283
  this.close();
3805
4284
  }
3806
- describe(field) {
3807
- const parts = [];
3808
- if (field.label && field.label !== field.name) {
3809
- parts.push(field.label);
3810
- }
3811
- if (field.dataType) {
3812
- parts.push(field.objectType ? `${field.dataType} · ${field.objectType}` : field.dataType);
3813
- }
3814
- if (field.isRequired) {
3815
- parts.push('obbligatorio');
3816
- }
3817
- return parts.join(' · ');
4285
+ describe(entry) {
4286
+ return describePathEntry(entry);
3818
4287
  }
3819
4288
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.27", ngImport: i0, type: FieldPickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3820
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.27", type: FieldPickerComponent, isStandalone: true, selector: "fb-field-picker", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, object: { classPropertyName: "object", publicName: "object", isSignal: true, isRequired: false, transformFunction: null }, usage: { classPropertyName: "usage", publicName: "usage", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange" }, ngImport: i0, template: "<div class=\"fb-pick\" [class.fb-pick--open]=\"isOpen()\">\r\n <div class=\"fb-pick__control\">\r\n <input\r\n class=\"fb-pick__input\"\r\n type=\"text\"\r\n role=\"combobox\"\r\n autocomplete=\"off\"\r\n [value]=\"text()\"\r\n [placeholder]=\"placeholder()\"\r\n [disabled]=\"disabled()\"\r\n [attr.aria-label]=\"label()\"\r\n [attr.aria-expanded]=\"isOpen()\"\r\n (input)=\"onInput($any($event.target).value)\"\r\n (focus)=\"open()\"\r\n (keydown.escape)=\"close()\"\r\n />\r\n <button\r\n type=\"button\"\r\n class=\"fb-pick__toggle\"\r\n [disabled]=\"disabled()\"\r\n [attr.aria-expanded]=\"isOpen()\"\r\n aria-label=\"Mostra i campi disponibili\"\r\n (click)=\"toggle()\"\r\n >\r\n \u25BE\r\n </button>\r\n @if (value()) {\r\n <button type=\"button\" class=\"fb-pick__clear\" aria-label=\"Svuota\" (click)=\"clear()\">\u00D7</button>\r\n }\r\n </div>\r\n\r\n @if (isRelationPath()) {\r\n <p class=\"fb-pick__hint fb-pick__hint--warn\">\r\n Percorso di relazione: la validazione non lo verifica, un errore si scopre solo eseguendo.\r\n </p>\r\n } @else if (isUnknown()) {\r\n <p class=\"fb-pick__hint fb-pick__hint--warn\">{{ unknownMessage() }}</p>\r\n } @else if (describeValue()) {\r\n <p class=\"fb-pick__hint\">{{ describeValue() }}</p>\r\n } @else if (loadErrored()) {\r\n <p class=\"fb-pick__hint fb-pick__hint--warn\">\r\n Elenco dei campi non disponibile: puoi scrivere il nome a mano.\r\n </p>\r\n }\r\n\r\n @if (isOpen()) {\r\n <div class=\"fb-pick__panel\" role=\"listbox\">\r\n @if (options().length === 0) {\r\n <p class=\"fb-pick__empty\">\r\n @if (!object()) {\r\n Scegli prima un oggetto.\r\n } @else {\r\n {{ hasCatalog() ? 'Nessun campo corrisponde.' : 'Catalogo dei campi non disponibile.' }}\r\n }\r\n </p>\r\n }\r\n @for (field of options(); track field.name) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-pick__option\"\r\n role=\"option\"\r\n [attr.aria-selected]=\"field.name === value()\"\r\n (click)=\"choose(field)\"\r\n >\r\n <span class=\"fb-pick__name\">{{ field.name }}</span>\r\n @if (describe(field)) {\r\n <span class=\"fb-pick__meta\">{{ describe(field) }}</span>\r\n }\r\n </button>\r\n }\r\n <button type=\"button\" class=\"fb-pick__close\" (click)=\"close()\">Chiudi</button>\r\n </div>\r\n }\r\n</div>\r\n", styles: [":host{display:block}.fb-pick{position:relative}.fb-pick__control{display:flex;align-items:stretch;border:1px solid var(--fb-border, #d6dae1);border-radius:6px;background:var(--fb-surface, #fff);overflow:hidden}.fb-pick--open .fb-pick__control{border-color:var(--fb-accent, #2f6feb)}.fb-pick__input{flex:1;min-width:0;padding:5px 7px;border:0;background:transparent;color:var(--fb-text, #1d2939);font:inherit;font-size:12px}.fb-pick__input:focus-visible{outline:none}.fb-pick__input--mono,.fb-pick__name--mono{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.fb-pick__toggle,.fb-pick__clear{flex:0 0 auto;padding:0 7px;border:0;border-left:1px solid var(--fb-border-subtle, #e6e9ee);background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:12px;cursor:pointer}.fb-pick__toggle:hover,.fb-pick__clear:hover{background:var(--fb-surface-alt, #f8f9fb)}.fb-pick__hint{margin:3px 0 0;font-size:10px;line-height:1.35;color:var(--fb-text-subtle, #98a2b3)}.fb-pick__hint--warn{color:var(--fb-warning, #b7791f)}.fb-pick__hint--error{color:var(--fb-error, #c9372c)}.fb-pick__panel{position:absolute;z-index:30;top:calc(100% + 3px);left:0;right:0;max-height:260px;overflow-y:auto;padding:6px;border:1px solid var(--fb-border, #d6dae1);border-radius:6px;background:var(--fb-surface, #fff);box-shadow:0 6px 18px #10182829}.fb-pick__option{display:flex;flex-direction:column;width:100%;padding:4px 6px;border:0;border-radius:4px;background:transparent;color:var(--fb-text, #1d2939);font:inherit;text-align:left;cursor:pointer}.fb-pick__option:hover,.fb-pick__option[aria-selected=true]{background:var(--fb-surface-alt, #f8f9fb)}.fb-pick__name{font-size:12px}.fb-pick__meta{font-size:10px;color:var(--fb-text-muted, #667085)}.fb-pick__group{margin:2px 4px 4px;font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--fb-text-muted, #667085)}.fb-pick__empty{margin:4px;font-size:11px;color:var(--fb-text-muted, #667085)}.fb-pick__close{width:100%;margin-top:4px;padding:4px;border:0;border-top:1px solid var(--fb-border-subtle, #e6e9ee);background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:11px;cursor:pointer}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4289
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.27", type: FieldPickerComponent, isStandalone: true, selector: "fb-field-picker", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, object: { classPropertyName: "object", publicName: "object", isSignal: true, isRequired: false, transformFunction: null }, usage: { classPropertyName: "usage", publicName: "usage", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange" }, ngImport: i0, template: "<div class=\"fb-pick\" [class.fb-pick--open]=\"isOpen()\">\r\n <div class=\"fb-pick__control\">\r\n <input\r\n class=\"fb-pick__input\"\r\n type=\"text\"\r\n role=\"combobox\"\r\n autocomplete=\"off\"\r\n [value]=\"text()\"\r\n [placeholder]=\"placeholder()\"\r\n [disabled]=\"disabled()\"\r\n [attr.aria-label]=\"label()\"\r\n [attr.aria-expanded]=\"isOpen()\"\r\n (input)=\"onInput($any($event.target).value)\"\r\n (focus)=\"open()\"\r\n (keydown.escape)=\"close()\"\r\n />\r\n <button\r\n type=\"button\"\r\n class=\"fb-pick__toggle\"\r\n [disabled]=\"disabled()\"\r\n [attr.aria-expanded]=\"isOpen()\"\r\n aria-label=\"Mostra i campi disponibili\"\r\n (click)=\"toggle()\"\r\n >\r\n \u25BE\r\n </button>\r\n @if (value()) {\r\n <button type=\"button\" class=\"fb-pick__clear\" aria-label=\"Svuota\" (click)=\"clear()\">\u00D7</button>\r\n }\r\n </div>\r\n\r\n @if (isUnknown()) {\r\n <p class=\"fb-pick__hint fb-pick__hint--warn\">{{ unknownMessage() }}</p>\r\n } @else if (isNotVerifiable()) {\r\n <p class=\"fb-pick__hint fb-pick__hint--warn\">{{ notVerifiableMessage() }}</p>\r\n } @else if (describeValue()) {\r\n <p class=\"fb-pick__hint\">{{ describeValue() }}</p>\r\n } @else if (loadErrored()) {\r\n <p class=\"fb-pick__hint fb-pick__hint--warn\">\r\n Elenco dei campi non disponibile: puoi scrivere il nome a mano.\r\n </p>\r\n }\r\n\r\n @if (isOpen()) {\r\n <div class=\"fb-pick__panel\" role=\"listbox\">\r\n @if (prefix()) {\r\n <p class=\"fb-pick__group\">Campi di {{ levelLabel() }}</p>\r\n }\r\n @if (options().length === 0) {\r\n <p class=\"fb-pick__empty\">\r\n @if (!object()) {\r\n Scegli prima un oggetto.\r\n } @else {\r\n {{ hasCatalog() ? 'Nessun campo corrisponde.' : 'Catalogo dei campi non disponibile.' }}\r\n }\r\n </p>\r\n }\r\n @for (entry of options(); track entry.name) {\r\n <div class=\"fb-pick__row\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-pick__option\"\r\n role=\"option\"\r\n [attr.aria-selected]=\"prefix() + entry.name === value()\"\r\n (click)=\"choose(entry)\"\r\n >\r\n <span class=\"fb-pick__name\">{{ entry.name }}</span>\r\n @if (describe(entry)) {\r\n <span class=\"fb-pick__meta\">{{ describe(entry) }}</span>\r\n }\r\n </button>\r\n @if (canDescend(entry)) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-pick__into\"\r\n [attr.aria-label]=\"'Entra in ' + entry.name\"\r\n title=\"Entra nella relazione\"\r\n (click)=\"descend(entry)\"\r\n >\r\n \u203A\r\n </button>\r\n }\r\n </div>\r\n }\r\n <button type=\"button\" class=\"fb-pick__close\" (click)=\"close()\">Chiudi</button>\r\n </div>\r\n }\r\n</div>\r\n", styles: [":host{display:block}.fb-pick{position:relative}.fb-pick__control{display:flex;align-items:stretch;border:1px solid var(--fb-border, #d6dae1);border-radius:6px;background:var(--fb-surface, #fff);overflow:hidden}.fb-pick--open .fb-pick__control{border-color:var(--fb-accent, #2f6feb)}.fb-pick__input{flex:1;min-width:0;padding:5px 7px;border:0;background:transparent;color:var(--fb-text, #1d2939);font:inherit;font-size:12px}.fb-pick__input:focus-visible{outline:none}.fb-pick__input--mono,.fb-pick__name--mono{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.fb-pick__toggle,.fb-pick__clear{flex:0 0 auto;padding:0 7px;border:0;border-left:1px solid var(--fb-border-subtle, #e6e9ee);background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:12px;cursor:pointer}.fb-pick__toggle:hover,.fb-pick__clear:hover{background:var(--fb-surface-alt, #f8f9fb)}.fb-pick__hint{margin:3px 0 0;font-size:10px;line-height:1.35;color:var(--fb-text-subtle, #98a2b3)}.fb-pick__hint--warn{color:var(--fb-warning, #b7791f)}.fb-pick__hint--error{color:var(--fb-error, #c9372c)}.fb-pick__panel{position:absolute;z-index:30;top:calc(100% + 3px);left:0;right:0;max-height:260px;overflow-y:auto;padding:6px;border:1px solid var(--fb-border, #d6dae1);border-radius:6px;background:var(--fb-surface, #fff);box-shadow:0 6px 18px #10182829}.fb-pick__option{display:flex;flex-direction:column;width:100%;padding:4px 6px;border:0;border-radius:4px;background:transparent;color:var(--fb-text, #1d2939);font:inherit;text-align:left;cursor:pointer}.fb-pick__option:hover,.fb-pick__option[aria-selected=true]{background:var(--fb-surface-alt, #f8f9fb)}.fb-pick__row{display:flex;align-items:stretch;gap:2px}.fb-pick__row .fb-pick__option{flex:1;min-width:0}.fb-pick__into{flex:0 0 auto;padding:0 7px;border:0;border-radius:4px;background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:13px;cursor:pointer}.fb-pick__into:hover{background:var(--fb-surface-alt, #f8f9fb);color:var(--fb-accent, #2f6feb)}.fb-pick__name{font-size:12px}.fb-pick__meta{font-size:10px;color:var(--fb-text-muted, #667085)}.fb-pick__group{margin:2px 4px 4px;font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--fb-text-muted, #667085)}.fb-pick__empty{margin:4px;font-size:11px;color:var(--fb-text-muted, #667085)}.fb-pick__close{width:100%;margin-top:4px;padding:4px;border:0;border-top:1px solid var(--fb-border-subtle, #e6e9ee);background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:11px;cursor:pointer}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3821
4290
  }
3822
4291
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImport: i0, type: FieldPickerComponent, decorators: [{
3823
4292
  type: Component,
3824
- args: [{ selector: 'fb-field-picker', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"fb-pick\" [class.fb-pick--open]=\"isOpen()\">\r\n <div class=\"fb-pick__control\">\r\n <input\r\n class=\"fb-pick__input\"\r\n type=\"text\"\r\n role=\"combobox\"\r\n autocomplete=\"off\"\r\n [value]=\"text()\"\r\n [placeholder]=\"placeholder()\"\r\n [disabled]=\"disabled()\"\r\n [attr.aria-label]=\"label()\"\r\n [attr.aria-expanded]=\"isOpen()\"\r\n (input)=\"onInput($any($event.target).value)\"\r\n (focus)=\"open()\"\r\n (keydown.escape)=\"close()\"\r\n />\r\n <button\r\n type=\"button\"\r\n class=\"fb-pick__toggle\"\r\n [disabled]=\"disabled()\"\r\n [attr.aria-expanded]=\"isOpen()\"\r\n aria-label=\"Mostra i campi disponibili\"\r\n (click)=\"toggle()\"\r\n >\r\n \u25BE\r\n </button>\r\n @if (value()) {\r\n <button type=\"button\" class=\"fb-pick__clear\" aria-label=\"Svuota\" (click)=\"clear()\">\u00D7</button>\r\n }\r\n </div>\r\n\r\n @if (isRelationPath()) {\r\n <p class=\"fb-pick__hint fb-pick__hint--warn\">\r\n Percorso di relazione: la validazione non lo verifica, un errore si scopre solo eseguendo.\r\n </p>\r\n } @else if (isUnknown()) {\r\n <p class=\"fb-pick__hint fb-pick__hint--warn\">{{ unknownMessage() }}</p>\r\n } @else if (describeValue()) {\r\n <p class=\"fb-pick__hint\">{{ describeValue() }}</p>\r\n } @else if (loadErrored()) {\r\n <p class=\"fb-pick__hint fb-pick__hint--warn\">\r\n Elenco dei campi non disponibile: puoi scrivere il nome a mano.\r\n </p>\r\n }\r\n\r\n @if (isOpen()) {\r\n <div class=\"fb-pick__panel\" role=\"listbox\">\r\n @if (options().length === 0) {\r\n <p class=\"fb-pick__empty\">\r\n @if (!object()) {\r\n Scegli prima un oggetto.\r\n } @else {\r\n {{ hasCatalog() ? 'Nessun campo corrisponde.' : 'Catalogo dei campi non disponibile.' }}\r\n }\r\n </p>\r\n }\r\n @for (field of options(); track field.name) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-pick__option\"\r\n role=\"option\"\r\n [attr.aria-selected]=\"field.name === value()\"\r\n (click)=\"choose(field)\"\r\n >\r\n <span class=\"fb-pick__name\">{{ field.name }}</span>\r\n @if (describe(field)) {\r\n <span class=\"fb-pick__meta\">{{ describe(field) }}</span>\r\n }\r\n </button>\r\n }\r\n <button type=\"button\" class=\"fb-pick__close\" (click)=\"close()\">Chiudi</button>\r\n </div>\r\n }\r\n</div>\r\n", styles: [":host{display:block}.fb-pick{position:relative}.fb-pick__control{display:flex;align-items:stretch;border:1px solid var(--fb-border, #d6dae1);border-radius:6px;background:var(--fb-surface, #fff);overflow:hidden}.fb-pick--open .fb-pick__control{border-color:var(--fb-accent, #2f6feb)}.fb-pick__input{flex:1;min-width:0;padding:5px 7px;border:0;background:transparent;color:var(--fb-text, #1d2939);font:inherit;font-size:12px}.fb-pick__input:focus-visible{outline:none}.fb-pick__input--mono,.fb-pick__name--mono{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.fb-pick__toggle,.fb-pick__clear{flex:0 0 auto;padding:0 7px;border:0;border-left:1px solid var(--fb-border-subtle, #e6e9ee);background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:12px;cursor:pointer}.fb-pick__toggle:hover,.fb-pick__clear:hover{background:var(--fb-surface-alt, #f8f9fb)}.fb-pick__hint{margin:3px 0 0;font-size:10px;line-height:1.35;color:var(--fb-text-subtle, #98a2b3)}.fb-pick__hint--warn{color:var(--fb-warning, #b7791f)}.fb-pick__hint--error{color:var(--fb-error, #c9372c)}.fb-pick__panel{position:absolute;z-index:30;top:calc(100% + 3px);left:0;right:0;max-height:260px;overflow-y:auto;padding:6px;border:1px solid var(--fb-border, #d6dae1);border-radius:6px;background:var(--fb-surface, #fff);box-shadow:0 6px 18px #10182829}.fb-pick__option{display:flex;flex-direction:column;width:100%;padding:4px 6px;border:0;border-radius:4px;background:transparent;color:var(--fb-text, #1d2939);font:inherit;text-align:left;cursor:pointer}.fb-pick__option:hover,.fb-pick__option[aria-selected=true]{background:var(--fb-surface-alt, #f8f9fb)}.fb-pick__name{font-size:12px}.fb-pick__meta{font-size:10px;color:var(--fb-text-muted, #667085)}.fb-pick__group{margin:2px 4px 4px;font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--fb-text-muted, #667085)}.fb-pick__empty{margin:4px;font-size:11px;color:var(--fb-text-muted, #667085)}.fb-pick__close{width:100%;margin-top:4px;padding:4px;border:0;border-top:1px solid var(--fb-border-subtle, #e6e9ee);background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:11px;cursor:pointer}\n"] }]
4293
+ args: [{ selector: 'fb-field-picker', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"fb-pick\" [class.fb-pick--open]=\"isOpen()\">\r\n <div class=\"fb-pick__control\">\r\n <input\r\n class=\"fb-pick__input\"\r\n type=\"text\"\r\n role=\"combobox\"\r\n autocomplete=\"off\"\r\n [value]=\"text()\"\r\n [placeholder]=\"placeholder()\"\r\n [disabled]=\"disabled()\"\r\n [attr.aria-label]=\"label()\"\r\n [attr.aria-expanded]=\"isOpen()\"\r\n (input)=\"onInput($any($event.target).value)\"\r\n (focus)=\"open()\"\r\n (keydown.escape)=\"close()\"\r\n />\r\n <button\r\n type=\"button\"\r\n class=\"fb-pick__toggle\"\r\n [disabled]=\"disabled()\"\r\n [attr.aria-expanded]=\"isOpen()\"\r\n aria-label=\"Mostra i campi disponibili\"\r\n (click)=\"toggle()\"\r\n >\r\n \u25BE\r\n </button>\r\n @if (value()) {\r\n <button type=\"button\" class=\"fb-pick__clear\" aria-label=\"Svuota\" (click)=\"clear()\">\u00D7</button>\r\n }\r\n </div>\r\n\r\n @if (isUnknown()) {\r\n <p class=\"fb-pick__hint fb-pick__hint--warn\">{{ unknownMessage() }}</p>\r\n } @else if (isNotVerifiable()) {\r\n <p class=\"fb-pick__hint fb-pick__hint--warn\">{{ notVerifiableMessage() }}</p>\r\n } @else if (describeValue()) {\r\n <p class=\"fb-pick__hint\">{{ describeValue() }}</p>\r\n } @else if (loadErrored()) {\r\n <p class=\"fb-pick__hint fb-pick__hint--warn\">\r\n Elenco dei campi non disponibile: puoi scrivere il nome a mano.\r\n </p>\r\n }\r\n\r\n @if (isOpen()) {\r\n <div class=\"fb-pick__panel\" role=\"listbox\">\r\n @if (prefix()) {\r\n <p class=\"fb-pick__group\">Campi di {{ levelLabel() }}</p>\r\n }\r\n @if (options().length === 0) {\r\n <p class=\"fb-pick__empty\">\r\n @if (!object()) {\r\n Scegli prima un oggetto.\r\n } @else {\r\n {{ hasCatalog() ? 'Nessun campo corrisponde.' : 'Catalogo dei campi non disponibile.' }}\r\n }\r\n </p>\r\n }\r\n @for (entry of options(); track entry.name) {\r\n <div class=\"fb-pick__row\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-pick__option\"\r\n role=\"option\"\r\n [attr.aria-selected]=\"prefix() + entry.name === value()\"\r\n (click)=\"choose(entry)\"\r\n >\r\n <span class=\"fb-pick__name\">{{ entry.name }}</span>\r\n @if (describe(entry)) {\r\n <span class=\"fb-pick__meta\">{{ describe(entry) }}</span>\r\n }\r\n </button>\r\n @if (canDescend(entry)) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-pick__into\"\r\n [attr.aria-label]=\"'Entra in ' + entry.name\"\r\n title=\"Entra nella relazione\"\r\n (click)=\"descend(entry)\"\r\n >\r\n \u203A\r\n </button>\r\n }\r\n </div>\r\n }\r\n <button type=\"button\" class=\"fb-pick__close\" (click)=\"close()\">Chiudi</button>\r\n </div>\r\n }\r\n</div>\r\n", styles: [":host{display:block}.fb-pick{position:relative}.fb-pick__control{display:flex;align-items:stretch;border:1px solid var(--fb-border, #d6dae1);border-radius:6px;background:var(--fb-surface, #fff);overflow:hidden}.fb-pick--open .fb-pick__control{border-color:var(--fb-accent, #2f6feb)}.fb-pick__input{flex:1;min-width:0;padding:5px 7px;border:0;background:transparent;color:var(--fb-text, #1d2939);font:inherit;font-size:12px}.fb-pick__input:focus-visible{outline:none}.fb-pick__input--mono,.fb-pick__name--mono{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.fb-pick__toggle,.fb-pick__clear{flex:0 0 auto;padding:0 7px;border:0;border-left:1px solid var(--fb-border-subtle, #e6e9ee);background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:12px;cursor:pointer}.fb-pick__toggle:hover,.fb-pick__clear:hover{background:var(--fb-surface-alt, #f8f9fb)}.fb-pick__hint{margin:3px 0 0;font-size:10px;line-height:1.35;color:var(--fb-text-subtle, #98a2b3)}.fb-pick__hint--warn{color:var(--fb-warning, #b7791f)}.fb-pick__hint--error{color:var(--fb-error, #c9372c)}.fb-pick__panel{position:absolute;z-index:30;top:calc(100% + 3px);left:0;right:0;max-height:260px;overflow-y:auto;padding:6px;border:1px solid var(--fb-border, #d6dae1);border-radius:6px;background:var(--fb-surface, #fff);box-shadow:0 6px 18px #10182829}.fb-pick__option{display:flex;flex-direction:column;width:100%;padding:4px 6px;border:0;border-radius:4px;background:transparent;color:var(--fb-text, #1d2939);font:inherit;text-align:left;cursor:pointer}.fb-pick__option:hover,.fb-pick__option[aria-selected=true]{background:var(--fb-surface-alt, #f8f9fb)}.fb-pick__row{display:flex;align-items:stretch;gap:2px}.fb-pick__row .fb-pick__option{flex:1;min-width:0}.fb-pick__into{flex:0 0 auto;padding:0 7px;border:0;border-radius:4px;background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:13px;cursor:pointer}.fb-pick__into:hover{background:var(--fb-surface-alt, #f8f9fb);color:var(--fb-accent, #2f6feb)}.fb-pick__name{font-size:12px}.fb-pick__meta{font-size:10px;color:var(--fb-text-muted, #667085)}.fb-pick__group{margin:2px 4px 4px;font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--fb-text-muted, #667085)}.fb-pick__empty{margin:4px;font-size:11px;color:var(--fb-text-muted, #667085)}.fb-pick__close{width:100%;margin-top:4px;padding:4px;border:0;border-top:1px solid var(--fb-border-subtle, #e6e9ee);background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:11px;cursor:pointer}\n"] }]
3825
4294
  }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], object: [{ type: i0.Input, args: [{ isSignal: true, alias: "object", required: false }] }], usage: [{ type: i0.Input, args: [{ isSignal: true, alias: "usage", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], valueChange: [{ type: i0.Output, args: ["valueChange"] }] } });
3826
4295
 
3827
4296
  /**
@@ -3927,11 +4396,11 @@ class NamePickerComponent {
3927
4396
  return parts.join(' · ');
3928
4397
  }
3929
4398
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.27", ngImport: i0, type: NamePickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3930
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.27", type: NamePickerComponent, isStandalone: true, selector: "fb-name-picker", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, unknownMessage: { classPropertyName: "unknownMessage", publicName: "unknownMessage", isSignal: true, isRequired: false, transformFunction: null }, unknownSeverity: { classPropertyName: "unknownSeverity", publicName: "unknownSeverity", isSignal: true, isRequired: false, transformFunction: null }, emptyMessage: { classPropertyName: "emptyMessage", publicName: "emptyMessage", isSignal: true, isRequired: false, transformFunction: null }, isMono: { classPropertyName: "isMono", publicName: "isMono", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange" }, ngImport: i0, template: "<div class=\"fb-pick\" [class.fb-pick--open]=\"isOpen()\">\r\n <div class=\"fb-pick__control\">\r\n <input\r\n class=\"fb-pick__input\"\r\n [class.fb-pick__input--mono]=\"isMono()\"\r\n type=\"text\"\r\n role=\"combobox\"\r\n autocomplete=\"off\"\r\n [value]=\"text()\"\r\n [placeholder]=\"placeholder()\"\r\n [disabled]=\"disabled()\"\r\n [attr.aria-label]=\"label()\"\r\n [attr.aria-expanded]=\"isOpen()\"\r\n (input)=\"onInput($any($event.target).value)\"\r\n (focus)=\"open()\"\r\n (keydown.escape)=\"close()\"\r\n />\r\n <button\r\n type=\"button\"\r\n class=\"fb-pick__toggle\"\r\n [disabled]=\"disabled()\"\r\n [attr.aria-expanded]=\"isOpen()\"\r\n aria-label=\"Mostra i nomi disponibili\"\r\n (click)=\"toggle()\"\r\n >\r\n \u25BE\r\n </button>\r\n @if (value()) {\r\n <button type=\"button\" class=\"fb-pick__clear\" aria-label=\"Svuota\" (click)=\"clear()\">\u00D7</button>\r\n }\r\n </div>\r\n\r\n @if (isUnknown()) {\r\n <p\r\n class=\"fb-pick__hint\"\r\n [class.fb-pick__hint--warn]=\"unknownSeverity() === 'warn'\"\r\n [class.fb-pick__hint--error]=\"unknownSeverity() === 'error'\"\r\n >\r\n {{ unknownMessage() }}\r\n </p>\r\n } @else if (labelOfValue()) {\r\n <p class=\"fb-pick__hint\">{{ labelOfValue() }}</p>\r\n } @else if (!hasOptions()) {\r\n <p class=\"fb-pick__hint\">{{ emptyMessage() }}</p>\r\n }\r\n\r\n @if (isOpen()) {\r\n <div class=\"fb-pick__panel\" role=\"listbox\">\r\n @if (visibleOptions().length === 0) {\r\n <p class=\"fb-pick__empty\">\r\n {{ hasOptions() ? 'Nessun nome corrisponde.' : 'Nessun candidato da proporre.' }}\r\n </p>\r\n }\r\n @for (option of visibleOptions(); track option.name) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-pick__option\"\r\n role=\"option\"\r\n [attr.aria-selected]=\"option.name === value()\"\r\n (click)=\"choose(option)\"\r\n >\r\n <span class=\"fb-pick__name\" [class.fb-pick__name--mono]=\"isMono()\">{{ option.name }}</span>\r\n @if (describe(option)) {\r\n <span class=\"fb-pick__meta\">{{ describe(option) }}</span>\r\n }\r\n </button>\r\n }\r\n <button type=\"button\" class=\"fb-pick__close\" (click)=\"close()\">Chiudi</button>\r\n </div>\r\n }\r\n</div>\r\n", styles: [":host{display:block}.fb-pick{position:relative}.fb-pick__control{display:flex;align-items:stretch;border:1px solid var(--fb-border, #d6dae1);border-radius:6px;background:var(--fb-surface, #fff);overflow:hidden}.fb-pick--open .fb-pick__control{border-color:var(--fb-accent, #2f6feb)}.fb-pick__input{flex:1;min-width:0;padding:5px 7px;border:0;background:transparent;color:var(--fb-text, #1d2939);font:inherit;font-size:12px}.fb-pick__input:focus-visible{outline:none}.fb-pick__input--mono,.fb-pick__name--mono{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.fb-pick__toggle,.fb-pick__clear{flex:0 0 auto;padding:0 7px;border:0;border-left:1px solid var(--fb-border-subtle, #e6e9ee);background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:12px;cursor:pointer}.fb-pick__toggle:hover,.fb-pick__clear:hover{background:var(--fb-surface-alt, #f8f9fb)}.fb-pick__hint{margin:3px 0 0;font-size:10px;line-height:1.35;color:var(--fb-text-subtle, #98a2b3)}.fb-pick__hint--warn{color:var(--fb-warning, #b7791f)}.fb-pick__hint--error{color:var(--fb-error, #c9372c)}.fb-pick__panel{position:absolute;z-index:30;top:calc(100% + 3px);left:0;right:0;max-height:260px;overflow-y:auto;padding:6px;border:1px solid var(--fb-border, #d6dae1);border-radius:6px;background:var(--fb-surface, #fff);box-shadow:0 6px 18px #10182829}.fb-pick__option{display:flex;flex-direction:column;width:100%;padding:4px 6px;border:0;border-radius:4px;background:transparent;color:var(--fb-text, #1d2939);font:inherit;text-align:left;cursor:pointer}.fb-pick__option:hover,.fb-pick__option[aria-selected=true]{background:var(--fb-surface-alt, #f8f9fb)}.fb-pick__name{font-size:12px}.fb-pick__meta{font-size:10px;color:var(--fb-text-muted, #667085)}.fb-pick__group{margin:2px 4px 4px;font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--fb-text-muted, #667085)}.fb-pick__empty{margin:4px;font-size:11px;color:var(--fb-text-muted, #667085)}.fb-pick__close{width:100%;margin-top:4px;padding:4px;border:0;border-top:1px solid var(--fb-border-subtle, #e6e9ee);background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:11px;cursor:pointer}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4399
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.27", type: NamePickerComponent, isStandalone: true, selector: "fb-name-picker", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, unknownMessage: { classPropertyName: "unknownMessage", publicName: "unknownMessage", isSignal: true, isRequired: false, transformFunction: null }, unknownSeverity: { classPropertyName: "unknownSeverity", publicName: "unknownSeverity", isSignal: true, isRequired: false, transformFunction: null }, emptyMessage: { classPropertyName: "emptyMessage", publicName: "emptyMessage", isSignal: true, isRequired: false, transformFunction: null }, isMono: { classPropertyName: "isMono", publicName: "isMono", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange" }, ngImport: i0, template: "<div class=\"fb-pick\" [class.fb-pick--open]=\"isOpen()\">\r\n <div class=\"fb-pick__control\">\r\n <input\r\n class=\"fb-pick__input\"\r\n [class.fb-pick__input--mono]=\"isMono()\"\r\n type=\"text\"\r\n role=\"combobox\"\r\n autocomplete=\"off\"\r\n [value]=\"text()\"\r\n [placeholder]=\"placeholder()\"\r\n [disabled]=\"disabled()\"\r\n [attr.aria-label]=\"label()\"\r\n [attr.aria-expanded]=\"isOpen()\"\r\n (input)=\"onInput($any($event.target).value)\"\r\n (focus)=\"open()\"\r\n (keydown.escape)=\"close()\"\r\n />\r\n <button\r\n type=\"button\"\r\n class=\"fb-pick__toggle\"\r\n [disabled]=\"disabled()\"\r\n [attr.aria-expanded]=\"isOpen()\"\r\n aria-label=\"Mostra i nomi disponibili\"\r\n (click)=\"toggle()\"\r\n >\r\n \u25BE\r\n </button>\r\n @if (value()) {\r\n <button type=\"button\" class=\"fb-pick__clear\" aria-label=\"Svuota\" (click)=\"clear()\">\u00D7</button>\r\n }\r\n </div>\r\n\r\n @if (isUnknown()) {\r\n <p\r\n class=\"fb-pick__hint\"\r\n [class.fb-pick__hint--warn]=\"unknownSeverity() === 'warn'\"\r\n [class.fb-pick__hint--error]=\"unknownSeverity() === 'error'\"\r\n >\r\n {{ unknownMessage() }}\r\n </p>\r\n } @else if (labelOfValue()) {\r\n <p class=\"fb-pick__hint\">{{ labelOfValue() }}</p>\r\n } @else if (!hasOptions()) {\r\n <p class=\"fb-pick__hint\">{{ emptyMessage() }}</p>\r\n }\r\n\r\n @if (isOpen()) {\r\n <div class=\"fb-pick__panel\" role=\"listbox\">\r\n @if (visibleOptions().length === 0) {\r\n <p class=\"fb-pick__empty\">\r\n {{ hasOptions() ? 'Nessun nome corrisponde.' : 'Nessun candidato da proporre.' }}\r\n </p>\r\n }\r\n @for (option of visibleOptions(); track option.name) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-pick__option\"\r\n role=\"option\"\r\n [attr.aria-selected]=\"option.name === value()\"\r\n (click)=\"choose(option)\"\r\n >\r\n <span class=\"fb-pick__name\" [class.fb-pick__name--mono]=\"isMono()\">{{ option.name }}</span>\r\n @if (describe(option)) {\r\n <span class=\"fb-pick__meta\">{{ describe(option) }}</span>\r\n }\r\n </button>\r\n }\r\n <button type=\"button\" class=\"fb-pick__close\" (click)=\"close()\">Chiudi</button>\r\n </div>\r\n }\r\n</div>\r\n", styles: [":host{display:block}.fb-pick{position:relative}.fb-pick__control{display:flex;align-items:stretch;border:1px solid var(--fb-border, #d6dae1);border-radius:6px;background:var(--fb-surface, #fff);overflow:hidden}.fb-pick--open .fb-pick__control{border-color:var(--fb-accent, #2f6feb)}.fb-pick__input{flex:1;min-width:0;padding:5px 7px;border:0;background:transparent;color:var(--fb-text, #1d2939);font:inherit;font-size:12px}.fb-pick__input:focus-visible{outline:none}.fb-pick__input--mono,.fb-pick__name--mono{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.fb-pick__toggle,.fb-pick__clear{flex:0 0 auto;padding:0 7px;border:0;border-left:1px solid var(--fb-border-subtle, #e6e9ee);background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:12px;cursor:pointer}.fb-pick__toggle:hover,.fb-pick__clear:hover{background:var(--fb-surface-alt, #f8f9fb)}.fb-pick__hint{margin:3px 0 0;font-size:10px;line-height:1.35;color:var(--fb-text-subtle, #98a2b3)}.fb-pick__hint--warn{color:var(--fb-warning, #b7791f)}.fb-pick__hint--error{color:var(--fb-error, #c9372c)}.fb-pick__panel{position:absolute;z-index:30;top:calc(100% + 3px);left:0;right:0;max-height:260px;overflow-y:auto;padding:6px;border:1px solid var(--fb-border, #d6dae1);border-radius:6px;background:var(--fb-surface, #fff);box-shadow:0 6px 18px #10182829}.fb-pick__option{display:flex;flex-direction:column;width:100%;padding:4px 6px;border:0;border-radius:4px;background:transparent;color:var(--fb-text, #1d2939);font:inherit;text-align:left;cursor:pointer}.fb-pick__option:hover,.fb-pick__option[aria-selected=true]{background:var(--fb-surface-alt, #f8f9fb)}.fb-pick__row{display:flex;align-items:stretch;gap:2px}.fb-pick__row .fb-pick__option{flex:1;min-width:0}.fb-pick__into{flex:0 0 auto;padding:0 7px;border:0;border-radius:4px;background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:13px;cursor:pointer}.fb-pick__into:hover{background:var(--fb-surface-alt, #f8f9fb);color:var(--fb-accent, #2f6feb)}.fb-pick__name{font-size:12px}.fb-pick__meta{font-size:10px;color:var(--fb-text-muted, #667085)}.fb-pick__group{margin:2px 4px 4px;font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--fb-text-muted, #667085)}.fb-pick__empty{margin:4px;font-size:11px;color:var(--fb-text-muted, #667085)}.fb-pick__close{width:100%;margin-top:4px;padding:4px;border:0;border-top:1px solid var(--fb-border-subtle, #e6e9ee);background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:11px;cursor:pointer}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3931
4400
  }
3932
4401
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImport: i0, type: NamePickerComponent, decorators: [{
3933
4402
  type: Component,
3934
- args: [{ selector: 'fb-name-picker', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"fb-pick\" [class.fb-pick--open]=\"isOpen()\">\r\n <div class=\"fb-pick__control\">\r\n <input\r\n class=\"fb-pick__input\"\r\n [class.fb-pick__input--mono]=\"isMono()\"\r\n type=\"text\"\r\n role=\"combobox\"\r\n autocomplete=\"off\"\r\n [value]=\"text()\"\r\n [placeholder]=\"placeholder()\"\r\n [disabled]=\"disabled()\"\r\n [attr.aria-label]=\"label()\"\r\n [attr.aria-expanded]=\"isOpen()\"\r\n (input)=\"onInput($any($event.target).value)\"\r\n (focus)=\"open()\"\r\n (keydown.escape)=\"close()\"\r\n />\r\n <button\r\n type=\"button\"\r\n class=\"fb-pick__toggle\"\r\n [disabled]=\"disabled()\"\r\n [attr.aria-expanded]=\"isOpen()\"\r\n aria-label=\"Mostra i nomi disponibili\"\r\n (click)=\"toggle()\"\r\n >\r\n \u25BE\r\n </button>\r\n @if (value()) {\r\n <button type=\"button\" class=\"fb-pick__clear\" aria-label=\"Svuota\" (click)=\"clear()\">\u00D7</button>\r\n }\r\n </div>\r\n\r\n @if (isUnknown()) {\r\n <p\r\n class=\"fb-pick__hint\"\r\n [class.fb-pick__hint--warn]=\"unknownSeverity() === 'warn'\"\r\n [class.fb-pick__hint--error]=\"unknownSeverity() === 'error'\"\r\n >\r\n {{ unknownMessage() }}\r\n </p>\r\n } @else if (labelOfValue()) {\r\n <p class=\"fb-pick__hint\">{{ labelOfValue() }}</p>\r\n } @else if (!hasOptions()) {\r\n <p class=\"fb-pick__hint\">{{ emptyMessage() }}</p>\r\n }\r\n\r\n @if (isOpen()) {\r\n <div class=\"fb-pick__panel\" role=\"listbox\">\r\n @if (visibleOptions().length === 0) {\r\n <p class=\"fb-pick__empty\">\r\n {{ hasOptions() ? 'Nessun nome corrisponde.' : 'Nessun candidato da proporre.' }}\r\n </p>\r\n }\r\n @for (option of visibleOptions(); track option.name) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-pick__option\"\r\n role=\"option\"\r\n [attr.aria-selected]=\"option.name === value()\"\r\n (click)=\"choose(option)\"\r\n >\r\n <span class=\"fb-pick__name\" [class.fb-pick__name--mono]=\"isMono()\">{{ option.name }}</span>\r\n @if (describe(option)) {\r\n <span class=\"fb-pick__meta\">{{ describe(option) }}</span>\r\n }\r\n </button>\r\n }\r\n <button type=\"button\" class=\"fb-pick__close\" (click)=\"close()\">Chiudi</button>\r\n </div>\r\n }\r\n</div>\r\n", styles: [":host{display:block}.fb-pick{position:relative}.fb-pick__control{display:flex;align-items:stretch;border:1px solid var(--fb-border, #d6dae1);border-radius:6px;background:var(--fb-surface, #fff);overflow:hidden}.fb-pick--open .fb-pick__control{border-color:var(--fb-accent, #2f6feb)}.fb-pick__input{flex:1;min-width:0;padding:5px 7px;border:0;background:transparent;color:var(--fb-text, #1d2939);font:inherit;font-size:12px}.fb-pick__input:focus-visible{outline:none}.fb-pick__input--mono,.fb-pick__name--mono{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.fb-pick__toggle,.fb-pick__clear{flex:0 0 auto;padding:0 7px;border:0;border-left:1px solid var(--fb-border-subtle, #e6e9ee);background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:12px;cursor:pointer}.fb-pick__toggle:hover,.fb-pick__clear:hover{background:var(--fb-surface-alt, #f8f9fb)}.fb-pick__hint{margin:3px 0 0;font-size:10px;line-height:1.35;color:var(--fb-text-subtle, #98a2b3)}.fb-pick__hint--warn{color:var(--fb-warning, #b7791f)}.fb-pick__hint--error{color:var(--fb-error, #c9372c)}.fb-pick__panel{position:absolute;z-index:30;top:calc(100% + 3px);left:0;right:0;max-height:260px;overflow-y:auto;padding:6px;border:1px solid var(--fb-border, #d6dae1);border-radius:6px;background:var(--fb-surface, #fff);box-shadow:0 6px 18px #10182829}.fb-pick__option{display:flex;flex-direction:column;width:100%;padding:4px 6px;border:0;border-radius:4px;background:transparent;color:var(--fb-text, #1d2939);font:inherit;text-align:left;cursor:pointer}.fb-pick__option:hover,.fb-pick__option[aria-selected=true]{background:var(--fb-surface-alt, #f8f9fb)}.fb-pick__name{font-size:12px}.fb-pick__meta{font-size:10px;color:var(--fb-text-muted, #667085)}.fb-pick__group{margin:2px 4px 4px;font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--fb-text-muted, #667085)}.fb-pick__empty{margin:4px;font-size:11px;color:var(--fb-text-muted, #667085)}.fb-pick__close{width:100%;margin-top:4px;padding:4px;border:0;border-top:1px solid var(--fb-border-subtle, #e6e9ee);background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:11px;cursor:pointer}\n"] }]
4403
+ args: [{ selector: 'fb-name-picker', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"fb-pick\" [class.fb-pick--open]=\"isOpen()\">\r\n <div class=\"fb-pick__control\">\r\n <input\r\n class=\"fb-pick__input\"\r\n [class.fb-pick__input--mono]=\"isMono()\"\r\n type=\"text\"\r\n role=\"combobox\"\r\n autocomplete=\"off\"\r\n [value]=\"text()\"\r\n [placeholder]=\"placeholder()\"\r\n [disabled]=\"disabled()\"\r\n [attr.aria-label]=\"label()\"\r\n [attr.aria-expanded]=\"isOpen()\"\r\n (input)=\"onInput($any($event.target).value)\"\r\n (focus)=\"open()\"\r\n (keydown.escape)=\"close()\"\r\n />\r\n <button\r\n type=\"button\"\r\n class=\"fb-pick__toggle\"\r\n [disabled]=\"disabled()\"\r\n [attr.aria-expanded]=\"isOpen()\"\r\n aria-label=\"Mostra i nomi disponibili\"\r\n (click)=\"toggle()\"\r\n >\r\n \u25BE\r\n </button>\r\n @if (value()) {\r\n <button type=\"button\" class=\"fb-pick__clear\" aria-label=\"Svuota\" (click)=\"clear()\">\u00D7</button>\r\n }\r\n </div>\r\n\r\n @if (isUnknown()) {\r\n <p\r\n class=\"fb-pick__hint\"\r\n [class.fb-pick__hint--warn]=\"unknownSeverity() === 'warn'\"\r\n [class.fb-pick__hint--error]=\"unknownSeverity() === 'error'\"\r\n >\r\n {{ unknownMessage() }}\r\n </p>\r\n } @else if (labelOfValue()) {\r\n <p class=\"fb-pick__hint\">{{ labelOfValue() }}</p>\r\n } @else if (!hasOptions()) {\r\n <p class=\"fb-pick__hint\">{{ emptyMessage() }}</p>\r\n }\r\n\r\n @if (isOpen()) {\r\n <div class=\"fb-pick__panel\" role=\"listbox\">\r\n @if (visibleOptions().length === 0) {\r\n <p class=\"fb-pick__empty\">\r\n {{ hasOptions() ? 'Nessun nome corrisponde.' : 'Nessun candidato da proporre.' }}\r\n </p>\r\n }\r\n @for (option of visibleOptions(); track option.name) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-pick__option\"\r\n role=\"option\"\r\n [attr.aria-selected]=\"option.name === value()\"\r\n (click)=\"choose(option)\"\r\n >\r\n <span class=\"fb-pick__name\" [class.fb-pick__name--mono]=\"isMono()\">{{ option.name }}</span>\r\n @if (describe(option)) {\r\n <span class=\"fb-pick__meta\">{{ describe(option) }}</span>\r\n }\r\n </button>\r\n }\r\n <button type=\"button\" class=\"fb-pick__close\" (click)=\"close()\">Chiudi</button>\r\n </div>\r\n }\r\n</div>\r\n", styles: [":host{display:block}.fb-pick{position:relative}.fb-pick__control{display:flex;align-items:stretch;border:1px solid var(--fb-border, #d6dae1);border-radius:6px;background:var(--fb-surface, #fff);overflow:hidden}.fb-pick--open .fb-pick__control{border-color:var(--fb-accent, #2f6feb)}.fb-pick__input{flex:1;min-width:0;padding:5px 7px;border:0;background:transparent;color:var(--fb-text, #1d2939);font:inherit;font-size:12px}.fb-pick__input:focus-visible{outline:none}.fb-pick__input--mono,.fb-pick__name--mono{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.fb-pick__toggle,.fb-pick__clear{flex:0 0 auto;padding:0 7px;border:0;border-left:1px solid var(--fb-border-subtle, #e6e9ee);background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:12px;cursor:pointer}.fb-pick__toggle:hover,.fb-pick__clear:hover{background:var(--fb-surface-alt, #f8f9fb)}.fb-pick__hint{margin:3px 0 0;font-size:10px;line-height:1.35;color:var(--fb-text-subtle, #98a2b3)}.fb-pick__hint--warn{color:var(--fb-warning, #b7791f)}.fb-pick__hint--error{color:var(--fb-error, #c9372c)}.fb-pick__panel{position:absolute;z-index:30;top:calc(100% + 3px);left:0;right:0;max-height:260px;overflow-y:auto;padding:6px;border:1px solid var(--fb-border, #d6dae1);border-radius:6px;background:var(--fb-surface, #fff);box-shadow:0 6px 18px #10182829}.fb-pick__option{display:flex;flex-direction:column;width:100%;padding:4px 6px;border:0;border-radius:4px;background:transparent;color:var(--fb-text, #1d2939);font:inherit;text-align:left;cursor:pointer}.fb-pick__option:hover,.fb-pick__option[aria-selected=true]{background:var(--fb-surface-alt, #f8f9fb)}.fb-pick__row{display:flex;align-items:stretch;gap:2px}.fb-pick__row .fb-pick__option{flex:1;min-width:0}.fb-pick__into{flex:0 0 auto;padding:0 7px;border:0;border-radius:4px;background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:13px;cursor:pointer}.fb-pick__into:hover{background:var(--fb-surface-alt, #f8f9fb);color:var(--fb-accent, #2f6feb)}.fb-pick__name{font-size:12px}.fb-pick__meta{font-size:10px;color:var(--fb-text-muted, #667085)}.fb-pick__group{margin:2px 4px 4px;font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--fb-text-muted, #667085)}.fb-pick__empty{margin:4px;font-size:11px;color:var(--fb-text-muted, #667085)}.fb-pick__close{width:100%;margin-top:4px;padding:4px;border:0;border-top:1px solid var(--fb-border-subtle, #e6e9ee);background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:11px;cursor:pointer}\n"] }]
3935
4404
  }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], unknownMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "unknownMessage", required: false }] }], unknownSeverity: [{ type: i0.Input, args: [{ isSignal: true, alias: "unknownSeverity", required: false }] }], emptyMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyMessage", required: false }] }], isMono: [{ type: i0.Input, args: [{ isSignal: true, alias: "isMono", required: false }] }], valueChange: [{ type: i0.Output, args: ["valueChange"] }] } });
3936
4405
 
3937
4406
  /**
@@ -4007,7 +4476,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImpo
4007
4476
  }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], valueChange: [{ type: i0.Output, args: ["valueChange"] }] } });
4008
4477
 
4009
4478
  /**
4010
- * Selettore di **membro** di una classe — FRONTEND.md §4.7.
4479
+ * Selettore di **membro** di una classe — FRONTEND.md §4.7, §4.7.1.
4011
4480
  *
4012
4481
  * È l'analogo di {@link FieldPickerComponent} per le `Structure`: i membri arrivano dalla
4013
4482
  * descrizione della classe (`GET /catalog/structures/{className}/members`, che restituisce
@@ -4017,10 +4486,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImpo
4017
4486
  * quindi con `usage="writable"` non compare nemmeno nell'elenco.
4018
4487
  *
4019
4488
  * Un membro che non esiste e' `STRUCTURE_MEMBER_UNKNOWN`, e il messaggio elenca i disponibili
4020
- * invece di dire soltanto che il nome e' sbagliato. Dei percorsi annidati
4021
- * (`Richiesta.Cliente.Nome`) viene verificato il **primo** segmento, come per i percorsi di
4022
- * relazione: oltre quello nessuno lo sa, ne' l'editor ne' il backend — la risposta e' piatta e
4023
- * i membri della classe annidata sono una seconda chiamata (§4.7.1).
4489
+ * invece di dire soltanto che il nome e' sbagliato.
4490
+ *
4491
+ * I percorsi **annidati** (`Richiesta.Cliente.Nome`) sono verificati fino in fondo: la risposta
4492
+ * resta piatta, ma la catena si segue una chiamata alla volta — `objectType` di un membro
4493
+ * `Structure` porta la classe annidata, quello di un membro `Object` porta un'**entita' dello
4494
+ * schema**, e da lì si continua con i campi (§4.7.1). Lo fa `core/path-navigation`, lo stesso
4495
+ * codice del field picker, perche' l'unica differenza fra i due mondi e' la primitiva che descrive
4496
+ * la tappa. Dove la catena si interrompe l'avviso e' `PATH_NOT_VERIFIABLE`: un "non lo so", che non
4497
+ * impedisce di scrivere.
4024
4498
  *
4025
4499
  * La risposta ha **tre** esiti e qui si vedono tutti e tre: elenco autorevole, classe valida coi
4026
4500
  * membri non dichiarati (si digita, senza accuse) e classe non registrata (404). Confonderli
@@ -4037,26 +4511,19 @@ class StructureMemberPickerComponent {
4037
4511
  placeholder = input('Scrivi o scegli un membro', ...(ngDevMode ? [{ debugName: "placeholder" }] : []));
4038
4512
  disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
4039
4513
  valueChange = output();
4040
- /**
4041
- * §4.7.1 — non basta l'elenco: serve l'**esito**. Elenco vuoto perche' i membri non sono
4042
- * dichiarati (si digita a mano) e classe non registrata (non c'e' niente da digitare) hanno
4043
- * la stessa lista e messaggi opposti.
4044
- */
4045
- result = signal({ status: 'unknown-catalog', members: [] }, ...(ngDevMode ? [{ debugName: "result" }] : []));
4046
- members = computed(() => this.result().members, ...(ngDevMode ? [{ debugName: "members" }] : []));
4047
4514
  isOpen = signal(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : []));
4048
4515
  /** Il testo digitato mentre il pannello e' aperto: filtra l'elenco. */
4049
4516
  query = signal(null, ...(ngDevMode ? [{ debugName: "query" }] : []));
4517
+ /**
4518
+ * La navigazione del percorso corrente, dalla classe in giu'. Il testo digitato conta quanto il
4519
+ * valore: mentre si scrive `Cliente.No` l'elenco deve già essere quello dei campi di `Cliente`.
4520
+ */
4521
+ navigation = navigatePath(this.catalog, () => ({
4522
+ root: this.className() ? { kind: 'structure', name: this.className() } : null,
4523
+ path: this.query() ?? this.value() ?? '',
4524
+ }));
4525
+ resolution = this.navigation.resolution;
4050
4526
  constructor() {
4051
- effect(() => {
4052
- const className = this.className();
4053
- if (!className) {
4054
- this.result.set({ status: 'unknown-catalog', members: [] });
4055
- return;
4056
- }
4057
- // `describeStructure` non rifiuta: gli esiti sono dati, non errori (§4.7.1).
4058
- void this.catalog.describeStructure(className).then((result) => this.result.set(result));
4059
- });
4060
4527
  // Vedi ObjectPickerComponent: il filtro sopravvive alla propria scrittura, non a un valore
4061
4528
  // che arriva da fuori (un'altra riga della lista, un altro elemento).
4062
4529
  effect(() => {
@@ -4067,75 +4534,97 @@ class StructureMemberPickerComponent {
4067
4534
  });
4068
4535
  }
4069
4536
  text = computed(() => this.query() ?? this.value() ?? '', ...(ngDevMode ? [{ debugName: "text" }] : []));
4070
- /** Con `usage="writable"` i membri di sola lettura non si propongono affatto. */
4537
+ /** La tappa in cui si sta scrivendo: la classe di partenza, o quella in cui si e' entrati. */
4538
+ tail = computed(() => this.resolution()?.tail, ...(ngDevMode ? [{ debugName: "tail" }] : []));
4539
+ /** Cio' che si premette al nome scelto: `''` sul primo segmento, `Cliente.` sul secondo. */
4540
+ prefix = computed(() => {
4541
+ const prefix = this.tail()?.prefix ?? '';
4542
+ return prefix ? `${prefix}.` : '';
4543
+ }, ...(ngDevMode ? [{ debugName: "prefix" }] : []));
4544
+ /**
4545
+ * Con `usage="writable"` i segmenti di sola lettura non si propongono affatto — con
4546
+ * un'eccezione: quelli **navigabili** restano, perche' un membro record che non si sostituisce
4547
+ * puo' avere campi aggiornabili, e nasconderlo chiuderebbe la strada a `Richiesta.Cliente.Email`.
4548
+ */
4071
4549
  available = computed(() => {
4072
- const all = this.members();
4550
+ const entries = this.tail()?.level.entries ?? [];
4073
4551
  if (this.usage() !== 'writable') {
4074
- return all;
4552
+ return entries;
4075
4553
  }
4076
- return all.filter((member) => member.isWritable !== false);
4554
+ return entries.filter((entry) => entry.isWritable || !!entry.next);
4077
4555
  }, ...(ngDevMode ? [{ debugName: "available" }] : []));
4078
4556
  options = computed(() => {
4079
- const needle = (this.query() ?? '').trim().toLowerCase();
4557
+ const needle = (this.tail()?.segment ?? '').trim().toLowerCase();
4080
4558
  const all = this.available();
4081
4559
  if (!needle) {
4082
4560
  return all;
4083
4561
  }
4084
- return all.filter((member) => `${member.name} ${member.label ?? ''}`.toLowerCase().includes(needle));
4562
+ return all.filter((entry) => `${entry.name} ${entry.label ?? ''}`.toLowerCase().includes(needle));
4085
4563
  }, ...(ngDevMode ? [{ debugName: "options" }] : []));
4086
4564
  /**
4087
- * Come chiamare la classe: la `label` la porta l'involucro della descrizione, ed e' il motivo
4088
- * per cui l'involucro esiste (§4.7.1). Assente, si mostra il nome della classe.
4565
+ * Come chiamare la tappa: la `label` la porta l'involucro della descrizione, ed e' il motivo per
4566
+ * cui l'involucro esiste (§4.7.1). Assente, si mostra il nome della classe o dell'entita'.
4089
4567
  */
4090
- classLabel = computed(() => this.result().label || this.className() || '', ...(ngDevMode ? [{ debugName: "classLabel" }] : []));
4091
- /** Elenco autorevole: solo qui un membro fuori elenco e' un errore (§4.7.1). */
4092
- hasCatalog = computed(() => this.result().status === 'declared', ...(ngDevMode ? [{ debugName: "hasCatalog" }] : []));
4093
- /** 404: la classe non e' registrata. Il nome del membro non c'e' modo di indovinarlo. */
4094
- isClassUnknown = computed(() => this.result().status === 'unknown-class', ...(ngDevMode ? [{ debugName: "isClassUnknown" }] : []));
4568
+ classLabel = computed(() => pathContainerLabel(this.tail()?.level) || this.className() || '', ...(ngDevMode ? [{ debugName: "classLabel" }] : []));
4569
+ /** `true` se la tappa in cui si scrive ha un elenco **autorevole** (§4.7.1). */
4570
+ hasCatalog = computed(() => this.tail()?.level.status === 'declared', ...(ngDevMode ? [{ debugName: "hasCatalog" }] : []));
4571
+ /** Nell'elenco dei campi di un'entita' il titolo lo dice: non sono membri di una classe. */
4572
+ isObjectLevel = computed(() => this.tail()?.level.container.kind === 'object', ...(ngDevMode ? [{ debugName: "isObjectLevel" }] : []));
4573
+ rootStatus = computed(() => this.resolution()?.levels[0]?.status, ...(ngDevMode ? [{ debugName: "rootStatus" }] : []));
4574
+ /** 404 sulla classe di partenza: il nome del membro non c'e' modo di indovinarlo. */
4575
+ isClassUnknown = computed(() => this.rootStatus() === 'unknown-container', ...(ngDevMode ? [{ debugName: "isClassUnknown" }] : []));
4095
4576
  /** Classe valida, membri non dichiarati: si digita, e non si segnala niente. */
4096
- isUndeclared = computed(() => this.result().status === 'undeclared', ...(ngDevMode ? [{ debugName: "isUndeclared" }] : []));
4097
- /** Il primo segmento: e' l'unico che si puo' verificare (§4.7). */
4098
- firstSegment = computed(() => {
4099
- const value = (this.value() ?? '').trim();
4100
- const dot = value.indexOf('.');
4101
- return dot < 0 ? value : value.slice(0, dot);
4102
- }, ...(ngDevMode ? [{ debugName: "firstSegment" }] : []));
4103
- /** `true` se il valore naviga dentro un membro: oltre il primo segmento non si verifica. */
4104
- isNestedPath = computed(() => (this.value() ?? '').includes('.'), ...(ngDevMode ? [{ debugName: "isNestedPath" }] : []));
4105
- declared = computed(() => {
4106
- const segment = this.firstSegment();
4107
- return segment ? this.members().find((member) => member.name === segment) : undefined;
4108
- }, ...(ngDevMode ? [{ debugName: "declared" }] : []));
4109
- isUnknown = computed(() => {
4110
- if (!this.firstSegment() || !this.hasCatalog()) {
4111
- return false;
4112
- }
4113
- return !this.declared();
4114
- }, ...(ngDevMode ? [{ debugName: "isUnknown" }] : []));
4115
- /** Il messaggio elenca i disponibili: dire solo "non esiste" non aiuta a correggere. */
4577
+ isUndeclared = computed(() => this.rootStatus() === 'undeclared', ...(ngDevMode ? [{ debugName: "isUndeclared" }] : []));
4578
+ /** Catalogo assente per un motivo che non riguarda la classe: "non lo so". */
4579
+ loadErrored = computed(() => this.rootStatus() === 'unknown-catalog' && !!this.className(), ...(ngDevMode ? [{ debugName: "loadErrored" }] : []));
4580
+ isUnknown = computed(() => this.resolution()?.status === 'unknown', ...(ngDevMode ? [{ debugName: "isUnknown" }] : []));
4581
+ /** §4.4, §4.7.1 — la catena si e' interrotta: avviso, non errore. */
4582
+ isNotVerifiable = computed(() => this.resolution()?.status === 'unverifiable' && this.rootStatus() === 'declared', ...(ngDevMode ? [{ debugName: "isNotVerifiable" }] : []));
4583
+ /**
4584
+ * Il messaggio elenca i disponibili: dire solo "non esiste" non aiuta a correggere. Su un
4585
+ * percorso annidato il segmento che manca e' di **quella** tappa, e le parole cambiano con essa
4586
+ * un campo di un'entita' non e' un membro di una classe.
4587
+ */
4116
4588
  unknownMessage = computed(() => {
4117
- const names = this.available()
4118
- .map((member) => member.name)
4119
- .join(', ');
4120
- const prefix = `«${this.firstSegment()}» non e’ un membro di ${this.className()} (STRUCTURE_MEMBER_UNKNOWN).`;
4121
- return names ? `${prefix} Disponibili: ${names}.` : prefix;
4589
+ const resolution = this.resolution();
4590
+ if (!resolution) {
4591
+ return '';
4592
+ }
4593
+ const segment = resolution.stoppedAt?.segment ?? '';
4594
+ const names = pathAvailableNames(resolution).join(', ');
4595
+ const level = resolution.levels[resolution.levels.length - 1];
4596
+ const what = level?.container.kind === 'object'
4597
+ ? `«${segment}» non e’ un campo di ${level.container.name} (FIELD_UNKNOWN).`
4598
+ : `«${segment}» non e’ un membro di ${level?.container.name} (STRUCTURE_MEMBER_UNKNOWN).`;
4599
+ return names ? `${what} Disponibili: ${names}.` : what;
4122
4600
  }, ...(ngDevMode ? [{ debugName: "unknownMessage" }] : []));
4601
+ notVerifiableMessage = computed(() => {
4602
+ const resolution = this.resolution();
4603
+ return resolution ? pathNotVerifiableMessage(resolution) : '';
4604
+ }, ...(ngDevMode ? [{ debugName: "notVerifiableMessage" }] : []));
4123
4605
  /**
4124
- * Un membro che esiste ma non e' scrivibile, scelto dove serve una destinazione: capita su un
4125
- * documento scritto altrove, e va detto qui invece di lasciarlo scoprire dall'attivazione.
4606
+ * Un segmento che esiste ma non e' scrivibile, scelto dove serve una destinazione: capita su un
4607
+ * documento scritto altrove, e va detto qui invece di lasciarlo scoprire dall'attivazione. Su un
4608
+ * campo di record "scrivibile" vuol dire `createable` **oppure** `updateable` (§5.7).
4126
4609
  */
4127
4610
  isNotWritable = computed(() => {
4128
4611
  if (this.usage() !== 'writable') {
4129
4612
  return false;
4130
4613
  }
4131
- return this.declared()?.isWritable === false;
4614
+ const last = this.resolution()?.last;
4615
+ return !!last && !last.isWritable;
4132
4616
  }, ...(ngDevMode ? [{ debugName: "isNotWritable" }] : []));
4617
+ notWritableMessage = computed(() => {
4618
+ const resolution = this.resolution();
4619
+ const level = resolution?.levels[resolution.levels.length - 1];
4620
+ return level?.container.kind === 'object'
4621
+ ? 'Questo campo non si scrive ne’ creando ne’ aggiornando un record: assegnarlo e’ TARGET_NOT_WRITABLE.'
4622
+ : 'Questo membro e’ calcolato: si legge, ma assegnarlo e’ TARGET_NOT_WRITABLE.';
4623
+ }, ...(ngDevMode ? [{ debugName: "notWritableMessage" }] : []));
4133
4624
  describeValue = computed(() => {
4134
- const member = this.declared();
4135
- return member && !this.isNestedPath() ? this.describe(member) || null : null;
4625
+ const last = this.resolution()?.last;
4626
+ return last ? describePathEntry(last, { showWritability: this.usage() === 'writable' }) || null : null;
4136
4627
  }, ...(ngDevMode ? [{ debugName: "describeValue" }] : []));
4137
- /** Catalogo assente per un motivo che non riguarda la classe: "non lo so". */
4138
- loadErrored = computed(() => this.result().status === 'unknown-catalog' && !!this.className(), ...(ngDevMode ? [{ debugName: "loadErrored" }] : []));
4139
4628
  open() {
4140
4629
  if (this.disabled()) {
4141
4630
  return;
@@ -4154,34 +4643,37 @@ class StructureMemberPickerComponent {
4154
4643
  this.open();
4155
4644
  this.valueChange.emit(text.trim() ? text.trim() : undefined);
4156
4645
  }
4157
- choose(member) {
4158
- this.valueChange.emit(member.name);
4646
+ choose(entry) {
4647
+ this.valueChange.emit(`${this.prefix()}${entry.name}`);
4159
4648
  this.close();
4160
4649
  }
4650
+ /**
4651
+ * Scende dentro un membro composto invece di sceglierlo: e' l'equivalente di scrivere il punto, e
4652
+ * infatti scrive il punto. Serve perche' `Richiesta.Cliente` puo' essere la destinazione che si
4653
+ * vuole **oppure** la tappa da cui continuare.
4654
+ */
4655
+ descend(entry) {
4656
+ const path = `${this.prefix()}${entry.name}.`;
4657
+ this.query.set(path);
4658
+ this.valueChange.emit(path);
4659
+ this.open();
4660
+ }
4661
+ canDescend(entry) {
4662
+ return !!entry.next;
4663
+ }
4161
4664
  clear() {
4162
4665
  this.valueChange.emit(undefined);
4163
4666
  this.close();
4164
4667
  }
4165
- describe(member) {
4166
- const parts = [];
4167
- if (member.label && member.label !== member.name) {
4168
- parts.push(member.label);
4169
- }
4170
- if (member.dataType) {
4171
- const type = member.isCollection ? `${member.dataType}[]` : member.dataType;
4172
- parts.push(member.objectType ? `${type} · ${member.objectType}` : type);
4173
- }
4174
- if (member.isWritable === false) {
4175
- parts.push('sola lettura');
4176
- }
4177
- return parts.join(' · ');
4668
+ describe(entry) {
4669
+ return describePathEntry(entry, { showWritability: this.usage() === 'writable' });
4178
4670
  }
4179
4671
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.27", ngImport: i0, type: StructureMemberPickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4180
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.27", type: StructureMemberPickerComponent, isStandalone: true, selector: "fb-structure-member-picker", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, usage: { classPropertyName: "usage", publicName: "usage", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange" }, ngImport: i0, template: "<div class=\"fb-pick\" [class.fb-pick--open]=\"isOpen()\">\r\n <div class=\"fb-pick__control\">\r\n <input\r\n class=\"fb-pick__input\"\r\n type=\"text\"\r\n role=\"combobox\"\r\n autocomplete=\"off\"\r\n [value]=\"text()\"\r\n [placeholder]=\"placeholder()\"\r\n [disabled]=\"disabled()\"\r\n [attr.aria-label]=\"label()\"\r\n [attr.aria-expanded]=\"isOpen()\"\r\n (input)=\"onInput($any($event.target).value)\"\r\n (focus)=\"open()\"\r\n (keydown.escape)=\"close()\"\r\n />\r\n <button\r\n type=\"button\"\r\n class=\"fb-pick__toggle\"\r\n [disabled]=\"disabled()\"\r\n [attr.aria-expanded]=\"isOpen()\"\r\n aria-label=\"Mostra i membri disponibili\"\r\n (click)=\"toggle()\"\r\n >\r\n \u25BE\r\n </button>\r\n @if (value()) {\r\n <button type=\"button\" class=\"fb-pick__clear\" aria-label=\"Svuota\" (click)=\"clear()\">\u00D7</button>\r\n }\r\n </div>\r\n\r\n @if (isClassUnknown()) {\r\n <p class=\"fb-pick__hint fb-pick__hint--error\">\r\n La classe {{ className() }} non e\u2019 registrata (STRUCTURE_TYPE_UNKNOWN): correggi la classe,\r\n non il membro.\r\n </p>\r\n } @else if (isUnknown()) {\r\n <p class=\"fb-pick__hint fb-pick__hint--error\">{{ unknownMessage() }}</p>\r\n } @else if (isNotWritable()) {\r\n <p class=\"fb-pick__hint fb-pick__hint--error\">\r\n Questo membro e\u2019 calcolato: si legge, ma assegnarlo e\u2019 TARGET_NOT_WRITABLE.\r\n </p>\r\n } @else if (isNestedPath()) {\r\n <p class=\"fb-pick__hint fb-pick__hint--warn\">\r\n Percorso annidato: viene verificato solo il primo segmento, il resto si scopre eseguendo.\r\n </p>\r\n } @else if (describeValue()) {\r\n <p class=\"fb-pick__hint\">{{ describeValue() }}</p>\r\n } @else if (isUndeclared()) {\r\n <p class=\"fb-pick__hint fb-pick__hint--warn\">\r\n {{ classLabel() }} non dichiara i suoi membri: scrivi il nome a mano, non e\u2019 verificabile.\r\n </p>\r\n } @else if (loadErrored()) {\r\n <p class=\"fb-pick__hint fb-pick__hint--warn\">\r\n Elenco dei membri non disponibile: puoi scrivere il nome a mano.\r\n </p>\r\n }\r\n\r\n @if (isOpen()) {\r\n <div class=\"fb-pick__panel\" role=\"listbox\">\r\n @if (className()) {\r\n <p class=\"fb-pick__group\">Membri di {{ classLabel() }}</p>\r\n }\r\n @if (options().length === 0) {\r\n <p class=\"fb-pick__empty\">\r\n @if (!className()) {\r\n Scegli prima una classe.\r\n } @else if (hasCatalog()) {\r\n Nessun membro corrisponde.\r\n } @else if (isClassUnknown()) {\r\n Classe non registrata: nessun membro da proporre.\r\n } @else {\r\n Membri non dichiarati: scrivi il nome del membro.\r\n }\r\n </p>\r\n }\r\n @for (member of options(); track member.name) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-pick__option\"\r\n role=\"option\"\r\n [attr.aria-selected]=\"member.name === value()\"\r\n (click)=\"choose(member)\"\r\n >\r\n <span class=\"fb-pick__name\">{{ member.name }}</span>\r\n @if (describe(member)) {\r\n <span class=\"fb-pick__meta\">{{ describe(member) }}</span>\r\n }\r\n </button>\r\n }\r\n <button type=\"button\" class=\"fb-pick__close\" (click)=\"close()\">Chiudi</button>\r\n </div>\r\n }\r\n</div>\r\n", styles: [":host{display:block}.fb-pick{position:relative}.fb-pick__control{display:flex;align-items:stretch;border:1px solid var(--fb-border, #d6dae1);border-radius:6px;background:var(--fb-surface, #fff);overflow:hidden}.fb-pick--open .fb-pick__control{border-color:var(--fb-accent, #2f6feb)}.fb-pick__input{flex:1;min-width:0;padding:5px 7px;border:0;background:transparent;color:var(--fb-text, #1d2939);font:inherit;font-size:12px}.fb-pick__input:focus-visible{outline:none}.fb-pick__input--mono,.fb-pick__name--mono{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.fb-pick__toggle,.fb-pick__clear{flex:0 0 auto;padding:0 7px;border:0;border-left:1px solid var(--fb-border-subtle, #e6e9ee);background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:12px;cursor:pointer}.fb-pick__toggle:hover,.fb-pick__clear:hover{background:var(--fb-surface-alt, #f8f9fb)}.fb-pick__hint{margin:3px 0 0;font-size:10px;line-height:1.35;color:var(--fb-text-subtle, #98a2b3)}.fb-pick__hint--warn{color:var(--fb-warning, #b7791f)}.fb-pick__hint--error{color:var(--fb-error, #c9372c)}.fb-pick__panel{position:absolute;z-index:30;top:calc(100% + 3px);left:0;right:0;max-height:260px;overflow-y:auto;padding:6px;border:1px solid var(--fb-border, #d6dae1);border-radius:6px;background:var(--fb-surface, #fff);box-shadow:0 6px 18px #10182829}.fb-pick__option{display:flex;flex-direction:column;width:100%;padding:4px 6px;border:0;border-radius:4px;background:transparent;color:var(--fb-text, #1d2939);font:inherit;text-align:left;cursor:pointer}.fb-pick__option:hover,.fb-pick__option[aria-selected=true]{background:var(--fb-surface-alt, #f8f9fb)}.fb-pick__name{font-size:12px}.fb-pick__meta{font-size:10px;color:var(--fb-text-muted, #667085)}.fb-pick__group{margin:2px 4px 4px;font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--fb-text-muted, #667085)}.fb-pick__empty{margin:4px;font-size:11px;color:var(--fb-text-muted, #667085)}.fb-pick__close{width:100%;margin-top:4px;padding:4px;border:0;border-top:1px solid var(--fb-border-subtle, #e6e9ee);background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:11px;cursor:pointer}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4672
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.27", type: StructureMemberPickerComponent, isStandalone: true, selector: "fb-structure-member-picker", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, usage: { classPropertyName: "usage", publicName: "usage", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange" }, ngImport: i0, template: "<div class=\"fb-pick\" [class.fb-pick--open]=\"isOpen()\">\r\n <div class=\"fb-pick__control\">\r\n <input\r\n class=\"fb-pick__input\"\r\n type=\"text\"\r\n role=\"combobox\"\r\n autocomplete=\"off\"\r\n [value]=\"text()\"\r\n [placeholder]=\"placeholder()\"\r\n [disabled]=\"disabled()\"\r\n [attr.aria-label]=\"label()\"\r\n [attr.aria-expanded]=\"isOpen()\"\r\n (input)=\"onInput($any($event.target).value)\"\r\n (focus)=\"open()\"\r\n (keydown.escape)=\"close()\"\r\n />\r\n <button\r\n type=\"button\"\r\n class=\"fb-pick__toggle\"\r\n [disabled]=\"disabled()\"\r\n [attr.aria-expanded]=\"isOpen()\"\r\n aria-label=\"Mostra i membri disponibili\"\r\n (click)=\"toggle()\"\r\n >\r\n \u25BE\r\n </button>\r\n @if (value()) {\r\n <button type=\"button\" class=\"fb-pick__clear\" aria-label=\"Svuota\" (click)=\"clear()\">\u00D7</button>\r\n }\r\n </div>\r\n\r\n @if (isClassUnknown()) {\r\n <p class=\"fb-pick__hint fb-pick__hint--error\">\r\n La classe {{ className() }} non e\u2019 registrata (STRUCTURE_TYPE_UNKNOWN): correggi la classe,\r\n non il membro.\r\n </p>\r\n } @else if (isUnknown()) {\r\n <p class=\"fb-pick__hint fb-pick__hint--error\">{{ unknownMessage() }}</p>\r\n } @else if (isNotWritable()) {\r\n <p class=\"fb-pick__hint fb-pick__hint--error\">{{ notWritableMessage() }}</p>\r\n } @else if (isUndeclared()) {\r\n <p class=\"fb-pick__hint fb-pick__hint--warn\">\r\n {{ classLabel() }} non dichiara i suoi membri: scrivi il nome a mano, non e\u2019 verificabile.\r\n </p>\r\n } @else if (isNotVerifiable()) {\r\n <p class=\"fb-pick__hint fb-pick__hint--warn\">{{ notVerifiableMessage() }}</p>\r\n } @else if (describeValue()) {\r\n <p class=\"fb-pick__hint\">{{ describeValue() }}</p>\r\n } @else if (loadErrored()) {\r\n <p class=\"fb-pick__hint fb-pick__hint--warn\">\r\n Elenco dei membri non disponibile: puoi scrivere il nome a mano.\r\n </p>\r\n }\r\n\r\n @if (isOpen()) {\r\n <div class=\"fb-pick__panel\" role=\"listbox\">\r\n @if (className()) {\r\n <p class=\"fb-pick__group\">\r\n {{ isObjectLevel() ? 'Campi di ' : 'Membri di ' }}{{ classLabel() }}\r\n </p>\r\n }\r\n @if (options().length === 0) {\r\n <p class=\"fb-pick__empty\">\r\n @if (!className()) {\r\n Scegli prima una classe.\r\n } @else if (hasCatalog()) {\r\n Nessun membro corrisponde.\r\n } @else if (isClassUnknown()) {\r\n Classe non registrata: nessun membro da proporre.\r\n } @else {\r\n Membri non dichiarati: scrivi il nome del membro.\r\n }\r\n </p>\r\n }\r\n @for (entry of options(); track entry.name) {\r\n <div class=\"fb-pick__row\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-pick__option\"\r\n role=\"option\"\r\n [attr.aria-selected]=\"prefix() + entry.name === value()\"\r\n (click)=\"choose(entry)\"\r\n >\r\n <span class=\"fb-pick__name\">{{ entry.name }}</span>\r\n @if (describe(entry)) {\r\n <span class=\"fb-pick__meta\">{{ describe(entry) }}</span>\r\n }\r\n </button>\r\n @if (canDescend(entry)) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-pick__into\"\r\n [attr.aria-label]=\"'Entra in ' + entry.name\"\r\n title=\"Entra nel membro composto\"\r\n (click)=\"descend(entry)\"\r\n >\r\n \u203A\r\n </button>\r\n }\r\n </div>\r\n }\r\n <button type=\"button\" class=\"fb-pick__close\" (click)=\"close()\">Chiudi</button>\r\n </div>\r\n }\r\n</div>\r\n", styles: [":host{display:block}.fb-pick{position:relative}.fb-pick__control{display:flex;align-items:stretch;border:1px solid var(--fb-border, #d6dae1);border-radius:6px;background:var(--fb-surface, #fff);overflow:hidden}.fb-pick--open .fb-pick__control{border-color:var(--fb-accent, #2f6feb)}.fb-pick__input{flex:1;min-width:0;padding:5px 7px;border:0;background:transparent;color:var(--fb-text, #1d2939);font:inherit;font-size:12px}.fb-pick__input:focus-visible{outline:none}.fb-pick__input--mono,.fb-pick__name--mono{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.fb-pick__toggle,.fb-pick__clear{flex:0 0 auto;padding:0 7px;border:0;border-left:1px solid var(--fb-border-subtle, #e6e9ee);background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:12px;cursor:pointer}.fb-pick__toggle:hover,.fb-pick__clear:hover{background:var(--fb-surface-alt, #f8f9fb)}.fb-pick__hint{margin:3px 0 0;font-size:10px;line-height:1.35;color:var(--fb-text-subtle, #98a2b3)}.fb-pick__hint--warn{color:var(--fb-warning, #b7791f)}.fb-pick__hint--error{color:var(--fb-error, #c9372c)}.fb-pick__panel{position:absolute;z-index:30;top:calc(100% + 3px);left:0;right:0;max-height:260px;overflow-y:auto;padding:6px;border:1px solid var(--fb-border, #d6dae1);border-radius:6px;background:var(--fb-surface, #fff);box-shadow:0 6px 18px #10182829}.fb-pick__option{display:flex;flex-direction:column;width:100%;padding:4px 6px;border:0;border-radius:4px;background:transparent;color:var(--fb-text, #1d2939);font:inherit;text-align:left;cursor:pointer}.fb-pick__option:hover,.fb-pick__option[aria-selected=true]{background:var(--fb-surface-alt, #f8f9fb)}.fb-pick__row{display:flex;align-items:stretch;gap:2px}.fb-pick__row .fb-pick__option{flex:1;min-width:0}.fb-pick__into{flex:0 0 auto;padding:0 7px;border:0;border-radius:4px;background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:13px;cursor:pointer}.fb-pick__into:hover{background:var(--fb-surface-alt, #f8f9fb);color:var(--fb-accent, #2f6feb)}.fb-pick__name{font-size:12px}.fb-pick__meta{font-size:10px;color:var(--fb-text-muted, #667085)}.fb-pick__group{margin:2px 4px 4px;font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--fb-text-muted, #667085)}.fb-pick__empty{margin:4px;font-size:11px;color:var(--fb-text-muted, #667085)}.fb-pick__close{width:100%;margin-top:4px;padding:4px;border:0;border-top:1px solid var(--fb-border-subtle, #e6e9ee);background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:11px;cursor:pointer}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4181
4673
  }
4182
4674
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImport: i0, type: StructureMemberPickerComponent, decorators: [{
4183
4675
  type: Component,
4184
- args: [{ selector: 'fb-structure-member-picker', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"fb-pick\" [class.fb-pick--open]=\"isOpen()\">\r\n <div class=\"fb-pick__control\">\r\n <input\r\n class=\"fb-pick__input\"\r\n type=\"text\"\r\n role=\"combobox\"\r\n autocomplete=\"off\"\r\n [value]=\"text()\"\r\n [placeholder]=\"placeholder()\"\r\n [disabled]=\"disabled()\"\r\n [attr.aria-label]=\"label()\"\r\n [attr.aria-expanded]=\"isOpen()\"\r\n (input)=\"onInput($any($event.target).value)\"\r\n (focus)=\"open()\"\r\n (keydown.escape)=\"close()\"\r\n />\r\n <button\r\n type=\"button\"\r\n class=\"fb-pick__toggle\"\r\n [disabled]=\"disabled()\"\r\n [attr.aria-expanded]=\"isOpen()\"\r\n aria-label=\"Mostra i membri disponibili\"\r\n (click)=\"toggle()\"\r\n >\r\n \u25BE\r\n </button>\r\n @if (value()) {\r\n <button type=\"button\" class=\"fb-pick__clear\" aria-label=\"Svuota\" (click)=\"clear()\">\u00D7</button>\r\n }\r\n </div>\r\n\r\n @if (isClassUnknown()) {\r\n <p class=\"fb-pick__hint fb-pick__hint--error\">\r\n La classe {{ className() }} non e\u2019 registrata (STRUCTURE_TYPE_UNKNOWN): correggi la classe,\r\n non il membro.\r\n </p>\r\n } @else if (isUnknown()) {\r\n <p class=\"fb-pick__hint fb-pick__hint--error\">{{ unknownMessage() }}</p>\r\n } @else if (isNotWritable()) {\r\n <p class=\"fb-pick__hint fb-pick__hint--error\">\r\n Questo membro e\u2019 calcolato: si legge, ma assegnarlo e\u2019 TARGET_NOT_WRITABLE.\r\n </p>\r\n } @else if (isNestedPath()) {\r\n <p class=\"fb-pick__hint fb-pick__hint--warn\">\r\n Percorso annidato: viene verificato solo il primo segmento, il resto si scopre eseguendo.\r\n </p>\r\n } @else if (describeValue()) {\r\n <p class=\"fb-pick__hint\">{{ describeValue() }}</p>\r\n } @else if (isUndeclared()) {\r\n <p class=\"fb-pick__hint fb-pick__hint--warn\">\r\n {{ classLabel() }} non dichiara i suoi membri: scrivi il nome a mano, non e\u2019 verificabile.\r\n </p>\r\n } @else if (loadErrored()) {\r\n <p class=\"fb-pick__hint fb-pick__hint--warn\">\r\n Elenco dei membri non disponibile: puoi scrivere il nome a mano.\r\n </p>\r\n }\r\n\r\n @if (isOpen()) {\r\n <div class=\"fb-pick__panel\" role=\"listbox\">\r\n @if (className()) {\r\n <p class=\"fb-pick__group\">Membri di {{ classLabel() }}</p>\r\n }\r\n @if (options().length === 0) {\r\n <p class=\"fb-pick__empty\">\r\n @if (!className()) {\r\n Scegli prima una classe.\r\n } @else if (hasCatalog()) {\r\n Nessun membro corrisponde.\r\n } @else if (isClassUnknown()) {\r\n Classe non registrata: nessun membro da proporre.\r\n } @else {\r\n Membri non dichiarati: scrivi il nome del membro.\r\n }\r\n </p>\r\n }\r\n @for (member of options(); track member.name) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-pick__option\"\r\n role=\"option\"\r\n [attr.aria-selected]=\"member.name === value()\"\r\n (click)=\"choose(member)\"\r\n >\r\n <span class=\"fb-pick__name\">{{ member.name }}</span>\r\n @if (describe(member)) {\r\n <span class=\"fb-pick__meta\">{{ describe(member) }}</span>\r\n }\r\n </button>\r\n }\r\n <button type=\"button\" class=\"fb-pick__close\" (click)=\"close()\">Chiudi</button>\r\n </div>\r\n }\r\n</div>\r\n", styles: [":host{display:block}.fb-pick{position:relative}.fb-pick__control{display:flex;align-items:stretch;border:1px solid var(--fb-border, #d6dae1);border-radius:6px;background:var(--fb-surface, #fff);overflow:hidden}.fb-pick--open .fb-pick__control{border-color:var(--fb-accent, #2f6feb)}.fb-pick__input{flex:1;min-width:0;padding:5px 7px;border:0;background:transparent;color:var(--fb-text, #1d2939);font:inherit;font-size:12px}.fb-pick__input:focus-visible{outline:none}.fb-pick__input--mono,.fb-pick__name--mono{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.fb-pick__toggle,.fb-pick__clear{flex:0 0 auto;padding:0 7px;border:0;border-left:1px solid var(--fb-border-subtle, #e6e9ee);background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:12px;cursor:pointer}.fb-pick__toggle:hover,.fb-pick__clear:hover{background:var(--fb-surface-alt, #f8f9fb)}.fb-pick__hint{margin:3px 0 0;font-size:10px;line-height:1.35;color:var(--fb-text-subtle, #98a2b3)}.fb-pick__hint--warn{color:var(--fb-warning, #b7791f)}.fb-pick__hint--error{color:var(--fb-error, #c9372c)}.fb-pick__panel{position:absolute;z-index:30;top:calc(100% + 3px);left:0;right:0;max-height:260px;overflow-y:auto;padding:6px;border:1px solid var(--fb-border, #d6dae1);border-radius:6px;background:var(--fb-surface, #fff);box-shadow:0 6px 18px #10182829}.fb-pick__option{display:flex;flex-direction:column;width:100%;padding:4px 6px;border:0;border-radius:4px;background:transparent;color:var(--fb-text, #1d2939);font:inherit;text-align:left;cursor:pointer}.fb-pick__option:hover,.fb-pick__option[aria-selected=true]{background:var(--fb-surface-alt, #f8f9fb)}.fb-pick__name{font-size:12px}.fb-pick__meta{font-size:10px;color:var(--fb-text-muted, #667085)}.fb-pick__group{margin:2px 4px 4px;font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--fb-text-muted, #667085)}.fb-pick__empty{margin:4px;font-size:11px;color:var(--fb-text-muted, #667085)}.fb-pick__close{width:100%;margin-top:4px;padding:4px;border:0;border-top:1px solid var(--fb-border-subtle, #e6e9ee);background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:11px;cursor:pointer}\n"] }]
4676
+ args: [{ selector: 'fb-structure-member-picker', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"fb-pick\" [class.fb-pick--open]=\"isOpen()\">\r\n <div class=\"fb-pick__control\">\r\n <input\r\n class=\"fb-pick__input\"\r\n type=\"text\"\r\n role=\"combobox\"\r\n autocomplete=\"off\"\r\n [value]=\"text()\"\r\n [placeholder]=\"placeholder()\"\r\n [disabled]=\"disabled()\"\r\n [attr.aria-label]=\"label()\"\r\n [attr.aria-expanded]=\"isOpen()\"\r\n (input)=\"onInput($any($event.target).value)\"\r\n (focus)=\"open()\"\r\n (keydown.escape)=\"close()\"\r\n />\r\n <button\r\n type=\"button\"\r\n class=\"fb-pick__toggle\"\r\n [disabled]=\"disabled()\"\r\n [attr.aria-expanded]=\"isOpen()\"\r\n aria-label=\"Mostra i membri disponibili\"\r\n (click)=\"toggle()\"\r\n >\r\n \u25BE\r\n </button>\r\n @if (value()) {\r\n <button type=\"button\" class=\"fb-pick__clear\" aria-label=\"Svuota\" (click)=\"clear()\">\u00D7</button>\r\n }\r\n </div>\r\n\r\n @if (isClassUnknown()) {\r\n <p class=\"fb-pick__hint fb-pick__hint--error\">\r\n La classe {{ className() }} non e\u2019 registrata (STRUCTURE_TYPE_UNKNOWN): correggi la classe,\r\n non il membro.\r\n </p>\r\n } @else if (isUnknown()) {\r\n <p class=\"fb-pick__hint fb-pick__hint--error\">{{ unknownMessage() }}</p>\r\n } @else if (isNotWritable()) {\r\n <p class=\"fb-pick__hint fb-pick__hint--error\">{{ notWritableMessage() }}</p>\r\n } @else if (isUndeclared()) {\r\n <p class=\"fb-pick__hint fb-pick__hint--warn\">\r\n {{ classLabel() }} non dichiara i suoi membri: scrivi il nome a mano, non e\u2019 verificabile.\r\n </p>\r\n } @else if (isNotVerifiable()) {\r\n <p class=\"fb-pick__hint fb-pick__hint--warn\">{{ notVerifiableMessage() }}</p>\r\n } @else if (describeValue()) {\r\n <p class=\"fb-pick__hint\">{{ describeValue() }}</p>\r\n } @else if (loadErrored()) {\r\n <p class=\"fb-pick__hint fb-pick__hint--warn\">\r\n Elenco dei membri non disponibile: puoi scrivere il nome a mano.\r\n </p>\r\n }\r\n\r\n @if (isOpen()) {\r\n <div class=\"fb-pick__panel\" role=\"listbox\">\r\n @if (className()) {\r\n <p class=\"fb-pick__group\">\r\n {{ isObjectLevel() ? 'Campi di ' : 'Membri di ' }}{{ classLabel() }}\r\n </p>\r\n }\r\n @if (options().length === 0) {\r\n <p class=\"fb-pick__empty\">\r\n @if (!className()) {\r\n Scegli prima una classe.\r\n } @else if (hasCatalog()) {\r\n Nessun membro corrisponde.\r\n } @else if (isClassUnknown()) {\r\n Classe non registrata: nessun membro da proporre.\r\n } @else {\r\n Membri non dichiarati: scrivi il nome del membro.\r\n }\r\n </p>\r\n }\r\n @for (entry of options(); track entry.name) {\r\n <div class=\"fb-pick__row\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-pick__option\"\r\n role=\"option\"\r\n [attr.aria-selected]=\"prefix() + entry.name === value()\"\r\n (click)=\"choose(entry)\"\r\n >\r\n <span class=\"fb-pick__name\">{{ entry.name }}</span>\r\n @if (describe(entry)) {\r\n <span class=\"fb-pick__meta\">{{ describe(entry) }}</span>\r\n }\r\n </button>\r\n @if (canDescend(entry)) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-pick__into\"\r\n [attr.aria-label]=\"'Entra in ' + entry.name\"\r\n title=\"Entra nel membro composto\"\r\n (click)=\"descend(entry)\"\r\n >\r\n \u203A\r\n </button>\r\n }\r\n </div>\r\n }\r\n <button type=\"button\" class=\"fb-pick__close\" (click)=\"close()\">Chiudi</button>\r\n </div>\r\n }\r\n</div>\r\n", styles: [":host{display:block}.fb-pick{position:relative}.fb-pick__control{display:flex;align-items:stretch;border:1px solid var(--fb-border, #d6dae1);border-radius:6px;background:var(--fb-surface, #fff);overflow:hidden}.fb-pick--open .fb-pick__control{border-color:var(--fb-accent, #2f6feb)}.fb-pick__input{flex:1;min-width:0;padding:5px 7px;border:0;background:transparent;color:var(--fb-text, #1d2939);font:inherit;font-size:12px}.fb-pick__input:focus-visible{outline:none}.fb-pick__input--mono,.fb-pick__name--mono{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.fb-pick__toggle,.fb-pick__clear{flex:0 0 auto;padding:0 7px;border:0;border-left:1px solid var(--fb-border-subtle, #e6e9ee);background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:12px;cursor:pointer}.fb-pick__toggle:hover,.fb-pick__clear:hover{background:var(--fb-surface-alt, #f8f9fb)}.fb-pick__hint{margin:3px 0 0;font-size:10px;line-height:1.35;color:var(--fb-text-subtle, #98a2b3)}.fb-pick__hint--warn{color:var(--fb-warning, #b7791f)}.fb-pick__hint--error{color:var(--fb-error, #c9372c)}.fb-pick__panel{position:absolute;z-index:30;top:calc(100% + 3px);left:0;right:0;max-height:260px;overflow-y:auto;padding:6px;border:1px solid var(--fb-border, #d6dae1);border-radius:6px;background:var(--fb-surface, #fff);box-shadow:0 6px 18px #10182829}.fb-pick__option{display:flex;flex-direction:column;width:100%;padding:4px 6px;border:0;border-radius:4px;background:transparent;color:var(--fb-text, #1d2939);font:inherit;text-align:left;cursor:pointer}.fb-pick__option:hover,.fb-pick__option[aria-selected=true]{background:var(--fb-surface-alt, #f8f9fb)}.fb-pick__row{display:flex;align-items:stretch;gap:2px}.fb-pick__row .fb-pick__option{flex:1;min-width:0}.fb-pick__into{flex:0 0 auto;padding:0 7px;border:0;border-radius:4px;background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:13px;cursor:pointer}.fb-pick__into:hover{background:var(--fb-surface-alt, #f8f9fb);color:var(--fb-accent, #2f6feb)}.fb-pick__name{font-size:12px}.fb-pick__meta{font-size:10px;color:var(--fb-text-muted, #667085)}.fb-pick__group{margin:2px 4px 4px;font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--fb-text-muted, #667085)}.fb-pick__empty{margin:4px;font-size:11px;color:var(--fb-text-muted, #667085)}.fb-pick__close{width:100%;margin-top:4px;padding:4px;border:0;border-top:1px solid var(--fb-border-subtle, #e6e9ee);background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:11px;cursor:pointer}\n"] }]
4185
4677
  }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], className: [{ type: i0.Input, args: [{ isSignal: true, alias: "className", required: false }] }], usage: [{ type: i0.Input, args: [{ isSignal: true, alias: "usage", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], valueChange: [{ type: i0.Output, args: ["valueChange"] }] } });
4186
4678
 
4187
4679
  /**
@@ -8118,17 +8610,44 @@ class ProblemsPanelComponent {
8118
8610
  isRunning = computed(() => this.validation.isRunning(), ...(ngDevMode ? [{ debugName: "isRunning" }] : []));
8119
8611
  hasResult = computed(() => this.validation.hasResult(), ...(ngDevMode ? [{ debugName: "hasResult" }] : []));
8120
8612
  lastError = computed(() => this.validation.lastError(), ...(ngDevMode ? [{ debugName: "lastError" }] : []));
8121
- errorCount = computed(() => this.validation.errorCount(), ...(ngDevMode ? [{ debugName: "errorCount" }] : []));
8122
- warningCount = computed(() => this.validation.warningCount(), ...(ngDevMode ? [{ debugName: "warningCount" }] : []));
8123
- infoCount = computed(() => this.validation.infoCount(), ...(ngDevMode ? [{ debugName: "infoCount" }] : []));
8613
+ /**
8614
+ * I conteggi dei chip vengono dai rilievi, non dal risultato: il numero sul filtro deve
8615
+ * essere quello che il filtro apre (vedi `FlowValidationStore.issueCounts`).
8616
+ */
8617
+ counts = computed(() => this.validation.issueCounts(), ...(ngDevMode ? [{ debugName: "counts" }] : []));
8618
+ errorCount = computed(() => this.counts().Error, ...(ngDevMode ? [{ debugName: "errorCount" }] : []));
8619
+ warningCount = computed(() => this.counts().Warning, ...(ngDevMode ? [{ debugName: "warningCount" }] : []));
8620
+ infoCount = computed(() => this.counts().Info, ...(ngDevMode ? [{ debugName: "infoCount" }] : []));
8621
+ totalCount = computed(() => this.validation.issues().length, ...(ngDevMode ? [{ debugName: "totalCount" }] : []));
8124
8622
  issues = computed(() => {
8125
8623
  const filter = this.filter();
8126
8624
  const issues = this.validation.issues();
8127
- return filter === 'all' ? issues : issues.filter((issue) => issue.severity === filter);
8625
+ if (filter === 'all') {
8626
+ return issues;
8627
+ }
8628
+ // Il secchio, non la stringa: `severity` e' un tipo aperto e un `error` minuscolo o un
8629
+ // `Log` non corrisponderebbero a niente, svuotando la lista senza dire perche'.
8630
+ return issues.filter((issue) => severityBucket(issue.severity) === filter);
8128
8631
  }, ...(ngDevMode ? [{ debugName: "issues" }] : []));
8129
- isClean = computed(() => this.hasResult() && this.validation.issues().length === 0, ...(ngDevMode ? [{ debugName: "isClean" }] : []));
8632
+ isClean = computed(() => this.hasResult() && this.totalCount() === 0, ...(ngDevMode ? [{ debugName: "isClean" }] : []));
8633
+ /** Ci sono rilievi, ma il filtro attivo non ne mostra nessuno: va detto, non lasciato in bianco. */
8634
+ isFilteredEmpty = computed(() => this.totalCount() > 0 && this.issues().length === 0, ...(ngDevMode ? [{ debugName: "isFilteredEmpty" }] : []));
8635
+ activeFilterLabel = computed(() => {
8636
+ const filter = this.filter();
8637
+ return filter === 'all' ? '' : SEVERITY_LABEL[filter];
8638
+ }, ...(ngDevMode ? [{ debugName: "activeFilterLabel" }] : []));
8639
+ /** Un secondo clic sul filtro attivo torna a «Tutti»: e' l'unico modo per uscirne col mouse dov'e'. */
8130
8640
  setFilter(filter) {
8131
- this.filter.set(filter);
8641
+ this.filter.set(this.filter() === filter ? 'all' : filter);
8642
+ }
8643
+ bucketOf(issue) {
8644
+ return severityBucket(issue.severity);
8645
+ }
8646
+ iconText(bucket) {
8647
+ return SEVERITY_ICON[bucket];
8648
+ }
8649
+ labelText(bucket) {
8650
+ return SEVERITY_LABEL[bucket];
8132
8651
  }
8133
8652
  focus(issue) {
8134
8653
  // Un rilievo senza `elementName` riguarda il documento: l'ancora piu' vicina e' lo Start.
@@ -8138,11 +8657,11 @@ class ProblemsPanelComponent {
8138
8657
  this.closed.emit();
8139
8658
  }
8140
8659
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.27", ngImport: i0, type: ProblemsPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8141
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.27", type: ProblemsPanelComponent, isStandalone: true, selector: "fb-problems-panel", outputs: { elementFocused: "elementFocused", closed: "closed" }, ngImport: i0, template: "<header class=\"fb-prob__header\">\r\n <h2 class=\"fb-prob__title\">\r\n Problemi\r\n @if (isRunning()) {\r\n <span class=\"fb-prob__running\">validazione in corso\u2026</span>\r\n }\r\n </h2>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" aria-label=\"Chiudi\" (click)=\"close()\">\u00D7</button>\r\n</header>\r\n\r\n<nav class=\"fb-prob__filters\" aria-label=\"Filtra per gravita\u2019\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-prob__filter\"\r\n [class.fb-prob__filter--active]=\"filter() === 'all'\"\r\n (click)=\"setFilter('all')\"\r\n >\r\n Tutti\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-prob__filter fb-prob__filter--error\"\r\n [class.fb-prob__filter--active]=\"filter() === 'Error'\"\r\n (click)=\"setFilter('Error')\"\r\n >\r\n Errori <span class=\"fb-prob__count\">{{ errorCount() }}</span>\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-prob__filter fb-prob__filter--warning\"\r\n [class.fb-prob__filter--active]=\"filter() === 'Warning'\"\r\n (click)=\"setFilter('Warning')\"\r\n >\r\n Avvisi <span class=\"fb-prob__count\">{{ warningCount() }}</span>\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-prob__filter\"\r\n [class.fb-prob__filter--active]=\"filter() === 'Info'\"\r\n (click)=\"setFilter('Info')\"\r\n >\r\n Note <span class=\"fb-prob__count\">{{ infoCount() }}</span>\r\n </button>\r\n</nav>\r\n\r\n<div class=\"fb-prob__body\">\r\n @if (lastError()) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Non e\u2019 stato possibile validare: {{ lastError() }}\r\n </p>\r\n }\r\n\r\n @if (!hasResult() && !isRunning()) {\r\n <p class=\"fb-empty\">La validazione non e\u2019 ancora stata eseguita.</p>\r\n }\r\n\r\n @if (isClean()) {\r\n <p class=\"fb-empty\">\r\n Nessun rilievo. Attenzione: non e\u2019 una garanzia di correttezza \u2014 la validazione non verifica i percorsi\r\n di relazione nei campi, la sintassi delle formule, ne\u2019 action, form e parametri quando il catalogo\r\n corrispondente non e\u2019 popolato.\r\n </p>\r\n }\r\n\r\n <ul class=\"fb-prob__list\">\r\n <!-- `$index`: due rilievi possono avere codice, elemento e path identici (due condizioni\r\n incomplete nella stessa regola), e una chiave duplicata e' un errore di Angular. -->\r\n @for (issue of issues(); track $index) {\r\n <li\r\n class=\"fb-prob__item\"\r\n [class.fb-prob__item--error]=\"issue.severity === 'Error'\"\r\n [class.fb-prob__item--warning]=\"issue.severity === 'Warning'\"\r\n >\r\n <button type=\"button\" class=\"fb-prob__link\" (click)=\"focus(issue)\">\r\n <span class=\"fb-prob__message\">{{ issue.message }}</span>\r\n <span class=\"fb-prob__meta\">\r\n <code>{{ issue.code }}</code>\r\n @if (issue.elementName) {\r\n \u00B7 {{ issue.elementName }}\r\n }\r\n @if (issue.path) {\r\n \u00B7 {{ issue.path }}\r\n }\r\n </span>\r\n </button>\r\n </li>\r\n }\r\n </ul>\r\n\r\n @if (errorCount() > 0) {\r\n <p class=\"fb-prob__note\">\r\n Gli errori bloccano l\u2019<strong>attivazione</strong>, non il salvataggio: puoi interrompere il lavoro a\r\n met\u00E0 e riprenderlo.\r\n </p>\r\n }\r\n</div>\r\n", styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0;background:var(--fb-surface, #fff);border-top:1px solid var(--fb-border, #d6dae1)}.fb-prob__header{display:flex;align-items:center;justify-content:space-between;padding:6px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-prob__title{display:flex;align-items:baseline;gap:8px;margin:0;font-size:13px;font-weight:600;color:var(--fb-text, #1d2939)}.fb-prob__running{font-size:10px;font-weight:400;color:var(--fb-text-subtle, #98a2b3)}.fb-prob__filters{display:flex;gap:3px;padding:5px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-prob__filter{display:inline-flex;align-items:center;gap:4px;padding:2px 8px;border:1px solid var(--fb-border-subtle, #e6e9ee);border-radius:12px;background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:11px;cursor:pointer}.fb-prob__filter--active{border-color:var(--fb-accent, #2f6feb);background:color-mix(in srgb,var(--fb-accent, #2f6feb) 10%,transparent);color:var(--fb-accent, #2f6feb);font-weight:600}.fb-prob__filter--error.fb-prob__filter--active{border-color:var(--fb-error, #c9372c);background:color-mix(in srgb,var(--fb-error, #c9372c) 10%,transparent);color:var(--fb-error, #c9372c)}.fb-prob__filter--warning.fb-prob__filter--active{border-color:var(--fb-warning, #b7791f);background:color-mix(in srgb,var(--fb-warning, #b7791f) 12%,transparent);color:var(--fb-warning, #b7791f)}.fb-prob__count{padding:0 4px;border-radius:6px;background:var(--fb-border, #d6dae1);font-size:9px;color:var(--fb-text, #1d2939)}.fb-prob__body{flex:1;min-height:0;overflow-y:auto;padding:8px 12px}.fb-prob__list{margin:0;padding:0;list-style:none}.fb-prob__item{border-left:3px solid var(--fb-text-subtle, #98a2b3);margin-bottom:3px;border-radius:3px;background:var(--fb-surface-alt, #f8f9fb)}.fb-prob__item--error{border-left-color:var(--fb-error, #c9372c)}.fb-prob__item--warning{border-left-color:var(--fb-warning, #b7791f)}.fb-prob__link{display:flex;flex-direction:column;width:100%;padding:5px 8px;border:0;background:transparent;color:var(--fb-text, #1d2939);font:inherit;text-align:left;cursor:pointer}.fb-prob__link:hover{background:color-mix(in srgb,var(--fb-accent, #2f6feb) 6%,transparent)}.fb-prob__message{font-size:12px;line-height:1.4}.fb-prob__meta{font-size:10px;color:var(--fb-text-muted, #667085)}.fb-prob__note{margin:8px 0 0;font-size:10px;color:var(--fb-text-subtle, #98a2b3)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
8660
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.27", type: ProblemsPanelComponent, isStandalone: true, selector: "fb-problems-panel", outputs: { elementFocused: "elementFocused", closed: "closed" }, ngImport: i0, template: "<header class=\"fb-prob__header\">\r\n <h2 class=\"fb-prob__title\">\r\n Problemi\r\n @if (isRunning()) {\r\n <span class=\"fb-prob__running\">validazione in corso\u2026</span>\r\n }\r\n </h2>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" aria-label=\"Chiudi\" (click)=\"close()\">\u00D7</button>\r\n</header>\r\n\r\n<nav class=\"fb-prob__filters\" aria-label=\"Filtra per gravita\u2019\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-prob__filter\"\r\n [class.fb-prob__filter--active]=\"filter() === 'all'\"\r\n (click)=\"setFilter('all')\"\r\n >\r\n Tutti <span class=\"fb-prob__count\">{{ totalCount() }}</span>\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-prob__filter fb-prob__filter--error\"\r\n [class.fb-prob__filter--active]=\"filter() === 'Error'\"\r\n [attr.aria-pressed]=\"filter() === 'Error'\"\r\n (click)=\"setFilter('Error')\"\r\n >\r\n <span class=\"fb-prob__icon fb-prob__icon--error\" aria-hidden=\"true\">\u2715</span>\r\n Errori <span class=\"fb-prob__count\">{{ errorCount() }}</span>\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-prob__filter fb-prob__filter--warning\"\r\n [class.fb-prob__filter--active]=\"filter() === 'Warning'\"\r\n [attr.aria-pressed]=\"filter() === 'Warning'\"\r\n (click)=\"setFilter('Warning')\"\r\n >\r\n <span class=\"fb-prob__icon fb-prob__icon--warning\" aria-hidden=\"true\">!</span>\r\n Avvisi <span class=\"fb-prob__count\">{{ warningCount() }}</span>\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-prob__filter fb-prob__filter--info\"\r\n [class.fb-prob__filter--active]=\"filter() === 'Info'\"\r\n [attr.aria-pressed]=\"filter() === 'Info'\"\r\n (click)=\"setFilter('Info')\"\r\n >\r\n <span class=\"fb-prob__icon fb-prob__icon--info\" aria-hidden=\"true\">i</span>\r\n Note <span class=\"fb-prob__count\">{{ infoCount() }}</span>\r\n </button>\r\n</nav>\r\n\r\n<div class=\"fb-prob__body\">\r\n @if (lastError()) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Non e\u2019 stato possibile validare: {{ lastError() }}\r\n </p>\r\n }\r\n\r\n @if (!hasResult() && !isRunning()) {\r\n <p class=\"fb-empty\">La validazione non e\u2019 ancora stata eseguita.</p>\r\n }\r\n\r\n @if (isClean()) {\r\n <p class=\"fb-empty\">\r\n Nessun rilievo. Attenzione: non e\u2019 una garanzia di correttezza \u2014 la validazione non verifica i percorsi\r\n di relazione nei campi, la sintassi delle formule, ne\u2019 action, form e parametri quando il catalogo\r\n corrispondente non e\u2019 popolato.\r\n </p>\r\n }\r\n\r\n @if (isFilteredEmpty()) {\r\n <p class=\"fb-empty\">\r\n Nessun rilievo di tipo \u00AB{{ activeFilterLabel() }}\u00BB. Ce ne sono altri: scegli\r\n <button type=\"button\" class=\"fb-prob__inline-link\" (click)=\"setFilter('all')\">Tutti</button>.\r\n </p>\r\n }\r\n\r\n <ul class=\"fb-prob__list\">\r\n <!-- `$index`: due rilievi possono avere codice, elemento e path identici (due condizioni\r\n incomplete nella stessa regola), e una chiave duplicata e' un errore di Angular. -->\r\n @for (issue of issues(); track $index) {\r\n <!-- Il secchio si calcola una volta: `severity` e' una stringa aperta e serve\r\n normalizzata a classe, icona ed etichetta. -->\r\n @let bucket = bucketOf(issue);\r\n <li\r\n class=\"fb-prob__item\"\r\n [class.fb-prob__item--error]=\"bucket === 'Error'\"\r\n [class.fb-prob__item--warning]=\"bucket === 'Warning'\"\r\n [class.fb-prob__item--info]=\"bucket === 'Info'\"\r\n >\r\n <button type=\"button\" class=\"fb-prob__link\" (click)=\"focus(issue)\">\r\n <!-- Il glifo porta il colore: `role=\"img\"` + `aria-label` perche' la gravita\u2019 non\r\n sia leggibile solo a vista. -->\r\n <span\r\n class=\"fb-prob__icon\"\r\n [class.fb-prob__icon--error]=\"bucket === 'Error'\"\r\n [class.fb-prob__icon--warning]=\"bucket === 'Warning'\"\r\n [class.fb-prob__icon--info]=\"bucket === 'Info'\"\r\n role=\"img\"\r\n [attr.aria-label]=\"labelText(bucket)\"\r\n [title]=\"labelText(bucket)\"\r\n >{{ iconText(bucket) }}</span\r\n >\r\n <span class=\"fb-prob__message\">{{ issue.message }}</span>\r\n <span class=\"fb-prob__meta\">\r\n <code>{{ issue.code }}</code>\r\n @if (issue.elementName) {\r\n \u00B7 {{ issue.elementName }}\r\n }\r\n @if (issue.path) {\r\n \u00B7 {{ issue.path }}\r\n }\r\n </span>\r\n </button>\r\n </li>\r\n }\r\n </ul>\r\n\r\n @if (errorCount() > 0) {\r\n <p class=\"fb-prob__note\">\r\n Gli errori bloccano l\u2019<strong>attivazione</strong>, non il salvataggio: puoi interrompere il lavoro a\r\n met\u00E0 e riprenderlo.\r\n </p>\r\n }\r\n</div>\r\n", styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0;background:var(--fb-surface, #fff);border-top:1px solid var(--fb-border, #d6dae1)}.fb-prob__header{display:flex;align-items:center;justify-content:space-between;padding:6px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-prob__title{display:flex;align-items:baseline;gap:8px;margin:0;font-size:13px;font-weight:600;color:var(--fb-text, #1d2939)}.fb-prob__running{font-size:10px;font-weight:400;color:var(--fb-text-subtle, #98a2b3)}.fb-prob__filters{display:flex;gap:3px;padding:5px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-prob__filter{display:inline-flex;align-items:center;gap:4px;padding:2px 8px;border:1px solid var(--fb-border-subtle, #e6e9ee);border-radius:12px;background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:11px;cursor:pointer}.fb-prob__filter--active{border-color:var(--fb-accent, #2f6feb);background:color-mix(in srgb,var(--fb-accent, #2f6feb) 10%,transparent);color:var(--fb-accent, #2f6feb);font-weight:600}.fb-prob__filter--error.fb-prob__filter--active{border-color:var(--fb-error, #c9372c);background:color-mix(in srgb,var(--fb-error, #c9372c) 10%,transparent);color:var(--fb-error, #c9372c)}.fb-prob__filter--warning.fb-prob__filter--active{border-color:var(--fb-warning, #b7791f);background:color-mix(in srgb,var(--fb-warning, #b7791f) 12%,transparent);color:var(--fb-warning, #b7791f)}.fb-prob__icon{display:inline-grid;place-items:center;flex:0 0 auto;width:13px;height:13px;border-radius:50%;background:var(--fb-text-subtle, #98a2b3);color:var(--fb-surface, #fff);font-size:9px;font-weight:700;line-height:1}.fb-prob__icon--error{background:var(--fb-error, #c9372c)}.fb-prob__icon--warning{background:var(--fb-warning, #b7791f)}.fb-prob__icon--info{background:var(--fb-accent, #2f6feb)}.fb-prob__count{padding:0 4px;border-radius:6px;background:var(--fb-border, #d6dae1);font-size:9px;color:var(--fb-text, #1d2939)}.fb-prob__body{flex:1;min-height:0;overflow-y:auto;padding:8px 12px}.fb-prob__list{margin:0;padding:0;list-style:none}.fb-prob__item{border-left:3px solid var(--fb-text-subtle, #98a2b3);margin-bottom:3px;border-radius:3px;background:var(--fb-surface-alt, #f8f9fb)}.fb-prob__item--error{border-left-color:var(--fb-error, #c9372c)}.fb-prob__item--warning{border-left-color:var(--fb-warning, #b7791f)}.fb-prob__item--info{border-left-color:var(--fb-accent, #2f6feb)}.fb-prob__link{display:grid;grid-template-columns:auto minmax(0,1fr);column-gap:7px;align-items:start;width:100%;padding:5px 8px;border:0;background:transparent;color:var(--fb-text, #1d2939);font:inherit;text-align:left;cursor:pointer}.fb-prob__link:hover{background:color-mix(in srgb,var(--fb-accent, #2f6feb) 6%,transparent)}.fb-prob__link>.fb-prob__icon{grid-column:1;grid-row:1 / span 2;margin-top:2px}.fb-prob__message{grid-column:2;grid-row:1;font-size:12px;line-height:1.4}.fb-prob__meta{grid-column:2;grid-row:2;font-size:10px;color:var(--fb-text-muted, #667085)}.fb-prob__inline-link{padding:0;border:0;background:transparent;color:var(--fb-accent, #2f6feb);font:inherit;text-decoration:underline;cursor:pointer}.fb-prob__note{margin:8px 0 0;font-size:10px;color:var(--fb-text-subtle, #98a2b3)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
8142
8661
  }
8143
8662
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImport: i0, type: ProblemsPanelComponent, decorators: [{
8144
8663
  type: Component,
8145
- args: [{ selector: 'fb-problems-panel', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<header class=\"fb-prob__header\">\r\n <h2 class=\"fb-prob__title\">\r\n Problemi\r\n @if (isRunning()) {\r\n <span class=\"fb-prob__running\">validazione in corso\u2026</span>\r\n }\r\n </h2>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" aria-label=\"Chiudi\" (click)=\"close()\">\u00D7</button>\r\n</header>\r\n\r\n<nav class=\"fb-prob__filters\" aria-label=\"Filtra per gravita\u2019\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-prob__filter\"\r\n [class.fb-prob__filter--active]=\"filter() === 'all'\"\r\n (click)=\"setFilter('all')\"\r\n >\r\n Tutti\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-prob__filter fb-prob__filter--error\"\r\n [class.fb-prob__filter--active]=\"filter() === 'Error'\"\r\n (click)=\"setFilter('Error')\"\r\n >\r\n Errori <span class=\"fb-prob__count\">{{ errorCount() }}</span>\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-prob__filter fb-prob__filter--warning\"\r\n [class.fb-prob__filter--active]=\"filter() === 'Warning'\"\r\n (click)=\"setFilter('Warning')\"\r\n >\r\n Avvisi <span class=\"fb-prob__count\">{{ warningCount() }}</span>\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-prob__filter\"\r\n [class.fb-prob__filter--active]=\"filter() === 'Info'\"\r\n (click)=\"setFilter('Info')\"\r\n >\r\n Note <span class=\"fb-prob__count\">{{ infoCount() }}</span>\r\n </button>\r\n</nav>\r\n\r\n<div class=\"fb-prob__body\">\r\n @if (lastError()) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Non e\u2019 stato possibile validare: {{ lastError() }}\r\n </p>\r\n }\r\n\r\n @if (!hasResult() && !isRunning()) {\r\n <p class=\"fb-empty\">La validazione non e\u2019 ancora stata eseguita.</p>\r\n }\r\n\r\n @if (isClean()) {\r\n <p class=\"fb-empty\">\r\n Nessun rilievo. Attenzione: non e\u2019 una garanzia di correttezza \u2014 la validazione non verifica i percorsi\r\n di relazione nei campi, la sintassi delle formule, ne\u2019 action, form e parametri quando il catalogo\r\n corrispondente non e\u2019 popolato.\r\n </p>\r\n }\r\n\r\n <ul class=\"fb-prob__list\">\r\n <!-- `$index`: due rilievi possono avere codice, elemento e path identici (due condizioni\r\n incomplete nella stessa regola), e una chiave duplicata e' un errore di Angular. -->\r\n @for (issue of issues(); track $index) {\r\n <li\r\n class=\"fb-prob__item\"\r\n [class.fb-prob__item--error]=\"issue.severity === 'Error'\"\r\n [class.fb-prob__item--warning]=\"issue.severity === 'Warning'\"\r\n >\r\n <button type=\"button\" class=\"fb-prob__link\" (click)=\"focus(issue)\">\r\n <span class=\"fb-prob__message\">{{ issue.message }}</span>\r\n <span class=\"fb-prob__meta\">\r\n <code>{{ issue.code }}</code>\r\n @if (issue.elementName) {\r\n \u00B7 {{ issue.elementName }}\r\n }\r\n @if (issue.path) {\r\n \u00B7 {{ issue.path }}\r\n }\r\n </span>\r\n </button>\r\n </li>\r\n }\r\n </ul>\r\n\r\n @if (errorCount() > 0) {\r\n <p class=\"fb-prob__note\">\r\n Gli errori bloccano l\u2019<strong>attivazione</strong>, non il salvataggio: puoi interrompere il lavoro a\r\n met\u00E0 e riprenderlo.\r\n </p>\r\n }\r\n</div>\r\n", styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0;background:var(--fb-surface, #fff);border-top:1px solid var(--fb-border, #d6dae1)}.fb-prob__header{display:flex;align-items:center;justify-content:space-between;padding:6px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-prob__title{display:flex;align-items:baseline;gap:8px;margin:0;font-size:13px;font-weight:600;color:var(--fb-text, #1d2939)}.fb-prob__running{font-size:10px;font-weight:400;color:var(--fb-text-subtle, #98a2b3)}.fb-prob__filters{display:flex;gap:3px;padding:5px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-prob__filter{display:inline-flex;align-items:center;gap:4px;padding:2px 8px;border:1px solid var(--fb-border-subtle, #e6e9ee);border-radius:12px;background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:11px;cursor:pointer}.fb-prob__filter--active{border-color:var(--fb-accent, #2f6feb);background:color-mix(in srgb,var(--fb-accent, #2f6feb) 10%,transparent);color:var(--fb-accent, #2f6feb);font-weight:600}.fb-prob__filter--error.fb-prob__filter--active{border-color:var(--fb-error, #c9372c);background:color-mix(in srgb,var(--fb-error, #c9372c) 10%,transparent);color:var(--fb-error, #c9372c)}.fb-prob__filter--warning.fb-prob__filter--active{border-color:var(--fb-warning, #b7791f);background:color-mix(in srgb,var(--fb-warning, #b7791f) 12%,transparent);color:var(--fb-warning, #b7791f)}.fb-prob__count{padding:0 4px;border-radius:6px;background:var(--fb-border, #d6dae1);font-size:9px;color:var(--fb-text, #1d2939)}.fb-prob__body{flex:1;min-height:0;overflow-y:auto;padding:8px 12px}.fb-prob__list{margin:0;padding:0;list-style:none}.fb-prob__item{border-left:3px solid var(--fb-text-subtle, #98a2b3);margin-bottom:3px;border-radius:3px;background:var(--fb-surface-alt, #f8f9fb)}.fb-prob__item--error{border-left-color:var(--fb-error, #c9372c)}.fb-prob__item--warning{border-left-color:var(--fb-warning, #b7791f)}.fb-prob__link{display:flex;flex-direction:column;width:100%;padding:5px 8px;border:0;background:transparent;color:var(--fb-text, #1d2939);font:inherit;text-align:left;cursor:pointer}.fb-prob__link:hover{background:color-mix(in srgb,var(--fb-accent, #2f6feb) 6%,transparent)}.fb-prob__message{font-size:12px;line-height:1.4}.fb-prob__meta{font-size:10px;color:var(--fb-text-muted, #667085)}.fb-prob__note{margin:8px 0 0;font-size:10px;color:var(--fb-text-subtle, #98a2b3)}\n"] }]
8664
+ args: [{ selector: 'fb-problems-panel', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<header class=\"fb-prob__header\">\r\n <h2 class=\"fb-prob__title\">\r\n Problemi\r\n @if (isRunning()) {\r\n <span class=\"fb-prob__running\">validazione in corso\u2026</span>\r\n }\r\n </h2>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" aria-label=\"Chiudi\" (click)=\"close()\">\u00D7</button>\r\n</header>\r\n\r\n<nav class=\"fb-prob__filters\" aria-label=\"Filtra per gravita\u2019\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-prob__filter\"\r\n [class.fb-prob__filter--active]=\"filter() === 'all'\"\r\n (click)=\"setFilter('all')\"\r\n >\r\n Tutti <span class=\"fb-prob__count\">{{ totalCount() }}</span>\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-prob__filter fb-prob__filter--error\"\r\n [class.fb-prob__filter--active]=\"filter() === 'Error'\"\r\n [attr.aria-pressed]=\"filter() === 'Error'\"\r\n (click)=\"setFilter('Error')\"\r\n >\r\n <span class=\"fb-prob__icon fb-prob__icon--error\" aria-hidden=\"true\">\u2715</span>\r\n Errori <span class=\"fb-prob__count\">{{ errorCount() }}</span>\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-prob__filter fb-prob__filter--warning\"\r\n [class.fb-prob__filter--active]=\"filter() === 'Warning'\"\r\n [attr.aria-pressed]=\"filter() === 'Warning'\"\r\n (click)=\"setFilter('Warning')\"\r\n >\r\n <span class=\"fb-prob__icon fb-prob__icon--warning\" aria-hidden=\"true\">!</span>\r\n Avvisi <span class=\"fb-prob__count\">{{ warningCount() }}</span>\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-prob__filter fb-prob__filter--info\"\r\n [class.fb-prob__filter--active]=\"filter() === 'Info'\"\r\n [attr.aria-pressed]=\"filter() === 'Info'\"\r\n (click)=\"setFilter('Info')\"\r\n >\r\n <span class=\"fb-prob__icon fb-prob__icon--info\" aria-hidden=\"true\">i</span>\r\n Note <span class=\"fb-prob__count\">{{ infoCount() }}</span>\r\n </button>\r\n</nav>\r\n\r\n<div class=\"fb-prob__body\">\r\n @if (lastError()) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Non e\u2019 stato possibile validare: {{ lastError() }}\r\n </p>\r\n }\r\n\r\n @if (!hasResult() && !isRunning()) {\r\n <p class=\"fb-empty\">La validazione non e\u2019 ancora stata eseguita.</p>\r\n }\r\n\r\n @if (isClean()) {\r\n <p class=\"fb-empty\">\r\n Nessun rilievo. Attenzione: non e\u2019 una garanzia di correttezza \u2014 la validazione non verifica i percorsi\r\n di relazione nei campi, la sintassi delle formule, ne\u2019 action, form e parametri quando il catalogo\r\n corrispondente non e\u2019 popolato.\r\n </p>\r\n }\r\n\r\n @if (isFilteredEmpty()) {\r\n <p class=\"fb-empty\">\r\n Nessun rilievo di tipo \u00AB{{ activeFilterLabel() }}\u00BB. Ce ne sono altri: scegli\r\n <button type=\"button\" class=\"fb-prob__inline-link\" (click)=\"setFilter('all')\">Tutti</button>.\r\n </p>\r\n }\r\n\r\n <ul class=\"fb-prob__list\">\r\n <!-- `$index`: due rilievi possono avere codice, elemento e path identici (due condizioni\r\n incomplete nella stessa regola), e una chiave duplicata e' un errore di Angular. -->\r\n @for (issue of issues(); track $index) {\r\n <!-- Il secchio si calcola una volta: `severity` e' una stringa aperta e serve\r\n normalizzata a classe, icona ed etichetta. -->\r\n @let bucket = bucketOf(issue);\r\n <li\r\n class=\"fb-prob__item\"\r\n [class.fb-prob__item--error]=\"bucket === 'Error'\"\r\n [class.fb-prob__item--warning]=\"bucket === 'Warning'\"\r\n [class.fb-prob__item--info]=\"bucket === 'Info'\"\r\n >\r\n <button type=\"button\" class=\"fb-prob__link\" (click)=\"focus(issue)\">\r\n <!-- Il glifo porta il colore: `role=\"img\"` + `aria-label` perche' la gravita\u2019 non\r\n sia leggibile solo a vista. -->\r\n <span\r\n class=\"fb-prob__icon\"\r\n [class.fb-prob__icon--error]=\"bucket === 'Error'\"\r\n [class.fb-prob__icon--warning]=\"bucket === 'Warning'\"\r\n [class.fb-prob__icon--info]=\"bucket === 'Info'\"\r\n role=\"img\"\r\n [attr.aria-label]=\"labelText(bucket)\"\r\n [title]=\"labelText(bucket)\"\r\n >{{ iconText(bucket) }}</span\r\n >\r\n <span class=\"fb-prob__message\">{{ issue.message }}</span>\r\n <span class=\"fb-prob__meta\">\r\n <code>{{ issue.code }}</code>\r\n @if (issue.elementName) {\r\n \u00B7 {{ issue.elementName }}\r\n }\r\n @if (issue.path) {\r\n \u00B7 {{ issue.path }}\r\n }\r\n </span>\r\n </button>\r\n </li>\r\n }\r\n </ul>\r\n\r\n @if (errorCount() > 0) {\r\n <p class=\"fb-prob__note\">\r\n Gli errori bloccano l\u2019<strong>attivazione</strong>, non il salvataggio: puoi interrompere il lavoro a\r\n met\u00E0 e riprenderlo.\r\n </p>\r\n }\r\n</div>\r\n", styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0;background:var(--fb-surface, #fff);border-top:1px solid var(--fb-border, #d6dae1)}.fb-prob__header{display:flex;align-items:center;justify-content:space-between;padding:6px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-prob__title{display:flex;align-items:baseline;gap:8px;margin:0;font-size:13px;font-weight:600;color:var(--fb-text, #1d2939)}.fb-prob__running{font-size:10px;font-weight:400;color:var(--fb-text-subtle, #98a2b3)}.fb-prob__filters{display:flex;gap:3px;padding:5px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-prob__filter{display:inline-flex;align-items:center;gap:4px;padding:2px 8px;border:1px solid var(--fb-border-subtle, #e6e9ee);border-radius:12px;background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:11px;cursor:pointer}.fb-prob__filter--active{border-color:var(--fb-accent, #2f6feb);background:color-mix(in srgb,var(--fb-accent, #2f6feb) 10%,transparent);color:var(--fb-accent, #2f6feb);font-weight:600}.fb-prob__filter--error.fb-prob__filter--active{border-color:var(--fb-error, #c9372c);background:color-mix(in srgb,var(--fb-error, #c9372c) 10%,transparent);color:var(--fb-error, #c9372c)}.fb-prob__filter--warning.fb-prob__filter--active{border-color:var(--fb-warning, #b7791f);background:color-mix(in srgb,var(--fb-warning, #b7791f) 12%,transparent);color:var(--fb-warning, #b7791f)}.fb-prob__icon{display:inline-grid;place-items:center;flex:0 0 auto;width:13px;height:13px;border-radius:50%;background:var(--fb-text-subtle, #98a2b3);color:var(--fb-surface, #fff);font-size:9px;font-weight:700;line-height:1}.fb-prob__icon--error{background:var(--fb-error, #c9372c)}.fb-prob__icon--warning{background:var(--fb-warning, #b7791f)}.fb-prob__icon--info{background:var(--fb-accent, #2f6feb)}.fb-prob__count{padding:0 4px;border-radius:6px;background:var(--fb-border, #d6dae1);font-size:9px;color:var(--fb-text, #1d2939)}.fb-prob__body{flex:1;min-height:0;overflow-y:auto;padding:8px 12px}.fb-prob__list{margin:0;padding:0;list-style:none}.fb-prob__item{border-left:3px solid var(--fb-text-subtle, #98a2b3);margin-bottom:3px;border-radius:3px;background:var(--fb-surface-alt, #f8f9fb)}.fb-prob__item--error{border-left-color:var(--fb-error, #c9372c)}.fb-prob__item--warning{border-left-color:var(--fb-warning, #b7791f)}.fb-prob__item--info{border-left-color:var(--fb-accent, #2f6feb)}.fb-prob__link{display:grid;grid-template-columns:auto minmax(0,1fr);column-gap:7px;align-items:start;width:100%;padding:5px 8px;border:0;background:transparent;color:var(--fb-text, #1d2939);font:inherit;text-align:left;cursor:pointer}.fb-prob__link:hover{background:color-mix(in srgb,var(--fb-accent, #2f6feb) 6%,transparent)}.fb-prob__link>.fb-prob__icon{grid-column:1;grid-row:1 / span 2;margin-top:2px}.fb-prob__message{grid-column:2;grid-row:1;font-size:12px;line-height:1.4}.fb-prob__meta{grid-column:2;grid-row:2;font-size:10px;color:var(--fb-text-muted, #667085)}.fb-prob__inline-link{padding:0;border:0;background:transparent;color:var(--fb-accent, #2f6feb);font:inherit;text-decoration:underline;cursor:pointer}.fb-prob__note{margin:8px 0 0;font-size:10px;color:var(--fb-text-subtle, #98a2b3)}\n"] }]
8146
8665
  }], propDecorators: { elementFocused: [{ type: i0.Output, args: ["elementFocused"] }], closed: [{ type: i0.Output, args: ["closed"] }] } });
8147
8666
 
8148
8667
  /**
@@ -8877,6 +9396,17 @@ class FlowBuilderComponent {
8877
9396
  });
8878
9397
  }
8879
9398
  processTypes = computed(() => this.dictionaries.processTypes(), ...(ngDevMode ? [{ debugName: "processTypes" }] : []));
9399
+ /**
9400
+ * Il `processType` del documento non e' fra quelli del dizionario. Non e' un caso di scuola:
9401
+ * basta che il dizionario arrivi senza `processTypes`, o che il backend salvi una scrittura
9402
+ * diversa da quella che elenca, e la select — che non ha un'opzione corrispondente — si
9403
+ * presenta bianca su un flow che invece ha il suo tipo. Il template aggiunge l'opzione
9404
+ * mancante; qui si decide solo se serve.
9405
+ */
9406
+ isProcessTypeOutOfCatalog = computed(() => {
9407
+ const current = this.store.document().processType;
9408
+ return !!current && !this.processTypes().some((type) => type.value === current);
9409
+ }, ...(ngDevMode ? [{ debugName: "isProcessTypeOutOfCatalog" }] : []));
8880
9410
  /** `AutoLaunched` + screen e' un errore di validazione: si segnala subito (§13.6). */
8881
9411
  hasScreensInAutoLaunched = computed(() => this.store.document().processType === 'AutoLaunched' &&
8882
9412
  (this.store.document().screens?.length ?? 0) > 0, ...(ngDevMode ? [{ debugName: "hasScreensInAutoLaunched" }] : []));
@@ -9219,7 +9749,7 @@ class FlowBuilderComponent {
9219
9749
  FlowValidationStore,
9220
9750
  FlowEditorSession,
9221
9751
  FlowLayoutService,
9222
- ], ngImport: i0, template: "<div class=\"fb-builder\" [attr.data-fb-theme]=\"null\">\r\n <header class=\"fb-top\">\r\n <div class=\"fb-top__identity\">\r\n <input\r\n class=\"fb-top__label\"\r\n [value]=\"document().label || ''\"\r\n placeholder=\"Nome del flow\"\r\n aria-label=\"Nome del flow\"\r\n [disabled]=\"!isEditable()\"\r\n (input)=\"setLabel($any($event.target).value)\"\r\n />\r\n <div class=\"fb-top__meta\">\r\n <input\r\n class=\"fb-top__name\"\r\n [value]=\"document().fullName || ''\"\r\n placeholder=\"NomeTecnico\"\r\n aria-label=\"Nome tecnico del flow\"\r\n [disabled]=\"!isEditable()\"\r\n (input)=\"setFullName($any($event.target).value)\"\r\n />\r\n <select\r\n class=\"fb-top__process\"\r\n [fbValue]=\"document().processType || ''\"\r\n aria-label=\"Tipo di flow\"\r\n [disabled]=\"!isEditable()\"\r\n (change)=\"setProcessType($any($event.target).value)\"\r\n >\r\n @for (type of processTypes(); track type.value) {\r\n <option [value]=\"type.value\">{{ type.label }}</option>\r\n }\r\n </select>\r\n <span class=\"fb-top__status\">{{ statusLabel() }}</span>\r\n @if (session.version() !== null) {\r\n <span class=\"fb-top__version\">v{{ session.version() }}</span>\r\n }\r\n @if (isDirty()) {\r\n <span class=\"fb-top__dirty\" title=\"Ci sono modifiche non salvate\">modificato</span>\r\n }\r\n </div>\r\n </div>\r\n\r\n <div class=\"fb-top__actions\">\r\n <button type=\"button\" class=\"fb-btn\" [disabled]=\"!canUndo()\" aria-label=\"Annulla\" (click)=\"undo()\">\u21B6</button>\r\n <button type=\"button\" class=\"fb-btn\" [disabled]=\"!canRedo()\" aria-label=\"Ripeti\" (click)=\"redo()\">\u21B7</button>\r\n <button type=\"button\" class=\"fb-btn\" title=\"Ricalcola le posizioni\" (click)=\"autoLayout()\">Riordina</button>\r\n @if (isDialogMode()) {\r\n <!-- Il doppio click sul node fa la stessa cosa, ma non si vede: questo comando s\u00EC. -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"!selectedName()\"\r\n title=\"Apri il dettaglio dell\u2019elemento selezionato\"\r\n (click)=\"openSelectedElement()\"\r\n >\r\n Dettaglio\r\n </button>\r\n }\r\n <button type=\"button\" class=\"fb-btn\" [disabled]=\"isBusy()\" (click)=\"validateNow()\">Valida</button>\r\n\r\n @switch (primaryCommand()) {\r\n @case ('newVersion') {\r\n <!-- Su una versione non modificabile il comando primario e' \"Nuova versione\" (\u00A78.1). -->\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" [disabled]=\"isBusy()\" (click)=\"createNewVersion()\">\r\n Nuova versione\r\n </button>\r\n }\r\n @case ('create') {\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" [disabled]=\"isBusy()\" (click)=\"save()\">\r\n Crea\r\n </button>\r\n }\r\n @default {\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" [disabled]=\"isBusy()\" (click)=\"save()\">\r\n Salva\r\n </button>\r\n }\r\n }\r\n\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"isBusy() || !canActivate()\"\r\n [title]=\"\r\n canActivate()\r\n ? 'Attiva questa versione'\r\n : 'L\u2019attivazione esige zero errori: correggili nel pannello dei problemi'\r\n \"\r\n (click)=\"activate()\"\r\n >\r\n Attiva\r\n </button>\r\n </div>\r\n </header>\r\n\r\n @if (conflict()) {\r\n <!-- \u00A79.3: qualcun altro ha salvato. Due strade, entrambe offerte. -->\r\n <div class=\"fb-banner fb-banner--warn\" role=\"alert\">\r\n <span>\r\n {{ conflict()?.message }}\r\n @if (conflict()?.conflictingAuthor) {\r\n Ha salvato {{ conflict()?.conflictingAuthor }}.\r\n }\r\n </span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--icon\" (click)=\"reloadAfterConflict()\">Ricarica</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--icon\" (click)=\"saveAsNewVersionAfterConflict()\">\r\n Salva come nuova versione\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" (click)=\"dismissConflict()\">\u00D7</button>\r\n </div>\r\n }\r\n\r\n @if (notice()) {\r\n <div\r\n class=\"fb-banner\"\r\n [class.fb-banner--error]=\"notice()?.kind === 'error'\"\r\n role=\"status\"\r\n >\r\n <span>{{ notice()?.message }}</span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" aria-label=\"Chiudi\" (click)=\"dismissNotice()\">\r\n \u00D7\r\n </button>\r\n </div>\r\n }\r\n\r\n @if (hasScreensInAutoLaunched()) {\r\n <div class=\"fb-banner fb-banner--error\">\r\n Un flow AutoLaunched che contiene screen e\u2019 un errore di validazione: non c\u2019e\u2019 nessuno a cui mostrarli.\r\n </div>\r\n }\r\n @if (isOrchestrationWithoutStages()) {\r\n <div class=\"fb-banner fb-banner--warn\">\r\n Un flow Orchestration senza stage di orchestrazione viene segnalato dalla validazione\r\n (ORCHESTRATION_WITHOUT_STAGES).\r\n </div>\r\n }\r\n @if (hasNoScreensInScreenFlow()) {\r\n <div class=\"fb-banner fb-banner--warn\">\r\n Un flow di tipo Screen senza nessuno screen viene segnalato dalla validazione.\r\n </div>\r\n }\r\n @if (!isEditable()) {\r\n <div class=\"fb-banner\">\r\n Questa versione e\u2019 in sola lettura: per modificarla creane una nuova.\r\n </div>\r\n }\r\n\r\n <div class=\"fb-main\">\r\n <aside class=\"fb-main__palette\">\r\n <fb-element-palette [processType]=\"document().processType\" (elementPicked)=\"onElementPicked($event)\" />\r\n </aside>\r\n\r\n <div class=\"fb-main__center\">\r\n <fb-flow-canvas\r\n class=\"fb-main__canvas\"\r\n [selectedName]=\"selectedName()\"\r\n [outline]=\"outline()\"\r\n (selectionChange)=\"onSelectionChange($event)\"\r\n (nodeOpened)=\"onNodeOpened($event)\"\r\n (nodeRemoveRequested)=\"onRemoveNode($event)\"\r\n (nodeDuplicateRequested)=\"onDuplicateNode($event)\"\r\n (elementDropped)=\"onElementDropped($event)\"\r\n />\r\n\r\n @if (showProblems()) {\r\n <fb-problems-panel\r\n class=\"fb-main__problems\"\r\n (elementFocused)=\"focusElement($event)\"\r\n (closed)=\"toggleProblems()\"\r\n />\r\n } @else {\r\n <button type=\"button\" class=\"fb-main__problems-toggle\" (click)=\"toggleProblems()\">\r\n Problemi\r\n @if (errorCount()) {\r\n <span class=\"fb-main__count fb-main__count--error\">{{ errorCount() }}</span>\r\n }\r\n @if (warningCount()) {\r\n <span class=\"fb-main__count fb-main__count--warn\">{{ warningCount() }}</span>\r\n }\r\n </button>\r\n }\r\n </div>\r\n\r\n <aside class=\"fb-main__side\">\r\n <nav class=\"fb-side__tabs\" aria-label=\"Pannelli\">\r\n @if (!isDialogMode()) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-side__tab\"\r\n [class.fb-side__tab--active]=\"sidePanel() === 'inspector'\"\r\n (click)=\"setPanel('inspector')\"\r\n >\r\n Elemento\r\n </button>\r\n }\r\n <button\r\n type=\"button\"\r\n class=\"fb-side__tab\"\r\n [class.fb-side__tab--active]=\"sidePanel() === 'resources'\"\r\n (click)=\"setPanel('resources')\"\r\n >\r\n Risorse\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-side__tab\"\r\n [class.fb-side__tab--active]=\"sidePanel() === 'versions'\"\r\n (click)=\"setPanel('versions')\"\r\n >\r\n Versioni\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-side__tab\"\r\n [class.fb-side__tab--active]=\"sidePanel() === 'debug'\"\r\n (click)=\"setPanel('debug')\"\r\n >\r\n Prova\r\n </button>\r\n </nav>\r\n\r\n <div class=\"fb-side__content\">\r\n @switch (sidePanel()) {\r\n @case ('inspector') {\r\n <fb-element-inspector\r\n [selectedName]=\"selectedName()\"\r\n (removeRequested)=\"onRemoveNode($event)\"\r\n (duplicateRequested)=\"onDuplicateNode($event)\"\r\n (closed)=\"setPanel('resources')\"\r\n />\r\n }\r\n @case ('resources') {\r\n <fb-resource-panel (closed)=\"setPanel('inspector')\" />\r\n }\r\n @case ('versions') {\r\n <fb-version-panel\r\n (versionOpened)=\"openVersion($event)\"\r\n (notice)=\"showNotice($event)\"\r\n (closed)=\"setPanel('inspector')\"\r\n />\r\n }\r\n @case ('debug') {\r\n <!-- Evidenzia sul canvas senza rubare il pannello: l'interview e\u2019 in corso. -->\r\n <fb-debug-panel (elementFocused)=\"highlightElement($event)\" (closed)=\"setPanel('inspector')\" />\r\n }\r\n }\r\n </div>\r\n\r\n <footer class=\"fb-side__footer\">\r\n <label class=\"fb-btn fb-btn--icon\">\r\n Importa JSON\r\n <input type=\"file\" accept=\"application/json,.json\" hidden (change)=\"onFileSelected($event)\" />\r\n </label>\r\n </footer>\r\n </aside>\r\n </div>\r\n\r\n @if (isDialogMode() && isDialogOpen() && selectedName()) {\r\n <!-- La dialog sta dentro il builder, non nel body: la libreria e\u2019 innestabile. -->\r\n <fb-element-dialog\r\n [selectedName]=\"selectedName()\"\r\n (closed)=\"closeDialog()\"\r\n (removeRequested)=\"onRemoveNode($event)\"\r\n (duplicateRequested)=\"onDuplicateNode($event)\"\r\n />\r\n }\r\n</div>\r\n", styles: [":host{display:block;width:100%;height:100%;min-height:0;font:inherit;color:var(--fb-text, #1d2939)}.fb-builder{position:relative;display:flex;flex-direction:column;width:100%;height:100%;min-height:0;background:var(--fb-surface, #fff)}.fb-top{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:9px 14px;border-bottom:1px solid var(--fb-border, #e2e5eb);background:var(--fb-surface, #fff)}.fb-top__identity{min-width:0}.fb-top__label{width:100%;max-width:420px;padding:2px 4px;border:1px solid transparent;border-radius:4px;background:transparent;color:var(--fb-text, #1d2939);font:inherit;font-size:15px;font-weight:600}.fb-top__label:hover:not(:disabled),.fb-top__label:focus-visible{border-color:var(--fb-border, #d6dae1);background:var(--fb-surface, #fff)}.fb-top__meta{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-top:2px}.fb-top__name{width:180px;padding:1px 4px;border:1px solid transparent;border-radius:4px;background:transparent;color:var(--fb-text-muted, #667085);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px}.fb-top__name:hover:not(:disabled),.fb-top__name:focus-visible{border-color:var(--fb-border, #d6dae1)}.fb-top__process{padding:1px 4px;border:1px solid var(--fb-border-subtle, #e6e9ee);border-radius:4px;background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:11px}.fb-top__status,.fb-top__version,.fb-top__dirty{padding:2px 7px;border-radius:999px;background:var(--fb-surface-sunken, #eef0f4);color:var(--fb-text-muted, #6b7086);font-size:10px;font-weight:600}.fb-top__version{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.fb-top__dirty{background:color-mix(in srgb,var(--fb-warning, #b7791f) 16%,transparent);color:var(--fb-warning, #b7791f)}.fb-top__actions{display:flex;flex-wrap:wrap;gap:4px}.fb-banner{display:flex;flex-wrap:wrap;align-items:center;gap:8px;padding:6px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee);background:color-mix(in srgb,var(--fb-accent, #2f6feb) 7%,transparent);font-size:11px}.fb-banner--warn{background:color-mix(in srgb,var(--fb-warning, #b7791f) 12%,transparent);color:var(--fb-warning, #b7791f)}.fb-banner--error{background:color-mix(in srgb,var(--fb-error, #c9372c) 10%,transparent);color:var(--fb-error, #c9372c)}.fb-banner>span{flex:1;min-width:200px}.fb-main{display:flex;flex:1;min-height:0}.fb-main__palette{flex:0 0 190px;min-width:0}.fb-main__center{display:flex;flex:1;flex-direction:column;min-width:0;min-height:0}.fb-main__canvas{flex:1;min-height:0}.fb-main__problems{flex:0 0 auto;height:220px}.fb-main__problems-toggle{display:flex;align-items:center;gap:6px;padding:4px 12px;border:0;border-top:1px solid var(--fb-border, #d6dae1);background:var(--fb-surface-alt, #f8f9fb);color:var(--fb-text-muted, #667085);font:inherit;font-size:11px;cursor:pointer}.fb-main__count{padding:0 5px;border-radius:8px;background:var(--fb-border, #d6dae1);font-size:9px;font-weight:700}.fb-main__count--error{background:color-mix(in srgb,var(--fb-error, #c9372c) 16%,transparent);color:var(--fb-error, #c9372c)}.fb-main__count--warn{background:color-mix(in srgb,var(--fb-warning, #b7791f) 16%,transparent);color:var(--fb-warning, #b7791f)}.fb-main__side{display:flex;flex-direction:column;flex:0 0 340px;min-width:0;min-height:0;border-left:1px solid var(--fb-border, #d6dae1);background:var(--fb-surface, #fff)}.fb-side__tabs{display:flex;gap:2px;margin:8px 10px;padding:3px;border-radius:var(--fb-radius, 10px);background:var(--fb-surface-sunken, #eef0f4)}.fb-side__tab{flex:1;padding:5px 8px;border:0;border-radius:var(--fb-radius-xs, 6px);background:transparent;color:var(--fb-text-muted, #6b7086);font:inherit;font-size:11px;cursor:pointer;transition:background .12s ease,color .12s ease}.fb-side__tab:hover:not(.fb-side__tab--active){color:var(--fb-text, #1a1c23)}.fb-side__tab--active{background:var(--fb-surface, #fff);box-shadow:var(--fb-shadow-sm, 0 1px 2px rgb(16 24 40 / 6%));color:var(--fb-text, #1a1c23);font-weight:600}.fb-side__content{flex:1;min-height:0;border-top:1px solid var(--fb-border-subtle, #eef0f4)}.fb-side__content>*{height:100%}.fb-side__footer{display:flex;gap:6px;padding:6px 8px;border-top:1px solid var(--fb-border-subtle, #e6e9ee)}@media(max-width:1200px){.fb-main__palette{flex-basis:150px}.fb-main__side{flex-basis:290px}}\n"], dependencies: [{ kind: "component", type: DebugPanelComponent, selector: "fb-debug-panel", outputs: ["closed", "elementFocused"] }, { kind: "component", type: ElementDialogComponent, selector: "fb-element-dialog", inputs: ["selectedName"], outputs: ["closed", "removeRequested", "duplicateRequested"] }, { kind: "component", type: ElementInspectorComponent, selector: "fb-element-inspector", inputs: ["selectedName", "showHeader"], outputs: ["closed", "removeRequested", "duplicateRequested"] }, { kind: "component", type: ElementPaletteComponent, selector: "fb-element-palette", inputs: ["processType"], outputs: ["elementPicked"] }, { kind: "component", type: FlowCanvasComponent, selector: "fb-flow-canvas", inputs: ["selectedName", "outline"], outputs: ["selectionChange", "nodeOpened", "nodeRemoveRequested", "nodeDuplicateRequested", "elementDropped"] }, { kind: "component", type: ProblemsPanelComponent, selector: "fb-problems-panel", outputs: ["elementFocused", "closed"] }, { kind: "component", type: ResourcePanelComponent, selector: "fb-resource-panel", outputs: ["closed"] }, { kind: "component", type: VersionPanelComponent, selector: "fb-version-panel", outputs: ["closed", "versionOpened", "notice"] }, { kind: "directive", type: SelectValueDirective, selector: "select[fbValue]", inputs: ["fbValue"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
9752
+ ], ngImport: i0, template: "<div class=\"fb-builder\" [attr.data-fb-theme]=\"null\">\r\n <header class=\"fb-top\">\r\n <div class=\"fb-top__identity\">\r\n <input\r\n class=\"fb-top__label\"\r\n [value]=\"document().label || ''\"\r\n placeholder=\"Nome del flow\"\r\n aria-label=\"Nome del flow\"\r\n [disabled]=\"!isEditable()\"\r\n (input)=\"setLabel($any($event.target).value)\"\r\n />\r\n <div class=\"fb-top__meta\">\r\n <input\r\n class=\"fb-top__name\"\r\n [value]=\"document().fullName || ''\"\r\n placeholder=\"NomeTecnico\"\r\n aria-label=\"Nome tecnico del flow\"\r\n [disabled]=\"!isEditable()\"\r\n (input)=\"setFullName($any($event.target).value)\"\r\n />\r\n <select\r\n class=\"fb-top__process\"\r\n [fbValue]=\"document().processType || ''\"\r\n aria-label=\"Tipo di flow\"\r\n [disabled]=\"!isEditable()\"\r\n (change)=\"setProcessType($any($event.target).value)\"\r\n >\r\n <!--\r\n Il segnaposto esiste perche' un `<select>` senza opzione corrispondente non e'\r\n \u00ABvuoto\u00BB: e' a `selectedIndex = -1`, e mostra una casella bianca. Su un flow nuovo\r\n (`processType` non ancora scelto) e' questa la riga che si vede, disabilitata\r\n perche' non e' un valore valido da salvare.\r\n -->\r\n @if (!document().processType) {\r\n <option value=\"\" disabled>\u2014 tipo di flow \u2014</option>\r\n }\r\n @for (type of processTypes(); track type.value) {\r\n <option [value]=\"type.value\">{{ type.label }}</option>\r\n }\r\n <!--\r\n Il tipo del documento che il dizionario non conosce: senza questa opzione la select\r\n resta bianca su un flow che il backend ha salvato con un `processType` fuori\r\n catalogo (o quando `processTypes` manca dalla risposta dei dizionari), e sembra che\r\n l'editor non abbia ricaricato il flow. Va mostrato **senza** riscrivere il\r\n documento: il valore e' del backend, non nostro da correggere.\r\n -->\r\n @if (isProcessTypeOutOfCatalog()) {\r\n <option [value]=\"document().processType\">{{ document().processType }} (fuori catalogo)</option>\r\n }\r\n </select>\r\n <span class=\"fb-top__status\">{{ statusLabel() }}</span>\r\n @if (session.version() !== null) {\r\n <span class=\"fb-top__version\">v{{ session.version() }}</span>\r\n }\r\n @if (isDirty()) {\r\n <span class=\"fb-top__dirty\" title=\"Ci sono modifiche non salvate\">modificato</span>\r\n }\r\n </div>\r\n </div>\r\n\r\n <div class=\"fb-top__actions\">\r\n <button type=\"button\" class=\"fb-btn\" [disabled]=\"!canUndo()\" aria-label=\"Annulla\" (click)=\"undo()\">\u21B6</button>\r\n <button type=\"button\" class=\"fb-btn\" [disabled]=\"!canRedo()\" aria-label=\"Ripeti\" (click)=\"redo()\">\u21B7</button>\r\n <button type=\"button\" class=\"fb-btn\" title=\"Ricalcola le posizioni\" (click)=\"autoLayout()\">Riordina</button>\r\n @if (isDialogMode()) {\r\n <!-- Il doppio click sul node fa la stessa cosa, ma non si vede: questo comando s\u00EC. -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"!selectedName()\"\r\n title=\"Apri il dettaglio dell\u2019elemento selezionato\"\r\n (click)=\"openSelectedElement()\"\r\n >\r\n Dettaglio\r\n </button>\r\n }\r\n <button type=\"button\" class=\"fb-btn\" [disabled]=\"isBusy()\" (click)=\"validateNow()\">Valida</button>\r\n\r\n @switch (primaryCommand()) {\r\n @case ('newVersion') {\r\n <!-- Su una versione non modificabile il comando primario e' \"Nuova versione\" (\u00A78.1). -->\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" [disabled]=\"isBusy()\" (click)=\"createNewVersion()\">\r\n Nuova versione\r\n </button>\r\n }\r\n @case ('create') {\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" [disabled]=\"isBusy()\" (click)=\"save()\">\r\n Crea\r\n </button>\r\n }\r\n @default {\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" [disabled]=\"isBusy()\" (click)=\"save()\">\r\n Salva\r\n </button>\r\n }\r\n }\r\n\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"isBusy() || !canActivate()\"\r\n [title]=\"\r\n canActivate()\r\n ? 'Attiva questa versione'\r\n : 'L\u2019attivazione esige zero errori: correggili nel pannello dei problemi'\r\n \"\r\n (click)=\"activate()\"\r\n >\r\n Attiva\r\n </button>\r\n </div>\r\n </header>\r\n\r\n @if (conflict()) {\r\n <!-- \u00A79.3: qualcun altro ha salvato. Due strade, entrambe offerte. -->\r\n <div class=\"fb-banner fb-banner--warn\" role=\"alert\">\r\n <span>\r\n {{ conflict()?.message }}\r\n @if (conflict()?.conflictingAuthor) {\r\n Ha salvato {{ conflict()?.conflictingAuthor }}.\r\n }\r\n </span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--icon\" (click)=\"reloadAfterConflict()\">Ricarica</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--icon\" (click)=\"saveAsNewVersionAfterConflict()\">\r\n Salva come nuova versione\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" (click)=\"dismissConflict()\">\u00D7</button>\r\n </div>\r\n }\r\n\r\n @if (notice()) {\r\n <div\r\n class=\"fb-banner\"\r\n [class.fb-banner--error]=\"notice()?.kind === 'error'\"\r\n role=\"status\"\r\n >\r\n <span>{{ notice()?.message }}</span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" aria-label=\"Chiudi\" (click)=\"dismissNotice()\">\r\n \u00D7\r\n </button>\r\n </div>\r\n }\r\n\r\n @if (hasScreensInAutoLaunched()) {\r\n <div class=\"fb-banner fb-banner--error\">\r\n Un flow AutoLaunched che contiene screen e\u2019 un errore di validazione: non c\u2019e\u2019 nessuno a cui mostrarli.\r\n </div>\r\n }\r\n @if (isOrchestrationWithoutStages()) {\r\n <div class=\"fb-banner fb-banner--warn\">\r\n Un flow Orchestration senza stage di orchestrazione viene segnalato dalla validazione\r\n (ORCHESTRATION_WITHOUT_STAGES).\r\n </div>\r\n }\r\n @if (hasNoScreensInScreenFlow()) {\r\n <div class=\"fb-banner fb-banner--warn\">\r\n Un flow di tipo Screen senza nessuno screen viene segnalato dalla validazione.\r\n </div>\r\n }\r\n @if (!isEditable()) {\r\n <div class=\"fb-banner\">\r\n Questa versione e\u2019 in sola lettura: per modificarla creane una nuova.\r\n </div>\r\n }\r\n\r\n <div class=\"fb-main\">\r\n <aside class=\"fb-main__palette\">\r\n <fb-element-palette [processType]=\"document().processType\" (elementPicked)=\"onElementPicked($event)\" />\r\n </aside>\r\n\r\n <div class=\"fb-main__center\">\r\n <fb-flow-canvas\r\n class=\"fb-main__canvas\"\r\n [selectedName]=\"selectedName()\"\r\n [outline]=\"outline()\"\r\n [isEditable]=\"isEditable()\"\r\n (selectionChange)=\"onSelectionChange($event)\"\r\n (nodeOpened)=\"onNodeOpened($event)\"\r\n (nodeRemoveRequested)=\"onRemoveNode($event)\"\r\n (nodeDuplicateRequested)=\"onDuplicateNode($event)\"\r\n (elementDropped)=\"onElementDropped($event)\"\r\n />\r\n\r\n @if (showProblems()) {\r\n <fb-problems-panel\r\n class=\"fb-main__problems\"\r\n (elementFocused)=\"focusElement($event)\"\r\n (closed)=\"toggleProblems()\"\r\n />\r\n } @else {\r\n <button type=\"button\" class=\"fb-main__problems-toggle\" (click)=\"toggleProblems()\">\r\n Problemi\r\n @if (errorCount()) {\r\n <span class=\"fb-main__count fb-main__count--error\">{{ errorCount() }}</span>\r\n }\r\n @if (warningCount()) {\r\n <span class=\"fb-main__count fb-main__count--warn\">{{ warningCount() }}</span>\r\n }\r\n </button>\r\n }\r\n </div>\r\n\r\n <aside class=\"fb-main__side\">\r\n <nav class=\"fb-side__tabs\" aria-label=\"Pannelli\">\r\n @if (!isDialogMode()) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-side__tab\"\r\n [class.fb-side__tab--active]=\"sidePanel() === 'inspector'\"\r\n (click)=\"setPanel('inspector')\"\r\n >\r\n Elemento\r\n </button>\r\n }\r\n <button\r\n type=\"button\"\r\n class=\"fb-side__tab\"\r\n [class.fb-side__tab--active]=\"sidePanel() === 'resources'\"\r\n (click)=\"setPanel('resources')\"\r\n >\r\n Risorse\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-side__tab\"\r\n [class.fb-side__tab--active]=\"sidePanel() === 'versions'\"\r\n (click)=\"setPanel('versions')\"\r\n >\r\n Versioni\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-side__tab\"\r\n [class.fb-side__tab--active]=\"sidePanel() === 'debug'\"\r\n (click)=\"setPanel('debug')\"\r\n >\r\n Prova\r\n </button>\r\n </nav>\r\n\r\n <div class=\"fb-side__content\">\r\n @switch (sidePanel()) {\r\n @case ('inspector') {\r\n <fb-element-inspector\r\n [selectedName]=\"selectedName()\"\r\n (removeRequested)=\"onRemoveNode($event)\"\r\n (duplicateRequested)=\"onDuplicateNode($event)\"\r\n (closed)=\"setPanel('resources')\"\r\n />\r\n }\r\n @case ('resources') {\r\n <fb-resource-panel (closed)=\"setPanel('inspector')\" />\r\n }\r\n @case ('versions') {\r\n <fb-version-panel\r\n (versionOpened)=\"openVersion($event)\"\r\n (notice)=\"showNotice($event)\"\r\n (closed)=\"setPanel('inspector')\"\r\n />\r\n }\r\n @case ('debug') {\r\n <!-- Evidenzia sul canvas senza rubare il pannello: l'interview e\u2019 in corso. -->\r\n <fb-debug-panel (elementFocused)=\"highlightElement($event)\" (closed)=\"setPanel('inspector')\" />\r\n }\r\n }\r\n </div>\r\n\r\n <footer class=\"fb-side__footer\">\r\n <label class=\"fb-btn fb-btn--icon\">\r\n Importa JSON\r\n <input type=\"file\" accept=\"application/json,.json\" hidden (change)=\"onFileSelected($event)\" />\r\n </label>\r\n </footer>\r\n </aside>\r\n </div>\r\n\r\n @if (isDialogMode() && isDialogOpen() && selectedName()) {\r\n <!-- La dialog sta dentro il builder, non nel body: la libreria e\u2019 innestabile. -->\r\n <fb-element-dialog\r\n [selectedName]=\"selectedName()\"\r\n (closed)=\"closeDialog()\"\r\n (removeRequested)=\"onRemoveNode($event)\"\r\n (duplicateRequested)=\"onDuplicateNode($event)\"\r\n />\r\n }\r\n</div>\r\n", styles: [":host{display:block;width:100%;height:100%;min-height:0;font:inherit;color:var(--fb-text, #1d2939)}.fb-builder{position:relative;display:flex;flex-direction:column;width:100%;height:100%;min-height:0;background:var(--fb-surface, #fff)}.fb-top{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:9px 14px;border-bottom:1px solid var(--fb-border, #e2e5eb);background:var(--fb-surface, #fff)}.fb-top__identity{min-width:0}.fb-top__label{width:100%;max-width:420px;padding:2px 4px;border:1px solid transparent;border-radius:4px;background:transparent;color:var(--fb-text, #1d2939);font:inherit;font-size:15px;font-weight:600}.fb-top__label:hover:not(:disabled),.fb-top__label:focus-visible{border-color:var(--fb-border, #d6dae1);background:var(--fb-surface, #fff)}.fb-top__meta{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-top:2px}.fb-top__name{width:180px;padding:1px 4px;border:1px solid transparent;border-radius:4px;background:transparent;color:var(--fb-text-muted, #667085);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px}.fb-top__name:hover:not(:disabled),.fb-top__name:focus-visible{border-color:var(--fb-border, #d6dae1)}.fb-top__process{padding:1px 4px;border:1px solid var(--fb-border-subtle, #e6e9ee);border-radius:4px;background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:11px}.fb-top__status,.fb-top__version,.fb-top__dirty{padding:2px 7px;border-radius:999px;background:var(--fb-surface-sunken, #eef0f4);color:var(--fb-text-muted, #6b7086);font-size:10px;font-weight:600}.fb-top__version{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.fb-top__dirty{background:color-mix(in srgb,var(--fb-warning, #b7791f) 16%,transparent);color:var(--fb-warning, #b7791f)}.fb-top__actions{display:flex;flex-wrap:wrap;gap:4px}.fb-banner{display:flex;flex-wrap:wrap;align-items:center;gap:8px;padding:6px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee);background:color-mix(in srgb,var(--fb-accent, #2f6feb) 7%,transparent);font-size:11px}.fb-banner--warn{background:color-mix(in srgb,var(--fb-warning, #b7791f) 12%,transparent);color:var(--fb-warning, #b7791f)}.fb-banner--error{background:color-mix(in srgb,var(--fb-error, #c9372c) 10%,transparent);color:var(--fb-error, #c9372c)}.fb-banner>span{flex:1;min-width:200px}.fb-main{display:flex;flex:1;min-height:0}.fb-main__palette{flex:0 0 190px;min-width:0}.fb-main__center{display:flex;flex:1;flex-direction:column;min-width:0;min-height:0}.fb-main__canvas{flex:1;min-height:0}.fb-main__problems{flex:0 0 auto;height:220px}.fb-main__problems-toggle{display:flex;align-items:center;gap:6px;padding:4px 12px;border:0;border-top:1px solid var(--fb-border, #d6dae1);background:var(--fb-surface-alt, #f8f9fb);color:var(--fb-text-muted, #667085);font:inherit;font-size:11px;cursor:pointer}.fb-main__count{padding:0 5px;border-radius:8px;background:var(--fb-border, #d6dae1);font-size:9px;font-weight:700}.fb-main__count--error{background:color-mix(in srgb,var(--fb-error, #c9372c) 16%,transparent);color:var(--fb-error, #c9372c)}.fb-main__count--warn{background:color-mix(in srgb,var(--fb-warning, #b7791f) 16%,transparent);color:var(--fb-warning, #b7791f)}.fb-main__side{display:flex;flex-direction:column;flex:0 0 340px;min-width:0;min-height:0;border-left:1px solid var(--fb-border, #d6dae1);background:var(--fb-surface, #fff)}.fb-side__tabs{display:flex;gap:2px;margin:8px 10px;padding:3px;border-radius:var(--fb-radius, 10px);background:var(--fb-surface-sunken, #eef0f4)}.fb-side__tab{flex:1;padding:5px 8px;border:0;border-radius:var(--fb-radius-xs, 6px);background:transparent;color:var(--fb-text-muted, #6b7086);font:inherit;font-size:11px;cursor:pointer;transition:background .12s ease,color .12s ease}.fb-side__tab:hover:not(.fb-side__tab--active){color:var(--fb-text, #1a1c23)}.fb-side__tab--active{background:var(--fb-surface, #fff);box-shadow:var(--fb-shadow-sm, 0 1px 2px rgb(16 24 40 / 6%));color:var(--fb-text, #1a1c23);font-weight:600}.fb-side__content{flex:1;min-height:0;border-top:1px solid var(--fb-border-subtle, #eef0f4)}.fb-side__content>*{height:100%}.fb-side__footer{display:flex;gap:6px;padding:6px 8px;border-top:1px solid var(--fb-border-subtle, #e6e9ee)}@media(max-width:1200px){.fb-main__palette{flex-basis:150px}.fb-main__side{flex-basis:290px}}\n"], dependencies: [{ kind: "component", type: DebugPanelComponent, selector: "fb-debug-panel", outputs: ["closed", "elementFocused"] }, { kind: "component", type: ElementDialogComponent, selector: "fb-element-dialog", inputs: ["selectedName"], outputs: ["closed", "removeRequested", "duplicateRequested"] }, { kind: "component", type: ElementInspectorComponent, selector: "fb-element-inspector", inputs: ["selectedName", "showHeader"], outputs: ["closed", "removeRequested", "duplicateRequested"] }, { kind: "component", type: ElementPaletteComponent, selector: "fb-element-palette", inputs: ["processType"], outputs: ["elementPicked"] }, { kind: "component", type: FlowCanvasComponent, selector: "fb-flow-canvas", inputs: ["selectedName", "outline", "isEditable"], outputs: ["selectionChange", "nodeOpened", "nodeRemoveRequested", "nodeDuplicateRequested", "elementDropped"] }, { kind: "component", type: ProblemsPanelComponent, selector: "fb-problems-panel", outputs: ["elementFocused", "closed"] }, { kind: "component", type: ResourcePanelComponent, selector: "fb-resource-panel", outputs: ["closed"] }, { kind: "component", type: VersionPanelComponent, selector: "fb-version-panel", outputs: ["closed", "versionOpened", "notice"] }, { kind: "directive", type: SelectValueDirective, selector: "select[fbValue]", inputs: ["fbValue"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
9223
9753
  }
9224
9754
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImport: i0, type: FlowBuilderComponent, decorators: [{
9225
9755
  type: Component,
@@ -9237,12 +9767,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImpo
9237
9767
  FlowValidationStore,
9238
9768
  FlowEditorSession,
9239
9769
  FlowLayoutService,
9240
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"fb-builder\" [attr.data-fb-theme]=\"null\">\r\n <header class=\"fb-top\">\r\n <div class=\"fb-top__identity\">\r\n <input\r\n class=\"fb-top__label\"\r\n [value]=\"document().label || ''\"\r\n placeholder=\"Nome del flow\"\r\n aria-label=\"Nome del flow\"\r\n [disabled]=\"!isEditable()\"\r\n (input)=\"setLabel($any($event.target).value)\"\r\n />\r\n <div class=\"fb-top__meta\">\r\n <input\r\n class=\"fb-top__name\"\r\n [value]=\"document().fullName || ''\"\r\n placeholder=\"NomeTecnico\"\r\n aria-label=\"Nome tecnico del flow\"\r\n [disabled]=\"!isEditable()\"\r\n (input)=\"setFullName($any($event.target).value)\"\r\n />\r\n <select\r\n class=\"fb-top__process\"\r\n [fbValue]=\"document().processType || ''\"\r\n aria-label=\"Tipo di flow\"\r\n [disabled]=\"!isEditable()\"\r\n (change)=\"setProcessType($any($event.target).value)\"\r\n >\r\n @for (type of processTypes(); track type.value) {\r\n <option [value]=\"type.value\">{{ type.label }}</option>\r\n }\r\n </select>\r\n <span class=\"fb-top__status\">{{ statusLabel() }}</span>\r\n @if (session.version() !== null) {\r\n <span class=\"fb-top__version\">v{{ session.version() }}</span>\r\n }\r\n @if (isDirty()) {\r\n <span class=\"fb-top__dirty\" title=\"Ci sono modifiche non salvate\">modificato</span>\r\n }\r\n </div>\r\n </div>\r\n\r\n <div class=\"fb-top__actions\">\r\n <button type=\"button\" class=\"fb-btn\" [disabled]=\"!canUndo()\" aria-label=\"Annulla\" (click)=\"undo()\">\u21B6</button>\r\n <button type=\"button\" class=\"fb-btn\" [disabled]=\"!canRedo()\" aria-label=\"Ripeti\" (click)=\"redo()\">\u21B7</button>\r\n <button type=\"button\" class=\"fb-btn\" title=\"Ricalcola le posizioni\" (click)=\"autoLayout()\">Riordina</button>\r\n @if (isDialogMode()) {\r\n <!-- Il doppio click sul node fa la stessa cosa, ma non si vede: questo comando s\u00EC. -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"!selectedName()\"\r\n title=\"Apri il dettaglio dell\u2019elemento selezionato\"\r\n (click)=\"openSelectedElement()\"\r\n >\r\n Dettaglio\r\n </button>\r\n }\r\n <button type=\"button\" class=\"fb-btn\" [disabled]=\"isBusy()\" (click)=\"validateNow()\">Valida</button>\r\n\r\n @switch (primaryCommand()) {\r\n @case ('newVersion') {\r\n <!-- Su una versione non modificabile il comando primario e' \"Nuova versione\" (\u00A78.1). -->\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" [disabled]=\"isBusy()\" (click)=\"createNewVersion()\">\r\n Nuova versione\r\n </button>\r\n }\r\n @case ('create') {\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" [disabled]=\"isBusy()\" (click)=\"save()\">\r\n Crea\r\n </button>\r\n }\r\n @default {\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" [disabled]=\"isBusy()\" (click)=\"save()\">\r\n Salva\r\n </button>\r\n }\r\n }\r\n\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"isBusy() || !canActivate()\"\r\n [title]=\"\r\n canActivate()\r\n ? 'Attiva questa versione'\r\n : 'L\u2019attivazione esige zero errori: correggili nel pannello dei problemi'\r\n \"\r\n (click)=\"activate()\"\r\n >\r\n Attiva\r\n </button>\r\n </div>\r\n </header>\r\n\r\n @if (conflict()) {\r\n <!-- \u00A79.3: qualcun altro ha salvato. Due strade, entrambe offerte. -->\r\n <div class=\"fb-banner fb-banner--warn\" role=\"alert\">\r\n <span>\r\n {{ conflict()?.message }}\r\n @if (conflict()?.conflictingAuthor) {\r\n Ha salvato {{ conflict()?.conflictingAuthor }}.\r\n }\r\n </span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--icon\" (click)=\"reloadAfterConflict()\">Ricarica</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--icon\" (click)=\"saveAsNewVersionAfterConflict()\">\r\n Salva come nuova versione\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" (click)=\"dismissConflict()\">\u00D7</button>\r\n </div>\r\n }\r\n\r\n @if (notice()) {\r\n <div\r\n class=\"fb-banner\"\r\n [class.fb-banner--error]=\"notice()?.kind === 'error'\"\r\n role=\"status\"\r\n >\r\n <span>{{ notice()?.message }}</span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" aria-label=\"Chiudi\" (click)=\"dismissNotice()\">\r\n \u00D7\r\n </button>\r\n </div>\r\n }\r\n\r\n @if (hasScreensInAutoLaunched()) {\r\n <div class=\"fb-banner fb-banner--error\">\r\n Un flow AutoLaunched che contiene screen e\u2019 un errore di validazione: non c\u2019e\u2019 nessuno a cui mostrarli.\r\n </div>\r\n }\r\n @if (isOrchestrationWithoutStages()) {\r\n <div class=\"fb-banner fb-banner--warn\">\r\n Un flow Orchestration senza stage di orchestrazione viene segnalato dalla validazione\r\n (ORCHESTRATION_WITHOUT_STAGES).\r\n </div>\r\n }\r\n @if (hasNoScreensInScreenFlow()) {\r\n <div class=\"fb-banner fb-banner--warn\">\r\n Un flow di tipo Screen senza nessuno screen viene segnalato dalla validazione.\r\n </div>\r\n }\r\n @if (!isEditable()) {\r\n <div class=\"fb-banner\">\r\n Questa versione e\u2019 in sola lettura: per modificarla creane una nuova.\r\n </div>\r\n }\r\n\r\n <div class=\"fb-main\">\r\n <aside class=\"fb-main__palette\">\r\n <fb-element-palette [processType]=\"document().processType\" (elementPicked)=\"onElementPicked($event)\" />\r\n </aside>\r\n\r\n <div class=\"fb-main__center\">\r\n <fb-flow-canvas\r\n class=\"fb-main__canvas\"\r\n [selectedName]=\"selectedName()\"\r\n [outline]=\"outline()\"\r\n (selectionChange)=\"onSelectionChange($event)\"\r\n (nodeOpened)=\"onNodeOpened($event)\"\r\n (nodeRemoveRequested)=\"onRemoveNode($event)\"\r\n (nodeDuplicateRequested)=\"onDuplicateNode($event)\"\r\n (elementDropped)=\"onElementDropped($event)\"\r\n />\r\n\r\n @if (showProblems()) {\r\n <fb-problems-panel\r\n class=\"fb-main__problems\"\r\n (elementFocused)=\"focusElement($event)\"\r\n (closed)=\"toggleProblems()\"\r\n />\r\n } @else {\r\n <button type=\"button\" class=\"fb-main__problems-toggle\" (click)=\"toggleProblems()\">\r\n Problemi\r\n @if (errorCount()) {\r\n <span class=\"fb-main__count fb-main__count--error\">{{ errorCount() }}</span>\r\n }\r\n @if (warningCount()) {\r\n <span class=\"fb-main__count fb-main__count--warn\">{{ warningCount() }}</span>\r\n }\r\n </button>\r\n }\r\n </div>\r\n\r\n <aside class=\"fb-main__side\">\r\n <nav class=\"fb-side__tabs\" aria-label=\"Pannelli\">\r\n @if (!isDialogMode()) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-side__tab\"\r\n [class.fb-side__tab--active]=\"sidePanel() === 'inspector'\"\r\n (click)=\"setPanel('inspector')\"\r\n >\r\n Elemento\r\n </button>\r\n }\r\n <button\r\n type=\"button\"\r\n class=\"fb-side__tab\"\r\n [class.fb-side__tab--active]=\"sidePanel() === 'resources'\"\r\n (click)=\"setPanel('resources')\"\r\n >\r\n Risorse\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-side__tab\"\r\n [class.fb-side__tab--active]=\"sidePanel() === 'versions'\"\r\n (click)=\"setPanel('versions')\"\r\n >\r\n Versioni\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-side__tab\"\r\n [class.fb-side__tab--active]=\"sidePanel() === 'debug'\"\r\n (click)=\"setPanel('debug')\"\r\n >\r\n Prova\r\n </button>\r\n </nav>\r\n\r\n <div class=\"fb-side__content\">\r\n @switch (sidePanel()) {\r\n @case ('inspector') {\r\n <fb-element-inspector\r\n [selectedName]=\"selectedName()\"\r\n (removeRequested)=\"onRemoveNode($event)\"\r\n (duplicateRequested)=\"onDuplicateNode($event)\"\r\n (closed)=\"setPanel('resources')\"\r\n />\r\n }\r\n @case ('resources') {\r\n <fb-resource-panel (closed)=\"setPanel('inspector')\" />\r\n }\r\n @case ('versions') {\r\n <fb-version-panel\r\n (versionOpened)=\"openVersion($event)\"\r\n (notice)=\"showNotice($event)\"\r\n (closed)=\"setPanel('inspector')\"\r\n />\r\n }\r\n @case ('debug') {\r\n <!-- Evidenzia sul canvas senza rubare il pannello: l'interview e\u2019 in corso. -->\r\n <fb-debug-panel (elementFocused)=\"highlightElement($event)\" (closed)=\"setPanel('inspector')\" />\r\n }\r\n }\r\n </div>\r\n\r\n <footer class=\"fb-side__footer\">\r\n <label class=\"fb-btn fb-btn--icon\">\r\n Importa JSON\r\n <input type=\"file\" accept=\"application/json,.json\" hidden (change)=\"onFileSelected($event)\" />\r\n </label>\r\n </footer>\r\n </aside>\r\n </div>\r\n\r\n @if (isDialogMode() && isDialogOpen() && selectedName()) {\r\n <!-- La dialog sta dentro il builder, non nel body: la libreria e\u2019 innestabile. -->\r\n <fb-element-dialog\r\n [selectedName]=\"selectedName()\"\r\n (closed)=\"closeDialog()\"\r\n (removeRequested)=\"onRemoveNode($event)\"\r\n (duplicateRequested)=\"onDuplicateNode($event)\"\r\n />\r\n }\r\n</div>\r\n", styles: [":host{display:block;width:100%;height:100%;min-height:0;font:inherit;color:var(--fb-text, #1d2939)}.fb-builder{position:relative;display:flex;flex-direction:column;width:100%;height:100%;min-height:0;background:var(--fb-surface, #fff)}.fb-top{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:9px 14px;border-bottom:1px solid var(--fb-border, #e2e5eb);background:var(--fb-surface, #fff)}.fb-top__identity{min-width:0}.fb-top__label{width:100%;max-width:420px;padding:2px 4px;border:1px solid transparent;border-radius:4px;background:transparent;color:var(--fb-text, #1d2939);font:inherit;font-size:15px;font-weight:600}.fb-top__label:hover:not(:disabled),.fb-top__label:focus-visible{border-color:var(--fb-border, #d6dae1);background:var(--fb-surface, #fff)}.fb-top__meta{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-top:2px}.fb-top__name{width:180px;padding:1px 4px;border:1px solid transparent;border-radius:4px;background:transparent;color:var(--fb-text-muted, #667085);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px}.fb-top__name:hover:not(:disabled),.fb-top__name:focus-visible{border-color:var(--fb-border, #d6dae1)}.fb-top__process{padding:1px 4px;border:1px solid var(--fb-border-subtle, #e6e9ee);border-radius:4px;background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:11px}.fb-top__status,.fb-top__version,.fb-top__dirty{padding:2px 7px;border-radius:999px;background:var(--fb-surface-sunken, #eef0f4);color:var(--fb-text-muted, #6b7086);font-size:10px;font-weight:600}.fb-top__version{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.fb-top__dirty{background:color-mix(in srgb,var(--fb-warning, #b7791f) 16%,transparent);color:var(--fb-warning, #b7791f)}.fb-top__actions{display:flex;flex-wrap:wrap;gap:4px}.fb-banner{display:flex;flex-wrap:wrap;align-items:center;gap:8px;padding:6px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee);background:color-mix(in srgb,var(--fb-accent, #2f6feb) 7%,transparent);font-size:11px}.fb-banner--warn{background:color-mix(in srgb,var(--fb-warning, #b7791f) 12%,transparent);color:var(--fb-warning, #b7791f)}.fb-banner--error{background:color-mix(in srgb,var(--fb-error, #c9372c) 10%,transparent);color:var(--fb-error, #c9372c)}.fb-banner>span{flex:1;min-width:200px}.fb-main{display:flex;flex:1;min-height:0}.fb-main__palette{flex:0 0 190px;min-width:0}.fb-main__center{display:flex;flex:1;flex-direction:column;min-width:0;min-height:0}.fb-main__canvas{flex:1;min-height:0}.fb-main__problems{flex:0 0 auto;height:220px}.fb-main__problems-toggle{display:flex;align-items:center;gap:6px;padding:4px 12px;border:0;border-top:1px solid var(--fb-border, #d6dae1);background:var(--fb-surface-alt, #f8f9fb);color:var(--fb-text-muted, #667085);font:inherit;font-size:11px;cursor:pointer}.fb-main__count{padding:0 5px;border-radius:8px;background:var(--fb-border, #d6dae1);font-size:9px;font-weight:700}.fb-main__count--error{background:color-mix(in srgb,var(--fb-error, #c9372c) 16%,transparent);color:var(--fb-error, #c9372c)}.fb-main__count--warn{background:color-mix(in srgb,var(--fb-warning, #b7791f) 16%,transparent);color:var(--fb-warning, #b7791f)}.fb-main__side{display:flex;flex-direction:column;flex:0 0 340px;min-width:0;min-height:0;border-left:1px solid var(--fb-border, #d6dae1);background:var(--fb-surface, #fff)}.fb-side__tabs{display:flex;gap:2px;margin:8px 10px;padding:3px;border-radius:var(--fb-radius, 10px);background:var(--fb-surface-sunken, #eef0f4)}.fb-side__tab{flex:1;padding:5px 8px;border:0;border-radius:var(--fb-radius-xs, 6px);background:transparent;color:var(--fb-text-muted, #6b7086);font:inherit;font-size:11px;cursor:pointer;transition:background .12s ease,color .12s ease}.fb-side__tab:hover:not(.fb-side__tab--active){color:var(--fb-text, #1a1c23)}.fb-side__tab--active{background:var(--fb-surface, #fff);box-shadow:var(--fb-shadow-sm, 0 1px 2px rgb(16 24 40 / 6%));color:var(--fb-text, #1a1c23);font-weight:600}.fb-side__content{flex:1;min-height:0;border-top:1px solid var(--fb-border-subtle, #eef0f4)}.fb-side__content>*{height:100%}.fb-side__footer{display:flex;gap:6px;padding:6px 8px;border-top:1px solid var(--fb-border-subtle, #e6e9ee)}@media(max-width:1200px){.fb-main__palette{flex-basis:150px}.fb-main__side{flex-basis:290px}}\n"] }]
9770
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"fb-builder\" [attr.data-fb-theme]=\"null\">\r\n <header class=\"fb-top\">\r\n <div class=\"fb-top__identity\">\r\n <input\r\n class=\"fb-top__label\"\r\n [value]=\"document().label || ''\"\r\n placeholder=\"Nome del flow\"\r\n aria-label=\"Nome del flow\"\r\n [disabled]=\"!isEditable()\"\r\n (input)=\"setLabel($any($event.target).value)\"\r\n />\r\n <div class=\"fb-top__meta\">\r\n <input\r\n class=\"fb-top__name\"\r\n [value]=\"document().fullName || ''\"\r\n placeholder=\"NomeTecnico\"\r\n aria-label=\"Nome tecnico del flow\"\r\n [disabled]=\"!isEditable()\"\r\n (input)=\"setFullName($any($event.target).value)\"\r\n />\r\n <select\r\n class=\"fb-top__process\"\r\n [fbValue]=\"document().processType || ''\"\r\n aria-label=\"Tipo di flow\"\r\n [disabled]=\"!isEditable()\"\r\n (change)=\"setProcessType($any($event.target).value)\"\r\n >\r\n <!--\r\n Il segnaposto esiste perche' un `<select>` senza opzione corrispondente non e'\r\n \u00ABvuoto\u00BB: e' a `selectedIndex = -1`, e mostra una casella bianca. Su un flow nuovo\r\n (`processType` non ancora scelto) e' questa la riga che si vede, disabilitata\r\n perche' non e' un valore valido da salvare.\r\n -->\r\n @if (!document().processType) {\r\n <option value=\"\" disabled>\u2014 tipo di flow \u2014</option>\r\n }\r\n @for (type of processTypes(); track type.value) {\r\n <option [value]=\"type.value\">{{ type.label }}</option>\r\n }\r\n <!--\r\n Il tipo del documento che il dizionario non conosce: senza questa opzione la select\r\n resta bianca su un flow che il backend ha salvato con un `processType` fuori\r\n catalogo (o quando `processTypes` manca dalla risposta dei dizionari), e sembra che\r\n l'editor non abbia ricaricato il flow. Va mostrato **senza** riscrivere il\r\n documento: il valore e' del backend, non nostro da correggere.\r\n -->\r\n @if (isProcessTypeOutOfCatalog()) {\r\n <option [value]=\"document().processType\">{{ document().processType }} (fuori catalogo)</option>\r\n }\r\n </select>\r\n <span class=\"fb-top__status\">{{ statusLabel() }}</span>\r\n @if (session.version() !== null) {\r\n <span class=\"fb-top__version\">v{{ session.version() }}</span>\r\n }\r\n @if (isDirty()) {\r\n <span class=\"fb-top__dirty\" title=\"Ci sono modifiche non salvate\">modificato</span>\r\n }\r\n </div>\r\n </div>\r\n\r\n <div class=\"fb-top__actions\">\r\n <button type=\"button\" class=\"fb-btn\" [disabled]=\"!canUndo()\" aria-label=\"Annulla\" (click)=\"undo()\">\u21B6</button>\r\n <button type=\"button\" class=\"fb-btn\" [disabled]=\"!canRedo()\" aria-label=\"Ripeti\" (click)=\"redo()\">\u21B7</button>\r\n <button type=\"button\" class=\"fb-btn\" title=\"Ricalcola le posizioni\" (click)=\"autoLayout()\">Riordina</button>\r\n @if (isDialogMode()) {\r\n <!-- Il doppio click sul node fa la stessa cosa, ma non si vede: questo comando s\u00EC. -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"!selectedName()\"\r\n title=\"Apri il dettaglio dell\u2019elemento selezionato\"\r\n (click)=\"openSelectedElement()\"\r\n >\r\n Dettaglio\r\n </button>\r\n }\r\n <button type=\"button\" class=\"fb-btn\" [disabled]=\"isBusy()\" (click)=\"validateNow()\">Valida</button>\r\n\r\n @switch (primaryCommand()) {\r\n @case ('newVersion') {\r\n <!-- Su una versione non modificabile il comando primario e' \"Nuova versione\" (\u00A78.1). -->\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" [disabled]=\"isBusy()\" (click)=\"createNewVersion()\">\r\n Nuova versione\r\n </button>\r\n }\r\n @case ('create') {\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" [disabled]=\"isBusy()\" (click)=\"save()\">\r\n Crea\r\n </button>\r\n }\r\n @default {\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" [disabled]=\"isBusy()\" (click)=\"save()\">\r\n Salva\r\n </button>\r\n }\r\n }\r\n\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"isBusy() || !canActivate()\"\r\n [title]=\"\r\n canActivate()\r\n ? 'Attiva questa versione'\r\n : 'L\u2019attivazione esige zero errori: correggili nel pannello dei problemi'\r\n \"\r\n (click)=\"activate()\"\r\n >\r\n Attiva\r\n </button>\r\n </div>\r\n </header>\r\n\r\n @if (conflict()) {\r\n <!-- \u00A79.3: qualcun altro ha salvato. Due strade, entrambe offerte. -->\r\n <div class=\"fb-banner fb-banner--warn\" role=\"alert\">\r\n <span>\r\n {{ conflict()?.message }}\r\n @if (conflict()?.conflictingAuthor) {\r\n Ha salvato {{ conflict()?.conflictingAuthor }}.\r\n }\r\n </span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--icon\" (click)=\"reloadAfterConflict()\">Ricarica</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--icon\" (click)=\"saveAsNewVersionAfterConflict()\">\r\n Salva come nuova versione\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" (click)=\"dismissConflict()\">\u00D7</button>\r\n </div>\r\n }\r\n\r\n @if (notice()) {\r\n <div\r\n class=\"fb-banner\"\r\n [class.fb-banner--error]=\"notice()?.kind === 'error'\"\r\n role=\"status\"\r\n >\r\n <span>{{ notice()?.message }}</span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" aria-label=\"Chiudi\" (click)=\"dismissNotice()\">\r\n \u00D7\r\n </button>\r\n </div>\r\n }\r\n\r\n @if (hasScreensInAutoLaunched()) {\r\n <div class=\"fb-banner fb-banner--error\">\r\n Un flow AutoLaunched che contiene screen e\u2019 un errore di validazione: non c\u2019e\u2019 nessuno a cui mostrarli.\r\n </div>\r\n }\r\n @if (isOrchestrationWithoutStages()) {\r\n <div class=\"fb-banner fb-banner--warn\">\r\n Un flow Orchestration senza stage di orchestrazione viene segnalato dalla validazione\r\n (ORCHESTRATION_WITHOUT_STAGES).\r\n </div>\r\n }\r\n @if (hasNoScreensInScreenFlow()) {\r\n <div class=\"fb-banner fb-banner--warn\">\r\n Un flow di tipo Screen senza nessuno screen viene segnalato dalla validazione.\r\n </div>\r\n }\r\n @if (!isEditable()) {\r\n <div class=\"fb-banner\">\r\n Questa versione e\u2019 in sola lettura: per modificarla creane una nuova.\r\n </div>\r\n }\r\n\r\n <div class=\"fb-main\">\r\n <aside class=\"fb-main__palette\">\r\n <fb-element-palette [processType]=\"document().processType\" (elementPicked)=\"onElementPicked($event)\" />\r\n </aside>\r\n\r\n <div class=\"fb-main__center\">\r\n <fb-flow-canvas\r\n class=\"fb-main__canvas\"\r\n [selectedName]=\"selectedName()\"\r\n [outline]=\"outline()\"\r\n [isEditable]=\"isEditable()\"\r\n (selectionChange)=\"onSelectionChange($event)\"\r\n (nodeOpened)=\"onNodeOpened($event)\"\r\n (nodeRemoveRequested)=\"onRemoveNode($event)\"\r\n (nodeDuplicateRequested)=\"onDuplicateNode($event)\"\r\n (elementDropped)=\"onElementDropped($event)\"\r\n />\r\n\r\n @if (showProblems()) {\r\n <fb-problems-panel\r\n class=\"fb-main__problems\"\r\n (elementFocused)=\"focusElement($event)\"\r\n (closed)=\"toggleProblems()\"\r\n />\r\n } @else {\r\n <button type=\"button\" class=\"fb-main__problems-toggle\" (click)=\"toggleProblems()\">\r\n Problemi\r\n @if (errorCount()) {\r\n <span class=\"fb-main__count fb-main__count--error\">{{ errorCount() }}</span>\r\n }\r\n @if (warningCount()) {\r\n <span class=\"fb-main__count fb-main__count--warn\">{{ warningCount() }}</span>\r\n }\r\n </button>\r\n }\r\n </div>\r\n\r\n <aside class=\"fb-main__side\">\r\n <nav class=\"fb-side__tabs\" aria-label=\"Pannelli\">\r\n @if (!isDialogMode()) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-side__tab\"\r\n [class.fb-side__tab--active]=\"sidePanel() === 'inspector'\"\r\n (click)=\"setPanel('inspector')\"\r\n >\r\n Elemento\r\n </button>\r\n }\r\n <button\r\n type=\"button\"\r\n class=\"fb-side__tab\"\r\n [class.fb-side__tab--active]=\"sidePanel() === 'resources'\"\r\n (click)=\"setPanel('resources')\"\r\n >\r\n Risorse\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-side__tab\"\r\n [class.fb-side__tab--active]=\"sidePanel() === 'versions'\"\r\n (click)=\"setPanel('versions')\"\r\n >\r\n Versioni\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-side__tab\"\r\n [class.fb-side__tab--active]=\"sidePanel() === 'debug'\"\r\n (click)=\"setPanel('debug')\"\r\n >\r\n Prova\r\n </button>\r\n </nav>\r\n\r\n <div class=\"fb-side__content\">\r\n @switch (sidePanel()) {\r\n @case ('inspector') {\r\n <fb-element-inspector\r\n [selectedName]=\"selectedName()\"\r\n (removeRequested)=\"onRemoveNode($event)\"\r\n (duplicateRequested)=\"onDuplicateNode($event)\"\r\n (closed)=\"setPanel('resources')\"\r\n />\r\n }\r\n @case ('resources') {\r\n <fb-resource-panel (closed)=\"setPanel('inspector')\" />\r\n }\r\n @case ('versions') {\r\n <fb-version-panel\r\n (versionOpened)=\"openVersion($event)\"\r\n (notice)=\"showNotice($event)\"\r\n (closed)=\"setPanel('inspector')\"\r\n />\r\n }\r\n @case ('debug') {\r\n <!-- Evidenzia sul canvas senza rubare il pannello: l'interview e\u2019 in corso. -->\r\n <fb-debug-panel (elementFocused)=\"highlightElement($event)\" (closed)=\"setPanel('inspector')\" />\r\n }\r\n }\r\n </div>\r\n\r\n <footer class=\"fb-side__footer\">\r\n <label class=\"fb-btn fb-btn--icon\">\r\n Importa JSON\r\n <input type=\"file\" accept=\"application/json,.json\" hidden (change)=\"onFileSelected($event)\" />\r\n </label>\r\n </footer>\r\n </aside>\r\n </div>\r\n\r\n @if (isDialogMode() && isDialogOpen() && selectedName()) {\r\n <!-- La dialog sta dentro il builder, non nel body: la libreria e\u2019 innestabile. -->\r\n <fb-element-dialog\r\n [selectedName]=\"selectedName()\"\r\n (closed)=\"closeDialog()\"\r\n (removeRequested)=\"onRemoveNode($event)\"\r\n (duplicateRequested)=\"onDuplicateNode($event)\"\r\n />\r\n }\r\n</div>\r\n", styles: [":host{display:block;width:100%;height:100%;min-height:0;font:inherit;color:var(--fb-text, #1d2939)}.fb-builder{position:relative;display:flex;flex-direction:column;width:100%;height:100%;min-height:0;background:var(--fb-surface, #fff)}.fb-top{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:9px 14px;border-bottom:1px solid var(--fb-border, #e2e5eb);background:var(--fb-surface, #fff)}.fb-top__identity{min-width:0}.fb-top__label{width:100%;max-width:420px;padding:2px 4px;border:1px solid transparent;border-radius:4px;background:transparent;color:var(--fb-text, #1d2939);font:inherit;font-size:15px;font-weight:600}.fb-top__label:hover:not(:disabled),.fb-top__label:focus-visible{border-color:var(--fb-border, #d6dae1);background:var(--fb-surface, #fff)}.fb-top__meta{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-top:2px}.fb-top__name{width:180px;padding:1px 4px;border:1px solid transparent;border-radius:4px;background:transparent;color:var(--fb-text-muted, #667085);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px}.fb-top__name:hover:not(:disabled),.fb-top__name:focus-visible{border-color:var(--fb-border, #d6dae1)}.fb-top__process{padding:1px 4px;border:1px solid var(--fb-border-subtle, #e6e9ee);border-radius:4px;background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:11px}.fb-top__status,.fb-top__version,.fb-top__dirty{padding:2px 7px;border-radius:999px;background:var(--fb-surface-sunken, #eef0f4);color:var(--fb-text-muted, #6b7086);font-size:10px;font-weight:600}.fb-top__version{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.fb-top__dirty{background:color-mix(in srgb,var(--fb-warning, #b7791f) 16%,transparent);color:var(--fb-warning, #b7791f)}.fb-top__actions{display:flex;flex-wrap:wrap;gap:4px}.fb-banner{display:flex;flex-wrap:wrap;align-items:center;gap:8px;padding:6px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee);background:color-mix(in srgb,var(--fb-accent, #2f6feb) 7%,transparent);font-size:11px}.fb-banner--warn{background:color-mix(in srgb,var(--fb-warning, #b7791f) 12%,transparent);color:var(--fb-warning, #b7791f)}.fb-banner--error{background:color-mix(in srgb,var(--fb-error, #c9372c) 10%,transparent);color:var(--fb-error, #c9372c)}.fb-banner>span{flex:1;min-width:200px}.fb-main{display:flex;flex:1;min-height:0}.fb-main__palette{flex:0 0 190px;min-width:0}.fb-main__center{display:flex;flex:1;flex-direction:column;min-width:0;min-height:0}.fb-main__canvas{flex:1;min-height:0}.fb-main__problems{flex:0 0 auto;height:220px}.fb-main__problems-toggle{display:flex;align-items:center;gap:6px;padding:4px 12px;border:0;border-top:1px solid var(--fb-border, #d6dae1);background:var(--fb-surface-alt, #f8f9fb);color:var(--fb-text-muted, #667085);font:inherit;font-size:11px;cursor:pointer}.fb-main__count{padding:0 5px;border-radius:8px;background:var(--fb-border, #d6dae1);font-size:9px;font-weight:700}.fb-main__count--error{background:color-mix(in srgb,var(--fb-error, #c9372c) 16%,transparent);color:var(--fb-error, #c9372c)}.fb-main__count--warn{background:color-mix(in srgb,var(--fb-warning, #b7791f) 16%,transparent);color:var(--fb-warning, #b7791f)}.fb-main__side{display:flex;flex-direction:column;flex:0 0 340px;min-width:0;min-height:0;border-left:1px solid var(--fb-border, #d6dae1);background:var(--fb-surface, #fff)}.fb-side__tabs{display:flex;gap:2px;margin:8px 10px;padding:3px;border-radius:var(--fb-radius, 10px);background:var(--fb-surface-sunken, #eef0f4)}.fb-side__tab{flex:1;padding:5px 8px;border:0;border-radius:var(--fb-radius-xs, 6px);background:transparent;color:var(--fb-text-muted, #6b7086);font:inherit;font-size:11px;cursor:pointer;transition:background .12s ease,color .12s ease}.fb-side__tab:hover:not(.fb-side__tab--active){color:var(--fb-text, #1a1c23)}.fb-side__tab--active{background:var(--fb-surface, #fff);box-shadow:var(--fb-shadow-sm, 0 1px 2px rgb(16 24 40 / 6%));color:var(--fb-text, #1a1c23);font-weight:600}.fb-side__content{flex:1;min-height:0;border-top:1px solid var(--fb-border-subtle, #eef0f4)}.fb-side__content>*{height:100%}.fb-side__footer{display:flex;gap:6px;padding:6px 8px;border-top:1px solid var(--fb-border-subtle, #e6e9ee)}@media(max-width:1200px){.fb-main__palette{flex-basis:150px}.fb-main__side{flex-basis:290px}}\n"] }]
9241
9771
  }], ctorParameters: () => [], propDecorators: { flowName: [{ type: i0.Input, args: [{ isSignal: true, alias: "flowName", required: false }] }], version: [{ type: i0.Input, args: [{ isSignal: true, alias: "version", required: false }] }], author: [{ type: i0.Input, args: [{ isSignal: true, alias: "author", required: false }] }], defaultProcessType: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultProcessType", required: false }] }], inspectorMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "inspectorMode", required: false }] }], saved: [{ type: i0.Output, args: ["saved"] }], activated: [{ type: i0.Output, args: ["activated"] }], closeRequested: [{ type: i0.Output, args: ["closeRequested"] }] } });
9242
9772
 
9243
9773
  /**
9244
9774
  * Generated bundle index. Do not edit.
9245
9775
  */
9246
9776
 
9247
- export { ConditionEditorComponent, ConnectorEditorComponent, DebugPanelComponent, ElementDialogComponent, ElementInspectorComponent, ElementPaletteComponent, 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, START_NODE_NAME, SelectValueDirective, StartInspectorComponent, StructureMemberPickerComponent, StructurePickerComponent, TYPES_WITH_AUTOMATIC_OUTPUT, TYPE_BY_COLLECTION, UNSUPPORTED_TYPES, ValueEditorComponent, VersionPanelComponent, areTypesComparable, canvasNodeId, checkConditionLogic, checkFlowName, elementIcon, emptyFlowDefinition, flowNodeWidth, flowNodeWidthClass, isCustomConditionLogic, isGlobalReference, isNumericType, isTypeCheckedOperator, isValidFlowName, moveCondition, outletByKey, outletsOf, parseCanvasNodeId, parseInvariantNumber, parseSourceConnectorId, parseTargetConnectorId, referenceRoot, remapConditionLogic, removeCondition, slugifyFlowName, sourceConnectorId, stageStepNames, stageStepOutputReferenced, stepsOf, targetConnectorId, uniqueFlowName, variantFieldOf, variantOf, variantPresetOf };
9777
+ export { ConditionEditorComponent, ConnectorEditorComponent, DebugPanelComponent, ElementDialogComponent, ElementInspectorComponent, ElementPaletteComponent, 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, flowNodeWidth, flowNodeWidthClass, isCustomConditionLogic, isGlobalReference, isNumericType, isTypeCheckedOperator, isValidFlowName, loadPathLevel, 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 };
9248
9778
  //# sourceMappingURL=esfaenza-flow-builder.mjs.map