@esfaenza/flow-builder 20.3.12 → 20.3.14

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.
@@ -1205,6 +1205,67 @@ function parseInvariantNumber(raw) {
1205
1205
  return Number.isFinite(parsed) ? parsed : undefined;
1206
1206
  }
1207
1207
 
1208
+ /**
1209
+ * Quale campo di un `FlowElementReferenceOrValue` porta davvero il valore — FRONTEND.md §4.2.
1210
+ *
1211
+ * La regola del contratto e' "uno e un solo campo valorizzato", e la §2 dice che cio' che non c'e'
1212
+ * si **omette**. Un backend che serializza tutte le proprieta' — `System.Text.Json` senza
1213
+ * `IgnoreNullValues` lo fa di default — rispetta il contratto nella sostanza e lo viola nella forma:
1214
+ * arriva `{"enumValue": "InIstruttoria", "formulaExpression": null, ...}`, cioe' un solo valore e
1215
+ * sette caselle piene di niente.
1216
+ *
1217
+ * Il campo si sceglieva con `!== undefined`, e un `null` passava: la prima casella letta —
1218
+ * `formulaExpression` — vinceva, l'editor apriva la formula (vuota) e il valore, pur presente nel
1219
+ * documento, spariva dalla vista. Da qui questa funzione sola: **`null` e' assente quanto
1220
+ * `undefined`**, e chi interpreta un valore la usa invece di confrontare con `undefined`.
1221
+ *
1222
+ * Sta in `core/` perche' i chiamanti sono piu' d'uno — l'editor del valore, l'editor delle
1223
+ * condizioni — e la regola non deve divergere fra loro.
1224
+ */
1225
+ /**
1226
+ * I campi che possono portare il valore, **nell'ordine in cui si interpretano**: `formulaExpression`
1227
+ * per primo perche' e' l'unico a due campi, poi il riferimento, poi i letterali. `formulaDataType`
1228
+ * non c'e': accompagna l'espressione, non e' un valore da solo.
1229
+ */
1230
+ const FLOW_VALUE_FIELDS = [
1231
+ 'formulaExpression',
1232
+ 'elementReference',
1233
+ 'stringValue',
1234
+ 'integerValue',
1235
+ 'numberValue',
1236
+ 'dateValue',
1237
+ 'booleanValue',
1238
+ 'enumValue',
1239
+ ];
1240
+ /** I soli campi letterali: quelli che l'editor mostra sotto la modalita' «Valore». */
1241
+ const FLOW_VALUE_LITERAL_FIELDS = [
1242
+ 'stringValue',
1243
+ 'integerValue',
1244
+ 'numberValue',
1245
+ 'dateValue',
1246
+ 'booleanValue',
1247
+ 'enumValue',
1248
+ ];
1249
+ /**
1250
+ * `true` se il campo porta un valore. Attenzione ai falsi legittimi: `booleanValue: false`,
1251
+ * `numberValue: 0` e `stringValue: ''` sono valori, quindi il confronto e' con `null`/`undefined`
1252
+ * e non una verifica di verita'.
1253
+ */
1254
+ function isValued(field) {
1255
+ return field !== undefined && field !== null;
1256
+ }
1257
+ /** I campi valorizzati, nell'ordine di interpretazione: piu' di uno e' `VALUE_AMBIGUOUS` (§4.2). */
1258
+ function valuedFieldsOf(value) {
1259
+ if (!value) {
1260
+ return [];
1261
+ }
1262
+ return FLOW_VALUE_FIELDS.filter((field) => isValued(value[field]));
1263
+ }
1264
+ /** Il campo che l'editor considera **il** valore, cioe' il primo valorizzato nell'ordine sopra. */
1265
+ function valuedFieldOf(value) {
1266
+ return valuedFieldsOf(value)[0];
1267
+ }
1268
+
1208
1269
  /**
1209
1270
  * Gli step di uno stage di orchestrazione — FRONTEND.md §5.13.
1210
1271
  *
@@ -3781,6 +3842,244 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImpo
3781
3842
  args: [{ selector: 'fb-element-palette', standalone: true, imports: [FFlowModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"fb-palette__search\">\r\n <input\r\n type=\"search\"\r\n placeholder=\"Cerca un elemento\"\r\n aria-label=\"Cerca un elemento\"\r\n (input)=\"onFilter($any($event.target).value)\"\r\n />\r\n</div>\r\n\r\n@if (isEmpty()) {\r\n <p class=\"fb-palette__empty\">\r\n Il dizionario degli elementi non e\u2019 ancora disponibile.\r\n </p>\r\n}\r\n\r\n<div class=\"fb-palette__groups\">\r\n @for (group of groups(); track group.category) {\r\n <section class=\"fb-palette__group\">\r\n <h3 class=\"fb-palette__category\">{{ group.category }}</h3>\r\n @for (item of group.items; track item.key) {\r\n <!--\r\n `fExternalItem` rende la voce trascinabile sul canvas: il rilascio emette\r\n `fCreateNode` con questo `fData` e la posizione, che diventa locationX/locationY.\r\n Il dato e' l'oggetto {type, variant}, non il solo tipo: due voci dello stesso tipo\r\n si distinguono soltanto per la variante.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-palette__item\"\r\n fExternalItem\r\n [fExternalItemId]=\"item.key\"\r\n [fData]=\"item.data\"\r\n [class.fb-palette__item--warn]=\"isIncompatible(item)\"\r\n [title]=\"hintOf(item)\"\r\n (click)=\"pick(item)\"\r\n >\r\n <span class=\"fb-palette__icon\" [class]=\"'fb-palette__icon ' + categoryClass(group.category)\">\r\n {{ iconOf(item) }}\r\n </span>\r\n <span class=\"fb-palette__text\">\r\n <span class=\"fb-palette__label\">{{ item.label }}</span>\r\n @if (item.entry.hasAutomaticOutput) {\r\n <span class=\"fb-palette__flag\" title=\"Espone un output automatico sotto il nome dell\u2019elemento\">\r\n output automatico\r\n </span>\r\n }\r\n @if (faultLabel(item)) {\r\n <span class=\"fb-palette__flag\" title=\"Ha un ramo che l\u2019autore puo\u2019 prevedere e disegnare\">\r\n {{ faultLabel(item) }}\r\n </span>\r\n }\r\n </span>\r\n @if (isIncompatible(item)) {\r\n <span class=\"fb-palette__warn\" aria-hidden=\"true\">!</span>\r\n }\r\n </button>\r\n }\r\n </section>\r\n }\r\n</div>\r\n", styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0;background:var(--fb-surface, #fff);border-right:1px solid var(--fb-border, #d6dae1)}.fb-palette__search{padding:8px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-palette__search input{box-sizing:border-box;width:100%;padding:5px 8px;border:1px solid var(--fb-border, #d6dae1);border-radius:6px;background:var(--fb-surface, #fff);color:var(--fb-text, #1d2939);font:inherit;font-size:12px}.fb-palette__search input:focus-visible{outline:2px solid var(--fb-accent, #2f6feb);outline-offset:-1px}.fb-palette__groups{flex:1;min-height:0;overflow-y:auto;padding:4px 0 12px}.fb-palette__empty{margin:12px 10px;font-size:12px;color:var(--fb-text-muted, #667085)}.fb-palette__category{margin:10px 10px 4px;font-size:10px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--fb-text-subtle, #98a2b3)}.fb-palette__item{display:flex;align-items:center;gap:8px;box-sizing:border-box;width:calc(100% - 12px);margin:1px 6px;padding:5px 8px;border:0;border-radius:var(--fb-radius-xs, 6px);background:transparent;color:var(--fb-text, #1a1c23);font:inherit;text-align:left;cursor:grab;transition:background .12s ease}.fb-palette__item:hover{background:var(--fb-surface-alt, #f7f8fa)}.fb-palette__item:focus-visible{outline:2px solid var(--fb-accent, #2f6feb);outline-offset:-2px}.fb-palette__icon{display:grid;place-items:center;flex:0 0 auto;width:24px;height:24px;border-radius:var(--fb-radius-xs, 6px);background:var(--fb-icon-bg, #98a2b3);color:#fff;font-size:11px;font-weight:700}.cat-screen{--fb-icon-bg: #3b82f6}.cat-logic{--fb-icon-bg: #8b5cf6}.cat-data{--fb-icon-bg: #06b6d4}.cat-action{--fb-icon-bg: #f59e0b}.cat-flow{--fb-icon-bg: #14b8a6}.fb-palette__text{display:flex;flex-direction:column;min-width:0}.fb-palette__label{font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-palette__flag{font-size:9px;color:var(--fb-text-subtle, #98a2b3)}.fb-palette__item--warn .fb-palette__label{color:var(--fb-warning, #b7791f)}.fb-palette__warn{margin-left:auto;color:var(--fb-warning, #b7791f);font-weight:700}:host ::ng-deep .f-external-item-preview{padding:4px 8px;border:1px solid var(--fb-accent, #2f6feb);border-radius:6px;background:var(--fb-surface, #fff);box-shadow:0 4px 12px #1018282e;opacity:.95}\n"] }]
3782
3843
  }], propDecorators: { processType: [{ type: i0.Input, args: [{ isSignal: true, alias: "processType", required: false }] }], elementPicked: [{ type: i0.Output, args: ["elementPicked"] }] } });
3783
3844
 
3845
+ /**
3846
+ * Il tipo di cio' che un riferimento designa — FRONTEND.md §4.1, §4.3, §4.4, §4.6, §4.7.1.
3847
+ *
3848
+ * `POST /flows/references` elenca le **radici**, non i percorsi: `Richiesta` c'e', `Richiesta.Stato`
3849
+ * no. Cercare la corrispondenza esatta del valore in quell'elenco basta per una risorsa e non basta
3850
+ * per un percorso, e la conseguenza non e' cosmetica: senza il tipo dell'ultimo segmento un membro
3851
+ * `Enum` diventa una casella di testo su un insieme chiuso (§4.6) e un confronto fra tipi
3852
+ * incompatibili non si vede prima del backend (§4.3).
3853
+ *
3854
+ * Il tipo di un percorso pero' si **ricava**: e' `core/path-navigation` che naviga la catena, e qui
3855
+ * c'e' l'innesto fra i due mondi — dall'elenco dei riferimenti alla tappa da cui partire, e dalla
3856
+ * tappa all'ultimo segmento. Sta in `core/` e non in un inspector perche' i chiamanti sono tre
3857
+ * (Assignment, condizioni, filtri) e la regola del **prefisso dichiarato piu' lungo** e' una sola:
3858
+ * duplicarla vorrebbe dire farla divergere.
3859
+ *
3860
+ * Le due asimmetrie da tenere a mente:
3861
+ *
3862
+ * - **Non ogni radice e' navigabile.** Un output automatico dichiara il proprio tipo, ma la
3863
+ * specifica tiene i percorsi che ne partono fuori dalla validazione (§7): dedurne il tipo qui
3864
+ * significherebbe far dire all'editor cio' che il backend non verifica. Stessa scelta del
3865
+ * `reference-picker`, e le due devono restare la stessa.
3866
+ * - **"Non lo so" resta "non lo so".** Catena interrotta, catalogo assente, segmento inesistente:
3867
+ * l'esito e' `null` e il chiamante ricade sul controllo generico. Un tipo indovinato e' peggio
3868
+ * di un tipo mancante, perche' fa comparire un elenco chiuso di valori sbagliati.
3869
+ */
3870
+ /**
3871
+ * Il riferimento dichiarato piu' **lungo** che sia un prefisso del valore, sui confini del punto.
3872
+ *
3873
+ * Non e' il primo segmento: un nome dichiarato puo' contenere il punto, e le globali sono il caso
3874
+ * per cui la regola esiste — di `$User.Anagrafica.Citta` il dizionario dichiara `$User.Anagrafica`
3875
+ * (§4.1: vince il prefisso dichiarato piu' lungo).
3876
+ */
3877
+ function declaredPrefixOf(references, value) {
3878
+ const trimmed = (value ?? '').trim();
3879
+ if (!trimmed) {
3880
+ return undefined;
3881
+ }
3882
+ let best;
3883
+ for (const candidate of references) {
3884
+ if (!candidate.name || (trimmed !== candidate.name && !trimmed.startsWith(`${candidate.name}.`))) {
3885
+ continue;
3886
+ }
3887
+ if (!best || candidate.name.length > best.name.length) {
3888
+ best = candidate;
3889
+ }
3890
+ }
3891
+ return best;
3892
+ }
3893
+ /**
3894
+ * La risorsa da cui il valore sta navigando, se e solo se il percorso e' verificabile: una
3895
+ * `Structure` con la sua classe, un record con la sua entita'. Un output automatico e' escluso di
3896
+ * proposito (vedi l'intestazione), e una collection non si naviga — il percorso designa l'insieme.
3897
+ */
3898
+ function navigableRootOf(references, value, isStructure) {
3899
+ const reference = declaredPrefixOf(references, value);
3900
+ if (!reference || reference.kind === 'ElementOutput' || !reference.objectType || reference.isCollection) {
3901
+ return undefined;
3902
+ }
3903
+ const trimmed = (value ?? '').trim();
3904
+ const path = trimmed.length > reference.name.length ? trimmed.slice(reference.name.length + 1) : '';
3905
+ if (isStructure(reference.dataType)) {
3906
+ return { reference, container: { kind: 'structure', name: reference.objectType }, path };
3907
+ }
3908
+ return reference.dataType === 'Object'
3909
+ ? { reference, container: { kind: 'object', name: reference.objectType }, path }
3910
+ : undefined;
3911
+ }
3912
+ /** Il tipo di un segmento risolto: campo o membro, ridotti al minimo comune (§4.7.1). */
3913
+ function typeOfEntry(entry) {
3914
+ return {
3915
+ dataType: entry.dataType,
3916
+ objectType: entry.objectType ?? undefined,
3917
+ isCollection: entry.isCollection,
3918
+ isWritable: entry.isWritable,
3919
+ origin: 'member',
3920
+ };
3921
+ }
3922
+ function typeOfReference(reference) {
3923
+ return {
3924
+ dataType: reference.dataType ?? undefined,
3925
+ objectType: reference.objectType ?? undefined,
3926
+ isCollection: reference.isCollection,
3927
+ isWritable: reference.isWritable,
3928
+ origin: 'resource',
3929
+ };
3930
+ }
3931
+ /**
3932
+ * Il tipo di un riferimento: la risorsa se il nome e' in elenco, altrimenti l'ultimo segmento del
3933
+ * percorso navigato. `null` = non si sa, e non autorizza a mostrare un elenco chiuso.
3934
+ *
3935
+ * `usage` vale solo per l'ultimo segmento quando la tappa e' un'entita' (§4.4): in un filtro serve
3936
+ * `filterable`, altrove `any` — chiederlo con l'uso sbagliato farebbe sparire un campo che esiste.
3937
+ */
3938
+ async function resolveReferenceType(catalog, references, value, isStructure, usage) {
3939
+ const trimmed = (value ?? '').trim();
3940
+ if (!trimmed) {
3941
+ return null;
3942
+ }
3943
+ const exact = references.find((candidate) => candidate.name === trimmed);
3944
+ if (exact) {
3945
+ return typeOfReference(exact);
3946
+ }
3947
+ const root = navigableRootOf(references, trimmed, isStructure);
3948
+ if (!root?.path) {
3949
+ return null;
3950
+ }
3951
+ return resolvePathType(catalog, root.container, root.path, usage);
3952
+ }
3953
+ /** Come sopra, dentro una tappa già nota: e' il caso dei filtri, dove la radice e' l'entita'. */
3954
+ async function resolvePathType(catalog, container, path, usage) {
3955
+ const trimmed = (path ?? '').trim();
3956
+ if (!trimmed) {
3957
+ return null;
3958
+ }
3959
+ const resolution = await resolvePath(catalog, container, trimmed, { usage });
3960
+ // Solo `resolved` e' autorevole: `unknown` e `unverifiable` sono cio' che il picker segnala, non
3961
+ // un tipo su cui costruire un elenco di valori.
3962
+ return resolution.status === 'resolved' && resolution.last ? typeOfEntry(resolution.last) : null;
3963
+ }
3964
+ function sameType(a, b) {
3965
+ if (!a || !b) {
3966
+ return a === b;
3967
+ }
3968
+ return (a.dataType === b.dataType &&
3969
+ a.objectType === b.objectType &&
3970
+ !!a.isCollection === !!b.isCollection &&
3971
+ a.isWritable === b.isWritable &&
3972
+ a.origin === b.origin);
3973
+ }
3974
+ /**
3975
+ * Il motore: risolve in background e pubblica per lettura sincrona.
3976
+ *
3977
+ * Perche' non un `computed`: la risoluzione e' asincrona (una chiamata di catalogo per tappa) e i
3978
+ * valori sono **molti** — un'operazione di Assignment per riga. Quindi un `effect` che risolve e una
3979
+ * mappa da cui i template leggono. Da chiamare in un **contesto di iniezione**.
3980
+ *
3981
+ * Il token di sequenza per chiave non e' cosmetico: una risposta superata che arriva dopo
3982
+ * scriverebbe il tipo di un valore che l'utente ha già cambiato, e l'editor mostrerebbe l'elenco di
3983
+ * valori sbagliato senza che nulla lo segnali.
3984
+ */
3985
+ function trackTypes(request) {
3986
+ const types = signal(new Map(), ...(ngDevMode ? [{ debugName: "types" }] : []));
3987
+ const scope = signal('', ...(ngDevMode ? [{ debugName: "scope" }] : []));
3988
+ /**
3989
+ * Fuori dai signal di proposito: e' il registro di cio' che e' già stato chiesto, non uno stato da
3990
+ * cui ridisegnare. Leggerlo dentro l'`effect` che lo scrive sarebbe un ciclo.
3991
+ */
3992
+ const requested = new Map();
3993
+ const tokens = new Map();
3994
+ let sequence = 0;
3995
+ effect(() => {
3996
+ const current = request();
3997
+ scope.set(current?.scope ?? '');
3998
+ if (!current) {
3999
+ return;
4000
+ }
4001
+ for (const value of new Set(current.values)) {
4002
+ const trimmed = (value ?? '').trim();
4003
+ if (!trimmed) {
4004
+ continue;
4005
+ }
4006
+ const key = `${current.scope}${trimmed}`;
4007
+ if (requested.get(key) === current.version) {
4008
+ continue;
4009
+ }
4010
+ requested.set(key, current.version);
4011
+ const token = ++sequence;
4012
+ tokens.set(key, token);
4013
+ void current.resolve(trimmed).then((result) => {
4014
+ if (tokens.get(key) !== token) {
4015
+ return;
4016
+ }
4017
+ const known = types();
4018
+ // Riscrivere la mappa con lo stesso contenuto ridisegnerebbe l'inspector a ogni battuta.
4019
+ if (known.has(key) && sameType(known.get(key), result)) {
4020
+ return;
4021
+ }
4022
+ const next = new Map(known);
4023
+ next.set(key, result);
4024
+ types.set(next);
4025
+ });
4026
+ }
4027
+ });
4028
+ const typeOf = (value) => {
4029
+ const trimmed = (value ?? '').trim();
4030
+ if (!trimmed) {
4031
+ return undefined;
4032
+ }
4033
+ return types().get(`${scope()}${trimmed}`) ?? undefined;
4034
+ };
4035
+ return {
4036
+ typeOf,
4037
+ dataTypeOf: (value) => typeOf(value)?.dataType,
4038
+ objectTypeOf: (value) => typeOf(value)?.objectType,
4039
+ };
4040
+ }
4041
+ /**
4042
+ * I tipi di piu' riferimenti insieme, risolti in background. È cio' che sostituisce la ricerca per
4043
+ * corrispondenza esatta negli inspector: sulle risorse risponde come prima, sui percorsi risponde
4044
+ * dove prima taceva.
4045
+ */
4046
+ function referenceTypes(catalog, isStructure, request) {
4047
+ return trackTypes(() => {
4048
+ const current = request();
4049
+ if (!current) {
4050
+ return null;
4051
+ }
4052
+ const references = current.references;
4053
+ const usage = current.usage ?? 'any';
4054
+ return {
4055
+ scope: `references|${usage}`,
4056
+ // L'elenco arriva da una richiesta: cambia identita' a ogni risposta, ed e' esattamente
4057
+ // quando le deduzioni vanno rifatte (una variabile rinominata, un tipo cambiato).
4058
+ version: references,
4059
+ values: current.values,
4060
+ resolve: (value) => resolveReferenceType(catalog, references, value, isStructure, usage),
4061
+ };
4062
+ });
4063
+ }
4064
+ /** I tipi di piu' percorsi dentro una tappa nota: i filtri su un'entita' (§4.4). */
4065
+ function pathTypes(catalog, request) {
4066
+ return trackTypes(() => {
4067
+ const current = request();
4068
+ const container = current?.container;
4069
+ if (!current || !container?.name) {
4070
+ return null;
4071
+ }
4072
+ const usage = current.usage ?? 'any';
4073
+ return {
4074
+ scope: `${container.kind}:${container.name}|${usage}`,
4075
+ // Tappa e uso stanno già nello scope: dentro lo stesso scope la risposta non cambia.
4076
+ version: 'container',
4077
+ values: current.paths,
4078
+ resolve: (path) => resolvePathType(catalog, container, path, usage),
4079
+ };
4080
+ });
4081
+ }
4082
+
3784
4083
  /**
3785
4084
  * Selettore di riferimento — FRONTEND.md §4.1, §4.5, §4.7, §5.2, §6.4, §13.3.
3786
4085
  *
@@ -3941,57 +4240,18 @@ class ReferencePickerComponent {
3941
4240
  });
3942
4241
  }
3943
4242
  /**
3944
- * Il riferimento dichiarato piu' **lungo** che sia un prefisso del valore, sui confini del punto.
4243
+ * Il riferimento dichiarato piu' **lungo** che sia un prefisso del valore, e la tappa da cui
4244
+ * navigare il resto. La regola sta in `core/reference-path` perche' non e' solo del picker: gli
4245
+ * inspector ne ricavano il **tipo** dell'ultimo segmento con la stessa catena, e due copie di
4246
+ * «prefisso piu' lungo» divergerebbero (§4.1, §4.7.1).
3945
4247
  *
3946
- * Non basta il primo segmento: un nome dichiarato puo' contenere il punto. Le globali sono il caso
3947
- * per cui la regola esiste — di `$User.Anagrafica.Citta` il dizionario dichiara `$User.Anagrafica`,
3948
- * e cercare la corrispondenza esatta di tutto cio' che segue lo scope segnalerebbe come rotto un
3949
- * percorso giusto (§4.1: vince il prefisso dichiarato piu' lungo).
4248
+ * Fra **tutte** le radici, non fra quelle filtrate: la radice di `Richiesta.Ragione` in un campo
4249
+ * `String` sta solo nell'elenco non filtrato.
3950
4250
  */
