@esfaenza/flow-builder 20.3.20 → 20.3.22

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,6 +1,6 @@
1
1
  import { HttpClient, HttpErrorResponse, HttpParams } from '@angular/common/http';
2
2
  import * as i0 from '@angular/core';
3
- import { InjectionToken, inject, signal, effect, computed, Injectable, DestroyRef, input, output, viewChild, ChangeDetectionStrategy, Component, untracked, ElementRef, Directive, Injector, afterNextRender } from '@angular/core';
3
+ import { InjectionToken, inject, signal, effect, Injectable, computed, DestroyRef, input, output, viewChild, ChangeDetectionStrategy, Component, untracked, ElementRef, Directive, Injector, afterNextRender } from '@angular/core';
4
4
  import { firstValueFrom } from 'rxjs';
5
5
  import dagre from 'dagre';
6
6
  import * as i1 from '@foblex/flow';
@@ -276,6 +276,45 @@ class FlowBuilderApi {
276
276
  return missing('describeStructure');
277
277
  }
278
278
  // -------------------------------------------------------------------------
279
+ // Esecuzione: i comandi «Esegui» e «Debug» della barra
280
+ // -------------------------------------------------------------------------
281
+ //
282
+ // Questi due non sono primitive del contratto: sono il punto in cui l'editor **passa la
283
+ // mano**. L'editor chiede i valori di ingresso in una finestra e poi chiama; cosa succeda
284
+ // dopo — un runner dell'ospite, una scheda nuova, un debugger vero — non lo sa e non deve
285
+ // saperlo. Sono opzionali come tutte le primitive che dipendono da un servizio dell'host, e
286
+ // il rifiuto `MissingService` e' la risposta giusta: il comando si spegne con un messaggio,
287
+ // l'editor resta usabile.
288
+ /**
289
+ * «Esegui»: avvia il flow **salvato** con i valori raccolti dalla finestra.
290
+ *
291
+ * Chi integra l'editor la implementa. Non c'e' un default che «prova» il flow: l'editor non
292
+ * ha un motore, e nascondere l'assenza dietro un'esecuzione finta e' il modo piu' veloce di
293
+ * far credere che un flow funzioni.
294
+ *
295
+ * Il ritorno e' **facoltativo** ed e' l'unica cosa che l'editor sa dell'esecuzione: un
296
+ * {@link FlowRunOutcome} finisce nel pannello «Debug» — stato, traccia, risorse, output —
297
+ * mentre `void` significa «guardo altrove», e l'editor dice solo che e' partita. Serve
298
+ * soprattutto ai flow **senza schermate**: lì non c'e' nessuna interfaccia del runtime in cui
299
+ * vedere cos'e' successo, e la traccia accanto al grafo e' tutto cio' che si ha.
300
+ */
301
+ runFlow(request) {
302
+ void request;
303
+ return missing('runFlow');
304
+ }
305
+ /**
306
+ * «Debug»: come {@link runFlow}, ma `request.debug` e' `true` — ed e' il caso in cui il
307
+ * ritorno conta davvero, perche' `trace` e `resources` esistono solo in debug (§6.5).
308
+ *
309
+ * Sono due metodi e non un flag perche' quasi sempre sono due strade diverse dell'ospite —
310
+ * l'una avvia e basta, l'altra apre un ispettore — e perche' un ambiente puo' esporre l'una
311
+ * senza l'altra: due `MissingService` distinti dicono quale delle due manca.
312
+ */
313
+ debugFlow(request) {
314
+ void request;
315
+ return missing('debugFlow');
316
+ }
317
+ // -------------------------------------------------------------------------
279
318
  // §6.5 Provare un flow dall'editor
280
319
  // -------------------------------------------------------------------------
281
320
  /**
@@ -1987,6 +2026,528 @@ function filterReferences(references, filter) {
1987
2026
  return references.filter((entry) => matchesReferenceFilter(entry, filter));
1988
2027
  }
1989
2028
 
2029
+ /**
2030
+ * Copia e incolla di **elementi** — la logica pura, senza Angular e senza storage.
2031
+ *
2032
+ * Il gesto e' quello che ci si aspetta da un editor, ma il contratto lo rende meno banale di
2033
+ * un `JSON.parse(JSON.stringify(...))`, e le tre cose che lo complicano sono tutte della §3.3:
2034
+ *
2035
+ * 1. **lo spazio dei nomi e' unico e case-insensitive.** Un elemento incollato non porta solo
2036
+ * il proprio nome: porta anche quelli dei suoi step di orchestrazione, dei campi di uno
2037
+ * screen dinamico e delle sue screen action, che sono nomi come gli altri (§5.2, §5.14).
2038
+ * Un omonimo e' `NAME_DUPLICATED`, quindi tutto cio' che collide va rinominato **prima**
2039
+ * di entrare nel documento;
2040
+ * 2. **rinominare non aggiorna i riferimenti** e nessuna primitiva lo fa (§13.8). Se il nome
2041
+ * cambia all'ingresso, i riferimenti interni al blocco vanno riscritti qui: le condizioni
2042
+ * che guardano un campo, i target dei connector interni, gli output automatici
2043
+ * (`Leggi_Ordine.Numero`);
2044
+ * 3. **un elemento non e' autosufficiente.** Referenzia risorse — variabili, formule, choice —
2045
+ * che nel flow di destinazione possono non esistere. Portarsele dietro e' il senso di
2046
+ * «copiare senza riconfigurare»; inventarle in silenzio no, ed e' per questo che
2047
+ * {@link planPaste} produce un **piano** che l'interfaccia mostra prima di applicarlo.
2048
+ *
2049
+ * Cio' che di proposito **non** si copia: le uscite che escono dal blocco. Un connector che
2050
+ * punta a un elemento rimasto nell'altro flow sarebbe `CONNECTOR_TARGET_UNKNOWN`, cioe' un
2051
+ * errore che blocca l'attivazione; quelle **interne** al blocco si conservano e si rimappano,
2052
+ * ed e' cio' che rende utile copiare due elementi insieme invece che uno alla volta.
2053
+ */
2054
+ /**
2055
+ * I campi il cui valore e' un riferimento a un node o a una risorsa.
2056
+ *
2057
+ * Elenco unico, condiviso con la rinomina: due elenchi che devono restare uguali diventano due
2058
+ * elenchi diversi al primo campo aggiunto, e il campo dimenticato non da' nessun errore —
2059
+ * lascia un riferimento al nome vecchio che si scopre solo alla validazione (§13.8).
2060
+ */
2061
+ const FLOW_REFERENCE_FIELDS = new Set([
2062
+ 'targetReference',
2063
+ 'elementReference',
2064
+ 'assignToReference',
2065
+ 'leftValueReference',
2066
+ 'collectionReference',
2067
+ 'assignNextValueToReference',
2068
+ 'outputReference',
2069
+ 'inputReference',
2070
+ 'assignRecordIdToReference',
2071
+ // Nomi di risorse e di cose dichiarate nel flow, che referenziano per nome **senza** chiamarsi
2072
+ // `...Reference`: sono la meta' dell'elenco che si dimentica. `stageReference` punta a uno
2073
+ // stage; le due `choice...` a una `FlowChoice` o a un `FlowDynamicChoiceSet` (§5.2);
2074
+ // `screenActionName` e `triggerFieldName` a un'action e a un campo **della stessa schermata**,
2075
+ // e sono i due che rendono un trigger `SCREEN_ACTION_UNKNOWN` /
2076
+ // `SCREEN_TRIGGER_FIELD_UNKNOWN` quando il nome cambia sotto di loro.
2077
+ 'stageReference',
2078
+ 'choiceReferences',
2079
+ 'defaultSelectedChoiceReference',
2080
+ 'screenActionName',
2081
+ 'triggerFieldName',
2082
+ ]);
2083
+ /**
2084
+ * Riscrive ogni occorrenza di un nome nei campi che contengono riferimenti.
2085
+ *
2086
+ * Cammina tutto cio' che gli si passa — un documento intero per la rinomina, il solo frammento
2087
+ * copiato per l'incolla — perche' i riferimenti compaiono in una ventina di posti diversi (§4)
2088
+ * e l'elenco dei posti sarebbe la cosa che si dimentica di aggiornare.
2089
+ */
2090
+ function rewriteReferences(root, oldName, newName) {
2091
+ if (!oldName || !newName || oldName === newName) {
2092
+ return;
2093
+ }
2094
+ const rewrite = (value) => {
2095
+ if (value === oldName) {
2096
+ return newName;
2097
+ }
2098
+ // Riferimento navigato: `Vecchio.Campo` → `Nuovo.Campo`.
2099
+ if (value.startsWith(`${oldName}.`)) {
2100
+ return `${newName}${value.slice(oldName.length)}`;
2101
+ }
2102
+ return value;
2103
+ };
2104
+ const walk = (value) => {
2105
+ if (Array.isArray(value)) {
2106
+ value.forEach(walk);
2107
+ return;
2108
+ }
2109
+ if (!value || typeof value !== 'object') {
2110
+ return;
2111
+ }
2112
+ const record = value;
2113
+ for (const [key, child] of Object.entries(record)) {
2114
+ if (!FLOW_REFERENCE_FIELDS.has(key)) {
2115
+ walk(child);
2116
+ continue;
2117
+ }
2118
+ if (typeof child === 'string') {
2119
+ record[key] = rewrite(child);
2120
+ }
2121
+ else if (Array.isArray(child) && child.every((item) => typeof item === 'string')) {
2122
+ // `choiceReferences` e' un array di **nomi**: senza questo ramo il walk ci scendeva
2123
+ // dentro e non trovava nessuna stringa da riscrivere, perche' le stringhe erano gli
2124
+ // elementi e non i valori di una chiave.
2125
+ record[key] = child.map(rewrite);
2126
+ }
2127
+ else {
2128
+ walk(child);
2129
+ }
2130
+ }
2131
+ };
2132
+ walk(root);
2133
+ }
2134
+ // ---------------------------------------------------------------------------
2135
+ // Il formato degli appunti
2136
+ // ---------------------------------------------------------------------------
2137
+ /**
2138
+ * Marcatore del formato. Sta scritto dentro il payload perche' gli appunti viaggiano anche
2139
+ * fuori dall'editor (storage del browser, incolla di un JSON a mano): leggere qualcosa che non
2140
+ * e' nostro deve fallire con un messaggio, non con un documento rovinato.
2141
+ */
2142
+ const FLOW_CLIPBOARD_KIND = 'flow-builder/elements';
2143
+ const FLOW_CLIPBOARD_VERSION = 1;
2144
+ function isClipboardPayload(value) {
2145
+ const payload = value;
2146
+ return (!!payload &&
2147
+ typeof payload === 'object' &&
2148
+ payload.kind === FLOW_CLIPBOARD_KIND &&
2149
+ Array.isArray(payload.nodes) &&
2150
+ payload.nodes.length > 0);
2151
+ }
2152
+ // ---------------------------------------------------------------------------
2153
+ // I nomi che un blocco si porta dietro
2154
+ // ---------------------------------------------------------------------------
2155
+ /**
2156
+ * I nomi **dichiarati** dentro un node, oltre al suo: step di orchestrazione, campi di screen
2157
+ * dinamico (contenitori compresi) e screen action. Stanno tutti nello stesso spazio dei nomi
2158
+ * del node (§3.3), quindi collidono come lui e vanno rinominati come lui.
2159
+ *
2160
+ * Si cammina la struttura sapendo **dove** stanno, invece di cercare ogni `name` nell'albero:
2161
+ * `inputParameters[].name` e' il nome di un parametro dell'action, non una risorsa del flow, e
2162
+ * rinominarlo perche' per caso coincide con un campo copiato romperebbe la chiamata.
2163
+ */
2164
+ function innerNamesOf(node) {
2165
+ const names = [];
2166
+ const pushFields = (fields) => {
2167
+ for (const field of fields ?? []) {
2168
+ if (typeof field['name'] === 'string' && field['name']) {
2169
+ names.push(field['name']);
2170
+ }
2171
+ pushFields(field['fields']);
2172
+ }
2173
+ };
2174
+ pushFields(node['fields']);
2175
+ for (const action of node['actions'] ?? []) {
2176
+ if (typeof action['name'] === 'string' && action['name']) {
2177
+ names.push(action['name']);
2178
+ }
2179
+ }
2180
+ for (const step of node['stageSteps'] ?? []) {
2181
+ if (typeof step['name'] === 'string' && step['name']) {
2182
+ names.push(step['name']);
2183
+ }
2184
+ }
2185
+ return names;
2186
+ }
2187
+ /** Riscrive un nome dichiarato **dove e' dichiarato**, con le stesse regole di `innerNamesOf`. */
2188
+ function renameInnerName(node, oldName, newName) {
2189
+ const visitFields = (fields) => {
2190
+ for (const field of fields ?? []) {
2191
+ if (field['name'] === oldName) {
2192
+ field['name'] = newName;
2193
+ }
2194
+ visitFields(field['fields']);
2195
+ }
2196
+ };
2197
+ visitFields(node['fields']);
2198
+ for (const action of node['actions'] ?? []) {
2199
+ if (action['name'] === oldName) {
2200
+ action['name'] = newName;
2201
+ }
2202
+ }
2203
+ for (const step of node['stageSteps'] ?? []) {
2204
+ if (step['name'] === oldName) {
2205
+ step['name'] = newName;
2206
+ }
2207
+ }
2208
+ }
2209
+ /**
2210
+ * Le **radici** dei riferimenti presenti in un frammento: di `Leggi_Ordine.Numero` la radice e'
2211
+ * `Leggi_Ordine`, ed e' quella che deve esistere nella destinazione (§6.4 — l'elenco dei
2212
+ * riferimenti porta le radici, non i percorsi).
2213
+ *
2214
+ * I `targetReference` restano fuori: sono i connector, e quelli li tratta il taglio delle
2215
+ * uscite. Contarli come riferimenti farebbe comparire fra le dipendenze l'elemento a valle,
2216
+ * che e' esattamente cio' che copiare un blocco non si porta dietro.
2217
+ */
2218
+ function referencedRootsOf(fragment) {
2219
+ const roots = new Set();
2220
+ const walk = (value) => {
2221
+ if (Array.isArray(value)) {
2222
+ value.forEach(walk);
2223
+ return;
2224
+ }
2225
+ if (!value || typeof value !== 'object') {
2226
+ return;
2227
+ }
2228
+ for (const [key, child] of Object.entries(value)) {
2229
+ if (key === 'targetReference' || !FLOW_REFERENCE_FIELDS.has(key)) {
2230
+ walk(child);
2231
+ continue;
2232
+ }
2233
+ // Una stringa o un array di nomi (`choiceReferences`): di ognuno conta la radice.
2234
+ const values = typeof child === 'string' ? [child] : Array.isArray(child) ? child : [];
2235
+ let matched = false;
2236
+ for (const item of values) {
2237
+ if (typeof item !== 'string') {
2238
+ continue;
2239
+ }
2240
+ matched = true;
2241
+ const root = item.split('.')[0];
2242
+ if (root) {
2243
+ roots.add(root);
2244
+ }
2245
+ }
2246
+ if (!matched) {
2247
+ walk(child);
2248
+ }
2249
+ }
2250
+ };
2251
+ walk(fragment);
2252
+ return [...roots];
2253
+ }
2254
+ // ---------------------------------------------------------------------------
2255
+ // Copia
2256
+ // ---------------------------------------------------------------------------
2257
+ function clone$1(value) {
2258
+ return JSON.parse(JSON.stringify(value));
2259
+ }
2260
+ /**
2261
+ * Costruisce gli appunti a partire dalla selezione.
2262
+ *
2263
+ * Le uscite si tengono **solo** se restano dentro il blocco; le altre si tolgono del tutto e
2264
+ * non si azzerano: un connector dichiarato senza destinazione e' `CONNECTOR_TARGET_MISSING`,
2265
+ * un avviso che l'utente non ha nessun modo di far sparire se non riaprendo il ramo, mentre un
2266
+ * ramo senza connector e' «il percorso finisce qui», che e' cio' che e' davvero successo (§3.5).
2267
+ */
2268
+ function buildClipboardPayload(input) {
2269
+ const selected = new Set(input.names);
2270
+ const picked = input.nodes.filter((reference) => selected.has(reference.name));
2271
+ if (!picked.length) {
2272
+ return null;
2273
+ }
2274
+ /** I node del blocco: e' l'insieme che decide quali **uscite** sopravvivono. */
2275
+ const insideNodes = new Set(picked.map((reference) => reference.name));
2276
+ /**
2277
+ * Tutto cio' che il blocco dichiara, node compresi: e' l'insieme che decide quali
2278
+ * **riferimenti** sono interni. Senza i nomi dichiarati — campi di uno screen dinamico,
2279
+ * screen action, step — una schermata risultava referenziare i propri campi dall'esterno, e
2280
+ * la finestra dell'incolla li elencava come «da sistemare».
2281
+ */
2282
+ const insideNames = new Set([
2283
+ ...insideNodes,
2284
+ ...picked.flatMap((reference) => innerNamesOf(reference.node)),
2285
+ ]);
2286
+ const nodes = picked.map((reference) => {
2287
+ const copy = clone$1(reference.node);
2288
+ for (const outlet of outletsOf(reference.type, copy)) {
2289
+ const connector = outlet.read(copy);
2290
+ const target = connector?.targetReference;
2291
+ if (!connector || !target || !insideNodes.has(target)) {
2292
+ outlet.write(copy, undefined);
2293
+ }
2294
+ }
2295
+ return { collection: reference.collection, node: copy };
2296
+ });
2297
+ // Le risorse referenziate dal blocco: si copiano per intero, perche' e' la meta' del lavoro
2298
+ // che «senza riconfigurare» vuol dire.
2299
+ const roots = referencedRootsOf(nodes.map((entry) => entry.node));
2300
+ const resourceByName = new Map(input.resources.map((reference) => [reference.name.toLowerCase(), reference]));
2301
+ const resources = [];
2302
+ const externalReferences = [];
2303
+ for (const root of roots) {
2304
+ if (insideNames.has(root)) {
2305
+ continue;
2306
+ }
2307
+ const resource = resourceByName.get(root.toLowerCase());
2308
+ if (resource) {
2309
+ resources.push({ collection: resource.collection, resource: clone$1(resource.resource) });
2310
+ }
2311
+ else {
2312
+ externalReferences.push(root);
2313
+ }
2314
+ }
2315
+ return {
2316
+ kind: FLOW_CLIPBOARD_KIND,
2317
+ version: FLOW_CLIPBOARD_VERSION,
2318
+ flowName: input.flowName,
2319
+ processType: input.processType,
2320
+ nodes,
2321
+ resources,
2322
+ externalReferences,
2323
+ };
2324
+ }
2325
+ /**
2326
+ * Decide **prima** di toccare il documento cosa cambierebbe: quali nomi vanno rinominati, quali
2327
+ * risorse nascono e quali si riusano, cosa resta irrisolto. La finestra dell'incolla mostra
2328
+ * questo, e `applyPaste` lo esegue senza deciderne piu' niente.
2329
+ *
2330
+ * Una risorsa il cui nome esiste già nella destinazione si **riusa**: e' quasi sempre la stessa
2331
+ * variabile di prima (stesso flow, altra versione), e crearne una omonima sarebbe comunque
2332
+ * `NAME_DUPLICATED`. Rinominarla d'ufficio sarebbe peggio: i riferimenti del blocco
2333
+ * continuerebbero a puntare a quella esistente pur avendone appena creata una identica.
2334
+ */
2335
+ function planPaste(payload, target) {
2336
+ const used = [...target.usedNames];
2337
+ const taken = new Set(used.map((name) => name.toLowerCase()));
2338
+ const renames = [];
2339
+ const reserve = (name, kind) => {
2340
+ if (!taken.has(name.toLowerCase())) {
2341
+ taken.add(name.toLowerCase());
2342
+ used.push(name);
2343
+ return name;
2344
+ }
2345
+ const fresh = uniqueFlowName(`${name}_copia`, used);
2346
+ taken.add(fresh.toLowerCase());
2347
+ used.push(fresh);
2348
+ renames.push({ kind, from: name, to: fresh });
2349
+ return fresh;
2350
+ };
2351
+ for (const entry of payload.nodes) {
2352
+ const name = entry.node.name ?? '';
2353
+ if (name) {
2354
+ reserve(name, 'node');
2355
+ }
2356
+ for (const inner of innerNamesOf(entry.node)) {
2357
+ reserve(inner, 'inner');
2358
+ }
2359
+ }
2360
+ const resources = [];
2361
+ const resourceNames = new Set();
2362
+ for (const entry of payload.resources) {
2363
+ const name = entry.resource['name'] ?? '';
2364
+ if (!name) {
2365
+ continue;
2366
+ }
2367
+ resourceNames.add(name.toLowerCase());
2368
+ // Il confronto e' con i nomi **della destinazione**, non con quelli appena riservati: una
2369
+ // risorsa che esiste già la si riusa, e riservarla la farebbe rinominare.
2370
+ const exists = target.usedNames.some((existing) => existing.toLowerCase() === name.toLowerCase());
2371
+ resources.push({ collection: entry.collection, name, status: exists ? 'reused' : 'new' });
2372
+ }
2373
+ const known = new Set([
2374
+ ...target.usedNames.map((name) => name.toLowerCase()),
2375
+ ...resourceNames,
2376
+ ]);
2377
+ const unresolved = payload.externalReferences.filter((name) => {
2378
+ // Le globali non sono risorse del flow: il loro catalogo e' del backend, e un `$Flow` che
2379
+ // «non esiste» sarebbe un falso allarme (§4.1).
2380
+ if (name.startsWith('$')) {
2381
+ return false;
2382
+ }
2383
+ return !known.has(name.toLowerCase());
2384
+ });
2385
+ return {
2386
+ nodeCount: payload.nodes.length,
2387
+ renames,
2388
+ resources,
2389
+ unresolved,
2390
+ processTypeMismatch: payload.processType && target.processType && payload.processType !== target.processType
2391
+ ? payload.processType
2392
+ : null,
2393
+ };
2394
+ }
2395
+ // ---------------------------------------------------------------------------
2396
+ // Incolla
2397
+ // ---------------------------------------------------------------------------
2398
+ /**
2399
+ * Applica gli appunti a un documento di lavoro, seguendo il piano.
2400
+ *
2401
+ * L'ordine conta: **prima** si rinomina dentro il frammento — nomi dichiarati e riferimenti —
2402
+ * e **poi** si innesta. Riscrivere dopo l'innesto significherebbe camminare tutto il documento
2403
+ * per ogni nome, e riscrivere anche i riferimenti degli elementi che c'erano già: un elemento
2404
+ * di destinazione che citava `Priorita` finirebbe a citare `Priorita_copia`.
2405
+ */
2406
+ function applyPaste(draft, payload, plan, options) {
2407
+ const offset = options.offset ?? { x: 40, y: 40 };
2408
+ const fragment = clone$1(payload.nodes);
2409
+ for (const rename of plan.renames) {
2410
+ for (const entry of fragment) {
2411
+ if (rename.kind === 'node') {
2412
+ if (entry.node.name === rename.from) {
2413
+ entry.node.name = rename.to;
2414
+ }
2415
+ }
2416
+ else {
2417
+ renameInnerName(entry.node, rename.from, rename.to);
2418
+ }
2419
+ }
2420
+ // I riferimenti si riscrivono sul **frammento**: i connector interni al blocco, le
2421
+ // condizioni che guardano un campo copiato, gli output automatici (§13.8).
2422
+ rewriteReferences(fragment, rename.from, rename.to);
2423
+ }
2424
+ if (options.createMissingResources) {
2425
+ for (const entry of payload.resources) {
2426
+ const name = entry.resource['name'] ?? '';
2427
+ const planned = plan.resources.find((resource) => resource.name === name);
2428
+ if (!name || planned?.status !== 'new') {
2429
+ continue;
2430
+ }
2431
+ const list = draft[entry.collection] ?? [];
2432
+ list.push(clone$1(entry.resource));
2433
+ draft[entry.collection] = list;
2434
+ }
2435
+ }
2436
+ const created = [];
2437
+ for (const entry of fragment) {
2438
+ entry.node.locationX = (entry.node.locationX ?? 0) + offset.x;
2439
+ entry.node.locationY = (entry.node.locationY ?? 0) + offset.y;
2440
+ const list = draft[entry.collection] ?? [];
2441
+ list.push(entry.node);
2442
+ draft[entry.collection] = list;
2443
+ if (entry.node.name) {
2444
+ created.push(entry.node.name);
2445
+ }
2446
+ }
2447
+ return created;
2448
+ }
2449
+ /** Il tipo dell'elemento a partire dalla collection: serve solo a raccontare il blocco. */
2450
+ function typeOfCollection(collection) {
2451
+ return TYPE_BY_COLLECTION[collection] ?? collection;
2452
+ }
2453
+
2454
+ /**
2455
+ * Gli appunti: dove il blocco copiato aspetta di essere incollato.
2456
+ *
2457
+ * E' l'**unico** servizio dichiarato `providedIn: 'root'`, e la ragione e' la stessa per cui
2458
+ * tutti gli altri non lo sono: gli store del documento sono provider del componente perche' due
2459
+ * builder devono editare due flow indipendenti — ma copiare da un flow e incollare nell'altro
2460
+ * significa, per definizione, un contenitore condiviso fra le due istanze.
2461
+ *
2462
+ * Il canale e' `localStorage`, non la clipboard di sistema. Leggere la clipboard di sistema
2463
+ * richiede un permesso che il browser chiede all'utente e che in un iframe puo' non arrivare
2464
+ * mai: un incolla che a volte non fa niente e non spiega perche' e' peggio di un incolla che
2465
+ * funziona solo dentro questo browser. Effetto collaterale voluto: gli appunti sopravvivono al
2466
+ * ricaricamento della pagina e valgono fra schede diverse, che e' proprio il caso «copio da un
2467
+ * flow, apro l'altro, incollo».
2468
+ *
2469
+ * Su un ambiente senza `localStorage` (SSR, storage negato) si degrada a una copia in memoria:
2470
+ * copiare e incollare nella stessa pagina continua a funzionare, fra schede no.
2471
+ */
2472
+ const STORAGE_KEY = 'fb.clipboard.elements';
2473
+ class FlowClipboardService {
2474
+ /** La copia in memoria: fonte di verita' quando lo storage non c'e' o rifiuta. */
2475
+ memory = null;
2476
+ _revision = signal(0, ...(ngDevMode ? [{ debugName: "_revision" }] : []));
2477
+ constructor() {
2478
+ // Una copia fatta in un'altra scheda: l'evento arriva solo alle **altre**, ed e' cio' che
2479
+ // accende il comando «Incolla» senza dover ricaricare.
2480
+ if (typeof window !== 'undefined') {
2481
+ window.addEventListener('storage', (event) => {
2482
+ if (event.key === STORAGE_KEY) {
2483
+ this._revision.update((value) => value + 1);
2484
+ }
2485
+ });
2486
+ }
2487
+ }
2488
+ storage() {
2489
+ try {
2490
+ return typeof localStorage === 'undefined' ? null : localStorage;
2491
+ }
2492
+ catch {
2493
+ // Storage negato dalle impostazioni del browser: non e' un errore da mostrare.
2494
+ return null;
2495
+ }
2496
+ }
2497
+ write(payload) {
2498
+ this.memory = payload;
2499
+ try {
2500
+ this.storage()?.setItem(STORAGE_KEY, JSON.stringify(payload));
2501
+ }
2502
+ catch {
2503
+ // Quota piena o storage negato: resta la copia in memoria.
2504
+ }
2505
+ this._revision.update((value) => value + 1);
2506
+ }
2507
+ read() {
2508
+ const raw = (() => {
2509
+ try {
2510
+ return this.storage()?.getItem(STORAGE_KEY) ?? null;
2511
+ }
2512
+ catch {
2513
+ return null;
2514
+ }
2515
+ })();
2516
+ if (raw) {
2517
+ try {
2518
+ const parsed = JSON.parse(raw);
2519
+ if (isClipboardPayload(parsed)) {
2520
+ return parsed;
2521
+ }
2522
+ }
2523
+ catch {
2524
+ // Contenuto non nostro o rovinato: vale come «appunti vuoti».
2525
+ }
2526
+ }
2527
+ return this.memory;
2528
+ }
2529
+ /**
2530
+ * C'e' qualcosa da incollare. E' un signal e non un booleano calcolato al momento perche' il
2531
+ * comando «Incolla» sta in una toolbar `OnPush`: senza un segnale che cambia, il pulsante
2532
+ * resterebbe spento fino al successivo giro di change detection.
2533
+ */
2534
+ hasContent = signal(false, ...(ngDevMode ? [{ debugName: "hasContent" }] : []));
2535
+ /** Da chiamare dove serve il valore aggiornato: rilegge lo storage e aggiorna il signal. */
2536
+ refresh() {
2537
+ const has = !!this.read();
2538
+ this.hasContent.set(has);
2539
+ return has;
2540
+ }
2541
+ /** Cambia a ogni scrittura, anche da un'altra scheda: chi vuole reagire osservi questo. */
2542
+ revision = this._revision.asReadonly();
2543
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.27", ngImport: i0, type: FlowClipboardService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2544
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.27", ngImport: i0, type: FlowClipboardService, providedIn: 'root' });
2545
+ }
2546
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImport: i0, type: FlowClipboardService, decorators: [{
2547
+ type: Injectable,
2548
+ args: [{ providedIn: 'root' }]
2549
+ }], ctorParameters: () => [] });
2550
+
1990
2551
  /**
1991
2552
  * Lo store del documento in lavorazione.
1992
2553
  *
@@ -2360,67 +2921,59 @@ class FlowDocumentStore {
2360
2921
  }
2361
2922
  /**
2362
2923
  * Riscrive ogni occorrenza di un nome nei campi che contengono riferimenti.
2363
- * Cammina l'intero documento: e' l'unico modo per non dimenticare un campo, dato che
2364
- * i riferimenti compaiono in una ventina di posti diversi (§4).
2924
+ *
2925
+ * L'implementazione sta in `core/flow-clipboard.ts` perche' la usa anche l'incolla, sul solo
2926
+ * frammento invece che sul documento: l'elenco dei campi-riferimento deve essere **uno**.
2365
2927
  */
2366
2928
  static rewriteReferences(draft, oldName, newName) {
2367
- /** Campi il cui valore e' un riferimento a risorsa o a node. */
2368
- const referenceFields = new Set([
2369
- 'targetReference',
2370
- 'elementReference',
2371
- 'assignToReference',
2372
- 'leftValueReference',
2373
- 'collectionReference',
2374
- 'assignNextValueToReference',
2375
- 'outputReference',
2376
- 'inputReference',
2377
- 'assignRecordIdToReference',
2378
- ]);
2379
- const rewrite = (value) => {
2380
- if (value === oldName) {
2381
- return newName;
2382
- }
2383
- // Riferimento navigato: `Vecchio.Campo` → `Nuovo.Campo`.
2384
- if (value.startsWith(`${oldName}.`)) {
2385
- return `${newName}${value.slice(oldName.length)}`;
2386
- }
2387
- return value;
2388
- };
2389
- const walk = (value) => {
2390
- if (Array.isArray(value)) {
2391
- value.forEach(walk);
2392
- return;
2393
- }
2394
- if (!value || typeof value !== 'object') {
2395
- return;
2396
- }
2397
- const record = value;
2398
- for (const [key, child] of Object.entries(record)) {
2399
- if (typeof child === 'string' && referenceFields.has(key)) {
2400
- record[key] = rewrite(child);
2401
- }
2402
- else {
2403
- walk(child);
2404
- }
2405
- }
2406
- };
2407
- walk(draft);
2929
+ rewriteReferences(draft, oldName, newName);
2408
2930
  }
2409
- /** Duplica un node con un nome nuovo, senza copiarne le uscite. */
2410
- duplicateNode(name, newName, offset = { x: 40, y: 40 }) {
2411
- const reference = this.nodeByName().get(name);
2412
- if (!reference) {
2413
- return;
2414
- }
2415
- const copy = clone(reference.node);
2416
- copy.name = newName;
2417
- copy.locationX = (copy.locationX ?? 0) + offset.x;
2418
- copy.locationY = (copy.locationY ?? 0) + offset.y;
2419
- // Le uscite non si copiano: duplicherebbero rami che l'utente non ha disegnato.
2420
- for (const outlet of outletsOf(reference.type, copy)) {
2421
- outlet.write(copy, undefined);
2931
+ /**
2932
+ * Duplica uno o piu' node, ritornando i nomi creati.
2933
+ *
2934
+ * E' un copia-e-incolla nello stesso documento, e passa dallo stesso motore: e' cio' che gli
2935
+ * fa rinominare **tutto** cio' che collide — non solo il node, ma anche i suoi step, i campi
2936
+ * di uno screen dinamico e le sue screen action, che stanno nello stesso spazio dei nomi
2937
+ * (§3.3) e riscrivere i riferimenti interni al blocco di conseguenza. Duplicare uno screen
2938
+ * dinamico copiandolo e basta produceva un `NAME_DUPLICATED` per ogni campo.
2939
+ *
2940
+ * Le uscite **verso l'esterno** non si copiano (duplicherebbero rami che l'utente non ha
2941
+ * disegnato); quelle interne al blocco sì, ed e' cio' che rende utile duplicare due elementi
2942
+ * collegati invece che uno alla volta.
2943
+ */
2944
+ duplicateNodes(names, offset = { x: 40, y: 40 }) {
2945
+ const payload = buildClipboardPayload({
2946
+ nodes: this.nodes(),
2947
+ resources: this.resources(),
2948
+ names,
2949
+ flowName: this._document().fullName,
2950
+ processType: this._document().processType,
2951
+ });
2952
+ if (!payload) {
2953
+ return [];
2422
2954
  }
2423
- this.addNode(reference.collection, copy);
2955
+ const plan = planPaste(payload, {
2956
+ usedNames: this.usedNames(),
2957
+ processType: this._document().processType,
2958
+ });
2959
+ // Le risorse ci sono già per definizione — si duplica dentro il flow che le contiene — e
2960
+ // il piano infatti le segna tutte `reused`: crearle sarebbe un doppione.
2961
+ return this.pasteClipboard(payload, plan, { createMissingResources: false, offset });
2962
+ }
2963
+ /**
2964
+ * Incolla gli appunti seguendo il piano già mostrato all'utente, in **un solo** passo di
2965
+ * storico: un incolla che si annulla in sette volte — un elemento, una variabile, un altro
2966
+ * elemento — non e' il gesto che l'utente ha compiuto.
2967
+ *
2968
+ * Ritorna i nomi creati, che sono cio' che va selezionato dopo: senza, la selezione resterebbe
2969
+ * sull'elemento da cui si e' copiato e il blocco appena incollato non si saprebbe dov'e'.
2970
+ */
2971
+ pasteClipboard(payload, plan, options) {
2972
+ let created = [];
2973
+ this.update((draft) => {
2974
+ created = applyPaste(draft, payload, plan, options);
2975
+ });
2976
+ return created;
2424
2977
  }
2425
2978
  // -------------------------------------------------------------------------
2426
2979
  // Connector
@@ -3906,6 +4459,13 @@ class FlowCanvasComponent {
3906
4459
  sides = EFConnectableSide;
3907
4460
  /** Il nome dell'elemento selezionato, `$start` per lo Start. */
3908
4461
  selectedName = input(null, ...(ngDevMode ? [{ debugName: "selectedName" }] : []));
4462
+ /**
4463
+ * La selezione **intera**, quando ne fa parte piu' di un elemento: la libreria sa selezionare
4464
+ * a rettangolo e con `Ctrl`+`A`, e i comandi che agiscono su un blocco (copia, duplica,
4465
+ * elimina) devono vederli tutti. `selectedName` resta l'elemento «corrente», quello di cui si
4466
+ * apre il form: un inspector che mostrasse cinque elementi insieme non saprebbe cosa mostrare.
4467
+ */
4468
+ selectedNames = input([], ...(ngDevMode ? [{ debugName: "selectedNames" }] : []));
3909
4469
  /**
3910
4470
  * L'outline del backend, quando disponibile: da' la raggiungibilita' autoritativa (§6.4).
3911
4471
  * Assente → si usa il calcolo locale.
@@ -3917,6 +4477,10 @@ class FlowCanvasComponent {
3917
4477
  * perche' il canvas resti montabile da solo.
3918
4478
  */
3919
4479
  isEditable = input(true, ...(ngDevMode ? [{ debugName: "isEditable" }] : []));
4480
+ /**
4481
+ * La selezione dopo il gesto: vuota se si e' cliccato nel vuoto. Il primo nome e' quello
4482
+ * «corrente» — chi ascolta ne fa la selezione singola.
4483
+ */
3920
4484
  selectionChange = output();
3921
4485
  nodeOpened = output();
3922
4486
  nodeRemoveRequested = output();
@@ -4172,8 +4736,10 @@ class FlowCanvasComponent {
4172
4736
  });
4173
4737
  }
4174
4738
  onSelectionChange(event) {
4175
- const first = event.nodeIds[0];
4176
- this.selectionChange.emit(first ? parseCanvasNodeId(first) : null);
4739
+ const names = event.nodeIds
4740
+ .map((id) => parseCanvasNodeId(id))
4741
+ .filter((name) => !!name);
4742
+ this.selectionChange.emit(names);
4177
4743
  }
4178
4744
  /** `Delete` sulla selezione: lo Start non e' cancellabile, e' uno per flow (§3.4). */
4179
4745
  onDeleteSelected(event) {
@@ -4226,14 +4792,24 @@ class FlowCanvasComponent {
4226
4792
  event.stopPropagation();
4227
4793
  this.nodeRemoveRequested.emit(name);
4228
4794
  }
4795
+ /** Come ✎ e ×: `stopPropagation` perche' il clic non valga anche come selezione. */
4796
+ onDuplicateClick(event, name) {
4797
+ event.stopPropagation();
4798
+ this.nodeDuplicateRequested.emit(name);
4799
+ }
4229
4800
  connectorIdOf(node, outletKey) {
4230
4801
  return sourceConnectorId(node.name, outletKey);
4231
4802
  }
4232
4803
  targetIdOf(node) {
4233
4804
  return targetConnectorId(node.name);
4234
4805
  }
4806
+ /**
4807
+ * Selezionato = corrente **oppure** parte del blocco selezionato. Senza il secondo caso, un
4808
+ * rettangolo che prende cinque elementi ne evidenziava uno, e i comandi che agiscono su tutti
4809
+ * e cinque sembravano agire su quello.
4810
+ */
4235
4811
  isSelected(node) {
4236
- return this.selectedName() === node.name;
4812
+ return this.selectedName() === node.name || this.selectedNames().includes(node.name);
4237
4813
  }
4238
4814
  /** Etichette dei rami dichiarati senza destinazione, per il tooltip del badge. */
4239
4815
  danglingLabels(node) {
@@ -4263,12 +4839,12 @@ class FlowCanvasComponent {
4263
4839
  return 'cat-other';
4264
4840
  }
4265
4841
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.27", ngImport: i0, type: FlowCanvasComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4266
- 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.4), 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 });
4842
+ 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 }, selectedNames: { classPropertyName: "selectedNames", publicName: "selectedNames", 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 Duplica: sta accanto a \u270E e non solo nell'inspector perche' e' il gesto con cui si\r\n riusa un elemento gi\u00E0 configurato, e cercarlo dentro il form dell'elemento da\r\n copiare e' il posto in cui non lo si cerca. Compare come \u00AB\u00D7\u00BB, sul solo node\r\n selezionato: aggiunge un elemento al documento, quindi non deve trovarsi sotto il\r\n puntatore di chi sta solo attraversando il grafo.\r\n -->\r\n <button\r\n type=\"button\"\r\n fDragBlocker\r\n class=\"fb-node__duplicate\"\r\n [attr.aria-label]=\"'Duplica ' + node.label\"\r\n title=\"Duplica questo elemento (Ctrl+D)\"\r\n (click)=\"onDuplicateClick($event, node.name)\"\r\n >\r\n \u29C9\r\n </button>\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.4), 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__duplicate,.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__duplicate,.fb-node--selected .fb-node__remove,.fb-node__duplicate:focus-visible,.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__duplicate:hover{background:color-mix(in srgb,var(--fb-accent, #3b6ef2) 12%,transparent);color:var(--fb-accent, #3b6ef2)}.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 });
4267
4843
  }
4268
4844
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImport: i0, type: FlowCanvasComponent, decorators: [{
4269
4845
  type: Component,
4270
- 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.4), 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"] }]
4271
- }], 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 }] }] } });
4846
+ 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 Duplica: sta accanto a \u270E e non solo nell'inspector perche' e' il gesto con cui si\r\n riusa un elemento gi\u00E0 configurato, e cercarlo dentro il form dell'elemento da\r\n copiare e' il posto in cui non lo si cerca. Compare come \u00AB\u00D7\u00BB, sul solo node\r\n selezionato: aggiunge un elemento al documento, quindi non deve trovarsi sotto il\r\n puntatore di chi sta solo attraversando il grafo.\r\n -->\r\n <button\r\n type=\"button\"\r\n fDragBlocker\r\n class=\"fb-node__duplicate\"\r\n [attr.aria-label]=\"'Duplica ' + node.label\"\r\n title=\"Duplica questo elemento (Ctrl+D)\"\r\n (click)=\"onDuplicateClick($event, node.name)\"\r\n >\r\n \u29C9\r\n </button>\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.4), 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__duplicate,.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__duplicate,.fb-node--selected .fb-node__remove,.fb-node__duplicate:focus-visible,.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__duplicate:hover{background:color-mix(in srgb,var(--fb-accent, #3b6ef2) 12%,transparent);color:var(--fb-accent, #3b6ef2)}.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"] }]
4847
+ }], propDecorators: { selectedName: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedName", required: false }] }], selectedNames: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedNames", 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 }] }] } });
4272
4848
 
4273
4849
  /**
4274
4850
  * La palette degli elementi — FRONTEND.md §3.2, §11 ("Aggiungere un elemento").
@@ -9499,7 +10075,7 @@ class DynamicScreenInspectorComponent extends NodeInspectorBase {
9499
10075
  });
9500
10076
  }
9501
10077
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.27", ngImport: i0, type: DynamicScreenInspectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
9502
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.27", type: DynamicScreenInspectorComponent, isStandalone: true, selector: "fb-dynamic-screen-inspector", usesInheritance: true, ngImport: i0, template: "<div class=\"fb-scr\">\r\n <!-- ============================================================ sinistra -->\r\n <aside class=\"fb-scr__side\" aria-label=\"Componenti e campi\">\r\n <div class=\"fb-scr__tabs\" role=\"tablist\">\r\n <button\r\n type=\"button\"\r\n role=\"tab\"\r\n class=\"fb-scr__tab\"\r\n [class.fb-scr__tab--active]=\"paletteTab() === 'components'\"\r\n [attr.aria-selected]=\"paletteTab() === 'components'\"\r\n (click)=\"setPaletteTab('components')\"\r\n >\r\n Componenti\r\n </button>\r\n <button\r\n type=\"button\"\r\n role=\"tab\"\r\n class=\"fb-scr__tab\"\r\n [class.fb-scr__tab--active]=\"paletteTab() === 'fields'\"\r\n [attr.aria-selected]=\"paletteTab() === 'fields'\"\r\n (click)=\"setPaletteTab('fields')\"\r\n >\r\n Campi\r\n </button>\r\n </div>\r\n\r\n @if (paletteTab() === 'components') {\r\n @if (!dictionary.screenFieldTypes().length) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Il dizionario <code>screenFieldTypes</code> non e\u2019 disponibile: senza i suoi flag l\u2019editor non\r\n sa quali campi ammette ciascun tipo, e non li inventa.\r\n </p>\r\n }\r\n <p class=\"fb-scr__side-hint\">Trascina sulla schermata, oppure clicca per aggiungere.</p>\r\n <ul class=\"fb-scr__palette\">\r\n @for (entry of dictionary.screenFieldTypes(); track entry.value) {\r\n <li>\r\n <button\r\n type=\"button\"\r\n class=\"fb-scr__chip\"\r\n cdkDrag\r\n [title]=\"entry.description || entry.label\"\r\n (cdkDragMoved)=\"onPaletteDragMoved($event)\"\r\n (cdkDragEnded)=\"onComponentDragEnded($event, entry.value)\"\r\n (click)=\"addComponentAtSelection(entry.value)\"\r\n >\r\n <span class=\"fb-scr__chip-icon\" aria-hidden=\"true\">{{ iconOf(entry.value) }}</span>\r\n <span class=\"fb-scr__chip-label\">{{ entry.label }}</span>\r\n </button>\r\n </li>\r\n }\r\n </ul>\r\n } @else {\r\n <p class=\"fb-scr__side-hint\">\r\n Un campo dell\u2019entita\u2019 porta con se\u2019 tipo, etichetta, obbligatorieta\u2019 e opzioni: arrivano dallo\r\n schema dati, qui non si ridichiarano.\r\n </p>\r\n <!-- Il catalogo non e' un dizionario chiuso: si sceglie o si scrive, come ovunque si\r\n indichi un'entita' (vedi `object-picker`). -->\r\n <fb-object-picker\r\n [value]=\"fieldsObject()\"\r\n label=\"Entita\u2019\"\r\n placeholder=\"Scrivi o scegli un\u2019entita\u2019\"\r\n (valueChange)=\"setFieldsObject($event)\"\r\n />\r\n @if (fieldsObject() && !fieldsOfObject().length) {\r\n <p class=\"fb-scr__side-empty\">\r\n Nessun campo dichiarato per questa entita\u2019: il catalogo non li espone, non significa che non\r\n ci siano.\r\n </p>\r\n }\r\n <ul class=\"fb-scr__palette\">\r\n @for (field of fieldsOfObject(); track $index) {\r\n <li>\r\n <button\r\n type=\"button\"\r\n class=\"fb-scr__chip\"\r\n cdkDrag\r\n [title]=\"field.name + (field.dataType ? ' \u00B7 ' + field.dataType : '')\"\r\n (cdkDragMoved)=\"onPaletteDragMoved($event)\"\r\n (cdkDragEnded)=\"onObjectFieldDragEnded($event, field)\"\r\n (click)=\"addObjectFieldAtSelection(field)\"\r\n >\r\n <span class=\"fb-scr__chip-icon\" aria-hidden=\"true\">\u2317</span>\r\n <span class=\"fb-scr__chip-label\">\r\n {{ field.label || field.name }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n <small>{{ field.dataType }}</small>\r\n </span>\r\n </button>\r\n </li>\r\n }\r\n </ul>\r\n }\r\n </aside>\r\n\r\n <!-- ============================================================== centro -->\r\n <!-- Cliccare fuori da un campo riporta a destra le proprieta' della schermata. -->\r\n <section class=\"fb-scr__canvas\" aria-label=\"Anteprima della schermata\" (click)=\"selectScreen()\">\r\n <div class=\"fb-scr__frame\">\r\n @if (showHeader()) {\r\n <header class=\"fb-scr__frame-head\">\r\n {{ $any(node()).label || name() }}\r\n @if (screen().helpText) {\r\n <span class=\"fb-scr__help\" [title]=\"screen().helpText || ''\">?</span>\r\n }\r\n </header>\r\n }\r\n\r\n <div\r\n class=\"fb-scr__body\"\r\n [attr.data-drop]=\"''\"\r\n data-axis=\"column\"\r\n [class.fb-scr__body--empty]=\"isEmpty()\"\r\n >\r\n <ng-container\r\n [ngTemplateOutlet]=\"listTpl\"\r\n [ngTemplateOutletContext]=\"{ $implicit: screen().fields || [], parent: [], axis: 'column' }\"\r\n />\r\n </div>\r\n\r\n <!--\r\n La barra di inserimento e' un **velo** sopra l'anteprima, non un figlio del contenitore di\r\n rilascio: dentro la griglia occupava una riga intera e spostava in basso proprio la sezione\r\n che si stava puntando, che allora usciva da sotto il puntatore e il bersaglio oscillava.\r\n -->\r\n @if (dropMarker(); as bar) {\r\n <div\r\n class=\"fb-scr__marker\"\r\n [style.left.px]=\"bar.left\"\r\n [style.top.px]=\"bar.top\"\r\n [style.width.px]=\"bar.width\"\r\n [style.height.px]=\"bar.height\"\r\n ></div>\r\n }\r\n\r\n @if (showFooter()) {\r\n <footer class=\"fb-scr__frame-foot\">\r\n @if (allowPause()) {\r\n <span class=\"fb-scr__btn fb-scr__btn--ghost\">{{ screen().pauseButtonLabel || 'Pausa' }}</span>\r\n }\r\n <span class=\"fb-scr__spacer\"></span>\r\n @if (allowBack()) {\r\n <span class=\"fb-scr__btn fb-scr__btn--ghost\">{{ screen().backButtonLabel || 'Indietro' }}</span>\r\n }\r\n <span class=\"fb-scr__btn fb-scr__btn--primary\">\r\n {{ screen().nextOrFinishButtonLabel || (allowFinish() ? 'Fine' : 'Avanti') }}\r\n </span>\r\n </footer>\r\n }\r\n </div>\r\n\r\n @if (isEmpty()) {\r\n <p class=\"fb-callout fb-callout--warn fb-scr__canvas-note\">\r\n La schermata non ha campi: non c\u2019e\u2019 niente da mostrare all\u2019utente (SCREEN_WITHOUT_FIELDS).\r\n </p>\r\n }\r\n </section>\r\n\r\n <!-- ============================================================== destra -->\r\n <aside class=\"fb-scr__props\" aria-label=\"Proprieta\u2019\">\r\n @if (!selectedField()) {\r\n <!-- ------------------------------------------------ proprieta' della schermata -->\r\n <header class=\"fb-scr__props-head\">\r\n <h3 class=\"fb-scr__props-title\">Schermata</h3>\r\n </header>\r\n <p class=\"fb-scr__side-hint\">Seleziona un campo nell\u2019anteprima per configurarlo.</p>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Testo di aiuto</label>\r\n <textarea\r\n class=\"fb-textarea\"\r\n [value]=\"screen().helpText || ''\"\r\n (input)=\"setScreenText('helpText', $any($event.target).value)\"\r\n ></textarea>\r\n </div>\r\n\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Navigazione</legend>\r\n <p class=\"fb-section__note\">\r\n Questi flag sono un\u2019intenzione, non la verita\u2019 finale: a runtime il motore comunica\r\n <code>canGoBack</code>, <code>canFinish</code> e <code>canPause</code> nella richiesta della\r\n schermata.\r\n </p>\r\n <label class=\"fb-check\">\r\n <input type=\"checkbox\" [checked]=\"allowBack()\" (change)=\"setScreenFlag('allowBack', $any($event.target).checked)\" />\r\n Consenti \u00ABindietro\u00BB\r\n </label>\r\n <label class=\"fb-check\">\r\n <input type=\"checkbox\" [checked]=\"allowFinish()\" (change)=\"setScreenFlag('allowFinish', $any($event.target).checked)\" />\r\n Consenti \u00ABfine\u00BB\r\n </label>\r\n <label class=\"fb-check\">\r\n <input type=\"checkbox\" [checked]=\"allowPause()\" (change)=\"setScreenFlag('allowPause', $any($event.target).checked)\" />\r\n Consenti \u00ABpausa\u00BB\r\n </label>\r\n <label class=\"fb-check\">\r\n <input type=\"checkbox\" [checked]=\"showHeader()\" (change)=\"setScreenFlag('showHeader', $any($event.target).checked)\" />\r\n Mostra l\u2019intestazione\r\n </label>\r\n <label class=\"fb-check\">\r\n <input type=\"checkbox\" [checked]=\"showFooter()\" (change)=\"setScreenFlag('showFooter', $any($event.target).checked)\" />\r\n Mostra il piede\r\n </label>\r\n\r\n @if (allowPause()) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Testo mostrato alla pausa</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"screen().pausedText || ''\"\r\n (input)=\"setScreenText('pausedText', $any($event.target).value)\"\r\n />\r\n </div>\r\n }\r\n\r\n @if (isDeadEnd()) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n Questa schermata non ha una destinazione e non consente \u00ABfine\u00BB: e\u2019 un vicolo cieco, e\r\n l\u2019utente resterebbe bloccato (SCREEN_DEAD_END).\r\n </p>\r\n }\r\n </fieldset>\r\n\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Pulsanti</legend>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Etichetta \u00ABindietro\u00BB</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"screen().backButtonLabel || ''\"\r\n (input)=\"setScreenText('backButtonLabel', $any($event.target).value)\"\r\n />\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Etichetta \u00ABavanti / fine\u00BB</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"screen().nextOrFinishButtonLabel || ''\"\r\n (input)=\"setScreenText('nextOrFinishButtonLabel', $any($event.target).value)\"\r\n />\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Etichetta \u00ABpausa\u00BB</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"screen().pauseButtonLabel || ''\"\r\n (input)=\"setScreenText('pauseButtonLabel', $any($event.target).value)\"\r\n />\r\n </div>\r\n </fieldset>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Stage mostrato nell\u2019avanzamento</label>\r\n <fb-name-picker\r\n [value]=\"screen().stageReference\"\r\n [options]=\"stageOptions()\"\r\n label=\"Stage\"\r\n placeholder=\"Scegli uno stage\"\r\n unknownMessage=\"Questo stage non e\u2019 dichiarato dal flow.\"\r\n emptyMessage=\"Il flow non dichiara stage: creali nel pannello delle risorse.\"\r\n (valueChange)=\"setScreenText('stageReference', $event ?? '')\"\r\n />\r\n </div>\r\n\r\n <!--\r\n Screen action (\u00A75.2). Due elenchi e non uno perche' il contratto li separa: piu' trigger\r\n possono invocare la stessa action, quindi \u00ABchi la chiama\u00BB non e' una sua proprieta'. Le\r\n action si aprono una alla volta: i cataloghi dipendono da tipo e nome, e tenerne N in volo\r\n sarebbe N volte la corsa fra risposte.\r\n -->\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Azioni della schermata</legend>\r\n <p class=\"fb-section__note\">\r\n Un campo che l\u2019utente compila puo\u2019 innescare un\u2019action i cui risultati finiscono negli\r\n <strong>altri campi di questa schermata</strong>: e\u2019 il caso \u00ABscrivi il codice fiscale e nome\r\n e cognome compaiono da soli\u00BB. L\u2019alternativa sarebbe spezzare la schermata in due con un\r\n elemento Action in mezzo.\r\n </p>\r\n\r\n <div class=\"fb-list\">\r\n @for (action of screenActions(); track $index) {\r\n <div class=\"fb-list__item\">\r\n <div class=\"fb-list__header\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost\"\r\n [attr.aria-expanded]=\"selectedActionIndex() === $index\"\r\n (click)=\"selectAction(selectedActionIndex() === $index ? null : $index)\"\r\n >\r\n {{ selectedActionIndex() === $index ? '\u25BE' : '\u25B8' }}\r\n {{ action.name || '(senza nome)' }}\r\n </button>\r\n <span class=\"fb-list__spacer\"></span>\r\n <span class=\"fb-scr__side-hint\">{{ action.actionName || 'action non scelta' }}</span>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n aria-label=\"Rimuovi l\u2019action\"\r\n (click)=\"removeScreenAction($index)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n\r\n @if (actionLosesResult(action)) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Questa action viene invocata ma il suo risultato si perde: aggiungi un parametro di\r\n uscita, oppure accendi l\u2019output automatico (SCREEN_ACTION_WITHOUT_OUTPUTS).\r\n </p>\r\n }\r\n @if (actionHasNoTrigger(action)) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Nessun trigger la invoca: senza, non girera\u2019 mai.\r\n </p>\r\n }\r\n\r\n @if (selectedActionIndex() === $index) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Nome</label>\r\n <input\r\n class=\"fb-input fb-input--mono\"\r\n [value]=\"action.name || ''\"\r\n (change)=\"setActionName($index, $any($event.target).value)\"\r\n />\r\n @if (actionNameError(action); as error) {\r\n <p class=\"fb-field__error\">{{ error }}</p>\r\n }\r\n <p class=\"fb-field__hint\">\r\n E\u2019 il nome con cui la citano i trigger, e vive nello spazio dei nomi del flow. Con\r\n l\u2019output automatico e\u2019 anche la radice del riferimento:\r\n <code>{{ action.name || 'Cerca' }}.NomeOutput</code>.\r\n </p>\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Tipo di action</label>\r\n <fb-name-picker\r\n [value]=\"action.actionType\"\r\n [options]=\"actionTypeOptions()\"\r\n label=\"Tipo di action\"\r\n placeholder=\"Scrivi o scegli un tipo\"\r\n [isMono]=\"false\"\r\n unknownMessage=\"Questo tipo di action non e\u2019 fra quelli dichiarati dal sistema ospite.\"\r\n emptyMessage=\"Catalogo dei tipi di action non disponibile: puoi scrivere il nome a mano.\"\r\n (valueChange)=\"setActionType($index, $event ?? '')\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Action</label>\r\n <fb-name-picker\r\n [value]=\"action.actionName\"\r\n [options]=\"actionOptions()\"\r\n label=\"Action\"\r\n placeholder=\"Scrivi o scegli un\u2019action\"\r\n [isMono]=\"false\"\r\n unknownMessage=\"Questa action non esiste nel catalogo del tipo scelto: e\u2019 ACTION_UNKNOWN.\"\r\n unknownSeverity=\"error\"\r\n emptyMessage=\"Scegli prima il tipo di action, oppure scrivi il nome a mano.\"\r\n (valueChange)=\"setActionTarget($index, $event ?? '')\"\r\n />\r\n @if (!action.actionName) {\r\n <p class=\"fb-field__error\">Obbligatoria: senza, ACTION_NAME_MISSING.</p>\r\n }\r\n </div>\r\n\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"action.storeOutputAutomatically === true\"\r\n (change)=\"setActionStoreOutput($index, $any($event.target).checked)\"\r\n />\r\n Output automatico\r\n </label>\r\n <p class=\"fb-field__hint\">\r\n L\u2019output automatico rende il risultato leggibile come\r\n <code>{{ action.name || 'Cerca' }}.NomeOutput</code>, ma\r\n <strong>non scrive in nessun campo</strong>: e\u2019 la forma da usare quando il risultato\r\n serve a una condizione, non a precompilare.\r\n </p>\r\n\r\n <!--\r\n L'unico posto in cui un campo di schermata e' una destinazione (\u00A75.2):\r\n `POST /flows/references/writable` continua a escluderlo, e un Assignment che ci\r\n scrive resta TARGET_NOT_WRITABLE.\r\n -->\r\n <fb-parameter-editor\r\n [holder]=\"action\"\r\n [catalogParameters]=\"actionParameterCatalog()\"\r\n [showOutputs]=\"action.storeOutputAutomatically !== true\"\r\n outputTitle=\"Campi da riempire\"\r\n [extraTargets]=\"screenFieldTargets()\"\r\n [outputsDisabledReason]=\"\r\n action.storeOutputAutomatically === true\r\n ? 'Con l\u2019output automatico il risultato non viene scritto nei campi.'\r\n : null\r\n \"\r\n (changed)=\"onActionParametersChanged($index, $event)\"\r\n />\r\n }\r\n </div>\r\n } @empty {\r\n <p class=\"fb-empty\">Nessuna action: i campi di questa schermata li compila solo l\u2019utente.</p>\r\n }\r\n </div>\r\n\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"addScreenAction()\">Aggiungi action</button>\r\n </fieldset>\r\n\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Trigger</legend>\r\n <p class=\"fb-section__note\">\r\n Quale campo invoca quale action. Senza condizioni l\u2019action gira a ogni cambio del campo.\r\n </p>\r\n\r\n <div class=\"fb-list\">\r\n @for (trigger of screenTriggers(); track $index) {\r\n <div class=\"fb-list__item\">\r\n <div class=\"fb-list__header\">\r\n <span class=\"fb-list__index\">{{ $index + 1 }}</span>\r\n <span class=\"fb-list__spacer\"></span>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n aria-label=\"Rimuovi il trigger\"\r\n (click)=\"removeScreenTrigger($index)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Action da invocare</label>\r\n <fb-name-picker\r\n [value]=\"trigger.screenActionName\"\r\n [options]=\"screenActionOptions()\"\r\n label=\"Action della schermata\"\r\n placeholder=\"Scegli un\u2019action\"\r\n unknownMessage=\"Questa schermata non dichiara un\u2019action con questo nome: e\u2019 SCREEN_ACTION_UNKNOWN.\"\r\n unknownSeverity=\"error\"\r\n emptyMessage=\"Aggiungi prima un\u2019action qui sopra.\"\r\n (valueChange)=\"setTriggerProperty($index, 'screenActionName', $event ?? '')\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Campo che la innesca</label>\r\n <!-- I campi di **questa** schermata, non i riferimenti in generale (\u00A75.2). -->\r\n <fb-name-picker\r\n [value]=\"trigger.triggerFieldName\"\r\n [options]=\"triggerFieldOptions()\"\r\n label=\"Campo della schermata\"\r\n placeholder=\"Scegli un campo\"\r\n unknownMessage=\"Questo non e\u2019 un campo di questa schermata: e\u2019 SCREEN_TRIGGER_FIELD_UNKNOWN.\"\r\n unknownSeverity=\"error\"\r\n emptyMessage=\"La schermata non ha ancora campi che raccolgono un valore.\"\r\n (valueChange)=\"setTriggerProperty($index, 'triggerFieldName', $event ?? '')\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">All\u2019arrivo sulla schermata</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"trigger.initBehavior || ''\"\r\n (change)=\"setTriggerProperty($index, 'initBehavior', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 non invocare \u2014</option>\r\n <option value=\"runOnLoad\">Invoca al caricamento (runOnLoad)</option>\r\n </select>\r\n @if (triggerInitIsUnknown(trigger)) {\r\n <p class=\"fb-field__error\">\r\n \u00AB{{ trigger.initBehavior }}\u00BB non e\u2019 ammesso: l\u2019unico valore e\u2019 <code>runOnLoad</code>\r\n (SCREEN_TRIGGER_INIT_BEHAVIOR_UNKNOWN).\r\n </p>\r\n }\r\n </div>\r\n\r\n @if (triggerHasNoCause(trigger)) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Senza un campo che la innesca e senza l\u2019invocazione al caricamento, questo trigger non\r\n scattera\u2019 mai (SCREEN_TRIGGER_WITHOUT_CAUSE).\r\n </p>\r\n }\r\n\r\n <fb-condition-editor\r\n [holder]=\"triggerConditions(trigger)\"\r\n title=\"Invoca l\u2019action quando\"\r\n [allowFormula]=\"false\"\r\n [issuePath]=\"'triggers[' + $index + ']'\"\r\n (changed)=\"onTriggerConditionsChanged($index, $event)\"\r\n />\r\n </div>\r\n } @empty {\r\n <p class=\"fb-empty\">Nessun trigger: le action dichiarate non verranno invocate.</p>\r\n }\r\n </div>\r\n\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"addScreenTrigger()\">Aggiungi trigger</button>\r\n </fieldset>\r\n } @else {\r\n <!-- ---------------------------------------------------- proprieta' del campo -->\r\n <header class=\"fb-scr__props-head\">\r\n <h3 class=\"fb-scr__props-title\">{{ captionOf(selectedField()!) }}</h3>\r\n <div class=\"fb-scr__props-actions\">\r\n <button type=\"button\" class=\"fb-btn fb-btn--sm\" title=\"Sposta su\" (click)=\"moveSelected(-1)\">\u2191</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--sm\" title=\"Sposta giu\u2019\" (click)=\"moveSelected(1)\">\u2193</button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--sm\"\r\n title=\"Porta fuori dal contenitore\"\r\n [disabled]=\"!canOutdent()\"\r\n (click)=\"outdentSelected()\"\r\n >\r\n \u21E4\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--sm\" title=\"Duplica\" (click)=\"duplicateSelected()\">\u29C9</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--danger fb-btn--sm\" title=\"Elimina\" (click)=\"removeSelected()\">\r\n \u00D7\r\n </button>\r\n </div>\r\n </header>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Tipo</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"selectedField()!.fieldType || ''\"\r\n (change)=\"setFieldType($any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 scegli \u2014</option>\r\n @for (entry of dictionary.screenFieldTypes(); track entry.value) {\r\n <option [value]=\"entry.value\">{{ entry.label }}</option>\r\n }\r\n </select>\r\n @if (!selectedField()!.fieldType) {\r\n <p class=\"fb-field__error\">Il tipo e\u2019 obbligatorio: senza, SCREEN_FIELD_TYPE_MISSING.</p>\r\n } @else if (isUnknownType(selectedField())) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n Questo tipo non e\u2019 nel dizionario: l\u2019editor non sa quali campi ammetta e mostra solo i comuni.\r\n </p>\r\n } @else if (selectedType()?.description) {\r\n <p class=\"fb-field__hint\">{{ selectedType()?.description }}</p>\r\n }\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Nome</label>\r\n <!-- Sul `change` e non sull\u2019`input`: la rinomina riscrive i riferimenti nel documento,\r\n e farlo a ogni tasto significherebbe riscriverlo per ogni lettera. -->\r\n <input\r\n class=\"fb-input fb-input--mono\"\r\n [value]=\"selectedField()!.name || ''\"\r\n (change)=\"setFieldName($any($event.target).value)\"\r\n />\r\n @if (selectedIsResource()) {\r\n <p class=\"fb-field__hint\">\r\n Questo campo e\u2019 una <strong>risorsa</strong>: lo referenzi come\r\n <code>{{ selectedField()!.name || 'Nome' }}</code> in condizioni, formule e parametri. \u00C8 di\r\n sola lettura per il flow \u2014 un Assignment che ci scrive e\u2019 TARGET_NOT_WRITABLE.\r\n </p>\r\n }\r\n </div>\r\n\r\n @if (!selectedType()?.isContainer && selectedField()!.fieldType !== 'ComponentInstance') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">\r\n {{ selectedField()!.fieldType === 'DisplayText' ? 'Testo' : 'Etichetta' }}\r\n </label>\r\n <textarea\r\n class=\"fb-textarea\"\r\n [value]=\"selectedField()!.fieldText || ''\"\r\n (input)=\"setFieldProperty('fieldText', $any($event.target).value)\"\r\n ></textarea>\r\n <p class=\"fb-field__hint\">Supporta i merge field <code>&#123;!Riferimento&#125;</code>.</p>\r\n </div>\r\n } @else if (selectedType()?.isContainer) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Intestazione</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"selectedField()!.fieldText || ''\"\r\n (input)=\"setFieldProperty('fieldText', $any($event.target).value)\"\r\n />\r\n </div>\r\n }\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Testo di aiuto</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"selectedField()!.helpText || ''\"\r\n (input)=\"setFieldProperty('helpText', $any($event.target).value)\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Larghezza</label>\r\n <div class=\"fb-scr__width\">\r\n <input\r\n type=\"range\"\r\n min=\"1\"\r\n max=\"12\"\r\n step=\"1\"\r\n [value]=\"widthOf(selectedField()!)\"\r\n (input)=\"setNumberProperty('width', $any($event.target).value)\"\r\n />\r\n <span class=\"fb-scr__width-value\">{{ widthOf(selectedField()!) }}/12</span>\r\n </div>\r\n <p class=\"fb-field__hint\">\r\n Colonne della griglia della schermata. \u00C8 un\u2019indicazione: il frontend puo\u2019 ignorarla.\r\n </p>\r\n </div>\r\n\r\n <!-- ------------------------------------------------ campi che raccolgono un valore -->\r\n @if (selectedType()?.storesValue) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Valore</legend>\r\n\r\n @if (selectedField()!.fieldType !== 'ObjectProvided') {\r\n <div class=\"fb-field\">\r\n <label\r\n class=\"fb-field__label\"\r\n [class.fb-field__label--required]=\"!!selectedType()?.requiresDataType\"\r\n >\r\n Tipo di dato\r\n </label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"selectedField()!.dataType || ''\"\r\n (change)=\"setFieldProperty('dataType', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 scegli \u2014</option>\r\n @for (entry of dictionary.dataTypes(); track entry.value) {\r\n <option [value]=\"entry.value\">{{ entry.label }}</option>\r\n }\r\n </select>\r\n @if (selectedType()?.requiresDataType && !selectedField()!.dataType) {\r\n <p class=\"fb-field__error\">Obbligatorio per questo tipo di campo (DATA_TYPE_MISSING).</p>\r\n }\r\n @if (selectedType()?.isCollection) {\r\n <p class=\"fb-field__hint\">\r\n Il valore raccolto e\u2019 una <strong>collection</strong>, non una stringa con i valori\r\n separati: nelle condizioni si usano gli operatori di collection.\r\n </p>\r\n }\r\n </div>\r\n\r\n @if (requiresObjectType()) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\" [class.fb-field__label--required]=\"objectTypeIsStructure()\">\r\n {{\r\n objectTypeIsStructure()\r\n ? 'Classe'\r\n : selectedField()!.dataType === 'Enum'\r\n ? 'Tipo di enumerazione'\r\n : 'Oggetto'\r\n }}\r\n </label>\r\n @if (selectedField()!.dataType === 'Enum') {\r\n <!-- Dizionario chiuso: si sceglie, non si scrive. -->\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"selectedField()!.objectType || ''\"\r\n (change)=\"setFieldProperty('objectType', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 scegli \u2014</option>\r\n @for (entry of enumOptions(); track entry.name) {\r\n <option [value]=\"entry.name\">{{ entry.label || entry.name }}</option>\r\n }\r\n </select>\r\n @if (!selectedField()!.objectType) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n Senza il tipo concreto l\u2019editor non puo\u2019 proporre i valori dell\u2019enumerazione.\r\n </p>\r\n }\r\n } @else if (objectTypeIsStructure()) {\r\n <fb-structure-picker\r\n [value]=\"selectedField()!.objectType\"\r\n label=\"Classe\"\r\n (valueChange)=\"setFieldProperty('objectType', $event ?? '')\"\r\n />\r\n @if (!selectedField()!.objectType) {\r\n <p class=\"fb-field__error\">\r\n La classe e\u2019 obbligatoria: senza, il runtime non ha nulla da istanziare\r\n (OBJECT_TYPE_MISSING).\r\n </p>\r\n }\r\n } @else {\r\n <fb-object-picker\r\n [value]=\"selectedField()!.objectType\"\r\n label=\"Oggetto\"\r\n (valueChange)=\"setFieldProperty('objectType', $event ?? '')\"\r\n />\r\n }\r\n </div>\r\n }\r\n } @else {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Campo dell\u2019entita\u2019</label>\r\n <p class=\"fb-field__hint\">\r\n Tipo, label, obbligatorieta\u2019, scala e \u2014 se il campo e\u2019 una picklist \u2014 le opzioni arrivano\r\n dallo schema dati: qui non si ridichiarano.\r\n </p>\r\n <fb-object-picker\r\n [value]=\"providedObject() || undefined\"\r\n label=\"Oggetto\"\r\n (valueChange)=\"setProvidedObject($event)\"\r\n />\r\n <fb-field-picker\r\n [value]=\"providedField() || undefined\"\r\n [object]=\"providedObject() || undefined\"\r\n label=\"Campo\"\r\n (valueChange)=\"setProvidedField($event)\"\r\n />\r\n </div>\r\n }\r\n\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"selectedIsRequired()\"\r\n (change)=\"setRequired($any($event.target).checked)\"\r\n />\r\n Obbligatorio\r\n </label>\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"selectedIsEditable()\"\r\n (change)=\"setEditable($any($event.target).checked)\"\r\n />\r\n Modificabile\r\n </label>\r\n @if (!selectedIsEditable()) {\r\n <p class=\"fb-field__hint\">\r\n A <code>false</code> il runtime <strong>ignora</strong> cio\u2019 che il client rimanda indietro:\r\n e\u2019 l\u2019unico modo di rendere un campo davvero di sola lettura.\r\n </p>\r\n }\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Valore di default</label>\r\n <fb-value-editor\r\n [value]=\"selectedField()!.defaultValue\"\r\n [dataType]=\"selectedField()!.dataType\"\r\n [objectType]=\"selectedField()!.objectType\"\r\n [isCollection]=\"selectedType()?.isCollection\"\r\n label=\"Valore di default\"\r\n (valueChange)=\"setDefaultValue($event)\"\r\n />\r\n </div>\r\n\r\n @if (scaleApplies()) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Decimali</label>\r\n <input\r\n class=\"fb-input\"\r\n type=\"number\"\r\n min=\"0\"\r\n [value]=\"selectedField()!.scale ?? ''\"\r\n (change)=\"setNumberProperty('scale', $any($event.target).value)\"\r\n />\r\n </div>\r\n } @else if (selectedField()!.scale !== undefined) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n <code>scale</code> su un campo non numerico e\u2019 SCALE_NOT_APPLICABLE.\r\n </p>\r\n }\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Lunghezza massima</label>\r\n <input\r\n class=\"fb-input\"\r\n type=\"number\"\r\n min=\"1\"\r\n [value]=\"selectedField()!.maxLength ?? ''\"\r\n (change)=\"setNumberProperty('maxLength', $any($event.target).value)\"\r\n />\r\n <p class=\"fb-field__hint\">Verificata anche dal runtime (TOO_LONG).</p>\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Tornando sulla schermata</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"selectedField()!.inputsOnNextNavToAssocScrn || ''\"\r\n (change)=\"setFieldProperty('inputsOnNextNavToAssocScrn', $any($event.target).value)\"\r\n >\r\n <option value=\"\">Predefinito (mantieni i valori)</option>\r\n @for (entry of dictionary.screenFieldInputsRevisited(); track entry.value) {\r\n <option [value]=\"entry.value\">{{ entry.label }}</option>\r\n }\r\n </select>\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Regola di validazione</label>\r\n <!--\r\n `commitOn=\"change\"`: qui mutare il documento ridisegna l'anteprima, e riscriverla a\r\n ogni tasto costa. La verifica parte lo stesso mentre si digita (\u00A76.3).\r\n -->\r\n <fb-formula-editor\r\n [expression]=\"selectedField()!.validationRule?.formulaExpression || ''\"\r\n usage=\"ValidationRule\"\r\n expectedDataType=\"Boolean\"\r\n commitOn=\"change\"\r\n [rows]=\"2\"\r\n placeholder=\"Espressione, es. LEN(Nome) > 3\"\r\n ariaLabel=\"Regola di validazione\"\r\n (expressionChange)=\"setValidationRule('formulaExpression', $event)\"\r\n />\r\n <input\r\n class=\"fb-input\"\r\n placeholder=\"Messaggio mostrato quando l\u2019espressione e\u2019 falsa\"\r\n [value]=\"selectedField()!.validationRule?.errorMessage || ''\"\r\n (change)=\"setValidationRule('errorMessage', $any($event.target).value)\"\r\n />\r\n <p class=\"fb-field__hint\">\r\n L\u2019espressione la valuta il motore di regole: il backend non la interpreta, la fa\r\n verificare al motore.\r\n </p>\r\n </div>\r\n </fieldset>\r\n }\r\n\r\n <!-- --------------------------------------------------------------- choice -->\r\n @if (selectedType()?.acceptsChoices) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Opzioni</legend>\r\n <p class=\"fb-section__note\">\r\n Accetta <strong>Choice</strong> e <strong>Dynamic choice set</strong>, nell\u2019ordine in cui le\r\n opzioni compaiono. Puntare a una variabile e\u2019 SCREEN_FIELD_CHOICE_UNKNOWN.\r\n </p>\r\n\r\n @if (!selectedField()!.choiceReferences?.length) {\r\n @if (selectedField()!.fieldType === 'ObjectProvided') {\r\n <!-- Le opzioni di una picklist arrivano dallo schema: qui si aggiungono solo se\r\n si vuole sostituirle, e non dichiararne nessuna e' il caso normale. -->\r\n <p class=\"fb-field__hint\">\r\n Se il campo dello schema e\u2019 una picklist, le opzioni arrivano da l\u00EC: dichiararle qui\r\n serve solo a sostituirle.\r\n </p>\r\n } @else {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Nessuna opzione: il campo non ha niente da mostrare (SCREEN_FIELD_CHOICES_MISSING).\r\n </p>\r\n }\r\n }\r\n\r\n <div class=\"fb-list\">\r\n @for (choice of selectedField()!.choiceReferences || []; track $index) {\r\n <div class=\"fb-list__item\">\r\n <div class=\"fb-list__header\">\r\n <span class=\"fb-list__index\">{{ $index + 1 }}</span>\r\n <span class=\"fb-list__title\">{{ choice }}</span>\r\n <span class=\"fb-list__spacer\"></span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--sm\" (click)=\"moveChoice($index, -1)\">\u2191</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--sm\" (click)=\"moveChoice($index, 1)\">\u2193</button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--sm\"\r\n aria-label=\"Rimuovi\"\r\n (click)=\"removeChoice($index)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Aggiungi un\u2019opzione</label>\r\n <fb-name-picker\r\n [options]=\"choiceOptions()\"\r\n label=\"Choice\"\r\n placeholder=\"Scegli una choice o un choice set\"\r\n unknownMessage=\"Questo nome non e\u2019 una choice ne\u2019 un choice set: e\u2019 SCREEN_FIELD_CHOICE_UNKNOWN.\"\r\n unknownSeverity=\"error\"\r\n emptyMessage=\"Il flow non dichiara nessuna choice: creale nel pannello delle risorse.\"\r\n (valueChange)=\"addChoice($event)\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Opzione selezionata all\u2019apertura</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"selectedField()!.defaultSelectedChoiceReference || ''\"\r\n (change)=\"setFieldProperty('defaultSelectedChoiceReference', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 nessuna \u2014</option>\r\n @for (choice of selectedField()!.choiceReferences || []; track $index) {\r\n <option [value]=\"choice\">{{ choice }}</option>\r\n }\r\n </select>\r\n @if (defaultChoiceIsForeign()) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n \u00AB{{ selectedField()!.defaultSelectedChoiceReference }}\u00BB non e\u2019 fra le opzioni elencate qui\r\n sopra.\r\n </p>\r\n }\r\n </div>\r\n </fieldset>\r\n }\r\n\r\n <!-- ------------------------------------------------------- contenitori -->\r\n @if (selectedType()?.isContainer) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Contenitore</legend>\r\n @if (selectedField()!.fieldType === 'RegionContainer') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Tipo di sezione</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"selectedField()!.regionContainerType || ''\"\r\n (change)=\"setFieldProperty('regionContainerType', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 predefinito \u2014</option>\r\n @for (entry of dictionary.regionContainerTypes(); track entry.value) {\r\n <option [value]=\"entry.value\">{{ entry.label }}</option>\r\n }\r\n </select>\r\n </div>\r\n }\r\n @if (!selectedField()!.fields?.length) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Il contenitore e\u2019 vuoto: non produce niente sulla schermata (SCREEN_CONTAINER_EMPTY).\r\n </p>\r\n }\r\n </fieldset>\r\n }\r\n\r\n <!-- --------------------------------------------------- ComponentInstance -->\r\n @if (selectedField()!.fieldType === 'ComponentInstance') {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Componente</legend>\r\n <p class=\"fb-section__note\">\r\n Componenti e form sono la stessa domanda al frontend \u2014 cosa sa rendere, e con quali parametri\r\n \u2014 e passano dallo stesso catalogo. Un <code>ComponentInstance</code> non ha un valore proprio:\r\n lo hanno i suoi output.\r\n </p>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Componente</label>\r\n <fb-name-picker\r\n [value]=\"selectedField()!.extensionName\"\r\n [options]=\"componentOptions()\"\r\n [unusableOptions]=\"unusableComponents()\"\r\n label=\"Componente\"\r\n placeholder=\"Scrivi o scegli un componente\"\r\n [isMono]=\"false\"\r\n unknownMessage=\"Questo componente non esiste nel catalogo: e\u2019 SCREEN_COMPONENT_UNKNOWN.\"\r\n unknownSeverity=\"error\"\r\n unusableMessage=\"Questo nome e\u2019 una schermata intera, non un componente montabile qui (FORM_KIND_MISMATCH).\"\r\n emptyMessage=\"Il catalogo dei componenti non e\u2019 popolato: il nome non viene verificato.\"\r\n (valueChange)=\"setFieldProperty('extensionName', $event ?? '')\"\r\n />\r\n @if (!selectedField()!.extensionName) {\r\n <p class=\"fb-field__error\">Obbligatorio: senza, SCREEN_COMPONENT_MISSING.</p>\r\n }\r\n </div>\r\n\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"selectedField()!.storeOutputAutomatically === true\"\r\n (change)=\"setStoreOutputAutomatically($any($event.target).checked)\"\r\n />\r\n Rendi gli output referenziabili automaticamente\r\n </label>\r\n @if (selectedField()!.storeOutputAutomatically) {\r\n <p class=\"fb-field__hint\">\r\n Gli output si referenziano come\r\n <code>{{ selectedField()!.name || 'Campo' }}.nomeOutput</code>, senza dichiarare variabili.\r\n </p>\r\n }\r\n @if (hasOutputConflict()) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n Output automatici <strong>e</strong> parametri di uscita insieme:\r\n OUTPUT_CONFIGURATION_CONFLICT.\r\n </p>\r\n }\r\n\r\n <fb-parameter-editor\r\n [holder]=\"$any(selectedField())\"\r\n [catalogParameters]=\"componentParameterList()\"\r\n inputTitle=\"Valori passati al componente\"\r\n outputTitle=\"Valori raccolti dal componente\"\r\n [showOutputs]=\"!selectedField()!.storeOutputAutomatically\"\r\n outputsDisabledReason=\"Gli output sono automatici: disattivalo per assegnarli a variabili.\"\r\n (changed)=\"onComponentParametersChanged($event)\"\r\n />\r\n </fieldset>\r\n }\r\n\r\n <!-- ------------------------------------------------------- visibilita' -->\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Visibilita\u2019</legend>\r\n <p class=\"fb-section__note\">\r\n Le regole si rivalutano <strong>sui valori appena inviati</strong>: e\u2019 cos\u00EC che un campo compare\r\n in funzione di un altro campo della stessa schermata. Un campo risultato nascosto viene\r\n <strong>azzerato</strong> e non viene validato.\r\n </p>\r\n <fb-condition-editor\r\n [holder]=\"visibilityRule()\"\r\n title=\"Mostra il campo quando\"\r\n [allowFormula]=\"false\"\r\n [issuePath]=\"'fields[' + (selectedField()!.name || '') + '].visibilityRule'\"\r\n (changed)=\"onVisibilityChanged($event)\"\r\n />\r\n </fieldset>\r\n }\r\n </aside>\r\n</div>\r\n\r\n<fb-connector-editor\r\n [nodeName]=\"name()\"\r\n [node]=\"node()\"\r\n [outlets]=\"outlets()\"\r\n (connectorChanged)=\"onConnectorChanged($event)\"\r\n/>\r\n\r\n<!--\r\n ============================================================== l'anteprima\r\n Due template ricorsivi. `listTpl` disegna i figli di un contenitore, e nient'altro: `data-item`\r\n marca cio' che conta come fratello nel calcolo del punto di rilascio, e il segnaposto del\r\n contenitore vuoto ne resta fuori. La barra di inserimento non e' qui \u2014 sta sopra l'anteprima,\r\n vedi il velo nel frame.\r\n-->\r\n<ng-template #listTpl let-fields let-parent=\"parent\" let-axis=\"axis\">\r\n @for (child of fields; track $index) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"fieldTpl\"\r\n [ngTemplateOutletContext]=\"{ $implicit: child, path: childPath(parent, $index), axis: axis }\"\r\n />\r\n }\r\n @if (!fields.length) {\r\n <p class=\"fb-scr__drop-hint\">Trascina qui un componente</p>\r\n }\r\n</ng-template>\r\n\r\n<ng-template #fieldTpl let-field let-path=\"path\" let-axis=\"axis\">\r\n <div\r\n class=\"fb-scr__item\"\r\n data-item=\"\"\r\n cdkDrag\r\n [style.grid-column]=\"'span ' + widthOf(field)\"\r\n [class.fb-scr__item--selected]=\"isSelected(path)\"\r\n [class.fb-scr__item--dragging]=\"isDragging(path)\"\r\n [class.fb-scr__item--container]=\"isContainer(field)\"\r\n (cdkDragStarted)=\"onFieldDragStarted(path)\"\r\n (cdkDragMoved)=\"onFieldDragMoved($event)\"\r\n (cdkDragEnded)=\"onFieldDragEnded($event, path)\"\r\n (click)=\"select(path); $event.stopPropagation()\"\r\n >\r\n <span class=\"fb-scr__grip\" cdkDragHandle title=\"Trascina per spostare\" aria-hidden=\"true\">\u283F</span>\r\n @if (field.visibilityRule?.conditions?.length) {\r\n <span class=\"fb-scr__flag\" title=\"Ha una regola di visibilita\u2019\">\u25D0</span>\r\n }\r\n\r\n @switch (field.fieldType) {\r\n @case ('RegionContainer') {\r\n <div class=\"fb-scr__section\">\r\n @if (field.regionContainerType !== 'SectionWithoutHeader') {\r\n <header class=\"fb-scr__section-head\">{{ field.fieldText || field.name }}</header>\r\n }\r\n <div class=\"fb-scr__cols\" [attr.data-drop]=\"dropId(path)\" data-axis=\"row\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"listTpl\"\r\n [ngTemplateOutletContext]=\"{ $implicit: field.fields || [], parent: path, axis: 'row' }\"\r\n />\r\n </div>\r\n </div>\r\n }\r\n @case ('Region') {\r\n <div class=\"fb-scr__region\">\r\n <span class=\"fb-scr__region-tag\">{{ field.name }} \u00B7 {{ widthOf(field) }}/12</span>\r\n <div class=\"fb-scr__region-body\" [attr.data-drop]=\"dropId(path)\" data-axis=\"column\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"listTpl\"\r\n [ngTemplateOutletContext]=\"{ $implicit: field.fields || [], parent: path, axis: 'column' }\"\r\n />\r\n </div>\r\n </div>\r\n }\r\n @case ('DisplayText') {\r\n <p class=\"fb-scr__display\">{{ field.fieldText || '(testo vuoto)' }}</p>\r\n }\r\n @case ('LargeTextArea') {\r\n <label class=\"fb-scr__label\">\r\n {{ captionOf(field) }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__control fb-scr__control--area\">{{ placeholderOf(field) }}</div>\r\n }\r\n @case ('PasswordField') {\r\n <label class=\"fb-scr__label\">\r\n {{ captionOf(field) }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__control\">\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022</div>\r\n }\r\n @case ('DropdownBox') {\r\n <label class=\"fb-scr__label\">\r\n {{ captionOf(field) }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__control fb-scr__control--select\">\r\n <span>{{ field.defaultSelectedChoiceReference || choiceLabelsOf(field)[0] || '\u2014 scegli \u2014' }}</span>\r\n <span aria-hidden=\"true\">\u25BE</span>\r\n </div>\r\n }\r\n @case ('MultiSelectPicklist') {\r\n <label class=\"fb-scr__label\">\r\n {{ captionOf(field) }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__control fb-scr__control--select\">\r\n <span>{{ choiceLabelsOf(field).join(', ') || '\u2014 scegli \u2014' }}</span>\r\n <span aria-hidden=\"true\">\u2261</span>\r\n </div>\r\n }\r\n @case ('RadioButtons') {\r\n <label class=\"fb-scr__label\">\r\n {{ captionOf(field) }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__options\">\r\n @for (option of choiceLabelsOf(field); track $index) {\r\n <span class=\"fb-scr__option\">\u25EF {{ option }}</span>\r\n }\r\n @if (!choiceLabelsOf(field).length) {\r\n <span class=\"fb-scr__option fb-scr__option--missing\">Nessuna opzione</span>\r\n }\r\n </div>\r\n }\r\n @case ('MultiSelectCheckboxes') {\r\n <label class=\"fb-scr__label\">\r\n {{ captionOf(field) }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__options\">\r\n @for (option of choiceLabelsOf(field); track $index) {\r\n <span class=\"fb-scr__option\">\u2610 {{ option }}</span>\r\n }\r\n @if (!choiceLabelsOf(field).length) {\r\n <span class=\"fb-scr__option fb-scr__option--missing\">Nessuna opzione</span>\r\n }\r\n </div>\r\n }\r\n @case ('ComponentInstance') {\r\n <div class=\"fb-scr__component\">\r\n <span aria-hidden=\"true\">\u2B21</span>\r\n {{ field.extensionName || 'Componente non indicato' }}\r\n </div>\r\n }\r\n @case ('ObjectProvided') {\r\n <label class=\"fb-scr__label\">\r\n {{ field.fieldText || field.objectFieldReference || field.name }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__control\">{{ placeholderOf(field) }}</div>\r\n <span class=\"fb-scr__tag\">{{ field.objectFieldReference || 'campo non indicato' }}</span>\r\n }\r\n @default {\r\n <label class=\"fb-scr__label\">\r\n {{ captionOf(field) }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__control\">{{ placeholderOf(field) }}</div>\r\n @if (isUnknownType(field)) {\r\n <span class=\"fb-scr__tag fb-scr__tag--warn\">{{ field.fieldType }}: tipo non nel dizionario</span>\r\n }\r\n }\r\n }\r\n </div>\r\n</ng-template>\r\n", styles: [".fb-scr{display:grid;grid-template-columns:220px minmax(0,1fr) 340px;gap:12px;align-items:start;margin-bottom:14px}@media(max-width:1100px){.fb-scr{grid-template-columns:minmax(0,1fr)}}.fb-scr__side,.fb-scr__props{min-width:0;padding:10px;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius, 10px);background:var(--fb-surface-alt, #f7f8fa)}.fb-scr__tabs{display:flex;gap:4px;margin-bottom:8px}.fb-scr__tab{flex:1;padding:5px 8px;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-xs, 6px);background:var(--fb-surface, #fff);color:var(--fb-text-muted, #6b7086);font:inherit;font-size:11px;font-weight:600;cursor:pointer}.fb-scr__tab--active{border-color:var(--fb-accent, #4f6ef7);background:color-mix(in srgb,var(--fb-accent, #4f6ef7) 10%,transparent);color:var(--fb-accent-strong, #3d59e0)}.fb-scr__side-hint,.fb-scr__side-empty{margin:6px 0;font-size:11px;color:var(--fb-text-muted, #6b7086)}.fb-scr__palette{display:flex;flex-direction:column;gap:4px;margin:8px 0 0;padding:0;max-height:320px;overflow-y:auto;overscroll-behavior:contain;list-style:none}.fb-scr__chip{display:flex;align-items:center;gap:8px;width:100%;padding:6px 8px;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-xs, 6px);background:var(--fb-surface, #fff);color:var(--fb-text, #1a1c23);font:inherit;font-size:12px;text-align:left;cursor:grab}.fb-scr__chip:hover{border-color:var(--fb-accent, #4f6ef7)}.fb-scr__chip-icon{flex:none;width:20px;text-align:center;color:var(--fb-text-muted, #6b7086)}.fb-scr__chip-label{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-scr__chip-label small{margin-left:4px;color:var(--fb-text-subtle, #98a2b3);font-size:10px}.fb-scr__canvas{min-width:0;padding:14px;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius, 10px);background:var(--fb-canvas-bg, #f4f5f7)}.fb-scr__frame{position:relative;display:flex;flex-direction:column;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-sm, 8px);background:var(--fb-surface, #fff);box-shadow:var(--fb-shadow-sm, 0 1px 2px rgb(16 24 40 / 6%));overflow:hidden}.fb-scr__frame-head{display:flex;align-items:center;gap:6px;padding:8px 12px;border-bottom:1px solid var(--fb-border-subtle, #eef0f4);font-size:13px;font-weight:600}.fb-scr__help{display:inline-flex;align-items:center;justify-content:center;width:15px;height:15px;border-radius:50%;background:var(--fb-surface-sunken, #eef0f4);color:var(--fb-text-muted, #6b7086);font-size:10px}.fb-scr__body,.fb-scr__region-body{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));align-content:start;gap:8px 0;padding:12px;min-height:90px}.fb-scr__region-body{padding:8px;min-height:60px}.fb-scr__cols{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));align-items:stretch;gap:0;padding:8px;min-height:60px}.fb-scr__cols>.fb-scr__item{display:flex;align-self:stretch}.fb-scr__cols>.fb-scr__item>.fb-scr__region{flex:1;min-width:0}.fb-scr__drop-hint{grid-column:1 / -1;margin:0;padding:10px;border:1px dashed var(--fb-border-strong, #cfd4de);border-radius:var(--fb-radius-xs, 6px);color:var(--fb-text-subtle, #98a2b3);font-size:11px;text-align:center}.fb-scr__marker{position:absolute;z-index:2;border-radius:2px;background:var(--fb-accent, #4f6ef7);pointer-events:none}.fb-scr__item{position:relative;box-sizing:border-box;padding:6px 8px;border:1px solid transparent;border-radius:var(--fb-radius-xs, 6px);cursor:pointer}.fb-scr__item:hover{border-color:var(--fb-border-strong, #cfd4de);background:color-mix(in srgb,var(--fb-accent, #4f6ef7) 4%,transparent)}.fb-scr__item--selected{border-color:var(--fb-accent, #4f6ef7);box-shadow:inset 0 0 0 1px var(--fb-accent, #4f6ef7)}.fb-scr__item--dragging{opacity:.45;pointer-events:none}.fb-scr__grip{position:absolute;top:2px;left:-2px;padding:0 3px;color:var(--fb-text-subtle, #98a2b3);font-size:11px;line-height:1;opacity:0;cursor:grab}.fb-scr__item:hover>.fb-scr__grip,.fb-scr__item--selected>.fb-scr__grip{opacity:1}.fb-scr__flag{position:absolute;top:2px;right:4px;color:var(--fb-text-muted, #6b7086);font-size:11px}.fb-scr__label{display:block;margin-bottom:3px;font-size:11px;font-weight:600;color:var(--fb-text, #1a1c23)}.fb-scr__req{color:var(--fb-error, #dc2626)}.fb-scr__control{display:flex;align-items:center;justify-content:space-between;gap:6px;min-height:26px;padding:4px 8px;border:1px solid var(--fb-border-strong, #cfd4de);border-radius:var(--fb-radius-xs, 6px);background:var(--fb-surface, #fff);color:var(--fb-text-subtle, #98a2b3);font-size:12px;pointer-events:none}.fb-scr__control--area{min-height:54px;align-items:flex-start}.fb-scr__display{margin:0;font-size:12px;color:var(--fb-text, #1a1c23)}.fb-scr__options{display:flex;flex-direction:column;gap:2px;pointer-events:none}.fb-scr__option{font-size:12px;color:var(--fb-text-muted, #6b7086)}.fb-scr__option--missing{color:var(--fb-warning, #b7791f)}.fb-scr__component{display:flex;align-items:center;gap:6px;padding:10px;border:1px dashed var(--fb-accent, #4f6ef7);border-radius:var(--fb-radius-xs, 6px);background:color-mix(in srgb,var(--fb-accent, #4f6ef7) 5%,transparent);font-size:12px;color:var(--fb-text-muted, #6b7086)}.fb-scr__tag{display:inline-block;margin-top:3px;font-size:10px;color:var(--fb-text-subtle, #98a2b3)}.fb-scr__tag--warn{color:var(--fb-warning, #b7791f)}.fb-scr__section{border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-xs, 6px);background:var(--fb-surface-alt, #f7f8fa)}.fb-scr__section-head{padding:5px 10px;border-bottom:1px solid var(--fb-border-subtle, #eef0f4);font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--fb-text-muted, #6b7086)}.fb-scr__region{border:1px dashed var(--fb-border-strong, #cfd4de);border-radius:var(--fb-radius-xs, 6px);background:var(--fb-surface, #fff)}.fb-scr__region-tag{display:block;padding:3px 6px 0;font-size:10px;color:var(--fb-text-subtle, #98a2b3)}.fb-scr__frame-foot{display:flex;align-items:center;gap:6px;padding:8px 12px;border-top:1px solid var(--fb-border-subtle, #eef0f4);background:var(--fb-surface-alt, #f7f8fa)}.fb-scr__spacer{flex:1}.fb-scr__btn{padding:3px 10px;border:1px solid var(--fb-border-strong, #cfd4de);border-radius:var(--fb-radius-xs, 6px);font-size:11px;color:var(--fb-text-muted, #6b7086)}.fb-scr__btn--primary{border-color:var(--fb-accent, #4f6ef7);background:var(--fb-accent, #4f6ef7);color:var(--fb-accent-contrast, #fff)}.fb-scr__canvas-note{margin-top:10px}.fb-scr__props{background:var(--fb-surface, #fff)}.fb-scr__props-head{display:flex;align-items:center;gap:8px;margin-bottom:8px}.fb-scr__props-title{flex:1;min-width:0;margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--fb-text-muted, #6b7086)}.fb-scr__props-actions{display:flex;flex-wrap:wrap;gap:3px}.fb-btn--sm{padding:3px 7px;font-size:11px}.fb-scr__width{display:flex;align-items:center;gap:8px}.fb-scr__width input[type=range]{flex:1;min-width:0}.fb-scr__width-value{font-size:11px;color:var(--fb-text-muted, #6b7086)}\n"], dependencies: [{ kind: "directive", type: CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "component", type: ConditionEditorComponent, selector: "fb-condition-editor", inputs: ["holder", "title", "allowFormula", "allowLogic", "issuePath"], outputs: ["changed"] }, { kind: "component", type: ConnectorEditorComponent, selector: "fb-connector-editor", inputs: ["nodeName", "node", "outlets", "title"], outputs: ["connectorChanged"] }, { kind: "component", type: FieldPickerComponent, selector: "fb-field-picker", inputs: ["value", "object", "usage", "label", "placeholder", "disabled"], outputs: ["valueChange"] }, { kind: "component", type: FormulaEditorComponent, selector: "fb-formula-editor", inputs: ["expression", "usage", "expectedDataType", "scale", "placeholder", "ariaLabel", "disabled", "rows", "commitOn"], outputs: ["expressionChange"] }, { kind: "component", type: NamePickerComponent, selector: "fb-name-picker", inputs: ["value", "options", "label", "placeholder", "disabled", "unknownMessage", "unknownSeverity", "unusableOptions", "unusableMessage", "emptyMessage", "isMono"], outputs: ["valueChange"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: ObjectPickerComponent, selector: "fb-object-picker", inputs: ["value", "label", "placeholder", "disabled"], outputs: ["valueChange"] }, { kind: "component", type: ParameterEditorComponent, selector: "fb-parameter-editor", inputs: ["holder", "catalogParameters", "inputTitle", "outputTitle", "showInputs", "showOutputs", "outputsDisabledReason", "extraTargets"], outputs: ["changed"] }, { kind: "directive", type: SelectValueDirective, selector: "select[fbValue]", inputs: ["fbValue"] }, { kind: "component", type: StructurePickerComponent, selector: "fb-structure-picker", inputs: ["value", "label", "placeholder", "disabled"], outputs: ["valueChange"] }, { kind: "component", type: ValueEditorComponent, selector: "fb-value-editor", inputs: ["value", "label", "dataType", "objectType", "isCollection", "disabled", "allowFormula"], outputs: ["valueChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
10078
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.27", type: DynamicScreenInspectorComponent, isStandalone: true, selector: "fb-dynamic-screen-inspector", usesInheritance: true, ngImport: i0, template: "<div class=\"fb-scr\">\r\n <!-- ============================================================ sinistra -->\r\n <aside class=\"fb-scr__side\" aria-label=\"Componenti e campi\">\r\n <div class=\"fb-scr__tabs\" role=\"tablist\">\r\n <button\r\n type=\"button\"\r\n role=\"tab\"\r\n class=\"fb-scr__tab\"\r\n [class.fb-scr__tab--active]=\"paletteTab() === 'components'\"\r\n [attr.aria-selected]=\"paletteTab() === 'components'\"\r\n (click)=\"setPaletteTab('components')\"\r\n >\r\n Componenti\r\n </button>\r\n <button\r\n type=\"button\"\r\n role=\"tab\"\r\n class=\"fb-scr__tab\"\r\n [class.fb-scr__tab--active]=\"paletteTab() === 'fields'\"\r\n [attr.aria-selected]=\"paletteTab() === 'fields'\"\r\n (click)=\"setPaletteTab('fields')\"\r\n >\r\n Campi\r\n </button>\r\n </div>\r\n\r\n @if (paletteTab() === 'components') {\r\n @if (!dictionary.screenFieldTypes().length) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Il dizionario <code>screenFieldTypes</code> non e\u2019 disponibile: senza i suoi flag l\u2019editor non\r\n sa quali campi ammette ciascun tipo, e non li inventa.\r\n </p>\r\n }\r\n <p class=\"fb-scr__side-hint\">Trascina sulla schermata, oppure clicca per aggiungere.</p>\r\n <ul class=\"fb-scr__palette\">\r\n @for (entry of dictionary.screenFieldTypes(); track entry.value) {\r\n <li>\r\n <button\r\n type=\"button\"\r\n class=\"fb-scr__chip\"\r\n cdkDrag\r\n [title]=\"entry.description || entry.label\"\r\n (cdkDragMoved)=\"onPaletteDragMoved($event)\"\r\n (cdkDragEnded)=\"onComponentDragEnded($event, entry.value)\"\r\n (click)=\"addComponentAtSelection(entry.value)\"\r\n >\r\n <span class=\"fb-scr__chip-icon\" aria-hidden=\"true\">{{ iconOf(entry.value) }}</span>\r\n <span class=\"fb-scr__chip-label\">{{ entry.label }}</span>\r\n </button>\r\n </li>\r\n }\r\n </ul>\r\n } @else {\r\n <p class=\"fb-scr__side-hint\">\r\n Un campo dell\u2019entita\u2019 porta con se\u2019 tipo, etichetta, obbligatorieta\u2019 e opzioni: arrivano dallo\r\n schema dati, qui non si ridichiarano.\r\n </p>\r\n <!-- Il catalogo non e' un dizionario chiuso: si sceglie o si scrive, come ovunque si\r\n indichi un'entita' (vedi `object-picker`). -->\r\n <fb-object-picker\r\n [value]=\"fieldsObject()\"\r\n label=\"Entita\u2019\"\r\n placeholder=\"Scrivi o scegli un\u2019entita\u2019\"\r\n (valueChange)=\"setFieldsObject($event)\"\r\n />\r\n @if (fieldsObject() && !fieldsOfObject().length) {\r\n <p class=\"fb-scr__side-empty\">\r\n Nessun campo dichiarato per questa entita\u2019: il catalogo non li espone, non significa che non\r\n ci siano.\r\n </p>\r\n }\r\n <ul class=\"fb-scr__palette\">\r\n @for (field of fieldsOfObject(); track $index) {\r\n <li>\r\n <button\r\n type=\"button\"\r\n class=\"fb-scr__chip\"\r\n cdkDrag\r\n [title]=\"field.name + (field.dataType ? ' \u00B7 ' + field.dataType : '')\"\r\n (cdkDragMoved)=\"onPaletteDragMoved($event)\"\r\n (cdkDragEnded)=\"onObjectFieldDragEnded($event, field)\"\r\n (click)=\"addObjectFieldAtSelection(field)\"\r\n >\r\n <span class=\"fb-scr__chip-icon\" aria-hidden=\"true\">\u2317</span>\r\n <span class=\"fb-scr__chip-label\">\r\n {{ field.label || field.name }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n <small>{{ field.dataType }}</small>\r\n </span>\r\n </button>\r\n </li>\r\n }\r\n </ul>\r\n }\r\n </aside>\r\n\r\n <!-- ============================================================== centro -->\r\n <!-- Cliccare fuori da un campo riporta a destra le proprieta' della schermata. -->\r\n <section class=\"fb-scr__canvas\" aria-label=\"Anteprima della schermata\" (click)=\"selectScreen()\">\r\n <div class=\"fb-scr__frame\">\r\n @if (showHeader()) {\r\n <header class=\"fb-scr__frame-head\">\r\n {{ $any(node()).label || name() }}\r\n @if (screen().helpText) {\r\n <span class=\"fb-scr__help\" [title]=\"screen().helpText || ''\">?</span>\r\n }\r\n </header>\r\n }\r\n\r\n <div\r\n class=\"fb-scr__body\"\r\n [attr.data-drop]=\"''\"\r\n data-axis=\"column\"\r\n [class.fb-scr__body--empty]=\"isEmpty()\"\r\n >\r\n <ng-container\r\n [ngTemplateOutlet]=\"listTpl\"\r\n [ngTemplateOutletContext]=\"{ $implicit: screen().fields || [], parent: [], axis: 'column' }\"\r\n />\r\n </div>\r\n\r\n <!--\r\n La barra di inserimento e' un **velo** sopra l'anteprima, non un figlio del contenitore di\r\n rilascio: dentro la griglia occupava una riga intera e spostava in basso proprio la sezione\r\n che si stava puntando, che allora usciva da sotto il puntatore e il bersaglio oscillava.\r\n -->\r\n @if (dropMarker(); as bar) {\r\n <div\r\n class=\"fb-scr__marker\"\r\n [style.left.px]=\"bar.left\"\r\n [style.top.px]=\"bar.top\"\r\n [style.width.px]=\"bar.width\"\r\n [style.height.px]=\"bar.height\"\r\n ></div>\r\n }\r\n\r\n @if (showFooter()) {\r\n <footer class=\"fb-scr__frame-foot\">\r\n @if (allowPause()) {\r\n <span class=\"fb-scr__btn fb-scr__btn--ghost\">{{ screen().pauseButtonLabel || 'Pausa' }}</span>\r\n }\r\n <span class=\"fb-scr__spacer\"></span>\r\n @if (allowBack()) {\r\n <span class=\"fb-scr__btn fb-scr__btn--ghost\">{{ screen().backButtonLabel || 'Indietro' }}</span>\r\n }\r\n <span class=\"fb-scr__btn fb-scr__btn--primary\">\r\n {{ screen().nextOrFinishButtonLabel || (allowFinish() ? 'Fine' : 'Avanti') }}\r\n </span>\r\n </footer>\r\n }\r\n </div>\r\n\r\n @if (isEmpty()) {\r\n <p class=\"fb-callout fb-callout--warn fb-scr__canvas-note\">\r\n La schermata non ha campi: non c\u2019e\u2019 niente da mostrare all\u2019utente (SCREEN_WITHOUT_FIELDS).\r\n </p>\r\n }\r\n </section>\r\n\r\n <!-- ============================================================== destra -->\r\n <aside class=\"fb-scr__props\" aria-label=\"Proprieta\u2019\">\r\n @if (!selectedField()) {\r\n <!-- ------------------------------------------------ proprieta' della schermata -->\r\n <header class=\"fb-scr__props-head\">\r\n <h3 class=\"fb-scr__props-title\">Schermata</h3>\r\n </header>\r\n <p class=\"fb-scr__side-hint\">Seleziona un campo nell\u2019anteprima per configurarlo.</p>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Testo di aiuto</label>\r\n <textarea\r\n class=\"fb-textarea\"\r\n [value]=\"screen().helpText || ''\"\r\n (input)=\"setScreenText('helpText', $any($event.target).value)\"\r\n ></textarea>\r\n </div>\r\n\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Navigazione</legend>\r\n <p class=\"fb-section__note\">\r\n Questi flag sono un\u2019intenzione, non la verita\u2019 finale: a runtime il motore comunica\r\n <code>canGoBack</code>, <code>canFinish</code> e <code>canPause</code> nella richiesta della\r\n schermata.\r\n </p>\r\n <label class=\"fb-check\">\r\n <input type=\"checkbox\" [checked]=\"allowBack()\" (change)=\"setScreenFlag('allowBack', $any($event.target).checked)\" />\r\n Consenti \u00ABindietro\u00BB\r\n </label>\r\n <label class=\"fb-check\">\r\n <input type=\"checkbox\" [checked]=\"allowFinish()\" (change)=\"setScreenFlag('allowFinish', $any($event.target).checked)\" />\r\n Consenti \u00ABfine\u00BB\r\n </label>\r\n <label class=\"fb-check\">\r\n <input type=\"checkbox\" [checked]=\"allowPause()\" (change)=\"setScreenFlag('allowPause', $any($event.target).checked)\" />\r\n Consenti \u00ABpausa\u00BB\r\n </label>\r\n <label class=\"fb-check\">\r\n <input type=\"checkbox\" [checked]=\"showHeader()\" (change)=\"setScreenFlag('showHeader', $any($event.target).checked)\" />\r\n Mostra l\u2019intestazione\r\n </label>\r\n <label class=\"fb-check\">\r\n <input type=\"checkbox\" [checked]=\"showFooter()\" (change)=\"setScreenFlag('showFooter', $any($event.target).checked)\" />\r\n Mostra il piede\r\n </label>\r\n\r\n @if (allowPause()) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Testo mostrato alla pausa</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"screen().pausedText || ''\"\r\n (input)=\"setScreenText('pausedText', $any($event.target).value)\"\r\n />\r\n </div>\r\n }\r\n\r\n @if (isDeadEnd()) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n Questa schermata non ha una destinazione e non consente \u00ABfine\u00BB: e\u2019 un vicolo cieco, e\r\n l\u2019utente resterebbe bloccato (SCREEN_DEAD_END).\r\n </p>\r\n }\r\n </fieldset>\r\n\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Pulsanti</legend>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Etichetta \u00ABindietro\u00BB</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"screen().backButtonLabel || ''\"\r\n (input)=\"setScreenText('backButtonLabel', $any($event.target).value)\"\r\n />\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Etichetta \u00ABavanti / fine\u00BB</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"screen().nextOrFinishButtonLabel || ''\"\r\n (input)=\"setScreenText('nextOrFinishButtonLabel', $any($event.target).value)\"\r\n />\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Etichetta \u00ABpausa\u00BB</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"screen().pauseButtonLabel || ''\"\r\n (input)=\"setScreenText('pauseButtonLabel', $any($event.target).value)\"\r\n />\r\n </div>\r\n </fieldset>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Stage mostrato nell\u2019avanzamento</label>\r\n <fb-name-picker\r\n [value]=\"screen().stageReference\"\r\n [options]=\"stageOptions()\"\r\n label=\"Stage\"\r\n placeholder=\"Scegli uno stage\"\r\n unknownMessage=\"Questo stage non e\u2019 dichiarato dal flow.\"\r\n emptyMessage=\"Il flow non dichiara stage: creali nel pannello delle risorse.\"\r\n (valueChange)=\"setScreenText('stageReference', $event ?? '')\"\r\n />\r\n </div>\r\n\r\n <!--\r\n Screen action (\u00A75.2). Due elenchi e non uno perche' il contratto li separa: piu' trigger\r\n possono invocare la stessa action, quindi \u00ABchi la chiama\u00BB non e' una sua proprieta'. Le\r\n action si aprono una alla volta: i cataloghi dipendono da tipo e nome, e tenerne N in volo\r\n sarebbe N volte la corsa fra risposte.\r\n -->\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Azioni della schermata</legend>\r\n <p class=\"fb-section__note\">\r\n Un campo che l\u2019utente compila puo\u2019 innescare un\u2019action i cui risultati finiscono negli\r\n <strong>altri campi di questa schermata</strong>: e\u2019 il caso \u00ABscrivi il codice fiscale e nome\r\n e cognome compaiono da soli\u00BB. L\u2019alternativa sarebbe spezzare la schermata in due con un\r\n elemento Action in mezzo.\r\n </p>\r\n\r\n <div class=\"fb-list\">\r\n @for (action of screenActions(); track $index) {\r\n <div class=\"fb-list__item\">\r\n <div class=\"fb-list__header\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost\"\r\n [attr.aria-expanded]=\"selectedActionIndex() === $index\"\r\n (click)=\"selectAction(selectedActionIndex() === $index ? null : $index)\"\r\n >\r\n {{ selectedActionIndex() === $index ? '\u25BE' : '\u25B8' }}\r\n {{ action.name || '(senza nome)' }}\r\n </button>\r\n <span class=\"fb-list__spacer\"></span>\r\n <span class=\"fb-scr__side-hint\">{{ action.actionName || 'action non scelta' }}</span>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n aria-label=\"Rimuovi l\u2019action\"\r\n (click)=\"removeScreenAction($index)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n\r\n @if (actionLosesResult(action)) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Questa action viene invocata ma il suo risultato si perde: aggiungi un parametro di\r\n uscita, oppure accendi l\u2019output automatico (SCREEN_ACTION_WITHOUT_OUTPUTS).\r\n </p>\r\n }\r\n @if (actionHasNoTrigger(action)) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Nessun trigger la invoca: senza, non girera\u2019 mai.\r\n </p>\r\n }\r\n\r\n @if (selectedActionIndex() === $index) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Nome</label>\r\n <input\r\n class=\"fb-input fb-input--mono\"\r\n [value]=\"action.name || ''\"\r\n (change)=\"setActionName($index, $any($event.target).value)\"\r\n />\r\n @if (actionNameError(action); as error) {\r\n <p class=\"fb-field__error\">{{ error }}</p>\r\n }\r\n <p class=\"fb-field__hint\">\r\n E\u2019 il nome con cui la citano i trigger, e vive nello spazio dei nomi del flow. Con\r\n l\u2019output automatico e\u2019 anche la radice del riferimento:\r\n <code>{{ action.name || 'Cerca' }}.NomeOutput</code>.\r\n </p>\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Tipo di action</label>\r\n <fb-name-picker\r\n [value]=\"action.actionType\"\r\n [options]=\"actionTypeOptions()\"\r\n label=\"Tipo di action\"\r\n placeholder=\"Scrivi o scegli un tipo\"\r\n [isMono]=\"false\"\r\n unknownMessage=\"Questo tipo di action non e\u2019 fra quelli dichiarati dal sistema ospite.\"\r\n emptyMessage=\"Catalogo dei tipi di action non disponibile: puoi scrivere il nome a mano.\"\r\n (valueChange)=\"setActionType($index, $event ?? '')\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Action</label>\r\n <fb-name-picker\r\n [value]=\"action.actionName\"\r\n [options]=\"actionOptions()\"\r\n label=\"Action\"\r\n placeholder=\"Scrivi o scegli un\u2019action\"\r\n [isMono]=\"false\"\r\n unknownMessage=\"Questa action non esiste nel catalogo del tipo scelto: e\u2019 ACTION_UNKNOWN.\"\r\n unknownSeverity=\"error\"\r\n emptyMessage=\"Scegli prima il tipo di action, oppure scrivi il nome a mano.\"\r\n (valueChange)=\"setActionTarget($index, $event ?? '')\"\r\n />\r\n @if (!action.actionName) {\r\n <p class=\"fb-field__error\">Obbligatoria: senza, ACTION_NAME_MISSING.</p>\r\n }\r\n </div>\r\n\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"action.storeOutputAutomatically === true\"\r\n (change)=\"setActionStoreOutput($index, $any($event.target).checked)\"\r\n />\r\n Output automatico\r\n </label>\r\n <p class=\"fb-field__hint\">\r\n L\u2019output automatico rende il risultato leggibile come\r\n <code>{{ action.name || 'Cerca' }}.NomeOutput</code>, ma\r\n <strong>non scrive in nessun campo</strong>: e\u2019 la forma da usare quando il risultato\r\n serve a una condizione, non a precompilare.\r\n </p>\r\n\r\n <!--\r\n L'unico posto in cui un campo di schermata e' una destinazione (\u00A75.2):\r\n `POST /flows/references/writable` continua a escluderlo, e un Assignment che ci\r\n scrive resta TARGET_NOT_WRITABLE.\r\n -->\r\n <fb-parameter-editor\r\n [holder]=\"action\"\r\n [catalogParameters]=\"actionParameterCatalog()\"\r\n [showOutputs]=\"action.storeOutputAutomatically !== true\"\r\n outputTitle=\"Campi da riempire\"\r\n [extraTargets]=\"screenFieldTargets()\"\r\n [outputsDisabledReason]=\"\r\n action.storeOutputAutomatically === true\r\n ? 'Con l\u2019output automatico il risultato non viene scritto nei campi.'\r\n : null\r\n \"\r\n (changed)=\"onActionParametersChanged($index, $event)\"\r\n />\r\n }\r\n </div>\r\n } @empty {\r\n <p class=\"fb-empty\">Nessuna action: i campi di questa schermata li compila solo l\u2019utente.</p>\r\n }\r\n </div>\r\n\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"addScreenAction()\">Aggiungi action</button>\r\n </fieldset>\r\n\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Trigger</legend>\r\n <p class=\"fb-section__note\">\r\n Quale campo invoca quale action. Senza condizioni l\u2019action gira a ogni cambio del campo.\r\n </p>\r\n\r\n <div class=\"fb-list\">\r\n @for (trigger of screenTriggers(); track $index) {\r\n <div class=\"fb-list__item\">\r\n <div class=\"fb-list__header\">\r\n <span class=\"fb-list__index\">{{ $index + 1 }}</span>\r\n <span class=\"fb-list__spacer\"></span>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n aria-label=\"Rimuovi il trigger\"\r\n (click)=\"removeScreenTrigger($index)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Action da invocare</label>\r\n <fb-name-picker\r\n [value]=\"trigger.screenActionName\"\r\n [options]=\"screenActionOptions()\"\r\n label=\"Action della schermata\"\r\n placeholder=\"Scegli un\u2019action\"\r\n unknownMessage=\"Questa schermata non dichiara un\u2019action con questo nome: e\u2019 SCREEN_ACTION_UNKNOWN.\"\r\n unknownSeverity=\"error\"\r\n emptyMessage=\"Aggiungi prima un\u2019action qui sopra.\"\r\n (valueChange)=\"setTriggerProperty($index, 'screenActionName', $event ?? '')\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Campo che la innesca</label>\r\n <!-- I campi di **questa** schermata, non i riferimenti in generale (\u00A75.2). -->\r\n <fb-name-picker\r\n [value]=\"trigger.triggerFieldName\"\r\n [options]=\"triggerFieldOptions()\"\r\n label=\"Campo della schermata\"\r\n placeholder=\"Scegli un campo\"\r\n unknownMessage=\"Questo non e\u2019 un campo di questa schermata: e\u2019 SCREEN_TRIGGER_FIELD_UNKNOWN.\"\r\n unknownSeverity=\"error\"\r\n emptyMessage=\"La schermata non ha ancora campi che raccolgono un valore.\"\r\n (valueChange)=\"setTriggerProperty($index, 'triggerFieldName', $event ?? '')\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">All\u2019arrivo sulla schermata</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"trigger.initBehavior || ''\"\r\n (change)=\"setTriggerProperty($index, 'initBehavior', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 non invocare \u2014</option>\r\n <option value=\"runOnLoad\">Invoca al caricamento (runOnLoad)</option>\r\n </select>\r\n @if (triggerInitIsUnknown(trigger)) {\r\n <p class=\"fb-field__error\">\r\n \u00AB{{ trigger.initBehavior }}\u00BB non e\u2019 ammesso: l\u2019unico valore e\u2019 <code>runOnLoad</code>\r\n (SCREEN_TRIGGER_INIT_BEHAVIOR_UNKNOWN).\r\n </p>\r\n }\r\n </div>\r\n\r\n @if (triggerHasNoCause(trigger)) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Senza un campo che la innesca e senza l\u2019invocazione al caricamento, questo trigger non\r\n scattera\u2019 mai (SCREEN_TRIGGER_WITHOUT_CAUSE).\r\n </p>\r\n }\r\n\r\n <fb-condition-editor\r\n [holder]=\"triggerConditions(trigger)\"\r\n title=\"Invoca l\u2019action quando\"\r\n [allowFormula]=\"false\"\r\n [issuePath]=\"'triggers[' + $index + ']'\"\r\n (changed)=\"onTriggerConditionsChanged($index, $event)\"\r\n />\r\n </div>\r\n } @empty {\r\n <p class=\"fb-empty\">Nessun trigger: le action dichiarate non verranno invocate.</p>\r\n }\r\n </div>\r\n\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"addScreenTrigger()\">Aggiungi trigger</button>\r\n </fieldset>\r\n } @else {\r\n <!-- ---------------------------------------------------- proprieta' del campo -->\r\n <header class=\"fb-scr__props-head\">\r\n <h3 class=\"fb-scr__props-title\">{{ captionOf(selectedField()!) }}</h3>\r\n <div class=\"fb-scr__props-actions\">\r\n <button type=\"button\" class=\"fb-btn fb-btn--sm\" title=\"Sposta su\" (click)=\"moveSelected(-1)\">\u2191</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--sm\" title=\"Sposta giu\u2019\" (click)=\"moveSelected(1)\">\u2193</button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--sm\"\r\n title=\"Porta fuori dal contenitore\"\r\n [disabled]=\"!canOutdent()\"\r\n (click)=\"outdentSelected()\"\r\n >\r\n \u21E4\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--sm\" title=\"Duplica\" (click)=\"duplicateSelected()\">\u29C9</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--danger fb-btn--sm\" title=\"Elimina\" (click)=\"removeSelected()\">\r\n \u00D7\r\n </button>\r\n </div>\r\n </header>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Tipo</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"selectedField()!.fieldType || ''\"\r\n (change)=\"setFieldType($any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 scegli \u2014</option>\r\n @for (entry of dictionary.screenFieldTypes(); track entry.value) {\r\n <option [value]=\"entry.value\">{{ entry.label }}</option>\r\n }\r\n </select>\r\n @if (!selectedField()!.fieldType) {\r\n <p class=\"fb-field__error\">Il tipo e\u2019 obbligatorio: senza, SCREEN_FIELD_TYPE_MISSING.</p>\r\n } @else if (isUnknownType(selectedField())) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n Questo tipo non e\u2019 nel dizionario: l\u2019editor non sa quali campi ammetta e mostra solo i comuni.\r\n </p>\r\n } @else if (selectedType()?.description) {\r\n <p class=\"fb-field__hint\">{{ selectedType()?.description }}</p>\r\n }\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Nome</label>\r\n <!-- Sul `change` e non sull\u2019`input`: la rinomina riscrive i riferimenti nel documento,\r\n e farlo a ogni tasto significherebbe riscriverlo per ogni lettera. -->\r\n <input\r\n class=\"fb-input fb-input--mono\"\r\n [value]=\"selectedField()!.name || ''\"\r\n (change)=\"setFieldName($any($event.target).value)\"\r\n />\r\n @if (selectedIsResource()) {\r\n <p class=\"fb-field__hint\">\r\n Questo campo e\u2019 una <strong>risorsa</strong>: lo referenzi come\r\n <code>{{ selectedField()!.name || 'Nome' }}</code> in condizioni, formule e parametri. \u00C8 di\r\n sola lettura per il flow \u2014 un Assignment che ci scrive e\u2019 TARGET_NOT_WRITABLE.\r\n </p>\r\n }\r\n </div>\r\n\r\n @if (!selectedType()?.isContainer) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">\r\n {{ selectedField()!.fieldType === 'DisplayText' ? 'Testo' : 'Etichetta' }}\r\n </label>\r\n <textarea\r\n class=\"fb-textarea\"\r\n [value]=\"selectedField()!.fieldText || ''\"\r\n (input)=\"setFieldProperty('fieldText', $any($event.target).value)\"\r\n ></textarea>\r\n <p class=\"fb-field__hint\">\r\n Supporta i merge field <code>&#123;!Riferimento&#125;</code>.\r\n @if (selectedField()!.fieldType === 'ComponentInstance') {\r\n <!--\r\n \u00A75.2 \u2014 su un componente l\u2019etichetta e\u2019 **ammessa** e viene consegnata al frontend\r\n in `label`, ma la sua assenza non e\u2019 un rilievo: un componente di solito disegna\r\n la propria intestazione. Nasconderla, come faceva prima l\u2019editor, toglieva un\r\n campo che il contratto prevede.\r\n -->\r\n Facoltativa: un componente di solito disegna la propria intestazione.\r\n }\r\n </p>\r\n </div>\r\n } @else {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Intestazione</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"selectedField()!.fieldText || ''\"\r\n (input)=\"setFieldProperty('fieldText', $any($event.target).value)\"\r\n />\r\n </div>\r\n }\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Testo di aiuto</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"selectedField()!.helpText || ''\"\r\n (input)=\"setFieldProperty('helpText', $any($event.target).value)\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Larghezza</label>\r\n <div class=\"fb-scr__width\">\r\n <input\r\n type=\"range\"\r\n min=\"1\"\r\n max=\"12\"\r\n step=\"1\"\r\n [value]=\"widthOf(selectedField()!)\"\r\n (input)=\"setNumberProperty('width', $any($event.target).value)\"\r\n />\r\n <span class=\"fb-scr__width-value\">{{ widthOf(selectedField()!) }}/12</span>\r\n </div>\r\n <p class=\"fb-field__hint\">\r\n Colonne della griglia della schermata. \u00C8 un\u2019indicazione: il frontend puo\u2019 ignorarla.\r\n </p>\r\n </div>\r\n\r\n @if (selectedField()!.fieldType === 'ComponentInstance') {\r\n <!--\r\n \u00A75.2 \u2014 perche\u2019 qui non c\u2019e\u2019 \u00ABobbligatorio\u00BB. La domanda arriva sempre, e la risposta non\r\n si indovina guardando l\u2019interfaccia: un componente non ha un valore proprio, ce l\u2019hanno\r\n i suoi output, che vanno in **variabili** scelte dall\u2019autore. \u00ABObbligatorio\u00BB dovrebbe\r\n dire *quale output* deve essere valorizzato, e il metadata non ha modo di dirlo \u2014\r\n dichiararlo qui sarebbe `SCREEN_FIELD_CONFIGURATION_INVALID` e il runtime lo\r\n ignorerebbe. Le due strade vere stanno nel contratto e sono queste.\r\n -->\r\n <p class=\"fb-field__hint\">\r\n Un componente non raccoglie un valore \u2014 lo fanno i suoi output \u2014 quindi obbligatorieta\u2019, valore\r\n predefinito e regola di validazione non si applicano: dichiararli e\u2019\r\n <code>SCREEN_FIELD_CONFIGURATION_INVALID</code> e il runtime li ignora. Per pretendere una\r\n compilazione: la validazione la fa il componente stesso, oppure si controlla la variabile di\r\n destinazione con una Decision dopo la schermata.\r\n </p>\r\n }\r\n\r\n <!-- ------------------------------------------------ campi che raccolgono un valore -->\r\n @if (selectedType()?.storesValue) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Valore</legend>\r\n\r\n @if (selectedField()!.fieldType !== 'ObjectProvided') {\r\n <div class=\"fb-field\">\r\n <label\r\n class=\"fb-field__label\"\r\n [class.fb-field__label--required]=\"!!selectedType()?.requiresDataType\"\r\n >\r\n Tipo di dato\r\n </label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"selectedField()!.dataType || ''\"\r\n (change)=\"setFieldProperty('dataType', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 scegli \u2014</option>\r\n @for (entry of dictionary.dataTypes(); track entry.value) {\r\n <option [value]=\"entry.value\">{{ entry.label }}</option>\r\n }\r\n </select>\r\n @if (selectedType()?.requiresDataType && !selectedField()!.dataType) {\r\n <p class=\"fb-field__error\">Obbligatorio per questo tipo di campo (DATA_TYPE_MISSING).</p>\r\n }\r\n @if (selectedType()?.isCollection) {\r\n <p class=\"fb-field__hint\">\r\n Il valore raccolto e\u2019 una <strong>collection</strong>, non una stringa con i valori\r\n separati: nelle condizioni si usano gli operatori di collection.\r\n </p>\r\n }\r\n </div>\r\n\r\n @if (requiresObjectType()) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\" [class.fb-field__label--required]=\"objectTypeIsStructure()\">\r\n {{\r\n objectTypeIsStructure()\r\n ? 'Classe'\r\n : selectedField()!.dataType === 'Enum'\r\n ? 'Tipo di enumerazione'\r\n : 'Oggetto'\r\n }}\r\n </label>\r\n @if (selectedField()!.dataType === 'Enum') {\r\n <!-- Dizionario chiuso: si sceglie, non si scrive. -->\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"selectedField()!.objectType || ''\"\r\n (change)=\"setFieldProperty('objectType', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 scegli \u2014</option>\r\n @for (entry of enumOptions(); track entry.name) {\r\n <option [value]=\"entry.name\">{{ entry.label || entry.name }}</option>\r\n }\r\n </select>\r\n @if (!selectedField()!.objectType) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n Senza il tipo concreto l\u2019editor non puo\u2019 proporre i valori dell\u2019enumerazione.\r\n </p>\r\n }\r\n } @else if (objectTypeIsStructure()) {\r\n <fb-structure-picker\r\n [value]=\"selectedField()!.objectType\"\r\n label=\"Classe\"\r\n (valueChange)=\"setFieldProperty('objectType', $event ?? '')\"\r\n />\r\n @if (!selectedField()!.objectType) {\r\n <p class=\"fb-field__error\">\r\n La classe e\u2019 obbligatoria: senza, il runtime non ha nulla da istanziare\r\n (OBJECT_TYPE_MISSING).\r\n </p>\r\n }\r\n } @else {\r\n <fb-object-picker\r\n [value]=\"selectedField()!.objectType\"\r\n label=\"Oggetto\"\r\n (valueChange)=\"setFieldProperty('objectType', $event ?? '')\"\r\n />\r\n }\r\n </div>\r\n }\r\n } @else {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Campo dell\u2019entita\u2019</label>\r\n <p class=\"fb-field__hint\">\r\n Tipo, label, obbligatorieta\u2019, scala e \u2014 se il campo e\u2019 una picklist \u2014 le opzioni arrivano\r\n dallo schema dati: qui non si ridichiarano.\r\n </p>\r\n <fb-object-picker\r\n [value]=\"providedObject() || undefined\"\r\n label=\"Oggetto\"\r\n (valueChange)=\"setProvidedObject($event)\"\r\n />\r\n <fb-field-picker\r\n [value]=\"providedField() || undefined\"\r\n [object]=\"providedObject() || undefined\"\r\n label=\"Campo\"\r\n (valueChange)=\"setProvidedField($event)\"\r\n />\r\n </div>\r\n }\r\n\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"selectedIsRequired()\"\r\n (change)=\"setRequired($any($event.target).checked)\"\r\n />\r\n Obbligatorio\r\n </label>\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"selectedIsEditable()\"\r\n (change)=\"setEditable($any($event.target).checked)\"\r\n />\r\n Modificabile\r\n </label>\r\n @if (!selectedIsEditable()) {\r\n <p class=\"fb-field__hint\">\r\n A <code>false</code> il runtime <strong>ignora</strong> cio\u2019 che il client rimanda indietro:\r\n e\u2019 l\u2019unico modo di rendere un campo davvero di sola lettura.\r\n </p>\r\n }\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Valore di default</label>\r\n <fb-value-editor\r\n [value]=\"selectedField()!.defaultValue\"\r\n [dataType]=\"selectedField()!.dataType\"\r\n [objectType]=\"selectedField()!.objectType\"\r\n [isCollection]=\"selectedType()?.isCollection\"\r\n label=\"Valore di default\"\r\n (valueChange)=\"setDefaultValue($event)\"\r\n />\r\n </div>\r\n\r\n @if (scaleApplies()) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Decimali</label>\r\n <input\r\n class=\"fb-input\"\r\n type=\"number\"\r\n min=\"0\"\r\n [value]=\"selectedField()!.scale ?? ''\"\r\n (change)=\"setNumberProperty('scale', $any($event.target).value)\"\r\n />\r\n </div>\r\n } @else if (selectedField()!.scale !== undefined) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n <code>scale</code> su un campo non numerico e\u2019 SCALE_NOT_APPLICABLE.\r\n </p>\r\n }\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Lunghezza massima</label>\r\n <input\r\n class=\"fb-input\"\r\n type=\"number\"\r\n min=\"1\"\r\n [value]=\"selectedField()!.maxLength ?? ''\"\r\n (change)=\"setNumberProperty('maxLength', $any($event.target).value)\"\r\n />\r\n <p class=\"fb-field__hint\">Verificata anche dal runtime (TOO_LONG).</p>\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Tornando sulla schermata</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"selectedField()!.inputsOnNextNavToAssocScrn || ''\"\r\n (change)=\"setFieldProperty('inputsOnNextNavToAssocScrn', $any($event.target).value)\"\r\n >\r\n <option value=\"\">Predefinito (mantieni i valori)</option>\r\n @for (entry of dictionary.screenFieldInputsRevisited(); track entry.value) {\r\n <option [value]=\"entry.value\">{{ entry.label }}</option>\r\n }\r\n </select>\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Regola di validazione</label>\r\n <!--\r\n `commitOn=\"change\"`: qui mutare il documento ridisegna l'anteprima, e riscriverla a\r\n ogni tasto costa. La verifica parte lo stesso mentre si digita (\u00A76.3).\r\n -->\r\n <fb-formula-editor\r\n [expression]=\"selectedField()!.validationRule?.formulaExpression || ''\"\r\n usage=\"ValidationRule\"\r\n expectedDataType=\"Boolean\"\r\n commitOn=\"change\"\r\n [rows]=\"2\"\r\n placeholder=\"Espressione, es. LEN(Nome) > 3\"\r\n ariaLabel=\"Regola di validazione\"\r\n (expressionChange)=\"setValidationRule('formulaExpression', $event)\"\r\n />\r\n <input\r\n class=\"fb-input\"\r\n placeholder=\"Messaggio mostrato quando l\u2019espressione e\u2019 falsa\"\r\n [value]=\"selectedField()!.validationRule?.errorMessage || ''\"\r\n (change)=\"setValidationRule('errorMessage', $any($event.target).value)\"\r\n />\r\n <p class=\"fb-field__hint\">\r\n L\u2019espressione la valuta il motore di regole: il backend non la interpreta, la fa\r\n verificare al motore.\r\n </p>\r\n </div>\r\n </fieldset>\r\n }\r\n\r\n <!-- --------------------------------------------------------------- choice -->\r\n @if (selectedType()?.acceptsChoices) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Opzioni</legend>\r\n <p class=\"fb-section__note\">\r\n Accetta <strong>Choice</strong> e <strong>Dynamic choice set</strong>, nell\u2019ordine in cui le\r\n opzioni compaiono. Puntare a una variabile e\u2019 SCREEN_FIELD_CHOICE_UNKNOWN.\r\n </p>\r\n\r\n @if (!selectedField()!.choiceReferences?.length) {\r\n @if (selectedField()!.fieldType === 'ObjectProvided') {\r\n <!-- Le opzioni di una picklist arrivano dallo schema: qui si aggiungono solo se\r\n si vuole sostituirle, e non dichiararne nessuna e' il caso normale. -->\r\n <p class=\"fb-field__hint\">\r\n Se il campo dello schema e\u2019 una picklist, le opzioni arrivano da l\u00EC: dichiararle qui\r\n serve solo a sostituirle.\r\n </p>\r\n } @else {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Nessuna opzione: il campo non ha niente da mostrare (SCREEN_FIELD_CHOICES_MISSING).\r\n </p>\r\n }\r\n }\r\n\r\n <div class=\"fb-list\">\r\n @for (choice of selectedField()!.choiceReferences || []; track $index) {\r\n <div class=\"fb-list__item\">\r\n <div class=\"fb-list__header\">\r\n <span class=\"fb-list__index\">{{ $index + 1 }}</span>\r\n <span class=\"fb-list__title\">{{ choice }}</span>\r\n <span class=\"fb-list__spacer\"></span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--sm\" (click)=\"moveChoice($index, -1)\">\u2191</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--sm\" (click)=\"moveChoice($index, 1)\">\u2193</button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--sm\"\r\n aria-label=\"Rimuovi\"\r\n (click)=\"removeChoice($index)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Aggiungi un\u2019opzione</label>\r\n <fb-name-picker\r\n [options]=\"choiceOptions()\"\r\n label=\"Choice\"\r\n placeholder=\"Scegli una choice o un choice set\"\r\n unknownMessage=\"Questo nome non e\u2019 una choice ne\u2019 un choice set: e\u2019 SCREEN_FIELD_CHOICE_UNKNOWN.\"\r\n unknownSeverity=\"error\"\r\n emptyMessage=\"Il flow non dichiara nessuna choice: creale nel pannello delle risorse.\"\r\n (valueChange)=\"addChoice($event)\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Opzione selezionata all\u2019apertura</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"selectedField()!.defaultSelectedChoiceReference || ''\"\r\n (change)=\"setFieldProperty('defaultSelectedChoiceReference', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 nessuna \u2014</option>\r\n @for (choice of selectedField()!.choiceReferences || []; track $index) {\r\n <option [value]=\"choice\">{{ choice }}</option>\r\n }\r\n </select>\r\n @if (defaultChoiceIsForeign()) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n \u00AB{{ selectedField()!.defaultSelectedChoiceReference }}\u00BB non e\u2019 fra le opzioni elencate qui\r\n sopra.\r\n </p>\r\n }\r\n </div>\r\n </fieldset>\r\n }\r\n\r\n <!-- ------------------------------------------------------- contenitori -->\r\n @if (selectedType()?.isContainer) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Contenitore</legend>\r\n @if (selectedField()!.fieldType === 'RegionContainer') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Tipo di sezione</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"selectedField()!.regionContainerType || ''\"\r\n (change)=\"setFieldProperty('regionContainerType', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 predefinito \u2014</option>\r\n @for (entry of dictionary.regionContainerTypes(); track entry.value) {\r\n <option [value]=\"entry.value\">{{ entry.label }}</option>\r\n }\r\n </select>\r\n </div>\r\n }\r\n @if (!selectedField()!.fields?.length) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Il contenitore e\u2019 vuoto: non produce niente sulla schermata (SCREEN_CONTAINER_EMPTY).\r\n </p>\r\n }\r\n </fieldset>\r\n }\r\n\r\n <!-- --------------------------------------------------- ComponentInstance -->\r\n @if (selectedField()!.fieldType === 'ComponentInstance') {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Componente</legend>\r\n <p class=\"fb-section__note\">\r\n Componenti e form sono la stessa domanda al frontend \u2014 cosa sa rendere, e con quali parametri\r\n \u2014 e passano dallo stesso catalogo. Un <code>ComponentInstance</code> non ha un valore proprio:\r\n lo hanno i suoi output.\r\n </p>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Componente</label>\r\n <fb-name-picker\r\n [value]=\"selectedField()!.extensionName\"\r\n [options]=\"componentOptions()\"\r\n [unusableOptions]=\"unusableComponents()\"\r\n label=\"Componente\"\r\n placeholder=\"Scrivi o scegli un componente\"\r\n [isMono]=\"false\"\r\n unknownMessage=\"Questo componente non esiste nel catalogo: e\u2019 SCREEN_COMPONENT_UNKNOWN.\"\r\n unknownSeverity=\"error\"\r\n unusableMessage=\"Questo nome e\u2019 una schermata intera, non un componente montabile qui (FORM_KIND_MISMATCH).\"\r\n emptyMessage=\"Il catalogo dei componenti non e\u2019 popolato: il nome non viene verificato.\"\r\n (valueChange)=\"setFieldProperty('extensionName', $event ?? '')\"\r\n />\r\n @if (!selectedField()!.extensionName) {\r\n <p class=\"fb-field__error\">Obbligatorio: senza, SCREEN_COMPONENT_MISSING.</p>\r\n }\r\n </div>\r\n\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"selectedField()!.storeOutputAutomatically === true\"\r\n (change)=\"setStoreOutputAutomatically($any($event.target).checked)\"\r\n />\r\n Rendi gli output referenziabili automaticamente\r\n </label>\r\n @if (selectedField()!.storeOutputAutomatically) {\r\n <p class=\"fb-field__hint\">\r\n Gli output si referenziano come\r\n <code>{{ selectedField()!.name || 'Campo' }}.nomeOutput</code>, senza dichiarare variabili.\r\n </p>\r\n }\r\n @if (hasOutputConflict()) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n Output automatici <strong>e</strong> parametri di uscita insieme:\r\n OUTPUT_CONFIGURATION_CONFLICT.\r\n </p>\r\n }\r\n\r\n <fb-parameter-editor\r\n [holder]=\"$any(selectedField())\"\r\n [catalogParameters]=\"componentParameterList()\"\r\n inputTitle=\"Valori passati al componente\"\r\n outputTitle=\"Valori raccolti dal componente\"\r\n [showOutputs]=\"!selectedField()!.storeOutputAutomatically\"\r\n outputsDisabledReason=\"Gli output sono automatici: disattivalo per assegnarli a variabili.\"\r\n (changed)=\"onComponentParametersChanged($event)\"\r\n />\r\n </fieldset>\r\n }\r\n\r\n <!-- ------------------------------------------------------- visibilita' -->\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Visibilita\u2019</legend>\r\n <p class=\"fb-section__note\">\r\n Le regole si rivalutano <strong>sui valori appena inviati</strong>: e\u2019 cos\u00EC che un campo compare\r\n in funzione di un altro campo della stessa schermata. Un campo risultato nascosto viene\r\n <strong>azzerato</strong> e non viene validato.\r\n </p>\r\n <fb-condition-editor\r\n [holder]=\"visibilityRule()\"\r\n title=\"Mostra il campo quando\"\r\n [allowFormula]=\"false\"\r\n [issuePath]=\"'fields[' + (selectedField()!.name || '') + '].visibilityRule'\"\r\n (changed)=\"onVisibilityChanged($event)\"\r\n />\r\n </fieldset>\r\n }\r\n </aside>\r\n</div>\r\n\r\n<fb-connector-editor\r\n [nodeName]=\"name()\"\r\n [node]=\"node()\"\r\n [outlets]=\"outlets()\"\r\n (connectorChanged)=\"onConnectorChanged($event)\"\r\n/>\r\n\r\n<!--\r\n ============================================================== l'anteprima\r\n Due template ricorsivi. `listTpl` disegna i figli di un contenitore, e nient'altro: `data-item`\r\n marca cio' che conta come fratello nel calcolo del punto di rilascio, e il segnaposto del\r\n contenitore vuoto ne resta fuori. La barra di inserimento non e' qui \u2014 sta sopra l'anteprima,\r\n vedi il velo nel frame.\r\n-->\r\n<ng-template #listTpl let-fields let-parent=\"parent\" let-axis=\"axis\">\r\n @for (child of fields; track $index) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"fieldTpl\"\r\n [ngTemplateOutletContext]=\"{ $implicit: child, path: childPath(parent, $index), axis: axis }\"\r\n />\r\n }\r\n @if (!fields.length) {\r\n <p class=\"fb-scr__drop-hint\">Trascina qui un componente</p>\r\n }\r\n</ng-template>\r\n\r\n<ng-template #fieldTpl let-field let-path=\"path\" let-axis=\"axis\">\r\n <div\r\n class=\"fb-scr__item\"\r\n data-item=\"\"\r\n cdkDrag\r\n [style.grid-column]=\"'span ' + widthOf(field)\"\r\n [class.fb-scr__item--selected]=\"isSelected(path)\"\r\n [class.fb-scr__item--dragging]=\"isDragging(path)\"\r\n [class.fb-scr__item--container]=\"isContainer(field)\"\r\n (cdkDragStarted)=\"onFieldDragStarted(path)\"\r\n (cdkDragMoved)=\"onFieldDragMoved($event)\"\r\n (cdkDragEnded)=\"onFieldDragEnded($event, path)\"\r\n (click)=\"select(path); $event.stopPropagation()\"\r\n >\r\n <span class=\"fb-scr__grip\" cdkDragHandle title=\"Trascina per spostare\" aria-hidden=\"true\">\u283F</span>\r\n @if (field.visibilityRule?.conditions?.length) {\r\n <span class=\"fb-scr__flag\" title=\"Ha una regola di visibilita\u2019\">\u25D0</span>\r\n }\r\n\r\n @switch (field.fieldType) {\r\n @case ('RegionContainer') {\r\n <div class=\"fb-scr__section\">\r\n @if (field.regionContainerType !== 'SectionWithoutHeader') {\r\n <header class=\"fb-scr__section-head\">{{ field.fieldText || field.name }}</header>\r\n }\r\n <div class=\"fb-scr__cols\" [attr.data-drop]=\"dropId(path)\" data-axis=\"row\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"listTpl\"\r\n [ngTemplateOutletContext]=\"{ $implicit: field.fields || [], parent: path, axis: 'row' }\"\r\n />\r\n </div>\r\n </div>\r\n }\r\n @case ('Region') {\r\n <div class=\"fb-scr__region\">\r\n <span class=\"fb-scr__region-tag\">{{ field.name }} \u00B7 {{ widthOf(field) }}/12</span>\r\n <div class=\"fb-scr__region-body\" [attr.data-drop]=\"dropId(path)\" data-axis=\"column\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"listTpl\"\r\n [ngTemplateOutletContext]=\"{ $implicit: field.fields || [], parent: path, axis: 'column' }\"\r\n />\r\n </div>\r\n </div>\r\n }\r\n @case ('DisplayText') {\r\n <p class=\"fb-scr__display\">{{ field.fieldText || '(testo vuoto)' }}</p>\r\n }\r\n @case ('LargeTextArea') {\r\n <label class=\"fb-scr__label\">\r\n {{ captionOf(field) }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__control fb-scr__control--area\">{{ placeholderOf(field) }}</div>\r\n }\r\n @case ('PasswordField') {\r\n <label class=\"fb-scr__label\">\r\n {{ captionOf(field) }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__control\">\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022</div>\r\n }\r\n @case ('DropdownBox') {\r\n <label class=\"fb-scr__label\">\r\n {{ captionOf(field) }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__control fb-scr__control--select\">\r\n <span>{{ field.defaultSelectedChoiceReference || choiceLabelsOf(field)[0] || '\u2014 scegli \u2014' }}</span>\r\n <span aria-hidden=\"true\">\u25BE</span>\r\n </div>\r\n }\r\n @case ('MultiSelectPicklist') {\r\n <label class=\"fb-scr__label\">\r\n {{ captionOf(field) }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__control fb-scr__control--select\">\r\n <span>{{ choiceLabelsOf(field).join(', ') || '\u2014 scegli \u2014' }}</span>\r\n <span aria-hidden=\"true\">\u2261</span>\r\n </div>\r\n }\r\n @case ('RadioButtons') {\r\n <label class=\"fb-scr__label\">\r\n {{ captionOf(field) }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__options\">\r\n @for (option of choiceLabelsOf(field); track $index) {\r\n <span class=\"fb-scr__option\">\u25EF {{ option }}</span>\r\n }\r\n @if (!choiceLabelsOf(field).length) {\r\n <span class=\"fb-scr__option fb-scr__option--missing\">Nessuna opzione</span>\r\n }\r\n </div>\r\n }\r\n @case ('MultiSelectCheckboxes') {\r\n <label class=\"fb-scr__label\">\r\n {{ captionOf(field) }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__options\">\r\n @for (option of choiceLabelsOf(field); track $index) {\r\n <span class=\"fb-scr__option\">\u2610 {{ option }}</span>\r\n }\r\n @if (!choiceLabelsOf(field).length) {\r\n <span class=\"fb-scr__option fb-scr__option--missing\">Nessuna opzione</span>\r\n }\r\n </div>\r\n }\r\n @case ('ComponentInstance') {\r\n <!--\r\n L\u2019etichetta si disegna se c\u2019e\u2019: il runtime la consegna in `label` come per gli altri\r\n campi (\u00A75.2). Nessun asterisco: un componente non raccoglie un valore, quindi\r\n \u00ABobbligatorio\u00BB non ha niente su cui applicarsi.\r\n -->\r\n @if (field.fieldText) {\r\n <label class=\"fb-scr__label\">{{ field.fieldText }}</label>\r\n }\r\n <div class=\"fb-scr__component\">\r\n <span aria-hidden=\"true\">\u2B21</span>\r\n {{ field.extensionName || 'Componente non indicato' }}\r\n </div>\r\n }\r\n @case ('ObjectProvided') {\r\n <label class=\"fb-scr__label\">\r\n {{ field.fieldText || field.objectFieldReference || field.name }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__control\">{{ placeholderOf(field) }}</div>\r\n <span class=\"fb-scr__tag\">{{ field.objectFieldReference || 'campo non indicato' }}</span>\r\n }\r\n @default {\r\n <label class=\"fb-scr__label\">\r\n {{ captionOf(field) }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__control\">{{ placeholderOf(field) }}</div>\r\n @if (isUnknownType(field)) {\r\n <span class=\"fb-scr__tag fb-scr__tag--warn\">{{ field.fieldType }}: tipo non nel dizionario</span>\r\n }\r\n }\r\n }\r\n </div>\r\n</ng-template>\r\n", styles: [".fb-scr{display:grid;grid-template-columns:220px minmax(0,1fr) 340px;gap:12px;align-items:start;margin-bottom:14px}@media(max-width:1100px){.fb-scr{grid-template-columns:minmax(0,1fr)}}.fb-scr__side,.fb-scr__props{min-width:0;padding:10px;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius, 10px);background:var(--fb-surface-alt, #f7f8fa)}.fb-scr__tabs{display:flex;gap:4px;margin-bottom:8px}.fb-scr__tab{flex:1;padding:5px 8px;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-xs, 6px);background:var(--fb-surface, #fff);color:var(--fb-text-muted, #6b7086);font:inherit;font-size:11px;font-weight:600;cursor:pointer}.fb-scr__tab--active{border-color:var(--fb-accent, #4f6ef7);background:color-mix(in srgb,var(--fb-accent, #4f6ef7) 10%,transparent);color:var(--fb-accent-strong, #3d59e0)}.fb-scr__side-hint,.fb-scr__side-empty{margin:6px 0;font-size:11px;color:var(--fb-text-muted, #6b7086)}.fb-scr__palette{display:flex;flex-direction:column;gap:4px;margin:8px 0 0;padding:0;max-height:320px;overflow-y:auto;overscroll-behavior:contain;list-style:none}.fb-scr__chip{display:flex;align-items:center;gap:8px;width:100%;padding:6px 8px;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-xs, 6px);background:var(--fb-surface, #fff);color:var(--fb-text, #1a1c23);font:inherit;font-size:12px;text-align:left;cursor:grab}.fb-scr__chip:hover{border-color:var(--fb-accent, #4f6ef7)}.fb-scr__chip-icon{flex:none;width:20px;text-align:center;color:var(--fb-text-muted, #6b7086)}.fb-scr__chip-label{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-scr__chip-label small{margin-left:4px;color:var(--fb-text-subtle, #98a2b3);font-size:10px}.fb-scr__canvas{min-width:0;padding:14px;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius, 10px);background:var(--fb-canvas-bg, #f4f5f7)}.fb-scr__frame{position:relative;display:flex;flex-direction:column;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-sm, 8px);background:var(--fb-surface, #fff);box-shadow:var(--fb-shadow-sm, 0 1px 2px rgb(16 24 40 / 6%));overflow:hidden}.fb-scr__frame-head{display:flex;align-items:center;gap:6px;padding:8px 12px;border-bottom:1px solid var(--fb-border-subtle, #eef0f4);font-size:13px;font-weight:600}.fb-scr__help{display:inline-flex;align-items:center;justify-content:center;width:15px;height:15px;border-radius:50%;background:var(--fb-surface-sunken, #eef0f4);color:var(--fb-text-muted, #6b7086);font-size:10px}.fb-scr__body,.fb-scr__region-body{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));align-content:start;gap:8px 0;padding:12px;min-height:90px}.fb-scr__region-body{padding:8px;min-height:60px}.fb-scr__cols{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));align-items:stretch;gap:0;padding:8px;min-height:60px}.fb-scr__cols>.fb-scr__item{display:flex;align-self:stretch}.fb-scr__cols>.fb-scr__item>.fb-scr__region{flex:1;min-width:0}.fb-scr__drop-hint{grid-column:1 / -1;margin:0;padding:10px;border:1px dashed var(--fb-border-strong, #cfd4de);border-radius:var(--fb-radius-xs, 6px);color:var(--fb-text-subtle, #98a2b3);font-size:11px;text-align:center}.fb-scr__marker{position:absolute;z-index:2;border-radius:2px;background:var(--fb-accent, #4f6ef7);pointer-events:none}.fb-scr__item{position:relative;box-sizing:border-box;padding:6px 8px;border:1px solid transparent;border-radius:var(--fb-radius-xs, 6px);cursor:pointer}.fb-scr__item:hover{border-color:var(--fb-border-strong, #cfd4de);background:color-mix(in srgb,var(--fb-accent, #4f6ef7) 4%,transparent)}.fb-scr__item--selected{border-color:var(--fb-accent, #4f6ef7);box-shadow:inset 0 0 0 1px var(--fb-accent, #4f6ef7)}.fb-scr__item--dragging{opacity:.45;pointer-events:none}.fb-scr__grip{position:absolute;top:2px;left:-2px;padding:0 3px;color:var(--fb-text-subtle, #98a2b3);font-size:11px;line-height:1;opacity:0;cursor:grab}.fb-scr__item:hover>.fb-scr__grip,.fb-scr__item--selected>.fb-scr__grip{opacity:1}.fb-scr__flag{position:absolute;top:2px;right:4px;color:var(--fb-text-muted, #6b7086);font-size:11px}.fb-scr__label{display:block;margin-bottom:3px;font-size:11px;font-weight:600;color:var(--fb-text, #1a1c23)}.fb-scr__req{color:var(--fb-error, #dc2626)}.fb-scr__control{display:flex;align-items:center;justify-content:space-between;gap:6px;min-height:26px;padding:4px 8px;border:1px solid var(--fb-border-strong, #cfd4de);border-radius:var(--fb-radius-xs, 6px);background:var(--fb-surface, #fff);color:var(--fb-text-subtle, #98a2b3);font-size:12px;pointer-events:none}.fb-scr__control--area{min-height:54px;align-items:flex-start}.fb-scr__display{margin:0;font-size:12px;color:var(--fb-text, #1a1c23)}.fb-scr__options{display:flex;flex-direction:column;gap:2px;pointer-events:none}.fb-scr__option{font-size:12px;color:var(--fb-text-muted, #6b7086)}.fb-scr__option--missing{color:var(--fb-warning, #b7791f)}.fb-scr__component{display:flex;align-items:center;gap:6px;padding:10px;border:1px dashed var(--fb-accent, #4f6ef7);border-radius:var(--fb-radius-xs, 6px);background:color-mix(in srgb,var(--fb-accent, #4f6ef7) 5%,transparent);font-size:12px;color:var(--fb-text-muted, #6b7086)}.fb-scr__tag{display:inline-block;margin-top:3px;font-size:10px;color:var(--fb-text-subtle, #98a2b3)}.fb-scr__tag--warn{color:var(--fb-warning, #b7791f)}.fb-scr__section{border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-xs, 6px);background:var(--fb-surface-alt, #f7f8fa)}.fb-scr__section-head{padding:5px 10px;border-bottom:1px solid var(--fb-border-subtle, #eef0f4);font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--fb-text-muted, #6b7086)}.fb-scr__region{border:1px dashed var(--fb-border-strong, #cfd4de);border-radius:var(--fb-radius-xs, 6px);background:var(--fb-surface, #fff)}.fb-scr__region-tag{display:block;padding:3px 6px 0;font-size:10px;color:var(--fb-text-subtle, #98a2b3)}.fb-scr__frame-foot{display:flex;align-items:center;gap:6px;padding:8px 12px;border-top:1px solid var(--fb-border-subtle, #eef0f4);background:var(--fb-surface-alt, #f7f8fa)}.fb-scr__spacer{flex:1}.fb-scr__btn{padding:3px 10px;border:1px solid var(--fb-border-strong, #cfd4de);border-radius:var(--fb-radius-xs, 6px);font-size:11px;color:var(--fb-text-muted, #6b7086)}.fb-scr__btn--primary{border-color:var(--fb-accent, #4f6ef7);background:var(--fb-accent, #4f6ef7);color:var(--fb-accent-contrast, #fff)}.fb-scr__canvas-note{margin-top:10px}.fb-scr__props{background:var(--fb-surface, #fff)}.fb-scr__props-head{display:flex;align-items:center;gap:8px;margin-bottom:8px}.fb-scr__props-title{flex:1;min-width:0;margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--fb-text-muted, #6b7086)}.fb-scr__props-actions{display:flex;flex-wrap:wrap;gap:3px}.fb-btn--sm{padding:3px 7px;font-size:11px}.fb-scr__width{display:flex;align-items:center;gap:8px}.fb-scr__width input[type=range]{flex:1;min-width:0}.fb-scr__width-value{font-size:11px;color:var(--fb-text-muted, #6b7086)}\n"], dependencies: [{ kind: "directive", type: CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "component", type: ConditionEditorComponent, selector: "fb-condition-editor", inputs: ["holder", "title", "allowFormula", "allowLogic", "issuePath"], outputs: ["changed"] }, { kind: "component", type: ConnectorEditorComponent, selector: "fb-connector-editor", inputs: ["nodeName", "node", "outlets", "title"], outputs: ["connectorChanged"] }, { kind: "component", type: FieldPickerComponent, selector: "fb-field-picker", inputs: ["value", "object", "usage", "label", "placeholder", "disabled"], outputs: ["valueChange"] }, { kind: "component", type: FormulaEditorComponent, selector: "fb-formula-editor", inputs: ["expression", "usage", "expectedDataType", "scale", "placeholder", "ariaLabel", "disabled", "rows", "commitOn"], outputs: ["expressionChange"] }, { kind: "component", type: NamePickerComponent, selector: "fb-name-picker", inputs: ["value", "options", "label", "placeholder", "disabled", "unknownMessage", "unknownSeverity", "unusableOptions", "unusableMessage", "emptyMessage", "isMono"], outputs: ["valueChange"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: ObjectPickerComponent, selector: "fb-object-picker", inputs: ["value", "label", "placeholder", "disabled"], outputs: ["valueChange"] }, { kind: "component", type: ParameterEditorComponent, selector: "fb-parameter-editor", inputs: ["holder", "catalogParameters", "inputTitle", "outputTitle", "showInputs", "showOutputs", "outputsDisabledReason", "extraTargets"], outputs: ["changed"] }, { kind: "directive", type: SelectValueDirective, selector: "select[fbValue]", inputs: ["fbValue"] }, { kind: "component", type: StructurePickerComponent, selector: "fb-structure-picker", inputs: ["value", "label", "placeholder", "disabled"], outputs: ["valueChange"] }, { kind: "component", type: ValueEditorComponent, selector: "fb-value-editor", inputs: ["value", "label", "dataType", "objectType", "isCollection", "disabled", "allowFormula"], outputs: ["valueChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
9503
10079
  }
9504
10080
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImport: i0, type: DynamicScreenInspectorComponent, decorators: [{
9505
10081
  type: Component,
@@ -9517,7 +10093,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImpo
9517
10093
  SelectValueDirective,
9518
10094
  StructurePickerComponent,
9519
10095
  ValueEditorComponent,
9520
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"fb-scr\">\r\n <!-- ============================================================ sinistra -->\r\n <aside class=\"fb-scr__side\" aria-label=\"Componenti e campi\">\r\n <div class=\"fb-scr__tabs\" role=\"tablist\">\r\n <button\r\n type=\"button\"\r\n role=\"tab\"\r\n class=\"fb-scr__tab\"\r\n [class.fb-scr__tab--active]=\"paletteTab() === 'components'\"\r\n [attr.aria-selected]=\"paletteTab() === 'components'\"\r\n (click)=\"setPaletteTab('components')\"\r\n >\r\n Componenti\r\n </button>\r\n <button\r\n type=\"button\"\r\n role=\"tab\"\r\n class=\"fb-scr__tab\"\r\n [class.fb-scr__tab--active]=\"paletteTab() === 'fields'\"\r\n [attr.aria-selected]=\"paletteTab() === 'fields'\"\r\n (click)=\"setPaletteTab('fields')\"\r\n >\r\n Campi\r\n </button>\r\n </div>\r\n\r\n @if (paletteTab() === 'components') {\r\n @if (!dictionary.screenFieldTypes().length) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Il dizionario <code>screenFieldTypes</code> non e\u2019 disponibile: senza i suoi flag l\u2019editor non\r\n sa quali campi ammette ciascun tipo, e non li inventa.\r\n </p>\r\n }\r\n <p class=\"fb-scr__side-hint\">Trascina sulla schermata, oppure clicca per aggiungere.</p>\r\n <ul class=\"fb-scr__palette\">\r\n @for (entry of dictionary.screenFieldTypes(); track entry.value) {\r\n <li>\r\n <button\r\n type=\"button\"\r\n class=\"fb-scr__chip\"\r\n cdkDrag\r\n [title]=\"entry.description || entry.label\"\r\n (cdkDragMoved)=\"onPaletteDragMoved($event)\"\r\n (cdkDragEnded)=\"onComponentDragEnded($event, entry.value)\"\r\n (click)=\"addComponentAtSelection(entry.value)\"\r\n >\r\n <span class=\"fb-scr__chip-icon\" aria-hidden=\"true\">{{ iconOf(entry.value) }}</span>\r\n <span class=\"fb-scr__chip-label\">{{ entry.label }}</span>\r\n </button>\r\n </li>\r\n }\r\n </ul>\r\n } @else {\r\n <p class=\"fb-scr__side-hint\">\r\n Un campo dell\u2019entita\u2019 porta con se\u2019 tipo, etichetta, obbligatorieta\u2019 e opzioni: arrivano dallo\r\n schema dati, qui non si ridichiarano.\r\n </p>\r\n <!-- Il catalogo non e' un dizionario chiuso: si sceglie o si scrive, come ovunque si\r\n indichi un'entita' (vedi `object-picker`). -->\r\n <fb-object-picker\r\n [value]=\"fieldsObject()\"\r\n label=\"Entita\u2019\"\r\n placeholder=\"Scrivi o scegli un\u2019entita\u2019\"\r\n (valueChange)=\"setFieldsObject($event)\"\r\n />\r\n @if (fieldsObject() && !fieldsOfObject().length) {\r\n <p class=\"fb-scr__side-empty\">\r\n Nessun campo dichiarato per questa entita\u2019: il catalogo non li espone, non significa che non\r\n ci siano.\r\n </p>\r\n }\r\n <ul class=\"fb-scr__palette\">\r\n @for (field of fieldsOfObject(); track $index) {\r\n <li>\r\n <button\r\n type=\"button\"\r\n class=\"fb-scr__chip\"\r\n cdkDrag\r\n [title]=\"field.name + (field.dataType ? ' \u00B7 ' + field.dataType : '')\"\r\n (cdkDragMoved)=\"onPaletteDragMoved($event)\"\r\n (cdkDragEnded)=\"onObjectFieldDragEnded($event, field)\"\r\n (click)=\"addObjectFieldAtSelection(field)\"\r\n >\r\n <span class=\"fb-scr__chip-icon\" aria-hidden=\"true\">\u2317</span>\r\n <span class=\"fb-scr__chip-label\">\r\n {{ field.label || field.name }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n <small>{{ field.dataType }}</small>\r\n </span>\r\n </button>\r\n </li>\r\n }\r\n </ul>\r\n }\r\n </aside>\r\n\r\n <!-- ============================================================== centro -->\r\n <!-- Cliccare fuori da un campo riporta a destra le proprieta' della schermata. -->\r\n <section class=\"fb-scr__canvas\" aria-label=\"Anteprima della schermata\" (click)=\"selectScreen()\">\r\n <div class=\"fb-scr__frame\">\r\n @if (showHeader()) {\r\n <header class=\"fb-scr__frame-head\">\r\n {{ $any(node()).label || name() }}\r\n @if (screen().helpText) {\r\n <span class=\"fb-scr__help\" [title]=\"screen().helpText || ''\">?</span>\r\n }\r\n </header>\r\n }\r\n\r\n <div\r\n class=\"fb-scr__body\"\r\n [attr.data-drop]=\"''\"\r\n data-axis=\"column\"\r\n [class.fb-scr__body--empty]=\"isEmpty()\"\r\n >\r\n <ng-container\r\n [ngTemplateOutlet]=\"listTpl\"\r\n [ngTemplateOutletContext]=\"{ $implicit: screen().fields || [], parent: [], axis: 'column' }\"\r\n />\r\n </div>\r\n\r\n <!--\r\n La barra di inserimento e' un **velo** sopra l'anteprima, non un figlio del contenitore di\r\n rilascio: dentro la griglia occupava una riga intera e spostava in basso proprio la sezione\r\n che si stava puntando, che allora usciva da sotto il puntatore e il bersaglio oscillava.\r\n -->\r\n @if (dropMarker(); as bar) {\r\n <div\r\n class=\"fb-scr__marker\"\r\n [style.left.px]=\"bar.left\"\r\n [style.top.px]=\"bar.top\"\r\n [style.width.px]=\"bar.width\"\r\n [style.height.px]=\"bar.height\"\r\n ></div>\r\n }\r\n\r\n @if (showFooter()) {\r\n <footer class=\"fb-scr__frame-foot\">\r\n @if (allowPause()) {\r\n <span class=\"fb-scr__btn fb-scr__btn--ghost\">{{ screen().pauseButtonLabel || 'Pausa' }}</span>\r\n }\r\n <span class=\"fb-scr__spacer\"></span>\r\n @if (allowBack()) {\r\n <span class=\"fb-scr__btn fb-scr__btn--ghost\">{{ screen().backButtonLabel || 'Indietro' }}</span>\r\n }\r\n <span class=\"fb-scr__btn fb-scr__btn--primary\">\r\n {{ screen().nextOrFinishButtonLabel || (allowFinish() ? 'Fine' : 'Avanti') }}\r\n </span>\r\n </footer>\r\n }\r\n </div>\r\n\r\n @if (isEmpty()) {\r\n <p class=\"fb-callout fb-callout--warn fb-scr__canvas-note\">\r\n La schermata non ha campi: non c\u2019e\u2019 niente da mostrare all\u2019utente (SCREEN_WITHOUT_FIELDS).\r\n </p>\r\n }\r\n </section>\r\n\r\n <!-- ============================================================== destra -->\r\n <aside class=\"fb-scr__props\" aria-label=\"Proprieta\u2019\">\r\n @if (!selectedField()) {\r\n <!-- ------------------------------------------------ proprieta' della schermata -->\r\n <header class=\"fb-scr__props-head\">\r\n <h3 class=\"fb-scr__props-title\">Schermata</h3>\r\n </header>\r\n <p class=\"fb-scr__side-hint\">Seleziona un campo nell\u2019anteprima per configurarlo.</p>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Testo di aiuto</label>\r\n <textarea\r\n class=\"fb-textarea\"\r\n [value]=\"screen().helpText || ''\"\r\n (input)=\"setScreenText('helpText', $any($event.target).value)\"\r\n ></textarea>\r\n </div>\r\n\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Navigazione</legend>\r\n <p class=\"fb-section__note\">\r\n Questi flag sono un\u2019intenzione, non la verita\u2019 finale: a runtime il motore comunica\r\n <code>canGoBack</code>, <code>canFinish</code> e <code>canPause</code> nella richiesta della\r\n schermata.\r\n </p>\r\n <label class=\"fb-check\">\r\n <input type=\"checkbox\" [checked]=\"allowBack()\" (change)=\"setScreenFlag('allowBack', $any($event.target).checked)\" />\r\n Consenti \u00ABindietro\u00BB\r\n </label>\r\n <label class=\"fb-check\">\r\n <input type=\"checkbox\" [checked]=\"allowFinish()\" (change)=\"setScreenFlag('allowFinish', $any($event.target).checked)\" />\r\n Consenti \u00ABfine\u00BB\r\n </label>\r\n <label class=\"fb-check\">\r\n <input type=\"checkbox\" [checked]=\"allowPause()\" (change)=\"setScreenFlag('allowPause', $any($event.target).checked)\" />\r\n Consenti \u00ABpausa\u00BB\r\n </label>\r\n <label class=\"fb-check\">\r\n <input type=\"checkbox\" [checked]=\"showHeader()\" (change)=\"setScreenFlag('showHeader', $any($event.target).checked)\" />\r\n Mostra l\u2019intestazione\r\n </label>\r\n <label class=\"fb-check\">\r\n <input type=\"checkbox\" [checked]=\"showFooter()\" (change)=\"setScreenFlag('showFooter', $any($event.target).checked)\" />\r\n Mostra il piede\r\n </label>\r\n\r\n @if (allowPause()) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Testo mostrato alla pausa</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"screen().pausedText || ''\"\r\n (input)=\"setScreenText('pausedText', $any($event.target).value)\"\r\n />\r\n </div>\r\n }\r\n\r\n @if (isDeadEnd()) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n Questa schermata non ha una destinazione e non consente \u00ABfine\u00BB: e\u2019 un vicolo cieco, e\r\n l\u2019utente resterebbe bloccato (SCREEN_DEAD_END).\r\n </p>\r\n }\r\n </fieldset>\r\n\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Pulsanti</legend>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Etichetta \u00ABindietro\u00BB</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"screen().backButtonLabel || ''\"\r\n (input)=\"setScreenText('backButtonLabel', $any($event.target).value)\"\r\n />\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Etichetta \u00ABavanti / fine\u00BB</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"screen().nextOrFinishButtonLabel || ''\"\r\n (input)=\"setScreenText('nextOrFinishButtonLabel', $any($event.target).value)\"\r\n />\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Etichetta \u00ABpausa\u00BB</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"screen().pauseButtonLabel || ''\"\r\n (input)=\"setScreenText('pauseButtonLabel', $any($event.target).value)\"\r\n />\r\n </div>\r\n </fieldset>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Stage mostrato nell\u2019avanzamento</label>\r\n <fb-name-picker\r\n [value]=\"screen().stageReference\"\r\n [options]=\"stageOptions()\"\r\n label=\"Stage\"\r\n placeholder=\"Scegli uno stage\"\r\n unknownMessage=\"Questo stage non e\u2019 dichiarato dal flow.\"\r\n emptyMessage=\"Il flow non dichiara stage: creali nel pannello delle risorse.\"\r\n (valueChange)=\"setScreenText('stageReference', $event ?? '')\"\r\n />\r\n </div>\r\n\r\n <!--\r\n Screen action (\u00A75.2). Due elenchi e non uno perche' il contratto li separa: piu' trigger\r\n possono invocare la stessa action, quindi \u00ABchi la chiama\u00BB non e' una sua proprieta'. Le\r\n action si aprono una alla volta: i cataloghi dipendono da tipo e nome, e tenerne N in volo\r\n sarebbe N volte la corsa fra risposte.\r\n -->\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Azioni della schermata</legend>\r\n <p class=\"fb-section__note\">\r\n Un campo che l\u2019utente compila puo\u2019 innescare un\u2019action i cui risultati finiscono negli\r\n <strong>altri campi di questa schermata</strong>: e\u2019 il caso \u00ABscrivi il codice fiscale e nome\r\n e cognome compaiono da soli\u00BB. L\u2019alternativa sarebbe spezzare la schermata in due con un\r\n elemento Action in mezzo.\r\n </p>\r\n\r\n <div class=\"fb-list\">\r\n @for (action of screenActions(); track $index) {\r\n <div class=\"fb-list__item\">\r\n <div class=\"fb-list__header\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost\"\r\n [attr.aria-expanded]=\"selectedActionIndex() === $index\"\r\n (click)=\"selectAction(selectedActionIndex() === $index ? null : $index)\"\r\n >\r\n {{ selectedActionIndex() === $index ? '\u25BE' : '\u25B8' }}\r\n {{ action.name || '(senza nome)' }}\r\n </button>\r\n <span class=\"fb-list__spacer\"></span>\r\n <span class=\"fb-scr__side-hint\">{{ action.actionName || 'action non scelta' }}</span>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n aria-label=\"Rimuovi l\u2019action\"\r\n (click)=\"removeScreenAction($index)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n\r\n @if (actionLosesResult(action)) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Questa action viene invocata ma il suo risultato si perde: aggiungi un parametro di\r\n uscita, oppure accendi l\u2019output automatico (SCREEN_ACTION_WITHOUT_OUTPUTS).\r\n </p>\r\n }\r\n @if (actionHasNoTrigger(action)) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Nessun trigger la invoca: senza, non girera\u2019 mai.\r\n </p>\r\n }\r\n\r\n @if (selectedActionIndex() === $index) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Nome</label>\r\n <input\r\n class=\"fb-input fb-input--mono\"\r\n [value]=\"action.name || ''\"\r\n (change)=\"setActionName($index, $any($event.target).value)\"\r\n />\r\n @if (actionNameError(action); as error) {\r\n <p class=\"fb-field__error\">{{ error }}</p>\r\n }\r\n <p class=\"fb-field__hint\">\r\n E\u2019 il nome con cui la citano i trigger, e vive nello spazio dei nomi del flow. Con\r\n l\u2019output automatico e\u2019 anche la radice del riferimento:\r\n <code>{{ action.name || 'Cerca' }}.NomeOutput</code>.\r\n </p>\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Tipo di action</label>\r\n <fb-name-picker\r\n [value]=\"action.actionType\"\r\n [options]=\"actionTypeOptions()\"\r\n label=\"Tipo di action\"\r\n placeholder=\"Scrivi o scegli un tipo\"\r\n [isMono]=\"false\"\r\n unknownMessage=\"Questo tipo di action non e\u2019 fra quelli dichiarati dal sistema ospite.\"\r\n emptyMessage=\"Catalogo dei tipi di action non disponibile: puoi scrivere il nome a mano.\"\r\n (valueChange)=\"setActionType($index, $event ?? '')\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Action</label>\r\n <fb-name-picker\r\n [value]=\"action.actionName\"\r\n [options]=\"actionOptions()\"\r\n label=\"Action\"\r\n placeholder=\"Scrivi o scegli un\u2019action\"\r\n [isMono]=\"false\"\r\n unknownMessage=\"Questa action non esiste nel catalogo del tipo scelto: e\u2019 ACTION_UNKNOWN.\"\r\n unknownSeverity=\"error\"\r\n emptyMessage=\"Scegli prima il tipo di action, oppure scrivi il nome a mano.\"\r\n (valueChange)=\"setActionTarget($index, $event ?? '')\"\r\n />\r\n @if (!action.actionName) {\r\n <p class=\"fb-field__error\">Obbligatoria: senza, ACTION_NAME_MISSING.</p>\r\n }\r\n </div>\r\n\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"action.storeOutputAutomatically === true\"\r\n (change)=\"setActionStoreOutput($index, $any($event.target).checked)\"\r\n />\r\n Output automatico\r\n </label>\r\n <p class=\"fb-field__hint\">\r\n L\u2019output automatico rende il risultato leggibile come\r\n <code>{{ action.name || 'Cerca' }}.NomeOutput</code>, ma\r\n <strong>non scrive in nessun campo</strong>: e\u2019 la forma da usare quando il risultato\r\n serve a una condizione, non a precompilare.\r\n </p>\r\n\r\n <!--\r\n L'unico posto in cui un campo di schermata e' una destinazione (\u00A75.2):\r\n `POST /flows/references/writable` continua a escluderlo, e un Assignment che ci\r\n scrive resta TARGET_NOT_WRITABLE.\r\n -->\r\n <fb-parameter-editor\r\n [holder]=\"action\"\r\n [catalogParameters]=\"actionParameterCatalog()\"\r\n [showOutputs]=\"action.storeOutputAutomatically !== true\"\r\n outputTitle=\"Campi da riempire\"\r\n [extraTargets]=\"screenFieldTargets()\"\r\n [outputsDisabledReason]=\"\r\n action.storeOutputAutomatically === true\r\n ? 'Con l\u2019output automatico il risultato non viene scritto nei campi.'\r\n : null\r\n \"\r\n (changed)=\"onActionParametersChanged($index, $event)\"\r\n />\r\n }\r\n </div>\r\n } @empty {\r\n <p class=\"fb-empty\">Nessuna action: i campi di questa schermata li compila solo l\u2019utente.</p>\r\n }\r\n </div>\r\n\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"addScreenAction()\">Aggiungi action</button>\r\n </fieldset>\r\n\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Trigger</legend>\r\n <p class=\"fb-section__note\">\r\n Quale campo invoca quale action. Senza condizioni l\u2019action gira a ogni cambio del campo.\r\n </p>\r\n\r\n <div class=\"fb-list\">\r\n @for (trigger of screenTriggers(); track $index) {\r\n <div class=\"fb-list__item\">\r\n <div class=\"fb-list__header\">\r\n <span class=\"fb-list__index\">{{ $index + 1 }}</span>\r\n <span class=\"fb-list__spacer\"></span>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n aria-label=\"Rimuovi il trigger\"\r\n (click)=\"removeScreenTrigger($index)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Action da invocare</label>\r\n <fb-name-picker\r\n [value]=\"trigger.screenActionName\"\r\n [options]=\"screenActionOptions()\"\r\n label=\"Action della schermata\"\r\n placeholder=\"Scegli un\u2019action\"\r\n unknownMessage=\"Questa schermata non dichiara un\u2019action con questo nome: e\u2019 SCREEN_ACTION_UNKNOWN.\"\r\n unknownSeverity=\"error\"\r\n emptyMessage=\"Aggiungi prima un\u2019action qui sopra.\"\r\n (valueChange)=\"setTriggerProperty($index, 'screenActionName', $event ?? '')\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Campo che la innesca</label>\r\n <!-- I campi di **questa** schermata, non i riferimenti in generale (\u00A75.2). -->\r\n <fb-name-picker\r\n [value]=\"trigger.triggerFieldName\"\r\n [options]=\"triggerFieldOptions()\"\r\n label=\"Campo della schermata\"\r\n placeholder=\"Scegli un campo\"\r\n unknownMessage=\"Questo non e\u2019 un campo di questa schermata: e\u2019 SCREEN_TRIGGER_FIELD_UNKNOWN.\"\r\n unknownSeverity=\"error\"\r\n emptyMessage=\"La schermata non ha ancora campi che raccolgono un valore.\"\r\n (valueChange)=\"setTriggerProperty($index, 'triggerFieldName', $event ?? '')\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">All\u2019arrivo sulla schermata</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"trigger.initBehavior || ''\"\r\n (change)=\"setTriggerProperty($index, 'initBehavior', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 non invocare \u2014</option>\r\n <option value=\"runOnLoad\">Invoca al caricamento (runOnLoad)</option>\r\n </select>\r\n @if (triggerInitIsUnknown(trigger)) {\r\n <p class=\"fb-field__error\">\r\n \u00AB{{ trigger.initBehavior }}\u00BB non e\u2019 ammesso: l\u2019unico valore e\u2019 <code>runOnLoad</code>\r\n (SCREEN_TRIGGER_INIT_BEHAVIOR_UNKNOWN).\r\n </p>\r\n }\r\n </div>\r\n\r\n @if (triggerHasNoCause(trigger)) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Senza un campo che la innesca e senza l\u2019invocazione al caricamento, questo trigger non\r\n scattera\u2019 mai (SCREEN_TRIGGER_WITHOUT_CAUSE).\r\n </p>\r\n }\r\n\r\n <fb-condition-editor\r\n [holder]=\"triggerConditions(trigger)\"\r\n title=\"Invoca l\u2019action quando\"\r\n [allowFormula]=\"false\"\r\n [issuePath]=\"'triggers[' + $index + ']'\"\r\n (changed)=\"onTriggerConditionsChanged($index, $event)\"\r\n />\r\n </div>\r\n } @empty {\r\n <p class=\"fb-empty\">Nessun trigger: le action dichiarate non verranno invocate.</p>\r\n }\r\n </div>\r\n\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"addScreenTrigger()\">Aggiungi trigger</button>\r\n </fieldset>\r\n } @else {\r\n <!-- ---------------------------------------------------- proprieta' del campo -->\r\n <header class=\"fb-scr__props-head\">\r\n <h3 class=\"fb-scr__props-title\">{{ captionOf(selectedField()!) }}</h3>\r\n <div class=\"fb-scr__props-actions\">\r\n <button type=\"button\" class=\"fb-btn fb-btn--sm\" title=\"Sposta su\" (click)=\"moveSelected(-1)\">\u2191</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--sm\" title=\"Sposta giu\u2019\" (click)=\"moveSelected(1)\">\u2193</button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--sm\"\r\n title=\"Porta fuori dal contenitore\"\r\n [disabled]=\"!canOutdent()\"\r\n (click)=\"outdentSelected()\"\r\n >\r\n \u21E4\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--sm\" title=\"Duplica\" (click)=\"duplicateSelected()\">\u29C9</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--danger fb-btn--sm\" title=\"Elimina\" (click)=\"removeSelected()\">\r\n \u00D7\r\n </button>\r\n </div>\r\n </header>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Tipo</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"selectedField()!.fieldType || ''\"\r\n (change)=\"setFieldType($any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 scegli \u2014</option>\r\n @for (entry of dictionary.screenFieldTypes(); track entry.value) {\r\n <option [value]=\"entry.value\">{{ entry.label }}</option>\r\n }\r\n </select>\r\n @if (!selectedField()!.fieldType) {\r\n <p class=\"fb-field__error\">Il tipo e\u2019 obbligatorio: senza, SCREEN_FIELD_TYPE_MISSING.</p>\r\n } @else if (isUnknownType(selectedField())) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n Questo tipo non e\u2019 nel dizionario: l\u2019editor non sa quali campi ammetta e mostra solo i comuni.\r\n </p>\r\n } @else if (selectedType()?.description) {\r\n <p class=\"fb-field__hint\">{{ selectedType()?.description }}</p>\r\n }\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Nome</label>\r\n <!-- Sul `change` e non sull\u2019`input`: la rinomina riscrive i riferimenti nel documento,\r\n e farlo a ogni tasto significherebbe riscriverlo per ogni lettera. -->\r\n <input\r\n class=\"fb-input fb-input--mono\"\r\n [value]=\"selectedField()!.name || ''\"\r\n (change)=\"setFieldName($any($event.target).value)\"\r\n />\r\n @if (selectedIsResource()) {\r\n <p class=\"fb-field__hint\">\r\n Questo campo e\u2019 una <strong>risorsa</strong>: lo referenzi come\r\n <code>{{ selectedField()!.name || 'Nome' }}</code> in condizioni, formule e parametri. \u00C8 di\r\n sola lettura per il flow \u2014 un Assignment che ci scrive e\u2019 TARGET_NOT_WRITABLE.\r\n </p>\r\n }\r\n </div>\r\n\r\n @if (!selectedType()?.isContainer && selectedField()!.fieldType !== 'ComponentInstance') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">\r\n {{ selectedField()!.fieldType === 'DisplayText' ? 'Testo' : 'Etichetta' }}\r\n </label>\r\n <textarea\r\n class=\"fb-textarea\"\r\n [value]=\"selectedField()!.fieldText || ''\"\r\n (input)=\"setFieldProperty('fieldText', $any($event.target).value)\"\r\n ></textarea>\r\n <p class=\"fb-field__hint\">Supporta i merge field <code>&#123;!Riferimento&#125;</code>.</p>\r\n </div>\r\n } @else if (selectedType()?.isContainer) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Intestazione</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"selectedField()!.fieldText || ''\"\r\n (input)=\"setFieldProperty('fieldText', $any($event.target).value)\"\r\n />\r\n </div>\r\n }\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Testo di aiuto</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"selectedField()!.helpText || ''\"\r\n (input)=\"setFieldProperty('helpText', $any($event.target).value)\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Larghezza</label>\r\n <div class=\"fb-scr__width\">\r\n <input\r\n type=\"range\"\r\n min=\"1\"\r\n max=\"12\"\r\n step=\"1\"\r\n [value]=\"widthOf(selectedField()!)\"\r\n (input)=\"setNumberProperty('width', $any($event.target).value)\"\r\n />\r\n <span class=\"fb-scr__width-value\">{{ widthOf(selectedField()!) }}/12</span>\r\n </div>\r\n <p class=\"fb-field__hint\">\r\n Colonne della griglia della schermata. \u00C8 un\u2019indicazione: il frontend puo\u2019 ignorarla.\r\n </p>\r\n </div>\r\n\r\n <!-- ------------------------------------------------ campi che raccolgono un valore -->\r\n @if (selectedType()?.storesValue) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Valore</legend>\r\n\r\n @if (selectedField()!.fieldType !== 'ObjectProvided') {\r\n <div class=\"fb-field\">\r\n <label\r\n class=\"fb-field__label\"\r\n [class.fb-field__label--required]=\"!!selectedType()?.requiresDataType\"\r\n >\r\n Tipo di dato\r\n </label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"selectedField()!.dataType || ''\"\r\n (change)=\"setFieldProperty('dataType', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 scegli \u2014</option>\r\n @for (entry of dictionary.dataTypes(); track entry.value) {\r\n <option [value]=\"entry.value\">{{ entry.label }}</option>\r\n }\r\n </select>\r\n @if (selectedType()?.requiresDataType && !selectedField()!.dataType) {\r\n <p class=\"fb-field__error\">Obbligatorio per questo tipo di campo (DATA_TYPE_MISSING).</p>\r\n }\r\n @if (selectedType()?.isCollection) {\r\n <p class=\"fb-field__hint\">\r\n Il valore raccolto e\u2019 una <strong>collection</strong>, non una stringa con i valori\r\n separati: nelle condizioni si usano gli operatori di collection.\r\n </p>\r\n }\r\n </div>\r\n\r\n @if (requiresObjectType()) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\" [class.fb-field__label--required]=\"objectTypeIsStructure()\">\r\n {{\r\n objectTypeIsStructure()\r\n ? 'Classe'\r\n : selectedField()!.dataType === 'Enum'\r\n ? 'Tipo di enumerazione'\r\n : 'Oggetto'\r\n }}\r\n </label>\r\n @if (selectedField()!.dataType === 'Enum') {\r\n <!-- Dizionario chiuso: si sceglie, non si scrive. -->\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"selectedField()!.objectType || ''\"\r\n (change)=\"setFieldProperty('objectType', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 scegli \u2014</option>\r\n @for (entry of enumOptions(); track entry.name) {\r\n <option [value]=\"entry.name\">{{ entry.label || entry.name }}</option>\r\n }\r\n </select>\r\n @if (!selectedField()!.objectType) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n Senza il tipo concreto l\u2019editor non puo\u2019 proporre i valori dell\u2019enumerazione.\r\n </p>\r\n }\r\n } @else if (objectTypeIsStructure()) {\r\n <fb-structure-picker\r\n [value]=\"selectedField()!.objectType\"\r\n label=\"Classe\"\r\n (valueChange)=\"setFieldProperty('objectType', $event ?? '')\"\r\n />\r\n @if (!selectedField()!.objectType) {\r\n <p class=\"fb-field__error\">\r\n La classe e\u2019 obbligatoria: senza, il runtime non ha nulla da istanziare\r\n (OBJECT_TYPE_MISSING).\r\n </p>\r\n }\r\n } @else {\r\n <fb-object-picker\r\n [value]=\"selectedField()!.objectType\"\r\n label=\"Oggetto\"\r\n (valueChange)=\"setFieldProperty('objectType', $event ?? '')\"\r\n />\r\n }\r\n </div>\r\n }\r\n } @else {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Campo dell\u2019entita\u2019</label>\r\n <p class=\"fb-field__hint\">\r\n Tipo, label, obbligatorieta\u2019, scala e \u2014 se il campo e\u2019 una picklist \u2014 le opzioni arrivano\r\n dallo schema dati: qui non si ridichiarano.\r\n </p>\r\n <fb-object-picker\r\n [value]=\"providedObject() || undefined\"\r\n label=\"Oggetto\"\r\n (valueChange)=\"setProvidedObject($event)\"\r\n />\r\n <fb-field-picker\r\n [value]=\"providedField() || undefined\"\r\n [object]=\"providedObject() || undefined\"\r\n label=\"Campo\"\r\n (valueChange)=\"setProvidedField($event)\"\r\n />\r\n </div>\r\n }\r\n\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"selectedIsRequired()\"\r\n (change)=\"setRequired($any($event.target).checked)\"\r\n />\r\n Obbligatorio\r\n </label>\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"selectedIsEditable()\"\r\n (change)=\"setEditable($any($event.target).checked)\"\r\n />\r\n Modificabile\r\n </label>\r\n @if (!selectedIsEditable()) {\r\n <p class=\"fb-field__hint\">\r\n A <code>false</code> il runtime <strong>ignora</strong> cio\u2019 che il client rimanda indietro:\r\n e\u2019 l\u2019unico modo di rendere un campo davvero di sola lettura.\r\n </p>\r\n }\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Valore di default</label>\r\n <fb-value-editor\r\n [value]=\"selectedField()!.defaultValue\"\r\n [dataType]=\"selectedField()!.dataType\"\r\n [objectType]=\"selectedField()!.objectType\"\r\n [isCollection]=\"selectedType()?.isCollection\"\r\n label=\"Valore di default\"\r\n (valueChange)=\"setDefaultValue($event)\"\r\n />\r\n </div>\r\n\r\n @if (scaleApplies()) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Decimali</label>\r\n <input\r\n class=\"fb-input\"\r\n type=\"number\"\r\n min=\"0\"\r\n [value]=\"selectedField()!.scale ?? ''\"\r\n (change)=\"setNumberProperty('scale', $any($event.target).value)\"\r\n />\r\n </div>\r\n } @else if (selectedField()!.scale !== undefined) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n <code>scale</code> su un campo non numerico e\u2019 SCALE_NOT_APPLICABLE.\r\n </p>\r\n }\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Lunghezza massima</label>\r\n <input\r\n class=\"fb-input\"\r\n type=\"number\"\r\n min=\"1\"\r\n [value]=\"selectedField()!.maxLength ?? ''\"\r\n (change)=\"setNumberProperty('maxLength', $any($event.target).value)\"\r\n />\r\n <p class=\"fb-field__hint\">Verificata anche dal runtime (TOO_LONG).</p>\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Tornando sulla schermata</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"selectedField()!.inputsOnNextNavToAssocScrn || ''\"\r\n (change)=\"setFieldProperty('inputsOnNextNavToAssocScrn', $any($event.target).value)\"\r\n >\r\n <option value=\"\">Predefinito (mantieni i valori)</option>\r\n @for (entry of dictionary.screenFieldInputsRevisited(); track entry.value) {\r\n <option [value]=\"entry.value\">{{ entry.label }}</option>\r\n }\r\n </select>\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Regola di validazione</label>\r\n <!--\r\n `commitOn=\"change\"`: qui mutare il documento ridisegna l'anteprima, e riscriverla a\r\n ogni tasto costa. La verifica parte lo stesso mentre si digita (\u00A76.3).\r\n -->\r\n <fb-formula-editor\r\n [expression]=\"selectedField()!.validationRule?.formulaExpression || ''\"\r\n usage=\"ValidationRule\"\r\n expectedDataType=\"Boolean\"\r\n commitOn=\"change\"\r\n [rows]=\"2\"\r\n placeholder=\"Espressione, es. LEN(Nome) > 3\"\r\n ariaLabel=\"Regola di validazione\"\r\n (expressionChange)=\"setValidationRule('formulaExpression', $event)\"\r\n />\r\n <input\r\n class=\"fb-input\"\r\n placeholder=\"Messaggio mostrato quando l\u2019espressione e\u2019 falsa\"\r\n [value]=\"selectedField()!.validationRule?.errorMessage || ''\"\r\n (change)=\"setValidationRule('errorMessage', $any($event.target).value)\"\r\n />\r\n <p class=\"fb-field__hint\">\r\n L\u2019espressione la valuta il motore di regole: il backend non la interpreta, la fa\r\n verificare al motore.\r\n </p>\r\n </div>\r\n </fieldset>\r\n }\r\n\r\n <!-- --------------------------------------------------------------- choice -->\r\n @if (selectedType()?.acceptsChoices) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Opzioni</legend>\r\n <p class=\"fb-section__note\">\r\n Accetta <strong>Choice</strong> e <strong>Dynamic choice set</strong>, nell\u2019ordine in cui le\r\n opzioni compaiono. Puntare a una variabile e\u2019 SCREEN_FIELD_CHOICE_UNKNOWN.\r\n </p>\r\n\r\n @if (!selectedField()!.choiceReferences?.length) {\r\n @if (selectedField()!.fieldType === 'ObjectProvided') {\r\n <!-- Le opzioni di una picklist arrivano dallo schema: qui si aggiungono solo se\r\n si vuole sostituirle, e non dichiararne nessuna e' il caso normale. -->\r\n <p class=\"fb-field__hint\">\r\n Se il campo dello schema e\u2019 una picklist, le opzioni arrivano da l\u00EC: dichiararle qui\r\n serve solo a sostituirle.\r\n </p>\r\n } @else {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Nessuna opzione: il campo non ha niente da mostrare (SCREEN_FIELD_CHOICES_MISSING).\r\n </p>\r\n }\r\n }\r\n\r\n <div class=\"fb-list\">\r\n @for (choice of selectedField()!.choiceReferences || []; track $index) {\r\n <div class=\"fb-list__item\">\r\n <div class=\"fb-list__header\">\r\n <span class=\"fb-list__index\">{{ $index + 1 }}</span>\r\n <span class=\"fb-list__title\">{{ choice }}</span>\r\n <span class=\"fb-list__spacer\"></span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--sm\" (click)=\"moveChoice($index, -1)\">\u2191</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--sm\" (click)=\"moveChoice($index, 1)\">\u2193</button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--sm\"\r\n aria-label=\"Rimuovi\"\r\n (click)=\"removeChoice($index)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Aggiungi un\u2019opzione</label>\r\n <fb-name-picker\r\n [options]=\"choiceOptions()\"\r\n label=\"Choice\"\r\n placeholder=\"Scegli una choice o un choice set\"\r\n unknownMessage=\"Questo nome non e\u2019 una choice ne\u2019 un choice set: e\u2019 SCREEN_FIELD_CHOICE_UNKNOWN.\"\r\n unknownSeverity=\"error\"\r\n emptyMessage=\"Il flow non dichiara nessuna choice: creale nel pannello delle risorse.\"\r\n (valueChange)=\"addChoice($event)\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Opzione selezionata all\u2019apertura</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"selectedField()!.defaultSelectedChoiceReference || ''\"\r\n (change)=\"setFieldProperty('defaultSelectedChoiceReference', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 nessuna \u2014</option>\r\n @for (choice of selectedField()!.choiceReferences || []; track $index) {\r\n <option [value]=\"choice\">{{ choice }}</option>\r\n }\r\n </select>\r\n @if (defaultChoiceIsForeign()) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n \u00AB{{ selectedField()!.defaultSelectedChoiceReference }}\u00BB non e\u2019 fra le opzioni elencate qui\r\n sopra.\r\n </p>\r\n }\r\n </div>\r\n </fieldset>\r\n }\r\n\r\n <!-- ------------------------------------------------------- contenitori -->\r\n @if (selectedType()?.isContainer) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Contenitore</legend>\r\n @if (selectedField()!.fieldType === 'RegionContainer') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Tipo di sezione</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"selectedField()!.regionContainerType || ''\"\r\n (change)=\"setFieldProperty('regionContainerType', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 predefinito \u2014</option>\r\n @for (entry of dictionary.regionContainerTypes(); track entry.value) {\r\n <option [value]=\"entry.value\">{{ entry.label }}</option>\r\n }\r\n </select>\r\n </div>\r\n }\r\n @if (!selectedField()!.fields?.length) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Il contenitore e\u2019 vuoto: non produce niente sulla schermata (SCREEN_CONTAINER_EMPTY).\r\n </p>\r\n }\r\n </fieldset>\r\n }\r\n\r\n <!-- --------------------------------------------------- ComponentInstance -->\r\n @if (selectedField()!.fieldType === 'ComponentInstance') {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Componente</legend>\r\n <p class=\"fb-section__note\">\r\n Componenti e form sono la stessa domanda al frontend \u2014 cosa sa rendere, e con quali parametri\r\n \u2014 e passano dallo stesso catalogo. Un <code>ComponentInstance</code> non ha un valore proprio:\r\n lo hanno i suoi output.\r\n </p>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Componente</label>\r\n <fb-name-picker\r\n [value]=\"selectedField()!.extensionName\"\r\n [options]=\"componentOptions()\"\r\n [unusableOptions]=\"unusableComponents()\"\r\n label=\"Componente\"\r\n placeholder=\"Scrivi o scegli un componente\"\r\n [isMono]=\"false\"\r\n unknownMessage=\"Questo componente non esiste nel catalogo: e\u2019 SCREEN_COMPONENT_UNKNOWN.\"\r\n unknownSeverity=\"error\"\r\n unusableMessage=\"Questo nome e\u2019 una schermata intera, non un componente montabile qui (FORM_KIND_MISMATCH).\"\r\n emptyMessage=\"Il catalogo dei componenti non e\u2019 popolato: il nome non viene verificato.\"\r\n (valueChange)=\"setFieldProperty('extensionName', $event ?? '')\"\r\n />\r\n @if (!selectedField()!.extensionName) {\r\n <p class=\"fb-field__error\">Obbligatorio: senza, SCREEN_COMPONENT_MISSING.</p>\r\n }\r\n </div>\r\n\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"selectedField()!.storeOutputAutomatically === true\"\r\n (change)=\"setStoreOutputAutomatically($any($event.target).checked)\"\r\n />\r\n Rendi gli output referenziabili automaticamente\r\n </label>\r\n @if (selectedField()!.storeOutputAutomatically) {\r\n <p class=\"fb-field__hint\">\r\n Gli output si referenziano come\r\n <code>{{ selectedField()!.name || 'Campo' }}.nomeOutput</code>, senza dichiarare variabili.\r\n </p>\r\n }\r\n @if (hasOutputConflict()) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n Output automatici <strong>e</strong> parametri di uscita insieme:\r\n OUTPUT_CONFIGURATION_CONFLICT.\r\n </p>\r\n }\r\n\r\n <fb-parameter-editor\r\n [holder]=\"$any(selectedField())\"\r\n [catalogParameters]=\"componentParameterList()\"\r\n inputTitle=\"Valori passati al componente\"\r\n outputTitle=\"Valori raccolti dal componente\"\r\n [showOutputs]=\"!selectedField()!.storeOutputAutomatically\"\r\n outputsDisabledReason=\"Gli output sono automatici: disattivalo per assegnarli a variabili.\"\r\n (changed)=\"onComponentParametersChanged($event)\"\r\n />\r\n </fieldset>\r\n }\r\n\r\n <!-- ------------------------------------------------------- visibilita' -->\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Visibilita\u2019</legend>\r\n <p class=\"fb-section__note\">\r\n Le regole si rivalutano <strong>sui valori appena inviati</strong>: e\u2019 cos\u00EC che un campo compare\r\n in funzione di un altro campo della stessa schermata. Un campo risultato nascosto viene\r\n <strong>azzerato</strong> e non viene validato.\r\n </p>\r\n <fb-condition-editor\r\n [holder]=\"visibilityRule()\"\r\n title=\"Mostra il campo quando\"\r\n [allowFormula]=\"false\"\r\n [issuePath]=\"'fields[' + (selectedField()!.name || '') + '].visibilityRule'\"\r\n (changed)=\"onVisibilityChanged($event)\"\r\n />\r\n </fieldset>\r\n }\r\n </aside>\r\n</div>\r\n\r\n<fb-connector-editor\r\n [nodeName]=\"name()\"\r\n [node]=\"node()\"\r\n [outlets]=\"outlets()\"\r\n (connectorChanged)=\"onConnectorChanged($event)\"\r\n/>\r\n\r\n<!--\r\n ============================================================== l'anteprima\r\n Due template ricorsivi. `listTpl` disegna i figli di un contenitore, e nient'altro: `data-item`\r\n marca cio' che conta come fratello nel calcolo del punto di rilascio, e il segnaposto del\r\n contenitore vuoto ne resta fuori. La barra di inserimento non e' qui \u2014 sta sopra l'anteprima,\r\n vedi il velo nel frame.\r\n-->\r\n<ng-template #listTpl let-fields let-parent=\"parent\" let-axis=\"axis\">\r\n @for (child of fields; track $index) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"fieldTpl\"\r\n [ngTemplateOutletContext]=\"{ $implicit: child, path: childPath(parent, $index), axis: axis }\"\r\n />\r\n }\r\n @if (!fields.length) {\r\n <p class=\"fb-scr__drop-hint\">Trascina qui un componente</p>\r\n }\r\n</ng-template>\r\n\r\n<ng-template #fieldTpl let-field let-path=\"path\" let-axis=\"axis\">\r\n <div\r\n class=\"fb-scr__item\"\r\n data-item=\"\"\r\n cdkDrag\r\n [style.grid-column]=\"'span ' + widthOf(field)\"\r\n [class.fb-scr__item--selected]=\"isSelected(path)\"\r\n [class.fb-scr__item--dragging]=\"isDragging(path)\"\r\n [class.fb-scr__item--container]=\"isContainer(field)\"\r\n (cdkDragStarted)=\"onFieldDragStarted(path)\"\r\n (cdkDragMoved)=\"onFieldDragMoved($event)\"\r\n (cdkDragEnded)=\"onFieldDragEnded($event, path)\"\r\n (click)=\"select(path); $event.stopPropagation()\"\r\n >\r\n <span class=\"fb-scr__grip\" cdkDragHandle title=\"Trascina per spostare\" aria-hidden=\"true\">\u283F</span>\r\n @if (field.visibilityRule?.conditions?.length) {\r\n <span class=\"fb-scr__flag\" title=\"Ha una regola di visibilita\u2019\">\u25D0</span>\r\n }\r\n\r\n @switch (field.fieldType) {\r\n @case ('RegionContainer') {\r\n <div class=\"fb-scr__section\">\r\n @if (field.regionContainerType !== 'SectionWithoutHeader') {\r\n <header class=\"fb-scr__section-head\">{{ field.fieldText || field.name }}</header>\r\n }\r\n <div class=\"fb-scr__cols\" [attr.data-drop]=\"dropId(path)\" data-axis=\"row\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"listTpl\"\r\n [ngTemplateOutletContext]=\"{ $implicit: field.fields || [], parent: path, axis: 'row' }\"\r\n />\r\n </div>\r\n </div>\r\n }\r\n @case ('Region') {\r\n <div class=\"fb-scr__region\">\r\n <span class=\"fb-scr__region-tag\">{{ field.name }} \u00B7 {{ widthOf(field) }}/12</span>\r\n <div class=\"fb-scr__region-body\" [attr.data-drop]=\"dropId(path)\" data-axis=\"column\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"listTpl\"\r\n [ngTemplateOutletContext]=\"{ $implicit: field.fields || [], parent: path, axis: 'column' }\"\r\n />\r\n </div>\r\n </div>\r\n }\r\n @case ('DisplayText') {\r\n <p class=\"fb-scr__display\">{{ field.fieldText || '(testo vuoto)' }}</p>\r\n }\r\n @case ('LargeTextArea') {\r\n <label class=\"fb-scr__label\">\r\n {{ captionOf(field) }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__control fb-scr__control--area\">{{ placeholderOf(field) }}</div>\r\n }\r\n @case ('PasswordField') {\r\n <label class=\"fb-scr__label\">\r\n {{ captionOf(field) }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__control\">\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022</div>\r\n }\r\n @case ('DropdownBox') {\r\n <label class=\"fb-scr__label\">\r\n {{ captionOf(field) }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__control fb-scr__control--select\">\r\n <span>{{ field.defaultSelectedChoiceReference || choiceLabelsOf(field)[0] || '\u2014 scegli \u2014' }}</span>\r\n <span aria-hidden=\"true\">\u25BE</span>\r\n </div>\r\n }\r\n @case ('MultiSelectPicklist') {\r\n <label class=\"fb-scr__label\">\r\n {{ captionOf(field) }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__control fb-scr__control--select\">\r\n <span>{{ choiceLabelsOf(field).join(', ') || '\u2014 scegli \u2014' }}</span>\r\n <span aria-hidden=\"true\">\u2261</span>\r\n </div>\r\n }\r\n @case ('RadioButtons') {\r\n <label class=\"fb-scr__label\">\r\n {{ captionOf(field) }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__options\">\r\n @for (option of choiceLabelsOf(field); track $index) {\r\n <span class=\"fb-scr__option\">\u25EF {{ option }}</span>\r\n }\r\n @if (!choiceLabelsOf(field).length) {\r\n <span class=\"fb-scr__option fb-scr__option--missing\">Nessuna opzione</span>\r\n }\r\n </div>\r\n }\r\n @case ('MultiSelectCheckboxes') {\r\n <label class=\"fb-scr__label\">\r\n {{ captionOf(field) }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__options\">\r\n @for (option of choiceLabelsOf(field); track $index) {\r\n <span class=\"fb-scr__option\">\u2610 {{ option }}</span>\r\n }\r\n @if (!choiceLabelsOf(field).length) {\r\n <span class=\"fb-scr__option fb-scr__option--missing\">Nessuna opzione</span>\r\n }\r\n </div>\r\n }\r\n @case ('ComponentInstance') {\r\n <div class=\"fb-scr__component\">\r\n <span aria-hidden=\"true\">\u2B21</span>\r\n {{ field.extensionName || 'Componente non indicato' }}\r\n </div>\r\n }\r\n @case ('ObjectProvided') {\r\n <label class=\"fb-scr__label\">\r\n {{ field.fieldText || field.objectFieldReference || field.name }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__control\">{{ placeholderOf(field) }}</div>\r\n <span class=\"fb-scr__tag\">{{ field.objectFieldReference || 'campo non indicato' }}</span>\r\n }\r\n @default {\r\n <label class=\"fb-scr__label\">\r\n {{ captionOf(field) }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__control\">{{ placeholderOf(field) }}</div>\r\n @if (isUnknownType(field)) {\r\n <span class=\"fb-scr__tag fb-scr__tag--warn\">{{ field.fieldType }}: tipo non nel dizionario</span>\r\n }\r\n }\r\n }\r\n </div>\r\n</ng-template>\r\n", styles: [".fb-scr{display:grid;grid-template-columns:220px minmax(0,1fr) 340px;gap:12px;align-items:start;margin-bottom:14px}@media(max-width:1100px){.fb-scr{grid-template-columns:minmax(0,1fr)}}.fb-scr__side,.fb-scr__props{min-width:0;padding:10px;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius, 10px);background:var(--fb-surface-alt, #f7f8fa)}.fb-scr__tabs{display:flex;gap:4px;margin-bottom:8px}.fb-scr__tab{flex:1;padding:5px 8px;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-xs, 6px);background:var(--fb-surface, #fff);color:var(--fb-text-muted, #6b7086);font:inherit;font-size:11px;font-weight:600;cursor:pointer}.fb-scr__tab--active{border-color:var(--fb-accent, #4f6ef7);background:color-mix(in srgb,var(--fb-accent, #4f6ef7) 10%,transparent);color:var(--fb-accent-strong, #3d59e0)}.fb-scr__side-hint,.fb-scr__side-empty{margin:6px 0;font-size:11px;color:var(--fb-text-muted, #6b7086)}.fb-scr__palette{display:flex;flex-direction:column;gap:4px;margin:8px 0 0;padding:0;max-height:320px;overflow-y:auto;overscroll-behavior:contain;list-style:none}.fb-scr__chip{display:flex;align-items:center;gap:8px;width:100%;padding:6px 8px;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-xs, 6px);background:var(--fb-surface, #fff);color:var(--fb-text, #1a1c23);font:inherit;font-size:12px;text-align:left;cursor:grab}.fb-scr__chip:hover{border-color:var(--fb-accent, #4f6ef7)}.fb-scr__chip-icon{flex:none;width:20px;text-align:center;color:var(--fb-text-muted, #6b7086)}.fb-scr__chip-label{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-scr__chip-label small{margin-left:4px;color:var(--fb-text-subtle, #98a2b3);font-size:10px}.fb-scr__canvas{min-width:0;padding:14px;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius, 10px);background:var(--fb-canvas-bg, #f4f5f7)}.fb-scr__frame{position:relative;display:flex;flex-direction:column;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-sm, 8px);background:var(--fb-surface, #fff);box-shadow:var(--fb-shadow-sm, 0 1px 2px rgb(16 24 40 / 6%));overflow:hidden}.fb-scr__frame-head{display:flex;align-items:center;gap:6px;padding:8px 12px;border-bottom:1px solid var(--fb-border-subtle, #eef0f4);font-size:13px;font-weight:600}.fb-scr__help{display:inline-flex;align-items:center;justify-content:center;width:15px;height:15px;border-radius:50%;background:var(--fb-surface-sunken, #eef0f4);color:var(--fb-text-muted, #6b7086);font-size:10px}.fb-scr__body,.fb-scr__region-body{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));align-content:start;gap:8px 0;padding:12px;min-height:90px}.fb-scr__region-body{padding:8px;min-height:60px}.fb-scr__cols{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));align-items:stretch;gap:0;padding:8px;min-height:60px}.fb-scr__cols>.fb-scr__item{display:flex;align-self:stretch}.fb-scr__cols>.fb-scr__item>.fb-scr__region{flex:1;min-width:0}.fb-scr__drop-hint{grid-column:1 / -1;margin:0;padding:10px;border:1px dashed var(--fb-border-strong, #cfd4de);border-radius:var(--fb-radius-xs, 6px);color:var(--fb-text-subtle, #98a2b3);font-size:11px;text-align:center}.fb-scr__marker{position:absolute;z-index:2;border-radius:2px;background:var(--fb-accent, #4f6ef7);pointer-events:none}.fb-scr__item{position:relative;box-sizing:border-box;padding:6px 8px;border:1px solid transparent;border-radius:var(--fb-radius-xs, 6px);cursor:pointer}.fb-scr__item:hover{border-color:var(--fb-border-strong, #cfd4de);background:color-mix(in srgb,var(--fb-accent, #4f6ef7) 4%,transparent)}.fb-scr__item--selected{border-color:var(--fb-accent, #4f6ef7);box-shadow:inset 0 0 0 1px var(--fb-accent, #4f6ef7)}.fb-scr__item--dragging{opacity:.45;pointer-events:none}.fb-scr__grip{position:absolute;top:2px;left:-2px;padding:0 3px;color:var(--fb-text-subtle, #98a2b3);font-size:11px;line-height:1;opacity:0;cursor:grab}.fb-scr__item:hover>.fb-scr__grip,.fb-scr__item--selected>.fb-scr__grip{opacity:1}.fb-scr__flag{position:absolute;top:2px;right:4px;color:var(--fb-text-muted, #6b7086);font-size:11px}.fb-scr__label{display:block;margin-bottom:3px;font-size:11px;font-weight:600;color:var(--fb-text, #1a1c23)}.fb-scr__req{color:var(--fb-error, #dc2626)}.fb-scr__control{display:flex;align-items:center;justify-content:space-between;gap:6px;min-height:26px;padding:4px 8px;border:1px solid var(--fb-border-strong, #cfd4de);border-radius:var(--fb-radius-xs, 6px);background:var(--fb-surface, #fff);color:var(--fb-text-subtle, #98a2b3);font-size:12px;pointer-events:none}.fb-scr__control--area{min-height:54px;align-items:flex-start}.fb-scr__display{margin:0;font-size:12px;color:var(--fb-text, #1a1c23)}.fb-scr__options{display:flex;flex-direction:column;gap:2px;pointer-events:none}.fb-scr__option{font-size:12px;color:var(--fb-text-muted, #6b7086)}.fb-scr__option--missing{color:var(--fb-warning, #b7791f)}.fb-scr__component{display:flex;align-items:center;gap:6px;padding:10px;border:1px dashed var(--fb-accent, #4f6ef7);border-radius:var(--fb-radius-xs, 6px);background:color-mix(in srgb,var(--fb-accent, #4f6ef7) 5%,transparent);font-size:12px;color:var(--fb-text-muted, #6b7086)}.fb-scr__tag{display:inline-block;margin-top:3px;font-size:10px;color:var(--fb-text-subtle, #98a2b3)}.fb-scr__tag--warn{color:var(--fb-warning, #b7791f)}.fb-scr__section{border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-xs, 6px);background:var(--fb-surface-alt, #f7f8fa)}.fb-scr__section-head{padding:5px 10px;border-bottom:1px solid var(--fb-border-subtle, #eef0f4);font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--fb-text-muted, #6b7086)}.fb-scr__region{border:1px dashed var(--fb-border-strong, #cfd4de);border-radius:var(--fb-radius-xs, 6px);background:var(--fb-surface, #fff)}.fb-scr__region-tag{display:block;padding:3px 6px 0;font-size:10px;color:var(--fb-text-subtle, #98a2b3)}.fb-scr__frame-foot{display:flex;align-items:center;gap:6px;padding:8px 12px;border-top:1px solid var(--fb-border-subtle, #eef0f4);background:var(--fb-surface-alt, #f7f8fa)}.fb-scr__spacer{flex:1}.fb-scr__btn{padding:3px 10px;border:1px solid var(--fb-border-strong, #cfd4de);border-radius:var(--fb-radius-xs, 6px);font-size:11px;color:var(--fb-text-muted, #6b7086)}.fb-scr__btn--primary{border-color:var(--fb-accent, #4f6ef7);background:var(--fb-accent, #4f6ef7);color:var(--fb-accent-contrast, #fff)}.fb-scr__canvas-note{margin-top:10px}.fb-scr__props{background:var(--fb-surface, #fff)}.fb-scr__props-head{display:flex;align-items:center;gap:8px;margin-bottom:8px}.fb-scr__props-title{flex:1;min-width:0;margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--fb-text-muted, #6b7086)}.fb-scr__props-actions{display:flex;flex-wrap:wrap;gap:3px}.fb-btn--sm{padding:3px 7px;font-size:11px}.fb-scr__width{display:flex;align-items:center;gap:8px}.fb-scr__width input[type=range]{flex:1;min-width:0}.fb-scr__width-value{font-size:11px;color:var(--fb-text-muted, #6b7086)}\n"] }]
10096
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"fb-scr\">\r\n <!-- ============================================================ sinistra -->\r\n <aside class=\"fb-scr__side\" aria-label=\"Componenti e campi\">\r\n <div class=\"fb-scr__tabs\" role=\"tablist\">\r\n <button\r\n type=\"button\"\r\n role=\"tab\"\r\n class=\"fb-scr__tab\"\r\n [class.fb-scr__tab--active]=\"paletteTab() === 'components'\"\r\n [attr.aria-selected]=\"paletteTab() === 'components'\"\r\n (click)=\"setPaletteTab('components')\"\r\n >\r\n Componenti\r\n </button>\r\n <button\r\n type=\"button\"\r\n role=\"tab\"\r\n class=\"fb-scr__tab\"\r\n [class.fb-scr__tab--active]=\"paletteTab() === 'fields'\"\r\n [attr.aria-selected]=\"paletteTab() === 'fields'\"\r\n (click)=\"setPaletteTab('fields')\"\r\n >\r\n Campi\r\n </button>\r\n </div>\r\n\r\n @if (paletteTab() === 'components') {\r\n @if (!dictionary.screenFieldTypes().length) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Il dizionario <code>screenFieldTypes</code> non e\u2019 disponibile: senza i suoi flag l\u2019editor non\r\n sa quali campi ammette ciascun tipo, e non li inventa.\r\n </p>\r\n }\r\n <p class=\"fb-scr__side-hint\">Trascina sulla schermata, oppure clicca per aggiungere.</p>\r\n <ul class=\"fb-scr__palette\">\r\n @for (entry of dictionary.screenFieldTypes(); track entry.value) {\r\n <li>\r\n <button\r\n type=\"button\"\r\n class=\"fb-scr__chip\"\r\n cdkDrag\r\n [title]=\"entry.description || entry.label\"\r\n (cdkDragMoved)=\"onPaletteDragMoved($event)\"\r\n (cdkDragEnded)=\"onComponentDragEnded($event, entry.value)\"\r\n (click)=\"addComponentAtSelection(entry.value)\"\r\n >\r\n <span class=\"fb-scr__chip-icon\" aria-hidden=\"true\">{{ iconOf(entry.value) }}</span>\r\n <span class=\"fb-scr__chip-label\">{{ entry.label }}</span>\r\n </button>\r\n </li>\r\n }\r\n </ul>\r\n } @else {\r\n <p class=\"fb-scr__side-hint\">\r\n Un campo dell\u2019entita\u2019 porta con se\u2019 tipo, etichetta, obbligatorieta\u2019 e opzioni: arrivano dallo\r\n schema dati, qui non si ridichiarano.\r\n </p>\r\n <!-- Il catalogo non e' un dizionario chiuso: si sceglie o si scrive, come ovunque si\r\n indichi un'entita' (vedi `object-picker`). -->\r\n <fb-object-picker\r\n [value]=\"fieldsObject()\"\r\n label=\"Entita\u2019\"\r\n placeholder=\"Scrivi o scegli un\u2019entita\u2019\"\r\n (valueChange)=\"setFieldsObject($event)\"\r\n />\r\n @if (fieldsObject() && !fieldsOfObject().length) {\r\n <p class=\"fb-scr__side-empty\">\r\n Nessun campo dichiarato per questa entita\u2019: il catalogo non li espone, non significa che non\r\n ci siano.\r\n </p>\r\n }\r\n <ul class=\"fb-scr__palette\">\r\n @for (field of fieldsOfObject(); track $index) {\r\n <li>\r\n <button\r\n type=\"button\"\r\n class=\"fb-scr__chip\"\r\n cdkDrag\r\n [title]=\"field.name + (field.dataType ? ' \u00B7 ' + field.dataType : '')\"\r\n (cdkDragMoved)=\"onPaletteDragMoved($event)\"\r\n (cdkDragEnded)=\"onObjectFieldDragEnded($event, field)\"\r\n (click)=\"addObjectFieldAtSelection(field)\"\r\n >\r\n <span class=\"fb-scr__chip-icon\" aria-hidden=\"true\">\u2317</span>\r\n <span class=\"fb-scr__chip-label\">\r\n {{ field.label || field.name }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n <small>{{ field.dataType }}</small>\r\n </span>\r\n </button>\r\n </li>\r\n }\r\n </ul>\r\n }\r\n </aside>\r\n\r\n <!-- ============================================================== centro -->\r\n <!-- Cliccare fuori da un campo riporta a destra le proprieta' della schermata. -->\r\n <section class=\"fb-scr__canvas\" aria-label=\"Anteprima della schermata\" (click)=\"selectScreen()\">\r\n <div class=\"fb-scr__frame\">\r\n @if (showHeader()) {\r\n <header class=\"fb-scr__frame-head\">\r\n {{ $any(node()).label || name() }}\r\n @if (screen().helpText) {\r\n <span class=\"fb-scr__help\" [title]=\"screen().helpText || ''\">?</span>\r\n }\r\n </header>\r\n }\r\n\r\n <div\r\n class=\"fb-scr__body\"\r\n [attr.data-drop]=\"''\"\r\n data-axis=\"column\"\r\n [class.fb-scr__body--empty]=\"isEmpty()\"\r\n >\r\n <ng-container\r\n [ngTemplateOutlet]=\"listTpl\"\r\n [ngTemplateOutletContext]=\"{ $implicit: screen().fields || [], parent: [], axis: 'column' }\"\r\n />\r\n </div>\r\n\r\n <!--\r\n La barra di inserimento e' un **velo** sopra l'anteprima, non un figlio del contenitore di\r\n rilascio: dentro la griglia occupava una riga intera e spostava in basso proprio la sezione\r\n che si stava puntando, che allora usciva da sotto il puntatore e il bersaglio oscillava.\r\n -->\r\n @if (dropMarker(); as bar) {\r\n <div\r\n class=\"fb-scr__marker\"\r\n [style.left.px]=\"bar.left\"\r\n [style.top.px]=\"bar.top\"\r\n [style.width.px]=\"bar.width\"\r\n [style.height.px]=\"bar.height\"\r\n ></div>\r\n }\r\n\r\n @if (showFooter()) {\r\n <footer class=\"fb-scr__frame-foot\">\r\n @if (allowPause()) {\r\n <span class=\"fb-scr__btn fb-scr__btn--ghost\">{{ screen().pauseButtonLabel || 'Pausa' }}</span>\r\n }\r\n <span class=\"fb-scr__spacer\"></span>\r\n @if (allowBack()) {\r\n <span class=\"fb-scr__btn fb-scr__btn--ghost\">{{ screen().backButtonLabel || 'Indietro' }}</span>\r\n }\r\n <span class=\"fb-scr__btn fb-scr__btn--primary\">\r\n {{ screen().nextOrFinishButtonLabel || (allowFinish() ? 'Fine' : 'Avanti') }}\r\n </span>\r\n </footer>\r\n }\r\n </div>\r\n\r\n @if (isEmpty()) {\r\n <p class=\"fb-callout fb-callout--warn fb-scr__canvas-note\">\r\n La schermata non ha campi: non c\u2019e\u2019 niente da mostrare all\u2019utente (SCREEN_WITHOUT_FIELDS).\r\n </p>\r\n }\r\n </section>\r\n\r\n <!-- ============================================================== destra -->\r\n <aside class=\"fb-scr__props\" aria-label=\"Proprieta\u2019\">\r\n @if (!selectedField()) {\r\n <!-- ------------------------------------------------ proprieta' della schermata -->\r\n <header class=\"fb-scr__props-head\">\r\n <h3 class=\"fb-scr__props-title\">Schermata</h3>\r\n </header>\r\n <p class=\"fb-scr__side-hint\">Seleziona un campo nell\u2019anteprima per configurarlo.</p>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Testo di aiuto</label>\r\n <textarea\r\n class=\"fb-textarea\"\r\n [value]=\"screen().helpText || ''\"\r\n (input)=\"setScreenText('helpText', $any($event.target).value)\"\r\n ></textarea>\r\n </div>\r\n\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Navigazione</legend>\r\n <p class=\"fb-section__note\">\r\n Questi flag sono un\u2019intenzione, non la verita\u2019 finale: a runtime il motore comunica\r\n <code>canGoBack</code>, <code>canFinish</code> e <code>canPause</code> nella richiesta della\r\n schermata.\r\n </p>\r\n <label class=\"fb-check\">\r\n <input type=\"checkbox\" [checked]=\"allowBack()\" (change)=\"setScreenFlag('allowBack', $any($event.target).checked)\" />\r\n Consenti \u00ABindietro\u00BB\r\n </label>\r\n <label class=\"fb-check\">\r\n <input type=\"checkbox\" [checked]=\"allowFinish()\" (change)=\"setScreenFlag('allowFinish', $any($event.target).checked)\" />\r\n Consenti \u00ABfine\u00BB\r\n </label>\r\n <label class=\"fb-check\">\r\n <input type=\"checkbox\" [checked]=\"allowPause()\" (change)=\"setScreenFlag('allowPause', $any($event.target).checked)\" />\r\n Consenti \u00ABpausa\u00BB\r\n </label>\r\n <label class=\"fb-check\">\r\n <input type=\"checkbox\" [checked]=\"showHeader()\" (change)=\"setScreenFlag('showHeader', $any($event.target).checked)\" />\r\n Mostra l\u2019intestazione\r\n </label>\r\n <label class=\"fb-check\">\r\n <input type=\"checkbox\" [checked]=\"showFooter()\" (change)=\"setScreenFlag('showFooter', $any($event.target).checked)\" />\r\n Mostra il piede\r\n </label>\r\n\r\n @if (allowPause()) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Testo mostrato alla pausa</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"screen().pausedText || ''\"\r\n (input)=\"setScreenText('pausedText', $any($event.target).value)\"\r\n />\r\n </div>\r\n }\r\n\r\n @if (isDeadEnd()) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n Questa schermata non ha una destinazione e non consente \u00ABfine\u00BB: e\u2019 un vicolo cieco, e\r\n l\u2019utente resterebbe bloccato (SCREEN_DEAD_END).\r\n </p>\r\n }\r\n </fieldset>\r\n\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Pulsanti</legend>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Etichetta \u00ABindietro\u00BB</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"screen().backButtonLabel || ''\"\r\n (input)=\"setScreenText('backButtonLabel', $any($event.target).value)\"\r\n />\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Etichetta \u00ABavanti / fine\u00BB</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"screen().nextOrFinishButtonLabel || ''\"\r\n (input)=\"setScreenText('nextOrFinishButtonLabel', $any($event.target).value)\"\r\n />\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Etichetta \u00ABpausa\u00BB</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"screen().pauseButtonLabel || ''\"\r\n (input)=\"setScreenText('pauseButtonLabel', $any($event.target).value)\"\r\n />\r\n </div>\r\n </fieldset>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Stage mostrato nell\u2019avanzamento</label>\r\n <fb-name-picker\r\n [value]=\"screen().stageReference\"\r\n [options]=\"stageOptions()\"\r\n label=\"Stage\"\r\n placeholder=\"Scegli uno stage\"\r\n unknownMessage=\"Questo stage non e\u2019 dichiarato dal flow.\"\r\n emptyMessage=\"Il flow non dichiara stage: creali nel pannello delle risorse.\"\r\n (valueChange)=\"setScreenText('stageReference', $event ?? '')\"\r\n />\r\n </div>\r\n\r\n <!--\r\n Screen action (\u00A75.2). Due elenchi e non uno perche' il contratto li separa: piu' trigger\r\n possono invocare la stessa action, quindi \u00ABchi la chiama\u00BB non e' una sua proprieta'. Le\r\n action si aprono una alla volta: i cataloghi dipendono da tipo e nome, e tenerne N in volo\r\n sarebbe N volte la corsa fra risposte.\r\n -->\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Azioni della schermata</legend>\r\n <p class=\"fb-section__note\">\r\n Un campo che l\u2019utente compila puo\u2019 innescare un\u2019action i cui risultati finiscono negli\r\n <strong>altri campi di questa schermata</strong>: e\u2019 il caso \u00ABscrivi il codice fiscale e nome\r\n e cognome compaiono da soli\u00BB. L\u2019alternativa sarebbe spezzare la schermata in due con un\r\n elemento Action in mezzo.\r\n </p>\r\n\r\n <div class=\"fb-list\">\r\n @for (action of screenActions(); track $index) {\r\n <div class=\"fb-list__item\">\r\n <div class=\"fb-list__header\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost\"\r\n [attr.aria-expanded]=\"selectedActionIndex() === $index\"\r\n (click)=\"selectAction(selectedActionIndex() === $index ? null : $index)\"\r\n >\r\n {{ selectedActionIndex() === $index ? '\u25BE' : '\u25B8' }}\r\n {{ action.name || '(senza nome)' }}\r\n </button>\r\n <span class=\"fb-list__spacer\"></span>\r\n <span class=\"fb-scr__side-hint\">{{ action.actionName || 'action non scelta' }}</span>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n aria-label=\"Rimuovi l\u2019action\"\r\n (click)=\"removeScreenAction($index)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n\r\n @if (actionLosesResult(action)) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Questa action viene invocata ma il suo risultato si perde: aggiungi un parametro di\r\n uscita, oppure accendi l\u2019output automatico (SCREEN_ACTION_WITHOUT_OUTPUTS).\r\n </p>\r\n }\r\n @if (actionHasNoTrigger(action)) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Nessun trigger la invoca: senza, non girera\u2019 mai.\r\n </p>\r\n }\r\n\r\n @if (selectedActionIndex() === $index) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Nome</label>\r\n <input\r\n class=\"fb-input fb-input--mono\"\r\n [value]=\"action.name || ''\"\r\n (change)=\"setActionName($index, $any($event.target).value)\"\r\n />\r\n @if (actionNameError(action); as error) {\r\n <p class=\"fb-field__error\">{{ error }}</p>\r\n }\r\n <p class=\"fb-field__hint\">\r\n E\u2019 il nome con cui la citano i trigger, e vive nello spazio dei nomi del flow. Con\r\n l\u2019output automatico e\u2019 anche la radice del riferimento:\r\n <code>{{ action.name || 'Cerca' }}.NomeOutput</code>.\r\n </p>\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Tipo di action</label>\r\n <fb-name-picker\r\n [value]=\"action.actionType\"\r\n [options]=\"actionTypeOptions()\"\r\n label=\"Tipo di action\"\r\n placeholder=\"Scrivi o scegli un tipo\"\r\n [isMono]=\"false\"\r\n unknownMessage=\"Questo tipo di action non e\u2019 fra quelli dichiarati dal sistema ospite.\"\r\n emptyMessage=\"Catalogo dei tipi di action non disponibile: puoi scrivere il nome a mano.\"\r\n (valueChange)=\"setActionType($index, $event ?? '')\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Action</label>\r\n <fb-name-picker\r\n [value]=\"action.actionName\"\r\n [options]=\"actionOptions()\"\r\n label=\"Action\"\r\n placeholder=\"Scrivi o scegli un\u2019action\"\r\n [isMono]=\"false\"\r\n unknownMessage=\"Questa action non esiste nel catalogo del tipo scelto: e\u2019 ACTION_UNKNOWN.\"\r\n unknownSeverity=\"error\"\r\n emptyMessage=\"Scegli prima il tipo di action, oppure scrivi il nome a mano.\"\r\n (valueChange)=\"setActionTarget($index, $event ?? '')\"\r\n />\r\n @if (!action.actionName) {\r\n <p class=\"fb-field__error\">Obbligatoria: senza, ACTION_NAME_MISSING.</p>\r\n }\r\n </div>\r\n\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"action.storeOutputAutomatically === true\"\r\n (change)=\"setActionStoreOutput($index, $any($event.target).checked)\"\r\n />\r\n Output automatico\r\n </label>\r\n <p class=\"fb-field__hint\">\r\n L\u2019output automatico rende il risultato leggibile come\r\n <code>{{ action.name || 'Cerca' }}.NomeOutput</code>, ma\r\n <strong>non scrive in nessun campo</strong>: e\u2019 la forma da usare quando il risultato\r\n serve a una condizione, non a precompilare.\r\n </p>\r\n\r\n <!--\r\n L'unico posto in cui un campo di schermata e' una destinazione (\u00A75.2):\r\n `POST /flows/references/writable` continua a escluderlo, e un Assignment che ci\r\n scrive resta TARGET_NOT_WRITABLE.\r\n -->\r\n <fb-parameter-editor\r\n [holder]=\"action\"\r\n [catalogParameters]=\"actionParameterCatalog()\"\r\n [showOutputs]=\"action.storeOutputAutomatically !== true\"\r\n outputTitle=\"Campi da riempire\"\r\n [extraTargets]=\"screenFieldTargets()\"\r\n [outputsDisabledReason]=\"\r\n action.storeOutputAutomatically === true\r\n ? 'Con l\u2019output automatico il risultato non viene scritto nei campi.'\r\n : null\r\n \"\r\n (changed)=\"onActionParametersChanged($index, $event)\"\r\n />\r\n }\r\n </div>\r\n } @empty {\r\n <p class=\"fb-empty\">Nessuna action: i campi di questa schermata li compila solo l\u2019utente.</p>\r\n }\r\n </div>\r\n\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"addScreenAction()\">Aggiungi action</button>\r\n </fieldset>\r\n\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Trigger</legend>\r\n <p class=\"fb-section__note\">\r\n Quale campo invoca quale action. Senza condizioni l\u2019action gira a ogni cambio del campo.\r\n </p>\r\n\r\n <div class=\"fb-list\">\r\n @for (trigger of screenTriggers(); track $index) {\r\n <div class=\"fb-list__item\">\r\n <div class=\"fb-list__header\">\r\n <span class=\"fb-list__index\">{{ $index + 1 }}</span>\r\n <span class=\"fb-list__spacer\"></span>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n aria-label=\"Rimuovi il trigger\"\r\n (click)=\"removeScreenTrigger($index)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Action da invocare</label>\r\n <fb-name-picker\r\n [value]=\"trigger.screenActionName\"\r\n [options]=\"screenActionOptions()\"\r\n label=\"Action della schermata\"\r\n placeholder=\"Scegli un\u2019action\"\r\n unknownMessage=\"Questa schermata non dichiara un\u2019action con questo nome: e\u2019 SCREEN_ACTION_UNKNOWN.\"\r\n unknownSeverity=\"error\"\r\n emptyMessage=\"Aggiungi prima un\u2019action qui sopra.\"\r\n (valueChange)=\"setTriggerProperty($index, 'screenActionName', $event ?? '')\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Campo che la innesca</label>\r\n <!-- I campi di **questa** schermata, non i riferimenti in generale (\u00A75.2). -->\r\n <fb-name-picker\r\n [value]=\"trigger.triggerFieldName\"\r\n [options]=\"triggerFieldOptions()\"\r\n label=\"Campo della schermata\"\r\n placeholder=\"Scegli un campo\"\r\n unknownMessage=\"Questo non e\u2019 un campo di questa schermata: e\u2019 SCREEN_TRIGGER_FIELD_UNKNOWN.\"\r\n unknownSeverity=\"error\"\r\n emptyMessage=\"La schermata non ha ancora campi che raccolgono un valore.\"\r\n (valueChange)=\"setTriggerProperty($index, 'triggerFieldName', $event ?? '')\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">All\u2019arrivo sulla schermata</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"trigger.initBehavior || ''\"\r\n (change)=\"setTriggerProperty($index, 'initBehavior', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 non invocare \u2014</option>\r\n <option value=\"runOnLoad\">Invoca al caricamento (runOnLoad)</option>\r\n </select>\r\n @if (triggerInitIsUnknown(trigger)) {\r\n <p class=\"fb-field__error\">\r\n \u00AB{{ trigger.initBehavior }}\u00BB non e\u2019 ammesso: l\u2019unico valore e\u2019 <code>runOnLoad</code>\r\n (SCREEN_TRIGGER_INIT_BEHAVIOR_UNKNOWN).\r\n </p>\r\n }\r\n </div>\r\n\r\n @if (triggerHasNoCause(trigger)) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Senza un campo che la innesca e senza l\u2019invocazione al caricamento, questo trigger non\r\n scattera\u2019 mai (SCREEN_TRIGGER_WITHOUT_CAUSE).\r\n </p>\r\n }\r\n\r\n <fb-condition-editor\r\n [holder]=\"triggerConditions(trigger)\"\r\n title=\"Invoca l\u2019action quando\"\r\n [allowFormula]=\"false\"\r\n [issuePath]=\"'triggers[' + $index + ']'\"\r\n (changed)=\"onTriggerConditionsChanged($index, $event)\"\r\n />\r\n </div>\r\n } @empty {\r\n <p class=\"fb-empty\">Nessun trigger: le action dichiarate non verranno invocate.</p>\r\n }\r\n </div>\r\n\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"addScreenTrigger()\">Aggiungi trigger</button>\r\n </fieldset>\r\n } @else {\r\n <!-- ---------------------------------------------------- proprieta' del campo -->\r\n <header class=\"fb-scr__props-head\">\r\n <h3 class=\"fb-scr__props-title\">{{ captionOf(selectedField()!) }}</h3>\r\n <div class=\"fb-scr__props-actions\">\r\n <button type=\"button\" class=\"fb-btn fb-btn--sm\" title=\"Sposta su\" (click)=\"moveSelected(-1)\">\u2191</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--sm\" title=\"Sposta giu\u2019\" (click)=\"moveSelected(1)\">\u2193</button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--sm\"\r\n title=\"Porta fuori dal contenitore\"\r\n [disabled]=\"!canOutdent()\"\r\n (click)=\"outdentSelected()\"\r\n >\r\n \u21E4\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--sm\" title=\"Duplica\" (click)=\"duplicateSelected()\">\u29C9</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--danger fb-btn--sm\" title=\"Elimina\" (click)=\"removeSelected()\">\r\n \u00D7\r\n </button>\r\n </div>\r\n </header>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Tipo</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"selectedField()!.fieldType || ''\"\r\n (change)=\"setFieldType($any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 scegli \u2014</option>\r\n @for (entry of dictionary.screenFieldTypes(); track entry.value) {\r\n <option [value]=\"entry.value\">{{ entry.label }}</option>\r\n }\r\n </select>\r\n @if (!selectedField()!.fieldType) {\r\n <p class=\"fb-field__error\">Il tipo e\u2019 obbligatorio: senza, SCREEN_FIELD_TYPE_MISSING.</p>\r\n } @else if (isUnknownType(selectedField())) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n Questo tipo non e\u2019 nel dizionario: l\u2019editor non sa quali campi ammetta e mostra solo i comuni.\r\n </p>\r\n } @else if (selectedType()?.description) {\r\n <p class=\"fb-field__hint\">{{ selectedType()?.description }}</p>\r\n }\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Nome</label>\r\n <!-- Sul `change` e non sull\u2019`input`: la rinomina riscrive i riferimenti nel documento,\r\n e farlo a ogni tasto significherebbe riscriverlo per ogni lettera. -->\r\n <input\r\n class=\"fb-input fb-input--mono\"\r\n [value]=\"selectedField()!.name || ''\"\r\n (change)=\"setFieldName($any($event.target).value)\"\r\n />\r\n @if (selectedIsResource()) {\r\n <p class=\"fb-field__hint\">\r\n Questo campo e\u2019 una <strong>risorsa</strong>: lo referenzi come\r\n <code>{{ selectedField()!.name || 'Nome' }}</code> in condizioni, formule e parametri. \u00C8 di\r\n sola lettura per il flow \u2014 un Assignment che ci scrive e\u2019 TARGET_NOT_WRITABLE.\r\n </p>\r\n }\r\n </div>\r\n\r\n @if (!selectedType()?.isContainer) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">\r\n {{ selectedField()!.fieldType === 'DisplayText' ? 'Testo' : 'Etichetta' }}\r\n </label>\r\n <textarea\r\n class=\"fb-textarea\"\r\n [value]=\"selectedField()!.fieldText || ''\"\r\n (input)=\"setFieldProperty('fieldText', $any($event.target).value)\"\r\n ></textarea>\r\n <p class=\"fb-field__hint\">\r\n Supporta i merge field <code>&#123;!Riferimento&#125;</code>.\r\n @if (selectedField()!.fieldType === 'ComponentInstance') {\r\n <!--\r\n \u00A75.2 \u2014 su un componente l\u2019etichetta e\u2019 **ammessa** e viene consegnata al frontend\r\n in `label`, ma la sua assenza non e\u2019 un rilievo: un componente di solito disegna\r\n la propria intestazione. Nasconderla, come faceva prima l\u2019editor, toglieva un\r\n campo che il contratto prevede.\r\n -->\r\n Facoltativa: un componente di solito disegna la propria intestazione.\r\n }\r\n </p>\r\n </div>\r\n } @else {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Intestazione</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"selectedField()!.fieldText || ''\"\r\n (input)=\"setFieldProperty('fieldText', $any($event.target).value)\"\r\n />\r\n </div>\r\n }\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Testo di aiuto</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"selectedField()!.helpText || ''\"\r\n (input)=\"setFieldProperty('helpText', $any($event.target).value)\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Larghezza</label>\r\n <div class=\"fb-scr__width\">\r\n <input\r\n type=\"range\"\r\n min=\"1\"\r\n max=\"12\"\r\n step=\"1\"\r\n [value]=\"widthOf(selectedField()!)\"\r\n (input)=\"setNumberProperty('width', $any($event.target).value)\"\r\n />\r\n <span class=\"fb-scr__width-value\">{{ widthOf(selectedField()!) }}/12</span>\r\n </div>\r\n <p class=\"fb-field__hint\">\r\n Colonne della griglia della schermata. \u00C8 un\u2019indicazione: il frontend puo\u2019 ignorarla.\r\n </p>\r\n </div>\r\n\r\n @if (selectedField()!.fieldType === 'ComponentInstance') {\r\n <!--\r\n \u00A75.2 \u2014 perche\u2019 qui non c\u2019e\u2019 \u00ABobbligatorio\u00BB. La domanda arriva sempre, e la risposta non\r\n si indovina guardando l\u2019interfaccia: un componente non ha un valore proprio, ce l\u2019hanno\r\n i suoi output, che vanno in **variabili** scelte dall\u2019autore. \u00ABObbligatorio\u00BB dovrebbe\r\n dire *quale output* deve essere valorizzato, e il metadata non ha modo di dirlo \u2014\r\n dichiararlo qui sarebbe `SCREEN_FIELD_CONFIGURATION_INVALID` e il runtime lo\r\n ignorerebbe. Le due strade vere stanno nel contratto e sono queste.\r\n -->\r\n <p class=\"fb-field__hint\">\r\n Un componente non raccoglie un valore \u2014 lo fanno i suoi output \u2014 quindi obbligatorieta\u2019, valore\r\n predefinito e regola di validazione non si applicano: dichiararli e\u2019\r\n <code>SCREEN_FIELD_CONFIGURATION_INVALID</code> e il runtime li ignora. Per pretendere una\r\n compilazione: la validazione la fa il componente stesso, oppure si controlla la variabile di\r\n destinazione con una Decision dopo la schermata.\r\n </p>\r\n }\r\n\r\n <!-- ------------------------------------------------ campi che raccolgono un valore -->\r\n @if (selectedType()?.storesValue) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Valore</legend>\r\n\r\n @if (selectedField()!.fieldType !== 'ObjectProvided') {\r\n <div class=\"fb-field\">\r\n <label\r\n class=\"fb-field__label\"\r\n [class.fb-field__label--required]=\"!!selectedType()?.requiresDataType\"\r\n >\r\n Tipo di dato\r\n </label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"selectedField()!.dataType || ''\"\r\n (change)=\"setFieldProperty('dataType', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 scegli \u2014</option>\r\n @for (entry of dictionary.dataTypes(); track entry.value) {\r\n <option [value]=\"entry.value\">{{ entry.label }}</option>\r\n }\r\n </select>\r\n @if (selectedType()?.requiresDataType && !selectedField()!.dataType) {\r\n <p class=\"fb-field__error\">Obbligatorio per questo tipo di campo (DATA_TYPE_MISSING).</p>\r\n }\r\n @if (selectedType()?.isCollection) {\r\n <p class=\"fb-field__hint\">\r\n Il valore raccolto e\u2019 una <strong>collection</strong>, non una stringa con i valori\r\n separati: nelle condizioni si usano gli operatori di collection.\r\n </p>\r\n }\r\n </div>\r\n\r\n @if (requiresObjectType()) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\" [class.fb-field__label--required]=\"objectTypeIsStructure()\">\r\n {{\r\n objectTypeIsStructure()\r\n ? 'Classe'\r\n : selectedField()!.dataType === 'Enum'\r\n ? 'Tipo di enumerazione'\r\n : 'Oggetto'\r\n }}\r\n </label>\r\n @if (selectedField()!.dataType === 'Enum') {\r\n <!-- Dizionario chiuso: si sceglie, non si scrive. -->\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"selectedField()!.objectType || ''\"\r\n (change)=\"setFieldProperty('objectType', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 scegli \u2014</option>\r\n @for (entry of enumOptions(); track entry.name) {\r\n <option [value]=\"entry.name\">{{ entry.label || entry.name }}</option>\r\n }\r\n </select>\r\n @if (!selectedField()!.objectType) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n Senza il tipo concreto l\u2019editor non puo\u2019 proporre i valori dell\u2019enumerazione.\r\n </p>\r\n }\r\n } @else if (objectTypeIsStructure()) {\r\n <fb-structure-picker\r\n [value]=\"selectedField()!.objectType\"\r\n label=\"Classe\"\r\n (valueChange)=\"setFieldProperty('objectType', $event ?? '')\"\r\n />\r\n @if (!selectedField()!.objectType) {\r\n <p class=\"fb-field__error\">\r\n La classe e\u2019 obbligatoria: senza, il runtime non ha nulla da istanziare\r\n (OBJECT_TYPE_MISSING).\r\n </p>\r\n }\r\n } @else {\r\n <fb-object-picker\r\n [value]=\"selectedField()!.objectType\"\r\n label=\"Oggetto\"\r\n (valueChange)=\"setFieldProperty('objectType', $event ?? '')\"\r\n />\r\n }\r\n </div>\r\n }\r\n } @else {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Campo dell\u2019entita\u2019</label>\r\n <p class=\"fb-field__hint\">\r\n Tipo, label, obbligatorieta\u2019, scala e \u2014 se il campo e\u2019 una picklist \u2014 le opzioni arrivano\r\n dallo schema dati: qui non si ridichiarano.\r\n </p>\r\n <fb-object-picker\r\n [value]=\"providedObject() || undefined\"\r\n label=\"Oggetto\"\r\n (valueChange)=\"setProvidedObject($event)\"\r\n />\r\n <fb-field-picker\r\n [value]=\"providedField() || undefined\"\r\n [object]=\"providedObject() || undefined\"\r\n label=\"Campo\"\r\n (valueChange)=\"setProvidedField($event)\"\r\n />\r\n </div>\r\n }\r\n\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"selectedIsRequired()\"\r\n (change)=\"setRequired($any($event.target).checked)\"\r\n />\r\n Obbligatorio\r\n </label>\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"selectedIsEditable()\"\r\n (change)=\"setEditable($any($event.target).checked)\"\r\n />\r\n Modificabile\r\n </label>\r\n @if (!selectedIsEditable()) {\r\n <p class=\"fb-field__hint\">\r\n A <code>false</code> il runtime <strong>ignora</strong> cio\u2019 che il client rimanda indietro:\r\n e\u2019 l\u2019unico modo di rendere un campo davvero di sola lettura.\r\n </p>\r\n }\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Valore di default</label>\r\n <fb-value-editor\r\n [value]=\"selectedField()!.defaultValue\"\r\n [dataType]=\"selectedField()!.dataType\"\r\n [objectType]=\"selectedField()!.objectType\"\r\n [isCollection]=\"selectedType()?.isCollection\"\r\n label=\"Valore di default\"\r\n (valueChange)=\"setDefaultValue($event)\"\r\n />\r\n </div>\r\n\r\n @if (scaleApplies()) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Decimali</label>\r\n <input\r\n class=\"fb-input\"\r\n type=\"number\"\r\n min=\"0\"\r\n [value]=\"selectedField()!.scale ?? ''\"\r\n (change)=\"setNumberProperty('scale', $any($event.target).value)\"\r\n />\r\n </div>\r\n } @else if (selectedField()!.scale !== undefined) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n <code>scale</code> su un campo non numerico e\u2019 SCALE_NOT_APPLICABLE.\r\n </p>\r\n }\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Lunghezza massima</label>\r\n <input\r\n class=\"fb-input\"\r\n type=\"number\"\r\n min=\"1\"\r\n [value]=\"selectedField()!.maxLength ?? ''\"\r\n (change)=\"setNumberProperty('maxLength', $any($event.target).value)\"\r\n />\r\n <p class=\"fb-field__hint\">Verificata anche dal runtime (TOO_LONG).</p>\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Tornando sulla schermata</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"selectedField()!.inputsOnNextNavToAssocScrn || ''\"\r\n (change)=\"setFieldProperty('inputsOnNextNavToAssocScrn', $any($event.target).value)\"\r\n >\r\n <option value=\"\">Predefinito (mantieni i valori)</option>\r\n @for (entry of dictionary.screenFieldInputsRevisited(); track entry.value) {\r\n <option [value]=\"entry.value\">{{ entry.label }}</option>\r\n }\r\n </select>\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Regola di validazione</label>\r\n <!--\r\n `commitOn=\"change\"`: qui mutare il documento ridisegna l'anteprima, e riscriverla a\r\n ogni tasto costa. La verifica parte lo stesso mentre si digita (\u00A76.3).\r\n -->\r\n <fb-formula-editor\r\n [expression]=\"selectedField()!.validationRule?.formulaExpression || ''\"\r\n usage=\"ValidationRule\"\r\n expectedDataType=\"Boolean\"\r\n commitOn=\"change\"\r\n [rows]=\"2\"\r\n placeholder=\"Espressione, es. LEN(Nome) > 3\"\r\n ariaLabel=\"Regola di validazione\"\r\n (expressionChange)=\"setValidationRule('formulaExpression', $event)\"\r\n />\r\n <input\r\n class=\"fb-input\"\r\n placeholder=\"Messaggio mostrato quando l\u2019espressione e\u2019 falsa\"\r\n [value]=\"selectedField()!.validationRule?.errorMessage || ''\"\r\n (change)=\"setValidationRule('errorMessage', $any($event.target).value)\"\r\n />\r\n <p class=\"fb-field__hint\">\r\n L\u2019espressione la valuta il motore di regole: il backend non la interpreta, la fa\r\n verificare al motore.\r\n </p>\r\n </div>\r\n </fieldset>\r\n }\r\n\r\n <!-- --------------------------------------------------------------- choice -->\r\n @if (selectedType()?.acceptsChoices) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Opzioni</legend>\r\n <p class=\"fb-section__note\">\r\n Accetta <strong>Choice</strong> e <strong>Dynamic choice set</strong>, nell\u2019ordine in cui le\r\n opzioni compaiono. Puntare a una variabile e\u2019 SCREEN_FIELD_CHOICE_UNKNOWN.\r\n </p>\r\n\r\n @if (!selectedField()!.choiceReferences?.length) {\r\n @if (selectedField()!.fieldType === 'ObjectProvided') {\r\n <!-- Le opzioni di una picklist arrivano dallo schema: qui si aggiungono solo se\r\n si vuole sostituirle, e non dichiararne nessuna e' il caso normale. -->\r\n <p class=\"fb-field__hint\">\r\n Se il campo dello schema e\u2019 una picklist, le opzioni arrivano da l\u00EC: dichiararle qui\r\n serve solo a sostituirle.\r\n </p>\r\n } @else {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Nessuna opzione: il campo non ha niente da mostrare (SCREEN_FIELD_CHOICES_MISSING).\r\n </p>\r\n }\r\n }\r\n\r\n <div class=\"fb-list\">\r\n @for (choice of selectedField()!.choiceReferences || []; track $index) {\r\n <div class=\"fb-list__item\">\r\n <div class=\"fb-list__header\">\r\n <span class=\"fb-list__index\">{{ $index + 1 }}</span>\r\n <span class=\"fb-list__title\">{{ choice }}</span>\r\n <span class=\"fb-list__spacer\"></span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--sm\" (click)=\"moveChoice($index, -1)\">\u2191</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--sm\" (click)=\"moveChoice($index, 1)\">\u2193</button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--sm\"\r\n aria-label=\"Rimuovi\"\r\n (click)=\"removeChoice($index)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Aggiungi un\u2019opzione</label>\r\n <fb-name-picker\r\n [options]=\"choiceOptions()\"\r\n label=\"Choice\"\r\n placeholder=\"Scegli una choice o un choice set\"\r\n unknownMessage=\"Questo nome non e\u2019 una choice ne\u2019 un choice set: e\u2019 SCREEN_FIELD_CHOICE_UNKNOWN.\"\r\n unknownSeverity=\"error\"\r\n emptyMessage=\"Il flow non dichiara nessuna choice: creale nel pannello delle risorse.\"\r\n (valueChange)=\"addChoice($event)\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Opzione selezionata all\u2019apertura</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"selectedField()!.defaultSelectedChoiceReference || ''\"\r\n (change)=\"setFieldProperty('defaultSelectedChoiceReference', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 nessuna \u2014</option>\r\n @for (choice of selectedField()!.choiceReferences || []; track $index) {\r\n <option [value]=\"choice\">{{ choice }}</option>\r\n }\r\n </select>\r\n @if (defaultChoiceIsForeign()) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n \u00AB{{ selectedField()!.defaultSelectedChoiceReference }}\u00BB non e\u2019 fra le opzioni elencate qui\r\n sopra.\r\n </p>\r\n }\r\n </div>\r\n </fieldset>\r\n }\r\n\r\n <!-- ------------------------------------------------------- contenitori -->\r\n @if (selectedType()?.isContainer) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Contenitore</legend>\r\n @if (selectedField()!.fieldType === 'RegionContainer') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Tipo di sezione</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"selectedField()!.regionContainerType || ''\"\r\n (change)=\"setFieldProperty('regionContainerType', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 predefinito \u2014</option>\r\n @for (entry of dictionary.regionContainerTypes(); track entry.value) {\r\n <option [value]=\"entry.value\">{{ entry.label }}</option>\r\n }\r\n </select>\r\n </div>\r\n }\r\n @if (!selectedField()!.fields?.length) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Il contenitore e\u2019 vuoto: non produce niente sulla schermata (SCREEN_CONTAINER_EMPTY).\r\n </p>\r\n }\r\n </fieldset>\r\n }\r\n\r\n <!-- --------------------------------------------------- ComponentInstance -->\r\n @if (selectedField()!.fieldType === 'ComponentInstance') {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Componente</legend>\r\n <p class=\"fb-section__note\">\r\n Componenti e form sono la stessa domanda al frontend \u2014 cosa sa rendere, e con quali parametri\r\n \u2014 e passano dallo stesso catalogo. Un <code>ComponentInstance</code> non ha un valore proprio:\r\n lo hanno i suoi output.\r\n </p>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Componente</label>\r\n <fb-name-picker\r\n [value]=\"selectedField()!.extensionName\"\r\n [options]=\"componentOptions()\"\r\n [unusableOptions]=\"unusableComponents()\"\r\n label=\"Componente\"\r\n placeholder=\"Scrivi o scegli un componente\"\r\n [isMono]=\"false\"\r\n unknownMessage=\"Questo componente non esiste nel catalogo: e\u2019 SCREEN_COMPONENT_UNKNOWN.\"\r\n unknownSeverity=\"error\"\r\n unusableMessage=\"Questo nome e\u2019 una schermata intera, non un componente montabile qui (FORM_KIND_MISMATCH).\"\r\n emptyMessage=\"Il catalogo dei componenti non e\u2019 popolato: il nome non viene verificato.\"\r\n (valueChange)=\"setFieldProperty('extensionName', $event ?? '')\"\r\n />\r\n @if (!selectedField()!.extensionName) {\r\n <p class=\"fb-field__error\">Obbligatorio: senza, SCREEN_COMPONENT_MISSING.</p>\r\n }\r\n </div>\r\n\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"selectedField()!.storeOutputAutomatically === true\"\r\n (change)=\"setStoreOutputAutomatically($any($event.target).checked)\"\r\n />\r\n Rendi gli output referenziabili automaticamente\r\n </label>\r\n @if (selectedField()!.storeOutputAutomatically) {\r\n <p class=\"fb-field__hint\">\r\n Gli output si referenziano come\r\n <code>{{ selectedField()!.name || 'Campo' }}.nomeOutput</code>, senza dichiarare variabili.\r\n </p>\r\n }\r\n @if (hasOutputConflict()) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n Output automatici <strong>e</strong> parametri di uscita insieme:\r\n OUTPUT_CONFIGURATION_CONFLICT.\r\n </p>\r\n }\r\n\r\n <fb-parameter-editor\r\n [holder]=\"$any(selectedField())\"\r\n [catalogParameters]=\"componentParameterList()\"\r\n inputTitle=\"Valori passati al componente\"\r\n outputTitle=\"Valori raccolti dal componente\"\r\n [showOutputs]=\"!selectedField()!.storeOutputAutomatically\"\r\n outputsDisabledReason=\"Gli output sono automatici: disattivalo per assegnarli a variabili.\"\r\n (changed)=\"onComponentParametersChanged($event)\"\r\n />\r\n </fieldset>\r\n }\r\n\r\n <!-- ------------------------------------------------------- visibilita' -->\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Visibilita\u2019</legend>\r\n <p class=\"fb-section__note\">\r\n Le regole si rivalutano <strong>sui valori appena inviati</strong>: e\u2019 cos\u00EC che un campo compare\r\n in funzione di un altro campo della stessa schermata. Un campo risultato nascosto viene\r\n <strong>azzerato</strong> e non viene validato.\r\n </p>\r\n <fb-condition-editor\r\n [holder]=\"visibilityRule()\"\r\n title=\"Mostra il campo quando\"\r\n [allowFormula]=\"false\"\r\n [issuePath]=\"'fields[' + (selectedField()!.name || '') + '].visibilityRule'\"\r\n (changed)=\"onVisibilityChanged($event)\"\r\n />\r\n </fieldset>\r\n }\r\n </aside>\r\n</div>\r\n\r\n<fb-connector-editor\r\n [nodeName]=\"name()\"\r\n [node]=\"node()\"\r\n [outlets]=\"outlets()\"\r\n (connectorChanged)=\"onConnectorChanged($event)\"\r\n/>\r\n\r\n<!--\r\n ============================================================== l'anteprima\r\n Due template ricorsivi. `listTpl` disegna i figli di un contenitore, e nient'altro: `data-item`\r\n marca cio' che conta come fratello nel calcolo del punto di rilascio, e il segnaposto del\r\n contenitore vuoto ne resta fuori. La barra di inserimento non e' qui \u2014 sta sopra l'anteprima,\r\n vedi il velo nel frame.\r\n-->\r\n<ng-template #listTpl let-fields let-parent=\"parent\" let-axis=\"axis\">\r\n @for (child of fields; track $index) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"fieldTpl\"\r\n [ngTemplateOutletContext]=\"{ $implicit: child, path: childPath(parent, $index), axis: axis }\"\r\n />\r\n }\r\n @if (!fields.length) {\r\n <p class=\"fb-scr__drop-hint\">Trascina qui un componente</p>\r\n }\r\n</ng-template>\r\n\r\n<ng-template #fieldTpl let-field let-path=\"path\" let-axis=\"axis\">\r\n <div\r\n class=\"fb-scr__item\"\r\n data-item=\"\"\r\n cdkDrag\r\n [style.grid-column]=\"'span ' + widthOf(field)\"\r\n [class.fb-scr__item--selected]=\"isSelected(path)\"\r\n [class.fb-scr__item--dragging]=\"isDragging(path)\"\r\n [class.fb-scr__item--container]=\"isContainer(field)\"\r\n (cdkDragStarted)=\"onFieldDragStarted(path)\"\r\n (cdkDragMoved)=\"onFieldDragMoved($event)\"\r\n (cdkDragEnded)=\"onFieldDragEnded($event, path)\"\r\n (click)=\"select(path); $event.stopPropagation()\"\r\n >\r\n <span class=\"fb-scr__grip\" cdkDragHandle title=\"Trascina per spostare\" aria-hidden=\"true\">\u283F</span>\r\n @if (field.visibilityRule?.conditions?.length) {\r\n <span class=\"fb-scr__flag\" title=\"Ha una regola di visibilita\u2019\">\u25D0</span>\r\n }\r\n\r\n @switch (field.fieldType) {\r\n @case ('RegionContainer') {\r\n <div class=\"fb-scr__section\">\r\n @if (field.regionContainerType !== 'SectionWithoutHeader') {\r\n <header class=\"fb-scr__section-head\">{{ field.fieldText || field.name }}</header>\r\n }\r\n <div class=\"fb-scr__cols\" [attr.data-drop]=\"dropId(path)\" data-axis=\"row\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"listTpl\"\r\n [ngTemplateOutletContext]=\"{ $implicit: field.fields || [], parent: path, axis: 'row' }\"\r\n />\r\n </div>\r\n </div>\r\n }\r\n @case ('Region') {\r\n <div class=\"fb-scr__region\">\r\n <span class=\"fb-scr__region-tag\">{{ field.name }} \u00B7 {{ widthOf(field) }}/12</span>\r\n <div class=\"fb-scr__region-body\" [attr.data-drop]=\"dropId(path)\" data-axis=\"column\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"listTpl\"\r\n [ngTemplateOutletContext]=\"{ $implicit: field.fields || [], parent: path, axis: 'column' }\"\r\n />\r\n </div>\r\n </div>\r\n }\r\n @case ('DisplayText') {\r\n <p class=\"fb-scr__display\">{{ field.fieldText || '(testo vuoto)' }}</p>\r\n }\r\n @case ('LargeTextArea') {\r\n <label class=\"fb-scr__label\">\r\n {{ captionOf(field) }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__control fb-scr__control--area\">{{ placeholderOf(field) }}</div>\r\n }\r\n @case ('PasswordField') {\r\n <label class=\"fb-scr__label\">\r\n {{ captionOf(field) }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__control\">\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022</div>\r\n }\r\n @case ('DropdownBox') {\r\n <label class=\"fb-scr__label\">\r\n {{ captionOf(field) }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__control fb-scr__control--select\">\r\n <span>{{ field.defaultSelectedChoiceReference || choiceLabelsOf(field)[0] || '\u2014 scegli \u2014' }}</span>\r\n <span aria-hidden=\"true\">\u25BE</span>\r\n </div>\r\n }\r\n @case ('MultiSelectPicklist') {\r\n <label class=\"fb-scr__label\">\r\n {{ captionOf(field) }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__control fb-scr__control--select\">\r\n <span>{{ choiceLabelsOf(field).join(', ') || '\u2014 scegli \u2014' }}</span>\r\n <span aria-hidden=\"true\">\u2261</span>\r\n </div>\r\n }\r\n @case ('RadioButtons') {\r\n <label class=\"fb-scr__label\">\r\n {{ captionOf(field) }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__options\">\r\n @for (option of choiceLabelsOf(field); track $index) {\r\n <span class=\"fb-scr__option\">\u25EF {{ option }}</span>\r\n }\r\n @if (!choiceLabelsOf(field).length) {\r\n <span class=\"fb-scr__option fb-scr__option--missing\">Nessuna opzione</span>\r\n }\r\n </div>\r\n }\r\n @case ('MultiSelectCheckboxes') {\r\n <label class=\"fb-scr__label\">\r\n {{ captionOf(field) }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__options\">\r\n @for (option of choiceLabelsOf(field); track $index) {\r\n <span class=\"fb-scr__option\">\u2610 {{ option }}</span>\r\n }\r\n @if (!choiceLabelsOf(field).length) {\r\n <span class=\"fb-scr__option fb-scr__option--missing\">Nessuna opzione</span>\r\n }\r\n </div>\r\n }\r\n @case ('ComponentInstance') {\r\n <!--\r\n L\u2019etichetta si disegna se c\u2019e\u2019: il runtime la consegna in `label` come per gli altri\r\n campi (\u00A75.2). Nessun asterisco: un componente non raccoglie un valore, quindi\r\n \u00ABobbligatorio\u00BB non ha niente su cui applicarsi.\r\n -->\r\n @if (field.fieldText) {\r\n <label class=\"fb-scr__label\">{{ field.fieldText }}</label>\r\n }\r\n <div class=\"fb-scr__component\">\r\n <span aria-hidden=\"true\">\u2B21</span>\r\n {{ field.extensionName || 'Componente non indicato' }}\r\n </div>\r\n }\r\n @case ('ObjectProvided') {\r\n <label class=\"fb-scr__label\">\r\n {{ field.fieldText || field.objectFieldReference || field.name }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__control\">{{ placeholderOf(field) }}</div>\r\n <span class=\"fb-scr__tag\">{{ field.objectFieldReference || 'campo non indicato' }}</span>\r\n }\r\n @default {\r\n <label class=\"fb-scr__label\">\r\n {{ captionOf(field) }}\r\n @if (field.isRequired) {\r\n <span class=\"fb-scr__req\">*</span>\r\n }\r\n </label>\r\n <div class=\"fb-scr__control\">{{ placeholderOf(field) }}</div>\r\n @if (isUnknownType(field)) {\r\n <span class=\"fb-scr__tag fb-scr__tag--warn\">{{ field.fieldType }}: tipo non nel dizionario</span>\r\n }\r\n }\r\n }\r\n </div>\r\n</ng-template>\r\n", styles: [".fb-scr{display:grid;grid-template-columns:220px minmax(0,1fr) 340px;gap:12px;align-items:start;margin-bottom:14px}@media(max-width:1100px){.fb-scr{grid-template-columns:minmax(0,1fr)}}.fb-scr__side,.fb-scr__props{min-width:0;padding:10px;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius, 10px);background:var(--fb-surface-alt, #f7f8fa)}.fb-scr__tabs{display:flex;gap:4px;margin-bottom:8px}.fb-scr__tab{flex:1;padding:5px 8px;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-xs, 6px);background:var(--fb-surface, #fff);color:var(--fb-text-muted, #6b7086);font:inherit;font-size:11px;font-weight:600;cursor:pointer}.fb-scr__tab--active{border-color:var(--fb-accent, #4f6ef7);background:color-mix(in srgb,var(--fb-accent, #4f6ef7) 10%,transparent);color:var(--fb-accent-strong, #3d59e0)}.fb-scr__side-hint,.fb-scr__side-empty{margin:6px 0;font-size:11px;color:var(--fb-text-muted, #6b7086)}.fb-scr__palette{display:flex;flex-direction:column;gap:4px;margin:8px 0 0;padding:0;max-height:320px;overflow-y:auto;overscroll-behavior:contain;list-style:none}.fb-scr__chip{display:flex;align-items:center;gap:8px;width:100%;padding:6px 8px;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-xs, 6px);background:var(--fb-surface, #fff);color:var(--fb-text, #1a1c23);font:inherit;font-size:12px;text-align:left;cursor:grab}.fb-scr__chip:hover{border-color:var(--fb-accent, #4f6ef7)}.fb-scr__chip-icon{flex:none;width:20px;text-align:center;color:var(--fb-text-muted, #6b7086)}.fb-scr__chip-label{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-scr__chip-label small{margin-left:4px;color:var(--fb-text-subtle, #98a2b3);font-size:10px}.fb-scr__canvas{min-width:0;padding:14px;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius, 10px);background:var(--fb-canvas-bg, #f4f5f7)}.fb-scr__frame{position:relative;display:flex;flex-direction:column;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-sm, 8px);background:var(--fb-surface, #fff);box-shadow:var(--fb-shadow-sm, 0 1px 2px rgb(16 24 40 / 6%));overflow:hidden}.fb-scr__frame-head{display:flex;align-items:center;gap:6px;padding:8px 12px;border-bottom:1px solid var(--fb-border-subtle, #eef0f4);font-size:13px;font-weight:600}.fb-scr__help{display:inline-flex;align-items:center;justify-content:center;width:15px;height:15px;border-radius:50%;background:var(--fb-surface-sunken, #eef0f4);color:var(--fb-text-muted, #6b7086);font-size:10px}.fb-scr__body,.fb-scr__region-body{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));align-content:start;gap:8px 0;padding:12px;min-height:90px}.fb-scr__region-body{padding:8px;min-height:60px}.fb-scr__cols{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));align-items:stretch;gap:0;padding:8px;min-height:60px}.fb-scr__cols>.fb-scr__item{display:flex;align-self:stretch}.fb-scr__cols>.fb-scr__item>.fb-scr__region{flex:1;min-width:0}.fb-scr__drop-hint{grid-column:1 / -1;margin:0;padding:10px;border:1px dashed var(--fb-border-strong, #cfd4de);border-radius:var(--fb-radius-xs, 6px);color:var(--fb-text-subtle, #98a2b3);font-size:11px;text-align:center}.fb-scr__marker{position:absolute;z-index:2;border-radius:2px;background:var(--fb-accent, #4f6ef7);pointer-events:none}.fb-scr__item{position:relative;box-sizing:border-box;padding:6px 8px;border:1px solid transparent;border-radius:var(--fb-radius-xs, 6px);cursor:pointer}.fb-scr__item:hover{border-color:var(--fb-border-strong, #cfd4de);background:color-mix(in srgb,var(--fb-accent, #4f6ef7) 4%,transparent)}.fb-scr__item--selected{border-color:var(--fb-accent, #4f6ef7);box-shadow:inset 0 0 0 1px var(--fb-accent, #4f6ef7)}.fb-scr__item--dragging{opacity:.45;pointer-events:none}.fb-scr__grip{position:absolute;top:2px;left:-2px;padding:0 3px;color:var(--fb-text-subtle, #98a2b3);font-size:11px;line-height:1;opacity:0;cursor:grab}.fb-scr__item:hover>.fb-scr__grip,.fb-scr__item--selected>.fb-scr__grip{opacity:1}.fb-scr__flag{position:absolute;top:2px;right:4px;color:var(--fb-text-muted, #6b7086);font-size:11px}.fb-scr__label{display:block;margin-bottom:3px;font-size:11px;font-weight:600;color:var(--fb-text, #1a1c23)}.fb-scr__req{color:var(--fb-error, #dc2626)}.fb-scr__control{display:flex;align-items:center;justify-content:space-between;gap:6px;min-height:26px;padding:4px 8px;border:1px solid var(--fb-border-strong, #cfd4de);border-radius:var(--fb-radius-xs, 6px);background:var(--fb-surface, #fff);color:var(--fb-text-subtle, #98a2b3);font-size:12px;pointer-events:none}.fb-scr__control--area{min-height:54px;align-items:flex-start}.fb-scr__display{margin:0;font-size:12px;color:var(--fb-text, #1a1c23)}.fb-scr__options{display:flex;flex-direction:column;gap:2px;pointer-events:none}.fb-scr__option{font-size:12px;color:var(--fb-text-muted, #6b7086)}.fb-scr__option--missing{color:var(--fb-warning, #b7791f)}.fb-scr__component{display:flex;align-items:center;gap:6px;padding:10px;border:1px dashed var(--fb-accent, #4f6ef7);border-radius:var(--fb-radius-xs, 6px);background:color-mix(in srgb,var(--fb-accent, #4f6ef7) 5%,transparent);font-size:12px;color:var(--fb-text-muted, #6b7086)}.fb-scr__tag{display:inline-block;margin-top:3px;font-size:10px;color:var(--fb-text-subtle, #98a2b3)}.fb-scr__tag--warn{color:var(--fb-warning, #b7791f)}.fb-scr__section{border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-xs, 6px);background:var(--fb-surface-alt, #f7f8fa)}.fb-scr__section-head{padding:5px 10px;border-bottom:1px solid var(--fb-border-subtle, #eef0f4);font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--fb-text-muted, #6b7086)}.fb-scr__region{border:1px dashed var(--fb-border-strong, #cfd4de);border-radius:var(--fb-radius-xs, 6px);background:var(--fb-surface, #fff)}.fb-scr__region-tag{display:block;padding:3px 6px 0;font-size:10px;color:var(--fb-text-subtle, #98a2b3)}.fb-scr__frame-foot{display:flex;align-items:center;gap:6px;padding:8px 12px;border-top:1px solid var(--fb-border-subtle, #eef0f4);background:var(--fb-surface-alt, #f7f8fa)}.fb-scr__spacer{flex:1}.fb-scr__btn{padding:3px 10px;border:1px solid var(--fb-border-strong, #cfd4de);border-radius:var(--fb-radius-xs, 6px);font-size:11px;color:var(--fb-text-muted, #6b7086)}.fb-scr__btn--primary{border-color:var(--fb-accent, #4f6ef7);background:var(--fb-accent, #4f6ef7);color:var(--fb-accent-contrast, #fff)}.fb-scr__canvas-note{margin-top:10px}.fb-scr__props{background:var(--fb-surface, #fff)}.fb-scr__props-head{display:flex;align-items:center;gap:8px;margin-bottom:8px}.fb-scr__props-title{flex:1;min-width:0;margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--fb-text-muted, #6b7086)}.fb-scr__props-actions{display:flex;flex-wrap:wrap;gap:3px}.fb-btn--sm{padding:3px 7px;font-size:11px}.fb-scr__width{display:flex;align-items:center;gap:8px}.fb-scr__width input[type=range]{flex:1;min-width:0}.fb-scr__width-value{font-size:11px;color:var(--fb-text-muted, #6b7086)}\n"] }]
9521
10097
  }], ctorParameters: () => [] });
9522
10098
 
9523
10099
  /**
@@ -11465,6 +12041,70 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImpo
11465
12041
  args: [{ selector: 'fb-element-dialog', standalone: true, imports: [ElementInspectorComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<!--\r\n Il backdrop chiude: e' il gesto che tutti si aspettano. Il pannello ferma la propagazione\r\n del click, altrimenti configurare un campo chiuderebbe la dialog.\r\n-->\r\n<div class=\"fb-dialog__backdrop\" (click)=\"close()\"></div>\r\n\r\n<div\r\n #panel\r\n class=\"fb-dialog__panel\"\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n [attr.aria-label]=\"typeLabel() + ': ' + title()\"\r\n tabindex=\"-1\"\r\n (click)=\"$event.stopPropagation()\"\r\n (keydown.escape)=\"close()\"\r\n>\r\n <header class=\"fb-dialog__head\">\r\n <div class=\"fb-dialog__identity\">\r\n <span class=\"fb-dialog__type\">{{ typeLabel() }}</span>\r\n <h2 class=\"fb-dialog__title\">{{ title() }}</h2>\r\n </div>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" aria-label=\"Chiudi\" (click)=\"close()\">\u00D7</button>\r\n </header>\r\n\r\n <div class=\"fb-dialog__body\">\r\n <!-- L'intestazione dell'inspector qui e' quella della dialog: non si ripete. -->\r\n <fb-element-inspector\r\n [selectedName]=\"selectedName()\"\r\n [showHeader]=\"false\"\r\n (removeRequested)=\"onRemoveRequested($event)\"\r\n (duplicateRequested)=\"onDuplicateRequested($event)\"\r\n (renamed)=\"onRenamed($event)\"\r\n (closed)=\"close()\"\r\n />\r\n </div>\r\n\r\n <footer class=\"fb-dialog__foot\">\r\n <span class=\"fb-dialog__note\">\r\n Le modifiche sono gi\u00E0 nel documento: per tornare indietro c\u2019\u00E8 l\u2019annulla dell\u2019editor.\r\n </span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" (click)=\"close()\">Fatto</button>\r\n </footer>\r\n</div>\r\n", styles: [":host{position:absolute;inset:0;z-index:30;display:grid;place-items:center;padding:24px}.fb-dialog__backdrop{position:absolute;inset:0;background:#10182852;-webkit-backdrop-filter:blur(1px);backdrop-filter:blur(1px)}.fb-dialog__panel{position:relative;display:flex;flex-direction:column;width:min(var(--fb-dialog-width, 1400px),100%);height:min(var(--fb-dialog-height, 860px),100%);border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-lg, 12px);background:var(--fb-surface, #fff);box-shadow:var(--fb-shadow-lg, 0 18px 44px rgb(16 24 40 / 18%));outline:none;overflow:hidden}.fb-dialog__head{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 14px;border-bottom:1px solid var(--fb-border-subtle, #eef0f4)}.fb-dialog__identity{min-width:0}.fb-dialog__type{display:block;font-size:10px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--fb-text-subtle, #98a2b3)}.fb-dialog__title{margin:1px 0 0;font-size:15px;font-weight:600;color:var(--fb-text, #1a1c23);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-dialog__body{flex:1;min-height:0;overflow-y:auto;scrollbar-gutter:stable;overscroll-behavior:contain}.fb-dialog__foot{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 14px;border-top:1px solid var(--fb-border-subtle, #eef0f4);background:var(--fb-surface-alt, #f7f8fa)}.fb-dialog__note{font-size:11px;color:var(--fb-text-muted, #6b7086)}@media(max-height:620px){:host{padding:10px}}\n"] }]
11466
12042
  }], ctorParameters: () => [], propDecorators: { selectedName: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedName", required: false }] }], closed: [{ type: i0.Output, args: ["closed"] }], removeRequested: [{ type: i0.Output, args: ["removeRequested"] }], duplicateRequested: [{ type: i0.Output, args: ["duplicateRequested"] }], renamed: [{ type: i0.Output, args: ["renamed"] }], panel: [{ type: i0.ViewChild, args: ['panel', { isSignal: true }] }] } });
11467
12043
 
12044
+ /**
12045
+ * La finestra dell'incolla: cosa sta per entrare nel documento, prima che entri.
12046
+ *
12047
+ * Un incolla silenzioso sarebbe piu' rapido e sbagliato. Incollare un elemento in un altro flow
12048
+ * non e' mai una copia identica: dei nomi possono essere già presi (§3.3), delle risorse
12049
+ * referenziate possono non esistere di la', e dei riferimenti possono restare orfani perche'
12050
+ * puntano a un elemento che e' rimasto nel flow di origine. Tutte e tre le cose si sanno
12051
+ * **prima** — le calcola `planPaste` — e sono esattamente cio' che l'utente vorrebbe sapere
12052
+ * dopo, quando trovasse `Priorita_copia` fra le variabili senza aver chiesto niente.
12053
+ *
12054
+ * L'unica scelta e' la casella delle risorse mancanti: crearle e' cio' che rende «senza
12055
+ * riconfigurare» vero, ma chi sta incollando in un flow che ha già le sue variabili con altri
12056
+ * nomi preferisce ricollegarle a mano.
12057
+ */
12058
+ /**
12059
+ * Come si chiama, in italiano, la collection di una risorsa. Il nome tecnico (`variables`) qui
12060
+ * non aiuta: chi legge sta decidendo se creare quella cosa, non sta scrivendo il JSON.
12061
+ */
12062
+ const RESOURCE_LABELS = {
12063
+ variables: 'variabile',
12064
+ constants: 'costante',
12065
+ formulas: 'formula',
12066
+ textTemplates: 'text template',
12067
+ choices: 'choice',
12068
+ dynamicChoiceSets: 'dynamic choice set',
12069
+ stages: 'stage',
12070
+ };
12071
+ class PasteDialogComponent {
12072
+ payload = input.required(...(ngDevMode ? [{ debugName: "payload" }] : []));
12073
+ plan = input.required(...(ngDevMode ? [{ debugName: "plan" }] : []));
12074
+ confirmed = output();
12075
+ cancelled = output();
12076
+ /** Predefinito acceso: e' il motivo per cui si copia un elemento invece di rifarlo. */
12077
+ createMissingResources = signal(true, ...(ngDevMode ? [{ debugName: "createMissingResources" }] : []));
12078
+ elementLabel = computed(() => {
12079
+ const count = this.plan().nodeCount;
12080
+ return `${count} ${count === 1 ? 'elemento' : 'elementi'}`;
12081
+ }, ...(ngDevMode ? [{ debugName: "elementLabel" }] : []));
12082
+ newResources = computed(() => this.plan().resources.filter((entry) => entry.status === 'new'), ...(ngDevMode ? [{ debugName: "newResources" }] : []));
12083
+ reusedResources = computed(() => this.plan().resources.filter((entry) => entry.status === 'reused'), ...(ngDevMode ? [{ debugName: "reusedResources" }] : []));
12084
+ /** I nomi degli elementi che cambiano: si distinguono da quelli interni perche' il rimedio e' diverso. */
12085
+ nodeRenames = computed(() => this.plan().renames.filter((entry) => entry.kind === 'node'), ...(ngDevMode ? [{ debugName: "nodeRenames" }] : []));
12086
+ innerRenames = computed(() => this.plan().renames.filter((entry) => entry.kind === 'inner'), ...(ngDevMode ? [{ debugName: "innerRenames" }] : []));
12087
+ /** Fuori mappa si mostra il nome tecnico: meglio una parola strana che nessuna parola. */
12088
+ resourceLabel(collection) {
12089
+ return RESOURCE_LABELS[collection] ?? collection;
12090
+ }
12091
+ setCreateMissingResources(value) {
12092
+ this.createMissingResources.set(value);
12093
+ }
12094
+ confirm() {
12095
+ this.confirmed.emit({ createMissingResources: this.createMissingResources() });
12096
+ }
12097
+ cancel() {
12098
+ this.cancelled.emit();
12099
+ }
12100
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.27", ngImport: i0, type: PasteDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
12101
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.27", type: PasteDialogComponent, isStandalone: true, selector: "fb-paste-dialog", inputs: { payload: { classPropertyName: "payload", publicName: "payload", isSignal: true, isRequired: true, transformFunction: null }, plan: { classPropertyName: "plan", publicName: "plan", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { confirmed: "confirmed", cancelled: "cancelled" }, ngImport: i0, template: "<div class=\"fb-dialog__backdrop\" (click)=\"cancel()\"></div>\r\n\r\n<div\r\n class=\"fb-dialog__panel fb-paste\"\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n aria-label=\"Incolla elementi\"\r\n tabindex=\"-1\"\r\n (click)=\"$event.stopPropagation()\"\r\n (keydown.escape)=\"cancel()\"\r\n>\r\n <header class=\"fb-dialog__head\">\r\n <div class=\"fb-dialog__identity\">\r\n <span class=\"fb-dialog__type\">Appunti</span>\r\n <h2 class=\"fb-dialog__title\">Incolla {{ elementLabel() }}</h2>\r\n </div>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" aria-label=\"Chiudi\" (click)=\"cancel()\">\u00D7</button>\r\n </header>\r\n\r\n <div class=\"fb-dialog__body fb-paste__body\">\r\n @if (payload().flowName) {\r\n <p class=\"fb-paste__origin\">Copiati da <strong>{{ payload().flowName }}</strong>.</p>\r\n }\r\n\r\n <ul class=\"fb-paste__elements\">\r\n @for (entry of payload().nodes; track $index) {\r\n <li>{{ entry.node.label || entry.node.name }} <span class=\"fb-paste__tech\">&nbsp;{{ entry.node.name }}</span></li>\r\n }\r\n </ul>\r\n\r\n @if (plan().processTypeMismatch) {\r\n <!--\r\n Le globali dipendono dal `processType` con cui le si chiede: `$Record` non esiste in uno\r\n screen flow (\u00A74.1). Non blocca \u2014 il documento e' comunque salvabile \u2014 ma un riferimento\r\n che di la' era valido puo' non esserlo qui, e dirlo prima evita la caccia al rilievo.\r\n -->\r\n <p class=\"fb-paste__warn\">\r\n Il flow di origine e\u2019 di tipo <strong>{{ plan().processTypeMismatch }}</strong>: le variabili globali\r\n disponibili qui sono altre, e i riferimenti che le usano potrebbero non essere validi.\r\n </p>\r\n }\r\n\r\n @if (nodeRenames().length || innerRenames().length) {\r\n <section class=\"fb-paste__section\">\r\n <h3 class=\"fb-paste__title\">Nomi gi\u00E0 usati, quindi cambiati</h3>\r\n <p class=\"fb-field__hint\">\r\n Elementi, risorse, step, campi e screen action condividono un unico spazio di nomi: un omonimo\r\n sarebbe <code>NAME_DUPLICATED</code>. I riferimenti interni al blocco seguono il nome nuovo.\r\n </p>\r\n <ul class=\"fb-paste__list\">\r\n @for (rename of nodeRenames(); track $index) {\r\n <li><code>{{ rename.from }}</code> \u2192 <code>{{ rename.to }}</code></li>\r\n }\r\n @for (rename of innerRenames(); track $index) {\r\n <li><code>{{ rename.from }}</code> \u2192 <code>{{ rename.to }}</code> <span class=\"fb-paste__tech\">&nbsp;interno</span></li>\r\n }\r\n </ul>\r\n </section>\r\n }\r\n\r\n @if (newResources().length) {\r\n <section class=\"fb-paste__section\">\r\n <h3 class=\"fb-paste__title\">Risorse che qui non esistono</h3>\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"createMissingResources()\"\r\n (change)=\"setCreateMissingResources($any($event.target).checked)\"\r\n />\r\n <span>Crearle insieme agli elementi</span>\r\n </label>\r\n <ul class=\"fb-paste__list\">\r\n @for (resource of newResources(); track $index) {\r\n <li><code>{{ resource.name }}</code> <span class=\"fb-paste__tech\">&nbsp;{{ resourceLabel(resource.collection) }}</span></li>\r\n }\r\n </ul>\r\n @if (!createMissingResources()) {\r\n <p class=\"fb-paste__warn\">\r\n Senza di esse i riferimenti resteranno da ricollegare: il pannello dei problemi li segnalera\u2019\r\n come <code>REFERENCE_UNKNOWN</code>.\r\n </p>\r\n }\r\n </section>\r\n }\r\n\r\n @if (reusedResources().length) {\r\n <section class=\"fb-paste__section\">\r\n <h3 class=\"fb-paste__title\">Risorse gi\u00E0 presenti</h3>\r\n <p class=\"fb-field__hint\">\r\n Un nome uguale esiste gi\u00E0 in questo flow: si usa quello, non se ne crea una copia. Controlla che\r\n sia davvero la stessa cosa \u2014 il confronto e\u2019 sul nome, non sul contenuto.\r\n </p>\r\n <ul class=\"fb-paste__list\">\r\n @for (resource of reusedResources(); track $index) {\r\n <li><code>{{ resource.name }}</code></li>\r\n }\r\n </ul>\r\n </section>\r\n }\r\n\r\n @if (plan().unresolved.length) {\r\n <section class=\"fb-paste__section\">\r\n <h3 class=\"fb-paste__title\">Riferimenti che restano da sistemare</h3>\r\n <p class=\"fb-field__hint\">\r\n Puntano a qualcosa che in questo flow non c\u2019e\u2019 \u2014 quasi sempre un elemento rimasto nel flow di\r\n origine. L\u2019incolla non puo\u2019 portarseli dietro: vanno ricollegati a mano.\r\n </p>\r\n <ul class=\"fb-paste__list\">\r\n @for (name of plan().unresolved; track $index) {\r\n <li><code>{{ name }}</code></li>\r\n }\r\n </ul>\r\n </section>\r\n }\r\n </div>\r\n\r\n <footer class=\"fb-dialog__foot\">\r\n <span class=\"fb-dialog__note\">Un solo passo di annulla riporta tutto com\u2019era.</span>\r\n <div class=\"fb-paste__actions\">\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"cancel()\">Annulla</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" (click)=\"confirm()\">Incolla</button>\r\n </div>\r\n </footer>\r\n</div>\r\n", styles: [":host{position:absolute;inset:0;z-index:40;display:grid;place-items:center;padding:24px}.fb-dialog__backdrop{position:absolute;inset:0;background:#10182852;-webkit-backdrop-filter:blur(1px);backdrop-filter:blur(1px)}.fb-dialog__panel{position:relative;display:flex;flex-direction:column;width:min(560px,100%);max-height:min(720px,100%);border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-lg, 12px);background:var(--fb-surface, #fff);box-shadow:var(--fb-shadow-lg, 0 18px 44px rgb(16 24 40 / 18%));outline:none;overflow:hidden}.fb-dialog__head,.fb-dialog__foot{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 14px}.fb-dialog__head{border-bottom:1px solid var(--fb-border-subtle, #eef0f4)}.fb-dialog__foot{border-top:1px solid var(--fb-border-subtle, #eef0f4)}.fb-dialog__type{display:block;font-size:11px;text-transform:uppercase;letter-spacing:.04em;color:var(--fb-text-muted, #6b7086)}.fb-dialog__title{margin:0;font-size:15px;font-weight:600}.fb-dialog__note{color:var(--fb-text-muted, #6b7086);font-size:12px}.fb-dialog__body{display:flex;flex-direction:column;gap:14px;padding:14px;overflow:auto}.fb-paste__origin{margin:0;color:var(--fb-text-muted, #6b7086);font-size:12px}.fb-paste__elements,.fb-paste__list{margin:0;padding-left:18px;display:flex;flex-direction:column;gap:4px;font-size:13px}.fb-paste__section{display:flex;flex-direction:column;gap:6px;padding-top:12px;border-top:1px solid var(--fb-border-subtle, #eef0f4)}.fb-paste__title{margin:0;font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--fb-text-muted, #6b7086)}.fb-paste__tech{color:var(--fb-text-muted, #6b7086);font-size:11px}.fb-paste__warn{margin:0;padding:8px 10px;border-radius:var(--fb-radius-sm, 8px);background:var(--fb-surface-alt, #f7f8fa);color:var(--fb-warning, #b7791f);font-size:12px}.fb-paste__actions{display:flex;gap:8px}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
12102
+ }
12103
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImport: i0, type: PasteDialogComponent, decorators: [{
12104
+ type: Component,
12105
+ args: [{ selector: 'fb-paste-dialog', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"fb-dialog__backdrop\" (click)=\"cancel()\"></div>\r\n\r\n<div\r\n class=\"fb-dialog__panel fb-paste\"\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n aria-label=\"Incolla elementi\"\r\n tabindex=\"-1\"\r\n (click)=\"$event.stopPropagation()\"\r\n (keydown.escape)=\"cancel()\"\r\n>\r\n <header class=\"fb-dialog__head\">\r\n <div class=\"fb-dialog__identity\">\r\n <span class=\"fb-dialog__type\">Appunti</span>\r\n <h2 class=\"fb-dialog__title\">Incolla {{ elementLabel() }}</h2>\r\n </div>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" aria-label=\"Chiudi\" (click)=\"cancel()\">\u00D7</button>\r\n </header>\r\n\r\n <div class=\"fb-dialog__body fb-paste__body\">\r\n @if (payload().flowName) {\r\n <p class=\"fb-paste__origin\">Copiati da <strong>{{ payload().flowName }}</strong>.</p>\r\n }\r\n\r\n <ul class=\"fb-paste__elements\">\r\n @for (entry of payload().nodes; track $index) {\r\n <li>{{ entry.node.label || entry.node.name }} <span class=\"fb-paste__tech\">&nbsp;{{ entry.node.name }}</span></li>\r\n }\r\n </ul>\r\n\r\n @if (plan().processTypeMismatch) {\r\n <!--\r\n Le globali dipendono dal `processType` con cui le si chiede: `$Record` non esiste in uno\r\n screen flow (\u00A74.1). Non blocca \u2014 il documento e' comunque salvabile \u2014 ma un riferimento\r\n che di la' era valido puo' non esserlo qui, e dirlo prima evita la caccia al rilievo.\r\n -->\r\n <p class=\"fb-paste__warn\">\r\n Il flow di origine e\u2019 di tipo <strong>{{ plan().processTypeMismatch }}</strong>: le variabili globali\r\n disponibili qui sono altre, e i riferimenti che le usano potrebbero non essere validi.\r\n </p>\r\n }\r\n\r\n @if (nodeRenames().length || innerRenames().length) {\r\n <section class=\"fb-paste__section\">\r\n <h3 class=\"fb-paste__title\">Nomi gi\u00E0 usati, quindi cambiati</h3>\r\n <p class=\"fb-field__hint\">\r\n Elementi, risorse, step, campi e screen action condividono un unico spazio di nomi: un omonimo\r\n sarebbe <code>NAME_DUPLICATED</code>. I riferimenti interni al blocco seguono il nome nuovo.\r\n </p>\r\n <ul class=\"fb-paste__list\">\r\n @for (rename of nodeRenames(); track $index) {\r\n <li><code>{{ rename.from }}</code> \u2192 <code>{{ rename.to }}</code></li>\r\n }\r\n @for (rename of innerRenames(); track $index) {\r\n <li><code>{{ rename.from }}</code> \u2192 <code>{{ rename.to }}</code> <span class=\"fb-paste__tech\">&nbsp;interno</span></li>\r\n }\r\n </ul>\r\n </section>\r\n }\r\n\r\n @if (newResources().length) {\r\n <section class=\"fb-paste__section\">\r\n <h3 class=\"fb-paste__title\">Risorse che qui non esistono</h3>\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"createMissingResources()\"\r\n (change)=\"setCreateMissingResources($any($event.target).checked)\"\r\n />\r\n <span>Crearle insieme agli elementi</span>\r\n </label>\r\n <ul class=\"fb-paste__list\">\r\n @for (resource of newResources(); track $index) {\r\n <li><code>{{ resource.name }}</code> <span class=\"fb-paste__tech\">&nbsp;{{ resourceLabel(resource.collection) }}</span></li>\r\n }\r\n </ul>\r\n @if (!createMissingResources()) {\r\n <p class=\"fb-paste__warn\">\r\n Senza di esse i riferimenti resteranno da ricollegare: il pannello dei problemi li segnalera\u2019\r\n come <code>REFERENCE_UNKNOWN</code>.\r\n </p>\r\n }\r\n </section>\r\n }\r\n\r\n @if (reusedResources().length) {\r\n <section class=\"fb-paste__section\">\r\n <h3 class=\"fb-paste__title\">Risorse gi\u00E0 presenti</h3>\r\n <p class=\"fb-field__hint\">\r\n Un nome uguale esiste gi\u00E0 in questo flow: si usa quello, non se ne crea una copia. Controlla che\r\n sia davvero la stessa cosa \u2014 il confronto e\u2019 sul nome, non sul contenuto.\r\n </p>\r\n <ul class=\"fb-paste__list\">\r\n @for (resource of reusedResources(); track $index) {\r\n <li><code>{{ resource.name }}</code></li>\r\n }\r\n </ul>\r\n </section>\r\n }\r\n\r\n @if (plan().unresolved.length) {\r\n <section class=\"fb-paste__section\">\r\n <h3 class=\"fb-paste__title\">Riferimenti che restano da sistemare</h3>\r\n <p class=\"fb-field__hint\">\r\n Puntano a qualcosa che in questo flow non c\u2019e\u2019 \u2014 quasi sempre un elemento rimasto nel flow di\r\n origine. L\u2019incolla non puo\u2019 portarseli dietro: vanno ricollegati a mano.\r\n </p>\r\n <ul class=\"fb-paste__list\">\r\n @for (name of plan().unresolved; track $index) {\r\n <li><code>{{ name }}</code></li>\r\n }\r\n </ul>\r\n </section>\r\n }\r\n </div>\r\n\r\n <footer class=\"fb-dialog__foot\">\r\n <span class=\"fb-dialog__note\">Un solo passo di annulla riporta tutto com\u2019era.</span>\r\n <div class=\"fb-paste__actions\">\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"cancel()\">Annulla</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" (click)=\"confirm()\">Incolla</button>\r\n </div>\r\n </footer>\r\n</div>\r\n", styles: [":host{position:absolute;inset:0;z-index:40;display:grid;place-items:center;padding:24px}.fb-dialog__backdrop{position:absolute;inset:0;background:#10182852;-webkit-backdrop-filter:blur(1px);backdrop-filter:blur(1px)}.fb-dialog__panel{position:relative;display:flex;flex-direction:column;width:min(560px,100%);max-height:min(720px,100%);border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-lg, 12px);background:var(--fb-surface, #fff);box-shadow:var(--fb-shadow-lg, 0 18px 44px rgb(16 24 40 / 18%));outline:none;overflow:hidden}.fb-dialog__head,.fb-dialog__foot{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 14px}.fb-dialog__head{border-bottom:1px solid var(--fb-border-subtle, #eef0f4)}.fb-dialog__foot{border-top:1px solid var(--fb-border-subtle, #eef0f4)}.fb-dialog__type{display:block;font-size:11px;text-transform:uppercase;letter-spacing:.04em;color:var(--fb-text-muted, #6b7086)}.fb-dialog__title{margin:0;font-size:15px;font-weight:600}.fb-dialog__note{color:var(--fb-text-muted, #6b7086);font-size:12px}.fb-dialog__body{display:flex;flex-direction:column;gap:14px;padding:14px;overflow:auto}.fb-paste__origin{margin:0;color:var(--fb-text-muted, #6b7086);font-size:12px}.fb-paste__elements,.fb-paste__list{margin:0;padding-left:18px;display:flex;flex-direction:column;gap:4px;font-size:13px}.fb-paste__section{display:flex;flex-direction:column;gap:6px;padding-top:12px;border-top:1px solid var(--fb-border-subtle, #eef0f4)}.fb-paste__title{margin:0;font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--fb-text-muted, #6b7086)}.fb-paste__tech{color:var(--fb-text-muted, #6b7086);font-size:11px}.fb-paste__warn{margin:0;padding:8px 10px;border-radius:var(--fb-radius-sm, 8px);background:var(--fb-surface-alt, #f7f8fa);color:var(--fb-warning, #b7791f);font-size:12px}.fb-paste__actions{display:flex;gap:8px}\n"] }]
12106
+ }], propDecorators: { payload: [{ type: i0.Input, args: [{ isSignal: true, alias: "payload", required: true }] }], plan: [{ type: i0.Input, args: [{ isSignal: true, alias: "plan", required: true }] }], confirmed: [{ type: i0.Output, args: ["confirmed"] }], cancelled: [{ type: i0.Output, args: ["cancelled"] }] } });
12107
+
11468
12108
  /**
11469
12109
  * Le risorse — FRONTEND.md §4.6.
11470
12110
  *
@@ -11938,36 +12578,57 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImpo
11938
12578
  }], propDecorators: { closed: [{ type: i0.Output, args: ["closed"] }], versionOpened: [{ type: i0.Output, args: ["versionOpened"] }], notice: [{ type: i0.Output, args: ["notice"] }] } });
11939
12579
 
11940
12580
  /**
11941
- * Provare un flow dall'editor FRONTEND.md §6.5, §11 ("Provare").
11942
- *
11943
- * Sei cose che questo pannello tratta come dette:
11944
- * 1. `status: 'Failed'` **non** e' un errore di trasporto: la richiesta e' riuscita, il flow
11945
- * e' fallito. E `NotStarted` significa che i criteri dello Start non erano soddisfatti,
11946
- * che non e' un errore.
11947
- * 2. il `continuationToken` va rimandato **tale e quale**: e' opaco, non viene letto ne'
11948
- * modificato, e non finisce in nessun URL.
11949
- * 3. `canGoBack`/`canFinish`/`canPause` di `pendingScreen` sono la verita' su cosa
11950
- * abilitare, piu' precisa dei flag del metadata.
11951
- * 4. con `Previous` e `Pause` gli output **non** vengono memorizzati: i valori inseriti non
11952
- * sono definitivi, e il pannello lo dice.
11953
- * 5. `debug: true` popola `trace` e `resources`**e riporta anche i dati personali**:
11954
- * l'avviso e' in chiaro, non nascosto in una tooltip.
11955
- * 6. `Suspended` copre due attese diverse — un evento e uno step di orchestrazione e le
11956
- * distinguono `isWaitingForEvent` / `isWaitingForStageStep`. Nel secondo caso si puo'
11957
- * concludere lo step da qui, e la risposta puo' portare una `interviewKey` **nuova**: la
11958
- * vecchia non e' piu' valida, quindi si sostituisce invece di conservarla (§5.14).
12581
+ * La finestra di «Esegui» e «Debug»: i valori di ingresso, prima di passare la mano all'ospite.
12582
+ *
12583
+ * L'editor non esegue niente. Qui si raccoglie l'unica cosa che l'esecuzione non puo' inventare
12584
+ * i valori delle variabili `isInput`, cioe' il contratto del flow verso chi lo invoca (§4.6)
12585
+ * e li si consegna a `FlowBuilderApi.runFlow` / `debugFlow`. Chi integra l'editor decide cosa
12586
+ * significhi «eseguire»; senza implementazione il comando fallisce con `MissingService` e
12587
+ * l'editor lo dice, invece di fingere un avvio.
12588
+ *
12589
+ * Tre cose che il form generato tratta come dette:
12590
+ * 1. il tipo dichiarato **vince** anche in scrittura (§6.5): un `Number` si manda come numero,
12591
+ * non come la stringa digitata, altrimenti il motore confronta una stringa con un numero.
12592
+ * 2. un `Structure` non ha un letterale (§4.7): si compila un membro alla volta e si manda
12593
+ * `className` piu' i soli membri valorizzati la stessa forma che torna in lettura.
12594
+ * 3. un `Enum` si passa **per nome** (§4.6, §13.18), mai come numero; l'elenco vuoto significa
12595
+ * "non lo so" e allora il valore si scrive a mano.
11959
12596
  */
11960
- class DebugPanelComponent {
11961
- api = inject(FlowBuilderApi);
12597
+ class RunDialogComponent {
11962
12598
  store = inject(FlowDocumentStore);
11963
- session = inject(FlowEditorSession);
11964
12599
  dictionaries = inject(FlowDictionaryStore);
11965
12600
  catalog = inject(FlowCatalogStore);
12601
+ /** `debug` cambia il titolo, l'avviso sui dati e quale primitiva verra' chiamata. */
12602
+ mode = input.required(...(ngDevMode ? [{ debugName: "mode" }] : []));
12603
+ /** Il flow che verra' eseguito: e' quello **salvato**, non il documento in mano. */
12604
+ flowName = input(null, ...(ngDevMode ? [{ debugName: "flowName" }] : []));
12605
+ version = input(null, ...(ngDevMode ? [{ debugName: "version" }] : []));
12606
+ /** Con modifiche non salvate si esegue la versione salvata: dirlo evita la sorpresa. */
12607
+ isDirty = input(false, ...(ngDevMode ? [{ debugName: "isDirty" }] : []));
12608
+ /** L'ospite sta ancora rispondendo: il bottone resta spento invece di ripartire. */
12609
+ isBusy = input(false, ...(ngDevMode ? [{ debugName: "isBusy" }] : []));
12610
+ confirmed = output();
12611
+ cancelled = output();
12612
+ isDebug = computed(() => this.mode() === 'debug', ...(ngDevMode ? [{ debugName: "isDebug" }] : []));
12613
+ /** I valori compilati, come stringhe: la conversione al tipo avviene alla conferma. */
12614
+ rawValues = signal({}, ...(ngDevMode ? [{ debugName: "rawValues" }] : []));
12615
+ /** Le variabili `isInput` del flow: il contratto verso chi lo invoca (§4.6). */
12616
+ inputVariables = computed(() => this.store.document().variables?.filter((variable) => variable.isInput) ?? [], ...(ngDevMode ? [{ debugName: "inputVariables" }] : []));
12617
+ /**
12618
+ * §4.7 — i membri delle classi delle variabili `Structure` di input, per classe. Un'istanza si
12619
+ * compila un membro alla volta: non c'e' un letterale con cui scriverla in un campo solo.
12620
+ */
12621
+ membersByClass = signal({}, ...(ngDevMode ? [{ debugName: "membersByClass" }] : []));
12622
+ /**
12623
+ * §4.6 — i valori dei tipi `Enum` che compaiono fra gli input. Un insieme chiuso merita una
12624
+ * tendina: qui si scrive un nome che il motore confrontera', e digitarlo e' il modo piu'
12625
+ * facile di sbagliarlo.
12626
+ */
12627
+ valuesByEnumType = signal({}, ...(ngDevMode ? [{ debugName: "valuesByEnumType" }] : []));
11966
12628
  constructor() {
11967
- // Le classi delle variabili di input: servono a generare il form dei membri.
11968
12629
  effect(() => {
11969
12630
  const classes = new Set(this.inputVariables()
11970
- .filter((variable) => this.dictionaries.isStructure(variable.dataType))
12631
+ .filter((variable) => this.isStructureVariable(variable))
11971
12632
  .map((variable) => variable.objectType)
11972
12633
  .filter((className) => !!className));
11973
12634
  for (const className of classes) {
@@ -11977,15 +12638,10 @@ class DebugPanelComponent {
11977
12638
  void this.catalog
11978
12639
  .listStructureMembers(className)
11979
12640
  .then((list) => this.membersByClass.update((current) => ({ ...current, [className]: list ?? [] })))
11980
- // Catalogo non disponibile: nessun campo generato, e il pannello lo dice.
12641
+ // Catalogo non disponibile: nessun campo generato, e la finestra lo dice.
11981
12642
  .catch(() => this.membersByClass.update((current) => ({ ...current, [className]: [] })));
11982
12643
  }
11983
12644
  });
11984
- /**
11985
- * §4.6 — i valori dei tipi `Enum` che compaiono fra gli input, variabili e membri delle classi
11986
- * insieme: qui si compila un valore che il motore confrontera' per nome, e un insieme chiuso
11987
- * merita una tendina invece di una casella di testo. Un tipo senza valori resta digitabile.
11988
- */
11989
12645
  effect(() => {
11990
12646
  const types = new Set([
11991
12647
  ...this.inputVariables().map((variable) => variable.dataType === 'Enum' ? variable.objectType : undefined),
@@ -12006,130 +12662,25 @@ class DebugPanelComponent {
12006
12662
  }
12007
12663
  });
12008
12664
  }
12009
- closed = output();
12010
- /** Chiede all'host di evidenziare l'elemento corrente dell'esecuzione. */
12011
- elementFocused = output();
12012
- result = signal(null, ...(ngDevMode ? [{ debugName: "result" }] : []));
12013
- isRunning = signal(false, ...(ngDevMode ? [{ debugName: "isRunning" }] : []));
12014
- errorMessage = signal(null, ...(ngDevMode ? [{ debugName: "errorMessage" }] : []));
12015
- debugEnabled = signal(true, ...(ngDevMode ? [{ debugName: "debugEnabled" }] : []));
12016
- /** I valori che l'utente compila nel piccolo form generato dalle variabili di input. */
12017
- inputValues = signal({}, ...(ngDevMode ? [{ debugName: "inputValues" }] : []));
12018
- /** I valori raccolti per lo screen corrente, per nome di `outputParameters`. */
12019
- screenOutputs = signal({}, ...(ngDevMode ? [{ debugName: "screenOutputs" }] : []));
12020
- /** Le variabili `isInput` del flow: il contratto verso chi lo invoca (§4.6). */
12021
- inputVariables = computed(() => this.store.document().variables?.filter((variable) => variable.isInput) ?? [], ...(ngDevMode ? [{ debugName: "inputVariables" }] : []));
12022
- /**
12023
- * §4.7 — i membri delle classi delle variabili `Structure` di input, per classe. Un'istanza si
12024
- * compila un membro alla volta: non c'e' un letterale con cui scriverla in un campo solo.
12025
- */
12026
- membersByClass = signal({}, ...(ngDevMode ? [{ debugName: "membersByClass" }] : []));
12027
- /** I membri **scrivibili** di una classe: gli altri li calcola il backend. */
12665
+ /** I membri **scrivibili** di una classe: gli altri li calcola il motore. */
12028
12666
  membersOf(className) {
12029
12667
  if (!className) {
12030
12668
  return [];
12031
12669
  }
12032
12670
  return (this.membersByClass()[className] ?? []).filter((member) => member.isWritable !== false);
12033
12671
  }
12034
- /**
12035
- * §4.6 — i valori di un tipo di enumerazione, per nome del tipo. Elenco vuoto significa "non lo
12036
- * so" — tipo non indicato, primitiva assente, tipo senza valori dichiarati — e allora il valore
12037
- * si scrive a mano.
12038
- */
12039
- valuesByEnumType = signal({}, ...(ngDevMode ? [{ debugName: "valuesByEnumType" }] : []));
12040
12672
  enumValuesOf(enumType) {
12041
12673
  return enumType ? (this.valuesByEnumType()[enumType] ?? []) : [];
12042
12674
  }
12043
12675
  isStructureVariable(variable) {
12044
12676
  return this.dictionaries.isStructure(variable.dataType);
12045
12677
  }
12046
- canRun = computed(() => !this.session.isNew() && !this.isRunning(), ...(ngDevMode ? [{ debugName: "canRun" }] : []));
12047
- status = computed(() => this.result()?.status ?? null, ...(ngDevMode ? [{ debugName: "status" }] : []));
12048
- pendingScreen = computed(() => this.result()?.pendingScreen ?? null, ...(ngDevMode ? [{ debugName: "pendingScreen" }] : []));
12049
- isWaitingForScreen = computed(() => this.status() === 'WaitingForScreen', ...(ngDevMode ? [{ debugName: "isWaitingForScreen" }] : []));
12050
- /** §5.14 — l'attesa e' su uno step assegnato a una persona, non su un evento. */
12051
- isWaitingForStageStep = computed(() => this.result()?.isWaitingForStageStep === true, ...(ngDevMode ? [{ debugName: "isWaitingForStageStep" }] : []));
12052
- stageSteps = computed(() => this.result()?.stageSteps ?? [], ...(ngDevMode ? [{ debugName: "stageSteps" }] : []));
12053
- /** Lo step su cui c'e' un work item aperto: e' quello che si puo' concludere. */
12054
- waitingStageSteps = computed(() => this.stageSteps().filter((step) => step.isWaiting), ...(ngDevMode ? [{ debugName: "waitingStageSteps" }] : []));
12055
- /** I valori con cui si conclude uno step, per nome di output. */
12056
- stepOutputs = signal({}, ...(ngDevMode ? [{ debugName: "stepOutputs" }] : []));
12057
- trace = computed(() => this.result()?.trace ?? [], ...(ngDevMode ? [{ debugName: "trace" }] : []));
12058
- /** Le risorse come righe ordinate: il tipo dichiarato vince in lettura (§6.5). */
12059
- resourceRows = computed(() => {
12060
- const resources = this.result()?.resources ?? {};
12061
- return Object.entries(resources)
12062
- .map(([name, typed]) => ({
12063
- name,
12064
- type: typed?.type ?? '',
12065
- value: DebugPanelComponent.render(typed),
12066
- }))
12067
- .sort((left, right) => left.name.localeCompare(right.name));
12068
- }, ...(ngDevMode ? [{ debugName: "resourceRows" }] : []));
12069
- outputRows = computed(() => {
12070
- const outputs = this.result()?.outputs ?? {};
12071
- return Object.entries(outputs).map(([name, typed]) => ({
12072
- name,
12073
- type: typed?.type ?? '',
12074
- value: DebugPanelComponent.render(typed),
12075
- }));
12076
- }, ...(ngDevMode ? [{ debugName: "outputRows" }] : []));
12077
- /** Gli input che lo screen corrente riceve, per mostrarli accanto al form generico. */
12078
- screenInputRows = computed(() => {
12079
- const inputs = this.pendingScreen()?.inputs ?? {};
12080
- return Object.entries(inputs).map(([name, typed]) => ({
12081
- name,
12082
- type: typed?.type ?? '',
12083
- value: DebugPanelComponent.render(typed),
12084
- }));
12085
- }, ...(ngDevMode ? [{ debugName: "screenInputRows" }] : []));
12086
- /** I nomi degli output dichiarati dallo screen, per generare i campi da compilare. */
12087
- screenOutputNames = computed(() => {
12088
- const elementName = this.pendingScreen()?.elementName;
12089
- if (!elementName) {
12090
- return [];
12091
- }
12092
- const screen = this.store.document().screens?.find((candidate) => candidate.name === elementName);
12093
- return (screen?.outputParameters ?? []).map((parameter) => parameter.name ?? '').filter(Boolean);
12094
- }, ...(ngDevMode ? [{ debugName: "screenOutputNames" }] : []));
12095
- /**
12096
- * §6.5 — il tipo dichiarato vince, e due tipi hanno una forma propria invece di `value`: un
12097
- * record e un'istanza di classe. Cercare `value` su un `Structure` mostrerebbe «—» pur avendo
12098
- * i dati sotto gli occhi (§4.7).
12099
- */
12100
- static render(typed) {
12101
- if (!typed) {
12102
- return '—';
12103
- }
12104
- if (typed.instance) {
12105
- const members = Object.entries(typed.instance.members ?? {})
12106
- .map(([name, member]) => `${name}: ${DebugPanelComponent.render(member)}`)
12107
- .join(', ');
12108
- return `${typed.instance.className ?? 'istanza'} { ${members} }`;
12109
- }
12110
- if (typed.record) {
12111
- const fields = Object.entries(typed.record.fields ?? {})
12112
- .map(([name, value]) => `${name}: ${value === null || value === undefined ? '—' : String(value)}`)
12113
- .join(', ');
12114
- const head = [typed.record.objectType, typed.record.id].filter(Boolean).join('#');
12115
- return `${head || 'record'} { ${fields} }`;
12116
- }
12117
- if (typed.value === undefined || typed.value === null) {
12118
- return '—';
12119
- }
12120
- if (typeof typed.value === 'object') {
12121
- return JSON.stringify(typed.value);
12122
- }
12123
- return String(typed.value);
12124
- }
12125
- setDebugEnabled(value) {
12126
- this.debugEnabled.set(value);
12127
- }
12128
- setInputValue(name, value) {
12129
- this.inputValues.update((values) => ({ ...values, [name]: value }));
12678
+ setValue(key, value) {
12679
+ this.rawValues.update((values) => ({ ...values, [key]: value }));
12130
12680
  }
12131
- setScreenOutput(name, value) {
12132
- this.screenOutputs.update((values) => ({ ...values, [name]: value }));
12681
+ /** Il valore di un campo del form: serve a ripopolarlo se la finestra si ridisegna. */
12682
+ valueOf(key) {
12683
+ return this.rawValues()[key] ?? '';
12133
12684
  }
12134
12685
  /** Converte un valore del form nel tipo dichiarato: il tipo vince, anche in scrittura (§6.5). */
12135
12686
  static coerce(raw, dataType) {
@@ -12148,21 +12699,19 @@ class DebugPanelComponent {
12148
12699
  return raw;
12149
12700
  }
12150
12701
  }
12151
- /** Converte i valori del form nel tipo dichiarato dalla variabile. */
12152
- coerceInputs() {
12153
- const values = this.inputValues();
12702
+ /**
12703
+ * I valori da mandare. Un campo lasciato vuoto **non** si manda: mandare la stringa vuota
12704
+ * significherebbe assegnare, e un input non valorizzato e' un'altra cosa da un input a "".
12705
+ */
12706
+ collectInputs() {
12707
+ const values = this.rawValues();
12154
12708
  const result = {};
12155
12709
  for (const variable of this.inputVariables()) {
12156
12710
  const name = variable.name;
12157
12711
  if (!name) {
12158
12712
  continue;
12159
12713
  }
12160
- /**
12161
- * §4.7 — un'istanza non ha un valore letterale: si scrive `className` piu' i soli membri
12162
- * che si vogliono valorizzare, ed e' la **stessa** forma che il motore restituisce in
12163
- * lettura. I membri stanno nel form sotto la chiave `Variabile.Membro`.
12164
- */
12165
- if (this.dictionaries.isStructure(variable.dataType)) {
12714
+ if (this.isStructureVariable(variable)) {
12166
12715
  const className = variable.objectType;
12167
12716
  if (!className) {
12168
12717
  continue;
@@ -12173,7 +12722,7 @@ class DebugPanelComponent {
12173
12722
  if (raw === undefined || raw === '') {
12174
12723
  continue;
12175
12724
  }
12176
- const value = DebugPanelComponent.coerce(raw, member.dataType);
12725
+ const value = RunDialogComponent.coerce(raw, member.dataType);
12177
12726
  if (value !== undefined) {
12178
12727
  members[member.name] = { type: member.dataType, value };
12179
12728
  }
@@ -12187,138 +12736,85 @@ class DebugPanelComponent {
12187
12736
  if (raw === undefined || raw === '') {
12188
12737
  continue;
12189
12738
  }
12190
- const value = DebugPanelComponent.coerce(raw, variable.dataType);
12739
+ const value = RunDialogComponent.coerce(raw, variable.dataType);
12191
12740
  if (value !== undefined) {
12192
12741
  result[name] = value;
12193
12742
  }
12194
12743
  }
12195
12744
  return result;
12196
12745
  }
12197
- async start() {
12198
- const flowName = this.session.flowName();
12199
- if (!flowName) {
12200
- this.errorMessage.set('Salva il flow prima di provarlo.');
12201
- return;
12202
- }
12203
- this.isRunning.set(true);
12204
- this.errorMessage.set(null);
12205
- this.screenOutputs.set({});
12206
- try {
12207
- const result = await this.api.startInterview({
12208
- flowName,
12209
- version: this.session.version() ?? undefined,
12210
- inputs: this.coerceInputs(),
12211
- debug: this.debugEnabled(),
12212
- });
12213
- this.apply(result);
12214
- }
12215
- catch (error) {
12216
- this.handleError(error);
12217
- }
12218
- finally {
12219
- this.isRunning.set(false);
12220
- }
12221
- }
12222
- async respond(navigation) {
12223
- const current = this.result();
12224
- // Opaco: si rimanda tale e quale, senza leggerlo.
12225
- const token = current?.continuationToken;
12226
- if (!token) {
12227
- return;
12228
- }
12229
- this.isRunning.set(true);
12230
- this.errorMessage.set(null);
12231
- try {
12232
- const result = await this.api.respondToScreen({
12233
- continuationToken: token,
12234
- screenElementName: current?.pendingScreen?.elementName,
12235
- navigation,
12236
- // Con `Previous` e `Pause` il motore non memorizza gli output: si mandano comunque,
12237
- // ma il pannello avverte che non sono definitivi.
12238
- outputs: this.screenOutputs(),
12239
- debug: this.debugEnabled(),
12240
- });
12241
- this.screenOutputs.set({});
12242
- this.apply(result);
12243
- }
12244
- catch (error) {
12245
- this.handleError(error);
12246
- }
12247
- finally {
12248
- this.isRunning.set(false);
12249
- }
12250
- }
12251
- setStepOutput(name, value) {
12252
- this.stepOutputs.update((values) => ({ ...values, [name]: value }));
12746
+ confirm() {
12747
+ this.confirmed.emit(this.collectInputs());
12253
12748
  }
12254
- /** Gli output dichiarati dallo step, per generare i campi da compilare. */
12255
- stepOutputNames(stepName) {
12256
- if (!stepName) {
12257
- return [];
12258
- }
12259
- for (const stage of this.store.document().orchestratedStages ?? []) {
12260
- const step = stage.stageSteps?.find((candidate) => candidate.name === stepName);
12261
- if (step) {
12262
- return (step.outputParameters ?? []).map((parameter) => parameter.name ?? '').filter(Boolean);
12263
- }
12264
- }
12265
- return [];
12749
+ cancel() {
12750
+ this.cancelled.emit();
12266
12751
  }
12752
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.27", ngImport: i0, type: RunDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
12753
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.27", type: RunDialogComponent, isStandalone: true, selector: "fb-run-dialog", inputs: { mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: true, transformFunction: null }, flowName: { classPropertyName: "flowName", publicName: "flowName", isSignal: true, isRequired: false, transformFunction: null }, version: { classPropertyName: "version", publicName: "version", isSignal: true, isRequired: false, transformFunction: null }, isDirty: { classPropertyName: "isDirty", publicName: "isDirty", isSignal: true, isRequired: false, transformFunction: null }, isBusy: { classPropertyName: "isBusy", publicName: "isBusy", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { confirmed: "confirmed", cancelled: "cancelled" }, ngImport: i0, template: "<div class=\"fb-dialog__backdrop\" (click)=\"cancel()\"></div>\r\n\r\n<div\r\n class=\"fb-dialog__panel fb-run\"\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n [attr.aria-label]=\"isDebug() ? 'Esegui in debug' : 'Esegui il flow'\"\r\n tabindex=\"-1\"\r\n (click)=\"$event.stopPropagation()\"\r\n (keydown.escape)=\"cancel()\"\r\n>\r\n <header class=\"fb-dialog__head\">\r\n <div class=\"fb-dialog__identity\">\r\n <span class=\"fb-dialog__type\">{{ isDebug() ? 'Debug' : 'Esecuzione' }}</span>\r\n <h2 class=\"fb-dialog__title\">\r\n {{ isDebug() ? 'Esegui in debug' : 'Esegui' }}\r\n @if (flowName()) {\r\n \u00AB{{ flowName() }}\u00BB\r\n }\r\n @if (version() != null) {\r\n <span class=\"fb-run__version\">v{{ version() }}</span>\r\n }\r\n </h2>\r\n </div>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" aria-label=\"Chiudi\" (click)=\"cancel()\">\u00D7</button>\r\n </header>\r\n\r\n <div class=\"fb-dialog__body fb-run__body\">\r\n @if (isDirty()) {\r\n <!--\r\n Si esegue cio\u2019 che e\u2019 salvato: il documento in mano non e\u2019 ancora da nessuna parte.\r\n Dirlo qui evita la caccia al \u00ABperche\u2019 non vedo la modifica appena fatta\u00BB.\r\n -->\r\n <p class=\"fb-run__warn\">\r\n Ci sono modifiche non salvate: viene eseguita la <strong>versione salvata</strong>, non il documento\r\n aperto.\r\n </p>\r\n }\r\n\r\n <section class=\"fb-run__section\">\r\n <h3 class=\"fb-run__title\">Valori di ingresso</h3>\r\n @if (inputVariables().length) {\r\n <p class=\"fb-field__hint\">\r\n Le variabili di input del flow. Un campo lasciato vuoto non viene mandato: la variabile resta\r\n senza valore, che non e\u2019 la stessa cosa di una stringa vuota.\r\n </p>\r\n @for (variable of inputVariables(); track variable.name) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">\r\n {{ variable.name }}\r\n <span class=\"fb-run__type\">{{ variable.dataType }}{{ variable.isCollection ? '[]' : '' }}</span>\r\n </label>\r\n @if (isStructureVariable(variable)) {\r\n <!--\r\n \u00A74.7: un\u2019istanza non ha un letterale. Si compila un membro alla volta e si manda\r\n `className` piu\u2019 i soli membri valorizzati \u2014 la stessa forma che torna in lettura.\r\n -->\r\n <p class=\"fb-field__hint\">\r\n Istanza di <code>{{ variable.objectType }}</code>: valorizza i membri che ti servono.\r\n </p>\r\n @for (member of membersOf(variable.objectType); track member.name) {\r\n <div class=\"fb-field fb-run__member\">\r\n <label class=\"fb-field__label\">\r\n {{ member.name }}\r\n <span class=\"fb-run__type\">{{ member.dataType }}{{ member.isCollection ? '[]' : '' }}</span>\r\n </label>\r\n @if (member.dataType === 'Boolean') {\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"valueOf(variable.name + '.' + member.name)\"\r\n (change)=\"setValue(variable.name + '.' + member.name, $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014</option>\r\n <option value=\"true\">vero</option>\r\n <option value=\"false\">falso</option>\r\n </select>\r\n } @else if (enumValuesOf(member.objectType).length) {\r\n <!-- \u00A74.6: i valori del tipo del membro, per nome. Il numero non si manda mai. -->\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"valueOf(variable.name + '.' + member.name)\"\r\n (change)=\"setValue(variable.name + '.' + member.name, $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014</option>\r\n @for (entry of enumValuesOf(member.objectType); track entry.name) {\r\n <option [value]=\"entry.name\">{{ entry.label || entry.name }}</option>\r\n }\r\n </select>\r\n } @else {\r\n <input\r\n class=\"fb-input\"\r\n [type]=\"member.dataType === 'Number' || member.dataType === 'Integer' ? 'number' : 'text'\"\r\n [value]=\"valueOf(variable.name + '.' + member.name)\"\r\n (input)=\"setValue(variable.name + '.' + member.name, $any($event.target).value)\"\r\n />\r\n }\r\n </div>\r\n }\r\n @if (!membersOf(variable.objectType).length) {\r\n <p class=\"fb-field__hint\">\r\n Membri non disponibili: senza il catalogo della classe non c\u2019e\u2019 un form da generare.\r\n </p>\r\n }\r\n } @else if (variable.dataType === 'Boolean') {\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"valueOf(variable.name!)\"\r\n (change)=\"setValue(variable.name!, $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014</option>\r\n <option value=\"true\">vero</option>\r\n <option value=\"false\">falso</option>\r\n </select>\r\n } @else if (enumValuesOf(variable.objectType).length) {\r\n <!--\r\n \u00A74.6: un enum si passa **per nome**, e i nomi ammessi sono quelli del tipo. Senza i\r\n valori \u2014 primitiva assente o tipo senza valori \u2014 si torna alla casella di testo.\r\n -->\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"valueOf(variable.name!)\"\r\n (change)=\"setValue(variable.name!, $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014</option>\r\n @for (entry of enumValuesOf(variable.objectType); track entry.name) {\r\n <option [value]=\"entry.name\">{{ entry.label || entry.name }}</option>\r\n }\r\n </select>\r\n } @else {\r\n <input\r\n class=\"fb-input\"\r\n [type]=\"variable.dataType === 'Number' || variable.dataType === 'Integer' ? 'number' : 'text'\"\r\n [value]=\"valueOf(variable.name!)\"\r\n (input)=\"setValue(variable.name!, $any($event.target).value)\"\r\n />\r\n }\r\n </div>\r\n }\r\n } @else {\r\n <p class=\"fb-field__hint\">Il flow non dichiara variabili di input: non c\u2019e\u2019 niente da compilare.</p>\r\n }\r\n </section>\r\n\r\n @if (isDebug()) {\r\n <p class=\"fb-run__warn\">\r\n Il debug puo\u2019 riportare i valori di <strong>tutte</strong> le risorse, dati personali compresi: non\r\n usarlo su dati reali.\r\n </p>\r\n }\r\n </div>\r\n\r\n <footer class=\"fb-dialog__foot\">\r\n <span class=\"fb-dialog__note\">\r\n L\u2019esecuzione e\u2019 dell\u2019applicazione che ospita l\u2019editor: qui si raccolgono solo gli ingressi.\r\n </span>\r\n <div class=\"fb-run__actions\">\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"cancel()\">Annulla</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" [disabled]=\"isBusy()\" (click)=\"confirm()\">\r\n {{ isDebug() ? 'Avvia in debug' : 'Avvia' }}\r\n </button>\r\n </div>\r\n </footer>\r\n</div>\r\n", styles: [":host{position:absolute;inset:0;z-index:40;display:grid;place-items:center;padding:24px}.fb-dialog__backdrop{position:absolute;inset:0;background:#10182852;-webkit-backdrop-filter:blur(1px);backdrop-filter:blur(1px)}.fb-dialog__panel{position:relative;display:flex;flex-direction:column;width:min(520px,100%);max-height:min(720px,100%);border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-lg, 12px);background:var(--fb-surface, #fff);box-shadow:var(--fb-shadow-lg, 0 18px 44px rgb(16 24 40 / 18%));outline:none;overflow:hidden}.fb-dialog__head,.fb-dialog__foot{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 14px}.fb-dialog__head{border-bottom:1px solid var(--fb-border-subtle, #eef0f4)}.fb-dialog__foot{border-top:1px solid var(--fb-border-subtle, #eef0f4)}.fb-dialog__type{display:block;font-size:11px;text-transform:uppercase;letter-spacing:.04em;color:var(--fb-text-muted, #6b7086)}.fb-dialog__title{margin:0;font-size:15px;font-weight:600}.fb-dialog__note{color:var(--fb-text-muted, #6b7086);font-size:12px}.fb-dialog__foot .fb-dialog__note{flex:1 1 auto;min-width:0}.fb-dialog__body{display:flex;flex-direction:column;gap:14px;padding:14px;overflow:auto}.fb-run__version{margin-left:4px;color:var(--fb-text-muted, #6b7086);font-weight:500;font-size:12px}.fb-run__section{display:flex;flex-direction:column;gap:8px}.fb-run__title{margin:0;font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--fb-text-muted, #6b7086)}.fb-run__type{color:var(--fb-text-muted, #6b7086);font-size:11px;font-weight:400}.fb-run__member{padding-left:12px;border-left:2px solid var(--fb-border-subtle, #eef0f4)}.fb-run__warn{margin:0;padding:8px 10px;border-radius:var(--fb-radius-sm, 8px);background:var(--fb-surface-alt, #f7f8fa);color:var(--fb-warning, #b7791f);font-size:12px}.fb-run__actions{display:flex;flex:0 0 auto;gap:8px}.fb-run__actions .fb-btn{white-space:nowrap}\n"], dependencies: [{ kind: "directive", type: SelectValueDirective, selector: "select[fbValue]", inputs: ["fbValue"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
12754
+ }
12755
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImport: i0, type: RunDialogComponent, decorators: [{
12756
+ type: Component,
12757
+ args: [{ selector: 'fb-run-dialog', standalone: true, imports: [SelectValueDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"fb-dialog__backdrop\" (click)=\"cancel()\"></div>\r\n\r\n<div\r\n class=\"fb-dialog__panel fb-run\"\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n [attr.aria-label]=\"isDebug() ? 'Esegui in debug' : 'Esegui il flow'\"\r\n tabindex=\"-1\"\r\n (click)=\"$event.stopPropagation()\"\r\n (keydown.escape)=\"cancel()\"\r\n>\r\n <header class=\"fb-dialog__head\">\r\n <div class=\"fb-dialog__identity\">\r\n <span class=\"fb-dialog__type\">{{ isDebug() ? 'Debug' : 'Esecuzione' }}</span>\r\n <h2 class=\"fb-dialog__title\">\r\n {{ isDebug() ? 'Esegui in debug' : 'Esegui' }}\r\n @if (flowName()) {\r\n \u00AB{{ flowName() }}\u00BB\r\n }\r\n @if (version() != null) {\r\n <span class=\"fb-run__version\">v{{ version() }}</span>\r\n }\r\n </h2>\r\n </div>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" aria-label=\"Chiudi\" (click)=\"cancel()\">\u00D7</button>\r\n </header>\r\n\r\n <div class=\"fb-dialog__body fb-run__body\">\r\n @if (isDirty()) {\r\n <!--\r\n Si esegue cio\u2019 che e\u2019 salvato: il documento in mano non e\u2019 ancora da nessuna parte.\r\n Dirlo qui evita la caccia al \u00ABperche\u2019 non vedo la modifica appena fatta\u00BB.\r\n -->\r\n <p class=\"fb-run__warn\">\r\n Ci sono modifiche non salvate: viene eseguita la <strong>versione salvata</strong>, non il documento\r\n aperto.\r\n </p>\r\n }\r\n\r\n <section class=\"fb-run__section\">\r\n <h3 class=\"fb-run__title\">Valori di ingresso</h3>\r\n @if (inputVariables().length) {\r\n <p class=\"fb-field__hint\">\r\n Le variabili di input del flow. Un campo lasciato vuoto non viene mandato: la variabile resta\r\n senza valore, che non e\u2019 la stessa cosa di una stringa vuota.\r\n </p>\r\n @for (variable of inputVariables(); track variable.name) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">\r\n {{ variable.name }}\r\n <span class=\"fb-run__type\">{{ variable.dataType }}{{ variable.isCollection ? '[]' : '' }}</span>\r\n </label>\r\n @if (isStructureVariable(variable)) {\r\n <!--\r\n \u00A74.7: un\u2019istanza non ha un letterale. Si compila un membro alla volta e si manda\r\n `className` piu\u2019 i soli membri valorizzati \u2014 la stessa forma che torna in lettura.\r\n -->\r\n <p class=\"fb-field__hint\">\r\n Istanza di <code>{{ variable.objectType }}</code>: valorizza i membri che ti servono.\r\n </p>\r\n @for (member of membersOf(variable.objectType); track member.name) {\r\n <div class=\"fb-field fb-run__member\">\r\n <label class=\"fb-field__label\">\r\n {{ member.name }}\r\n <span class=\"fb-run__type\">{{ member.dataType }}{{ member.isCollection ? '[]' : '' }}</span>\r\n </label>\r\n @if (member.dataType === 'Boolean') {\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"valueOf(variable.name + '.' + member.name)\"\r\n (change)=\"setValue(variable.name + '.' + member.name, $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014</option>\r\n <option value=\"true\">vero</option>\r\n <option value=\"false\">falso</option>\r\n </select>\r\n } @else if (enumValuesOf(member.objectType).length) {\r\n <!-- \u00A74.6: i valori del tipo del membro, per nome. Il numero non si manda mai. -->\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"valueOf(variable.name + '.' + member.name)\"\r\n (change)=\"setValue(variable.name + '.' + member.name, $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014</option>\r\n @for (entry of enumValuesOf(member.objectType); track entry.name) {\r\n <option [value]=\"entry.name\">{{ entry.label || entry.name }}</option>\r\n }\r\n </select>\r\n } @else {\r\n <input\r\n class=\"fb-input\"\r\n [type]=\"member.dataType === 'Number' || member.dataType === 'Integer' ? 'number' : 'text'\"\r\n [value]=\"valueOf(variable.name + '.' + member.name)\"\r\n (input)=\"setValue(variable.name + '.' + member.name, $any($event.target).value)\"\r\n />\r\n }\r\n </div>\r\n }\r\n @if (!membersOf(variable.objectType).length) {\r\n <p class=\"fb-field__hint\">\r\n Membri non disponibili: senza il catalogo della classe non c\u2019e\u2019 un form da generare.\r\n </p>\r\n }\r\n } @else if (variable.dataType === 'Boolean') {\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"valueOf(variable.name!)\"\r\n (change)=\"setValue(variable.name!, $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014</option>\r\n <option value=\"true\">vero</option>\r\n <option value=\"false\">falso</option>\r\n </select>\r\n } @else if (enumValuesOf(variable.objectType).length) {\r\n <!--\r\n \u00A74.6: un enum si passa **per nome**, e i nomi ammessi sono quelli del tipo. Senza i\r\n valori \u2014 primitiva assente o tipo senza valori \u2014 si torna alla casella di testo.\r\n -->\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"valueOf(variable.name!)\"\r\n (change)=\"setValue(variable.name!, $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014</option>\r\n @for (entry of enumValuesOf(variable.objectType); track entry.name) {\r\n <option [value]=\"entry.name\">{{ entry.label || entry.name }}</option>\r\n }\r\n </select>\r\n } @else {\r\n <input\r\n class=\"fb-input\"\r\n [type]=\"variable.dataType === 'Number' || variable.dataType === 'Integer' ? 'number' : 'text'\"\r\n [value]=\"valueOf(variable.name!)\"\r\n (input)=\"setValue(variable.name!, $any($event.target).value)\"\r\n />\r\n }\r\n </div>\r\n }\r\n } @else {\r\n <p class=\"fb-field__hint\">Il flow non dichiara variabili di input: non c\u2019e\u2019 niente da compilare.</p>\r\n }\r\n </section>\r\n\r\n @if (isDebug()) {\r\n <p class=\"fb-run__warn\">\r\n Il debug puo\u2019 riportare i valori di <strong>tutte</strong> le risorse, dati personali compresi: non\r\n usarlo su dati reali.\r\n </p>\r\n }\r\n </div>\r\n\r\n <footer class=\"fb-dialog__foot\">\r\n <span class=\"fb-dialog__note\">\r\n L\u2019esecuzione e\u2019 dell\u2019applicazione che ospita l\u2019editor: qui si raccolgono solo gli ingressi.\r\n </span>\r\n <div class=\"fb-run__actions\">\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"cancel()\">Annulla</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" [disabled]=\"isBusy()\" (click)=\"confirm()\">\r\n {{ isDebug() ? 'Avvia in debug' : 'Avvia' }}\r\n </button>\r\n </div>\r\n </footer>\r\n</div>\r\n", styles: [":host{position:absolute;inset:0;z-index:40;display:grid;place-items:center;padding:24px}.fb-dialog__backdrop{position:absolute;inset:0;background:#10182852;-webkit-backdrop-filter:blur(1px);backdrop-filter:blur(1px)}.fb-dialog__panel{position:relative;display:flex;flex-direction:column;width:min(520px,100%);max-height:min(720px,100%);border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-lg, 12px);background:var(--fb-surface, #fff);box-shadow:var(--fb-shadow-lg, 0 18px 44px rgb(16 24 40 / 18%));outline:none;overflow:hidden}.fb-dialog__head,.fb-dialog__foot{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 14px}.fb-dialog__head{border-bottom:1px solid var(--fb-border-subtle, #eef0f4)}.fb-dialog__foot{border-top:1px solid var(--fb-border-subtle, #eef0f4)}.fb-dialog__type{display:block;font-size:11px;text-transform:uppercase;letter-spacing:.04em;color:var(--fb-text-muted, #6b7086)}.fb-dialog__title{margin:0;font-size:15px;font-weight:600}.fb-dialog__note{color:var(--fb-text-muted, #6b7086);font-size:12px}.fb-dialog__foot .fb-dialog__note{flex:1 1 auto;min-width:0}.fb-dialog__body{display:flex;flex-direction:column;gap:14px;padding:14px;overflow:auto}.fb-run__version{margin-left:4px;color:var(--fb-text-muted, #6b7086);font-weight:500;font-size:12px}.fb-run__section{display:flex;flex-direction:column;gap:8px}.fb-run__title{margin:0;font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--fb-text-muted, #6b7086)}.fb-run__type{color:var(--fb-text-muted, #6b7086);font-size:11px;font-weight:400}.fb-run__member{padding-left:12px;border-left:2px solid var(--fb-border-subtle, #eef0f4)}.fb-run__warn{margin:0;padding:8px 10px;border-radius:var(--fb-radius-sm, 8px);background:var(--fb-surface-alt, #f7f8fa);color:var(--fb-warning, #b7791f);font-size:12px}.fb-run__actions{display:flex;flex:0 0 auto;gap:8px}.fb-run__actions .fb-btn{white-space:nowrap}\n"] }]
12758
+ }], ctorParameters: () => [], propDecorators: { mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: true }] }], flowName: [{ type: i0.Input, args: [{ isSignal: true, alias: "flowName", required: false }] }], version: [{ type: i0.Input, args: [{ isSignal: true, alias: "version", required: false }] }], isDirty: [{ type: i0.Input, args: [{ isSignal: true, alias: "isDirty", required: false }] }], isBusy: [{ type: i0.Input, args: [{ isSignal: true, alias: "isBusy", required: false }] }], confirmed: [{ type: i0.Output, args: ["confirmed"] }], cancelled: [{ type: i0.Output, args: ["cancelled"] }] } });
12759
+
12760
+ /**
12761
+ * Il pannello «Debug»: cosa e' successo nell'ultima esecuzione lanciata dalla barra.
12762
+ *
12763
+ * Non conduce niente — l'esecuzione e' dell'applicazione ospite, e questo pannello **guarda**
12764
+ * cio' che `runFlow`/`debugFlow` hanno restituito. Esiste per il caso in cui non c'e' nient'altro
12765
+ * da guardare: un flow senza schermate (`AutoLaunched`, o un'orchestrazione tutta in background)
12766
+ * non apre nessuna interfaccia del runtime, quindi la traccia accanto al grafo e' l'unico modo di
12767
+ * sapere quali elementi sono stati eseguiti e in che ordine. Da lì si torna sull'elemento con un
12768
+ * click, che e' il motivo per cui sta nell'editor e non altrove.
12769
+ *
12770
+ * Tre cose che tratta come dette (§6.5):
12771
+ * 1. `Failed` **non** e' un errore di trasporto: la chiamata e' riuscita, il flow e' fallito.
12772
+ * E `NotStarted` significa che i criteri dello Start non erano soddisfatti.
12773
+ * 2. `trace` e `resources` arrivano solo con `debug: true`, e riportano i valori di **tutte**
12774
+ * le risorse — dati personali compresi: l'avviso e' in chiaro, non in una tooltip.
12775
+ * 3. l'ospite puo' non restituire niente: allora non c'e' niente da mostrare, e il pannello lo
12776
+ * dice invece di sembrare rotto.
12777
+ */
12778
+ class DebugPanelComponent {
12779
+ /** L'esito dell'ultima esecuzione; `null` = non ne e' ancora stata lanciata una. */
12780
+ outcome = input(null, ...(ngDevMode ? [{ debugName: "outcome" }] : []));
12781
+ /** L'ultima esecuzione era in debug: senza, traccia e risorse non arrivano ed e' normale. */
12782
+ wasDebug = input(false, ...(ngDevMode ? [{ debugName: "wasDebug" }] : []));
12783
+ /** L'ospite sta ancora eseguendo: l'esito, se arriva, arriva dopo. */
12784
+ isRunning = input(false, ...(ngDevMode ? [{ debugName: "isRunning" }] : []));
12785
+ closed = output();
12786
+ /** Chiede all'host di portare l'utente sull'elemento: e' l'ancora della traccia. */
12787
+ elementFocused = output();
12788
+ cleared = output();
12789
+ status = computed(() => this.outcome()?.status ?? null, ...(ngDevMode ? [{ debugName: "status" }] : []));
12790
+ trace = computed(() => this.outcome()?.trace ?? [], ...(ngDevMode ? [{ debugName: "trace" }] : []));
12791
+ /** Le risorse come righe ordinate: il tipo dichiarato vince in lettura (§6.5). */
12792
+ resourceRows = computed(() => rowsOf(this.outcome()?.resources).sort((left, right) => left.name.localeCompare(right.name)), ...(ngDevMode ? [{ debugName: "resourceRows" }] : []));
12793
+ outputRows = computed(() => rowsOf(this.outcome()?.outputs), ...(ngDevMode ? [{ debugName: "outputRows" }] : []));
12267
12794
  /**
12268
- * Conclude uno step come farebbe l'assegnatario. `Rejected` non e' un errore: e' l'esito che
12269
- * prende il ramo «Step rifiutato» dello stage — e se quel ramo non c'e', l'interview fallisce.
12795
+ * L'ospite ha restituito qualcosa, ma niente di leggibile: nessuno stato, nessuna traccia,
12796
+ * nessun valore. Va distinto dal «non e' stata lanciata nessuna esecuzione», perche' il
12797
+ * rimedio e' diverso — lì si preme «Esegui», qui manca un pezzo dell'implementazione.
12270
12798
  */
12271
- async completeStep(stepName, status) {
12272
- const key = this.result()?.interviewKey;
12273
- if (!key || !stepName) {
12274
- return;
12275
- }
12276
- this.isRunning.set(true);
12277
- this.errorMessage.set(null);
12278
- try {
12279
- const result = await this.api.completeStageStep({
12280
- interviewKey: key,
12281
- stepName,
12282
- status,
12283
- stepOutputs: this.stepOutputs(),
12284
- debug: this.debugEnabled(),
12285
- });
12286
- this.stepOutputs.set({});
12287
- // La risposta puo' portare una chiave nuova: `apply` sostituisce il risultato intero,
12288
- // quindi la vecchia chiave non resta in giro.
12289
- this.apply(result);
12290
- }
12291
- catch (error) {
12292
- this.handleError(error);
12293
- }
12294
- finally {
12295
- this.isRunning.set(false);
12296
- }
12297
- }
12298
- apply(result) {
12299
- this.result.set(result);
12300
- if (result.currentElementName) {
12301
- this.elementFocused.emit(result.currentElementName);
12302
- }
12303
- }
12304
- handleError(error) {
12305
- if (FlowApiError.is(error)) {
12306
- // `InvalidToken` significa che la sessione di prova non e' piu' valida: si riavvia.
12307
- this.errorMessage.set(error.message);
12308
- return;
12799
+ isEmptyOutcome = computed(() => {
12800
+ const outcome = this.outcome();
12801
+ if (!outcome) {
12802
+ return false;
12309
12803
  }
12310
- this.errorMessage.set(error instanceof Error ? error.message : String(error));
12311
- }
12312
- reset() {
12313
- this.result.set(null);
12314
- this.errorMessage.set(null);
12315
- this.screenOutputs.set({});
12316
- this.stepOutputs.set({});
12317
- }
12804
+ return (!outcome.status &&
12805
+ !outcome.trace?.length &&
12806
+ !outcome.errors?.length &&
12807
+ !outcome.fault &&
12808
+ !this.resourceRows().length &&
12809
+ !this.outputRows().length);
12810
+ }, ...(ngDevMode ? [{ debugName: "isEmptyOutcome" }] : []));
12318
12811
  close() {
12319
12812
  this.closed.emit();
12320
12813
  }
12321
- /** L'etichetta dello stato, che non e' un errore anche quando dice "Failed" (§6.5). */
12814
+ clear() {
12815
+ this.cleared.emit();
12816
+ }
12817
+ /** L'etichetta dello stato, che non e' un errore anche quando dice «Fallita» (§6.5). */
12322
12818
  statusLabel() {
12323
12819
  switch (this.status()) {
12324
12820
  case 'Completed':
@@ -12334,45 +12830,68 @@ class DebugPanelComponent {
12334
12830
  case 'NotStarted':
12335
12831
  return 'Non avviata';
12336
12832
  default:
12337
- return '';
12833
+ return this.status() ?? '';
12338
12834
  }
12339
12835
  }
12340
12836
  statusNote() {
12341
12837
  switch (this.status()) {
12342
12838
  case 'Failed':
12343
- return 'Il flow e’ fallito: la richiesta e’ andata a buon fine, l’esecuzione no.';
12839
+ return 'Il flow e’ fallito: la chiamata e’ andata a buon fine, l’esecuzione no.';
12344
12840
  case 'NotStarted':
12345
12841
  return 'I criteri dello Start non erano soddisfatti: non e’ un errore.';
12842
+ case 'WaitingForScreen':
12843
+ return 'L’esecuzione e’ ferma su una schermata: a mostrarla e’ l’applicazione, non l’editor.';
12346
12844
  case 'Paused':
12347
- return 'L’esecuzione e’ in pausa: puo’ essere ripresa dalla sua chiave.';
12348
12845
  case 'Suspended':
12349
- // Due attese diverse sotto lo stesso stato: dirlo evita di cercare l'evento sbagliato.
12350
- if (this.isWaitingForStageStep()) {
12351
- return 'Sospesa su uno step di orchestrazione: aspetta che un assegnatario lo concluda.';
12352
- }
12353
- return this.result()?.isWaitingForEvent
12354
- ? 'Sospesa in attesa di un evento: puo’ essere ripresa dalla sua chiave.'
12355
- : 'L’esecuzione e’ sospesa: puo’ essere ripresa dalla sua chiave.';
12846
+ return 'L’esecuzione non e’ finita: puo’ essere ripresa da chi la conduce.';
12356
12847
  default:
12357
12848
  return null;
12358
12849
  }
12359
12850
  }
12360
- canGoBack() {
12361
- return this.pendingScreen()?.canGoBack === true;
12362
- }
12363
- canFinish() {
12364
- return this.pendingScreen()?.canFinish === true;
12365
- }
12366
- canPause() {
12367
- return this.pendingScreen()?.canPause === true;
12368
- }
12369
12851
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.27", ngImport: i0, type: DebugPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
12370
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.27", type: DebugPanelComponent, isStandalone: true, selector: "fb-debug-panel", outputs: { closed: "closed", elementFocused: "elementFocused" }, ngImport: i0, template: "<header class=\"fb-dbg__header\">\r\n <h2 class=\"fb-dbg__title\">Prova</h2>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" aria-label=\"Chiudi\" (click)=\"close()\">\u00D7</button>\r\n</header>\r\n\r\n<div class=\"fb-dbg__body\">\r\n @if (errorMessage()) {\r\n <p class=\"fb-callout fb-callout--error\">{{ errorMessage() }}</p>\r\n }\r\n\r\n @if (!result()) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Valori iniziali</legend>\r\n @if (inputVariables().length) {\r\n @for (variable of inputVariables(); track variable.name) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">\r\n {{ variable.name }}\r\n <span class=\"fb-dbg__type\">{{ variable.dataType }}{{ variable.isCollection ? '[]' : '' }}</span>\r\n </label>\r\n @if (isStructureVariable(variable)) {\r\n <!--\r\n \u00A74.7: un'istanza non ha un letterale. Si compila un membro alla volta e si manda\r\n `className` piu' i soli membri valorizzati \u2014 la stessa forma che torna in lettura.\r\n -->\r\n <p class=\"fb-field__hint\">\r\n Istanza di <code>{{ variable.objectType }}</code>: valorizza i membri che ti servono.\r\n </p>\r\n @for (member of membersOf(variable.objectType); track member.name) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">\r\n {{ member.name }}\r\n <span class=\"fb-dbg__type\">{{ member.dataType }}{{ member.isCollection ? '[]' : '' }}</span>\r\n </label>\r\n @if (member.dataType === 'Boolean') {\r\n <select\r\n class=\"fb-select\"\r\n (change)=\"setInputValue(variable.name + '.' + member.name, $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014</option>\r\n <option value=\"true\">vero</option>\r\n <option value=\"false\">falso</option>\r\n </select>\r\n } @else if (enumValuesOf(member.objectType).length) {\r\n <!-- \u00A74.6: i valori del tipo del membro, per nome. Il numero e' solo mostrato. -->\r\n <select\r\n class=\"fb-select\"\r\n (change)=\"setInputValue(variable.name + '.' + member.name, $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014</option>\r\n @for (entry of enumValuesOf(member.objectType); track entry.name) {\r\n <option [value]=\"entry.name\">{{ entry.label || entry.name }}</option>\r\n }\r\n </select>\r\n } @else {\r\n <input\r\n class=\"fb-input\"\r\n [type]=\"member.dataType === 'Number' || member.dataType === 'Integer' ? 'number' : 'text'\"\r\n (input)=\"setInputValue(variable.name + '.' + member.name, $any($event.target).value)\"\r\n />\r\n }\r\n </div>\r\n }\r\n @if (!membersOf(variable.objectType).length) {\r\n <p class=\"fb-field__hint\">\r\n Membri non disponibili: senza il catalogo della classe non c\u2019e\u2019 un form da generare.\r\n </p>\r\n }\r\n } @else if (variable.dataType === 'Boolean') {\r\n <select class=\"fb-select\" (change)=\"setInputValue(variable.name!, $any($event.target).value)\">\r\n <option value=\"\">\u2014</option>\r\n <option value=\"true\">vero</option>\r\n <option value=\"false\">falso</option>\r\n </select>\r\n } @else if (enumValuesOf(variable.objectType).length) {\r\n <!--\r\n \u00A74.6: un enum si passa **per nome**, e i nomi ammessi sono quelli del tipo. Senza i\r\n valori \u2014 primitiva assente o tipo senza valori \u2014 si torna alla casella di testo.\r\n -->\r\n <select class=\"fb-select\" (change)=\"setInputValue(variable.name!, $any($event.target).value)\">\r\n <option value=\"\">\u2014</option>\r\n @for (entry of enumValuesOf(variable.objectType); track entry.name) {\r\n <option [value]=\"entry.name\">{{ entry.label || entry.name }}</option>\r\n }\r\n </select>\r\n } @else {\r\n <input\r\n class=\"fb-input\"\r\n [type]=\"variable.dataType === 'Number' || variable.dataType === 'Integer' ? 'number' : 'text'\"\r\n (input)=\"setInputValue(variable.name!, $any($event.target).value)\"\r\n />\r\n }\r\n </div>\r\n }\r\n } @else {\r\n <p class=\"fb-field__hint\">Il flow non dichiara variabili di input.</p>\r\n }\r\n </fieldset>\r\n\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"debugEnabled()\"\r\n (change)=\"setDebugEnabled($any($event.target).checked)\"\r\n />\r\n Traccia di debug\r\n </label>\r\n @if (debugEnabled()) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n La traccia riporta i valori di <strong>tutte</strong> le risorse, dati personali compresi: non usarla\r\n su dati reali.\r\n </p>\r\n }\r\n\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" [disabled]=\"!canRun()\" (click)=\"start()\">\r\n Avvia\r\n </button>\r\n @if (!canRun() && !isRunning()) {\r\n <p class=\"fb-field__hint\">Salva il flow prima di provarlo.</p>\r\n }\r\n }\r\n\r\n @if (result()) {\r\n <div class=\"fb-dbg__status\">\r\n <span\r\n class=\"fb-dbg__badge\"\r\n [class.fb-dbg__badge--ok]=\"status() === 'Completed'\"\r\n [class.fb-dbg__badge--fail]=\"status() === 'Failed'\"\r\n [class.fb-dbg__badge--wait]=\"isWaitingForScreen()\"\r\n >\r\n {{ statusLabel() }}\r\n </span>\r\n @if (result()?.currentElementName) {\r\n <span class=\"fb-dbg__current\">su {{ result()?.currentElementName }}</span>\r\n }\r\n <span class=\"fb-dbg__steps\">{{ result()?.steps || 0 }} passi</span>\r\n <span class=\"fb-list__spacer\"></span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" (click)=\"reset()\">Riavvia</button>\r\n </div>\r\n\r\n @if (statusNote()) {\r\n <p class=\"fb-callout\">{{ statusNote() }}</p>\r\n }\r\n\r\n @if (result()?.fault) {\r\n <p class=\"fb-callout fb-callout--error\">{{ result()?.fault }}</p>\r\n }\r\n @for (message of result()?.errors || []; track message) {\r\n <p class=\"fb-callout fb-callout--error\">{{ message }}</p>\r\n }\r\n\r\n @if (isWaitingForScreen() && pendingScreen()) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">\r\n Form \u00AB{{ pendingScreen()?.formName }}\u00BB\r\n </legend>\r\n <p class=\"fb-section__note\">\r\n Nell\u2019editor basta un form generico: qui vedi i valori che il form riceve e puoi compilare quelli\r\n che dichiara di restituire.\r\n </p>\r\n\r\n @if (pendingScreen()?.label) {\r\n <p class=\"fb-dbg__screen-label\">{{ pendingScreen()?.label }}</p>\r\n }\r\n @if (pendingScreen()?.helpText) {\r\n <p class=\"fb-field__hint\">{{ pendingScreen()?.helpText }}</p>\r\n }\r\n\r\n @if (screenInputRows().length) {\r\n <table class=\"fb-dbg__table\">\r\n <caption>\r\n Valori in ingresso\r\n </caption>\r\n <tbody>\r\n @for (row of screenInputRows(); track row.name) {\r\n <tr>\r\n <th scope=\"row\">{{ row.name }}</th>\r\n <td class=\"fb-dbg__type\">{{ row.type }}</td>\r\n <td>{{ row.value }}</td>\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n }\r\n\r\n @for (output of screenOutputNames(); track output) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">{{ output }}</label>\r\n <input class=\"fb-input\" (input)=\"setScreenOutput(output, $any($event.target).value)\" />\r\n </div>\r\n }\r\n @if (!screenOutputNames().length) {\r\n <p class=\"fb-field__hint\">Lo screen non dichiara parametri di uscita.</p>\r\n }\r\n\r\n <div class=\"fb-field__row\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--primary\"\r\n [disabled]=\"isRunning()\"\r\n (click)=\"respond('Next')\"\r\n >\r\n Avanti\r\n </button>\r\n <!-- canGoBack/canFinish/canPause sono la verita', piu' precisa dei flag del metadata. -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"isRunning() || !canGoBack()\"\r\n title=\"Con \u00ABindietro\u00BB gli output non vengono memorizzati\"\r\n (click)=\"respond('Previous')\"\r\n >\r\n Indietro\r\n </button>\r\n <button type=\"button\" class=\"fb-btn\" [disabled]=\"isRunning() || !canFinish()\" (click)=\"respond('Finish')\">\r\n Fine\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"isRunning() || !canPause()\"\r\n title=\"Con \u00ABpausa\u00BB gli output non vengono memorizzati\"\r\n (click)=\"respond('Pause')\"\r\n >\r\n Pausa\r\n </button>\r\n </div>\r\n <p class=\"fb-field__hint\">\r\n Con \u00ABindietro\u00BB e \u00ABpausa\u00BB i valori inseriti <strong>non</strong> vengono memorizzati.\r\n </p>\r\n </fieldset>\r\n }\r\n\r\n @if (isWaitingForStageStep()) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Step di orchestrazione</legend>\r\n <p class=\"fb-section__note\">\r\n L\u2019interview e\u2019 sospesa su uno step assegnato: qui si conclude al posto dell\u2019assegnatario.\r\n Concludere puo\u2019 far <strong>sospendere di nuovo</strong> lo stage, con una chiave nuova.\r\n </p>\r\n\r\n <table class=\"fb-dbg__table\">\r\n <tbody>\r\n @for (step of stageSteps(); track step.stepName) {\r\n <tr>\r\n <th scope=\"row\">{{ step.label || step.stepName }}</th>\r\n <td class=\"fb-dbg__type\">{{ step.actionType }}</td>\r\n <td>{{ step.isWaiting ? 'in attesa' : step.status }}</td>\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n\r\n @for (step of waitingStageSteps(); track step.stepName) {\r\n <div class=\"fb-list__item\">\r\n <div class=\"fb-list__header\">\r\n <span class=\"fb-list__title\">{{ step.label || step.stepName }}</span>\r\n </div>\r\n @for (output of stepOutputNames(step.stepName); track output) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">{{ output }}</label>\r\n <input class=\"fb-input\" (input)=\"setStepOutput(output, $any($event.target).value)\" />\r\n </div>\r\n }\r\n <div class=\"fb-field__row\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--primary\"\r\n [disabled]=\"isRunning()\"\r\n (click)=\"completeStep(step.stepName, 'Completed')\"\r\n >\r\n Concludi\r\n </button>\r\n <!-- Il rifiuto non e' un errore: prende il ramo \u00ABStep rifiutato\u00BB dello stage. -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"isRunning()\"\r\n title=\"Prende il ramo \u00ABStep rifiutato\u00BB; senza quel ramo l\u2019interview fallisce\"\r\n (click)=\"completeStep(step.stepName, 'Rejected')\"\r\n >\r\n Rifiuta\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost\"\r\n [disabled]=\"isRunning()\"\r\n (click)=\"completeStep(step.stepName, 'Cancelled')\"\r\n >\r\n Annulla lo step\r\n </button>\r\n </div>\r\n </div>\r\n }\r\n </fieldset>\r\n }\r\n\r\n @if (outputRows().length) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Output del flow</legend>\r\n <table class=\"fb-dbg__table\">\r\n <tbody>\r\n @for (row of outputRows(); track row.name) {\r\n <tr>\r\n <th scope=\"row\">{{ row.name }}</th>\r\n <td class=\"fb-dbg__type\">{{ row.type }}</td>\r\n <td>{{ row.value }}</td>\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n </fieldset>\r\n }\r\n\r\n @if (trace().length) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Traccia</legend>\r\n <ol class=\"fb-dbg__trace\">\r\n @for (entry of trace(); track entry.sequence) {\r\n <li class=\"fb-dbg__trace-item\">\r\n <span class=\"fb-dbg__trace-seq\">{{ entry.sequence }}</span>\r\n @if (entry.elementName) {\r\n <button type=\"button\" class=\"fb-dbg__trace-el\" (click)=\"elementFocused.emit(entry.elementName!)\">\r\n {{ entry.elementName }}\r\n </button>\r\n }\r\n <span class=\"fb-dbg__trace-msg\">{{ entry.message }}</span>\r\n </li>\r\n }\r\n </ol>\r\n </fieldset>\r\n }\r\n\r\n @if (resourceRows().length) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Risorse</legend>\r\n <table class=\"fb-dbg__table\">\r\n <tbody>\r\n @for (row of resourceRows(); track row.name) {\r\n <tr>\r\n <th scope=\"row\">{{ row.name }}</th>\r\n <td class=\"fb-dbg__type\">{{ row.type }}</td>\r\n <td>{{ row.value }}</td>\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n </fieldset>\r\n }\r\n\r\n @if (result()?.interviewKey) {\r\n <p class=\"fb-field__hint\">\r\n Chiave dell\u2019esecuzione sospesa: <code>{{ result()?.interviewKey }}</code>\r\n </p>\r\n }\r\n }\r\n</div>\r\n", styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0;background:var(--fb-surface, #fff)}.fb-dbg__header{display:flex;align-items:center;justify-content:space-between;padding:10px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-dbg__title{margin:0;font-size:14px;font-weight:600;color:var(--fb-text, #1d2939)}.fb-dbg__body{flex:1;min-height:0;overflow-y:auto;padding:10px 12px}.fb-dbg__status{display:flex;align-items:center;gap:8px;margin-bottom:10px}.fb-dbg__badge{padding:2px 8px;border-radius:10px;background:var(--fb-border, #d6dae1);font-size:11px;font-weight:600;color:var(--fb-text, #1d2939)}.fb-dbg__badge--ok{background:color-mix(in srgb,var(--fb-success, #3f8f5f) 18%,transparent);color:var(--fb-success, #3f8f5f)}.fb-dbg__badge--fail{background:color-mix(in srgb,var(--fb-error, #c9372c) 14%,transparent);color:var(--fb-error, #c9372c)}.fb-dbg__badge--wait{background:color-mix(in srgb,var(--fb-accent, #2f6feb) 12%,transparent);color:var(--fb-accent, #2f6feb)}.fb-dbg__current,.fb-dbg__steps{font-size:10px;color:var(--fb-text-muted, #667085)}.fb-dbg__type{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:9px;color:var(--fb-text-subtle, #98a2b3)}.fb-dbg__screen-label{margin:0 0 4px;font-size:12px;font-weight:600;color:var(--fb-text, #1d2939)}.fb-dbg__table{width:100%;border-collapse:collapse;font-size:11px}.fb-dbg__table caption{padding-bottom:3px;font-size:10px;color:var(--fb-text-subtle, #98a2b3);text-align:left}.fb-dbg__table th,.fb-dbg__table td{padding:3px 5px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee);text-align:left;vertical-align:top}.fb-dbg__table th{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-weight:600;color:var(--fb-text, #1d2939)}.fb-dbg__table td{color:var(--fb-text-muted, #667085);word-break:break-word}.fb-dbg__trace{margin:0;padding:0;list-style:none}.fb-dbg__trace-item{display:flex;gap:6px;padding:3px 0;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee);font-size:11px}.fb-dbg__trace-seq{flex:0 0 auto;width:18px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:9px;color:var(--fb-text-subtle, #98a2b3);text-align:right}.fb-dbg__trace-el{flex:0 0 auto;padding:0;border:0;background:transparent;color:var(--fb-accent, #2f6feb);font:inherit;font-size:10px;cursor:pointer;text-decoration:underline}.fb-dbg__trace-msg{color:var(--fb-text-muted, #667085);line-height:1.35}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
12852
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.27", type: DebugPanelComponent, isStandalone: true, selector: "fb-debug-panel", inputs: { outcome: { classPropertyName: "outcome", publicName: "outcome", isSignal: true, isRequired: false, transformFunction: null }, wasDebug: { classPropertyName: "wasDebug", publicName: "wasDebug", isSignal: true, isRequired: false, transformFunction: null }, isRunning: { classPropertyName: "isRunning", publicName: "isRunning", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closed: "closed", elementFocused: "elementFocused", cleared: "cleared" }, ngImport: i0, template: "<header class=\"fb-dbg__header\">\r\n <h2 class=\"fb-dbg__title\">Debug</h2>\r\n @if (outcome()) {\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" title=\"Svuota\" (click)=\"clear()\">Svuota</button>\r\n }\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" aria-label=\"Chiudi\" (click)=\"close()\">\u00D7</button>\r\n</header>\r\n\r\n<div class=\"fb-dbg__body\">\r\n @if (isRunning()) {\r\n <p class=\"fb-field__hint\">Esecuzione in corso\u2026</p>\r\n }\r\n\r\n @if (!outcome() && !isRunning()) {\r\n <!--\r\n Non e\u2019 un errore: e\u2019 lo stato iniziale. Il pannello serve soprattutto ai flow senza\r\n schermate, dove non c\u2019e\u2019 nessun\u2019altra interfaccia in cui guardare la traccia.\r\n -->\r\n <p class=\"fb-field__hint\">\r\n Nessuna esecuzione. Usa <strong>Esegui</strong> o <strong>Debug</strong> nella barra in alto: qui\r\n compare cio\u2019 che l\u2019applicazione restituisce \u2014 stato, traccia degli elementi eseguiti, risorse e\r\n output.\r\n </p>\r\n }\r\n\r\n @if (isEmptyOutcome()) {\r\n <!--\r\n L\u2019ospite ha risposto, ma senza niente da mostrare. Distinto dal caso \u00ABnessuna esecuzione\u00BB\r\n perche\u2019 il rimedio e\u2019 un altro: qui manca un pezzo dell\u2019implementazione di runFlow.\r\n -->\r\n <p class=\"fb-callout\">\r\n L\u2019esecuzione e\u2019 partita, ma l\u2019applicazione non ha restituito nessun dettaglio da mostrare.\r\n </p>\r\n }\r\n\r\n @if (outcome(); as run) {\r\n @if (status() || run.steps) {\r\n <div class=\"fb-dbg__status\">\r\n @if (status()) {\r\n <span\r\n class=\"fb-dbg__badge\"\r\n [class.fb-dbg__badge--ok]=\"status() === 'Completed'\"\r\n [class.fb-dbg__badge--fail]=\"status() === 'Failed'\"\r\n [class.fb-dbg__badge--wait]=\"status() === 'WaitingForScreen'\"\r\n >\r\n {{ statusLabel() }}\r\n </span>\r\n }\r\n @if (run.currentElementName) {\r\n <!-- Dove si e\u2019 fermata: cliccarlo porta sull\u2019elemento, come le righe della traccia. -->\r\n <button type=\"button\" class=\"fb-dbg__current\" (click)=\"elementFocused.emit(run.currentElementName!)\">\r\n su {{ run.currentElementName }}\r\n </button>\r\n }\r\n @if (run.steps) {\r\n <span class=\"fb-dbg__steps\">{{ run.steps }} passi</span>\r\n }\r\n </div>\r\n }\r\n\r\n @if (statusNote()) {\r\n <p class=\"fb-callout\">{{ statusNote() }}</p>\r\n }\r\n\r\n @if (run.fault) {\r\n <p class=\"fb-callout fb-callout--error\">{{ run.fault }}</p>\r\n }\r\n @for (message of run.errors || []; track $index) {\r\n <p class=\"fb-callout fb-callout--error\">{{ message }}</p>\r\n }\r\n\r\n @if (outputRows().length) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Output del flow</legend>\r\n <table class=\"fb-dbg__table\">\r\n <tbody>\r\n @for (row of outputRows(); track row.name) {\r\n <tr>\r\n <th scope=\"row\">{{ row.name }}</th>\r\n <td class=\"fb-dbg__type\">{{ row.type }}</td>\r\n <td>{{ row.value }}</td>\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n </fieldset>\r\n }\r\n\r\n @if (trace().length) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Traccia</legend>\r\n <p class=\"fb-callout fb-callout--warn\">\r\n La traccia riporta i valori di <strong>tutte</strong> le risorse, dati personali compresi.\r\n </p>\r\n <ol class=\"fb-dbg__trace\">\r\n @for (entry of trace(); track $index) {\r\n <li class=\"fb-dbg__trace-item\">\r\n <span class=\"fb-dbg__trace-seq\">{{ entry.sequence }}</span>\r\n @if (entry.elementName) {\r\n <!-- L\u2019ancora: dalla riga della traccia all\u2019elemento sul canvas. -->\r\n <button type=\"button\" class=\"fb-dbg__trace-el\" (click)=\"elementFocused.emit(entry.elementName!)\">\r\n {{ entry.elementName }}\r\n </button>\r\n }\r\n <span class=\"fb-dbg__trace-msg\">{{ entry.message }}</span>\r\n </li>\r\n }\r\n </ol>\r\n </fieldset>\r\n } @else if (!wasDebug() && !isEmptyOutcome()) {\r\n <!-- Non e\u2019 un difetto: `trace` e `resources` esistono solo con `debug: true` (\u00A76.5). -->\r\n <p class=\"fb-field__hint\">\r\n Traccia e risorse arrivano solo con il comando <strong>Debug</strong>.\r\n </p>\r\n }\r\n\r\n @if (resourceRows().length) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Risorse</legend>\r\n <table class=\"fb-dbg__table\">\r\n <tbody>\r\n @for (row of resourceRows(); track row.name) {\r\n <tr>\r\n <th scope=\"row\">{{ row.name }}</th>\r\n <td class=\"fb-dbg__type\">{{ row.type }}</td>\r\n <td>{{ row.value }}</td>\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n </fieldset>\r\n }\r\n }\r\n</div>\r\n", styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0;background:var(--fb-surface, #fff)}.fb-dbg__header{display:flex;align-items:center;gap:6px;padding:10px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-dbg__title{margin:0 auto 0 0;font-size:14px;font-weight:600;color:var(--fb-text, #1d2939)}.fb-dbg__body{flex:1;min-height:0;overflow-y:auto;padding:10px 12px}.fb-dbg__status{display:flex;align-items:center;gap:8px;margin-bottom:10px}.fb-dbg__badge{padding:2px 8px;border-radius:10px;background:var(--fb-border, #d6dae1);font-size:11px;font-weight:600;color:var(--fb-text, #1d2939)}.fb-dbg__badge--ok{background:color-mix(in srgb,var(--fb-success, #3f8f5f) 18%,transparent);color:var(--fb-success, #3f8f5f)}.fb-dbg__badge--fail{background:color-mix(in srgb,var(--fb-error, #c9372c) 14%,transparent);color:var(--fb-error, #c9372c)}.fb-dbg__badge--wait{background:color-mix(in srgb,var(--fb-accent, #2f6feb) 12%,transparent);color:var(--fb-accent, #2f6feb)}.fb-dbg__steps{font-size:10px;color:var(--fb-text-muted, #667085)}.fb-dbg__current{padding:0;border:0;background:transparent;color:var(--fb-accent, #2f6feb);font:inherit;font-size:10px;cursor:pointer;text-decoration:underline}.fb-dbg__type{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:9px;color:var(--fb-text-subtle, #98a2b3)}.fb-dbg__table{width:100%;border-collapse:collapse;font-size:11px}.fb-dbg__table caption{padding-bottom:3px;font-size:10px;color:var(--fb-text-subtle, #98a2b3);text-align:left}.fb-dbg__table th,.fb-dbg__table td{padding:3px 5px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee);text-align:left;vertical-align:top}.fb-dbg__table th{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-weight:600;color:var(--fb-text, #1d2939)}.fb-dbg__table td{color:var(--fb-text-muted, #667085);word-break:break-word}.fb-dbg__trace{margin:0;padding:0;list-style:none}.fb-dbg__trace-item{display:flex;gap:6px;padding:3px 0;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee);font-size:11px}.fb-dbg__trace-seq{flex:0 0 auto;width:18px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:9px;color:var(--fb-text-subtle, #98a2b3);text-align:right}.fb-dbg__trace-el{flex:0 0 auto;padding:0;border:0;background:transparent;color:var(--fb-accent, #2f6feb);font:inherit;font-size:10px;cursor:pointer;text-decoration:underline}.fb-dbg__trace-msg{color:var(--fb-text-muted, #667085);line-height:1.35}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
12371
12853
  }
12372
12854
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImport: i0, type: DebugPanelComponent, decorators: [{
12373
12855
  type: Component,
12374
- args: [{ selector: 'fb-debug-panel', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<header class=\"fb-dbg__header\">\r\n <h2 class=\"fb-dbg__title\">Prova</h2>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" aria-label=\"Chiudi\" (click)=\"close()\">\u00D7</button>\r\n</header>\r\n\r\n<div class=\"fb-dbg__body\">\r\n @if (errorMessage()) {\r\n <p class=\"fb-callout fb-callout--error\">{{ errorMessage() }}</p>\r\n }\r\n\r\n @if (!result()) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Valori iniziali</legend>\r\n @if (inputVariables().length) {\r\n @for (variable of inputVariables(); track variable.name) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">\r\n {{ variable.name }}\r\n <span class=\"fb-dbg__type\">{{ variable.dataType }}{{ variable.isCollection ? '[]' : '' }}</span>\r\n </label>\r\n @if (isStructureVariable(variable)) {\r\n <!--\r\n \u00A74.7: un'istanza non ha un letterale. Si compila un membro alla volta e si manda\r\n `className` piu' i soli membri valorizzati \u2014 la stessa forma che torna in lettura.\r\n -->\r\n <p class=\"fb-field__hint\">\r\n Istanza di <code>{{ variable.objectType }}</code>: valorizza i membri che ti servono.\r\n </p>\r\n @for (member of membersOf(variable.objectType); track member.name) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">\r\n {{ member.name }}\r\n <span class=\"fb-dbg__type\">{{ member.dataType }}{{ member.isCollection ? '[]' : '' }}</span>\r\n </label>\r\n @if (member.dataType === 'Boolean') {\r\n <select\r\n class=\"fb-select\"\r\n (change)=\"setInputValue(variable.name + '.' + member.name, $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014</option>\r\n <option value=\"true\">vero</option>\r\n <option value=\"false\">falso</option>\r\n </select>\r\n } @else if (enumValuesOf(member.objectType).length) {\r\n <!-- \u00A74.6: i valori del tipo del membro, per nome. Il numero e' solo mostrato. -->\r\n <select\r\n class=\"fb-select\"\r\n (change)=\"setInputValue(variable.name + '.' + member.name, $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014</option>\r\n @for (entry of enumValuesOf(member.objectType); track entry.name) {\r\n <option [value]=\"entry.name\">{{ entry.label || entry.name }}</option>\r\n }\r\n </select>\r\n } @else {\r\n <input\r\n class=\"fb-input\"\r\n [type]=\"member.dataType === 'Number' || member.dataType === 'Integer' ? 'number' : 'text'\"\r\n (input)=\"setInputValue(variable.name + '.' + member.name, $any($event.target).value)\"\r\n />\r\n }\r\n </div>\r\n }\r\n @if (!membersOf(variable.objectType).length) {\r\n <p class=\"fb-field__hint\">\r\n Membri non disponibili: senza il catalogo della classe non c\u2019e\u2019 un form da generare.\r\n </p>\r\n }\r\n } @else if (variable.dataType === 'Boolean') {\r\n <select class=\"fb-select\" (change)=\"setInputValue(variable.name!, $any($event.target).value)\">\r\n <option value=\"\">\u2014</option>\r\n <option value=\"true\">vero</option>\r\n <option value=\"false\">falso</option>\r\n </select>\r\n } @else if (enumValuesOf(variable.objectType).length) {\r\n <!--\r\n \u00A74.6: un enum si passa **per nome**, e i nomi ammessi sono quelli del tipo. Senza i\r\n valori \u2014 primitiva assente o tipo senza valori \u2014 si torna alla casella di testo.\r\n -->\r\n <select class=\"fb-select\" (change)=\"setInputValue(variable.name!, $any($event.target).value)\">\r\n <option value=\"\">\u2014</option>\r\n @for (entry of enumValuesOf(variable.objectType); track entry.name) {\r\n <option [value]=\"entry.name\">{{ entry.label || entry.name }}</option>\r\n }\r\n </select>\r\n } @else {\r\n <input\r\n class=\"fb-input\"\r\n [type]=\"variable.dataType === 'Number' || variable.dataType === 'Integer' ? 'number' : 'text'\"\r\n (input)=\"setInputValue(variable.name!, $any($event.target).value)\"\r\n />\r\n }\r\n </div>\r\n }\r\n } @else {\r\n <p class=\"fb-field__hint\">Il flow non dichiara variabili di input.</p>\r\n }\r\n </fieldset>\r\n\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"debugEnabled()\"\r\n (change)=\"setDebugEnabled($any($event.target).checked)\"\r\n />\r\n Traccia di debug\r\n </label>\r\n @if (debugEnabled()) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n La traccia riporta i valori di <strong>tutte</strong> le risorse, dati personali compresi: non usarla\r\n su dati reali.\r\n </p>\r\n }\r\n\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" [disabled]=\"!canRun()\" (click)=\"start()\">\r\n Avvia\r\n </button>\r\n @if (!canRun() && !isRunning()) {\r\n <p class=\"fb-field__hint\">Salva il flow prima di provarlo.</p>\r\n }\r\n }\r\n\r\n @if (result()) {\r\n <div class=\"fb-dbg__status\">\r\n <span\r\n class=\"fb-dbg__badge\"\r\n [class.fb-dbg__badge--ok]=\"status() === 'Completed'\"\r\n [class.fb-dbg__badge--fail]=\"status() === 'Failed'\"\r\n [class.fb-dbg__badge--wait]=\"isWaitingForScreen()\"\r\n >\r\n {{ statusLabel() }}\r\n </span>\r\n @if (result()?.currentElementName) {\r\n <span class=\"fb-dbg__current\">su {{ result()?.currentElementName }}</span>\r\n }\r\n <span class=\"fb-dbg__steps\">{{ result()?.steps || 0 }} passi</span>\r\n <span class=\"fb-list__spacer\"></span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" (click)=\"reset()\">Riavvia</button>\r\n </div>\r\n\r\n @if (statusNote()) {\r\n <p class=\"fb-callout\">{{ statusNote() }}</p>\r\n }\r\n\r\n @if (result()?.fault) {\r\n <p class=\"fb-callout fb-callout--error\">{{ result()?.fault }}</p>\r\n }\r\n @for (message of result()?.errors || []; track message) {\r\n <p class=\"fb-callout fb-callout--error\">{{ message }}</p>\r\n }\r\n\r\n @if (isWaitingForScreen() && pendingScreen()) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">\r\n Form \u00AB{{ pendingScreen()?.formName }}\u00BB\r\n </legend>\r\n <p class=\"fb-section__note\">\r\n Nell\u2019editor basta un form generico: qui vedi i valori che il form riceve e puoi compilare quelli\r\n che dichiara di restituire.\r\n </p>\r\n\r\n @if (pendingScreen()?.label) {\r\n <p class=\"fb-dbg__screen-label\">{{ pendingScreen()?.label }}</p>\r\n }\r\n @if (pendingScreen()?.helpText) {\r\n <p class=\"fb-field__hint\">{{ pendingScreen()?.helpText }}</p>\r\n }\r\n\r\n @if (screenInputRows().length) {\r\n <table class=\"fb-dbg__table\">\r\n <caption>\r\n Valori in ingresso\r\n </caption>\r\n <tbody>\r\n @for (row of screenInputRows(); track row.name) {\r\n <tr>\r\n <th scope=\"row\">{{ row.name }}</th>\r\n <td class=\"fb-dbg__type\">{{ row.type }}</td>\r\n <td>{{ row.value }}</td>\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n }\r\n\r\n @for (output of screenOutputNames(); track output) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">{{ output }}</label>\r\n <input class=\"fb-input\" (input)=\"setScreenOutput(output, $any($event.target).value)\" />\r\n </div>\r\n }\r\n @if (!screenOutputNames().length) {\r\n <p class=\"fb-field__hint\">Lo screen non dichiara parametri di uscita.</p>\r\n }\r\n\r\n <div class=\"fb-field__row\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--primary\"\r\n [disabled]=\"isRunning()\"\r\n (click)=\"respond('Next')\"\r\n >\r\n Avanti\r\n </button>\r\n <!-- canGoBack/canFinish/canPause sono la verita', piu' precisa dei flag del metadata. -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"isRunning() || !canGoBack()\"\r\n title=\"Con \u00ABindietro\u00BB gli output non vengono memorizzati\"\r\n (click)=\"respond('Previous')\"\r\n >\r\n Indietro\r\n </button>\r\n <button type=\"button\" class=\"fb-btn\" [disabled]=\"isRunning() || !canFinish()\" (click)=\"respond('Finish')\">\r\n Fine\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"isRunning() || !canPause()\"\r\n title=\"Con \u00ABpausa\u00BB gli output non vengono memorizzati\"\r\n (click)=\"respond('Pause')\"\r\n >\r\n Pausa\r\n </button>\r\n </div>\r\n <p class=\"fb-field__hint\">\r\n Con \u00ABindietro\u00BB e \u00ABpausa\u00BB i valori inseriti <strong>non</strong> vengono memorizzati.\r\n </p>\r\n </fieldset>\r\n }\r\n\r\n @if (isWaitingForStageStep()) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Step di orchestrazione</legend>\r\n <p class=\"fb-section__note\">\r\n L\u2019interview e\u2019 sospesa su uno step assegnato: qui si conclude al posto dell\u2019assegnatario.\r\n Concludere puo\u2019 far <strong>sospendere di nuovo</strong> lo stage, con una chiave nuova.\r\n </p>\r\n\r\n <table class=\"fb-dbg__table\">\r\n <tbody>\r\n @for (step of stageSteps(); track step.stepName) {\r\n <tr>\r\n <th scope=\"row\">{{ step.label || step.stepName }}</th>\r\n <td class=\"fb-dbg__type\">{{ step.actionType }}</td>\r\n <td>{{ step.isWaiting ? 'in attesa' : step.status }}</td>\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n\r\n @for (step of waitingStageSteps(); track step.stepName) {\r\n <div class=\"fb-list__item\">\r\n <div class=\"fb-list__header\">\r\n <span class=\"fb-list__title\">{{ step.label || step.stepName }}</span>\r\n </div>\r\n @for (output of stepOutputNames(step.stepName); track output) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">{{ output }}</label>\r\n <input class=\"fb-input\" (input)=\"setStepOutput(output, $any($event.target).value)\" />\r\n </div>\r\n }\r\n <div class=\"fb-field__row\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--primary\"\r\n [disabled]=\"isRunning()\"\r\n (click)=\"completeStep(step.stepName, 'Completed')\"\r\n >\r\n Concludi\r\n </button>\r\n <!-- Il rifiuto non e' un errore: prende il ramo \u00ABStep rifiutato\u00BB dello stage. -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"isRunning()\"\r\n title=\"Prende il ramo \u00ABStep rifiutato\u00BB; senza quel ramo l\u2019interview fallisce\"\r\n (click)=\"completeStep(step.stepName, 'Rejected')\"\r\n >\r\n Rifiuta\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost\"\r\n [disabled]=\"isRunning()\"\r\n (click)=\"completeStep(step.stepName, 'Cancelled')\"\r\n >\r\n Annulla lo step\r\n </button>\r\n </div>\r\n </div>\r\n }\r\n </fieldset>\r\n }\r\n\r\n @if (outputRows().length) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Output del flow</legend>\r\n <table class=\"fb-dbg__table\">\r\n <tbody>\r\n @for (row of outputRows(); track row.name) {\r\n <tr>\r\n <th scope=\"row\">{{ row.name }}</th>\r\n <td class=\"fb-dbg__type\">{{ row.type }}</td>\r\n <td>{{ row.value }}</td>\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n </fieldset>\r\n }\r\n\r\n @if (trace().length) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Traccia</legend>\r\n <ol class=\"fb-dbg__trace\">\r\n @for (entry of trace(); track entry.sequence) {\r\n <li class=\"fb-dbg__trace-item\">\r\n <span class=\"fb-dbg__trace-seq\">{{ entry.sequence }}</span>\r\n @if (entry.elementName) {\r\n <button type=\"button\" class=\"fb-dbg__trace-el\" (click)=\"elementFocused.emit(entry.elementName!)\">\r\n {{ entry.elementName }}\r\n </button>\r\n }\r\n <span class=\"fb-dbg__trace-msg\">{{ entry.message }}</span>\r\n </li>\r\n }\r\n </ol>\r\n </fieldset>\r\n }\r\n\r\n @if (resourceRows().length) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Risorse</legend>\r\n <table class=\"fb-dbg__table\">\r\n <tbody>\r\n @for (row of resourceRows(); track row.name) {\r\n <tr>\r\n <th scope=\"row\">{{ row.name }}</th>\r\n <td class=\"fb-dbg__type\">{{ row.type }}</td>\r\n <td>{{ row.value }}</td>\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n </fieldset>\r\n }\r\n\r\n @if (result()?.interviewKey) {\r\n <p class=\"fb-field__hint\">\r\n Chiave dell\u2019esecuzione sospesa: <code>{{ result()?.interviewKey }}</code>\r\n </p>\r\n }\r\n }\r\n</div>\r\n", styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0;background:var(--fb-surface, #fff)}.fb-dbg__header{display:flex;align-items:center;justify-content:space-between;padding:10px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-dbg__title{margin:0;font-size:14px;font-weight:600;color:var(--fb-text, #1d2939)}.fb-dbg__body{flex:1;min-height:0;overflow-y:auto;padding:10px 12px}.fb-dbg__status{display:flex;align-items:center;gap:8px;margin-bottom:10px}.fb-dbg__badge{padding:2px 8px;border-radius:10px;background:var(--fb-border, #d6dae1);font-size:11px;font-weight:600;color:var(--fb-text, #1d2939)}.fb-dbg__badge--ok{background:color-mix(in srgb,var(--fb-success, #3f8f5f) 18%,transparent);color:var(--fb-success, #3f8f5f)}.fb-dbg__badge--fail{background:color-mix(in srgb,var(--fb-error, #c9372c) 14%,transparent);color:var(--fb-error, #c9372c)}.fb-dbg__badge--wait{background:color-mix(in srgb,var(--fb-accent, #2f6feb) 12%,transparent);color:var(--fb-accent, #2f6feb)}.fb-dbg__current,.fb-dbg__steps{font-size:10px;color:var(--fb-text-muted, #667085)}.fb-dbg__type{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:9px;color:var(--fb-text-subtle, #98a2b3)}.fb-dbg__screen-label{margin:0 0 4px;font-size:12px;font-weight:600;color:var(--fb-text, #1d2939)}.fb-dbg__table{width:100%;border-collapse:collapse;font-size:11px}.fb-dbg__table caption{padding-bottom:3px;font-size:10px;color:var(--fb-text-subtle, #98a2b3);text-align:left}.fb-dbg__table th,.fb-dbg__table td{padding:3px 5px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee);text-align:left;vertical-align:top}.fb-dbg__table th{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-weight:600;color:var(--fb-text, #1d2939)}.fb-dbg__table td{color:var(--fb-text-muted, #667085);word-break:break-word}.fb-dbg__trace{margin:0;padding:0;list-style:none}.fb-dbg__trace-item{display:flex;gap:6px;padding:3px 0;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee);font-size:11px}.fb-dbg__trace-seq{flex:0 0 auto;width:18px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:9px;color:var(--fb-text-subtle, #98a2b3);text-align:right}.fb-dbg__trace-el{flex:0 0 auto;padding:0;border:0;background:transparent;color:var(--fb-accent, #2f6feb);font:inherit;font-size:10px;cursor:pointer;text-decoration:underline}.fb-dbg__trace-msg{color:var(--fb-text-muted, #667085);line-height:1.35}\n"] }]
12375
- }], ctorParameters: () => [], propDecorators: { closed: [{ type: i0.Output, args: ["closed"] }], elementFocused: [{ type: i0.Output, args: ["elementFocused"] }] } });
12856
+ args: [{ selector: 'fb-debug-panel', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<header class=\"fb-dbg__header\">\r\n <h2 class=\"fb-dbg__title\">Debug</h2>\r\n @if (outcome()) {\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" title=\"Svuota\" (click)=\"clear()\">Svuota</button>\r\n }\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" aria-label=\"Chiudi\" (click)=\"close()\">\u00D7</button>\r\n</header>\r\n\r\n<div class=\"fb-dbg__body\">\r\n @if (isRunning()) {\r\n <p class=\"fb-field__hint\">Esecuzione in corso\u2026</p>\r\n }\r\n\r\n @if (!outcome() && !isRunning()) {\r\n <!--\r\n Non e\u2019 un errore: e\u2019 lo stato iniziale. Il pannello serve soprattutto ai flow senza\r\n schermate, dove non c\u2019e\u2019 nessun\u2019altra interfaccia in cui guardare la traccia.\r\n -->\r\n <p class=\"fb-field__hint\">\r\n Nessuna esecuzione. Usa <strong>Esegui</strong> o <strong>Debug</strong> nella barra in alto: qui\r\n compare cio\u2019 che l\u2019applicazione restituisce \u2014 stato, traccia degli elementi eseguiti, risorse e\r\n output.\r\n </p>\r\n }\r\n\r\n @if (isEmptyOutcome()) {\r\n <!--\r\n L\u2019ospite ha risposto, ma senza niente da mostrare. Distinto dal caso \u00ABnessuna esecuzione\u00BB\r\n perche\u2019 il rimedio e\u2019 un altro: qui manca un pezzo dell\u2019implementazione di runFlow.\r\n -->\r\n <p class=\"fb-callout\">\r\n L\u2019esecuzione e\u2019 partita, ma l\u2019applicazione non ha restituito nessun dettaglio da mostrare.\r\n </p>\r\n }\r\n\r\n @if (outcome(); as run) {\r\n @if (status() || run.steps) {\r\n <div class=\"fb-dbg__status\">\r\n @if (status()) {\r\n <span\r\n class=\"fb-dbg__badge\"\r\n [class.fb-dbg__badge--ok]=\"status() === 'Completed'\"\r\n [class.fb-dbg__badge--fail]=\"status() === 'Failed'\"\r\n [class.fb-dbg__badge--wait]=\"status() === 'WaitingForScreen'\"\r\n >\r\n {{ statusLabel() }}\r\n </span>\r\n }\r\n @if (run.currentElementName) {\r\n <!-- Dove si e\u2019 fermata: cliccarlo porta sull\u2019elemento, come le righe della traccia. -->\r\n <button type=\"button\" class=\"fb-dbg__current\" (click)=\"elementFocused.emit(run.currentElementName!)\">\r\n su {{ run.currentElementName }}\r\n </button>\r\n }\r\n @if (run.steps) {\r\n <span class=\"fb-dbg__steps\">{{ run.steps }} passi</span>\r\n }\r\n </div>\r\n }\r\n\r\n @if (statusNote()) {\r\n <p class=\"fb-callout\">{{ statusNote() }}</p>\r\n }\r\n\r\n @if (run.fault) {\r\n <p class=\"fb-callout fb-callout--error\">{{ run.fault }}</p>\r\n }\r\n @for (message of run.errors || []; track $index) {\r\n <p class=\"fb-callout fb-callout--error\">{{ message }}</p>\r\n }\r\n\r\n @if (outputRows().length) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Output del flow</legend>\r\n <table class=\"fb-dbg__table\">\r\n <tbody>\r\n @for (row of outputRows(); track row.name) {\r\n <tr>\r\n <th scope=\"row\">{{ row.name }}</th>\r\n <td class=\"fb-dbg__type\">{{ row.type }}</td>\r\n <td>{{ row.value }}</td>\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n </fieldset>\r\n }\r\n\r\n @if (trace().length) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Traccia</legend>\r\n <p class=\"fb-callout fb-callout--warn\">\r\n La traccia riporta i valori di <strong>tutte</strong> le risorse, dati personali compresi.\r\n </p>\r\n <ol class=\"fb-dbg__trace\">\r\n @for (entry of trace(); track $index) {\r\n <li class=\"fb-dbg__trace-item\">\r\n <span class=\"fb-dbg__trace-seq\">{{ entry.sequence }}</span>\r\n @if (entry.elementName) {\r\n <!-- L\u2019ancora: dalla riga della traccia all\u2019elemento sul canvas. -->\r\n <button type=\"button\" class=\"fb-dbg__trace-el\" (click)=\"elementFocused.emit(entry.elementName!)\">\r\n {{ entry.elementName }}\r\n </button>\r\n }\r\n <span class=\"fb-dbg__trace-msg\">{{ entry.message }}</span>\r\n </li>\r\n }\r\n </ol>\r\n </fieldset>\r\n } @else if (!wasDebug() && !isEmptyOutcome()) {\r\n <!-- Non e\u2019 un difetto: `trace` e `resources` esistono solo con `debug: true` (\u00A76.5). -->\r\n <p class=\"fb-field__hint\">\r\n Traccia e risorse arrivano solo con il comando <strong>Debug</strong>.\r\n </p>\r\n }\r\n\r\n @if (resourceRows().length) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Risorse</legend>\r\n <table class=\"fb-dbg__table\">\r\n <tbody>\r\n @for (row of resourceRows(); track row.name) {\r\n <tr>\r\n <th scope=\"row\">{{ row.name }}</th>\r\n <td class=\"fb-dbg__type\">{{ row.type }}</td>\r\n <td>{{ row.value }}</td>\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n </fieldset>\r\n }\r\n }\r\n</div>\r\n", styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0;background:var(--fb-surface, #fff)}.fb-dbg__header{display:flex;align-items:center;gap:6px;padding:10px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-dbg__title{margin:0 auto 0 0;font-size:14px;font-weight:600;color:var(--fb-text, #1d2939)}.fb-dbg__body{flex:1;min-height:0;overflow-y:auto;padding:10px 12px}.fb-dbg__status{display:flex;align-items:center;gap:8px;margin-bottom:10px}.fb-dbg__badge{padding:2px 8px;border-radius:10px;background:var(--fb-border, #d6dae1);font-size:11px;font-weight:600;color:var(--fb-text, #1d2939)}.fb-dbg__badge--ok{background:color-mix(in srgb,var(--fb-success, #3f8f5f) 18%,transparent);color:var(--fb-success, #3f8f5f)}.fb-dbg__badge--fail{background:color-mix(in srgb,var(--fb-error, #c9372c) 14%,transparent);color:var(--fb-error, #c9372c)}.fb-dbg__badge--wait{background:color-mix(in srgb,var(--fb-accent, #2f6feb) 12%,transparent);color:var(--fb-accent, #2f6feb)}.fb-dbg__steps{font-size:10px;color:var(--fb-text-muted, #667085)}.fb-dbg__current{padding:0;border:0;background:transparent;color:var(--fb-accent, #2f6feb);font:inherit;font-size:10px;cursor:pointer;text-decoration:underline}.fb-dbg__type{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:9px;color:var(--fb-text-subtle, #98a2b3)}.fb-dbg__table{width:100%;border-collapse:collapse;font-size:11px}.fb-dbg__table caption{padding-bottom:3px;font-size:10px;color:var(--fb-text-subtle, #98a2b3);text-align:left}.fb-dbg__table th,.fb-dbg__table td{padding:3px 5px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee);text-align:left;vertical-align:top}.fb-dbg__table th{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-weight:600;color:var(--fb-text, #1d2939)}.fb-dbg__table td{color:var(--fb-text-muted, #667085);word-break:break-word}.fb-dbg__trace{margin:0;padding:0;list-style:none}.fb-dbg__trace-item{display:flex;gap:6px;padding:3px 0;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee);font-size:11px}.fb-dbg__trace-seq{flex:0 0 auto;width:18px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:9px;color:var(--fb-text-subtle, #98a2b3);text-align:right}.fb-dbg__trace-el{flex:0 0 auto;padding:0;border:0;background:transparent;color:var(--fb-accent, #2f6feb);font:inherit;font-size:10px;cursor:pointer;text-decoration:underline}.fb-dbg__trace-msg{color:var(--fb-text-muted, #667085);line-height:1.35}\n"] }]
12857
+ }], propDecorators: { outcome: [{ type: i0.Input, args: [{ isSignal: true, alias: "outcome", required: false }] }], wasDebug: [{ type: i0.Input, args: [{ isSignal: true, alias: "wasDebug", required: false }] }], isRunning: [{ type: i0.Input, args: [{ isSignal: true, alias: "isRunning", required: false }] }], closed: [{ type: i0.Output, args: ["closed"] }], elementFocused: [{ type: i0.Output, args: ["elementFocused"] }], cleared: [{ type: i0.Output, args: ["cleared"] }] } });
12858
+ function rowsOf(values) {
12859
+ return Object.entries(values ?? {}).map(([name, typed]) => ({
12860
+ name,
12861
+ type: typed?.type ?? '',
12862
+ value: render(typed),
12863
+ }));
12864
+ }
12865
+ /**
12866
+ * §6.5 — il tipo dichiarato vince, e due tipi hanno una forma propria invece di `value`: un
12867
+ * record e un'istanza di classe. Cercare `value` su un `Structure` mostrerebbe «—» pur avendo
12868
+ * i dati sotto gli occhi (§4.7).
12869
+ */
12870
+ function render(typed) {
12871
+ if (!typed) {
12872
+ return '—';
12873
+ }
12874
+ if (typed.instance) {
12875
+ const members = Object.entries(typed.instance.members ?? {})
12876
+ .map(([name, member]) => `${name}: ${render(member)}`)
12877
+ .join(', ');
12878
+ return `${typed.instance.className ?? 'istanza'} { ${members} }`;
12879
+ }
12880
+ if (typed.record) {
12881
+ const fields = Object.entries(typed.record.fields ?? {})
12882
+ .map(([name, value]) => `${name}: ${value === null || value === undefined ? '—' : String(value)}`)
12883
+ .join(', ');
12884
+ const head = [typed.record.objectType, typed.record.id].filter(Boolean).join('#');
12885
+ return `${head || 'record'} { ${fields} }`;
12886
+ }
12887
+ if (typed.value === undefined || typed.value === null) {
12888
+ return '—';
12889
+ }
12890
+ if (typeof typed.value === 'object') {
12891
+ return JSON.stringify(typed.value);
12892
+ }
12893
+ return String(typed.value);
12894
+ }
12376
12895
 
12377
12896
  /**
12378
12897
  * Il builder: il componente standalone da innestare nell'applicazione ospite.
@@ -12398,6 +12917,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImpo
12398
12917
  */
12399
12918
  class FlowBuilderComponent {
12400
12919
  api = inject(FlowBuilderApi);
12920
+ clipboard = inject(FlowClipboardService);
12401
12921
  destroyRef = inject(DestroyRef);
12402
12922
  /** Serve a `afterNextRender` chiamato fuori dal costruttore. */
12403
12923
  injector = inject(Injector);
@@ -12478,6 +12998,9 @@ class FlowBuilderComponent {
12478
12998
  }
12479
12999
  }, ...(ngDevMode ? [{ debugName: "statusLabel" }] : []));
12480
13000
  constructor() {
13001
+ // Gli appunti sopravvivono al ricaricamento (stanno nello storage): il comando «Incolla»
13002
+ // deve nascere acceso se qualcosa c'e' già, non solo dopo la prima copia di questa sessione.
13003
+ this.clipboard.refresh();
12481
13004
  /**
12482
13005
  * I dizionari si caricano una volta e si tengono in cache, **per `processType`**: le
12483
13006
  * globali dipendono dal tipo di flow — `$Record` non esiste in uno screen flow — e
@@ -12634,7 +13157,9 @@ class FlowBuilderComponent {
12634
13157
  * capita anche solo per spostare un node, e una finestra che si apre a ogni click sarebbe
12635
13158
  * insopportabile. Nel pannello laterale, invece, mostrare subito il form non costa nulla.
12636
13159
  */
12637
- onSelectionChange(name) {
13160
+ onSelectionChange(names) {
13161
+ this.selectedNames.set(names);
13162
+ const name = names[0] ?? null;
12638
13163
  this.selectedName.set(name);
12639
13164
  if (name && !this.isDialogMode()) {
12640
13165
  this.activePanel.set('inspector');
@@ -12668,9 +13193,9 @@ class FlowBuilderComponent {
12668
13193
  this.isDialogOpen.set(false);
12669
13194
  }
12670
13195
  /**
12671
- * Evidenzia l'elemento sul canvas senza cambiare pannello.
12672
- * Serve durante la prova: l'esecuzione segnala su quale elemento e' ferma, ma sostituire
12673
- * il pannello con l'inspector farebbe sparire il form da compilare a metà interview.
13196
+ * Evidenzia l'elemento sul canvas **senza** cambiare pannello.
13197
+ * Serve alla traccia del debug: portare l'utente sull'elemento e' il punto del pannello, ma
13198
+ * sostituirlo con l'inspector farebbe sparire la traccia che si stava leggendo.
12674
13199
  */
12675
13200
  highlightElement(name) {
12676
13201
  this.selectedName.set(name);
@@ -12741,6 +13266,7 @@ class FlowBuilderComponent {
12741
13266
  // Rimuove anche i connector che lo referenziavano: un target orfano bloccherebbe
12742
13267
  // l'attivazione con CONNECTOR_TARGET_UNKNOWN.
12743
13268
  this.store.removeNode(name);
13269
+ this.selectedNames.update((names) => names.filter((selected) => selected !== name));
12744
13270
  if (this.selectedName() === name) {
12745
13271
  this.selectedName.set(START_NODE_NAME);
12746
13272
  // L'elemento non c'e' piu': una dialog aperta sul vuoto non ha senso.
@@ -12755,9 +13281,154 @@ class FlowBuilderComponent {
12755
13281
  this.selectedName.set(newName);
12756
13282
  }
12757
13283
  onDuplicateNode(name) {
12758
- const newName = uniqueFlowName(`${name}_copia`, this.store.usedNames());
12759
- this.store.duplicateNode(name, newName);
12760
- this.selectedName.set(newName);
13284
+ this.duplicateNames([name]);
13285
+ }
13286
+ // ------------------------------------------------------------- copia e incolla
13287
+ //
13288
+ // Il gesto vale su un **blocco**: la selezione del canvas puo' contenere piu' elementi
13289
+ // (rettangolo, `Ctrl`+`A`), e i collegamenti interni al blocco si conservano. Lo Start non
13290
+ // entra mai in una copia: e' uno per flow (§3.4), e incollarne un secondo sarebbe un errore
13291
+ // che nessuno ha chiesto.
13292
+ /** La selezione del canvas per intero; `selectedName` resta l'elemento «corrente». */
13293
+ selectedNames = signal([], ...(ngDevMode ? [{ debugName: "selectedNames" }] : []));
13294
+ /** L'anteprima dell'incolla, mentre la finestra e' aperta. */
13295
+ pastePreview = signal(null, ...(ngDevMode ? [{ debugName: "pastePreview" }] : []));
13296
+ /**
13297
+ * I nomi copiabili: la selezione meno lo Start (§3.4) e meno cio' che nel documento non c'e'
13298
+ * piu'. Il secondo filtro non e' teorico: dopo un annulla la selezione resta sul nome di un
13299
+ * elemento appena tolto, e senza il controllo il comando risultava attivo su niente.
13300
+ */
13301
+ copyableNames() {
13302
+ const names = this.selectedNames().length ? this.selectedNames() : [this.selectedName() ?? ''];
13303
+ const known = this.store.nodeByName();
13304
+ return names.filter((name) => !!name && name !== START_NODE_NAME && known.has(name));
13305
+ }
13306
+ canCopy = computed(() => this.copyableNames().length > 0, ...(ngDevMode ? [{ debugName: "canCopy" }] : []));
13307
+ /** Il comando «Incolla» si accende quando negli appunti c'e' qualcosa, anche di un altro flow. */
13308
+ canPaste = computed(() => {
13309
+ // Letto dal signal di revisione: cambia anche quando la copia arriva da un'altra scheda.
13310
+ this.clipboard.revision();
13311
+ return this.clipboard.hasContent();
13312
+ }, ...(ngDevMode ? [{ debugName: "canPaste" }] : []));
13313
+ copySelection() {
13314
+ const names = this.copyableNames();
13315
+ if (!names.length) {
13316
+ return;
13317
+ }
13318
+ const payload = buildClipboardPayload({
13319
+ nodes: this.store.nodes(),
13320
+ resources: this.store.resources(),
13321
+ names,
13322
+ flowName: this.session.flowName() ?? this.store.document().fullName,
13323
+ processType: this.store.document().processType,
13324
+ });
13325
+ if (!payload) {
13326
+ return;
13327
+ }
13328
+ this.clipboard.write(payload);
13329
+ this.clipboard.refresh();
13330
+ const count = payload.nodes.length;
13331
+ const resources = payload.resources.length;
13332
+ this.notice.set({
13333
+ kind: 'info',
13334
+ message: resources
13335
+ ? `Copiati ${count} ${count === 1 ? 'elemento' : 'elementi'} e ${resources} ${resources === 1 ? 'risorsa referenziata' : 'risorse referenziate'}.`
13336
+ : `Copiati ${count} ${count === 1 ? 'elemento' : 'elementi'}.`,
13337
+ });
13338
+ }
13339
+ /**
13340
+ * Prepara l'incolla: calcola il piano e apre la finestra. Non tocca il documento — cosa
13341
+ * cambierebbe si vede prima, ed e' l'unico momento in cui la scelta sulle risorse mancanti ha
13342
+ * ancora senso.
13343
+ */
13344
+ startPaste() {
13345
+ if (!this.isEditable()) {
13346
+ return;
13347
+ }
13348
+ const payload = this.clipboard.read();
13349
+ if (!payload) {
13350
+ this.notice.set({ kind: 'info', message: 'Non c’e’ niente da incollare.' });
13351
+ return;
13352
+ }
13353
+ const plan = planPaste(payload, {
13354
+ usedNames: this.store.usedNames(),
13355
+ processType: this.store.document().processType,
13356
+ });
13357
+ this.pastePreview.set({ payload, plan });
13358
+ }
13359
+ cancelPaste() {
13360
+ this.pastePreview.set(null);
13361
+ }
13362
+ confirmPaste(options) {
13363
+ const preview = this.pastePreview();
13364
+ if (!preview) {
13365
+ return;
13366
+ }
13367
+ const created = this.store.pasteClipboard(preview.payload, preview.plan, {
13368
+ createMissingResources: options.createMissingResources,
13369
+ });
13370
+ this.pastePreview.set(null);
13371
+ this.selectNames(created);
13372
+ const count = created.length;
13373
+ this.notice.set({
13374
+ kind: 'info',
13375
+ message: `Incollati ${count} ${count === 1 ? 'elemento' : 'elementi'}.`,
13376
+ });
13377
+ }
13378
+ /** Duplica in loco, senza passare dagli appunti: stesso motore, nessuna domanda da fare. */
13379
+ duplicateSelection() {
13380
+ this.duplicateNames(this.copyableNames());
13381
+ }
13382
+ duplicateNames(names) {
13383
+ if (!this.isEditable() || !names.length) {
13384
+ return;
13385
+ }
13386
+ const created = this.store.duplicateNodes(names);
13387
+ this.selectNames(created);
13388
+ }
13389
+ /** Porta la selezione sul blocco appena creato: senza, resterebbe su cio' da cui e' nato. */
13390
+ selectNames(names) {
13391
+ if (!names.length) {
13392
+ return;
13393
+ }
13394
+ this.selectedNames.set(names);
13395
+ this.selectedName.set(names[0]);
13396
+ }
13397
+ /**
13398
+ * Le scorciatoie. L'ascoltatore sta sull'**host** del builder e non sul documento: la libreria
13399
+ * si innesta in una pagina che non e' nostra, e `Ctrl`+`C` catturato globalmente ruberebbe la
13400
+ * copia all'applicazione ospite. Per la stessa ragione il gesto si ignora quando il fuoco e'
13401
+ * dentro un campo: lì `Ctrl`+`C` copia il testo, che e' cio' che l'utente si aspetta.
13402
+ */
13403
+ onKeyDown(event) {
13404
+ if (!(event.ctrlKey || event.metaKey) || event.altKey) {
13405
+ return;
13406
+ }
13407
+ const target = event.target;
13408
+ const tag = target?.tagName;
13409
+ if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || target?.isContentEditable) {
13410
+ return;
13411
+ }
13412
+ switch (event.key.toLowerCase()) {
13413
+ case 'c':
13414
+ if (this.canCopy()) {
13415
+ event.preventDefault();
13416
+ this.copySelection();
13417
+ }
13418
+ return;
13419
+ case 'v':
13420
+ event.preventDefault();
13421
+ this.startPaste();
13422
+ return;
13423
+ case 'd':
13424
+ if (this.canCopy()) {
13425
+ event.preventDefault();
13426
+ this.duplicateSelection();
13427
+ }
13428
+ return;
13429
+ default:
13430
+ return;
13431
+ }
12761
13432
  }
12762
13433
  /** Riordina il grafo e **scrive** le coordinate nel documento (§3.5). */
12763
13434
  autoLayout() {
@@ -12919,6 +13590,105 @@ class FlowBuilderComponent {
12919
13590
  this.showError(error);
12920
13591
  }
12921
13592
  }
13593
+ // ------------------------------------------------------------ esegui e debug
13594
+ //
13595
+ // I due comandi non eseguono niente: raccolgono i valori di ingresso e chiamano
13596
+ // `runFlow`/`debugFlow`, che le implementa l'applicazione ospite. Senza implementazione la
13597
+ // primitiva rifiuta con `MissingService` e il banner lo dice — l'editor non finge un avvio.
13598
+ /** Quale delle due finestre e' aperta; `null` = nessuna. */
13599
+ runMode = signal(null, ...(ngDevMode ? [{ debugName: "runMode" }] : []));
13600
+ /** La chiamata all'ospite e' in volo: il bottone della finestra resta spento. */
13601
+ isStartingRun = signal(false, ...(ngDevMode ? [{ debugName: "isStartingRun" }] : []));
13602
+ /**
13603
+ * L'esito dell'ultima esecuzione, se l'ospite ne ha restituito uno.
13604
+ *
13605
+ * È tutto cio' che l'editor sa di un'esecuzione, e serve soprattutto ai flow **senza
13606
+ * schermate**: lì non si apre nessuna interfaccia del runtime, quindi senza questo pannello
13607
+ * non ci sarebbe **nessun** posto in cui vedere quali elementi sono stati eseguiti.
13608
+ */
13609
+ runOutcome = signal(null, ...(ngDevMode ? [{ debugName: "runOutcome" }] : []));
13610
+ /** L'ultima esecuzione era in debug: senza, traccia e risorse non arrivano ed e' normale. */
13611
+ wasRunInDebug = signal(false, ...(ngDevMode ? [{ debugName: "wasRunInDebug" }] : []));
13612
+ /**
13613
+ * Si esegue cio' che e' **salvato**: su un flow mai scritto non c'e' niente da avviare, e il
13614
+ * titolo del bottone lo dice invece di lasciare un comando che non risponde.
13615
+ */
13616
+ canRun = computed(() => !this.session.isNew() && !!this.session.flowName(), ...(ngDevMode ? [{ debugName: "canRun" }] : []));
13617
+ openRun(mode) {
13618
+ if (!this.canRun()) {
13619
+ return;
13620
+ }
13621
+ this.runMode.set(mode);
13622
+ }
13623
+ closeRun() {
13624
+ this.runMode.set(null);
13625
+ }
13626
+ /** Svuota il pannello: l'esito di prima non riguarda piu' il documento che si sta editando. */
13627
+ clearRunOutcome() {
13628
+ this.runOutcome.set(null);
13629
+ }
13630
+ /**
13631
+ * Consegna gli ingressi all'ospite.
13632
+ *
13633
+ * La finestra si chiude **subito**, prima che la chiamata risponda: un'esecuzione puo' durare
13634
+ * quanto vuole — con schermate da compilare puo' finire minuti dopo — e tenere aperto il form
13635
+ * degli ingressi per tutto quel tempo coprirebbe proprio il grafo che si sta guardando. Il
13636
+ * pannello «Debug» prende il posto dell'attesa, e l'esito ci arriva quando arriva.
13637
+ */
13638
+ async confirmRun(inputs) {
13639
+ const mode = this.runMode();
13640
+ const flowName = this.session.flowName();
13641
+ if (!mode || !flowName) {
13642
+ return;
13643
+ }
13644
+ const request = {
13645
+ flowName,
13646
+ version: this.session.version() ?? undefined,
13647
+ inputs,
13648
+ debug: mode === 'debug',
13649
+ };
13650
+ this.isStartingRun.set(true);
13651
+ this.runOutcome.set(null);
13652
+ this.wasRunInDebug.set(mode === 'debug');
13653
+ // Il pannello e' il posto in cui si guarda cosa sta succedendo: si apre insieme all'avvio.
13654
+ this.activePanel.set('debug');
13655
+ this.runMode.set(null);
13656
+ try {
13657
+ const outcome = await (mode === 'debug' ? this.api.debugFlow(request) : this.api.runFlow(request));
13658
+ /**
13659
+ * Restituire un esito e' facoltativo: un ospite che mostra tutto in una sua interfaccia
13660
+ * risolve con `void`, e allora non c'e' niente da mettere nel pannello.
13661
+ */
13662
+ this.runOutcome.set(outcome ?? null);
13663
+ if (!outcome) {
13664
+ this.notice.set({
13665
+ kind: 'info',
13666
+ message: mode === 'debug' ? 'Debug avviato.' : 'Esecuzione avviata.',
13667
+ });
13668
+ }
13669
+ }
13670
+ catch (error) {
13671
+ /**
13672
+ * `MissingService` non e' un guasto: e' un ambiente che quel comando non lo espone. Va
13673
+ * detto con parole sue — «il messaggio della primitiva» sarebbe «primitiva non
13674
+ * disponibile», che non aiuta chi ha appena premuto «Esegui».
13675
+ */
13676
+ if (FlowApiError.is(error) && error.category === 'MissingService') {
13677
+ this.notice.set({
13678
+ kind: 'error',
13679
+ message: mode === 'debug'
13680
+ ? 'Questo ambiente non espone il debug dei flow: manca l’implementazione di debugFlow.'
13681
+ : 'Questo ambiente non espone l’esecuzione dei flow: manca l’implementazione di runFlow.',
13682
+ });
13683
+ return;
13684
+ }
13685
+ // Un errore vero dell'ospite: si dice com'e' arrivato, senza travestirlo da esito.
13686
+ this.showError(error);
13687
+ }
13688
+ finally {
13689
+ this.isStartingRun.set(false);
13690
+ }
13691
+ }
12922
13692
  async createNewVersion() {
12923
13693
  try {
12924
13694
  const result = await this.session.createNewVersion();
@@ -13064,7 +13834,7 @@ class FlowBuilderComponent {
13064
13834
  });
13065
13835
  }
13066
13836
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.27", ngImport: i0, type: FlowBuilderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
13067
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.27", type: FlowBuilderComponent, isStandalone: true, selector: "fb-flow-builder", inputs: { flowName: { classPropertyName: "flowName", publicName: "flowName", isSignal: true, isRequired: false, transformFunction: null }, version: { classPropertyName: "version", publicName: "version", isSignal: true, isRequired: false, transformFunction: null }, author: { classPropertyName: "author", publicName: "author", isSignal: true, isRequired: false, transformFunction: null }, defaultProcessType: { classPropertyName: "defaultProcessType", publicName: "defaultProcessType", isSignal: true, isRequired: false, transformFunction: null }, inspectorMode: { classPropertyName: "inspectorMode", publicName: "inspectorMode", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { saved: "saved", activated: "activated", closeRequested: "closeRequested" }, providers: [
13837
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.27", type: FlowBuilderComponent, isStandalone: true, selector: "fb-flow-builder", inputs: { flowName: { classPropertyName: "flowName", publicName: "flowName", isSignal: true, isRequired: false, transformFunction: null }, version: { classPropertyName: "version", publicName: "version", isSignal: true, isRequired: false, transformFunction: null }, author: { classPropertyName: "author", publicName: "author", isSignal: true, isRequired: false, transformFunction: null }, defaultProcessType: { classPropertyName: "defaultProcessType", publicName: "defaultProcessType", isSignal: true, isRequired: false, transformFunction: null }, inspectorMode: { classPropertyName: "inspectorMode", publicName: "inspectorMode", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { saved: "saved", activated: "activated", closeRequested: "closeRequested" }, host: { listeners: { "keydown": "onKeyDown($event)" } }, providers: [
13068
13838
  FlowDocumentStore,
13069
13839
  FlowDictionaryStore,
13070
13840
  FlowCatalogStore,
@@ -13072,18 +13842,22 @@ class FlowBuilderComponent {
13072
13842
  FormulaValidationService,
13073
13843
  FlowEditorSession,
13074
13844
  FlowLayoutService,
13075
- ], viewQueries: [{ propertyName: "copyNameInput", first: true, predicate: ["copyNameInput"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"fb-builder\" [attr.data-fb-theme]=\"null\">\r\n <header class=\"fb-top\">\r\n <div class=\"fb-top__identity\">\r\n <input\r\n class=\"fb-top__label\"\r\n [value]=\"document().label || ''\"\r\n placeholder=\"Nome del flow\"\r\n aria-label=\"Nome del flow\"\r\n [disabled]=\"!isEditable()\"\r\n (input)=\"setLabel($any($event.target).value)\"\r\n />\r\n <div class=\"fb-top__meta\">\r\n <input\r\n class=\"fb-top__name\"\r\n [value]=\"document().fullName || ''\"\r\n placeholder=\"NomeTecnico\"\r\n aria-label=\"Nome tecnico del flow\"\r\n [disabled]=\"!isEditable()\"\r\n (input)=\"setFullName($any($event.target).value)\"\r\n />\r\n <select\r\n class=\"fb-top__process\"\r\n [fbValue]=\"document().processType || ''\"\r\n aria-label=\"Tipo di flow\"\r\n [disabled]=\"!isEditable()\"\r\n (change)=\"setProcessType($any($event.target).value)\"\r\n >\r\n <!--\r\n Il segnaposto esiste perche' un `<select>` senza opzione corrispondente non e'\r\n \u00ABvuoto\u00BB: e' a `selectedIndex = -1`, e mostra una casella bianca. Su un flow nuovo\r\n (`processType` non ancora scelto) e' questa la riga che si vede, disabilitata\r\n perche' non e' un valore valido da salvare.\r\n -->\r\n @if (!document().processType) {\r\n <option value=\"\" disabled>\u2014 tipo di flow \u2014</option>\r\n }\r\n @for (type of processTypes(); track type.value) {\r\n <option [value]=\"type.value\">{{ type.label }}</option>\r\n }\r\n <!--\r\n Il tipo del documento che il dizionario non conosce: senza questa opzione la select\r\n resta bianca su un flow che il backend ha salvato con un `processType` fuori\r\n catalogo (o quando `processTypes` manca dalla risposta dei dizionari), e sembra che\r\n l'editor non abbia ricaricato il flow. Va mostrato **senza** riscrivere il\r\n documento: il valore e' del backend, non nostro da correggere.\r\n -->\r\n @if (isProcessTypeOutOfCatalog()) {\r\n <option [value]=\"document().processType\">{{ document().processType }} (fuori catalogo)</option>\r\n }\r\n </select>\r\n <span class=\"fb-top__status\">{{ statusLabel() }}</span>\r\n @if (session.version() != null) {\r\n <span class=\"fb-top__version\">v{{ session.version() }}</span>\r\n }\r\n @if (isDirty()) {\r\n <span class=\"fb-top__dirty\" title=\"Ci sono modifiche non salvate\">modificato</span>\r\n }\r\n </div>\r\n </div>\r\n\r\n <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 @if (canDuplicate()) {\r\n <!-- \u00A76.2 \u00ABDuplica\u00BB: il flow sotto un altro nome, alla versione 1. -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"isBusy()\"\r\n title=\"Salva una copia con un altro nome\"\r\n (click)=\"startCopy()\"\r\n >\r\n Duplica\u2026\r\n </button>\r\n }\r\n\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"isBusy() || !canActivate()\"\r\n [title]=\"\r\n canActivate()\r\n ? 'Attiva questa versione'\r\n : 'L\u2019attivazione esige zero errori: correggili nel pannello dei problemi'\r\n \"\r\n (click)=\"activate()\"\r\n >\r\n Attiva\r\n </button>\r\n </div>\r\n </header>\r\n\r\n @if (conflict()) {\r\n <!-- \u00A79.3: qualcun altro ha salvato. Due strade, entrambe offerte. -->\r\n <div class=\"fb-banner fb-banner--warn\" role=\"alert\">\r\n <span>\r\n {{ conflict()?.message }}\r\n @if (conflict()?.conflictingAuthor) {\r\n Ha salvato {{ conflict()?.conflictingAuthor }}.\r\n }\r\n </span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--icon\" (click)=\"reloadAfterConflict()\">Ricarica</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--icon\" (click)=\"saveAsNewVersionAfterConflict()\">\r\n Salva come nuova versione\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" (click)=\"dismissConflict()\">\u00D7</button>\r\n </div>\r\n }\r\n\r\n @if (isCopyOpen()) {\r\n <!--\r\n Il nome si chiede prima di scrivere: e\u2019 l\u2019unico dato che il backend non puo\u2019 inventare, e\r\n un doppione lo rifiuta con AlreadyExists (\u00A76.2). Invio conferma, Esc annulla.\r\n -->\r\n <div class=\"fb-banner fb-copy\" role=\"group\" aria-label=\"Duplica il flow\">\r\n <label class=\"fb-copy__field\">\r\n <span class=\"fb-copy__caption\">Nome tecnico della copia</span>\r\n <input\r\n #copyNameInput\r\n class=\"fb-copy__input\"\r\n [value]=\"copyName()\"\r\n placeholder=\"NomeTecnico_Copia\"\r\n (input)=\"setCopyName($any($event.target).value)\"\r\n (keydown.enter)=\"confirmCopy()\"\r\n (keydown.escape)=\"cancelCopy()\"\r\n />\r\n </label>\r\n <label class=\"fb-copy__field\">\r\n <span class=\"fb-copy__caption\">Nome visibile</span>\r\n <input\r\n class=\"fb-copy__input\"\r\n [value]=\"copyLabel()\"\r\n placeholder=\"(facoltativo)\"\r\n (input)=\"setCopyLabel($any($event.target).value)\"\r\n (keydown.enter)=\"confirmCopy()\"\r\n (keydown.escape)=\"cancelCopy()\"\r\n />\r\n </label>\r\n <span class=\"fb-copy__hint\" [class.fb-copy__hint--error]=\"!!copyNameProblem()\">\r\n {{ copyNameProblem() || copyHint() }}\r\n </span>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--primary fb-btn--icon\"\r\n [disabled]=\"isBusy() || !!copyNameProblem()\"\r\n (click)=\"confirmCopy()\"\r\n >\r\n Duplica\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" (click)=\"cancelCopy()\">Annulla</button>\r\n </div>\r\n }\r\n\r\n @if (notice()) {\r\n <div\r\n class=\"fb-banner\"\r\n [class.fb-banner--error]=\"notice()?.kind === 'error'\"\r\n role=\"status\"\r\n >\r\n <span>{{ notice()?.message }}</span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" aria-label=\"Chiudi\" (click)=\"dismissNotice()\">\r\n \u00D7\r\n </button>\r\n </div>\r\n }\r\n\r\n @if (hasScreensInAutoLaunched()) {\r\n <div class=\"fb-banner fb-banner--error\">\r\n Un flow AutoLaunched che contiene screen e\u2019 un errore di validazione: non c\u2019e\u2019 nessuno a cui mostrarli.\r\n </div>\r\n }\r\n @if (isOrchestrationWithoutStages()) {\r\n <div class=\"fb-banner fb-banner--warn\">\r\n Un flow Orchestration senza stage di orchestrazione viene segnalato dalla validazione\r\n (ORCHESTRATION_WITHOUT_STAGES).\r\n </div>\r\n }\r\n @if (hasNoScreensInScreenFlow()) {\r\n <div class=\"fb-banner fb-banner--warn\">\r\n Un flow di tipo Screen senza nessuno screen viene segnalato dalla validazione.\r\n </div>\r\n }\r\n @if (!isEditable()) {\r\n <div class=\"fb-banner\">\r\n Questa versione e\u2019 in sola lettura: per modificarla creane una nuova.\r\n </div>\r\n }\r\n\r\n <div class=\"fb-main\">\r\n <aside class=\"fb-main__palette\">\r\n <fb-element-palette [processType]=\"document().processType\" (elementPicked)=\"onElementPicked($event)\" />\r\n </aside>\r\n\r\n <div class=\"fb-main__center\">\r\n <fb-flow-canvas\r\n class=\"fb-main__canvas\"\r\n [selectedName]=\"selectedName()\"\r\n [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 (renamed)=\"onNodeRenamed($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 (renamed)=\"onNodeRenamed($event)\"\r\n />\r\n }\r\n</div>\r\n", styles: [":host{display:block;width:100%;height:100%;min-height:0;font:inherit;color:var(--fb-text, #1d2939)}.fb-builder{position:relative;display:flex;flex-direction:column;width:100%;height:100%;min-height:0;background:var(--fb-surface, #fff)}.fb-top{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:9px 14px;border-bottom:1px solid var(--fb-border, #e2e5eb);background:var(--fb-surface, #fff)}.fb-top__identity{min-width:0}.fb-top__label{width:100%;max-width:420px;padding:2px 4px;border:1px solid transparent;border-radius:4px;background:transparent;color:var(--fb-text, #1d2939);font:inherit;font-size:15px;font-weight:600}.fb-top__label:hover:not(:disabled),.fb-top__label:focus-visible{border-color:var(--fb-border, #d6dae1);background:var(--fb-surface, #fff)}.fb-top__meta{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-top:2px}.fb-top__name{width:180px;padding:1px 4px;border:1px solid transparent;border-radius:4px;background:transparent;color:var(--fb-text-muted, #667085);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px}.fb-top__name:hover:not(:disabled),.fb-top__name:focus-visible{border-color:var(--fb-border, #d6dae1)}.fb-top__process{padding:1px 4px;border:1px solid var(--fb-border-subtle, #e6e9ee);border-radius:4px;background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:11px}.fb-top__status,.fb-top__version,.fb-top__dirty{padding:2px 7px;border-radius:999px;background:var(--fb-surface-sunken, #eef0f4);color:var(--fb-text-muted, #6b7086);font-size:10px;font-weight:600}.fb-top__version{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.fb-top__dirty{background:color-mix(in srgb,var(--fb-warning, #b7791f) 16%,transparent);color:var(--fb-warning, #b7791f)}.fb-top__actions{display:flex;flex-wrap:wrap;gap:4px}.fb-banner{display:flex;flex-wrap:wrap;align-items:center;gap:8px;padding:6px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee);background:color-mix(in srgb,var(--fb-accent, #2f6feb) 7%,transparent);font-size:11px}.fb-banner--warn{background:color-mix(in srgb,var(--fb-warning, #b7791f) 12%,transparent);color:var(--fb-warning, #b7791f)}.fb-banner--error{background:color-mix(in srgb,var(--fb-error, #c9372c) 10%,transparent);color:var(--fb-error, #c9372c)}.fb-banner>span{flex:1;min-width:200px}.fb-copy__field{display:flex;align-items:center;gap:6px}.fb-copy__caption{color:var(--fb-text-muted, #667085);white-space:nowrap}.fb-copy__input{width:180px;padding:2px 5px;border:1px solid var(--fb-border, #d6dae1);border-radius:4px;background:var(--fb-surface, #fff);color:var(--fb-text, #1d2939);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px}.fb-copy__hint{flex:1;min-width:180px;color:var(--fb-text-muted, #667085)}.fb-copy__hint--error{color:var(--fb-error, #c9372c)}.fb-main{display:flex;flex:1;min-height:0}.fb-main__palette{flex:0 0 190px;min-width:0}.fb-main__center{display:flex;flex:1;flex-direction:column;min-width:0;min-height:0}.fb-main__canvas{flex:1;min-height:0}.fb-main__problems{flex:0 0 auto;height:220px}.fb-main__problems-toggle{display:flex;align-items:center;gap:6px;padding:4px 12px;border:0;border-top:1px solid var(--fb-border, #d6dae1);background:var(--fb-surface-alt, #f8f9fb);color:var(--fb-text-muted, #667085);font:inherit;font-size:11px;cursor:pointer}.fb-main__count{padding:0 5px;border-radius:8px;background:var(--fb-border, #d6dae1);font-size:9px;font-weight:700}.fb-main__count--error{background:color-mix(in srgb,var(--fb-error, #c9372c) 16%,transparent);color:var(--fb-error, #c9372c)}.fb-main__count--warn{background:color-mix(in srgb,var(--fb-warning, #b7791f) 16%,transparent);color:var(--fb-warning, #b7791f)}.fb-main__side{display:flex;flex-direction:column;flex:0 0 340px;min-width:0;min-height:0;border-left:1px solid var(--fb-border, #d6dae1);background:var(--fb-surface, #fff)}.fb-side__tabs{display:flex;gap:2px;margin:8px 10px;padding:3px;border-radius:var(--fb-radius, 10px);background:var(--fb-surface-sunken, #eef0f4)}.fb-side__tab{flex:1;padding:5px 8px;border:0;border-radius:var(--fb-radius-xs, 6px);background:transparent;color:var(--fb-text-muted, #6b7086);font:inherit;font-size:11px;cursor:pointer;transition:background .12s ease,color .12s ease}.fb-side__tab: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", "renamed"] }, { kind: "component", type: ElementInspectorComponent, selector: "fb-element-inspector", inputs: ["selectedName", "showHeader"], outputs: ["closed", "removeRequested", "duplicateRequested", "renamed"] }, { kind: "component", type: ElementPaletteComponent, selector: "fb-element-palette", inputs: ["processType"], outputs: ["elementPicked"] }, { kind: "component", type: FlowCanvasComponent, selector: "fb-flow-canvas", inputs: ["selectedName", "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 });
13845
+ ], viewQueries: [{ propertyName: "copyNameInput", first: true, predicate: ["copyNameInput"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"fb-builder\" [attr.data-fb-theme]=\"null\">\r\n <header class=\"fb-top\">\r\n <div class=\"fb-top__identity\">\r\n <input\r\n class=\"fb-top__label\"\r\n [value]=\"document().label || ''\"\r\n placeholder=\"Nome del flow\"\r\n aria-label=\"Nome del flow\"\r\n [disabled]=\"!isEditable()\"\r\n (input)=\"setLabel($any($event.target).value)\"\r\n />\r\n <div class=\"fb-top__meta\">\r\n <input\r\n class=\"fb-top__name\"\r\n [value]=\"document().fullName || ''\"\r\n placeholder=\"NomeTecnico\"\r\n aria-label=\"Nome tecnico del flow\"\r\n [disabled]=\"!isEditable()\"\r\n (input)=\"setFullName($any($event.target).value)\"\r\n />\r\n <select\r\n class=\"fb-top__process\"\r\n [fbValue]=\"document().processType || ''\"\r\n aria-label=\"Tipo di flow\"\r\n [disabled]=\"!isEditable()\"\r\n (change)=\"setProcessType($any($event.target).value)\"\r\n >\r\n <!--\r\n Il segnaposto esiste perche' un `<select>` senza opzione corrispondente non e'\r\n \u00ABvuoto\u00BB: e' a `selectedIndex = -1`, e mostra una casella bianca. Su un flow nuovo\r\n (`processType` non ancora scelto) e' questa la riga che si vede, disabilitata\r\n perche' non e' un valore valido da salvare.\r\n -->\r\n @if (!document().processType) {\r\n <option value=\"\" disabled>\u2014 tipo di flow \u2014</option>\r\n }\r\n @for (type of processTypes(); track type.value) {\r\n <option [value]=\"type.value\">{{ type.label }}</option>\r\n }\r\n <!--\r\n Il tipo del documento che il dizionario non conosce: senza questa opzione la select\r\n resta bianca su un flow che il backend ha salvato con un `processType` fuori\r\n catalogo (o quando `processTypes` manca dalla risposta dei dizionari), e sembra che\r\n l'editor non abbia ricaricato il flow. Va mostrato **senza** riscrivere il\r\n documento: il valore e' del backend, non nostro da correggere.\r\n -->\r\n @if (isProcessTypeOutOfCatalog()) {\r\n <option [value]=\"document().processType\">{{ document().processType }} (fuori catalogo)</option>\r\n }\r\n </select>\r\n <span class=\"fb-top__status\">{{ statusLabel() }}</span>\r\n @if (session.version() != null) {\r\n <span class=\"fb-top__version\">v{{ session.version() }}</span>\r\n }\r\n @if (isDirty()) {\r\n <span class=\"fb-top__dirty\" title=\"Ci sono modifiche non salvate\">modificato</span>\r\n }\r\n </div>\r\n </div>\r\n\r\n <div class=\"fb-top__actions\">\r\n <button type=\"button\" class=\"fb-btn\" [disabled]=\"!canUndo()\" aria-label=\"Annulla\" (click)=\"undo()\">\u21B6</button>\r\n <button type=\"button\" class=\"fb-btn\" [disabled]=\"!canRedo()\" aria-label=\"Ripeti\" (click)=\"redo()\">\u21B7</button>\r\n <button type=\"button\" class=\"fb-btn\" title=\"Ricalcola le posizioni\" (click)=\"autoLayout()\">Riordina</button>\r\n\r\n <!--\r\n Copia e incolla stanno **anche** qui e non solo sui tasti: una scorciatoia che nessuno\r\n annuncia non esiste. Il titolo la dice, cos\u00EC si impara usandola una volta.\r\n L\u2019incolla e\u2019 acceso anche con la selezione vuota: cio\u2019 che si incolla sta negli appunti,\r\n e puo\u2019 venire da un altro flow.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"!canCopy()\"\r\n title=\"Copia gli elementi selezionati, con le risorse che usano (Ctrl+C)\"\r\n aria-label=\"Copia gli elementi selezionati\"\r\n (click)=\"copySelection()\"\r\n >\r\n \u29C9\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"!canPaste() || !isEditable()\"\r\n title=\"Incolla gli elementi copiati, anche da un altro flow (Ctrl+V)\"\r\n aria-label=\"Incolla gli elementi copiati\"\r\n (click)=\"startPaste()\"\r\n >\r\n \u2398\r\n </button>\r\n @if (isDialogMode()) {\r\n <!-- Il doppio click sul node fa la stessa cosa, ma non si vede: questo comando s\u00EC. -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"!selectedName()\"\r\n title=\"Apri il dettaglio dell\u2019elemento selezionato\"\r\n (click)=\"openSelectedElement()\"\r\n >\r\n Dettaglio\r\n </button>\r\n }\r\n <button type=\"button\" class=\"fb-btn\" [disabled]=\"isBusy()\" (click)=\"validateNow()\">Valida</button>\r\n\r\n <!--\r\n Esegui e Debug non eseguono niente qui dentro: raccolgono i valori di ingresso e li\r\n consegnano all\u2019applicazione ospite. Si esegue la versione **salvata**, quindi su un flow\r\n mai scritto sono spenti e il titolo dice perche\u2019.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"!canRun()\"\r\n [title]=\"canRun() ? 'Esegui il flow salvato' : 'Salva il flow prima di eseguirlo'\"\r\n (click)=\"openRun('run')\"\r\n >\r\n Esegui\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"!canRun()\"\r\n [title]=\"canRun() ? 'Esegui il flow salvato in debug' : 'Salva il flow prima di eseguirlo'\"\r\n (click)=\"openRun('debug')\"\r\n >\r\n Debug\r\n </button>\r\n\r\n @switch (primaryCommand()) {\r\n @case ('newVersion') {\r\n <!-- Su una versione non modificabile il comando primario e' \"Nuova versione\" (\u00A78.1). -->\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" [disabled]=\"isBusy()\" (click)=\"createNewVersion()\">\r\n Nuova versione\r\n </button>\r\n }\r\n @case ('create') {\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" [disabled]=\"isBusy()\" (click)=\"save()\">\r\n Crea\r\n </button>\r\n }\r\n @default {\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" [disabled]=\"isBusy()\" (click)=\"save()\">\r\n Salva\r\n </button>\r\n }\r\n }\r\n\r\n @if (canDuplicate()) {\r\n <!-- \u00A76.2 \u00ABDuplica\u00BB: il flow sotto un altro nome, alla versione 1. -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"isBusy()\"\r\n title=\"Salva una copia con un altro nome\"\r\n (click)=\"startCopy()\"\r\n >\r\n Duplica\u2026\r\n </button>\r\n }\r\n\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"isBusy() || !canActivate()\"\r\n [title]=\"\r\n canActivate()\r\n ? 'Attiva questa versione'\r\n : 'L\u2019attivazione esige zero errori: correggili nel pannello dei problemi'\r\n \"\r\n (click)=\"activate()\"\r\n >\r\n Attiva\r\n </button>\r\n </div>\r\n </header>\r\n\r\n @if (conflict()) {\r\n <!-- \u00A79.3: qualcun altro ha salvato. Due strade, entrambe offerte. -->\r\n <div class=\"fb-banner fb-banner--warn\" role=\"alert\">\r\n <span>\r\n {{ conflict()?.message }}\r\n @if (conflict()?.conflictingAuthor) {\r\n Ha salvato {{ conflict()?.conflictingAuthor }}.\r\n }\r\n </span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--icon\" (click)=\"reloadAfterConflict()\">Ricarica</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--icon\" (click)=\"saveAsNewVersionAfterConflict()\">\r\n Salva come nuova versione\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" (click)=\"dismissConflict()\">\u00D7</button>\r\n </div>\r\n }\r\n\r\n @if (isCopyOpen()) {\r\n <!--\r\n Il nome si chiede prima di scrivere: e\u2019 l\u2019unico dato che il backend non puo\u2019 inventare, e\r\n un doppione lo rifiuta con AlreadyExists (\u00A76.2). Invio conferma, Esc annulla.\r\n -->\r\n <div class=\"fb-banner fb-copy\" role=\"group\" aria-label=\"Duplica il flow\">\r\n <label class=\"fb-copy__field\">\r\n <span class=\"fb-copy__caption\">Nome tecnico della copia</span>\r\n <input\r\n #copyNameInput\r\n class=\"fb-copy__input\"\r\n [value]=\"copyName()\"\r\n placeholder=\"NomeTecnico_Copia\"\r\n (input)=\"setCopyName($any($event.target).value)\"\r\n (keydown.enter)=\"confirmCopy()\"\r\n (keydown.escape)=\"cancelCopy()\"\r\n />\r\n </label>\r\n <label class=\"fb-copy__field\">\r\n <span class=\"fb-copy__caption\">Nome visibile</span>\r\n <input\r\n class=\"fb-copy__input\"\r\n [value]=\"copyLabel()\"\r\n placeholder=\"(facoltativo)\"\r\n (input)=\"setCopyLabel($any($event.target).value)\"\r\n (keydown.enter)=\"confirmCopy()\"\r\n (keydown.escape)=\"cancelCopy()\"\r\n />\r\n </label>\r\n <span class=\"fb-copy__hint\" [class.fb-copy__hint--error]=\"!!copyNameProblem()\">\r\n {{ copyNameProblem() || copyHint() }}\r\n </span>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--primary fb-btn--icon\"\r\n [disabled]=\"isBusy() || !!copyNameProblem()\"\r\n (click)=\"confirmCopy()\"\r\n >\r\n Duplica\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" (click)=\"cancelCopy()\">Annulla</button>\r\n </div>\r\n }\r\n\r\n @if (notice()) {\r\n <div\r\n class=\"fb-banner\"\r\n [class.fb-banner--error]=\"notice()?.kind === 'error'\"\r\n role=\"status\"\r\n >\r\n <span>{{ notice()?.message }}</span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" aria-label=\"Chiudi\" (click)=\"dismissNotice()\">\r\n \u00D7\r\n </button>\r\n </div>\r\n }\r\n\r\n @if (hasScreensInAutoLaunched()) {\r\n <div class=\"fb-banner fb-banner--error\">\r\n Un flow AutoLaunched che contiene screen e\u2019 un errore di validazione: non c\u2019e\u2019 nessuno a cui mostrarli.\r\n </div>\r\n }\r\n @if (isOrchestrationWithoutStages()) {\r\n <div class=\"fb-banner fb-banner--warn\">\r\n Un flow Orchestration senza stage di orchestrazione viene segnalato dalla validazione\r\n (ORCHESTRATION_WITHOUT_STAGES).\r\n </div>\r\n }\r\n @if (hasNoScreensInScreenFlow()) {\r\n <div class=\"fb-banner fb-banner--warn\">\r\n Un flow di tipo Screen senza nessuno screen viene segnalato dalla validazione.\r\n </div>\r\n }\r\n @if (!isEditable()) {\r\n <div class=\"fb-banner\">\r\n Questa versione e\u2019 in sola lettura: per modificarla creane una nuova.\r\n </div>\r\n }\r\n\r\n <div class=\"fb-main\">\r\n <aside class=\"fb-main__palette\">\r\n <fb-element-palette [processType]=\"document().processType\" (elementPicked)=\"onElementPicked($event)\" />\r\n </aside>\r\n\r\n <div class=\"fb-main__center\">\r\n <fb-flow-canvas\r\n class=\"fb-main__canvas\"\r\n [selectedName]=\"selectedName()\"\r\n [selectedNames]=\"selectedNames()\"\r\n [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 <!--\r\n Il pannello dell\u2019ultima esecuzione. \u00C8 l\u2019unico posto in cui si vede la traccia di un\r\n flow **senza schermate**: l\u00EC il runtime non apre nessuna interfaccia, e senza questo\r\n tab non ci sarebbe niente da guardare.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-side__tab\"\r\n [class.fb-side__tab--active]=\"sidePanel() === 'debug'\"\r\n (click)=\"setPanel('debug')\"\r\n >\r\n Debug\r\n @if (isStartingRun()) {\r\n <span class=\"fb-side__tab-dot\" aria-label=\"esecuzione in corso\"></span>\r\n }\r\n </button>\r\n </nav>\r\n\r\n <div class=\"fb-side__content\">\r\n @switch (sidePanel()) {\r\n @case ('inspector') {\r\n <fb-element-inspector\r\n [selectedName]=\"selectedName()\"\r\n (removeRequested)=\"onRemoveNode($event)\"\r\n (duplicateRequested)=\"onDuplicateNode($event)\"\r\n (renamed)=\"onNodeRenamed($event)\"\r\n (closed)=\"setPanel('resources')\"\r\n />\r\n }\r\n @case ('resources') {\r\n <fb-resource-panel (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: la traccia si sta leggendo. -->\r\n <fb-debug-panel\r\n [outcome]=\"runOutcome()\"\r\n [wasDebug]=\"wasRunInDebug()\"\r\n [isRunning]=\"isStartingRun()\"\r\n (elementFocused)=\"highlightElement($event)\"\r\n (cleared)=\"clearRunOutcome()\"\r\n (closed)=\"setPanel('inspector')\"\r\n />\r\n }\r\n }\r\n </div>\r\n\r\n <footer class=\"fb-side__footer\">\r\n <label class=\"fb-btn fb-btn--icon\">\r\n Importa JSON\r\n <input type=\"file\" accept=\"application/json,.json\" hidden (change)=\"onFileSelected($event)\" />\r\n </label>\r\n </footer>\r\n </aside>\r\n </div>\r\n\r\n @if (pastePreview(); as preview) {\r\n <!--\r\n L\u2019incolla passa da una conferma perche\u2019 non e\u2019 mai una copia identica: nomi gi\u00E0 presi,\r\n risorse che qui non esistono, riferimenti che restano orfani. Il piano e\u2019 gi\u00E0 calcolato,\r\n la finestra lo mostra e basta.\r\n -->\r\n <fb-paste-dialog\r\n [payload]=\"preview.payload\"\r\n [plan]=\"preview.plan\"\r\n (confirmed)=\"confirmPaste($event)\"\r\n (cancelled)=\"cancelPaste()\"\r\n />\r\n }\r\n\r\n @if (runMode(); as mode) {\r\n <!--\r\n La finestra degli ingressi. Non avvia niente: raccoglie i valori e li passa all\u2019ospite,\r\n che e\u2019 l\u2019unico a sapere dove gira il motore.\r\n -->\r\n <fb-run-dialog\r\n [mode]=\"mode\"\r\n [flowName]=\"session.flowName()\"\r\n [version]=\"session.version()\"\r\n [isDirty]=\"isDirty()\"\r\n [isBusy]=\"isStartingRun()\"\r\n (confirmed)=\"confirmRun($event)\"\r\n (cancelled)=\"closeRun()\"\r\n />\r\n }\r\n\r\n @if (isDialogMode() && isDialogOpen() && selectedName()) {\r\n <!-- La dialog sta dentro il builder, non nel body: la libreria e\u2019 innestabile. -->\r\n <fb-element-dialog\r\n [selectedName]=\"selectedName()\"\r\n (closed)=\"closeDialog()\"\r\n (removeRequested)=\"onRemoveNode($event)\"\r\n (duplicateRequested)=\"onDuplicateNode($event)\"\r\n (renamed)=\"onNodeRenamed($event)\"\r\n />\r\n }\r\n</div>\r\n", styles: [":host{display:block;width:100%;height:100%;min-height:0;font:inherit;color:var(--fb-text, #1d2939)}.fb-builder{position:relative;display:flex;flex-direction:column;width:100%;height:100%;min-height:0;background:var(--fb-surface, #fff)}.fb-top{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:9px 14px;border-bottom:1px solid var(--fb-border, #e2e5eb);background:var(--fb-surface, #fff)}.fb-top__identity{min-width:0}.fb-top__label{width:100%;max-width:420px;padding:2px 4px;border:1px solid transparent;border-radius:4px;background:transparent;color:var(--fb-text, #1d2939);font:inherit;font-size:15px;font-weight:600}.fb-top__label:hover:not(:disabled),.fb-top__label:focus-visible{border-color:var(--fb-border, #d6dae1);background:var(--fb-surface, #fff)}.fb-top__meta{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-top:2px}.fb-top__name{width:180px;padding:1px 4px;border:1px solid transparent;border-radius:4px;background:transparent;color:var(--fb-text-muted, #667085);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px}.fb-top__name:hover:not(:disabled),.fb-top__name:focus-visible{border-color:var(--fb-border, #d6dae1)}.fb-top__process{padding:1px 4px;border:1px solid var(--fb-border-subtle, #e6e9ee);border-radius:4px;background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:11px}.fb-top__status,.fb-top__version,.fb-top__dirty{padding:2px 7px;border-radius:999px;background:var(--fb-surface-sunken, #eef0f4);color:var(--fb-text-muted, #6b7086);font-size:10px;font-weight:600}.fb-top__version{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.fb-top__dirty{background:color-mix(in srgb,var(--fb-warning, #b7791f) 16%,transparent);color:var(--fb-warning, #b7791f)}.fb-top__actions{display:flex;flex-wrap:wrap;gap:4px}.fb-banner{display:flex;flex-wrap:wrap;align-items:center;gap:8px;padding:6px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee);background:color-mix(in srgb,var(--fb-accent, #2f6feb) 7%,transparent);font-size:11px}.fb-banner--warn{background:color-mix(in srgb,var(--fb-warning, #b7791f) 12%,transparent);color:var(--fb-warning, #b7791f)}.fb-banner--error{background:color-mix(in srgb,var(--fb-error, #c9372c) 10%,transparent);color:var(--fb-error, #c9372c)}.fb-banner>span{flex:1;min-width:200px}.fb-copy__field{display:flex;align-items:center;gap:6px}.fb-copy__caption{color:var(--fb-text-muted, #667085);white-space:nowrap}.fb-copy__input{width:180px;padding:2px 5px;border:1px solid var(--fb-border, #d6dae1);border-radius:4px;background:var(--fb-surface, #fff);color:var(--fb-text, #1d2939);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px}.fb-copy__hint{flex:1;min-width:180px;color:var(--fb-text-muted, #667085)}.fb-copy__hint--error{color:var(--fb-error, #c9372c)}.fb-main{display:flex;flex:1;min-height:0}.fb-main__palette{flex:0 0 190px;min-width:0}.fb-main__center{display:flex;flex:1;flex-direction:column;min-width:0;min-height:0}.fb-main__canvas{flex:1;min-height:0}.fb-main__problems{flex:0 0 auto;height:220px}.fb-main__problems-toggle{display:flex;align-items:center;gap:6px;padding:4px 12px;border:0;border-top:1px solid var(--fb-border, #d6dae1);background:var(--fb-surface-alt, #f8f9fb);color:var(--fb-text-muted, #667085);font:inherit;font-size:11px;cursor:pointer}.fb-main__count{padding:0 5px;border-radius:8px;background:var(--fb-border, #d6dae1);font-size:9px;font-weight:700}.fb-main__count--error{background:color-mix(in srgb,var(--fb-error, #c9372c) 16%,transparent);color:var(--fb-error, #c9372c)}.fb-main__count--warn{background:color-mix(in srgb,var(--fb-warning, #b7791f) 16%,transparent);color:var(--fb-warning, #b7791f)}.fb-main__side{display:flex;flex-direction:column;flex:0 0 340px;min-width:0;min-height:0;border-left:1px solid var(--fb-border, #d6dae1);background:var(--fb-surface, #fff)}.fb-side__tabs{display:flex;gap:2px;margin:8px 10px;padding:3px;border-radius:var(--fb-radius, 10px);background:var(--fb-surface-sunken, #eef0f4)}.fb-side__tab{flex:1;padding:5px 8px;border:0;border-radius:var(--fb-radius-xs, 6px);background:transparent;color:var(--fb-text-muted, #6b7086);font:inherit;font-size:11px;cursor:pointer;transition:background .12s ease,color .12s ease}.fb-side__tab-dot{display:inline-block;width:6px;height:6px;margin-left:4px;border-radius:50%;background:var(--fb-accent, #2f6feb);vertical-align:middle}.fb-side__tab:hover:not(.fb-side__tab--active){color:var(--fb-text, #1a1c23)}.fb-side__tab--active{background:var(--fb-surface, #fff);box-shadow:var(--fb-shadow-sm, 0 1px 2px rgb(16 24 40 / 6%));color:var(--fb-text, #1a1c23);font-weight:600}.fb-side__content{flex:1;min-height:0;border-top:1px solid var(--fb-border-subtle, #eef0f4)}.fb-side__content>*{height:100%}.fb-side__footer{display:flex;gap:6px;padding:6px 8px;border-top:1px solid var(--fb-border-subtle, #e6e9ee)}@media(max-width:1200px){.fb-main__palette{flex-basis:150px}.fb-main__side{flex-basis:290px}}\n"], dependencies: [{ kind: "component", type: DebugPanelComponent, selector: "fb-debug-panel", inputs: ["outcome", "wasDebug", "isRunning"], outputs: ["closed", "elementFocused", "cleared"] }, { kind: "component", type: ElementDialogComponent, selector: "fb-element-dialog", inputs: ["selectedName"], outputs: ["closed", "removeRequested", "duplicateRequested", "renamed"] }, { kind: "component", type: ElementInspectorComponent, selector: "fb-element-inspector", inputs: ["selectedName", "showHeader"], outputs: ["closed", "removeRequested", "duplicateRequested", "renamed"] }, { kind: "component", type: ElementPaletteComponent, selector: "fb-element-palette", inputs: ["processType"], outputs: ["elementPicked"] }, { kind: "component", type: FlowCanvasComponent, selector: "fb-flow-canvas", inputs: ["selectedName", "selectedNames", "outline", "isEditable"], outputs: ["selectionChange", "nodeOpened", "nodeRemoveRequested", "nodeDuplicateRequested", "elementDropped"] }, { kind: "component", type: PasteDialogComponent, selector: "fb-paste-dialog", inputs: ["payload", "plan"], outputs: ["confirmed", "cancelled"] }, { kind: "component", type: ProblemsPanelComponent, selector: "fb-problems-panel", outputs: ["elementFocused", "closed"] }, { kind: "component", type: ResourcePanelComponent, selector: "fb-resource-panel", outputs: ["closed"] }, { kind: "component", type: RunDialogComponent, selector: "fb-run-dialog", inputs: ["mode", "flowName", "version", "isDirty", "isBusy"], outputs: ["confirmed", "cancelled"] }, { kind: "component", type: VersionPanelComponent, selector: "fb-version-panel", outputs: ["closed", "versionOpened", "notice"] }, { kind: "directive", type: SelectValueDirective, selector: "select[fbValue]", inputs: ["fbValue"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
13076
13846
  }
13077
13847
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImport: i0, type: FlowBuilderComponent, decorators: [{
13078
13848
  type: Component,
13079
- args: [{ selector: 'fb-flow-builder', standalone: true, imports: [DebugPanelComponent,
13849
+ args: [{ selector: 'fb-flow-builder', standalone: true, imports: [
13850
+ DebugPanelComponent,
13080
13851
  ElementDialogComponent,
13081
13852
  ElementInspectorComponent,
13082
13853
  ElementPaletteComponent,
13083
13854
  FlowCanvasComponent,
13855
+ PasteDialogComponent,
13084
13856
  ProblemsPanelComponent,
13085
13857
  ResourcePanelComponent,
13086
- VersionPanelComponent, SelectValueDirective], providers: [
13858
+ RunDialogComponent,
13859
+ VersionPanelComponent, SelectValueDirective
13860
+ ], providers: [
13087
13861
  FlowDocumentStore,
13088
13862
  FlowDictionaryStore,
13089
13863
  FlowCatalogStore,
@@ -13091,12 +13865,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImpo
13091
13865
  FormulaValidationService,
13092
13866
  FlowEditorSession,
13093
13867
  FlowLayoutService,
13094
- ], 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 @if (canDuplicate()) {\r\n <!-- \u00A76.2 \u00ABDuplica\u00BB: il flow sotto un altro nome, alla versione 1. -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"isBusy()\"\r\n title=\"Salva una copia con un altro nome\"\r\n (click)=\"startCopy()\"\r\n >\r\n Duplica\u2026\r\n </button>\r\n }\r\n\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"isBusy() || !canActivate()\"\r\n [title]=\"\r\n canActivate()\r\n ? 'Attiva questa versione'\r\n : 'L\u2019attivazione esige zero errori: correggili nel pannello dei problemi'\r\n \"\r\n (click)=\"activate()\"\r\n >\r\n Attiva\r\n </button>\r\n </div>\r\n </header>\r\n\r\n @if (conflict()) {\r\n <!-- \u00A79.3: qualcun altro ha salvato. Due strade, entrambe offerte. -->\r\n <div class=\"fb-banner fb-banner--warn\" role=\"alert\">\r\n <span>\r\n {{ conflict()?.message }}\r\n @if (conflict()?.conflictingAuthor) {\r\n Ha salvato {{ conflict()?.conflictingAuthor }}.\r\n }\r\n </span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--icon\" (click)=\"reloadAfterConflict()\">Ricarica</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--icon\" (click)=\"saveAsNewVersionAfterConflict()\">\r\n Salva come nuova versione\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" (click)=\"dismissConflict()\">\u00D7</button>\r\n </div>\r\n }\r\n\r\n @if (isCopyOpen()) {\r\n <!--\r\n Il nome si chiede prima di scrivere: e\u2019 l\u2019unico dato che il backend non puo\u2019 inventare, e\r\n un doppione lo rifiuta con AlreadyExists (\u00A76.2). Invio conferma, Esc annulla.\r\n -->\r\n <div class=\"fb-banner fb-copy\" role=\"group\" aria-label=\"Duplica il flow\">\r\n <label class=\"fb-copy__field\">\r\n <span class=\"fb-copy__caption\">Nome tecnico della copia</span>\r\n <input\r\n #copyNameInput\r\n class=\"fb-copy__input\"\r\n [value]=\"copyName()\"\r\n placeholder=\"NomeTecnico_Copia\"\r\n (input)=\"setCopyName($any($event.target).value)\"\r\n (keydown.enter)=\"confirmCopy()\"\r\n (keydown.escape)=\"cancelCopy()\"\r\n />\r\n </label>\r\n <label class=\"fb-copy__field\">\r\n <span class=\"fb-copy__caption\">Nome visibile</span>\r\n <input\r\n class=\"fb-copy__input\"\r\n [value]=\"copyLabel()\"\r\n placeholder=\"(facoltativo)\"\r\n (input)=\"setCopyLabel($any($event.target).value)\"\r\n (keydown.enter)=\"confirmCopy()\"\r\n (keydown.escape)=\"cancelCopy()\"\r\n />\r\n </label>\r\n <span class=\"fb-copy__hint\" [class.fb-copy__hint--error]=\"!!copyNameProblem()\">\r\n {{ copyNameProblem() || copyHint() }}\r\n </span>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--primary fb-btn--icon\"\r\n [disabled]=\"isBusy() || !!copyNameProblem()\"\r\n (click)=\"confirmCopy()\"\r\n >\r\n Duplica\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" (click)=\"cancelCopy()\">Annulla</button>\r\n </div>\r\n }\r\n\r\n @if (notice()) {\r\n <div\r\n class=\"fb-banner\"\r\n [class.fb-banner--error]=\"notice()?.kind === 'error'\"\r\n role=\"status\"\r\n >\r\n <span>{{ notice()?.message }}</span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" aria-label=\"Chiudi\" (click)=\"dismissNotice()\">\r\n \u00D7\r\n </button>\r\n </div>\r\n }\r\n\r\n @if (hasScreensInAutoLaunched()) {\r\n <div class=\"fb-banner fb-banner--error\">\r\n Un flow AutoLaunched che contiene screen e\u2019 un errore di validazione: non c\u2019e\u2019 nessuno a cui mostrarli.\r\n </div>\r\n }\r\n @if (isOrchestrationWithoutStages()) {\r\n <div class=\"fb-banner fb-banner--warn\">\r\n Un flow Orchestration senza stage di orchestrazione viene segnalato dalla validazione\r\n (ORCHESTRATION_WITHOUT_STAGES).\r\n </div>\r\n }\r\n @if (hasNoScreensInScreenFlow()) {\r\n <div class=\"fb-banner fb-banner--warn\">\r\n Un flow di tipo Screen senza nessuno screen viene segnalato dalla validazione.\r\n </div>\r\n }\r\n @if (!isEditable()) {\r\n <div class=\"fb-banner\">\r\n Questa versione e\u2019 in sola lettura: per modificarla creane una nuova.\r\n </div>\r\n }\r\n\r\n <div class=\"fb-main\">\r\n <aside class=\"fb-main__palette\">\r\n <fb-element-palette [processType]=\"document().processType\" (elementPicked)=\"onElementPicked($event)\" />\r\n </aside>\r\n\r\n <div class=\"fb-main__center\">\r\n <fb-flow-canvas\r\n class=\"fb-main__canvas\"\r\n [selectedName]=\"selectedName()\"\r\n [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 (renamed)=\"onNodeRenamed($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 (renamed)=\"onNodeRenamed($event)\"\r\n />\r\n }\r\n</div>\r\n", styles: [":host{display:block;width:100%;height:100%;min-height:0;font:inherit;color:var(--fb-text, #1d2939)}.fb-builder{position:relative;display:flex;flex-direction:column;width:100%;height:100%;min-height:0;background:var(--fb-surface, #fff)}.fb-top{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:9px 14px;border-bottom:1px solid var(--fb-border, #e2e5eb);background:var(--fb-surface, #fff)}.fb-top__identity{min-width:0}.fb-top__label{width:100%;max-width:420px;padding:2px 4px;border:1px solid transparent;border-radius:4px;background:transparent;color:var(--fb-text, #1d2939);font:inherit;font-size:15px;font-weight:600}.fb-top__label:hover:not(:disabled),.fb-top__label:focus-visible{border-color:var(--fb-border, #d6dae1);background:var(--fb-surface, #fff)}.fb-top__meta{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-top:2px}.fb-top__name{width:180px;padding:1px 4px;border:1px solid transparent;border-radius:4px;background:transparent;color:var(--fb-text-muted, #667085);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px}.fb-top__name:hover:not(:disabled),.fb-top__name:focus-visible{border-color:var(--fb-border, #d6dae1)}.fb-top__process{padding:1px 4px;border:1px solid var(--fb-border-subtle, #e6e9ee);border-radius:4px;background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:11px}.fb-top__status,.fb-top__version,.fb-top__dirty{padding:2px 7px;border-radius:999px;background:var(--fb-surface-sunken, #eef0f4);color:var(--fb-text-muted, #6b7086);font-size:10px;font-weight:600}.fb-top__version{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.fb-top__dirty{background:color-mix(in srgb,var(--fb-warning, #b7791f) 16%,transparent);color:var(--fb-warning, #b7791f)}.fb-top__actions{display:flex;flex-wrap:wrap;gap:4px}.fb-banner{display:flex;flex-wrap:wrap;align-items:center;gap:8px;padding:6px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee);background:color-mix(in srgb,var(--fb-accent, #2f6feb) 7%,transparent);font-size:11px}.fb-banner--warn{background:color-mix(in srgb,var(--fb-warning, #b7791f) 12%,transparent);color:var(--fb-warning, #b7791f)}.fb-banner--error{background:color-mix(in srgb,var(--fb-error, #c9372c) 10%,transparent);color:var(--fb-error, #c9372c)}.fb-banner>span{flex:1;min-width:200px}.fb-copy__field{display:flex;align-items:center;gap:6px}.fb-copy__caption{color:var(--fb-text-muted, #667085);white-space:nowrap}.fb-copy__input{width:180px;padding:2px 5px;border:1px solid var(--fb-border, #d6dae1);border-radius:4px;background:var(--fb-surface, #fff);color:var(--fb-text, #1d2939);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px}.fb-copy__hint{flex:1;min-width:180px;color:var(--fb-text-muted, #667085)}.fb-copy__hint--error{color:var(--fb-error, #c9372c)}.fb-main{display:flex;flex:1;min-height:0}.fb-main__palette{flex:0 0 190px;min-width:0}.fb-main__center{display:flex;flex:1;flex-direction:column;min-width:0;min-height:0}.fb-main__canvas{flex:1;min-height:0}.fb-main__problems{flex:0 0 auto;height:220px}.fb-main__problems-toggle{display:flex;align-items:center;gap:6px;padding:4px 12px;border:0;border-top:1px solid var(--fb-border, #d6dae1);background:var(--fb-surface-alt, #f8f9fb);color:var(--fb-text-muted, #667085);font:inherit;font-size:11px;cursor:pointer}.fb-main__count{padding:0 5px;border-radius:8px;background:var(--fb-border, #d6dae1);font-size:9px;font-weight:700}.fb-main__count--error{background:color-mix(in srgb,var(--fb-error, #c9372c) 16%,transparent);color:var(--fb-error, #c9372c)}.fb-main__count--warn{background:color-mix(in srgb,var(--fb-warning, #b7791f) 16%,transparent);color:var(--fb-warning, #b7791f)}.fb-main__side{display:flex;flex-direction:column;flex:0 0 340px;min-width:0;min-height:0;border-left:1px solid var(--fb-border, #d6dae1);background:var(--fb-surface, #fff)}.fb-side__tabs{display:flex;gap:2px;margin:8px 10px;padding:3px;border-radius:var(--fb-radius, 10px);background:var(--fb-surface-sunken, #eef0f4)}.fb-side__tab{flex:1;padding:5px 8px;border:0;border-radius:var(--fb-radius-xs, 6px);background:transparent;color:var(--fb-text-muted, #6b7086);font:inherit;font-size:11px;cursor:pointer;transition:background .12s ease,color .12s ease}.fb-side__tab: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"] }]
13868
+ ], changeDetection: ChangeDetectionStrategy.OnPush, host: { '(keydown)': 'onKeyDown($event)' }, template: "<div class=\"fb-builder\" [attr.data-fb-theme]=\"null\">\r\n <header class=\"fb-top\">\r\n <div class=\"fb-top__identity\">\r\n <input\r\n class=\"fb-top__label\"\r\n [value]=\"document().label || ''\"\r\n placeholder=\"Nome del flow\"\r\n aria-label=\"Nome del flow\"\r\n [disabled]=\"!isEditable()\"\r\n (input)=\"setLabel($any($event.target).value)\"\r\n />\r\n <div class=\"fb-top__meta\">\r\n <input\r\n class=\"fb-top__name\"\r\n [value]=\"document().fullName || ''\"\r\n placeholder=\"NomeTecnico\"\r\n aria-label=\"Nome tecnico del flow\"\r\n [disabled]=\"!isEditable()\"\r\n (input)=\"setFullName($any($event.target).value)\"\r\n />\r\n <select\r\n class=\"fb-top__process\"\r\n [fbValue]=\"document().processType || ''\"\r\n aria-label=\"Tipo di flow\"\r\n [disabled]=\"!isEditable()\"\r\n (change)=\"setProcessType($any($event.target).value)\"\r\n >\r\n <!--\r\n Il segnaposto esiste perche' un `<select>` senza opzione corrispondente non e'\r\n \u00ABvuoto\u00BB: e' a `selectedIndex = -1`, e mostra una casella bianca. Su un flow nuovo\r\n (`processType` non ancora scelto) e' questa la riga che si vede, disabilitata\r\n perche' non e' un valore valido da salvare.\r\n -->\r\n @if (!document().processType) {\r\n <option value=\"\" disabled>\u2014 tipo di flow \u2014</option>\r\n }\r\n @for (type of processTypes(); track type.value) {\r\n <option [value]=\"type.value\">{{ type.label }}</option>\r\n }\r\n <!--\r\n Il tipo del documento che il dizionario non conosce: senza questa opzione la select\r\n resta bianca su un flow che il backend ha salvato con un `processType` fuori\r\n catalogo (o quando `processTypes` manca dalla risposta dei dizionari), e sembra che\r\n l'editor non abbia ricaricato il flow. Va mostrato **senza** riscrivere il\r\n documento: il valore e' del backend, non nostro da correggere.\r\n -->\r\n @if (isProcessTypeOutOfCatalog()) {\r\n <option [value]=\"document().processType\">{{ document().processType }} (fuori catalogo)</option>\r\n }\r\n </select>\r\n <span class=\"fb-top__status\">{{ statusLabel() }}</span>\r\n @if (session.version() != null) {\r\n <span class=\"fb-top__version\">v{{ session.version() }}</span>\r\n }\r\n @if (isDirty()) {\r\n <span class=\"fb-top__dirty\" title=\"Ci sono modifiche non salvate\">modificato</span>\r\n }\r\n </div>\r\n </div>\r\n\r\n <div class=\"fb-top__actions\">\r\n <button type=\"button\" class=\"fb-btn\" [disabled]=\"!canUndo()\" aria-label=\"Annulla\" (click)=\"undo()\">\u21B6</button>\r\n <button type=\"button\" class=\"fb-btn\" [disabled]=\"!canRedo()\" aria-label=\"Ripeti\" (click)=\"redo()\">\u21B7</button>\r\n <button type=\"button\" class=\"fb-btn\" title=\"Ricalcola le posizioni\" (click)=\"autoLayout()\">Riordina</button>\r\n\r\n <!--\r\n Copia e incolla stanno **anche** qui e non solo sui tasti: una scorciatoia che nessuno\r\n annuncia non esiste. Il titolo la dice, cos\u00EC si impara usandola una volta.\r\n L\u2019incolla e\u2019 acceso anche con la selezione vuota: cio\u2019 che si incolla sta negli appunti,\r\n e puo\u2019 venire da un altro flow.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"!canCopy()\"\r\n title=\"Copia gli elementi selezionati, con le risorse che usano (Ctrl+C)\"\r\n aria-label=\"Copia gli elementi selezionati\"\r\n (click)=\"copySelection()\"\r\n >\r\n \u29C9\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"!canPaste() || !isEditable()\"\r\n title=\"Incolla gli elementi copiati, anche da un altro flow (Ctrl+V)\"\r\n aria-label=\"Incolla gli elementi copiati\"\r\n (click)=\"startPaste()\"\r\n >\r\n \u2398\r\n </button>\r\n @if (isDialogMode()) {\r\n <!-- Il doppio click sul node fa la stessa cosa, ma non si vede: questo comando s\u00EC. -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"!selectedName()\"\r\n title=\"Apri il dettaglio dell\u2019elemento selezionato\"\r\n (click)=\"openSelectedElement()\"\r\n >\r\n Dettaglio\r\n </button>\r\n }\r\n <button type=\"button\" class=\"fb-btn\" [disabled]=\"isBusy()\" (click)=\"validateNow()\">Valida</button>\r\n\r\n <!--\r\n Esegui e Debug non eseguono niente qui dentro: raccolgono i valori di ingresso e li\r\n consegnano all\u2019applicazione ospite. Si esegue la versione **salvata**, quindi su un flow\r\n mai scritto sono spenti e il titolo dice perche\u2019.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"!canRun()\"\r\n [title]=\"canRun() ? 'Esegui il flow salvato' : 'Salva il flow prima di eseguirlo'\"\r\n (click)=\"openRun('run')\"\r\n >\r\n Esegui\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"!canRun()\"\r\n [title]=\"canRun() ? 'Esegui il flow salvato in debug' : 'Salva il flow prima di eseguirlo'\"\r\n (click)=\"openRun('debug')\"\r\n >\r\n Debug\r\n </button>\r\n\r\n @switch (primaryCommand()) {\r\n @case ('newVersion') {\r\n <!-- Su una versione non modificabile il comando primario e' \"Nuova versione\" (\u00A78.1). -->\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" [disabled]=\"isBusy()\" (click)=\"createNewVersion()\">\r\n Nuova versione\r\n </button>\r\n }\r\n @case ('create') {\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" [disabled]=\"isBusy()\" (click)=\"save()\">\r\n Crea\r\n </button>\r\n }\r\n @default {\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" [disabled]=\"isBusy()\" (click)=\"save()\">\r\n Salva\r\n </button>\r\n }\r\n }\r\n\r\n @if (canDuplicate()) {\r\n <!-- \u00A76.2 \u00ABDuplica\u00BB: il flow sotto un altro nome, alla versione 1. -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"isBusy()\"\r\n title=\"Salva una copia con un altro nome\"\r\n (click)=\"startCopy()\"\r\n >\r\n Duplica\u2026\r\n </button>\r\n }\r\n\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"isBusy() || !canActivate()\"\r\n [title]=\"\r\n canActivate()\r\n ? 'Attiva questa versione'\r\n : 'L\u2019attivazione esige zero errori: correggili nel pannello dei problemi'\r\n \"\r\n (click)=\"activate()\"\r\n >\r\n Attiva\r\n </button>\r\n </div>\r\n </header>\r\n\r\n @if (conflict()) {\r\n <!-- \u00A79.3: qualcun altro ha salvato. Due strade, entrambe offerte. -->\r\n <div class=\"fb-banner fb-banner--warn\" role=\"alert\">\r\n <span>\r\n {{ conflict()?.message }}\r\n @if (conflict()?.conflictingAuthor) {\r\n Ha salvato {{ conflict()?.conflictingAuthor }}.\r\n }\r\n </span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--icon\" (click)=\"reloadAfterConflict()\">Ricarica</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--icon\" (click)=\"saveAsNewVersionAfterConflict()\">\r\n Salva come nuova versione\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" (click)=\"dismissConflict()\">\u00D7</button>\r\n </div>\r\n }\r\n\r\n @if (isCopyOpen()) {\r\n <!--\r\n Il nome si chiede prima di scrivere: e\u2019 l\u2019unico dato che il backend non puo\u2019 inventare, e\r\n un doppione lo rifiuta con AlreadyExists (\u00A76.2). Invio conferma, Esc annulla.\r\n -->\r\n <div class=\"fb-banner fb-copy\" role=\"group\" aria-label=\"Duplica il flow\">\r\n <label class=\"fb-copy__field\">\r\n <span class=\"fb-copy__caption\">Nome tecnico della copia</span>\r\n <input\r\n #copyNameInput\r\n class=\"fb-copy__input\"\r\n [value]=\"copyName()\"\r\n placeholder=\"NomeTecnico_Copia\"\r\n (input)=\"setCopyName($any($event.target).value)\"\r\n (keydown.enter)=\"confirmCopy()\"\r\n (keydown.escape)=\"cancelCopy()\"\r\n />\r\n </label>\r\n <label class=\"fb-copy__field\">\r\n <span class=\"fb-copy__caption\">Nome visibile</span>\r\n <input\r\n class=\"fb-copy__input\"\r\n [value]=\"copyLabel()\"\r\n placeholder=\"(facoltativo)\"\r\n (input)=\"setCopyLabel($any($event.target).value)\"\r\n (keydown.enter)=\"confirmCopy()\"\r\n (keydown.escape)=\"cancelCopy()\"\r\n />\r\n </label>\r\n <span class=\"fb-copy__hint\" [class.fb-copy__hint--error]=\"!!copyNameProblem()\">\r\n {{ copyNameProblem() || copyHint() }}\r\n </span>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--primary fb-btn--icon\"\r\n [disabled]=\"isBusy() || !!copyNameProblem()\"\r\n (click)=\"confirmCopy()\"\r\n >\r\n Duplica\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" (click)=\"cancelCopy()\">Annulla</button>\r\n </div>\r\n }\r\n\r\n @if (notice()) {\r\n <div\r\n class=\"fb-banner\"\r\n [class.fb-banner--error]=\"notice()?.kind === 'error'\"\r\n role=\"status\"\r\n >\r\n <span>{{ notice()?.message }}</span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" aria-label=\"Chiudi\" (click)=\"dismissNotice()\">\r\n \u00D7\r\n </button>\r\n </div>\r\n }\r\n\r\n @if (hasScreensInAutoLaunched()) {\r\n <div class=\"fb-banner fb-banner--error\">\r\n Un flow AutoLaunched che contiene screen e\u2019 un errore di validazione: non c\u2019e\u2019 nessuno a cui mostrarli.\r\n </div>\r\n }\r\n @if (isOrchestrationWithoutStages()) {\r\n <div class=\"fb-banner fb-banner--warn\">\r\n Un flow Orchestration senza stage di orchestrazione viene segnalato dalla validazione\r\n (ORCHESTRATION_WITHOUT_STAGES).\r\n </div>\r\n }\r\n @if (hasNoScreensInScreenFlow()) {\r\n <div class=\"fb-banner fb-banner--warn\">\r\n Un flow di tipo Screen senza nessuno screen viene segnalato dalla validazione.\r\n </div>\r\n }\r\n @if (!isEditable()) {\r\n <div class=\"fb-banner\">\r\n Questa versione e\u2019 in sola lettura: per modificarla creane una nuova.\r\n </div>\r\n }\r\n\r\n <div class=\"fb-main\">\r\n <aside class=\"fb-main__palette\">\r\n <fb-element-palette [processType]=\"document().processType\" (elementPicked)=\"onElementPicked($event)\" />\r\n </aside>\r\n\r\n <div class=\"fb-main__center\">\r\n <fb-flow-canvas\r\n class=\"fb-main__canvas\"\r\n [selectedName]=\"selectedName()\"\r\n [selectedNames]=\"selectedNames()\"\r\n [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 <!--\r\n Il pannello dell\u2019ultima esecuzione. \u00C8 l\u2019unico posto in cui si vede la traccia di un\r\n flow **senza schermate**: l\u00EC il runtime non apre nessuna interfaccia, e senza questo\r\n tab non ci sarebbe niente da guardare.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-side__tab\"\r\n [class.fb-side__tab--active]=\"sidePanel() === 'debug'\"\r\n (click)=\"setPanel('debug')\"\r\n >\r\n Debug\r\n @if (isStartingRun()) {\r\n <span class=\"fb-side__tab-dot\" aria-label=\"esecuzione in corso\"></span>\r\n }\r\n </button>\r\n </nav>\r\n\r\n <div class=\"fb-side__content\">\r\n @switch (sidePanel()) {\r\n @case ('inspector') {\r\n <fb-element-inspector\r\n [selectedName]=\"selectedName()\"\r\n (removeRequested)=\"onRemoveNode($event)\"\r\n (duplicateRequested)=\"onDuplicateNode($event)\"\r\n (renamed)=\"onNodeRenamed($event)\"\r\n (closed)=\"setPanel('resources')\"\r\n />\r\n }\r\n @case ('resources') {\r\n <fb-resource-panel (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: la traccia si sta leggendo. -->\r\n <fb-debug-panel\r\n [outcome]=\"runOutcome()\"\r\n [wasDebug]=\"wasRunInDebug()\"\r\n [isRunning]=\"isStartingRun()\"\r\n (elementFocused)=\"highlightElement($event)\"\r\n (cleared)=\"clearRunOutcome()\"\r\n (closed)=\"setPanel('inspector')\"\r\n />\r\n }\r\n }\r\n </div>\r\n\r\n <footer class=\"fb-side__footer\">\r\n <label class=\"fb-btn fb-btn--icon\">\r\n Importa JSON\r\n <input type=\"file\" accept=\"application/json,.json\" hidden (change)=\"onFileSelected($event)\" />\r\n </label>\r\n </footer>\r\n </aside>\r\n </div>\r\n\r\n @if (pastePreview(); as preview) {\r\n <!--\r\n L\u2019incolla passa da una conferma perche\u2019 non e\u2019 mai una copia identica: nomi gi\u00E0 presi,\r\n risorse che qui non esistono, riferimenti che restano orfani. Il piano e\u2019 gi\u00E0 calcolato,\r\n la finestra lo mostra e basta.\r\n -->\r\n <fb-paste-dialog\r\n [payload]=\"preview.payload\"\r\n [plan]=\"preview.plan\"\r\n (confirmed)=\"confirmPaste($event)\"\r\n (cancelled)=\"cancelPaste()\"\r\n />\r\n }\r\n\r\n @if (runMode(); as mode) {\r\n <!--\r\n La finestra degli ingressi. Non avvia niente: raccoglie i valori e li passa all\u2019ospite,\r\n che e\u2019 l\u2019unico a sapere dove gira il motore.\r\n -->\r\n <fb-run-dialog\r\n [mode]=\"mode\"\r\n [flowName]=\"session.flowName()\"\r\n [version]=\"session.version()\"\r\n [isDirty]=\"isDirty()\"\r\n [isBusy]=\"isStartingRun()\"\r\n (confirmed)=\"confirmRun($event)\"\r\n (cancelled)=\"closeRun()\"\r\n />\r\n }\r\n\r\n @if (isDialogMode() && isDialogOpen() && selectedName()) {\r\n <!-- La dialog sta dentro il builder, non nel body: la libreria e\u2019 innestabile. -->\r\n <fb-element-dialog\r\n [selectedName]=\"selectedName()\"\r\n (closed)=\"closeDialog()\"\r\n (removeRequested)=\"onRemoveNode($event)\"\r\n (duplicateRequested)=\"onDuplicateNode($event)\"\r\n (renamed)=\"onNodeRenamed($event)\"\r\n />\r\n }\r\n</div>\r\n", styles: [":host{display:block;width:100%;height:100%;min-height:0;font:inherit;color:var(--fb-text, #1d2939)}.fb-builder{position:relative;display:flex;flex-direction:column;width:100%;height:100%;min-height:0;background:var(--fb-surface, #fff)}.fb-top{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:9px 14px;border-bottom:1px solid var(--fb-border, #e2e5eb);background:var(--fb-surface, #fff)}.fb-top__identity{min-width:0}.fb-top__label{width:100%;max-width:420px;padding:2px 4px;border:1px solid transparent;border-radius:4px;background:transparent;color:var(--fb-text, #1d2939);font:inherit;font-size:15px;font-weight:600}.fb-top__label:hover:not(:disabled),.fb-top__label:focus-visible{border-color:var(--fb-border, #d6dae1);background:var(--fb-surface, #fff)}.fb-top__meta{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-top:2px}.fb-top__name{width:180px;padding:1px 4px;border:1px solid transparent;border-radius:4px;background:transparent;color:var(--fb-text-muted, #667085);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px}.fb-top__name:hover:not(:disabled),.fb-top__name:focus-visible{border-color:var(--fb-border, #d6dae1)}.fb-top__process{padding:1px 4px;border:1px solid var(--fb-border-subtle, #e6e9ee);border-radius:4px;background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:11px}.fb-top__status,.fb-top__version,.fb-top__dirty{padding:2px 7px;border-radius:999px;background:var(--fb-surface-sunken, #eef0f4);color:var(--fb-text-muted, #6b7086);font-size:10px;font-weight:600}.fb-top__version{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.fb-top__dirty{background:color-mix(in srgb,var(--fb-warning, #b7791f) 16%,transparent);color:var(--fb-warning, #b7791f)}.fb-top__actions{display:flex;flex-wrap:wrap;gap:4px}.fb-banner{display:flex;flex-wrap:wrap;align-items:center;gap:8px;padding:6px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee);background:color-mix(in srgb,var(--fb-accent, #2f6feb) 7%,transparent);font-size:11px}.fb-banner--warn{background:color-mix(in srgb,var(--fb-warning, #b7791f) 12%,transparent);color:var(--fb-warning, #b7791f)}.fb-banner--error{background:color-mix(in srgb,var(--fb-error, #c9372c) 10%,transparent);color:var(--fb-error, #c9372c)}.fb-banner>span{flex:1;min-width:200px}.fb-copy__field{display:flex;align-items:center;gap:6px}.fb-copy__caption{color:var(--fb-text-muted, #667085);white-space:nowrap}.fb-copy__input{width:180px;padding:2px 5px;border:1px solid var(--fb-border, #d6dae1);border-radius:4px;background:var(--fb-surface, #fff);color:var(--fb-text, #1d2939);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px}.fb-copy__hint{flex:1;min-width:180px;color:var(--fb-text-muted, #667085)}.fb-copy__hint--error{color:var(--fb-error, #c9372c)}.fb-main{display:flex;flex:1;min-height:0}.fb-main__palette{flex:0 0 190px;min-width:0}.fb-main__center{display:flex;flex:1;flex-direction:column;min-width:0;min-height:0}.fb-main__canvas{flex:1;min-height:0}.fb-main__problems{flex:0 0 auto;height:220px}.fb-main__problems-toggle{display:flex;align-items:center;gap:6px;padding:4px 12px;border:0;border-top:1px solid var(--fb-border, #d6dae1);background:var(--fb-surface-alt, #f8f9fb);color:var(--fb-text-muted, #667085);font:inherit;font-size:11px;cursor:pointer}.fb-main__count{padding:0 5px;border-radius:8px;background:var(--fb-border, #d6dae1);font-size:9px;font-weight:700}.fb-main__count--error{background:color-mix(in srgb,var(--fb-error, #c9372c) 16%,transparent);color:var(--fb-error, #c9372c)}.fb-main__count--warn{background:color-mix(in srgb,var(--fb-warning, #b7791f) 16%,transparent);color:var(--fb-warning, #b7791f)}.fb-main__side{display:flex;flex-direction:column;flex:0 0 340px;min-width:0;min-height:0;border-left:1px solid var(--fb-border, #d6dae1);background:var(--fb-surface, #fff)}.fb-side__tabs{display:flex;gap:2px;margin:8px 10px;padding:3px;border-radius:var(--fb-radius, 10px);background:var(--fb-surface-sunken, #eef0f4)}.fb-side__tab{flex:1;padding:5px 8px;border:0;border-radius:var(--fb-radius-xs, 6px);background:transparent;color:var(--fb-text-muted, #6b7086);font:inherit;font-size:11px;cursor:pointer;transition:background .12s ease,color .12s ease}.fb-side__tab-dot{display:inline-block;width:6px;height:6px;margin-left:4px;border-radius:50%;background:var(--fb-accent, #2f6feb);vertical-align:middle}.fb-side__tab:hover:not(.fb-side__tab--active){color:var(--fb-text, #1a1c23)}.fb-side__tab--active{background:var(--fb-surface, #fff);box-shadow:var(--fb-shadow-sm, 0 1px 2px rgb(16 24 40 / 6%));color:var(--fb-text, #1a1c23);font-weight:600}.fb-side__content{flex:1;min-height:0;border-top:1px solid var(--fb-border-subtle, #eef0f4)}.fb-side__content>*{height:100%}.fb-side__footer{display:flex;gap:6px;padding:6px 8px;border-top:1px solid var(--fb-border-subtle, #e6e9ee)}@media(max-width:1200px){.fb-main__palette{flex-basis:150px}.fb-main__side{flex-basis:290px}}\n"] }]
13095
13869
  }], 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"] }], copyNameInput: [{ type: i0.ViewChild, args: ['copyNameInput', { isSignal: true }] }] } });
13096
13870
 
13097
13871
  /**
13098
13872
  * Generated bundle index. Do not edit.
13099
13873
  */
13100
13874
 
13101
- export { ConditionEditorComponent, ConnectorEditorComponent, DebugPanelComponent, DynamicScreenInspectorComponent, ElementDialogComponent, ElementInspectorComponent, ElementPaletteComponent, EnumValuePickerComponent, FALLBACK_COLLECTION_BY_TYPE, FALLBACK_TYPE_LABEL, FLOW_BUILDER_HTTP_CONFIG, FLOW_ELEMENT_ICONS, FLOW_ELEMENT_VARIANT_FIELDS, FLOW_ELEMENT_VARIANT_ICONS, FLOW_ERROR_FALLBACK_MESSAGE, FLOW_ERROR_HTTP_STATUS, FLOW_NAME_PATTERN, FLOW_NODE_COLLECTIONS, FLOW_NODE_HEIGHT, FLOW_NODE_WIDTH, FLOW_RESOURCE_COLLECTIONS, FLOW_VALUE_FIELDS, FLOW_VALUE_LITERAL_FIELDS, FieldAssignmentEditorComponent, FieldPickerComponent, FlowApiError, FlowBuilderApi, FlowBuilderComponent, FlowCanvasComponent, FlowCatalogStore, FlowDictionaryStore, FlowDocumentStore, FlowEditorSession, FlowLayoutService, FlowValidationStore, FormulaEditorComponent, FormulaValidationService, HttpFlowBuilderApi, NamePickerComponent, NodeInspectorBase, ORCHESTRATION_CONDITION_OUTPUT, ObjectPickerComponent, OrchestratedStageInspectorComponent, ParameterEditorComponent, ProblemsPanelComponent, RecordFilterEditorComponent, ReferencePickerComponent, ResourcePanelComponent, SEVERITY_BUCKETS, SEVERITY_ICON, SEVERITY_LABEL, START_NODE_NAME, SelectValueDirective, StartInspectorComponent, StructureMemberPickerComponent, StructurePickerComponent, TYPES_WITH_AUTOMATIC_OUTPUT, TYPE_BY_COLLECTION, UNSUPPORTED_TYPES, ValueEditorComponent, VersionPanelComponent, allFieldsOf, areTypesComparable, canvasNodeId, checkConditionLogic, checkFlowName, describePathEntry, duplicateField, elementIcon, emptyFlowDefinition, fieldAt, filterReferences, flattenFields, flowNodeWidth, flowNodeWidthClass, insertField, isCustomConditionLogic, isEmptyReferenceFilter, isFieldResource, isGlobalReference, isNumericType, isPathInside, isTypeCheckedOperator, isValidFlowName, isValued, loadPathLevel, matchesReferenceFilter, moveCondition, moveField, navigatePath, outletByKey, outletsOf, parseCanvasNodeId, parseInvariantNumber, parseSourceConnectorId, parseTargetConnectorId, pathAvailableNames, pathContainerLabel, pathKey, pathNotVerifiableMessage, referenceRoot, remapConditionLogic, removeCondition, removeField, resolvePath, samePath, screenActionNames, screenFieldNames, severityBucket, slugifyFlowName, sourceConnectorId, stageStepNames, stageStepOutputReferenced, stepsOf, targetConnectorId, uniqueFlowName, valuedFieldOf, valuedFieldsOf, variantFieldOf, variantOf, variantPresetOf };
13875
+ export { ConditionEditorComponent, ConnectorEditorComponent, DebugPanelComponent, DynamicScreenInspectorComponent, ElementDialogComponent, ElementInspectorComponent, ElementPaletteComponent, EnumValuePickerComponent, FALLBACK_COLLECTION_BY_TYPE, FALLBACK_TYPE_LABEL, FLOW_BUILDER_HTTP_CONFIG, FLOW_CLIPBOARD_KIND, FLOW_CLIPBOARD_VERSION, FLOW_ELEMENT_ICONS, FLOW_ELEMENT_VARIANT_FIELDS, FLOW_ELEMENT_VARIANT_ICONS, FLOW_ERROR_FALLBACK_MESSAGE, FLOW_ERROR_HTTP_STATUS, FLOW_NAME_PATTERN, FLOW_NODE_COLLECTIONS, FLOW_NODE_HEIGHT, FLOW_NODE_WIDTH, FLOW_REFERENCE_FIELDS, FLOW_RESOURCE_COLLECTIONS, FLOW_VALUE_FIELDS, FLOW_VALUE_LITERAL_FIELDS, FieldAssignmentEditorComponent, FieldPickerComponent, FlowApiError, FlowBuilderApi, FlowBuilderComponent, FlowCanvasComponent, FlowCatalogStore, FlowClipboardService, FlowDictionaryStore, FlowDocumentStore, FlowEditorSession, FlowLayoutService, FlowValidationStore, FormulaEditorComponent, FormulaValidationService, HttpFlowBuilderApi, NamePickerComponent, NodeInspectorBase, ORCHESTRATION_CONDITION_OUTPUT, ObjectPickerComponent, OrchestratedStageInspectorComponent, ParameterEditorComponent, PasteDialogComponent, ProblemsPanelComponent, RecordFilterEditorComponent, ReferencePickerComponent, ResourcePanelComponent, RunDialogComponent, SEVERITY_BUCKETS, SEVERITY_ICON, SEVERITY_LABEL, START_NODE_NAME, SelectValueDirective, StartInspectorComponent, StructureMemberPickerComponent, StructurePickerComponent, TYPES_WITH_AUTOMATIC_OUTPUT, TYPE_BY_COLLECTION, UNSUPPORTED_TYPES, ValueEditorComponent, VersionPanelComponent, allFieldsOf, applyPaste, areTypesComparable, buildClipboardPayload, canvasNodeId, checkConditionLogic, checkFlowName, describePathEntry, duplicateField, elementIcon, emptyFlowDefinition, fieldAt, filterReferences, flattenFields, flowNodeWidth, flowNodeWidthClass, innerNamesOf, insertField, isClipboardPayload, isCustomConditionLogic, isEmptyReferenceFilter, isFieldResource, isGlobalReference, isNumericType, isPathInside, isTypeCheckedOperator, isValidFlowName, isValued, loadPathLevel, matchesReferenceFilter, moveCondition, moveField, navigatePath, outletByKey, outletsOf, parseCanvasNodeId, parseInvariantNumber, parseSourceConnectorId, parseTargetConnectorId, pathAvailableNames, pathContainerLabel, pathKey, pathNotVerifiableMessage, planPaste, referenceRoot, referencedRootsOf, remapConditionLogic, removeCondition, removeField, resolvePath, rewriteReferences, samePath, screenActionNames, screenFieldNames, severityBucket, slugifyFlowName, sourceConnectorId, stageStepNames, stageStepOutputReferenced, stepsOf, targetConnectorId, typeOfCollection, uniqueFlowName, valuedFieldOf, valuedFieldsOf, variantFieldOf, variantOf, variantPresetOf };
13102
13876
  //# sourceMappingURL=esfaenza-flow-builder.mjs.map