@esfaenza/flow-builder 20.3.29 → 20.3.31

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
@@ -144,6 +144,7 @@ sono opzionali e rifiutano con `MissingService` se non sovrascritti.
144
144
  | `listEnumTypes`, `listEvents`, `listSubflowCandidates` | astratti |
145
145
  | `describeObject` | opzionale |
146
146
  | `listFieldValues(object, field)` | opzionale — i valori ammessi di un campo a **scelta chiusa** (`hasClosedValueSet`, §6.4), cioe' l'altra meta' degli insiemi chiusi: quelli di un `Enum` sono del **tipo** (`listEnumValues`), questi della **colonna** — `Ordine.Stato` e `Attivita.Stato` possono ammettere valori diversi pur essendo due `String`, ed e' perche' serve la coppia. Lista vuota = "non lo so": il valore si scrive a mano |
147
+ | `listFormulaFunctions(usage?)` | opzionale — le funzioni (e gli operatori, se il motore li dichiara) scrivibili in un'espressione: e' la **palette** di `<fb-formula-editor>`, la terza gamba accanto ai nomi (`getReferences`) e al giudizio (`validateFormula`). `usage` e' lo stesso di `validateFormula` e un motore che non filtra risponde comunque tutto, che e' legittimo. Due regole: lista vuota = «il motore non le dichiara» e la palette non compare (§4.6), e l'elenco **non e' autoritativo** — accusare una funzione fuori catalogo sarebbe un rilievo falso su una formula valida |
147
148
  | `listEnumValues(enumType)` | opzionale — i **valori** di un tipo di enumerazione, cioe' i nomi scrivibili in `enumValue` (§4.2, §4.6). `listEnumTypes` e' l'altra meta' e non basta: quella dice quali tipi esistono, questa quali valori ha un tipo. `enumType` e' esattamente l'`objectType` della risorsa, del parametro o del **membro** di una classe (§4.7.1): la risposta e' del tipo, non del punto di uso, e la cache e' per nome del tipo. Lista vuota = "non lo so" — il valore si scrive a mano, e un tipo sconosciuto risponde vuoto, non 404 |
148
149
  | `listStructures`, `describeStructure(className)` | opzionali — le classi e i loro membri per le risorse `Structure` (§4.7). `listStructures` e' l'**elenco delle classi** senza membri; `describeStructure` restituisce la **descrizione** di una classe (`className`, `label`, `members`), non un array nudo (§4.7.1). Senza, le classi non sono verificabili e il nome si scrive a mano: "non lo so", non "non esiste". I tre esiti sono distinti: `members` popolato = elenco autorevole, `members: []` = membri non dichiarati (si digita, nessuna accusa), rifiuto `StructureNotFound` = classe non registrata (si segnala la **classe**) |
149
150
 
@@ -463,7 +464,15 @@ salto, `position` resterebbe un numero da contare a mano. Lo stesso componente o
463
464
  citabili come **autocompletamento** mentre si scrive un identificatore: la fonte e'
464
465
  `POST /flows/references`, la stessa del reference picker, output automatici compresi; frecce per
465
466
  scorrere, Invio o clic per inserire. Elenco non disponibile = nessun suggerimento e nessuna
466
- accusa — la formula si scrive lo stesso. Lo usano tutti i punti in cui si scrive
467
+ accusa — la formula si scrive lo stesso. La terza gamba e' la **palette**: `listFormulaFunctions`
468
+ (`GET /catalog/formula-functions?usage=`, §6.3) dice cosa si puo' scrivere in mezzo ai nomi, e il
469
+ componente la apre da un bottone, raggruppata per categoria e filtrabile; scegliere una voce
470
+ inserisce il suo `snippet` — assente, il nome — e porta il cursore **dentro** la prima parentesi.
471
+ Due cose che non si devono confondere: elenco vuoto significa «il motore non le dichiara», quindi
472
+ il bottone non compare affatto invece di aprire un pannello vuoto; e l'elenco **non e'
473
+ autoritativo** — a giudicare resta `validateFormula`, e accusare una funzione fuori catalogo
474
+ sarebbe un rilievo falso su una formula valida, perche' un motore puo' supportare piu' di quanto
475
+ dichiara. `signature` e `description` si mostrano e non si parsano: la grammatica resta del motore. Lo usano tutti i punti in cui si scrive
467
476
  un'espressione: la risorsa `Formula`, la modalita' Formula di una condizione, la `filterFormula`
468
477
  di un filtro, la regola di validazione di un campo di screen dinamico e il `formulaExpression`
469
478
  dentro un valore. A ogni punto corrispondono un `usage` e un `expectedDataType` — `Boolean` per
@@ -218,6 +218,30 @@ class FlowBuilderApi {
218
218
  void field;
219
219
  return missing('listFieldValues');
220
220
  }
221
+ /**
222
+ * `GET /catalog/formula-functions?usage=` — le funzioni (e gli operatori, se il motore li
223
+ * dichiara) scrivibili in un'espressione: la **palette** dell'editor di formule (§6.3, §6.4).
224
+ *
225
+ * È la terza gamba dell'editor di formule, e le altre due non la sostituiscono: i **nomi**
226
+ * citabili li da' {@link getReferences}, il **giudizio** sull'espressione
227
+ * {@link validateFormula}, e questa dice cosa si puo' scrivere in mezzo.
228
+ *
229
+ * `usage` chiede le sole funzioni che hanno senso in quel punto — lo stesso valore che va in
230
+ * {@link validateFormula}. Un motore che non filtra risponde comunque l'elenco intero, che e'
231
+ * legittimo: chi mostra non deve dedurre niente dal fatto che una voce ci sia.
232
+ *
233
+ * Due regole, e non sono dettagli. La lista **vuota** significa «il motore non le dichiara»,
234
+ * non «non ce ne sono» (§4.6): si mostra il campo di testo libero, non una palette vuota. E
235
+ * l'elenco **non e' autoritativo**: accusare una funzione che non c'e' produrrebbe un rilievo
236
+ * falso su una formula valida, perche' un motore puo' supportare piu' di quanto dichiara.
237
+ *
238
+ * Opzionale come ogni catalogo che dipende da un servizio dell'host: assente, l'editor di
239
+ * formule resta quello di prima — nomi e verifica, senza palette.
240
+ */
241
+ listFormulaFunctions(usage) {
242
+ void usage;
243
+ return missing('listFormulaFunctions');
244
+ }
221
245
  /**
222
246
  * `GET /catalog/enum-types/{enumType}/values` — i valori di **un** tipo di enumerazione, cioe'
223
247
  * i nomi che si possono scrivere in `enumValue` (§4.2, §4.6). `enumType` e' esattamente
@@ -567,6 +591,13 @@ class HttpFlowBuilderApi extends FlowBuilderApi {
567
591
  listFormParameters(formName) {
568
592
  return this.get(`/flows/editor/forms/${HttpFlowBuilderApi.segment(formName)}/parameters`);
569
593
  }
594
+ /**
595
+ * §6.3 — la palette dell'editor di formule. `usage` e' un filtro, non un obbligo: chi non lo
596
+ * manda ottiene l'elenco intero, ed e' legittimo anche che un motore lo ignori.
597
+ */
598
+ listFormulaFunctions(usage) {
599
+ return this.get('/flows/editor/formula-functions', HttpFlowBuilderApi.params({ usage }));
600
+ }
570
601
  listEnumTypes() {
571
602
  return this.get('/flows/editor/enum-types');
572
603
  }