3951
- declaredPrefix = computed(() => {
3952
- const value = (this.value() ?? '')?.trim();
3953
- if (!value) {
3954
- return undefined;
3955
- }
3956
- let best;
3957
- for (const candidate of this.roots()) {
3958
- if (value !== candidate.name && !value.startsWith(`${candidate.name}.`)) {
3959
- continue;
3960
- }
3961
- if (!best || candidate.name.length > best.name.length) {
3962
- best = candidate;
3963
- }
3964
- }
3965
- return best;
3966
- }, ...(ngDevMode ? [{ debugName: "declaredPrefix" }] : []));
3967
- /**
3968
- * La risorsa da cui il valore corrente sta navigando, quando la sua forma e' **dichiarata**: una
3969
- * `Structure` con la classe, un record con l'entita'. È cio' che rende il percorso verificabile.
3970
- *
3971
- * Un output automatico e' escluso di proposito: la primitiva ne dichiara il tipo, ma la
3972
- * specifica tiene i percorsi che ne partono fuori dalla validazione (§7, ultimo capoverso), e
3973
- * l'editor non deve accusare cio' che il backend non verifica.
3974
- */
3975
- navigableRoot = computed(() => {
3976
- // Fra tutte le radici: quella di `Richiesta.Ragione` in un campo `String` sta solo nell'elenco
3977
- // non filtrato.
3978
- const reference = this.declaredPrefix();
3979
- if (!reference || reference.kind === 'ElementOutput' || !reference.objectType || reference.isCollection) {
3980
- return undefined;
3981
- }
3982
- if (this.dictionaries.isStructure(reference.dataType)) {
3983
- return { reference, container: { kind: 'structure', name: reference.objectType } };
3984
- }
3985
- return reference.dataType === 'Object'
3986
- ? { reference, container: { kind: 'object', name: reference.objectType } }
3987
- : undefined;
3988
- }, ...(ngDevMode ? [{ debugName: "navigableRoot" }] : []));
4251
+ declaredPrefix = computed(() => declaredPrefixOf(this.roots(), this.value()), ...(ngDevMode ? [{ debugName: "declaredPrefix" }] : []));
4252
+ navigableRoot = computed(() => navigableRootOf(this.roots(), this.value(), (dataType) => this.dictionaries.isStructure(dataType)), ...(ngDevMode ? [{ debugName: "navigableRoot" }] : []));
3989
4253
  /** Il percorso dopo la radice: `Cliente.Email` di `Richiesta.Cliente.Email`. */
