@esfaenza/flow-builder 20.3.39 → 20.3.40

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.
package/README.md CHANGED
@@ -489,6 +489,16 @@ sapere: le globali sono di sola lettura, membri compresi — le assegnabili sono
489
489
  `$Flow.ActiveStages` e i campi di `$Record` — e il picker lo dice con `TARGET_NOT_WRITABLE` invece
490
490
  di far sembrare il nome inesistente (§5.2).
491
491
 
492
+ **Uno scope puo' nominarsi da solo.** `$Profile` senza percorso e' un riferimento valido se — e solo
493
+ se — l'host lo dichiara così, con una voce **senza punto** che porta il suo `dataType` e il suo
494
+ `objectType` (§4.1): e' come un host espone l'utente corrente come un'unica istanza invece di
495
+ elencarne i campi, e da lì la navigazione riparte, quindi `$Profile.Livello` si verifica sui membri
496
+ della classe senza che il catalogo dichiari un percorso per ognuno. Il prefisso dichiarato piu' lungo
497
+ e' la stessa regola di prima: la voce senza punto e' il prefisso **vuoto**, cioe' quello che vince
498
+ solo quando nessun altro corrisponde. Dove lo scope dichiara percorsi ma non la voce senza punto,
499
+ `$Profile` da solo resta `GLOBAL_UNKNOWN`: nella demo e' la differenza fra `$Profile` (dichiarato
500
+ così) e `$User` (che dichiara i suoi campi).
501
+
492
502
  Su una destinazione la colonna che decide non e' la stessa nei due mondi: un membro porta il proprio
493
503
  `isWritable`, un campo no. Fuori da un Create o da un Update il contesto non c'e' — un Assignment su
494
504
  `Cliente.Codice` non sa se quel record verra' creato o aggiornato — quindi lì basta `isCreateable`
@@ -575,6 +585,19 @@ aggiunte. Tre esiti restano distinti e nessuno dei tre e' «formula valida»: pr
575
585
  stata controllata), `isValid` (verificata davvero, ed e' l'unico caso con la spunta verde) — una
576
586
  spunta su un'espressione mai controllata e' peggio di nessuna spunta.
577
587
 
588
+ **Testo con merge field (§5.12).** Il messaggio di un Custom Error non e' una casella di testo
589
+ qualunque: supporta i segnaposto `{!...}` come il testo di un text template e le scritte di uno screen
590
+ dinamico, e lì servono piu' che altrove — *«L'ordine e' già stato evaso»* senza il numero dell'ordine e'
591
+ meta' messaggio. Lo monta `<fb-merge-text-editor>`, ed e' un componente diverso dall'editor di formule
592
+ perche' il contenuto e' diverso: qui c'e' una **frase**, con dentro delle isole. Tre conseguenze. Offre
593
+ l'**inserimento** di un riferimento (un bottone con l'elenco, piu' i suggerimenti mentre si scrive dentro
594
+ un `{!` aperto), e scrive l'isola intera, graffe comprese: la sintassi non deve stare a memoria. Manda il
595
+ testo al motore **solo se contiene `{!`**, perche' una frase in italiano mandata a un motore di
596
+ espressioni si farebbe accusare parola per parola. E gli esiti restano i tre delle formule, con la stessa
597
+ regola: nessuna spunta su un testo che nessuno ha guardato. Lo stesso controllo avviene nella validazione
598
+ del documento, con `usage: "Text"` e il rilievo sul path `customErrorMessages[n].errorMessage`: un refuso
599
+ dentro una graffa non si scopre davanti a chi sta usando il flow.
600
+
578
601
  **Un campo a `null` e' assente.** Il valore ha un campo per tipo e va valorizzato **uno e un solo**
579
602
  campo (§4.2); la §2 dice che cio' che non c'e' si omette, ma un backend che serializza tutte le
580
603
  proprieta' — `System.Text.Json` senza `IgnoreNullValues` lo fa di default — manda
@@ -849,7 +872,7 @@ distribuire e nessuna dipendenza da un font di icone dell'applicazione ospite.
849
872
  | 13 | **Testo e numero non si confrontano** (`CONDITION_TYPE_MISMATCH`) | `ConditionEditorComponent` conosce il tipo del lato sinistro da `POST /flows/references`: filtra gli operatori applicabili, guida il campo del letterale del secondo operando e segnala la coppia vietata. La regola sta in `core/condition-types.util.ts`, che salta gli operatori con semantica propria (`Contains`, `In`, …). I numeri si scrivono in cultura invariante: `1234,50` viene tradotto in `1234.50`, non troncato |
850
873
  | 14 | **`None` non e' un operatore, e' un "da completare"** | Condizione evidenziata come incompleta, con il conteggio in testa alla sezione e la frase «la bozza si salva, l'attivazione no»; il validatore della demo lo emette come **errore** |
851
874
  | 15 | **Un valore data senza `Z` significa ora locale** | `ValueEditorComponent` ha un selettore «Ora locale / UTC (Z)», dice che cambiare fuso riscrive l'orario e non lo converte, e toglie il suffisso solo per il controllo `datetime-local`, che con il fuso resterebbe vuoto |
852
- | 16 | **`Structure` e `Object` non sono intercambiabili** | `StructurePickerComponent` per la classe (fuori catalogo = **errore**, `STRUCTURE_TYPE_UNKNOWN`) e `StructureMemberPickerComponent` per i membri, filtrati su `isWritable` dove serve una destinazione. Il pannello risorse mostra la classe come obbligatoria con severita' di errore e **non** mostra il valore iniziale, perche' un `structureValue` non esiste; `ReferencePickerComponent` propone `Variabile.Membro` e verifica il percorso **fino in fondo** con `core/path-navigation.ts`, con `PATH_NOT_VERIFIABLE` dove la catena si interrompe; il Transform con target classe chiede un membro invece di un campo. `FlowDictionaryStore.isStructure` legge il flag dal dizionario, non dal nome del tipo. La risposta dei membri e' la **descrizione della classe** (`{className, label, members}`), e i suoi tre esiti restano distinti fino alla UI: `FlowCatalogStore.describeStructure` li traduce in `declared` / `undeclared` / `unknown-class` / `unknown-catalog`, e solo `declared` autorizza a dire che un membro non esiste (§4.7.1) |
875
+ | 16 | **`Structure` e `Object` non sono intercambiabili** | `StructurePickerComponent` per la classe (fuori catalogo = **errore**, `STRUCTURE_TYPE_UNKNOWN`) e `StructureMemberPickerComponent` per i membri, filtrati su `isWritable` **soltanto** con `usage="writable"`, cioe' dove serve una destinazione: un membro calcolato si legge — in una condizione, in una formula, in un valore — e filtrarlo ovunque renderebbe invisibili proprio i membri derivati (§13.16). Il pannello risorse mostra la classe come obbligatoria con severita' di errore e **non** mostra il valore iniziale, perche' un `structureValue` non esiste; `ReferencePickerComponent` propone `Variabile.Membro` e verifica il percorso **fino in fondo** con `core/path-navigation.ts`, con `PATH_NOT_VERIFIABLE` dove la catena si interrompe; il Transform con target classe chiede un membro invece di un campo. `FlowDictionaryStore.isStructure` legge il flag dal dizionario, non dal nome del tipo. La risposta dei membri e' la **descrizione della classe** (`{className, label, members}`), e i suoi tre esiti restano distinti fino alla UI: `FlowCatalogStore.describeStructure` li traduce in `declared` / `undeclared` / `unknown-class` / `unknown-catalog`, e solo `declared` autorizza a dire che un membro non esiste (§4.7.1) |
853
876
  | 17 | **Due body diversi per due famiglie di primitive** | `HttpFlowBuilderApi`: le primitive che **analizzano** il documento — `validate`, `parse`, `references`, `references/writable`, `connector-targets`, `outline` — lo mandano **nudo alla radice**; l'involucro `{definition, flowName, …}` resta alle sole primitive di scrittura della §6.2. Sbagliare involucro non da' nessun errore: il backend legge un flow vuoto e risponde `200`. Per lo stesso motivo `getReferences`/`getWritableReferences` prendono la `FlowDefinition` e non un oggetto query: non c'e' nessun campo di filtro nel body, e il filtro di tipo lo applica l'editor con `core/reference-filter.util.ts` |
854
877
  | 18 | **Gli enum si scrivono come stringhe** | Il modello e' fatto di unioni di stringhe letterali (`FlowDataType`, `FlowVersionStatus`, …) e non di enum numerici: `JSON.stringify` non ha modo di produrre un ordinale. Conta perche' `"status": 3` non e' `Draft` ma `InvalidDraft` e `"dataType": 0` e' `String`, non "non specificato" — nessun errore lo segnalerebbe. Chi scrive un'altra implementazione di `FlowBuilderApi` non traduca i valori in numeri |