@@ -3461,6 +3492,7 @@ class FlowCatalogStore {
3461
3492
  entriesCache = new AsyncCache();
3462
3493
  parametersCache = new AsyncCache();
3463
3494
  membersCache = new AsyncCache();
3495
+ formulaFunctionsCache = new AsyncCache();
3464
3496
  /**
3465
3497
  * Una lista vuota significa "non lo so", non "nessuno": con il catalogo non popolato la
3466
3498
  * validazione salta i controlli invece di produrre falsi allarmi (§7). L'editor fa lo
@@ -3516,6 +3548,17 @@ class FlowCatalogStore {
3516
3548
  }
3517
3549
  return this.parametersCache.get(`form|${formName}`, () => this.api.listFormParameters(formName).catch(() => []));
3518
3550
  }
3551
+ /**
3552
+ * §6.3 — la palette dell'editor di formule. La cache e' per **usage**, perche' e' cio' che
3553
+ * cambia la risposta: un motore che filtra risponde due elenchi diversi a `Condition` e a
3554
+ * `Resource`, e una chiave sola li farebbe sovrascrivere a vicenda.
3555
+ *
3556
+ * La primitiva e' opzionale e l'errore diventa lista vuota — che qui significa «il motore non
3557
+ * le dichiara»: nessuna palette e nessuna accusa a una funzione fuori elenco (§4.6, §6.3).
3558
+ */
3559
+ listFormulaFunctions(usage) {
3560
+ return this.formulaFunctionsCache.get(usage ?? '', () => this.api.listFormulaFunctions(usage).then((list) => list ?? []).catch(() => []));
3561
+ }
3519
3562
  listEnumTypes() {
3520
3563
  return this.entriesCache.get('enum-types', () => this.api.listEnumTypes().then((list) => list ?? []));
3521
3564
  }
@@ -3587,6 +3630,7 @@ class FlowCatalogStore {
3587
3630
  this.entriesCache.clear();
3588
3631
  this.parametersCache.clear();
3589
3632
  this.membersCache.clear();
3633
+ this.formulaFunctionsCache.clear();
3590
3634
  }
3591
3635
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: FlowCatalogStore, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3592
3636
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: FlowCatalogStore });
@@ -6838,6 +6882,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImpo
6838
6882
  * la primitiva non esiste proprio: silenzio, mai un esito positivo inventato.
6839
6883
  * - il codice del rilievo e' **del motore**, non della §7: si mostra e non si interpreta.
6840
6884
  *
6885
+ * La terza gamba e' la **palette**: `listFormulaFunctions` (§6.3) dice cosa si puo' scrivere in
6886
+ * mezzo ai nomi. Non e' un elenco autoritativo — a giudicare resta `validateFormula`, quindi una
6887
+ * funzione fuori catalogo non si segnala — e vuoto significa «il motore non le dichiara»: in quel
6888
+ * caso il bottone non compare affatto, invece di aprire un pannello vuoto.
6889
+ *
6841
6890
  * Il debounce e lo stato dell'esito stanno qui, uno per campo; nel servizio sta solo la memoria
6842
6891
  * di «la primitiva non c'e'», che e' l'unica cosa condivisa fra i campi.
6843
6892
  */
@@ -6847,6 +6896,7 @@ class FormulaEditorComponent {
6847
6896
  store = inject(FlowDocumentStore);
6848
6897
  api = inject(FlowBuilderApi);
6849
6898
  service = inject(FormulaValidationService);
6899
+ catalog = inject(FlowCatalogStore);
6850
6900
  destroyRef = inject(DestroyRef);
6851
6901
  expression = input('', ...(ngDevMode ? [{ debugName: "expression" }] : []));
6852
6902
  /** Dove comparira': e' cio' che dice al motore cosa pretendere (§6.3). */
@@ -6907,6 +6957,91 @@ class FormulaEditorComponent {
6907
6957
  }
6908
6958
  return matches.slice(0, 8);
6909
6959
  }, ...(ngDevMode ? [{ debugName: "suggestions" }] : []));