3990
- navigatedPath = computed(() => {
3991
- const root = this.navigableRoot()?.reference.name;
3992
- const value = (this.value() ?? '')?.trim();
3993
- return root && value.length > root.length ? value.slice(root.length + 1) : '';
3994
- }, ...(ngDevMode ? [{ debugName: "navigatedPath" }] : []));
4254
+ navigatedPath = computed(() => this.navigableRoot()?.path ?? '', ...(ngDevMode ? [{ debugName: "navigatedPath" }] : []));
3995
4255
  navigation = navigatePath(this.catalog, () => {
3996
4256
  const root = this.navigableRoot();
3997
4257
  return root ? { root: root.container, path: this.navigatedPath() } : null;
@@ -5207,18 +5467,16 @@ class ValueEditorComponent {
5207
5467
  // valore che il modello non sa portare (§4.2, §4.7).
5208
5468
  return this.dataType() && this.literalAllowed() ? 'literal' : 'reference';
5209
5469
  }
5210
- if (value.formulaExpression !== undefined) {
5470
+ // `isValued` e non `!== undefined`: un backend che serializza anche cio' che non c'e' manda
5471
+ // `formulaExpression: null`, e il primo controllo apriva la formula (vuota) su un valore che nel
5472
+ // documento c'era — un letterale che sparisce dalla vista senza che nulla lo segnali (§4.2).
5473
+ if (isValued(value.formulaExpression)) {
5211
5474
  return 'formula';
5212
5475
  }
5213
- if (value.elementReference !== undefined) {
5476
+ if (isValued(value.elementReference)) {
5214
5477
  return value.elementReference.startsWith('$GlobalConstant.') ? 'globalConstant' : 'reference';
5215
5478
  }
5216
- if (value.stringValue !== undefined ||
5217
- value.integerValue !== undefined ||
5218
- value.numberValue !== undefined ||
5219
- value.dateValue !== undefined ||
5220
- value.booleanValue !== undefined ||
5221
- value.enumValue !== undefined) {
5479
+ if (FLOW_VALUE_LITERAL_FIELDS.some((field) => isValued(value[field]))) {
5222
5480
  return 'literal';
5223
5481
  }
5224
5482
  return 'empty';
@@ -5228,23 +5486,7 @@ class ValueEditorComponent {
5228
5486
  * Puo' capitare su un documento arrivato da un altro editor: si segnala e si offre
5229
5487
  * di normalizzarlo, invece di nasconderlo.
5230
5488
  */
5231
- filledFieldCount = computed(() => {
5232
- const value = this.value();
5233
- if (!value) {
5234
- return 0;
5235
- }
5236
- const keys = [
5237
- 'elementReference',
5238
- 'stringValue',
5239
- 'integerValue',
5240
- 'numberValue',
5241
- 'dateValue',
5242
- 'booleanValue',
5243
- 'enumValue',
5244
- 'formulaExpression',
5245
- ];
5246
- return keys.filter((key) => value[key] !== undefined).length;
5247
- }, ...(ngDevMode ? [{ debugName: "filledFieldCount" }] : []));
5489
+ filledFieldCount = computed(() => valuedFieldsOf(this.value()).length, ...(ngDevMode ? [{ debugName: "filledFieldCount" }] : []));
5248
5490
  isAmbiguous = computed(() => this.filledFieldCount() > 1, ...(ngDevMode ? [{ debugName: "isAmbiguous" }] : []));
5249
5491
  /**
5250
5492
  * Il campo del valore che corrisponde al `dataType` (§4.2, tabella degli otto tipi). `Object` e
@@ -5299,7 +5541,7 @@ class ValueEditorComponent {
5299
5541
  return '';
5300
5542
  }
5301
5543
  const field = this.literalField();
5302
- if (field === 'dateValue' && value.dateValue !== undefined) {
5544
+ if (field === 'dateValue' && isValued(value.dateValue)) {
5303
5545
  // `datetime-local` rifiuta un valore col fuso e resterebbe **vuoto** pur avendo un
5304
5546
  // valore: il suffisso si toglie qui e si riscrive in uscita. Se il documento porta un
5305
5547
  // campo diverso da quello atteso si passa al fallback qui sotto, che lo mostra comunque.
@@ -5459,7 +5701,13 @@ class ValueEditorComponent {
5459
5701
  this.emit({ elementReference: value.elementReference });
5460
5702
  return;
5461
5703
  default: {
5462
- const field = this.literalField();
5704
+ // Il campo del tipo atteso, ma solo se e' quello che porta il valore: dove il tipo non si
5705
+ // sa — o il documento usa un campo diverso — «normalizza» scriverebbe un valore vuoto e
5706
+ // butterebbe via l'unico che c'era.
5707
+ const expected = this.literalField();
5708
+ const field = isValued(value[expected])
5709
+ ? expected
5710
+ : (FLOW_VALUE_LITERAL_FIELDS.find((candidate) => isValued(value[candidate])) ?? expected);
5463
5711
  this.emit({ [field]: value[field] });
5464
5712
  }
5465
5713
  }
@@ -5505,6 +5753,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImpo
5505
5753
  */
5506
5754
  class ConditionEditorComponent {
5507
5755
  dictionaries = inject(FlowDictionaryStore);
5756
+ catalog = inject(FlowCatalogStore);
5508
5757
  api = inject(FlowBuilderApi);
5509
5758
  store = inject(FlowDocumentStore);
5510
5759
  /** Il contenitore: porta `conditions`, `conditionLogic` e, se serve, `formula`. */
@@ -5529,6 +5778,20 @@ class ConditionEditorComponent {
5529
5778
  * fra testo e numero prima che lo dica la validazione (§4.3).
5530
5779
  */
5531
5780
  references = signal([], ...(ngDevMode ? [{ debugName: "references" }] : []));
5781
+ /**
5782
+ * Il tipo dei due lati, risorsa o **percorso**. L'elenco porta le radici e non i percorsi (§6.4):
5783
+ * su `Richiesta.Stato` la corrispondenza esatta non trova niente, e il tipo si ricava navigando la
5784
+ * classe (§4.7.1). È cio' che fa comparire l'elenco dei valori su un membro `Enum` (§4.6) e che
5785
+ * rende visibile un confronto fra tipi incompatibili anche dentro un'istanza (§4.3).
5786
+ */
5787
+ types = referenceTypes(this.catalog, (dataType) => this.dictionaries.isStructure(dataType), () => ({
5788
+ references: this.references(),
5789
+ // Entrambi i lati: il destro serve a `rightDataType` quando e' un riferimento.
5790
+ values: this.conditions().flatMap((condition) => [
5791
+ condition.leftValueReference,
5792
+ condition.rightValue?.elementReference,
5793
+ ]),
5794
+ }));
5532
5795
  constructor() {
5533
5796
  effect(() => {
5534
5797
  const definition = this.store.document();
@@ -5544,12 +5807,9 @@ class ConditionEditorComponent {
5544
5807
  .catch(() => this.references.set([]));
5545
5808
  });
5546
5809
  }
5547
- /** Il tipo di un riferimento, `undefined` se navigato o ignoto: lì non si segnala (§4.3). */
5810
+ /** Il tipo di un riferimento, `undefined` dove non si sa: lì non si segnala (§4.3). */
5548
5811
  typeOfReference(reference) {
5549
- if (!reference) {
5550
- return undefined;
5551
- }
5552
- return this.references().find((entry) => entry.name === reference)?.dataType ?? undefined;
5812
+ return this.types.dataTypeOf(reference);
5553
5813
  }
5554
5814
  /** Il tipo del lato sinistro: guida il secondo operando. */
5555
5815
  leftDataType(condition) {
@@ -5557,15 +5817,11 @@ class ConditionEditorComponent {
5557
5817
  }
5558
5818
  /**
5559
5819
  * Il tipo concreto del lato sinistro quando e' un `Enum`: senza di questo il valore di confronto
5560
- * resterebbe una casella di testo su un insieme chiuso (§4.6). Su un percorso navigato
5561
- * (`Cliente.Stato`) il riferimento non e' in elenco e resta `undefined`: si digita, come prima.
5820
+ * resterebbe una casella di testo su un insieme chiuso (§4.6). Vale anche su un percorso navigato
5821
+ * (`Richiesta.Stato`, `$Record.Stato`): il tipo del membro o del campo lo ricava {@link types}.
5562
5822
  */
5563
5823
  leftObjectType(condition) {
5564
- const reference = condition.leftValueReference;
5565
- if (!reference) {
5566
- return undefined;
5567
- }
5568
- return this.references().find((entry) => entry.name === reference)?.objectType ?? undefined;
5824
+ return this.types.objectTypeOf(condition.leftValueReference);
5569
5825
  }
5570
5826
  /** Il tipo del lato destro, dedotto dal campo valorizzato o dal riferimento scelto. */
5571
5827
  rightDataType(condition) {
@@ -5573,31 +5829,29 @@ class ConditionEditorComponent {
5573
5829
  if (!value) {
5574
5830
  return undefined;
5575
5831
  }
5576
- if (value.formulaExpression !== undefined) {
5577
- return value.formulaDataType;
5578
- }
5579
- if (value.elementReference !== undefined) {
5580
- return this.typeOfReference(value.elementReference);
5581
- }
5582
- if (value.stringValue !== undefined) {
5583
- return 'String';
5584
- }
5585
- if (value.integerValue !== undefined) {
5586
- return 'Integer';
5587
- }
5588
- if (value.numberValue !== undefined) {
5589
- return 'Number';
5590
- }
5591
- if (value.dateValue !== undefined) {
5592
- return 'Date';
5593
- }
5594
- if (value.booleanValue !== undefined) {
5595
- return 'Boolean';
5596
- }
5597
- if (value.enumValue !== undefined) {
5598
- return 'Enum';
5832
+ // Stessa regola dell'editor del valore: un campo a `null` e' assente, non valorizzato — con
5833
+ // `!== undefined` un backend che serializza tutto faceva risultare **formula** ogni letterale,
5834
+ // e il tipo dedotto era quello sbagliato (§4.2).
5835
+ switch (valuedFieldOf(value)) {
5836
+ case 'formulaExpression':
5837
+ return value.formulaDataType;
5838
+ case 'elementReference':
5839
+ return this.typeOfReference(value.elementReference);
5840
+ case 'stringValue':
5841
+ return 'String';
5842
+ case 'integerValue':
5843
+ return 'Integer';
5844
+ case 'numberValue':
5845
+ return 'Number';
5846
+ case 'dateValue':
5847
+ return 'Date';
5848
+ case 'booleanValue':
5849
+ return 'Boolean';
5850
+ case 'enumValue':
5851
+ return 'Enum';
5852
+ default:
5853
+ return undefined;
5599
5854
  }
5600
- return undefined;
5601
5855
  }
5602
5856
  /**
5603
5857
  * `true` quando i due lati sono incompatibili: e' `CONDITION_TYPE_MISMATCH`, un errore che
@@ -5768,7 +6022,7 @@ class ConditionEditorComponent {
5768
6022
  // "confronta con questo" ma "l'esito atteso e' questo": va riscritto, non tenuto.
5769
6023
  condition.rightValue = { booleanValue: true };
5770
6024
  }
5771
- else if (condition.rightValue?.booleanValue !== undefined && operator !== 'EqualTo') {
6025
+ else if (isValued(condition.rightValue?.booleanValue) && operator !== 'EqualTo') {
5772
6026
  condition.rightValue = undefined;
5773
6027
  }
5774
6028
  });
@@ -5815,8 +6069,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImpo
5815
6069
  *
5816
6070
  * - I campi proposti passano da `GET /schema/{object}/fields?usage=filterable`, così un
5817
6071
  * campo non filtrabile non arriva nemmeno alla validazione (`FIELD_NOT_FILTERABLE`).
5818
- * La scrittura libera resta possibile, perche' i percorsi di relazione
5819
- * (`Cliente.Citta`) non sono enumerabili e la validazione non li verifica.
6072
+ * La scrittura libera resta possibile un catalogo incompleto non deve bloccare il campo — ma i
6073
+ * percorsi di relazione (`Cliente.Citta`) **si navigano**: il tipo dell'ultimo segmento si ricava
6074
+ * con `core/reference-path`, ed e' cio' che fa comparire l'elenco dei valori su un `Enum` (§4.4).
5820
6075
  */
5821
6076
  class RecordFilterEditorComponent {
5822
6077
  catalog = inject(FlowCatalogStore);
@@ -5838,25 +6093,25 @@ class RecordFilterEditorComponent {
5838
6093
  emptyWarning = input(null, ...(ngDevMode ? [{ debugName: "emptyWarning" }] : []));
5839
6094
  emptyWarningSeverity = input('warn', ...(ngDevMode ? [{ debugName: "emptyWarningSeverity" }] : []));
5840
6095
  changed = output();
5841
- fields = signal([], ...(ngDevMode ? [{ debugName: "fields" }] : []));
5842
6096
  /** Tutti i campi, filtrabili o no: serve solo a riconoscere le chiavi composte (§5.7). */
5843
6097
  allFields = signal([], ...(ngDevMode ? [{ debugName: "allFields" }] : []));
5844
6098
  filters = computed(() => this.holder().filters ?? [], ...(ngDevMode ? [{ debugName: "filters" }] : []));
5845
6099
  operators = computed(() => this.dictionaries.recordFilterOperators(), ...(ngDevMode ? [{ debugName: "operators" }] : []));
6100
+ /**
6101
+ * Il tipo del campo filtrato, **compresi i percorsi di relazione**: `Cliente.Citta` non e' nella
6102
+ * lista piatta dei campi dell'entita' e il suo tipo si ricava navigando la catena (§4.4). Senza,
6103
+ * un campo `Enum` raggiunto per relazione tornava una casella di testo su un insieme chiuso (§4.6).
6104
+ *
6105
+ * L'`usage` e' quello del campo — `filterable` fra i filtri — e vale per l'**ultimo** segmento: i
6106
+ * segmenti intermedi li chiede `resolvePath` con `any`, perche' l'uso riguarda il campo confrontato
6107
+ * e non la relazione che porta a lui.
6108
+ */
6109
+ types = pathTypes(this.catalog, () => ({
6110
+ container: this.object() ? { kind: 'object', name: this.object() } : null,
6111
+ paths: this.filters().map((filter) => filter.field),
6112
+ usage: this.usage(),
6113
+ }));
5846
6114
  constructor() {
5847
- effect(() => {
5848
- const object = this.object();
5849
- const usage = this.usage();
5850
- if (!object) {
5851
- this.fields.set([]);
5852
- return;
5853
- }
5854
- void this.catalog
5855
- .listFields(object, usage)
5856
- .then((list) => this.fields.set(list ?? []))
5857
- // Catalogo non disponibile: il campo resta scrivibile a mano.
5858
- .catch(() => this.fields.set([]));
5859
- });
5860
6115
  effect(() => {
5861
6116
  const object = this.object();
5862
6117
  if (!object) {
@@ -5996,18 +6251,17 @@ class RecordFilterEditorComponent {
5996
6251
  }
5997
6252
  /**
5998
6253
  * Il tipo del campo, per proporre il controllo giusto sul valore: e' il motivo per cui il
5999
- * catalogo resta caricato qui, e non solo dentro il picker.
6254
+ * catalogo resta interrogato qui, e non solo dentro il picker.
6000
6255
  */
6001
6256
  fieldDataType(filter) {
6002
- return this.fields().find((field) => field.name === filter.field)?.dataType;
6257
+ return this.types.dataTypeOf(filter.field);
6003
6258
  }
6004
6259
  /**
6005
6260
  * Il tipo concreto del campo: su un campo `Enum` e' cio' che permette di proporre i valori
6006
- * ammessi invece di farli digitare (§4.6). Su un percorso di relazione il campo non e' in elenco
6007
- * e resta `undefined`, come per il tipo.
6261
+ * ammessi invece di farli digitare (§4.6).
6008
6262
  */
6009
6263
  fieldObjectType(filter) {
6010
- return this.fields().find((field) => field.name === filter.field)?.objectType ?? undefined;
6264
+ return this.types.objectTypeOf(filter.field);
6011
6265
  }
6012
6266
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.27", ngImport: i0, type: RecordFilterEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6013
6267
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.27", type: RecordFilterEditorComponent, isStandalone: true, selector: "fb-record-filter-editor", inputs: { holder: { classPropertyName: "holder", publicName: "holder", isSignal: true, isRequired: true, transformFunction: null }, object: { classPropertyName: "object", publicName: "object", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, usage: { classPropertyName: "usage", publicName: "usage", isSignal: true, isRequired: false, transformFunction: null }, supportsLogic: { classPropertyName: "supportsLogic", publicName: "supportsLogic", isSignal: true, isRequired: false, transformFunction: null }, supportsFormula: { classPropertyName: "supportsFormula", publicName: "supportsFormula", isSignal: true, isRequired: false, transformFunction: null }, emptyWarning: { classPropertyName: "emptyWarning", publicName: "emptyWarning", isSignal: true, isRequired: false, transformFunction: null }, emptyWarningSeverity: { classPropertyName: "emptyWarningSeverity", publicName: "emptyWarningSeverity", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { changed: "changed" }, ngImport: i0, template: "<fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">{{ title() }}</legend>\r\n\r\n @if (!object()) {\r\n <p class=\"fb-field__hint\">Scegli prima un oggetto per poter filtrare sui suoi campi.</p>\r\n }\r\n\r\n @if (supportsLogic()) {\r\n <div class=\"fb-filter__modes\" role=\"group\" aria-label=\"Logica dei filtri\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"logicMode() === 'and'\"\r\n (click)=\"setLogicMode('and')\"\r\n >\r\n Tutti (AND)\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"logicMode() === 'or'\"\r\n (click)=\"setLogicMode('or')\"\r\n >\r\n Almeno uno (OR)\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"logicMode() === 'custom'\"\r\n (click)=\"setLogicMode('custom')\"\r\n >\r\n Espressione\r\n </button>\r\n @if (supportsFormula()) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"logicMode() === 'formula'\"\r\n (click)=\"setLogicMode('formula')\"\r\n >\r\n Formula\r\n </button>\r\n }\r\n </div>\r\n } @else {\r\n <!-- Qui il modello non ha `filterLogic`: mostrarlo lo farebbe perdere al salvataggio. -->\r\n <p class=\"fb-field__hint\">Su questo elemento i filtri sono sempre combinati in AND.</p>\r\n }\r\n\r\n @if (supportsLogic() && logicMode() === 'custom') {\r\n <div class=\"fb-field\">\r\n <input\r\n class=\"fb-input fb-input--mono\"\r\n [value]=\"customLogic()\"\r\n placeholder=\"1 AND (2 OR 3)\"\r\n aria-label=\"Espressione sugli indici dei filtri\"\r\n (input)=\"setCustomLogic($any($event.target).value)\"\r\n />\r\n </div>\r\n }\r\n\r\n @if (supportsFormula() && logicMode() === 'formula') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Formula di filtro</label>\r\n <textarea\r\n class=\"fb-textarea fb-input--mono\"\r\n [value]=\"holder().filterFormula || ''\"\r\n (input)=\"setFormula($any($event.target).value)\"\r\n ></textarea>\r\n <p class=\"fb-field__hint\">Valutata dal motore di regole, in alternativa ai filtri.</p>\r\n </div>\r\n }\r\n\r\n @if (identifierNotFilterable()) {\r\n <p class=\"fb-field__hint\">\r\n Su questo oggetto l\u2019identificativo non e\u2019 filtrabile: la chiave e\u2019 composta e porta la propria\r\n forma canonica. Filtra per le colonne della chiave, una per colonna.\r\n </p>\r\n }\r\n\r\n @if (showEmptyWarning()) {\r\n <p\r\n class=\"fb-callout\"\r\n [class.fb-callout--warn]=\"emptyWarningSeverity() === 'warn'\"\r\n [class.fb-callout--error]=\"emptyWarningSeverity() === 'error'\"\r\n >\r\n {{ emptyWarning() }}\r\n </p>\r\n }\r\n\r\n <div class=\"fb-list\">\r\n @for (filter of filters(); track $index) {\r\n <div class=\"fb-list__item\">\r\n <div class=\"fb-list__header\">\r\n <span class=\"fb-list__index\">{{ $index + 1 }}</span>\r\n <span class=\"fb-list__spacer\"></span>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n aria-label=\"Rimuovi il filtro\"\r\n (click)=\"removeFilter($index)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Campo</label>\r\n <fb-field-picker\r\n [value]=\"filter.field\"\r\n [object]=\"object()\"\r\n [usage]=\"usage()\"\r\n placeholder=\"Scrivi o scegli un campo\"\r\n (valueChange)=\"setField($index, $event ?? '')\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Operatore</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"filter.operator || ''\"\r\n (change)=\"setOperator($index, $any($event.target).value)\"\r\n >\r\n @for (operator of operators(); track operator.value) {\r\n <option [value]=\"operator.value\">{{ operator.label }}</option>\r\n }\r\n </select>\r\n </div>\r\n\r\n @if (isNullOperator(filter)) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Esito atteso</label>\r\n <div class=\"fb-filter__modes\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"nullExpectation(filter)\"\r\n (click)=\"setNullExpectation($index, true)\"\r\n >\r\n \u00E8 vuoto\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"!nullExpectation(filter)\"\r\n (click)=\"setNullExpectation($index, false)\"\r\n >\r\n non \u00E8 vuoto\r\n </button>\r\n </div>\r\n </div>\r\n } @else {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Valore</label>\r\n <fb-value-editor\r\n [value]=\"filter.value\"\r\n [dataType]=\"fieldDataType(filter)\"\r\n [objectType]=\"fieldObjectType(filter)\"\r\n label=\"Valore del filtro\"\r\n [allowFormula]=\"false\"\r\n (valueChange)=\"setValue($index, $event)\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n } @empty {\r\n <p class=\"fb-empty\">Nessun filtro.</p>\r\n }\r\n </div>\r\n\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"addFilter()\">Aggiungi filtro</button>\r\n</fieldset>\r\n", styles: [":host{display:block}.fb-filter__modes{display:flex;flex-wrap:wrap;gap:3px;margin-bottom:8px}.fb-filter__mode{padding:3px 8px;border:1px solid var(--fb-border-subtle, #e6e9ee);border-radius:12px;background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:11px;cursor:pointer}.fb-filter__mode:hover{background:var(--fb-surface-alt, #f8f9fb)}.fb-filter__mode--active{border-color:var(--fb-accent, #2f6feb);background:color-mix(in srgb,var(--fb-accent, #2f6feb) 10%,transparent);color:var(--fb-accent, #2f6feb);font-weight:600}\n"], dependencies: [{ kind: "component", type: FieldPickerComponent, selector: "fb-field-picker", inputs: ["value", "object", "usage", "label", "placeholder", "disabled"], outputs: ["valueChange"] }, { kind: "component", type: ValueEditorComponent, selector: "fb-value-editor", inputs: ["value", "label", "dataType", "objectType", "isCollection", "disabled", "allowFormula"], outputs: ["valueChange"] }, { kind: "directive", type: SelectValueDirective, selector: "select[fbValue]", inputs: ["fbValue"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
@@ -6555,12 +6809,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImpo
6555
6809
  class AssignmentInspectorComponent extends NodeInspectorBase {
6556
6810
  elementType = 'Assignment';
6557
6811
  api = inject(FlowBuilderApi);
6812
+ catalog = inject(FlowCatalogStore);
6558
6813
  /**
6559
6814
  * I riferimenti **scrivibili**, per conoscere il tipo della destinazione. Non e' un doppione del
6560
6815
  * picker: serve il tipo, ed e' cio' che permette all'editor del valore di mostrare il controllo
6561
6816
  * giusto — e su un `Enum` di proporre i valori del tipo invece di farli digitare (§4.6).
6562
6817
  */
6563
6818
  writableReferences = signal([], ...(ngDevMode ? [{ debugName: "writableReferences" }] : []));
6819
+ /**
6820
+ * Il tipo della destinazione, risorsa o **percorso**. L'elenco porta le radici e non i percorsi
6821
+ * (§6.4): su `Richiesta.Stato` la corrispondenza esatta non trova niente, e il tipo si ricava
6822
+ * navigando la classe — altrimenti un membro `Enum` resterebbe una casella di testo (§4.6, §4.7.1).
6823
+ *
6824
+ * `usage` resta quello di default (`any`): un campo di `$Record` si assegna se e' createable
6825
+ * **oppure** updateable, e qui il contesto non dice quale dei due (§5.7).
6826
+ */
6827
+ targetTypes = referenceTypes(this.catalog, (dataType) => this.dictionaries.isStructure(dataType), () => ({
6828
+ references: this.writableReferences(),
6829
+ values: this.items().map((item) => item.assignToReference),
6830
+ }));
6564
6831
  constructor() {
6565
6832
  super();
6566
6833
  effect(() => {
@@ -6576,22 +6843,11 @@ class AssignmentInspectorComponent extends NodeInspectorBase {
6576
6843
  .catch(() => this.writableReferences.set([]));
6577
6844
  });
6578
6845
  }
6579
- /**
6580
- * Il tipo della destinazione, solo per corrispondenza **esatta** del nome: un percorso navigato
6581
- * (`Cliente.Stato`) non e' in elenco, e lì il tipo resta ignoto invece di essere indovinato.
6582
- */
6583
- targetOf(item) {
6584
- const reference = item.assignToReference;
6585
- if (!reference) {
6586
- return undefined;
6587
- }
6588
- return this.writableReferences().find((entry) => entry.name === reference);
6589
- }
6590
6846
  targetDataType(item) {
6591
- return this.targetOf(item)?.dataType ?? undefined;
6847
+ return this.targetTypes.dataTypeOf(item.assignToReference);
6592
6848
  }
6593
6849
  targetObjectType(item) {
6594
- return this.targetOf(item)?.objectType ?? undefined;
6850
+ return this.targetTypes.objectTypeOf(item.assignToReference);
6595
6851
  }
6596
6852
  assignment = computed(() => this.node(), ...(ngDevMode ? [{ debugName: "assignment" }] : []));
6597
6853
  items = computed(() => this.assignment().assignmentItems ?? [], ...(ngDevMode ? [{ debugName: "items" }] : []));
@@ -6679,11 +6935,11 @@ class AssignmentInspectorComponent extends NodeInspectorBase {
6679
6935
  return !operator.startsWith('Remove') || operator === 'RemovePosition' || operator === 'RemoveUncommon';
6680
6936
  }
6681
6937
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.27", ngImport: i0, type: AssignmentInspectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6682
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.27", type: AssignmentInspectorComponent, isStandalone: true, selector: "fb-assignment-inspector", usesInheritance: true, ngImport: i0, template: "<fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Operazioni</legend>\r\n <p class=\"fb-section__note\">\r\n Le operazioni sono eseguite <strong>nell\u2019ordine in cui compaiono</strong>: spostarne una cambia il\r\n risultato.\r\n </p>\r\n\r\n <div class=\"fb-list\">\r\n @for (item of items(); 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=\"Sposta su\"\r\n [disabled]=\"$first\"\r\n (click)=\"moveItem($index, -1)\"\r\n >\r\n \u2191\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n aria-label=\"Sposta giu\u2019\"\r\n [disabled]=\"$last\"\r\n (click)=\"moveItem($index, 1)\"\r\n >\r\n \u2193\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n aria-label=\"Rimuovi l\u2019operazione\"\r\n (click)=\"removeItem($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\">Destinazione</label>\r\n <!--\r\n Solo destinazioni scrivibili: una costante o una formula produrrebbero\r\n TARGET_NOT_WRITABLE. Le globali assegnabili \u2014 `$Flow.CurrentStage`,\r\n `$Flow.ActiveStages` e quelle dichiarate scrivibili dall'host \u2014 arrivano gi\u00E0\r\n dalla primitiva, non si aggiungono qui.\r\n -->\r\n <fb-reference-picker\r\n [value]=\"item.assignToReference\"\r\n [writableOnly]=\"true\"\r\n placeholder=\"Scegli una variabile\"\r\n (valueChange)=\"setTarget($index, $event)\"\r\n />\r\n <p class=\"fb-field__hint\">\r\n Puoi scrivere anche un campo di un record: <code>Cliente.Email</code>, <code>$Record.Stato</code>.\r\n </p>\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Operazione</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"item.operator || ''\"\r\n (change)=\"setOperator($index, $any($event.target).value)\"\r\n >\r\n @for (operator of operators(); track operator.value) {\r\n <option [value]=\"operator.value\">{{ operator.label }}</option>\r\n }\r\n </select>\r\n @if (operatorDescription(item.operator)) {\r\n <p class=\"fb-field__hint\">{{ operatorDescription(item.operator) }}</p>\r\n }\r\n @if (addSemanticsHint(item)) {\r\n <p class=\"fb-field__hint\">{{ addSemanticsHint(item) }}</p>\r\n }\r\n @if (expectsCollection(item)) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n Questa operazione ha senso solo su una destinazione collection: su una variabile singola non\r\n fallisce, semplicemente non fa nulla.\r\n </p>\r\n }\r\n </div>\r\n\r\n @if (needsValue(item)) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Valore</label>\r\n <!--\r\n Il tipo della destinazione guida il controllo: su un Enum i valori proponibili sono\r\n quelli del suo tipo, e farli digitare e' il modo piu' facile di scriverne uno che il\r\n runtime non riconosce (\u00A74.6). Ignoto il tipo, il campo resta generico.\r\n -->\r\n <fb-value-editor\r\n [value]=\"item.value\"\r\n label=\"Valore\"\r\n [dataType]=\"$any(targetDataType(item))\"\r\n [objectType]=\"targetObjectType(item)\"\r\n (valueChange)=\"setValue($index, $event)\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n } @empty {\r\n <p class=\"fb-empty\">\r\n Nessuna operazione: un Assignment senza operazioni non fa nulla (ASSIGNMENT_WITHOUT_ITEMS).\r\n </p>\r\n }\r\n </div>\r\n\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"addItem()\">Aggiungi operazione</button>\r\n</fieldset>\r\n\r\n<fb-connector-editor\r\n [nodeName]=\"name()\"\r\n [node]=\"node()\"\r\n [outlets]=\"outlets()\"\r\n (connectorChanged)=\"onConnectorChanged($event)\"\r\n/>\r\n", dependencies: [{ kind: "component", type: ConnectorEditorComponent, selector: "fb-connector-editor", inputs: ["nodeName", "node", "outlets", "title"], outputs: ["connectorChanged"] }, { kind: "component", type: ReferencePickerComponent, selector: "fb-reference-picker", inputs: ["value", "label", "placeholder", "disabled", "dataType", "isCollection", "objectType", "writableOnly", "elementsOnly"], outputs: ["valueChange"] }, { kind: "component", type: ValueEditorComponent, selector: "fb-value-editor", inputs: ["value", "label", "dataType", "objectType", "isCollection", "disabled", "allowFormula"], outputs: ["valueChange"] }, { kind: "directive", type: SelectValueDirective, selector: "select[fbValue]", inputs: ["fbValue"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
6938
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.27", type: AssignmentInspectorComponent, isStandalone: true, selector: "fb-assignment-inspector", usesInheritance: true, ngImport: i0, template: "<fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Operazioni</legend>\r\n <p class=\"fb-section__note\">\r\n Le operazioni sono eseguite <strong>nell\u2019ordine in cui compaiono</strong>: spostarne una cambia il\r\n risultato.\r\n </p>\r\n\r\n <div class=\"fb-list\">\r\n @for (item of items(); 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=\"Sposta su\"\r\n [disabled]=\"$first\"\r\n (click)=\"moveItem($index, -1)\"\r\n >\r\n \u2191\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n aria-label=\"Sposta giu\u2019\"\r\n [disabled]=\"$last\"\r\n (click)=\"moveItem($index, 1)\"\r\n >\r\n \u2193\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n aria-label=\"Rimuovi l\u2019operazione\"\r\n (click)=\"removeItem($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\">Destinazione</label>\r\n <!--\r\n Solo destinazioni scrivibili: una costante o una formula produrrebbero\r\n TARGET_NOT_WRITABLE. Le globali assegnabili \u2014 `$Flow.CurrentStage`,\r\n `$Flow.ActiveStages` e quelle dichiarate scrivibili dall'host \u2014 arrivano gi\u00E0\r\n dalla primitiva, non si aggiungono qui.\r\n -->\r\n <fb-reference-picker\r\n [value]=\"item.assignToReference\"\r\n [writableOnly]=\"true\"\r\n placeholder=\"Scegli una variabile\"\r\n (valueChange)=\"setTarget($index, $event)\"\r\n />\r\n <p class=\"fb-field__hint\">\r\n Puoi scrivere anche un campo di un record: <code>Cliente.Email</code>, <code>$Record.Stato</code>.\r\n </p>\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Operazione</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"item.operator || ''\"\r\n (change)=\"setOperator($index, $any($event.target).value)\"\r\n >\r\n @for (operator of operators(); track operator.value) {\r\n <option [value]=\"operator.value\">{{ operator.label }}</option>\r\n }\r\n </select>\r\n @if (operatorDescription(item.operator)) {\r\n <p class=\"fb-field__hint\">{{ operatorDescription(item.operator) }}</p>\r\n }\r\n @if (addSemanticsHint(item)) {\r\n <p class=\"fb-field__hint\">{{ addSemanticsHint(item) }}</p>\r\n }\r\n @if (expectsCollection(item)) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n Questa operazione ha senso solo su una destinazione collection: su una variabile singola non\r\n fallisce, semplicemente non fa nulla.\r\n </p>\r\n }\r\n </div>\r\n\r\n @if (needsValue(item)) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Valore</label>\r\n <!--\r\n Il tipo della destinazione guida il controllo: su un Enum i valori proponibili sono\r\n quelli del suo tipo, e farli digitare e' il modo piu' facile di scriverne uno che il\r\n runtime non riconosce (\u00A74.6). Vale anche dentro una classe \u2014 \u00ABRichiesta.Stato\u00BB \u2014 perche'\r\n il tipo si ricava navigando i membri (\u00A74.7.1). Ignoto il tipo, il campo resta generico.\r\n -->\r\n <fb-value-editor\r\n [value]=\"item.value\"\r\n label=\"Valore\"\r\n [dataType]=\"targetDataType(item)\"\r\n [objectType]=\"targetObjectType(item)\"\r\n (valueChange)=\"setValue($index, $event)\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n } @empty {\r\n <p class=\"fb-empty\">\r\n Nessuna operazione: un Assignment senza operazioni non fa nulla (ASSIGNMENT_WITHOUT_ITEMS).\r\n </p>\r\n }\r\n </div>\r\n\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"addItem()\">Aggiungi operazione</button>\r\n</fieldset>\r\n\r\n<fb-connector-editor\r\n [nodeName]=\"name()\"\r\n [node]=\"node()\"\r\n [outlets]=\"outlets()\"\r\n (connectorChanged)=\"onConnectorChanged($event)\"\r\n/>\r\n", dependencies: [{ kind: "component", type: ConnectorEditorComponent, selector: "fb-connector-editor", inputs: ["nodeName", "node", "outlets", "title"], outputs: ["connectorChanged"] }, { kind: "component", type: ReferencePickerComponent, selector: "fb-reference-picker", inputs: ["value", "label", "placeholder", "disabled", "dataType", "isCollection", "objectType", "writableOnly", "elementsOnly"], outputs: ["valueChange"] }, { kind: "component", type: ValueEditorComponent, selector: "fb-value-editor", inputs: ["value", "label", "dataType", "objectType", "isCollection", "disabled", "allowFormula"], outputs: ["valueChange"] }, { kind: "directive", type: SelectValueDirective, selector: "select[fbValue]", inputs: ["fbValue"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
6683
6939
  }
6684
6940
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImport: i0, type: AssignmentInspectorComponent, decorators: [{
6685
6941
  type: Component,
6686
- args: [{ selector: 'fb-assignment-inspector', standalone: true, imports: [ConnectorEditorComponent, ReferencePickerComponent, ValueEditorComponent, SelectValueDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Operazioni</legend>\r\n <p class=\"fb-section__note\">\r\n Le operazioni sono eseguite <strong>nell\u2019ordine in cui compaiono</strong>: spostarne una cambia il\r\n risultato.\r\n </p>\r\n\r\n <div class=\"fb-list\">\r\n @for (item of items(); 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=\"Sposta su\"\r\n [disabled]=\"$first\"\r\n (click)=\"moveItem($index, -1)\"\r\n >\r\n \u2191\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n aria-label=\"Sposta giu\u2019\"\r\n [disabled]=\"$last\"\r\n (click)=\"moveItem($index, 1)\"\r\n >\r\n \u2193\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n aria-label=\"Rimuovi l\u2019operazione\"\r\n (click)=\"removeItem($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\">Destinazione</label>\r\n <!--\r\n Solo destinazioni scrivibili: una costante o una formula produrrebbero\r\n TARGET_NOT_WRITABLE. Le globali assegnabili \u2014 `$Flow.CurrentStage`,\r\n `$Flow.ActiveStages` e quelle dichiarate scrivibili dall'host \u2014 arrivano gi\u00E0\r\n dalla primitiva, non si aggiungono qui.\r\n -->\r\n <fb-reference-picker\r\n [value]=\"item.assignToReference\"\r\n [writableOnly]=\"true\"\r\n placeholder=\"Scegli una variabile\"\r\n (valueChange)=\"setTarget($index, $event)\"\r\n />\r\n <p class=\"fb-field__hint\">\r\n Puoi scrivere anche un campo di un record: <code>Cliente.Email</code>, <code>$Record.Stato</code>.\r\n </p>\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Operazione</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"item.operator || ''\"\r\n (change)=\"setOperator($index, $any($event.target).value)\"\r\n >\r\n @for (operator of operators(); track operator.value) {\r\n <option [value]=\"operator.value\">{{ operator.label }}</option>\r\n }\r\n </select>\r\n @if (operatorDescription(item.operator)) {\r\n <p class=\"fb-field__hint\">{{ operatorDescription(item.operator) }}</p>\r\n }\r\n @if (addSemanticsHint(item)) {\r\n <p class=\"fb-field__hint\">{{ addSemanticsHint(item) }}</p>\r\n }\r\n @if (expectsCollection(item)) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n Questa operazione ha senso solo su una destinazione collection: su una variabile singola non\r\n fallisce, semplicemente non fa nulla.\r\n </p>\r\n }\r\n </div>\r\n\r\n @if (needsValue(item)) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Valore</label>\r\n <!--\r\n Il tipo della destinazione guida il controllo: su un Enum i valori proponibili sono\r\n quelli del suo tipo, e farli digitare e' il modo piu' facile di scriverne uno che il\r\n runtime non riconosce (\u00A74.6). Ignoto il tipo, il campo resta generico.\r\n -->\r\n <fb-value-editor\r\n [value]=\"item.value\"\r\n label=\"Valore\"\r\n [dataType]=\"$any(targetDataType(item))\"\r\n [objectType]=\"targetObjectType(item)\"\r\n (valueChange)=\"setValue($index, $event)\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n } @empty {\r\n <p class=\"fb-empty\">\r\n Nessuna operazione: un Assignment senza operazioni non fa nulla (ASSIGNMENT_WITHOUT_ITEMS).\r\n </p>\r\n }\r\n </div>\r\n\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"addItem()\">Aggiungi operazione</button>\r\n</fieldset>\r\n\r\n<fb-connector-editor\r\n [nodeName]=\"name()\"\r\n [node]=\"node()\"\r\n [outlets]=\"outlets()\"\r\n (connectorChanged)=\"onConnectorChanged($event)\"\r\n/>\r\n" }]
6942
+ args: [{ selector: 'fb-assignment-inspector', standalone: true, imports: [ConnectorEditorComponent, ReferencePickerComponent, ValueEditorComponent, SelectValueDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Operazioni</legend>\r\n <p class=\"fb-section__note\">\r\n Le operazioni sono eseguite <strong>nell\u2019ordine in cui compaiono</strong>: spostarne una cambia il\r\n risultato.\r\n </p>\r\n\r\n <div class=\"fb-list\">\r\n @for (item of items(); 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=\"Sposta su\"\r\n [disabled]=\"$first\"\r\n (click)=\"moveItem($index, -1)\"\r\n >\r\n \u2191\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n aria-label=\"Sposta giu\u2019\"\r\n [disabled]=\"$last\"\r\n (click)=\"moveItem($index, 1)\"\r\n >\r\n \u2193\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n aria-label=\"Rimuovi l\u2019operazione\"\r\n (click)=\"removeItem($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\">Destinazione</label>\r\n <!--\r\n Solo destinazioni scrivibili: una costante o una formula produrrebbero\r\n TARGET_NOT_WRITABLE. Le globali assegnabili \u2014 `$Flow.CurrentStage`,\r\n `$Flow.ActiveStages` e quelle dichiarate scrivibili dall'host \u2014 arrivano gi\u00E0\r\n dalla primitiva, non si aggiungono qui.\r\n -->\r\n <fb-reference-picker\r\n [value]=\"item.assignToReference\"\r\n [writableOnly]=\"true\"\r\n placeholder=\"Scegli una variabile\"\r\n (valueChange)=\"setTarget($index, $event)\"\r\n />\r\n <p class=\"fb-field__hint\">\r\n Puoi scrivere anche un campo di un record: <code>Cliente.Email</code>, <code>$Record.Stato</code>.\r\n </p>\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Operazione</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"item.operator || ''\"\r\n (change)=\"setOperator($index, $any($event.target).value)\"\r\n >\r\n @for (operator of operators(); track operator.value) {\r\n <option [value]=\"operator.value\">{{ operator.label }}</option>\r\n }\r\n </select>\r\n @if (operatorDescription(item.operator)) {\r\n <p class=\"fb-field__hint\">{{ operatorDescription(item.operator) }}</p>\r\n }\r\n @if (addSemanticsHint(item)) {\r\n <p class=\"fb-field__hint\">{{ addSemanticsHint(item) }}</p>\r\n }\r\n @if (expectsCollection(item)) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n Questa operazione ha senso solo su una destinazione collection: su una variabile singola non\r\n fallisce, semplicemente non fa nulla.\r\n </p>\r\n }\r\n </div>\r\n\r\n @if (needsValue(item)) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Valore</label>\r\n <!--\r\n Il tipo della destinazione guida il controllo: su un Enum i valori proponibili sono\r\n quelli del suo tipo, e farli digitare e' il modo piu' facile di scriverne uno che il\r\n runtime non riconosce (\u00A74.6). Vale anche dentro una classe \u2014 \u00ABRichiesta.Stato\u00BB \u2014 perche'\r\n il tipo si ricava navigando i membri (\u00A74.7.1). Ignoto il tipo, il campo resta generico.\r\n -->\r\n <fb-value-editor\r\n [value]=\"item.value\"\r\n label=\"Valore\"\r\n [dataType]=\"targetDataType(item)\"\r\n [objectType]=\"targetObjectType(item)\"\r\n (valueChange)=\"setValue($index, $event)\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n } @empty {\r\n <p class=\"fb-empty\">\r\n Nessuna operazione: un Assignment senza operazioni non fa nulla (ASSIGNMENT_WITHOUT_ITEMS).\r\n </p>\r\n }\r\n </div>\r\n\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"addItem()\">Aggiungi operazione</button>\r\n</fieldset>\r\n\r\n<fb-connector-editor\r\n [nodeName]=\"name()\"\r\n [node]=\"node()\"\r\n [outlets]=\"outlets()\"\r\n (connectorChanged)=\"onConnectorChanged($event)\"\r\n/>\r\n" }]
6687
6943
  }], ctorParameters: () => [] });
6688
6944
 
6689
6945
  /**
@@ -10376,5 +10632,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImpo
10376
10632
  * Generated bundle index. Do not edit.
10377
10633
  */
10378
10634
 
10379
- export { ConditionEditorComponent, ConnectorEditorComponent, DebugPanelComponent, ElementDialogComponent, ElementInspectorComponent, ElementPaletteComponent, EnumValuePickerComponent, FALLBACK_COLLECTION_BY_TYPE, FALLBACK_TYPE_LABEL, FLOW_BUILDER_HTTP_CONFIG, FLOW_ELEMENT_ICONS, FLOW_ELEMENT_VARIANT_FIELDS, FLOW_ELEMENT_VARIANT_ICONS, FLOW_ERROR_FALLBACK_MESSAGE, FLOW_ERROR_HTTP_STATUS, FLOW_NAME_PATTERN, FLOW_NODE_COLLECTIONS, FLOW_NODE_HEIGHT, FLOW_NODE_WIDTH, FLOW_RESOURCE_COLLECTIONS, FieldAssignmentEditorComponent, FieldPickerComponent, FlowApiError, FlowBuilderApi, FlowBuilderComponent, FlowCanvasComponent, FlowCatalogStore, FlowDictionaryStore, FlowDocumentStore, FlowEditorSession, FlowLayoutService, FlowValidationStore, HttpFlowBuilderApi, NamePickerComponent, NodeInspectorBase, ORCHESTRATION_CONDITION_OUTPUT, ObjectPickerComponent, OrchestratedStageInspectorComponent, ParameterEditorComponent, ProblemsPanelComponent, RecordFilterEditorComponent, ReferencePickerComponent, ResourcePanelComponent, SEVERITY_BUCKETS, SEVERITY_ICON, SEVERITY_LABEL, START_NODE_NAME, SelectValueDirective, StartInspectorComponent, StructureMemberPickerComponent, StructurePickerComponent, TYPES_WITH_AUTOMATIC_OUTPUT, TYPE_BY_COLLECTION, UNSUPPORTED_TYPES, ValueEditorComponent, VersionPanelComponent, areTypesComparable, canvasNodeId, checkConditionLogic, checkFlowName, describePathEntry, elementIcon, emptyFlowDefinition, filterReferences, flowNodeWidth, flowNodeWidthClass, isCustomConditionLogic, isEmptyReferenceFilter, isGlobalReference, isNumericType, isTypeCheckedOperator, isValidFlowName, loadPathLevel, matchesReferenceFilter, moveCondition, navigatePath, outletByKey, outletsOf, parseCanvasNodeId, parseInvariantNumber, parseSourceConnectorId, parseTargetConnectorId, pathAvailableNames, pathContainerLabel, pathNotVerifiableMessage, referenceRoot, remapConditionLogic, removeCondition, resolvePath, severityBucket, slugifyFlowName, sourceConnectorId, stageStepNames, stageStepOutputReferenced, stepsOf, targetConnectorId, uniqueFlowName, variantFieldOf, variantOf, variantPresetOf };
10635
+ export { ConditionEditorComponent, ConnectorEditorComponent, DebugPanelComponent, ElementDialogComponent, ElementInspectorComponent, ElementPaletteComponent, EnumValuePickerComponent, FALLBACK_COLLECTION_BY_TYPE, FALLBACK_TYPE_LABEL, FLOW_BUILDER_HTTP_CONFIG, FLOW_ELEMENT_ICONS, FLOW_ELEMENT_VARIANT_FIELDS, FLOW_ELEMENT_VARIANT_ICONS, FLOW_ERROR_FALLBACK_MESSAGE, FLOW_ERROR_HTTP_STATUS, FLOW_NAME_PATTERN, FLOW_NODE_COLLECTIONS, FLOW_NODE_HEIGHT, FLOW_NODE_WIDTH, FLOW_RESOURCE_COLLECTIONS, FLOW_VALUE_FIELDS, FLOW_VALUE_LITERAL_FIELDS, FieldAssignmentEditorComponent, FieldPickerComponent, FlowApiError, FlowBuilderApi, FlowBuilderComponent, FlowCanvasComponent, FlowCatalogStore, FlowDictionaryStore, FlowDocumentStore, FlowEditorSession, FlowLayoutService, FlowValidationStore, HttpFlowBuilderApi, NamePickerComponent, NodeInspectorBase, ORCHESTRATION_CONDITION_OUTPUT, ObjectPickerComponent, OrchestratedStageInspectorComponent, ParameterEditorComponent, ProblemsPanelComponent, RecordFilterEditorComponent, ReferencePickerComponent, ResourcePanelComponent, SEVERITY_BUCKETS, SEVERITY_ICON, SEVERITY_LABEL, START_NODE_NAME, SelectValueDirective, StartInspectorComponent, StructureMemberPickerComponent, StructurePickerComponent, TYPES_WITH_AUTOMATIC_OUTPUT, TYPE_BY_COLLECTION, UNSUPPORTED_TYPES, ValueEditorComponent, VersionPanelComponent, areTypesComparable, canvasNodeId, checkConditionLogic, checkFlowName, describePathEntry, elementIcon, emptyFlowDefinition, filterReferences, flowNodeWidth, flowNodeWidthClass, isCustomConditionLogic, isEmptyReferenceFilter, isGlobalReference, isNumericType, isTypeCheckedOperator, isValidFlowName, isValued, loadPathLevel, matchesReferenceFilter, moveCondition, navigatePath, outletByKey, outletsOf, parseCanvasNodeId, parseInvariantNumber, parseSourceConnectorId, parseTargetConnectorId, pathAvailableNames, pathContainerLabel, pathNotVerifiableMessage, referenceRoot, remapConditionLogic, removeCondition, resolvePath, severityBucket, slugifyFlowName, sourceConnectorId, stageStepNames, stageStepOutputReferenced, stepsOf, targetConnectorId, uniqueFlowName, valuedFieldOf, valuedFieldsOf, variantFieldOf, variantOf, variantPresetOf };
10380
10636
  //# sourceMappingURL=esfaenza-flow-builder.mjs.map