855
878
  | 19 | **Un campo di screen dinamico e' una risorsa, ma di sola lettura** | Lo restituisce `POST /flows/references` con `kind: "ScreenField"`, e il reference picker lo propone in un gruppo suo; nei selettori di **destinazione** non compare, perche' `references/writable` lo esclude. L'unica eccezione e' l'`assignToReference` di una **screen action**, dove il campo di *quella* schermata e' una destinazione e l'elenco lo passa il chiamante (`extraReferences`). Il nome entra in `FlowDocumentStore.usedNames`, quindi un campo omonimo di una variabile e' `NAME_DUPLICATED`, e `renameScreenField` riscrive i riferimenti come `renameNode` |
@@ -3821,14 +3821,21 @@ class FlowDictionaryStore {
3821
3821
  isGlobalScopeVerifiable(scope) {
3822
3822
  return (this.globalScope(scope)?.paths?.length ?? 0) > 0;
3823
3823
  }
3824
- /** I percorsi noti di uno scope, già prefissati: `$User.Email`. */
3824
+ /**
3825
+ * I percorsi noti di uno scope, già prefissati: `$User.Email`.
3826
+ *
3827
+ * Il percorso **vuoto** e' lo scope stesso (§4.1): un host puo' dichiarare `$Profile` senza
3828
+ * punto — un'unica istanza invece dell'elenco dei suoi campi — e lì il riferimento e' il nome
3829
+ * dello scope. Concatenare comunque il punto produrrebbe `$Profile.`, che non e' un
3830
+ * riferimento di niente.
3831
+ */
3825
3832
  globalPathsOf(scope) {
3826
3833
  const entry = this.globalScope(scope);
3827
3834
  if (!entry) {
3828
3835
  return [];
3829
3836
  }
3830
3837
  const prefix = entry.scope ?? entry.value;
3831
- return (entry.paths ?? []).map((path) => `${prefix}.${path}`);
3838
+ return (entry.paths ?? []).map((path) => (path ? `${prefix}.${path}` : prefix));
3832
3839
  }
3833
3840
  /** §5.14 — la voce di `stageStepTypes` di un `actionType`. */
3834
3841
  stageStepType(value) {
@@ -4081,7 +4088,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImpo
4081
4088
  * `code` e' stabile e si usa per decidere il comportamento; `message` e' testo per
4082
4089
  * l'utente e non va interpretato (§7).
4083
4090
  */
4084
- const DEBOUNCE_MS$1 = 400;
4091
+ const DEBOUNCE_MS$2 = 400;
4085
4092
  class FlowValidationStore {
4086
4093
  api = inject(FlowBuilderApi);
4087
4094
  destroyRef = inject(DestroyRef);
@@ -4161,7 +4168,7 @@ class FlowValidationStore {
4161
4168
  this.timer = setTimeout(() => {
4162
4169
  this.timer = undefined;
4163
4170
  void this.run(definition);
4164
- }, DEBOUNCE_MS$1);
4171
+ }, DEBOUNCE_MS$2);
4165
4172
  }
4166
4173
  /** Valida subito, saltando il debounce: prima di attivare, o su richiesta esplicita. */
4167
4174
  async run(definition) {
@@ -8222,7 +8229,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImpo
8222
8229
  * di «la primitiva non c'e'», che e' l'unica cosa condivisa fra i campi.
8223
8230
  */
8224
8231
  /** §11.4 — la stessa pausa di digitazione della validazione del documento. */
8225
- const DEBOUNCE_MS = 400;
8232
+ const DEBOUNCE_MS$1 = 400;
8226
8233
  class FormulaEditorComponent {
8227
8234
  store = inject(FlowDocumentStore);
8228
8235
  api = inject(FlowBuilderApi);
@@ -8576,7 +8583,7 @@ class FormulaEditorComponent {
8576
8583
  this.timer = setTimeout(() => {
8577
8584
  this.timer = undefined;
8578
8585
  void this.run(expression);
8579
- }, DEBOUNCE_MS);
8586
+ }, DEBOUNCE_MS$1);
8580
8587
  }
8581
8588
  async run(expression) {
8582
8589
  const token = ++this.sequence;
@@ -9900,6 +9907,346 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImpo
9900
9907
  args: [{ selector: 'fb-connector-editor', standalone: true, imports: [SelectValueDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">{{ title() }}</legend>\r\n\r\n @for (outlet of outlets(); track outlet.key) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\" [class.fb-field__label--required]=\"outlet.isStructural\">\r\n {{ outlet.label }}\r\n <span class=\"fb-conn__kind\" [attr.data-kind]=\"outlet.kind\">{{ outlet.kind }}</span>\r\n </label>\r\n\r\n <div class=\"fb-field__row\">\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"currentTarget(outlet)\"\r\n [attr.aria-label]=\"'Destinazione di ' + outlet.label\"\r\n (change)=\"setTarget(outlet, $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 nessuna destinazione \u2014</option>\r\n @for (target of targetOptions(); track target.name) {\r\n <option [value]=\"target.name\">{{ target.label || target.name }}</option>\r\n }\r\n </select>\r\n @if (!isTerminal(outlet)) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n aria-label=\"Rimuovi il ramo\"\r\n title=\"Rimuove il ramo: il percorso finira\u2019 qui\"\r\n (click)=\"clear(outlet)\"\r\n >\r\n \u00D7\r\n </button>\r\n }\r\n </div>\r\n\r\n @if (currentTarget(outlet)) {\r\n <label class=\"fb-check\">\r\n <input type=\"checkbox\" [checked]=\"isGoTo(outlet)\" (change)=\"setGoTo(outlet, $any($event.target).checked)\" />\r\n Disegna come salto indietro (isGoTo)\r\n </label>\r\n }\r\n\r\n @if (hintFor(outlet)) {\r\n <p\r\n class=\"fb-field__hint\"\r\n [class.fb-field__hint--warn]=\"isDangling(outlet) || (isTerminal(outlet) && outlet.isStructural)\"\r\n >\r\n {{ hintFor(outlet) }}\r\n </p>\r\n }\r\n </div>\r\n } @empty {\r\n <p class=\"fb-field__hint\">Questo elemento non ha uscite: interrompe l\u2019esecuzione.</p>\r\n }\r\n</fieldset>\r\n", styles: [":host{display:block}.fb-conn__kind{margin-left:5px;padding:0 5px;border-radius:8px;background:var(--fb-badge-bg, #eef1f5);font-size:9px;font-weight:600;text-transform:none;color:var(--fb-text-subtle, #98a2b3)}.fb-conn__kind[data-kind=Fault]{background:color-mix(in srgb,var(--fb-error, #c9372c) 14%,transparent);color:var(--fb-error, #c9372c)}.fb-conn__kind[data-kind=Timeout],.fb-conn__kind[data-kind=ScheduledPath]{background:color-mix(in srgb,var(--fb-warning, #b7791f) 14%,transparent);color:var(--fb-warning, #b7791f)}.fb-conn__kind[data-kind=Rule],.fb-conn__kind[data-kind=WaitEvent]{background:color-mix(in srgb,var(--fb-edge-rule, #7c5cd6) 14%,transparent);color:var(--fb-edge-rule, #7c5cd6)}.fb-field__row .fb-select{flex:1;min-width:0}\n"] }]
9901
9908
  }], ctorParameters: () => [], propDecorators: { nodeName: [{ type: i0.Input, args: [{ isSignal: true, alias: "nodeName", required: true }] }], node: [{ type: i0.Input, args: [{ isSignal: true, alias: "node", required: true }] }], outlets: [{ type: i0.Input, args: [{ isSignal: true, alias: "outlets", required: true }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], connectorChanged: [{ type: i0.Output, args: ["connectorChanged"] }] } });
9902
9909
 
9910
+ /**
9911
+ * Un testo con **merge field** — FRONTEND.md §5.12, §5.2.
9912
+ *
9913
+ * Non e' un editor di formule, ed e' la distinzione che regge tutto il componente: qui il
9914
+ * contenuto e' testo — una frase che l'utente legge — con dentro delle isole `{!...}` che il
9915
+ * runtime sostituisce quando l'elemento scatta. Trattarlo come un'espressione accuserebbe ogni
9916
+ * parola scritta in italiano; ignorarlo del tutto lascerebbe scoprire un refuso dentro una
9917
+ * graffa davanti a chi sta usando il flow.
9918
+ *
9919
+ * Tre regole, e vengono tutte dal contratto:
9920
+ *
9921
+ * - **si offre l'inserimento di un riferimento.** E' il posto in cui serve di piu' — «L'ordine
9922
+ * e' già stato evaso» senza il numero dell'ordine e' meta' messaggio — e i nomi sono quelli di
9923
+ * `POST /flows/references`, la stessa fonte del reference picker e dell'autocompletamento
9924
+ * delle formule (output automatici compresi, §4.5).
9925
+ * - **si verifica solo se contiene `{!`.** Un testo senza segnaposto non viene mandato a
9926
+ * nessuno: non c'e' niente da verificare, e la risposta di un motore di espressioni su una
9927
+ * frase sarebbe un rilievo per parola.
9928
+ * - **l'esito non e' mai una spunta inventata.** Come per le formule (§6.3) gli esiti sono tre e
9929
+ * restano distinti: primitiva assente (silenzio, e il servizio la spegne per la sessione),
9930
+ * `isVerified: false` (motore non configurato — si dice), `isValid` (l'unico con la spunta).
9931
+ *
9932
+ * Il codice del rilievo e' **del motore**, non della §7: si mostra e non si interpreta. La
9933
+ * `position` e' riferita al testo intero — anche quando il difetto sta dentro un'isola — perche'
9934
+ * e' lì che il bottone del rilievo va a selezionare.
9935
+ */
9936
+ /** §11.4 — la stessa pausa di digitazione della validazione del documento. */
9937
+ const DEBOUNCE_MS = 400;
9938
+ /** L'apertura di un'isola. Sta qui perche' la usano il rilevamento e l'inserimento. */
9939
+ const OPEN = '{!';
9940
+ class MergeTextEditorComponent {
9941
+ store = inject(FlowDocumentStore);
9942
+ api = inject(FlowBuilderApi);
9943
+ service = inject(FormulaValidationService);
9944
+ destroyRef = inject(DestroyRef);
9945
+ text = input('', ...(ngDevMode ? [{ debugName: "text" }] : []));
9946
+ /**
9947
+ * Dove comparira'. Resta un input perche' un motore puo' distinguere il messaggio di un errore
9948
+ * dalla scritta di un campo, ma il valore giusto e' quasi sempre quello predefinito.
9949
+ */
9950
+ usage = input('Text', ...(ngDevMode ? [{ debugName: "usage" }] : []));
9951
+ placeholder = input('', ...(ngDevMode ? [{ debugName: "placeholder" }] : []));
9952
+ ariaLabel = input('Testo', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : []));
9953
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
9954
+ rows = input(3, ...(ngDevMode ? [{ debugName: "rows" }] : []));
9955
+ /** Come nel formula editor: `change` dove mutare il documento ridisegna mezza schermata. */
9956
+ commitOn = input('input', ...(ngDevMode ? [{ debugName: "commitOn" }] : []));
9957
+ textChange = output();
9958
+ area = viewChild('area', ...(ngDevMode ? [{ debugName: "area" }] : []));
9959
+ /**
9960
+ * Il testo che si sta scrivendo. Non e' legato al DOM — la casella tiene il suo valore e la
9961
+ * `[value]` resta quella del documento — perche' riscrivere l'attributo a ogni tasto sposta il
9962
+ * cursore quando si corregge in mezzo alla frase.
9963
+ */
9964
+ draft = signal('', ...(ngDevMode ? [{ debugName: "draft" }] : []));
9965
+ _result = signal(null, ...(ngDevMode ? [{ debugName: "_result" }] : []));
9966
+ _isChecking = signal(false, ...(ngDevMode ? [{ debugName: "_isChecking" }] : []));
9967
+ timer;
9968
+ /** Cresce a ogni richiesta: scarta le risposte di verifiche già superate. */
9969
+ sequence = 0;
9970
+ result = this._result.asReadonly();
9971
+ isChecking = this._isChecking.asReadonly();
9972
+ // -------------------------------------------------------------------------
9973
+ // I nomi inseribili
9974
+ // -------------------------------------------------------------------------
9975
+ references = signal([], ...(ngDevMode ? [{ debugName: "references" }] : []));
9976
+ /** Il documento per cui l'elenco e' stato chiesto: evita una richiesta a ogni apertura. */
9977
+ loadedFor = null;
9978
+ isPickerOpen = signal(false, ...(ngDevMode ? [{ debugName: "isPickerOpen" }] : []));
9979
+ filter = signal('', ...(ngDevMode ? [{ debugName: "filter" }] : []));
9980
+ /**
9981
+ * Cio' che si sta scrivendo **dentro** un'isola aperta, se il cursore ci sta dentro: e' la
9982
+ * seconda strada per inserire un nome, e la piu' rapida — chi ha scritto `{!Ord` non va a
9983
+ * cercare un bottone.
9984
+ */
9985
+ token = signal(null, ...(ngDevMode ? [{ debugName: "token" }] : []));
9986
+ highlighted = signal(0, ...(ngDevMode ? [{ debugName: "highlighted" }] : []));
9987
+ suggestions = computed(() => {
9988
+ const token = this.token();
9989
+ if (!token) {
9990
+ return [];
9991
+ }
9992
+ const needle = token.text.toLowerCase();
9993
+ const matches = this.references().filter((reference) => reference.name.toLowerCase().startsWith(needle));
9994
+ if (matches.length === 1 && matches[0].name.toLowerCase() === needle) {
9995
+ return [];
9996
+ }
9997
+ return matches.slice(0, 8);
9998
+ }, ...(ngDevMode ? [{ debugName: "suggestions" }] : []));
9999
+ /** L'elenco del bottone: tutti i nomi, filtrati dalla ricerca. */
10000
+ pickerOptions = computed(() => {
10001
+ const needle = this.filter().trim().toLowerCase();
10002
+ const all = this.references();
10003
+ return (needle ? all.filter((entry) => entry.name.toLowerCase().includes(needle)) : all).slice(0, 60);
10004
+ }, ...(ngDevMode ? [{ debugName: "pickerOptions" }] : []));
10005
+ // -------------------------------------------------------------------------
10006
+ // Esito della verifica
10007
+ // -------------------------------------------------------------------------
10008
+ /** Un testo senza segnaposto non si manda: non c'e' niente da verificare (§5.12). */
10009
+ hasMergeFields = computed(() => this.draft().includes(OPEN), ...(ngDevMode ? [{ debugName: "hasMergeFields" }] : []));
10010
+ issues = computed(() => this._result()?.issues ?? [], ...(ngDevMode ? [{ debugName: "issues" }] : []));
10011
+ hasErrors = computed(() => this.issues().some((issue) => severityBucket(issue.severity) === 'Error'), ...(ngDevMode ? [{ debugName: "hasErrors" }] : []));
10012
+ isConfirmed = computed(() => {
10013
+ const result = this._result();
10014
+ return !!result && result.isVerified && result.isValid && !this.issues().length;
10015
+ }, ...(ngDevMode ? [{ debugName: "isConfirmed" }] : []));
10016
+ /** Il motore c'e' come primitiva ma non come motore: si dice, non si tace (§6.3). */
10017
+ isUnverified = computed(() => {
10018
+ const result = this._result();
10019
+ return !!result && !result.isVerified;
10020
+ }, ...(ngDevMode ? [{ debugName: "isUnverified" }] : []));
10021
+ constructor() {
10022
+ effect(() => {
10023
+ const text = this.text();
10024
+ this.usage();
10025
+ untracked(() => {
10026
+ this.draft.set(text);
10027
+ this.schedule(text);
10028
+ });
10029
+ });
10030
+ this.destroyRef.onDestroy(() => this.cancel());
10031
+ }
10032
+ onInput(value) {
10033
+ this.draft.set(value);
10034
+ this.schedule(value);
10035
+ this.refreshToken();
10036
+ if (this.commitOn() === 'input') {
10037
+ this.textChange.emit(value);
10038
+ }
10039
+ }
10040
+ onCommit(value) {
10041
+ if (this.commitOn() === 'change') {
10042
+ this.textChange.emit(value);
10043
+ }
10044
+ }
10045
+ onFocus() {
10046
+ void this.loadReferences();
10047
+ this.refreshToken();
10048
+ }
10049
+ onCaretMove() {
10050
+ this.refreshToken();
10051
+ }
10052
+ onBlur() {
10053
+ this.token.set(null);
10054
+ }
10055
+ /**
10056
+ * Le frecce scorrono i suggerimenti e Invio inserisce — ma **solo** quando ce ne sono: in un
10057
+ * testo Invio deve restare Invio, che qui e' un a capo dentro il messaggio.
10058
+ */
10059
+ onKeyDown(event) {
10060
+ const options = this.suggestions();
10061
+ if (!options.length) {
10062
+ return;
10063
+ }
10064
+ if (event.key === 'Escape') {
10065
+ this.token.set(null);
10066
+ event.stopPropagation();
10067
+ return;
10068
+ }
10069
+ if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
10070
+ const step = event.key === 'ArrowDown' ? 1 : -1;
10071
+ this.highlighted.update((index) => (index + step + options.length) % options.length);
10072
+ event.preventDefault();
10073
+ return;
10074
+ }
10075
+ if (event.key === 'Enter' || event.key === 'Tab') {
10076
+ this.complete(options[this.highlighted()]?.name ?? '');
10077
+ event.preventDefault();
10078
+ }
10079
+ }
10080
+ togglePicker() {
10081
+ const open = !this.isPickerOpen();
10082
+ this.isPickerOpen.set(open);
10083
+ if (open) {
10084
+ void this.loadReferences();
10085
+ }
10086
+ }
10087
+ /**
10088
+ * Il bottone: scrive un'isola **intera** al punto del cursore, graffe comprese. Inserire il
10089
+ * solo nome lascerebbe all'utente il compito di ricordarsi la sintassi, che e' esattamente
10090
+ * cio' che il comando esiste per evitare.
10091
+ */
10092
+ insertReference(name) {
10093
+ const element = this.area()?.nativeElement;
10094
+ if (!element || !name || this.disabled()) {
10095
+ return;
10096
+ }
10097
+ const snippet = `${OPEN}${name}}`;
10098
+ const start = element.selectionStart ?? element.value.length;
10099
+ const end = element.selectionEnd ?? start;
10100
+ const next = element.value.slice(0, start) + snippet + element.value.slice(end);
10101
+ const caret = start + snippet.length;
10102
+ this.write(next, caret);
10103
+ this.isPickerOpen.set(false);
10104
+ }
10105
+ /**
10106
+ * Il suggerimento dentro un'isola aperta: **completa** il nome e chiude la graffa.
10107
+ *
10108
+ * Non e' {@link insertReference}, ed e' la distinzione da non perdere: lì si scrive un'isola
10109
+ * nuova al punto del cursore, qui si sta già scrivendo dentro una — passare per l'altra strada
10110
+ * produce `{!IdOr{!IdOrdine}`, cioe' due aperture e un segnaposto che non si risolve.
10111
+ */
10112
+ complete(name) {
10113
+ const element = this.area()?.nativeElement;
10114
+ const token = this.token();
10115
+ if (!element || !name || !token) {
10116
+ return;
10117
+ }
10118
+ const after = element.value.slice(token.end);
10119
+ // Se la graffa c'e' già — si sta correggendo un'isola scritta prima — non se ne aggiunge una
10120
+ // seconda: `{!Ordine}}` non e' un segnaposto.
10121
+ const closing = after.startsWith('}') ? '' : '}';
10122
+ const next = element.value.slice(0, token.start) + name + closing + after;
10123
+ this.write(next, token.start + name.length + closing.length);
10124
+ }
10125
+ /** Scrive nella casella e propaga: la casella e' la sorgente del testo mentre si digita. */
10126
+ write(next, caret) {
10127
+ const element = this.area()?.nativeElement;
10128
+ if (!element) {
10129
+ return;
10130
+ }
10131
+ element.value = next;
10132
+ element.setSelectionRange(caret, caret);
10133
+ element.focus();
10134
+ this.token.set(null);
10135
+ this.onInput(next);
10136
+ // Con `commitOn="change"` il `change` del DOM non scatta su una scrittura da codice.
10137
+ this.onCommit(next);
10138
+ }
10139
+ async loadReferences() {
10140
+ const document = this.store.document();
10141
+ if (this.loadedFor === document) {
10142
+ return;
10143
+ }
10144
+ this.loadedFor = document;
10145
+ try {
10146
+ this.references.set(await this.api.getReferences(document));
10147
+ }
10148
+ catch {
10149
+ // Elenco non disponibile: nessuna proposta, nessuna accusa. Il segnaposto si scrive lo
10150
+ // stesso — l'inserimento e' un aiuto, non un vincolo.
10151
+ this.references.set([]);
10152
+ }
10153
+ }
10154
+ /**
10155
+ * La parola sotto il cursore, **solo dentro un'isola aperta**: fuori dalle graffe si sta
10156
+ * scrivendo una frase, e proporre nomi di variabili a ogni parola sarebbe rumore.
10157
+ */
10158
+ refreshToken() {
10159
+ const element = this.area()?.nativeElement;
10160
+ if (!element) {
10161
+ return;
10162
+ }
10163
+ const caret = element.selectionStart ?? 0;
10164
+ const before = element.value.slice(0, caret);
10165
+ const open = before.lastIndexOf(OPEN);
10166
+ // Un'isola già chiusa prima del cursore non conta: il `}` termina il segnaposto.
10167
+ if (open < 0 || before.indexOf('}', open) >= 0) {
10168
+ this.token.set(null);
10169
+ return;
10170
+ }
10171
+ const text = before.slice(open + OPEN.length);
10172
+ if (/[^A-Za-z0-9_.$]/.test(text)) {
10173
+ this.token.set(null);
10174
+ return;
10175
+ }
10176
+ this.token.set({ text, start: caret - text.length, end: caret });
10177
+ this.highlighted.set(0);
10178
+ }
10179
+ severityOf(issue) {
10180
+ return severityBucket(issue.severity);
10181
+ }
10182
+ hasPosition(issue) {
10183
+ return typeof issue.position === 'number' && issue.position >= 0;
10184
+ }
10185
+ /** Il tratto citato dal rilievo, così il messaggio dice *dove* anche senza il salto. */
10186
+ excerptOf(issue) {
10187
+ if (!this.hasPosition(issue)) {
10188
+ return '';
10189
+ }
10190
+ const start = issue.position;
10191
+ const length = typeof issue.length === 'number' && issue.length > 0 ? issue.length : 1;
10192
+ return this.draft().slice(start, start + length);
10193
+ }
10194
+ /** Porta il cursore sul tratto segnalato: e' il payoff di `position`. */
10195
+ goToIssue(issue) {
10196
+ if (!this.hasPosition(issue) || this.disabled()) {
10197
+ return;
10198
+ }
10199
+ const element = this.area()?.nativeElement;
10200
+ if (!element) {
10201
+ return;
10202
+ }
10203
+ const start = issue.position;
10204
+ const length = typeof issue.length === 'number' && issue.length > 0 ? issue.length : 1;
10205
+ element.focus();
10206
+ element.setSelectionRange(Math.min(start, element.value.length), Math.min(start + length, element.value.length));
10207
+ }
10208
+ schedule(text) {
10209
+ this.cancel();
10210
+ // Senza segnaposto non si chiama nessuno, e non si mostra nessun esito: non c'e' niente da
10211
+ // verificare, e una spunta su una frase direbbe che qualcuno l'ha guardata.
10212
+ if (!text.includes(OPEN) || !this.service.isSupported()) {
10213
+ this.sequence += 1;
10214
+ this._result.set(null);
10215
+ this._isChecking.set(false);
10216
+ return;
10217
+ }
10218
+ this._isChecking.set(true);
10219
+ this.timer = setTimeout(() => void this.check(text), DEBOUNCE_MS);
10220
+ }
10221
+ cancel() {
10222
+ if (this.timer) {
10223
+ clearTimeout(this.timer);
10224
+ this.timer = undefined;
10225
+ }
10226
+ }
10227
+ async check(text) {
10228
+ const token = ++this.sequence;
10229
+ // Il documento si manda **in lavorazione**: un messaggio cita le variabili appena aggiunte,
10230
+ // e verificarlo contro il salvato le direbbe inesistenti (§6.3).
10231
+ const result = await this.service.validate({
10232
+ flow: this.store.document(),
10233
+ expression: text,
10234
+ usage: this.usage(),
10235
+ });
10236
+ if (token !== this.sequence) {
10237
+ return;
10238
+ }
10239
+ this._isChecking.set(false);
10240
+ this._result.set(result);
10241
+ }
10242
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: MergeTextEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
10243
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.28", type: MergeTextEditorComponent, isStandalone: true, selector: "fb-merge-text-editor", inputs: { text: { classPropertyName: "text", publicName: "text", isSignal: true, isRequired: false, transformFunction: null }, usage: { classPropertyName: "usage", publicName: "usage", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: false, transformFunction: null }, commitOn: { classPropertyName: "commitOn", publicName: "commitOn", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { textChange: "textChange" }, viewQueries: [{ propertyName: "area", first: true, predicate: ["area"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"fb-merge__bar\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-merge__toggle\"\r\n [class.fb-merge__toggle--on]=\"isPickerOpen()\"\r\n [disabled]=\"disabled()\"\r\n [attr.aria-expanded]=\"isPickerOpen()\"\r\n title=\"Inserisci un riferimento nel testo\"\r\n (click)=\"togglePicker()\"\r\n >\r\n &#123;!&#125; Inserisci riferimento\r\n </button>\r\n</div>\r\n\r\n<textarea\r\n #area\r\n class=\"fb-textarea\"\r\n [class.fb-input--invalid]=\"hasErrors()\"\r\n [value]=\"text()\"\r\n [rows]=\"rows()\"\r\n [disabled]=\"disabled()\"\r\n [placeholder]=\"placeholder()\"\r\n [attr.aria-label]=\"ariaLabel()\"\r\n (input)=\"onInput($any($event.target).value)\"\r\n (change)=\"onCommit($any($event.target).value)\"\r\n (focus)=\"onFocus()\"\r\n (blur)=\"onBlur()\"\r\n (keydown)=\"onKeyDown($event)\"\r\n (keyup)=\"onCaretMove()\"\r\n (click)=\"onCaretMove()\"\r\n></textarea>\r\n\r\n@if (suggestions().length) {\r\n <!--\r\n I nomi proposti mentre si scrive **dentro** un segnaposto aperto: qui si **completa** il nome\r\n e si chiude la graffa, non si inserisce un\u2019isola nuova \u2014 quella e\u2019 l\u2019altra strada, il bottone.\r\n `mousedown` va fermato: il blur della casella arriverebbe prima del click e chiuderebbe\r\n l\u2019elenco.\r\n -->\r\n <ul class=\"fb-merge__hints\" role=\"listbox\" aria-label=\"Riferimenti disponibili\">\r\n @for (reference of suggestions(); track reference.name) {\r\n <li>\r\n <button\r\n type=\"button\"\r\n class=\"fb-merge__hint\"\r\n [class.fb-merge__hint--on]=\"$index === highlighted()\"\r\n role=\"option\"\r\n [attr.aria-selected]=\"$index === highlighted()\"\r\n (mousedown)=\"$event.preventDefault()\"\r\n (click)=\"complete(reference.name)\"\r\n >\r\n <code>{{ reference.name }}</code>\r\n @if (reference.dataType) {\r\n <span class=\"fb-merge__hint-type\">\r\n {{ reference.dataType }}{{ reference.isCollection ? '[]' : '' }}\r\n </span>\r\n }\r\n </button>\r\n </li>\r\n }\r\n </ul>\r\n}\r\n\r\n@if (isPickerOpen()) {\r\n <div class=\"fb-merge__picker\">\r\n <input\r\n type=\"search\"\r\n class=\"fb-input fb-merge__search\"\r\n placeholder=\"Cerca un riferimento\u2026\"\r\n aria-label=\"Cerca un riferimento\"\r\n [value]=\"filter()\"\r\n (input)=\"filter.set($any($event.target).value)\"\r\n />\r\n @for (reference of pickerOptions(); track reference.name) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-merge__option\"\r\n [disabled]=\"disabled()\"\r\n (mousedown)=\"$event.preventDefault()\"\r\n (click)=\"insertReference(reference.name)\"\r\n >\r\n <code>{{ reference.name }}</code>\r\n @if (reference.dataType) {\r\n <span class=\"fb-merge__hint-type\">\r\n {{ reference.dataType }}{{ reference.isCollection ? '[]' : '' }}\r\n </span>\r\n }\r\n </button>\r\n } @empty {\r\n <p class=\"fb-field__hint\">Nessun riferimento corrisponde alla ricerca.</p>\r\n }\r\n </div>\r\n}\r\n\r\n@if (isChecking()) {\r\n <p class=\"fb-field__hint\">Verifica dei segnaposto\u2026</p>\r\n} @else if (issues().length) {\r\n <ul class=\"fb-merge__issues\">\r\n @for (issue of issues(); track $index) {\r\n <li class=\"fb-merge__issue\" [class]=\"'fb-merge__issue--' + severityOf(issue)\">\r\n @if (hasPosition(issue)) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-merge__jump\"\r\n [disabled]=\"disabled()\"\r\n title=\"Seleziona il tratto segnalato\"\r\n (click)=\"goToIssue(issue)\"\r\n >\r\n @if (excerptOf(issue)) {\r\n <code>{{ excerptOf(issue) }}</code>\r\n } @else {\r\n car. {{ issue.position! + 1 }}\r\n }\r\n </button>\r\n }\r\n <span class=\"fb-merge__message\">{{ issue.message }}</span>\r\n <!-- Codice del motore, non della \u00A77: si mostra e non si interpreta. -->\r\n <code class=\"fb-merge__code\">{{ issue.code }}</code>\r\n </li>\r\n }\r\n </ul>\r\n} @else if (isConfirmed()) {\r\n <p class=\"fb-merge__ok\">\u2713 Segnaposto verificati dal motore.</p>\r\n} @else if (isUnverified()) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n Nessun motore di validazione delle formule e\u2019 configurato: i segnaposto non sono stati\r\n controllati.\r\n </p>\r\n}\r\n\r\n<ng-content />\r\n", styles: [":host{display:flex;flex-direction:column;gap:4px}.fb-merge__bar{display:flex;justify-content:flex-end}.fb-merge__toggle{padding:1px 6px;border:1px solid var(--fb-border, #d6dae1);border-radius:3px;background:var(--fb-surface, #fff);color:var(--fb-text-muted, #667085);font:inherit;font-size:10px;cursor:pointer}.fb-merge__toggle--on{border-color:var(--fb-accent, #2f6feb);color:var(--fb-accent, #2f6feb)}.fb-merge__toggle:disabled{opacity:.5;cursor:not-allowed}.fb-merge__hints,.fb-merge__picker{display:flex;flex-direction:column;gap:1px;max-height:180px;margin:0;padding:2px;overflow-y:auto;border:1px solid var(--fb-border, #d6dae1);border-radius:4px;background:var(--fb-surface, #fff);list-style:none}.fb-merge__picker{gap:2px;padding:4px;background:var(--fb-surface-alt, #f8f9fb)}.fb-merge__search{font-size:10px}.fb-merge__hint,.fb-merge__option{display:flex;gap:6px;align-items:baseline;width:100%;padding:2px 5px;border:0;border-radius:3px;background:transparent;color:var(--fb-text, #1d2939);font:inherit;font-size:10px;text-align:left;cursor:pointer}.fb-merge__option{background:var(--fb-surface, #fff)}.fb-merge__hint:hover,.fb-merge__hint--on,.fb-merge__option:hover{background:color-mix(in srgb,var(--fb-accent, #2f6feb) 10%,transparent)}.fb-merge__hint code,.fb-merge__option code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.fb-merge__hint-type{margin-left:auto;color:var(--fb-text-subtle, #98a2b3)}.fb-merge__option:disabled{opacity:.5;cursor:not-allowed}.fb-merge__issues{display:flex;flex-direction:column;gap:3px;margin:0;padding:0;list-style:none}.fb-merge__issue{display:flex;flex-wrap:wrap;gap:5px;align-items:baseline;padding:3px 6px;border-left:3px solid var(--fb-border, #d6dae1);border-radius:3px;background:var(--fb-surface-alt, #f8f9fb);font-size:10px;line-height:1.45}.fb-merge__issue--Error{border-left-color:var(--fb-error, #c9372c);color:var(--fb-error, #c9372c)}.fb-merge__issue--Warning{border-left-color:var(--fb-warning, #b7791f);color:var(--fb-warning, #b7791f)}.fb-merge__issue--Info{border-left-color:var(--fb-accent, #2f6feb);color:var(--fb-text-muted, #667085)}.fb-merge__message{flex:1 1 auto;min-width:0}.fb-merge__jump{padding:0 4px;border:1px solid currentcolor;border-radius:3px;background:transparent;color:inherit;font:inherit;font-size:10px;cursor:pointer}.fb-merge__jump:disabled{opacity:.5;cursor:not-allowed}.fb-merge__jump code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.fb-merge__code{color:var(--fb-text-subtle, #98a2b3);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:9px}.fb-merge__ok{margin:0;font-size:10px;color:var(--fb-success, #22a06b)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
10244
+ }
10245
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: MergeTextEditorComponent, decorators: [{
10246
+ type: Component,
10247
+ args: [{ selector: 'fb-merge-text-editor', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"fb-merge__bar\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-merge__toggle\"\r\n [class.fb-merge__toggle--on]=\"isPickerOpen()\"\r\n [disabled]=\"disabled()\"\r\n [attr.aria-expanded]=\"isPickerOpen()\"\r\n title=\"Inserisci un riferimento nel testo\"\r\n (click)=\"togglePicker()\"\r\n >\r\n &#123;!&#125; Inserisci riferimento\r\n </button>\r\n</div>\r\n\r\n<textarea\r\n #area\r\n class=\"fb-textarea\"\r\n [class.fb-input--invalid]=\"hasErrors()\"\r\n [value]=\"text()\"\r\n [rows]=\"rows()\"\r\n [disabled]=\"disabled()\"\r\n [placeholder]=\"placeholder()\"\r\n [attr.aria-label]=\"ariaLabel()\"\r\n (input)=\"onInput($any($event.target).value)\"\r\n (change)=\"onCommit($any($event.target).value)\"\r\n (focus)=\"onFocus()\"\r\n (blur)=\"onBlur()\"\r\n (keydown)=\"onKeyDown($event)\"\r\n (keyup)=\"onCaretMove()\"\r\n (click)=\"onCaretMove()\"\r\n></textarea>\r\n\r\n@if (suggestions().length) {\r\n <!--\r\n I nomi proposti mentre si scrive **dentro** un segnaposto aperto: qui si **completa** il nome\r\n e si chiude la graffa, non si inserisce un\u2019isola nuova \u2014 quella e\u2019 l\u2019altra strada, il bottone.\r\n `mousedown` va fermato: il blur della casella arriverebbe prima del click e chiuderebbe\r\n l\u2019elenco.\r\n -->\r\n <ul class=\"fb-merge__hints\" role=\"listbox\" aria-label=\"Riferimenti disponibili\">\r\n @for (reference of suggestions(); track reference.name) {\r\n <li>\r\n <button\r\n type=\"button\"\r\n class=\"fb-merge__hint\"\r\n [class.fb-merge__hint--on]=\"$index === highlighted()\"\r\n role=\"option\"\r\n [attr.aria-selected]=\"$index === highlighted()\"\r\n (mousedown)=\"$event.preventDefault()\"\r\n (click)=\"complete(reference.name)\"\r\n >\r\n <code>{{ reference.name }}</code>\r\n @if (reference.dataType) {\r\n <span class=\"fb-merge__hint-type\">\r\n {{ reference.dataType }}{{ reference.isCollection ? '[]' : '' }}\r\n </span>\r\n }\r\n </button>\r\n </li>\r\n }\r\n </ul>\r\n}\r\n\r\n@if (isPickerOpen()) {\r\n <div class=\"fb-merge__picker\">\r\n <input\r\n type=\"search\"\r\n class=\"fb-input fb-merge__search\"\r\n placeholder=\"Cerca un riferimento\u2026\"\r\n aria-label=\"Cerca un riferimento\"\r\n [value]=\"filter()\"\r\n (input)=\"filter.set($any($event.target).value)\"\r\n />\r\n @for (reference of pickerOptions(); track reference.name) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-merge__option\"\r\n [disabled]=\"disabled()\"\r\n (mousedown)=\"$event.preventDefault()\"\r\n (click)=\"insertReference(reference.name)\"\r\n >\r\n <code>{{ reference.name }}</code>\r\n @if (reference.dataType) {\r\n <span class=\"fb-merge__hint-type\">\r\n {{ reference.dataType }}{{ reference.isCollection ? '[]' : '' }}\r\n </span>\r\n }\r\n </button>\r\n } @empty {\r\n <p class=\"fb-field__hint\">Nessun riferimento corrisponde alla ricerca.</p>\r\n }\r\n </div>\r\n}\r\n\r\n@if (isChecking()) {\r\n <p class=\"fb-field__hint\">Verifica dei segnaposto\u2026</p>\r\n} @else if (issues().length) {\r\n <ul class=\"fb-merge__issues\">\r\n @for (issue of issues(); track $index) {\r\n <li class=\"fb-merge__issue\" [class]=\"'fb-merge__issue--' + severityOf(issue)\">\r\n @if (hasPosition(issue)) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-merge__jump\"\r\n [disabled]=\"disabled()\"\r\n title=\"Seleziona il tratto segnalato\"\r\n (click)=\"goToIssue(issue)\"\r\n >\r\n @if (excerptOf(issue)) {\r\n <code>{{ excerptOf(issue) }}</code>\r\n } @else {\r\n car. {{ issue.position! + 1 }}\r\n }\r\n </button>\r\n }\r\n <span class=\"fb-merge__message\">{{ issue.message }}</span>\r\n <!-- Codice del motore, non della \u00A77: si mostra e non si interpreta. -->\r\n <code class=\"fb-merge__code\">{{ issue.code }}</code>\r\n </li>\r\n }\r\n </ul>\r\n} @else if (isConfirmed()) {\r\n <p class=\"fb-merge__ok\">\u2713 Segnaposto verificati dal motore.</p>\r\n} @else if (isUnverified()) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n Nessun motore di validazione delle formule e\u2019 configurato: i segnaposto non sono stati\r\n controllati.\r\n </p>\r\n}\r\n\r\n<ng-content />\r\n", styles: [":host{display:flex;flex-direction:column;gap:4px}.fb-merge__bar{display:flex;justify-content:flex-end}.fb-merge__toggle{padding:1px 6px;border:1px solid var(--fb-border, #d6dae1);border-radius:3px;background:var(--fb-surface, #fff);color:var(--fb-text-muted, #667085);font:inherit;font-size:10px;cursor:pointer}.fb-merge__toggle--on{border-color:var(--fb-accent, #2f6feb);color:var(--fb-accent, #2f6feb)}.fb-merge__toggle:disabled{opacity:.5;cursor:not-allowed}.fb-merge__hints,.fb-merge__picker{display:flex;flex-direction:column;gap:1px;max-height:180px;margin:0;padding:2px;overflow-y:auto;border:1px solid var(--fb-border, #d6dae1);border-radius:4px;background:var(--fb-surface, #fff);list-style:none}.fb-merge__picker{gap:2px;padding:4px;background:var(--fb-surface-alt, #f8f9fb)}.fb-merge__search{font-size:10px}.fb-merge__hint,.fb-merge__option{display:flex;gap:6px;align-items:baseline;width:100%;padding:2px 5px;border:0;border-radius:3px;background:transparent;color:var(--fb-text, #1d2939);font:inherit;font-size:10px;text-align:left;cursor:pointer}.fb-merge__option{background:var(--fb-surface, #fff)}.fb-merge__hint:hover,.fb-merge__hint--on,.fb-merge__option:hover{background:color-mix(in srgb,var(--fb-accent, #2f6feb) 10%,transparent)}.fb-merge__hint code,.fb-merge__option code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.fb-merge__hint-type{margin-left:auto;color:var(--fb-text-subtle, #98a2b3)}.fb-merge__option:disabled{opacity:.5;cursor:not-allowed}.fb-merge__issues{display:flex;flex-direction:column;gap:3px;margin:0;padding:0;list-style:none}.fb-merge__issue{display:flex;flex-wrap:wrap;gap:5px;align-items:baseline;padding:3px 6px;border-left:3px solid var(--fb-border, #d6dae1);border-radius:3px;background:var(--fb-surface-alt, #f8f9fb);font-size:10px;line-height:1.45}.fb-merge__issue--Error{border-left-color:var(--fb-error, #c9372c);color:var(--fb-error, #c9372c)}.fb-merge__issue--Warning{border-left-color:var(--fb-warning, #b7791f);color:var(--fb-warning, #b7791f)}.fb-merge__issue--Info{border-left-color:var(--fb-accent, #2f6feb);color:var(--fb-text-muted, #667085)}.fb-merge__message{flex:1 1 auto;min-width:0}.fb-merge__jump{padding:0 4px;border:1px solid currentcolor;border-radius:3px;background:transparent;color:inherit;font:inherit;font-size:10px;cursor:pointer}.fb-merge__jump:disabled{opacity:.5;cursor:not-allowed}.fb-merge__jump code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.fb-merge__code{color:var(--fb-text-subtle, #98a2b3);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:9px}.fb-merge__ok{margin:0;font-size:10px;color:var(--fb-success, #22a06b)}\n"] }]
10248
+ }], ctorParameters: () => [], propDecorators: { text: [{ type: i0.Input, args: [{ isSignal: true, alias: "text", required: false }] }], usage: [{ type: i0.Input, args: [{ isSignal: true, alias: "usage", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: false }] }], commitOn: [{ type: i0.Input, args: [{ isSignal: true, alias: "commitOn", required: false }] }], textChange: [{ type: i0.Output, args: ["textChange"] }], area: [{ type: i0.ViewChild, args: ['area', { isSignal: true }] }] } });
10249
+
9903
10250
  /**
9904
10251
  * Base comune degli inspector.
9905
10252
  *
@@ -10399,6 +10746,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImpo
10399
10746
  * Il Custom Error **interrompe** l'esecuzione e restituisce i messaggi al chiamante: sono il
10400
10747
  * suo prodotto principale, e almeno uno e' obbligatorio (`CUSTOM_ERROR_WITHOUT_MESSAGES`).
10401
10748
  * Non ha uscite: per questo il form non mostra nessun connector.
10749
+ *
10750
+ * Il messaggio non e' una casella di testo qualunque: supporta i **merge field** `{!...}`, e qui
10751
+ * servono piu' che altrove — «L'ordine e' già stato evaso» senza il numero dell'ordine e' meta'
10752
+ * messaggio. Per questo ci va `fb-merge-text-editor`, che offre l'inserimento di un riferimento e
10753
+ * fa verificare i segnaposto dal motore di formule mentre si scrive; un testo senza `{!` non
10754
+ * viene mandato a nessuno.
10402
10755
  */