6960
+ // -------------------------------------------------------------------------
6961
+ // La palette delle funzioni (§6.3)
6962
+ // -------------------------------------------------------------------------
6963
+ /**
6964
+ * Le funzioni dichiarate dal motore. L'elenco lo tiene in cache lo store, per `usage`: piu'
6965
+ * editor montati insieme fanno una richiesta sola. Vuoto = non dichiarate, e allora la palette
6966
+ * non esiste — mostrarla vuota farebbe credere che il motore non abbia funzioni.
6967
+ */
6968
+ functions = signal([], ...(ngDevMode ? [{ debugName: "functions" }] : []));
6969
+ isPaletteOpen = signal(false, ...(ngDevMode ? [{ debugName: "isPaletteOpen" }] : []));
6970
+ paletteFilter = signal('', ...(ngDevMode ? [{ debugName: "paletteFilter" }] : []));
6971
+ hasPalette = computed(() => this.functions().length > 0, ...(ngDevMode ? [{ debugName: "hasPalette" }] : []));
6972
+ /**
6973
+ * Le voci raggruppate per `category`. La categoria puo' mancare: quelle senza finiscono in un
6974
+ * gruppo senza titolo, in coda, invece di sparire.
6975
+ */
6976
+ paletteGroups = computed(() => {
6977
+ const needle = this.paletteFilter().trim().toLowerCase();
6978
+ const groups = new Map();
6979
+ for (const entry of this.functions()) {
6980
+ if (needle && !this.matchesFilter(entry, needle)) {
6981
+ continue;
6982
+ }
6983
+ const category = entry.category?.trim() || '';
6984
+ const bucket = groups.get(category);
6985
+ if (bucket) {
6986
+ bucket.push(entry);
6987
+ }
6988
+ else {
6989
+ groups.set(category, [entry]);
6990
+ }
6991
+ }
6992
+ return Array.from(groups, ([category, items]) => ({ category, items })).sort(
6993
+ // Il gruppo senza titolo va in coda: e' il ripiego, non la prima cosa da leggere.
6994
+ (a, b) => (a.category ? 0 : 1) - (b.category ? 0 : 1) || a.category.localeCompare(b.category));
6995
+ }, ...(ngDevMode ? [{ debugName: "paletteGroups" }] : []));
6996
+ matchesFilter(entry, needle) {
6997
+ return (entry.name.toLowerCase().includes(needle) ||
6998
+ (entry.signature ?? '').toLowerCase().includes(needle) ||
6999
+ (entry.description ?? '').toLowerCase().includes(needle));
7000
+ }
7001
+ togglePalette() {
7002
+ this.isPaletteOpen.update((open) => !open);
7003
+ }
7004
+ /** La forma d'uso da mostrare: `signature` se il motore la da', altrimenti il solo nome. */
7005
+ signatureOf(entry) {
7006
+ return entry.signature?.trim() || entry.name;
7007
+ }
7008
+ /**
7009
+ * Inserisce la voce scelta al punto in cui si stava scrivendo. Cosa inserire lo dice `snippet`
7010
+ * — assente, il nome — e il cursore va **dentro** la prima parentesi: la palette serve a non
7011
+ * ricordare la firma, e lasciare il cursore in fondo costringerebbe a tornare indietro a mano.
7012
+ *
7013
+ * Non si scrive niente nel documento oltre al testo: la firma resta del motore, e qui non c'e'
7014
+ * nessun parser che la interpreti.
7015
+ */
7016
+ insertFunction(entry) {
7017
+ const element = this.area()?.nativeElement;
7018
+ if (!element || this.disabled()) {
7019
+ return;
7020
+ }
7021
+ const snippet = entry.snippet?.trim() || entry.name;
7022
+ // Se si stava digitando un nome, la voce lo **sostituisce**: chi scrive `RO` e sceglie
7023
+ // `ROUND(` non vuole `ROROUND(`.
7024
+ const token = this.token();
7025
+ const start = token ? token.start : (element.selectionStart ?? element.value.length);
7026
+ const end = token ? token.end : (element.selectionEnd ?? start);
7027
+ const next = element.value.slice(0, start) + snippet + element.value.slice(end);
7028
+ const opening = snippet.indexOf('(');
7029
+ const caret = start + (opening >= 0 ? opening + 1 : snippet.length);
7030
+ element.value = next;
7031
+ element.setSelectionRange(caret, caret);
7032
+ element.focus();
7033
+ this.token.set(null);
7034
+ this.onInput(next);
7035
+ // Con `commitOn="change"` il `change` del DOM non scatta su una scrittura da codice.
7036
+ this.onCommit(next);
7037
+ }
7038
+ async loadFunctions(usage) {
7039
+ // La primitiva e' opzionale: in errore lo store risponde lista vuota, cioe' «non dichiarate».
7040
+ const list = await this.catalog.listFormulaFunctions(usage);
7041
+ if (this.usage() === usage) {
7042
+ this.functions.set(list);
7043
+ }
7044
+ }
6910
7045
  issues = computed(() => this._result()?.issues ?? [], ...(ngDevMode ? [{ debugName: "issues" }] : []));
6911
7046
  hasErrors = computed(() => this.issues().some((issue) => severityBucket(issue.severity) === 'Error'), ...(ngDevMode ? [{ debugName: "hasErrors" }] : []));