10403
10756
  class CustomErrorInspectorComponent extends NodeInspectorBase {
10404
10757
  elementType = 'CustomError';
@@ -10453,11 +10806,11 @@ class CustomErrorInspectorComponent extends NodeInspectorBase {
10453
10806
  });
10454
10807
  }
10455
10808
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: CustomErrorInspectorComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
10456
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.28", type: CustomErrorInspectorComponent, isStandalone: true, selector: "fb-custom-error-inspector", usesInheritance: true, ngImport: i0, template: "<p class=\"fb-callout\">\r\n Questo elemento <strong>interrompe</strong> l\u2019esecuzione e restituisce i messaggi al chiamante. Non ha\r\n uscite: quello che c\u2019e\u2019 dopo non verra\u2019 eseguito.\r\n</p>\r\n\r\n<div class=\"fb-list\">\r\n @for (message of messages(); 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 messaggio\"\r\n (click)=\"removeMessage($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\">Messaggio</label>\r\n <textarea\r\n class=\"fb-textarea\"\r\n [value]=\"message.errorMessage || ''\"\r\n placeholder=\"L\u2019importo supera il limite consentito.\"\r\n (input)=\"setMessage($index, $any($event.target).value)\"\r\n ></textarea>\r\n </div>\r\n\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"message.isFieldError === true\"\r\n (change)=\"setIsFieldError($index, $any($event.target).checked)\"\r\n />\r\n Mostra accanto a un campo\r\n </label>\r\n\r\n @if (message.isFieldError) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Campo</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"message.fieldSelection || ''\"\r\n placeholder=\"Importo\"\r\n (input)=\"setFieldSelection($index, $any($event.target).value)\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n } @empty {\r\n <p class=\"fb-empty\">\r\n Almeno un messaggio e\u2019 obbligatorio: sono il prodotto di questo elemento\r\n (CUSTOM_ERROR_WITHOUT_MESSAGES).\r\n </p>\r\n }\r\n</div>\r\n\r\n<button type=\"button\" class=\"fb-btn\" (click)=\"addMessage()\">Aggiungi messaggio</button>\r\n", changeDetection: i0.ChangeDetectionStrategy.OnPush });
10809
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.28", type: CustomErrorInspectorComponent, isStandalone: true, selector: "fb-custom-error-inspector", usesInheritance: true, ngImport: i0, template: "<p class=\"fb-callout\">\r\n Questo elemento <strong>interrompe</strong> l\u2019esecuzione e restituisce i messaggi al chiamante. Non ha\r\n uscite: quello che c\u2019e\u2019 dopo non verra\u2019 eseguito.\r\n</p>\r\n\r\n<div class=\"fb-list\">\r\n @for (message of messages(); 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 messaggio\"\r\n (click)=\"removeMessage($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\">Messaggio</label>\r\n <!--\r\n \u00A75.12 \u2014 il messaggio supporta i merge field: l\u2019editor offre l\u2019inserimento di un\r\n riferimento e li fa verificare dal motore mentre si scrive.\r\n -->\r\n <fb-merge-text-editor\r\n [text]=\"message.errorMessage || ''\"\r\n placeholder=\"L\u2019ordine {{ '{' }}!Ordine.Numero} e\u2019 gi\u00E0 stato evaso.\"\r\n ariaLabel=\"Messaggio d\u2019errore\"\r\n [rows]=\"2\"\r\n (textChange)=\"setMessage($index, $event)\"\r\n >\r\n <p class=\"fb-field__hint\">\r\n Fra <code>&#123;!</code> e <code>&#125;</code> ci va un riferimento: il runtime lo\r\n sostituisce quando l\u2019errore scatta.\r\n </p>\r\n </fb-merge-text-editor>\r\n </div>\r\n\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"message.isFieldError === true\"\r\n (change)=\"setIsFieldError($index, $any($event.target).checked)\"\r\n />\r\n Mostra accanto a un campo\r\n </label>\r\n\r\n @if (message.isFieldError) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Campo</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"message.fieldSelection || ''\"\r\n placeholder=\"Importo\"\r\n (input)=\"setFieldSelection($index, $any($event.target).value)\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n } @empty {\r\n <p class=\"fb-empty\">\r\n Almeno un messaggio e\u2019 obbligatorio: sono il prodotto di questo elemento\r\n (CUSTOM_ERROR_WITHOUT_MESSAGES).\r\n </p>\r\n }\r\n</div>\r\n\r\n<button type=\"button\" class=\"fb-btn\" (click)=\"addMessage()\">Aggiungi messaggio</button>\r\n", dependencies: [{ kind: "component", type: MergeTextEditorComponent, selector: "fb-merge-text-editor", inputs: ["text", "usage", "placeholder", "ariaLabel", "disabled", "rows", "commitOn"], outputs: ["textChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
10457
10810
  }
10458
10811
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: CustomErrorInspectorComponent, decorators: [{
10459
10812
  type: Component,
10460
- args: [{ selector: 'fb-custom-error-inspector', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<p class=\"fb-callout\">\r\n Questo elemento <strong>interrompe</strong> l\u2019esecuzione e restituisce i messaggi al chiamante. Non ha\r\n uscite: quello che c\u2019e\u2019 dopo non verra\u2019 eseguito.\r\n</p>\r\n\r\n<div class=\"fb-list\">\r\n @for (message of messages(); 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 messaggio\"\r\n (click)=\"removeMessage($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\">Messaggio</label>\r\n <textarea\r\n class=\"fb-textarea\"\r\n [value]=\"message.errorMessage || ''\"\r\n placeholder=\"L\u2019importo supera il limite consentito.\"\r\n (input)=\"setMessage($index, $any($event.target).value)\"\r\n ></textarea>\r\n </div>\r\n\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"message.isFieldError === true\"\r\n (change)=\"setIsFieldError($index, $any($event.target).checked)\"\r\n />\r\n Mostra accanto a un campo\r\n </label>\r\n\r\n @if (message.isFieldError) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Campo</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"message.fieldSelection || ''\"\r\n placeholder=\"Importo\"\r\n (input)=\"setFieldSelection($index, $any($event.target).value)\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n } @empty {\r\n <p class=\"fb-empty\">\r\n Almeno un messaggio e\u2019 obbligatorio: sono il prodotto di questo elemento\r\n (CUSTOM_ERROR_WITHOUT_MESSAGES).\r\n </p>\r\n }\r\n</div>\r\n\r\n<button type=\"button\" class=\"fb-btn\" (click)=\"addMessage()\">Aggiungi messaggio</button>\r\n" }]
10813
+ args: [{ selector: 'fb-custom-error-inspector', standalone: true, imports: [MergeTextEditorComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<p class=\"fb-callout\">\r\n Questo elemento <strong>interrompe</strong> l\u2019esecuzione e restituisce i messaggi al chiamante. Non ha\r\n uscite: quello che c\u2019e\u2019 dopo non verra\u2019 eseguito.\r\n</p>\r\n\r\n<div class=\"fb-list\">\r\n @for (message of messages(); 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 messaggio\"\r\n (click)=\"removeMessage($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\">Messaggio</label>\r\n <!--\r\n \u00A75.12 \u2014 il messaggio supporta i merge field: l\u2019editor offre l\u2019inserimento di un\r\n riferimento e li fa verificare dal motore mentre si scrive.\r\n -->\r\n <fb-merge-text-editor\r\n [text]=\"message.errorMessage || ''\"\r\n placeholder=\"L\u2019ordine {{ '{' }}!Ordine.Numero} e\u2019 gi\u00E0 stato evaso.\"\r\n ariaLabel=\"Messaggio d\u2019errore\"\r\n [rows]=\"2\"\r\n (textChange)=\"setMessage($index, $event)\"\r\n >\r\n <p class=\"fb-field__hint\">\r\n Fra <code>&#123;!</code> e <code>&#125;</code> ci va un riferimento: il runtime lo\r\n sostituisce quando l\u2019errore scatta.\r\n </p>\r\n </fb-merge-text-editor>\r\n </div>\r\n\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"message.isFieldError === true\"\r\n (change)=\"setIsFieldError($index, $any($event.target).checked)\"\r\n />\r\n Mostra accanto a un campo\r\n </label>\r\n\r\n @if (message.isFieldError) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Campo</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"message.fieldSelection || ''\"\r\n placeholder=\"Importo\"\r\n (input)=\"setFieldSelection($index, $any($event.target).value)\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n } @empty {\r\n <p class=\"fb-empty\">\r\n Almeno un messaggio e\u2019 obbligatorio: sono il prodotto di questo elemento\r\n (CUSTOM_ERROR_WITHOUT_MESSAGES).\r\n </p>\r\n }\r\n</div>\r\n\r\n<button type=\"button\" class=\"fb-btn\" (click)=\"addMessage()\">Aggiungi messaggio</button>\r\n" }]
10461
10814
  }] });
10462
10815
 
10463
10816
  /**
@@ -17487,5 +17840,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImpo
17487
17840
  * Generated bundle index. Do not edit.
17488
17841
  */
17489
17842
 
17490
- export { ConditionEditorComponent, ConnectorEditorComponent, DebugPanelComponent, DynamicScreenInspectorComponent, ENUM_CHOICE_SET_DEFAULT_DISPLAY_FIELD, ENUM_CHOICE_SET_DEFAULT_VALUE_FIELD, ENUM_CHOICE_SET_FIELDS, ElementDialogComponent, ElementInspectorComponent, ElementPaletteComponent, EnumValuePickerComponent, FALLBACK_COLLECTION_BY_TYPE, FALLBACK_TYPE_LABEL, FLOW_BUILDER_HTTP_CONFIG, FLOW_CLIPBOARD_KIND, FLOW_CLIPBOARD_VERSION, FLOW_ELEMENT_ICONS, FLOW_ELEMENT_VARIANT_FIELDS, FLOW_ELEMENT_VARIANT_ICONS, FLOW_ERROR_FALLBACK_MESSAGE, FLOW_ERROR_HTTP_STATUS, FLOW_FOLD_HEIGHT, FLOW_FOLD_WIDTH, FLOW_GROUP_COLORS, FLOW_NAME_PATTERN, FLOW_NODE_COLLECTIONS, FLOW_NODE_HEIGHT, FLOW_NODE_WIDTH, FLOW_REFERENCE_FIELDS, FLOW_RESOURCE_COLLECTIONS, FLOW_VALUE_FIELDS, FLOW_VALUE_LITERAL_FIELDS, FieldAssignmentEditorComponent, FieldPickerComponent, FieldValuePickerComponent, FlowApiError, FlowBuilderApi, FlowBuilderComponent, FlowCanvasComponent, FlowCatalogStore, FlowClipboardService, FlowDictionaryStore, FlowDocumentStore, FlowEditorSession, FlowLayoutService, FlowValidationStore, FormulaEditorComponent, FormulaValidationService, GROUP_HEADER_HEIGHT, GROUP_MIN_HEIGHT, GROUP_MIN_WIDTH, GROUP_PADDING, GroupPanelComponent, HttpFlowBuilderApi, NamePickerComponent, NodeInspectorBase, ORCHESTRATION_CONDITION_OUTPUT, ObjectPickerComponent, OrchestratedStageInspectorComponent, ParameterEditorComponent, PasteDialogComponent, ProblemsPanelComponent, RecordFilterEditorComponent, ReferencePickerComponent, ResourceDialogComponent, ResourceFormComponent, ResourcePanelComponent, RunDialogComponent, SEVERITY_BUCKETS, SEVERITY_ICON, SEVERITY_LABEL, START_NODE_NAME, SelectValueDirective, StartInspectorComponent, StructureMemberPickerComponent, StructurePickerComponent, TYPES_WITH_AUTOMATIC_OUTPUT, TYPE_BY_COLLECTION, UNSUPPORTED_TYPES, ValueEditorComponent, VersionPanelComponent, allFieldsOf, applyPaste, areTypesComparable, boundsAround, buildClipboardPayload, canvasFoldId, canvasGroupId, canvasNodeId, checkConditionLogic, checkFlowName, choiceSetSourceOf, declaredChoiceSetSources, describeFieldPath, describePathEntry, duplicateField, elementIcon, emptyFlowDefinition, enumChoiceSetFieldType, fieldAt, filterReferences, flattenFields, flowNodeWidth, flowNodeWidthClass, foldSourceConnectorId, foldTargetConnectorId, groupColorClass, groupRect, hasAutoSize, innerNamesOf, insertField, isClipboardPayload, isCustomConditionLogic, isEmptyReferenceFilter, isFieldResource, isFoldReference, isGlobalReference, isNumericType, isPathInside, isTypeCheckedOperator, isUnknownEnumChoiceSetField, isValidFlowName, isValued, loadPathLevel, matchesReferenceFilter, membersInside, moveCondition, moveField, navigatePath, otherSourceFieldsOf, outletByKey, outletsOf, parseCanvasFoldId, parseCanvasGroupId, parseCanvasNodeId, parseFieldPath, parseInvariantNumber, parseSourceConnectorId, parseTargetConnectorId, pathAvailableNames, pathContainerLabel, pathKey, pathNotVerifiableMessage, planPaste, recomputeMembers, referenceRoot, referencedRootsOf, remapConditionLogic, removeCondition, removeField, resolvePath, rewriteReferences, sameMembers, samePath, screenActionNames, screenFieldNames, severityBucket, slugifyFlowName, sourceConnectorId, stageStepNames, stageStepOutputReferenced, stepsOf, targetConnectorId, typeOfCollection, uniqueFlowName, valuedFieldOf, valuedFieldsOf, variantFieldOf, variantOf, variantPresetOf };
17843
+ export { ConditionEditorComponent, ConnectorEditorComponent, DebugPanelComponent, DynamicScreenInspectorComponent, ENUM_CHOICE_SET_DEFAULT_DISPLAY_FIELD, ENUM_CHOICE_SET_DEFAULT_VALUE_FIELD, ENUM_CHOICE_SET_FIELDS, ElementDialogComponent, ElementInspectorComponent, ElementPaletteComponent, EnumValuePickerComponent, FALLBACK_COLLECTION_BY_TYPE, FALLBACK_TYPE_LABEL, FLOW_BUILDER_HTTP_CONFIG, FLOW_CLIPBOARD_KIND, FLOW_CLIPBOARD_VERSION, FLOW_ELEMENT_ICONS, FLOW_ELEMENT_VARIANT_FIELDS, FLOW_ELEMENT_VARIANT_ICONS, FLOW_ERROR_FALLBACK_MESSAGE, FLOW_ERROR_HTTP_STATUS, FLOW_FOLD_HEIGHT, FLOW_FOLD_WIDTH, FLOW_GROUP_COLORS, FLOW_NAME_PATTERN, FLOW_NODE_COLLECTIONS, FLOW_NODE_HEIGHT, FLOW_NODE_WIDTH, FLOW_REFERENCE_FIELDS, FLOW_RESOURCE_COLLECTIONS, FLOW_VALUE_FIELDS, FLOW_VALUE_LITERAL_FIELDS, FieldAssignmentEditorComponent, FieldPickerComponent, FieldValuePickerComponent, FlowApiError, FlowBuilderApi, FlowBuilderComponent, FlowCanvasComponent, FlowCatalogStore, FlowClipboardService, FlowDictionaryStore, FlowDocumentStore, FlowEditorSession, FlowLayoutService, FlowValidationStore, FormulaEditorComponent, FormulaValidationService, GROUP_HEADER_HEIGHT, GROUP_MIN_HEIGHT, GROUP_MIN_WIDTH, GROUP_PADDING, GroupPanelComponent, HttpFlowBuilderApi, MergeTextEditorComponent, NamePickerComponent, NodeInspectorBase, ORCHESTRATION_CONDITION_OUTPUT, ObjectPickerComponent, OrchestratedStageInspectorComponent, ParameterEditorComponent, PasteDialogComponent, ProblemsPanelComponent, RecordFilterEditorComponent, ReferencePickerComponent, ResourceDialogComponent, ResourceFormComponent, ResourcePanelComponent, RunDialogComponent, SEVERITY_BUCKETS, SEVERITY_ICON, SEVERITY_LABEL, START_NODE_NAME, SelectValueDirective, StartInspectorComponent, StructureMemberPickerComponent, StructurePickerComponent, TYPES_WITH_AUTOMATIC_OUTPUT, TYPE_BY_COLLECTION, UNSUPPORTED_TYPES, ValueEditorComponent, VersionPanelComponent, allFieldsOf, applyPaste, areTypesComparable, boundsAround, buildClipboardPayload, canvasFoldId, canvasGroupId, canvasNodeId, checkConditionLogic, checkFlowName, choiceSetSourceOf, declaredChoiceSetSources, describeFieldPath, describePathEntry, duplicateField, elementIcon, emptyFlowDefinition, enumChoiceSetFieldType, fieldAt, filterReferences, flattenFields, flowNodeWidth, flowNodeWidthClass, foldSourceConnectorId, foldTargetConnectorId, groupColorClass, groupRect, hasAutoSize, innerNamesOf, insertField, isClipboardPayload, isCustomConditionLogic, isEmptyReferenceFilter, isFieldResource, isFoldReference, isGlobalReference, isNumericType, isPathInside, isTypeCheckedOperator, isUnknownEnumChoiceSetField, isValidFlowName, isValued, loadPathLevel, matchesReferenceFilter, membersInside, moveCondition, moveField, navigatePath, otherSourceFieldsOf, outletByKey, outletsOf, parseCanvasFoldId, parseCanvasGroupId, parseCanvasNodeId, parseFieldPath, parseInvariantNumber, parseSourceConnectorId, parseTargetConnectorId, pathAvailableNames, pathContainerLabel, pathKey, pathNotVerifiableMessage, planPaste, recomputeMembers, referenceRoot, referencedRootsOf, remapConditionLogic, removeCondition, removeField, resolvePath, rewriteReferences, sameMembers, samePath, screenActionNames, screenFieldNames, severityBucket, slugifyFlowName, sourceConnectorId, stageStepNames, stageStepOutputReferenced, stepsOf, targetConnectorId, typeOfCollection, uniqueFlowName, valuedFieldOf, valuedFieldsOf, variantFieldOf, variantOf, variantPresetOf };
17491
17844
  //# sourceMappingURL=esfaenza-flow-builder.mjs.map