6912
7047
  /**
@@ -6942,6 +7077,12 @@ class FormulaEditorComponent {
6942
7077
  // La verifica riparte quando cambia l'espressione **arrivata da fuori** o cio' che ci si
6943
7078
  // aspetta che produca. Il documento no: si legge al momento della richiesta, perche'
6944
7079
  // tracciarlo rivaliderebbe ogni formula aperta a ogni tasto premuto altrove nell'editor.
7080
+ // La palette dipende solo da `usage`, e la richiesta la condivide lo store: piu' editor
7081
+ // montati insieme non fanno piu' di una chiamata per uso.
7082
+ effect(() => {
7083
+ const usage = this.usage();
7084
+ untracked(() => void this.loadFunctions(usage));
7085
+ });
6945
7086
  effect(() => {
6946
7087
  const expression = this.expression();
6947
7088
  this.usage();
@@ -7129,11 +7270,11 @@ class FormulaEditorComponent {
7129
7270
  }
7130
7271
  }
7131
7272
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: FormulaEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
7132
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.28", type: FormulaEditorComponent, isStandalone: true, selector: "fb-formula-editor", inputs: { expression: { classPropertyName: "expression", publicName: "expression", isSignal: true, isRequired: false, transformFunction: null }, usage: { classPropertyName: "usage", publicName: "usage", isSignal: true, isRequired: false, transformFunction: null }, expectedDataType: { classPropertyName: "expectedDataType", publicName: "expectedDataType", isSignal: true, isRequired: false, transformFunction: null }, scale: { classPropertyName: "scale", publicName: "scale", 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: { expressionChange: "expressionChange" }, viewQueries: [{ propertyName: "area", first: true, predicate: ["area"], descendants: true, isSignal: true }], ngImport: i0, template: "<textarea\r\n #area\r\n class=\"fb-textarea fb-input--mono\"\r\n [class.fb-input--invalid]=\"hasErrors()\"\r\n [value]=\"expression()\"\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 L'elenco dei riferimenti disponibili come autocompletamento (\u00A76.3): stessa fonte del\r\n reference picker, `POST /flows/references`, output automatici compresi. `mousedown` va\r\n fermato \u2014 il blur della casella arriverebbe prima del click e chiuderebbe l'elenco.\r\n -->\r\n <ul class=\"fb-formula__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-formula__hint\"\r\n [class.fb-formula__hint--on]=\"$index === highlighted()\"\r\n role=\"option\"\r\n [attr.aria-selected]=\"$index === highlighted()\"\r\n (mousedown)=\"$event.preventDefault()\"\r\n (click)=\"insert(reference.name)\"\r\n >\r\n <code>{{ reference.name }}</code>\r\n @if (reference.dataType) {\r\n <span class=\"fb-formula__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 (isChecking()) {\r\n <p class=\"fb-field__hint\">Verifica in corso\u2026</p>\r\n} @else if (issues().length) {\r\n <ul class=\"fb-formula__issues\">\r\n @for (issue of issues(); track $index) {\r\n <li class=\"fb-formula__issue\" [class]=\"'fb-formula__issue--' + severityOf(issue)\">\r\n @if (hasPosition(issue)) {\r\n <!--\r\n Il salto e' il senso di `position`: un indice di carattere che l'utente dovrebbe\r\n contare a mano non porta da nessuna parte.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-formula__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-formula__message\">{{ issue.message }}</span>\r\n <!-- Codice del motore, non della \u00A77: si mostra e non si interpreta. -->\r\n <code class=\"fb-formula__code\">{{ issue.code }}</code>\r\n </li>\r\n }\r\n </ul>\r\n} @else if (isConfirmed()) {\r\n <p class=\"fb-formula__ok\">\u2713 Espressione verificata dal motore.</p>\r\n} @else if (isUnverified()) {\r\n <!--\r\n Primitiva s\u00EC, motore no: nessun rilievo **e** nessuna conferma. Dirlo e' l'unico modo di\r\n non far leggere il silenzio come un via libera (\u00A76.3).\r\n -->\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n Nessun motore di validazione delle formule e\u2019 configurato: l\u2019espressione non e\u2019 stata\r\n controllata.\r\n </p>\r\n}\r\n\r\n@if (typeMismatch(); as mismatch) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n L\u2019espressione produce {{ mismatch.produced }}, ma qui serve {{ mismatch.expected }}.\r\n </p>\r\n}\r\n\r\n<ng-content />\r\n", styles: [":host{display:flex;flex-direction:column;gap:4px}.fb-formula__issues{display:flex;flex-direction:column;gap:3px;margin:0;padding:0;list-style:none}.fb-formula__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-formula__issue--Error{border-left-color:var(--fb-error, #c9372c);color:var(--fb-error, #c9372c)}.fb-formula__issue--Warning{border-left-color:var(--fb-warning, #b7791f);color:var(--fb-warning, #b7791f)}.fb-formula__issue--Info{border-left-color:var(--fb-accent, #2f6feb);color:var(--fb-text-muted, #667085)}.fb-formula__message{flex:1 1 auto;min-width:0}.fb-formula__jump{padding:0 4px;border:1px solid currentcolor;border-radius:3px;background:transparent;color:inherit;font:inherit;font-size:10px;cursor:pointer}.fb-formula__jump:disabled{opacity:.5;cursor:not-allowed}.fb-formula__jump code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.fb-formula__code{color:var(--fb-text-subtle, #98a2b3);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:9px}.fb-formula__ok{margin:0;font-size:10px;color:var(--fb-success, #22a06b)}.fb-formula__hints{display:flex;flex-direction:column;gap:1px;max-height:160px;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-formula__hint{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-formula__hint:hover,.fb-formula__hint--on{background:color-mix(in srgb,var(--fb-accent, #2f6feb) 10%,transparent)}.fb-formula__hint code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.fb-formula__hint-type{margin-left:auto;color:var(--fb-text-subtle, #98a2b3)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
7273
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.28", type: FormulaEditorComponent, isStandalone: true, selector: "fb-formula-editor", inputs: { expression: { classPropertyName: "expression", publicName: "expression", isSignal: true, isRequired: false, transformFunction: null }, usage: { classPropertyName: "usage", publicName: "usage", isSignal: true, isRequired: false, transformFunction: null }, expectedDataType: { classPropertyName: "expectedDataType", publicName: "expectedDataType", isSignal: true, isRequired: false, transformFunction: null }, scale: { classPropertyName: "scale", publicName: "scale", 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: { expressionChange: "expressionChange" }, viewQueries: [{ propertyName: "area", first: true, predicate: ["area"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (hasPalette()) {\r\n <!--\r\n La palette compare **solo** se il motore dichiara delle funzioni (\u00A76.3): elenco vuoto\r\n significa \u00ABnon le dichiara\u00BB, e un pannello vuoto farebbe credere che non ce ne siano.\r\n -->\r\n <div class=\"fb-formula__bar\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-formula__toggle\"\r\n [class.fb-formula__toggle--on]=\"isPaletteOpen()\"\r\n [disabled]=\"disabled()\"\r\n [attr.aria-expanded]=\"isPaletteOpen()\"\r\n title=\"Funzioni disponibili nel motore\"\r\n (click)=\"togglePalette()\"\r\n >\r\n <em>f</em>(x) Funzioni\r\n </button>\r\n </div>\r\n}\r\n\r\n<textarea\r\n #area\r\n class=\"fb-textarea fb-input--mono\"\r\n [class.fb-input--invalid]=\"hasErrors()\"\r\n [value]=\"expression()\"\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 L'elenco dei riferimenti disponibili come autocompletamento (\u00A76.3): stessa fonte del\r\n reference picker, `POST /flows/references`, output automatici compresi. `mousedown` va\r\n fermato \u2014 il blur della casella arriverebbe prima del click e chiuderebbe l'elenco.\r\n -->\r\n <ul class=\"fb-formula__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-formula__hint\"\r\n [class.fb-formula__hint--on]=\"$index === highlighted()\"\r\n role=\"option\"\r\n [attr.aria-selected]=\"$index === highlighted()\"\r\n (mousedown)=\"$event.preventDefault()\"\r\n (click)=\"insert(reference.name)\"\r\n >\r\n <code>{{ reference.name }}</code>\r\n @if (reference.dataType) {\r\n <span class=\"fb-formula__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 (hasPalette() && isPaletteOpen()) {\r\n <div class=\"fb-formula__palette\">\r\n <input\r\n type=\"search\"\r\n class=\"fb-input fb-formula__search\"\r\n placeholder=\"Cerca una funzione\u2026\"\r\n aria-label=\"Cerca una funzione\"\r\n [value]=\"paletteFilter()\"\r\n (input)=\"paletteFilter.set($any($event.target).value)\"\r\n />\r\n @if (paletteGroups().length) {\r\n @for (group of paletteGroups(); track $index) {\r\n <div class=\"fb-formula__group\">\r\n @if (group.category) {\r\n <h5 class=\"fb-formula__group-title\">{{ group.category }}</h5>\r\n }\r\n @for (entry of group.items; track entry.name) {\r\n <!--\r\n `mousedown` fermato come nei suggerimenti: il blur della casella arriverebbe prima\r\n del click. `signature` e `description` si mostrano e non si parsano \u2014 la grammatica\r\n resta del motore (\u00A76.3).\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-formula__fn\"\r\n [disabled]=\"disabled()\"\r\n (mousedown)=\"$event.preventDefault()\"\r\n (click)=\"insertFunction(entry)\"\r\n >\r\n <code class=\"fb-formula__fn-sig\">{{ signatureOf(entry) }}</code>\r\n @if (entry.resultDataType) {\r\n <span class=\"fb-formula__fn-type\">\r\n {{ entry.resultDataType }}{{ entry.isCollection ? '[]' : '' }}\r\n </span>\r\n }\r\n @if (entry.description) {\r\n <span class=\"fb-formula__fn-desc\">{{ entry.description }}</span>\r\n }\r\n @if (entry.example) {\r\n <code class=\"fb-formula__fn-example\">{{ entry.example }}</code>\r\n }\r\n </button>\r\n }\r\n </div>\r\n }\r\n } @else {\r\n <p class=\"fb-field__hint\">Nessuna funzione corrisponde alla ricerca.</p>\r\n }\r\n <!--\r\n L'elenco non e\u2019 autoritativo: un motore puo\u2019 supportare piu\u2019 di quanto dichiara, quindi una\r\n funzione che non c\u2019e\u2019 non e\u2019 un errore (\u00A76.3).\r\n -->\r\n <p class=\"fb-field__hint\">\r\n Elenco dichiarato dal motore: puo\u2019 non essere completo. A verificare l\u2019espressione resta il\r\n motore stesso.\r\n </p>\r\n </div>\r\n}\r\n\r\n@if (isChecking()) {\r\n <p class=\"fb-field__hint\">Verifica in corso\u2026</p>\r\n} @else if (issues().length) {\r\n <ul class=\"fb-formula__issues\">\r\n @for (issue of issues(); track $index) {\r\n <li class=\"fb-formula__issue\" [class]=\"'fb-formula__issue--' + severityOf(issue)\">\r\n @if (hasPosition(issue)) {\r\n <!--\r\n Il salto e' il senso di `position`: un indice di carattere che l'utente dovrebbe\r\n contare a mano non porta da nessuna parte.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-formula__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-formula__message\">{{ issue.message }}</span>\r\n <!-- Codice del motore, non della \u00A77: si mostra e non si interpreta. -->\r\n <code class=\"fb-formula__code\">{{ issue.code }}</code>\r\n </li>\r\n }\r\n </ul>\r\n} @else if (isConfirmed()) {\r\n <p class=\"fb-formula__ok\">\u2713 Espressione verificata dal motore.</p>\r\n} @else if (isUnverified()) {\r\n <!--\r\n Primitiva s\u00EC, motore no: nessun rilievo **e** nessuna conferma. Dirlo e' l'unico modo di\r\n non far leggere il silenzio come un via libera (\u00A76.3).\r\n -->\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n Nessun motore di validazione delle formule e\u2019 configurato: l\u2019espressione non e\u2019 stata\r\n controllata.\r\n </p>\r\n}\r\n\r\n@if (typeMismatch(); as mismatch) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n L\u2019espressione produce {{ mismatch.produced }}, ma qui serve {{ mismatch.expected }}.\r\n </p>\r\n}\r\n\r\n<ng-content />\r\n", styles: [":host{display:flex;flex-direction:column;gap:4px}.fb-formula__issues{display:flex;flex-direction:column;gap:3px;margin:0;padding:0;list-style:none}.fb-formula__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-formula__issue--Error{border-left-color:var(--fb-error, #c9372c);color:var(--fb-error, #c9372c)}.fb-formula__issue--Warning{border-left-color:var(--fb-warning, #b7791f);color:var(--fb-warning, #b7791f)}.fb-formula__issue--Info{border-left-color:var(--fb-accent, #2f6feb);color:var(--fb-text-muted, #667085)}.fb-formula__message{flex:1 1 auto;min-width:0}.fb-formula__jump{padding:0 4px;border:1px solid currentcolor;border-radius:3px;background:transparent;color:inherit;font:inherit;font-size:10px;cursor:pointer}.fb-formula__jump:disabled{opacity:.5;cursor:not-allowed}.fb-formula__jump code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.fb-formula__code{color:var(--fb-text-subtle, #98a2b3);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:9px}.fb-formula__ok{margin:0;font-size:10px;color:var(--fb-success, #22a06b)}.fb-formula__hints{display:flex;flex-direction:column;gap:1px;max-height:160px;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-formula__hint{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-formula__hint:hover,.fb-formula__hint--on{background:color-mix(in srgb,var(--fb-accent, #2f6feb) 10%,transparent)}.fb-formula__hint code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.fb-formula__hint-type{margin-left:auto;color:var(--fb-text-subtle, #98a2b3)}.fb-formula__bar{display:flex;justify-content:flex-end}.fb-formula__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-formula__toggle em{font-family:ui-serif,Georgia,serif}.fb-formula__toggle--on{border-color:var(--fb-accent, #2f6feb);color:var(--fb-accent, #2f6feb)}.fb-formula__toggle:disabled{opacity:.5;cursor:not-allowed}.fb-formula__palette{display:flex;flex-direction:column;gap:4px;max-height:220px;padding:4px;overflow-y:auto;border:1px solid var(--fb-border, #d6dae1);border-radius:4px;background:var(--fb-surface-alt, #f8f9fb)}.fb-formula__search{font-size:10px}.fb-formula__group{display:flex;flex-direction:column;gap:2px}.fb-formula__group-title{margin:2px 0 0;color:var(--fb-text-subtle, #98a2b3);font-size:9px;font-weight:600;text-transform:uppercase;letter-spacing:.04em}.fb-formula__fn{display:grid;grid-template-columns:1fr auto;gap:0 6px;padding:3px 5px;border:0;border-radius:3px;background:var(--fb-surface, #fff);color:var(--fb-text, #1d2939);font:inherit;font-size:10px;text-align:left;cursor:pointer}.fb-formula__fn:hover{background:color-mix(in srgb,var(--fb-accent, #2f6feb) 10%,transparent)}.fb-formula__fn:disabled{opacity:.5;cursor:not-allowed}.fb-formula__fn-sig{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.fb-formula__fn-type{color:var(--fb-text-subtle, #98a2b3)}.fb-formula__fn-desc,.fb-formula__fn-example{grid-column:1 / -1;color:var(--fb-text-muted, #667085);font-size:9px;line-height:1.4}.fb-formula__fn-example{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;color:var(--fb-text-subtle, #98a2b3)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
7133
7274
  }
7134
7275
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: FormulaEditorComponent, decorators: [{
7135
7276
  type: Component,
7136
- args: [{ selector: 'fb-formula-editor', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<textarea\r\n #area\r\n class=\"fb-textarea fb-input--mono\"\r\n [class.fb-input--invalid]=\"hasErrors()\"\r\n [value]=\"expression()\"\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 L'elenco dei riferimenti disponibili come autocompletamento (\u00A76.3): stessa fonte del\r\n reference picker, `POST /flows/references`, output automatici compresi. `mousedown` va\r\n fermato \u2014 il blur della casella arriverebbe prima del click e chiuderebbe l'elenco.\r\n -->\r\n <ul class=\"fb-formula__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-formula__hint\"\r\n [class.fb-formula__hint--on]=\"$index === highlighted()\"\r\n role=\"option\"\r\n [attr.aria-selected]=\"$index === highlighted()\"\r\n (mousedown)=\"$event.preventDefault()\"\r\n (click)=\"insert(reference.name)\"\r\n >\r\n <code>{{ reference.name }}</code>\r\n @if (reference.dataType) {\r\n <span class=\"fb-formula__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 (isChecking()) {\r\n <p class=\"fb-field__hint\">Verifica in corso\u2026</p>\r\n} @else if (issues().length) {\r\n <ul class=\"fb-formula__issues\">\r\n @for (issue of issues(); track $index) {\r\n <li class=\"fb-formula__issue\" [class]=\"'fb-formula__issue--' + severityOf(issue)\">\r\n @if (hasPosition(issue)) {\r\n <!--\r\n Il salto e' il senso di `position`: un indice di carattere che l'utente dovrebbe\r\n contare a mano non porta da nessuna parte.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-formula__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-formula__message\">{{ issue.message }}</span>\r\n <!-- Codice del motore, non della \u00A77: si mostra e non si interpreta. -->\r\n <code class=\"fb-formula__code\">{{ issue.code }}</code>\r\n </li>\r\n }\r\n </ul>\r\n} @else if (isConfirmed()) {\r\n <p class=\"fb-formula__ok\">\u2713 Espressione verificata dal motore.</p>\r\n} @else if (isUnverified()) {\r\n <!--\r\n Primitiva s\u00EC, motore no: nessun rilievo **e** nessuna conferma. Dirlo e' l'unico modo di\r\n non far leggere il silenzio come un via libera (\u00A76.3).\r\n -->\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n Nessun motore di validazione delle formule e\u2019 configurato: l\u2019espressione non e\u2019 stata\r\n controllata.\r\n </p>\r\n}\r\n\r\n@if (typeMismatch(); as mismatch) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n L\u2019espressione produce {{ mismatch.produced }}, ma qui serve {{ mismatch.expected }}.\r\n </p>\r\n}\r\n\r\n<ng-content />\r\n", styles: [":host{display:flex;flex-direction:column;gap:4px}.fb-formula__issues{display:flex;flex-direction:column;gap:3px;margin:0;padding:0;list-style:none}.fb-formula__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-formula__issue--Error{border-left-color:var(--fb-error, #c9372c);color:var(--fb-error, #c9372c)}.fb-formula__issue--Warning{border-left-color:var(--fb-warning, #b7791f);color:var(--fb-warning, #b7791f)}.fb-formula__issue--Info{border-left-color:var(--fb-accent, #2f6feb);color:var(--fb-text-muted, #667085)}.fb-formula__message{flex:1 1 auto;min-width:0}.fb-formula__jump{padding:0 4px;border:1px solid currentcolor;border-radius:3px;background:transparent;color:inherit;font:inherit;font-size:10px;cursor:pointer}.fb-formula__jump:disabled{opacity:.5;cursor:not-allowed}.fb-formula__jump code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.fb-formula__code{color:var(--fb-text-subtle, #98a2b3);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:9px}.fb-formula__ok{margin:0;font-size:10px;color:var(--fb-success, #22a06b)}.fb-formula__hints{display:flex;flex-direction:column;gap:1px;max-height:160px;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-formula__hint{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-formula__hint:hover,.fb-formula__hint--on{background:color-mix(in srgb,var(--fb-accent, #2f6feb) 10%,transparent)}.fb-formula__hint code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.fb-formula__hint-type{margin-left:auto;color:var(--fb-text-subtle, #98a2b3)}\n"] }]
7277
+ args: [{ selector: 'fb-formula-editor', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (hasPalette()) {\r\n <!--\r\n La palette compare **solo** se il motore dichiara delle funzioni (\u00A76.3): elenco vuoto\r\n significa \u00ABnon le dichiara\u00BB, e un pannello vuoto farebbe credere che non ce ne siano.\r\n -->\r\n <div class=\"fb-formula__bar\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-formula__toggle\"\r\n [class.fb-formula__toggle--on]=\"isPaletteOpen()\"\r\n [disabled]=\"disabled()\"\r\n [attr.aria-expanded]=\"isPaletteOpen()\"\r\n title=\"Funzioni disponibili nel motore\"\r\n (click)=\"togglePalette()\"\r\n >\r\n <em>f</em>(x) Funzioni\r\n </button>\r\n </div>\r\n}\r\n\r\n<textarea\r\n #area\r\n class=\"fb-textarea fb-input--mono\"\r\n [class.fb-input--invalid]=\"hasErrors()\"\r\n [value]=\"expression()\"\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 L'elenco dei riferimenti disponibili come autocompletamento (\u00A76.3): stessa fonte del\r\n reference picker, `POST /flows/references`, output automatici compresi. `mousedown` va\r\n fermato \u2014 il blur della casella arriverebbe prima del click e chiuderebbe l'elenco.\r\n -->\r\n <ul class=\"fb-formula__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-formula__hint\"\r\n [class.fb-formula__hint--on]=\"$index === highlighted()\"\r\n role=\"option\"\r\n [attr.aria-selected]=\"$index === highlighted()\"\r\n (mousedown)=\"$event.preventDefault()\"\r\n (click)=\"insert(reference.name)\"\r\n >\r\n <code>{{ reference.name }}</code>\r\n @if (reference.dataType) {\r\n <span class=\"fb-formula__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 (hasPalette() && isPaletteOpen()) {\r\n <div class=\"fb-formula__palette\">\r\n <input\r\n type=\"search\"\r\n class=\"fb-input fb-formula__search\"\r\n placeholder=\"Cerca una funzione\u2026\"\r\n aria-label=\"Cerca una funzione\"\r\n [value]=\"paletteFilter()\"\r\n (input)=\"paletteFilter.set($any($event.target).value)\"\r\n />\r\n @if (paletteGroups().length) {\r\n @for (group of paletteGroups(); track $index) {\r\n <div class=\"fb-formula__group\">\r\n @if (group.category) {\r\n <h5 class=\"fb-formula__group-title\">{{ group.category }}</h5>\r\n }\r\n @for (entry of group.items; track entry.name) {\r\n <!--\r\n `mousedown` fermato come nei suggerimenti: il blur della casella arriverebbe prima\r\n del click. `signature` e `description` si mostrano e non si parsano \u2014 la grammatica\r\n resta del motore (\u00A76.3).\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-formula__fn\"\r\n [disabled]=\"disabled()\"\r\n (mousedown)=\"$event.preventDefault()\"\r\n (click)=\"insertFunction(entry)\"\r\n >\r\n <code class=\"fb-formula__fn-sig\">{{ signatureOf(entry) }}</code>\r\n @if (entry.resultDataType) {\r\n <span class=\"fb-formula__fn-type\">\r\n {{ entry.resultDataType }}{{ entry.isCollection ? '[]' : '' }}\r\n </span>\r\n }\r\n @if (entry.description) {\r\n <span class=\"fb-formula__fn-desc\">{{ entry.description }}</span>\r\n }\r\n @if (entry.example) {\r\n <code class=\"fb-formula__fn-example\">{{ entry.example }}</code>\r\n }\r\n </button>\r\n }\r\n </div>\r\n }\r\n } @else {\r\n <p class=\"fb-field__hint\">Nessuna funzione corrisponde alla ricerca.</p>\r\n }\r\n <!--\r\n L'elenco non e\u2019 autoritativo: un motore puo\u2019 supportare piu\u2019 di quanto dichiara, quindi una\r\n funzione che non c\u2019e\u2019 non e\u2019 un errore (\u00A76.3).\r\n -->\r\n <p class=\"fb-field__hint\">\r\n Elenco dichiarato dal motore: puo\u2019 non essere completo. A verificare l\u2019espressione resta il\r\n motore stesso.\r\n </p>\r\n </div>\r\n}\r\n\r\n@if (isChecking()) {\r\n <p class=\"fb-field__hint\">Verifica in corso\u2026</p>\r\n} @else if (issues().length) {\r\n <ul class=\"fb-formula__issues\">\r\n @for (issue of issues(); track $index) {\r\n <li class=\"fb-formula__issue\" [class]=\"'fb-formula__issue--' + severityOf(issue)\">\r\n @if (hasPosition(issue)) {\r\n <!--\r\n Il salto e' il senso di `position`: un indice di carattere che l'utente dovrebbe\r\n contare a mano non porta da nessuna parte.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-formula__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-formula__message\">{{ issue.message }}</span>\r\n <!-- Codice del motore, non della \u00A77: si mostra e non si interpreta. -->\r\n <code class=\"fb-formula__code\">{{ issue.code }}</code>\r\n </li>\r\n }\r\n </ul>\r\n} @else if (isConfirmed()) {\r\n <p class=\"fb-formula__ok\">\u2713 Espressione verificata dal motore.</p>\r\n} @else if (isUnverified()) {\r\n <!--\r\n Primitiva s\u00EC, motore no: nessun rilievo **e** nessuna conferma. Dirlo e' l'unico modo di\r\n non far leggere il silenzio come un via libera (\u00A76.3).\r\n -->\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n Nessun motore di validazione delle formule e\u2019 configurato: l\u2019espressione non e\u2019 stata\r\n controllata.\r\n </p>\r\n}\r\n\r\n@if (typeMismatch(); as mismatch) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n L\u2019espressione produce {{ mismatch.produced }}, ma qui serve {{ mismatch.expected }}.\r\n </p>\r\n}\r\n\r\n<ng-content />\r\n", styles: [":host{display:flex;flex-direction:column;gap:4px}.fb-formula__issues{display:flex;flex-direction:column;gap:3px;margin:0;padding:0;list-style:none}.fb-formula__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-formula__issue--Error{border-left-color:var(--fb-error, #c9372c);color:var(--fb-error, #c9372c)}.fb-formula__issue--Warning{border-left-color:var(--fb-warning, #b7791f);color:var(--fb-warning, #b7791f)}.fb-formula__issue--Info{border-left-color:var(--fb-accent, #2f6feb);color:var(--fb-text-muted, #667085)}.fb-formula__message{flex:1 1 auto;min-width:0}.fb-formula__jump{padding:0 4px;border:1px solid currentcolor;border-radius:3px;background:transparent;color:inherit;font:inherit;font-size:10px;cursor:pointer}.fb-formula__jump:disabled{opacity:.5;cursor:not-allowed}.fb-formula__jump code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.fb-formula__code{color:var(--fb-text-subtle, #98a2b3);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:9px}.fb-formula__ok{margin:0;font-size:10px;color:var(--fb-success, #22a06b)}.fb-formula__hints{display:flex;flex-direction:column;gap:1px;max-height:160px;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-formula__hint{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-formula__hint:hover,.fb-formula__hint--on{background:color-mix(in srgb,var(--fb-accent, #2f6feb) 10%,transparent)}.fb-formula__hint code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.fb-formula__hint-type{margin-left:auto;color:var(--fb-text-subtle, #98a2b3)}.fb-formula__bar{display:flex;justify-content:flex-end}.fb-formula__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-formula__toggle em{font-family:ui-serif,Georgia,serif}.fb-formula__toggle--on{border-color:var(--fb-accent, #2f6feb);color:var(--fb-accent, #2f6feb)}.fb-formula__toggle:disabled{opacity:.5;cursor:not-allowed}.fb-formula__palette{display:flex;flex-direction:column;gap:4px;max-height:220px;padding:4px;overflow-y:auto;border:1px solid var(--fb-border, #d6dae1);border-radius:4px;background:var(--fb-surface-alt, #f8f9fb)}.fb-formula__search{font-size:10px}.fb-formula__group{display:flex;flex-direction:column;gap:2px}.fb-formula__group-title{margin:2px 0 0;color:var(--fb-text-subtle, #98a2b3);font-size:9px;font-weight:600;text-transform:uppercase;letter-spacing:.04em}.fb-formula__fn{display:grid;grid-template-columns:1fr auto;gap:0 6px;padding:3px 5px;border:0;border-radius:3px;background:var(--fb-surface, #fff);color:var(--fb-text, #1d2939);font:inherit;font-size:10px;text-align:left;cursor:pointer}.fb-formula__fn:hover{background:color-mix(in srgb,var(--fb-accent, #2f6feb) 10%,transparent)}.fb-formula__fn:disabled{opacity:.5;cursor:not-allowed}.fb-formula__fn-sig{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.fb-formula__fn-type{color:var(--fb-text-subtle, #98a2b3)}.fb-formula__fn-desc,.fb-formula__fn-example{grid-column:1 / -1;color:var(--fb-text-muted, #667085);font-size:9px;line-height:1.4}.fb-formula__fn-example{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;color:var(--fb-text-subtle, #98a2b3)}\n"] }]
7137
7278
  }], ctorParameters: () => [], propDecorators: { expression: [{ type: i0.Input, args: [{ isSignal: true, alias: "expression", required: false }] }], usage: [{ type: i0.Input, args: [{ isSignal: true, alias: "usage", required: false }] }], expectedDataType: [{ type: i0.Input, args: [{ isSignal: true, alias: "expectedDataType", required: false }] }], scale: [{ type: i0.Input, args: [{ isSignal: true, alias: "scale", 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 }] }], expressionChange: [{ type: i0.Output, args: ["expressionChange"] }], area: [{ type: i0.ViewChild, args: ['area', { isSignal: true }] }] } });
7138
7279
 
7139
7280
  /**
@@ -8127,7 +8268,17 @@ class ParameterEditorComponent {
8127
8268
  inputs = computed(() => this.holder().inputParameters ?? [], ...(ngDevMode ? [{ debugName: "inputs" }] : []));
8128
8269
  outputs = computed(() => this.holder().outputParameters ?? [], ...(ngDevMode ? [{ debugName: "outputs" }] : []));
8129
8270
  hasCatalog = computed(() => this.catalogParameters().length > 0, ...(ngDevMode ? [{ debugName: "hasCatalog" }] : []));
8130
- inputCatalog = computed(() => this.catalogParameters().filter((parameter) => parameter.isOutput !== true), ...(ngDevMode ? [{ debugName: "inputCatalog" }] : []));
8271
+ /**
8272
+ * I due elenchi **non** sono una partizione: `isInput` e `isOutput` sono flag indipendenti, e un
8273
+ * parametro bidirezionale (tipico dei componenti di screen) va proposto in tutt'e due i posti.
8274
+ * Filtrare i soli `isOutput !== true` lo faceva sparire dagli ingressi — e con esso dai
8275
+ * `missingRequired`, quindi non veniva proposto da nessuna parte; scritto a mano nel picker degli
8276
+ * input diventava poi `PARAMETER_UNKNOWN`, perche' il catalogo li' non lo conteneva.
8277
+ *
8278
+ * Chi non dichiara nessuno dei due flag vale **input**: e' il caso di gran lunga piu' comune, ed
8279
+ * e' cio' che i cataloghi che i flag non li dichiarano si aspettano.
8280
+ */
8281
+ inputCatalog = computed(() => this.catalogParameters().filter((parameter) => parameter.isInput === true || (parameter.isInput === undefined && parameter.isOutput !== true)), ...(ngDevMode ? [{ debugName: "inputCatalog" }] : []));
8131
8282
  outputCatalog = computed(() => this.catalogParameters().filter((parameter) => parameter.isOutput === true), ...(ngDevMode ? [{ debugName: "outputCatalog" }] : []));
8132
8283
  /**
8133
8284
  * I candidati come li vuole il picker. Il tipo e l'obbligatorieta' finiscono nella