@esfaenza/flow-builder 20.3.9 → 20.3.11
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 +29 -1
- package/fesm2022/esfaenza-flow-builder.mjs +277 -46
- package/fesm2022/esfaenza-flow-builder.mjs.map +1 -1
- package/index.d.ts +137 -23
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -133,7 +133,7 @@ sono opzionali e rifiutano con `MissingService` se non sovrascritti.
|
|
|
133
133
|
| §6.4 Dizionari e cataloghi | |
|
|
134
134
|
|---|---|
|
|
135
135
|
| `getDictionaries(processType?)` | astratto — `processType` filtra le globali (§4.1) |
|
|
136
|
-
| `getReferences(
|
|
136
|
+
| `getReferences(definition)` / `getWritableReferences(definition)` | astratti — prendono il **documento nudo** e restituiscono **tutti** i riferimenti: nel body non c'e' nessun filtro di tipo, e la compatibilita' col campo la decide l'editor (§6.4, §13.17) |
|
|
137
137
|
| `getConnectorTargets(definition, excluding?)` | astratto |
|
|
138
138
|
| `getOutline(definition)` | astratto |
|
|
139
139
|
| `listObjects()`, `listFields(object, usage?)` | astratti |
|
|
@@ -254,6 +254,32 @@ Restano fuori i percorsi radicati sul risultato **automatico** di un elemento
|
|
|
254
254
|
(`Leggi_Contatti.Owner.Name`): la validazione non li verifica, e l'editor non accusa cio' che il
|
|
255
255
|
backend non verifica.
|
|
256
256
|
|
|
257
|
+
Il filtro di tipo di un campo (§6.4) lo applica **l'editor**: la primitiva prende il documento nudo
|
|
258
|
+
e restituisce tutti i riferimenti, così la stessa risposta serve tutte le tendine di una schermata
|
|
259
|
+
(§13.17). La regola sta in `core/reference-filter.util.ts`, ed e' la stessa per le radici e per i
|
|
260
|
+
segmenti di un percorso. Il filtro guarda il tipo della **radice**, e quindi non basta: in un
|
|
261
|
+
parametro `String` una variabile `Structure` non e' compatibile, ma `Richiesta.Ragione` sì. Per
|
|
262
|
+
questo `<fb-reference-picker>` tiene l'elenco **completo** accanto a quello filtrato, e ne ricava
|
|
263
|
+
due cose: i contenitori compaiono come righe in cui si **entra** — cliccarle apre il percorso
|
|
264
|
+
invece di scegliere un valore del tipo sbagliato — e le radici si riconoscono tutte, così un
|
|
265
|
+
`Leggi_Ordine.Numero` già scritto in un campo `String` non viene accusato di non esistere. Nelle
|
|
266
|
+
proposte non entra comunque nessun valore del tipo sbagliato: i segmenti li filtra la stessa
|
|
267
|
+
regola, tenendo solo quelli da cui si scende. In un campo di **destinazione** le due liste sono due
|
|
268
|
+
richieste — `getWritableReferences` per le proposte, `getReferences` per le radici — perche' la
|
|
269
|
+
scrivibilita' e' del **percorso** e non della radice: un `$User.Anagrafica.Citta` scritto a mano e'
|
|
270
|
+
`TARGET_NOT_WRITABLE`, non un nome che non esiste.
|
|
271
|
+
|
|
272
|
+
**Le globali si navigano come le risorse.** Un percorso dichiarato dall'host puo' portare un
|
|
273
|
+
contenitore, e allora `objectType` dice di che tipo: `$User.Anagrafica.Citta` si propone e si
|
|
274
|
+
verifica come `Richiesta.Ragione`. La regola da non sbagliare e' che vince il **prefisso dichiarato
|
|
275
|
+
piu' lungo** — non tutto cio' che segue lo scope — perche' altrimenti un percorso giusto viene
|
|
276
|
+
segnalato come rotto (§4.1). Dove il catalogo dichiara il contenitore ma non il tipo il rilievo e'
|
|
277
|
+
`PATH_NOT_VERIFIABLE`, e la correzione sta nel catalogo dell'host; navigare uno scalare
|
|
278
|
+
(`$User.Email.Dominio`) e' invece `GLOBAL_UNKNOWN`. Sulle **destinazioni** non cambia niente da
|
|
279
|
+
sapere: le globali sono di sola lettura, membri compresi — le assegnabili sono `$Flow.CurrentStage`,
|
|
280
|
+
`$Flow.ActiveStages` e i campi di `$Record` — e il picker lo dice con `TARGET_NOT_WRITABLE` invece
|
|
281
|
+
di far sembrare il nome inesistente (§5.2).
|
|
282
|
+
|
|
257
283
|
Su una destinazione la colonna che decide non e' la stessa nei due mondi: un membro porta il proprio
|
|
258
284
|
`isWritable`, un campo no. Fuori da un Create o da un Update il contesto non c'e' — un Assignment su
|
|
259
285
|
`Cliente.Codice` non sa se quel record verra' creato o aggiornato — quindi lì basta `isCreateable`
|
|
@@ -430,6 +456,8 @@ distribuire e nessuna dipendenza da un font di icone dell'applicazione ospite.
|
|
|
430
456
|
| 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** |
|
|
431
457
|
| 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 |
|
|
432
458
|
| 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) |
|
|
459
|
+
| 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` |
|
|
460
|
+
| 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 |
|
|
433
461
|
|
|
434
462
|
Altre regole del contratto rispettate: liste vuote omesse invece di scritte come `[]` (§2),
|
|
435
463
|
`filterLogic` mostrato **solo** dove il modello lo prevede (§4.4), `fullName` / `status` mai
|
|
@@ -410,6 +410,12 @@ class HttpFlowBuilderApi extends FlowBuilderApi {
|
|
|
410
410
|
return this.del(`/flows/editor/${HttpFlowBuilderApi.segment(flowName)}`);
|
|
411
411
|
}
|
|
412
412
|
// ---------------------------------------------------------------- §6.3
|
|
413
|
+
/**
|
|
414
|
+
* Documento nudo alla radice: le primitive che **analizzano** il documento non usano
|
|
415
|
+
* l'involucro `{definition, flowName, …}` della §6.2, che serve solo a chi deve sapere dove
|
|
416
|
+
* riporlo (§6.4, §13.17). Sbagliare involucro qui non produce nessun errore: si otterrebbe la
|
|
417
|
+
* validazione di un flow vuoto, cioe' «manca lo Start».
|
|
418
|
+
*/
|
|
413
419
|
validateDefinition(definition) {
|
|
414
420
|
return this.post(`/flows/editor/validate`, definition);
|
|
415
421
|
}
|
|
@@ -421,11 +427,17 @@ class HttpFlowBuilderApi extends FlowBuilderApi {
|
|
|
421
427
|
// `processType` filtra le globali: `$Record` non esiste in uno screen flow (§4.1).
|
|
422
428
|
return this.get('/flows/editor/dictionaries', HttpFlowBuilderApi.params({ processType }));
|
|
423
429
|
}
|
|
424
|
-
|
|
425
|
-
|
|
430
|
+
/**
|
|
431
|
+
* Il body e' il documento **nudo**, come per validate/parse/outline/connector-targets (§6.4,
|
|
432
|
+
* §13.17): avvolgerlo (`{definition: …}`) o aggiungergli campi di filtro non da' nessun errore
|
|
433
|
+
* — il backend deserializza un `Flow` in cui non riconosce nessuna proprieta', risponde `200` e
|
|
434
|
+
* l'elenco contiene le sole globali. Se le tendine sembrano vuote, si guarda qui prima di tutto.
|
|
435
|
+
*/
|
|
436
|
+
getReferences(definition) {
|
|
437
|
+
return this.post('/flows/editor/references', definition);
|
|
426
438
|
}
|
|
427
|
-
getWritableReferences(
|
|
428
|
-
return this.post('/flows/editor/writable-references',
|
|
439
|
+
getWritableReferences(definition) {
|
|
440
|
+
return this.post('/flows/editor/writable-references', definition);
|
|
429
441
|
}
|
|
430
442
|
getConnectorTargets(definition, excluding) {
|
|
431
443
|
return this.post('/flows/editor/connector-targets', definition, HttpFlowBuilderApi.params({ excluding }));
|
|
@@ -1601,6 +1613,45 @@ function navigatePath(catalog, request) {
|
|
|
1601
1613
|
return { resolution: resolution.asReadonly(), isPending: isPending.asReadonly() };
|
|
1602
1614
|
}
|
|
1603
1615
|
|
|
1616
|
+
/**
|
|
1617
|
+
* Filtro di compatibilita' dei riferimenti — FRONTEND.md §6.4.
|
|
1618
|
+
*
|
|
1619
|
+
* `POST /flows/references` e `POST /flows/references/writable` prendono il **documento nudo** e
|
|
1620
|
+
* restituiscono **tutti** i riferimenti, ognuno col proprio `dataType`, `isCollection` e
|
|
1621
|
+
* `isWritable`: nel body non c'e' nessun campo di filtro. Chi sceglie cosa e' compatibile col
|
|
1622
|
+
* campo che l'utente sta compilando e' quindi l'editor, ed e' deliberato — la stessa risposta
|
|
1623
|
+
* serve tutte le tendine di una schermata, quindi si chiede una volta e si filtra n volte.
|
|
1624
|
+
*
|
|
1625
|
+
* Il filtro guarda **la radice**, non il percorso: una radice scartata si porta via anche cio'
|
|
1626
|
+
* che sta sotto, e chi propone i segmenti navigati applica la stessa regola separatamente
|
|
1627
|
+
* (`reference-picker`).
|
|
1628
|
+
*/
|
|
1629
|
+
/** `true` se il campo non pone nessun vincolo: lì filtrare e' inutile. */
|
|
1630
|
+
function isEmptyReferenceFilter(filter) {
|
|
1631
|
+
return !filter || (!filter.dataType && filter.isCollection == null && !filter.objectType);
|
|
1632
|
+
}
|
|
1633
|
+
function matchesReferenceFilter(entry, filter) {
|
|
1634
|
+
// Un riferimento di tipo ignoto non si scarta: sarebbe peggio nasconderlo.
|
|
1635
|
+
if (filter.dataType && entry.dataType && entry.dataType !== filter.dataType) {
|
|
1636
|
+
return false;
|
|
1637
|
+
}
|
|
1638
|
+
// `!= null`, non `!== undefined`: nei template il safe navigation di Angular produce **null**,
|
|
1639
|
+
// e trattarlo come "voglio i valori singoli" svuoterebbe l'elenco.
|
|
1640
|
+
if (filter.isCollection != null && entry.isCollection != null && !!entry.isCollection !== filter.isCollection) {
|
|
1641
|
+
return false;
|
|
1642
|
+
}
|
|
1643
|
+
if (filter.objectType && entry.objectType && entry.objectType !== filter.objectType) {
|
|
1644
|
+
return false;
|
|
1645
|
+
}
|
|
1646
|
+
return true;
|
|
1647
|
+
}
|
|
1648
|
+
function filterReferences(references, filter) {
|
|
1649
|
+
if (isEmptyReferenceFilter(filter)) {
|
|
1650
|
+
return [...references];
|
|
1651
|
+
}
|
|
1652
|
+
return references.filter((entry) => matchesReferenceFilter(entry, filter));
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1604
1655
|
/**
|
|
1605
1656
|
* Lo store del documento in lavorazione.
|
|
1606
1657
|
*
|
|
@@ -3702,6 +3753,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImpo
|
|
|
3702
3753
|
* pur non essendo dichiarati in nessuna collection di risorse: la primitiva li restituisce
|
|
3703
3754
|
* già, ed e' il motivo per cui non si ricostruisce l'elenco lato client.
|
|
3704
3755
|
*
|
|
3756
|
+
* La primitiva prende il **documento nudo** e restituisce **tutti** i riferimenti: il filtro di
|
|
3757
|
+
* tipo del campo lo applica l'editor, con `core/reference-filter.util` (§6.4, §13.17). L'elenco
|
|
3758
|
+
* completo resta comunque a portata di mano, e serve — il filtro guarda la **radice**, e una radice
|
|
3759
|
+
* scartata si porta via cio' che sta sotto: in un parametro `String` una variabile `Structure` non
|
|
3760
|
+
* e' compatibile, ma `Richiesta.Ragione` sì.
|
|
3761
|
+
*
|
|
3705
3762
|
* I **percorsi navigati** non sono piu' tutti uguali. Quando la radice e' una risorsa dichiarata
|
|
3706
3763
|
* di cui si conosce la forma — una `Structure` con la sua classe, un record con la sua entita' — il
|
|
3707
3764
|
* percorso si naviga **fino in fondo** (§4.4, §4.7.1): i segmenti si propongono un livello alla
|
|
@@ -3738,7 +3795,41 @@ class ReferencePickerComponent {
|
|
|
3738
3795
|
*/
|
|
3739
3796
|
elementsOnly = input(false, ...(ngDevMode ? [{ debugName: "elementsOnly" }] : []));
|
|
3740
3797
|
valueChange = output();
|
|
3741
|
-
|
|
3798
|
+
/**
|
|
3799
|
+
* L'elenco come arriva dalla primitiva, **senza** il filtro di tipo.
|
|
3800
|
+
*
|
|
3801
|
+
* Serve a due cose oltre che a produrre le proposte: riconoscere le radici dei percorsi e
|
|
3802
|
+
* mostrare i contenitori come righe in cui si entra. Il filtro (§6.4) guarda il tipo della
|
|
3803
|
+
* **radice**, e una radice scartata si porta via anche cio' che sta sotto: in un parametro
|
|
3804
|
+
* `String` una variabile `Structure` non e' compatibile, ma `Richiesta.Ragione` lo e', e
|
|
3805
|
+
* `Leggi_Ordine` non e' un `String` mentre `Leggi_Ordine.Numero` lo e'. Senza questo elenco il
|
|
3806
|
+
* primo membro non si puo' proporre e il secondo viene accusato di non esistere pur essendo un
|
|
3807
|
+
* riferimento valido (§4.5).
|
|
3808
|
+
*/
|
|
3809
|
+
unfiltered = signal([], ...(ngDevMode ? [{ debugName: "unfiltered" }] : []));
|
|
3810
|
+
/**
|
|
3811
|
+
* L'insieme da cui si pesca prima del filtro di tipo: in un campo di destinazione e' la
|
|
3812
|
+
* risposta di `getWritableReferences`, altrove {@link unfiltered} stesso.
|
|
3813
|
+
*/
|
|
3814
|
+
available = signal([], ...(ngDevMode ? [{ debugName: "available" }] : []));
|
|
3815
|
+
/**
|
|
3816
|
+
* Le proposte compatibili col campo. Il filtro e' **locale**: la primitiva prende il documento
|
|
3817
|
+
* nudo e restituisce tutto (§6.4, §13.17), cosi' la stessa risposta serve tutte le tendine di
|
|
3818
|
+
* una schermata.
|
|
3819
|
+
*/
|
|
3820
|
+
references = computed(() => filterReferences(this.available(), {
|
|
3821
|
+
/**
|
|
3822
|
+
* `?? undefined` non e' cosmetico: nei template il safe navigation di Angular
|
|
3823
|
+
* (`describe(x)?.isCollection`) produce **null**, non `undefined`. Un `isCollection: null`
|
|
3824
|
+
* letto come "filtra per valore singolo" svuoterebbe l'elenco: un campo senza filtro non
|
|
3825
|
+
* deve porre nessun vincolo.
|
|
3826
|
+
*/
|
|
3827
|
+
dataType: this.dataType() ?? undefined,
|
|
3828
|
+
isCollection: this.isCollection() ?? undefined,
|
|
3829
|
+
objectType: this.objectType() ?? undefined,
|
|
3830
|
+
}), ...(ngDevMode ? [{ debugName: "references" }] : []));
|
|
3831
|
+
/** Tutto cio' che puo' essere la radice di un percorso, filtrato o no. */
|
|
3832
|
+
roots = computed(() => this.unfiltered(), ...(ngDevMode ? [{ debugName: "roots" }] : []));
|
|
3742
3833
|
isLoading = signal(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
|
|
3743
3834
|
loadError = signal(null, ...(ngDevMode ? [{ debugName: "loadError" }] : []));
|
|
3744
3835
|
isOpen = signal(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : []));
|
|
@@ -3749,45 +3840,90 @@ class ReferencePickerComponent {
|
|
|
3749
3840
|
effect(() => {
|
|
3750
3841
|
const definition = this.store.document();
|
|
3751
3842
|
const writable = this.writableOnly();
|
|
3752
|
-
const dataType = this.dataType();
|
|
3753
|
-
const isCollection = this.isCollection();
|
|
3754
|
-
const objectType = this.objectType();
|
|
3755
3843
|
if (this.elementsOnly()) {
|
|
3756
3844
|
// I node li conosce già il documento: nessuna richiesta necessaria.
|
|
3757
|
-
this.
|
|
3845
|
+
const nodes = this.store.nodes().map((reference) => ({
|
|
3758
3846
|
name: reference.name,
|
|
3759
3847
|
label: reference.node.label ?? null,
|
|
3760
3848
|
kind: 'ElementOutput',
|
|
3761
3849
|
description: this.dictionaries.labelOf(reference.type),
|
|
3762
3850
|
isWritable: false,
|
|
3763
|
-
}))
|
|
3851
|
+
}));
|
|
3852
|
+
this.unfiltered.set(nodes);
|
|
3853
|
+
this.available.set(nodes);
|
|
3764
3854
|
return;
|
|
3765
3855
|
}
|
|
3766
|
-
/**
|
|
3767
|
-
* `?? undefined` non e' cosmetico: nei template il safe navigation di Angular
|
|
3768
|
-
* (`describe(x)?.isCollection`) produce **null**, non `undefined`. Un `isCollection: null`
|
|
3769
|
-
* che arriva alla primitiva viene letto come "filtra per valore singolo" e svuota
|
|
3770
|
-
* l'elenco: un campo senza filtro deve mandare il campo **assente**.
|
|
3771
|
-
*/
|
|
3772
|
-
const query = {
|
|
3773
|
-
definition,
|
|
3774
|
-
dataType: dataType ?? undefined,
|
|
3775
|
-
isCollection: isCollection ?? undefined,
|
|
3776
|
-
objectType: objectType ?? undefined,
|
|
3777
|
-
};
|
|
3778
3856
|
this.isLoading.set(true);
|
|
3779
3857
|
this.loadError.set(null);
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3858
|
+
/**
|
|
3859
|
+
* Una sola richiesta dove non c'e' vincolo di scrittura: il body e' il documento nudo e la
|
|
3860
|
+
* risposta e' completa, quindi l'elenco delle radici e quello da cui si pesca sono lo stesso
|
|
3861
|
+
* (§6.4, §13.17). Il filtro di tipo lo applica {@link references}.
|
|
3862
|
+
*/
|
|
3863
|
+
void this.api
|
|
3864
|
+
.getReferences(definition)
|
|
3865
|
+
.then((list) => {
|
|
3866
|
+
this.unfiltered.set(list ?? []);
|
|
3867
|
+
if (!writable) {
|
|
3868
|
+
this.available.set(list ?? []);
|
|
3869
|
+
}
|
|
3870
|
+
})
|
|
3783
3871
|
.catch((error) => {
|
|
3784
3872
|
// Senza l'elenco il campo resta scrivibile a mano: e' meglio di un campo bloccato.
|
|
3785
3873
|
this.loadError.set(error instanceof Error ? error.message : String(error));
|
|
3786
|
-
this.
|
|
3874
|
+
this.unfiltered.set([]);
|
|
3875
|
+
if (!writable) {
|
|
3876
|
+
this.available.set([]);
|
|
3877
|
+
}
|
|
3878
|
+
})
|
|
3879
|
+
.finally(() => {
|
|
3880
|
+
if (!writable) {
|
|
3881
|
+
this.isLoading.set(false);
|
|
3882
|
+
}
|
|
3883
|
+
});
|
|
3884
|
+
if (!writable) {
|
|
3885
|
+
return;
|
|
3886
|
+
}
|
|
3887
|
+
/**
|
|
3888
|
+
* In un campo di destinazione le proposte sono un elenco a parte, ma le **radici** restano
|
|
3889
|
+
* quelle di `getReferences`: la scrivibilita' e' del **percorso**, non della radice, e le
|
|
3890
|
+
* radici escluse sono quelle che servono per dire la cosa giusta — un `$User.Anagrafica.Citta`
|
|
3891
|
+
* scritto a mano e' `TARGET_NOT_WRITABLE`, non un nome che non esiste.
|
|
3892
|
+
*/
|
|
3893
|
+
void this.api
|
|
3894
|
+
.getWritableReferences(definition)
|
|
3895
|
+
.then((list) => this.available.set(list ?? []))
|
|
3896
|
+
.catch((error) => {
|
|
3897
|
+
this.loadError.set(error instanceof Error ? error.message : String(error));
|
|
3898
|
+
this.available.set([]);
|
|
3787
3899
|
})
|
|
3788
3900
|
.finally(() => this.isLoading.set(false));
|
|
3789
3901
|
});
|
|
3790
3902
|
}
|
|
3903
|
+
/**
|
|
3904
|
+
* Il riferimento dichiarato piu' **lungo** che sia un prefisso del valore, sui confini del punto.
|
|
3905
|
+
*
|
|
3906
|
+
* Non basta il primo segmento: un nome dichiarato puo' contenere il punto. Le globali sono il caso
|
|
3907
|
+
* per cui la regola esiste — di `$User.Anagrafica.Citta` il dizionario dichiara `$User.Anagrafica`,
|
|
3908
|
+
* e cercare la corrispondenza esatta di tutto cio' che segue lo scope segnalerebbe come rotto un
|
|
3909
|
+
* percorso giusto (§4.1: vince il prefisso dichiarato piu' lungo).
|
|
3910
|
+
*/
|
|
3911
|
+
declaredPrefix = computed(() => {
|
|
3912
|
+
const value = (this.value() ?? '')?.trim();
|
|
3913
|
+
if (!value) {
|
|
3914
|
+
return undefined;
|
|
3915
|
+
}
|
|
3916
|
+
let best;
|
|
3917
|
+
for (const candidate of this.roots()) {
|
|
3918
|
+
if (value !== candidate.name && !value.startsWith(`${candidate.name}.`)) {
|
|
3919
|
+
continue;
|
|
3920
|
+
}
|
|
3921
|
+
if (!best || candidate.name.length > best.name.length) {
|
|
3922
|
+
best = candidate;
|
|
3923
|
+
}
|
|
3924
|
+
}
|
|
3925
|
+
return best;
|
|
3926
|
+
}, ...(ngDevMode ? [{ debugName: "declaredPrefix" }] : []));
|
|
3791
3927
|
/**
|
|
3792
3928
|
* La risorsa da cui il valore corrente sta navigando, quando la sua forma e' **dichiarata**: una
|
|
3793
3929
|
* `Structure` con la classe, un record con l'entita'. È cio' che rende il percorso verificabile.
|
|
@@ -3797,11 +3933,9 @@ class ReferencePickerComponent {
|
|
|
3797
3933
|
* l'editor non deve accusare cio' che il backend non verifica.
|
|
3798
3934
|
*/
|
|
3799
3935
|
navigableRoot = computed(() => {
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
}
|
|
3804
|
-
const reference = this.references().find((candidate) => candidate.name === root);
|
|
3936
|
+
// Fra tutte le radici: quella di `Richiesta.Ragione` in un campo `String` sta solo nell'elenco
|
|
3937
|
+
// non filtrato.
|
|
3938
|
+
const reference = this.declaredPrefix();
|
|
3805
3939
|
if (!reference || reference.kind === 'ElementOutput' || !reference.objectType || reference.isCollection) {
|
|
3806
3940
|
return undefined;
|
|
3807
3941
|
}
|
|
@@ -3830,6 +3964,16 @@ class ReferencePickerComponent {
|
|
|
3830
3964
|
* segmento di sola lettura non si propone in un campo di destinazione, a meno che da lì il
|
|
3831
3965
|
* percorso continui: `Richiesta.Cliente` non si assegna, ma `Richiesta.Cliente.Email` sì (§5.7).
|
|
3832
3966
|
*/
|
|
3967
|
+
/**
|
|
3968
|
+
* §5.2 — dentro una globale dell'host **niente** e' scrivibile, per quanto il membro si dichiari
|
|
3969
|
+
* tale: le globali le risolve un componente che non ha modo di scriverle, e proporle come
|
|
3970
|
+
* destinazione farebbe attivare un flow che poi fallisce davanti all'utente. L'eccezione sono i
|
|
3971
|
+
* campi del record che innesca, che si assegnano (§4.1).
|
|
3972
|
+
*/
|
|
3973
|
+
isReadOnlyPath = computed(() => {
|
|
3974
|
+
const name = this.navigableRoot()?.reference.name;
|
|
3975
|
+
return !!name && isGlobalReference(name) && referenceRoot(name) !== '$Record';
|
|
3976
|
+
}, ...(ngDevMode ? [{ debugName: "isReadOnlyPath" }] : []));
|
|
3833
3977
|
pathReferences = computed(() => {
|
|
3834
3978
|
const root = this.navigableRoot();
|
|
3835
3979
|
const tail = this.tail();
|
|
@@ -3839,7 +3983,9 @@ class ReferencePickerComponent {
|
|
|
3839
3983
|
const prefix = tail.prefix ? `${root.reference.name}.${tail.prefix}` : root.reference.name;
|
|
3840
3984
|
const kind = tail.level.container.kind === 'object' ? 'RecordField' : 'StructureMember';
|
|
3841
3985
|
return tail.level.entries
|
|
3986
|
+
.map((entry) => (this.isReadOnlyPath() ? { ...entry, isWritable: false } : entry))
|
|
3842
3987
|
.filter((entry) => !this.writableOnly() || entry.isWritable || !!entry.next)
|
|
3988
|
+
.filter((entry) => this.matchesTarget(entry))
|
|
3843
3989
|
.map((entry) => ({
|
|
3844
3990
|
name: `${prefix}.${entry.name}`,
|
|
3845
3991
|
label: entry.label ?? null,
|
|
@@ -3851,12 +3997,47 @@ class ReferencePickerComponent {
|
|
|
3851
3997
|
description: entry.isWritable ? (entry.description ?? null) : 'sola lettura',
|
|
3852
3998
|
}));
|
|
3853
3999
|
}, ...(ngDevMode ? [{ debugName: "pathReferences" }] : []));
|
|
4000
|
+
/**
|
|
4001
|
+
* Lo stesso filtro delle radici (§6.4), applicato ai segmenti di un percorso: in un parametro
|
|
4002
|
+
* `String` non si propone un membro `Date`. Un segmento da cui si scende resta comunque proposto
|
|
4003
|
+
* anche se il suo tipo non combacia — e' la strada per arrivare al membro che combacia.
|
|
4004
|
+
*/
|
|
4005
|
+
matchesTarget(entry) {
|
|
4006
|
+
if (entry.next) {
|
|
4007
|
+
return true;
|
|
4008
|
+
}
|
|
4009
|
+
return matchesReferenceFilter(entry, {
|
|
4010
|
+
dataType: this.dataType() ?? undefined,
|
|
4011
|
+
isCollection: this.isCollection() ?? undefined,
|
|
4012
|
+
objectType: this.objectType() ?? undefined,
|
|
4013
|
+
});
|
|
4014
|
+
}
|
|
4015
|
+
/**
|
|
4016
|
+
* I contenitori che il filtro di tipo ha scartato: si mostrano per **entrarci**, non per
|
|
4017
|
+
* scegliersi. Quelli che il filtro ha tenuto stanno già fra le proposte normali.
|
|
4018
|
+
*/
|
|
4019
|
+
containerOptions = computed(() => {
|
|
4020
|
+
const proposed = new Set(this.references().map((reference) => reference.name));
|
|
4021
|
+
return this.unfiltered().filter((candidate) => {
|
|
4022
|
+
if (proposed.has(candidate.name) || !this.canDescend(candidate)) {
|
|
4023
|
+
return false;
|
|
4024
|
+
}
|
|
4025
|
+
// In un campo di destinazione una globale dell'host non porta a niente di assegnabile (§5.2):
|
|
4026
|
+
// proporre di entrarci sarebbe un vicolo cieco. Resta nelle radici, per il messaggio giusto.
|
|
4027
|
+
return !(this.writableOnly() &&
|
|
4028
|
+
isGlobalReference(candidate.name) &&
|
|
4029
|
+
referenceRoot(candidate.name) !== '$Record');
|
|
4030
|
+
});
|
|
4031
|
+
}, ...(ngDevMode ? [{ debugName: "containerOptions" }] : []));
|
|
4032
|
+
/** `true` → questa riga apre il percorso e non e' un valore valido per il campo. */
|
|
4033
|
+
isContainerOnly(reference) {
|
|
4034
|
+
return this.containerOptions().some((candidate) => candidate.name === reference.name);
|
|
4035
|
+
}
|
|
3854
4036
|
options = computed(() => {
|
|
3855
4037
|
const needle = this.query()?.trim().toLowerCase();
|
|
3856
|
-
// Le globali assegnabili — `$Flow.CurrentStage
|
|
3857
|
-
//
|
|
3858
|
-
|
|
3859
|
-
const all = [...this.references(), ...this.pathReferences()];
|
|
4038
|
+
// Le due globali assegnabili — `$Flow.CurrentStage` e `$Flow.ActiveStages` — le comprende già
|
|
4039
|
+
// `POST /flows/references/writable`: aggiungerle qui le duplicherebbe (§5.2, §6.4).
|
|
4040
|
+
const all = [...this.references(), ...this.containerOptions(), ...this.pathReferences()];
|
|
3860
4041
|
if (!needle) {
|
|
3861
4042
|
return all;
|
|
3862
4043
|
}
|
|
@@ -3913,11 +4094,16 @@ class ReferencePickerComponent {
|
|
|
3913
4094
|
* elemento): il percorso resta fuori dalla validazione, quindi si accetta senza segnalare;
|
|
3914
4095
|
* - `host`: scope del sistema ospite che **non dichiara percorsi**: non verificabile (§4.1);
|
|
3915
4096
|
* - `member`: percorso dentro una forma dichiarata, verificato fino in fondo (§4.4, §4.7.1);
|
|
4097
|
+
* - `containerRoot`: il contenitore da solo in un campo che vuole un altro tipo: il valore e' un
|
|
4098
|
+
* suo membro, non lui;
|
|
3916
4099
|
* - `memberUnknown`: un segmento non esiste nella sua tappa: `STRUCTURE_MEMBER_UNKNOWN` su una
|
|
3917
4100
|
* classe, `FIELD_UNKNOWN` su un'entita', e a differenza di prima si sa per certo;
|
|
3918
4101
|
* - `memberNotWritable`: il segmento esiste, non e' scrivibile, e questo campo vuole una
|
|
3919
4102
|
* destinazione: `TARGET_NOT_WRITABLE`;
|
|
3920
4103
|
* - `pathUnverified`: la catena si interrompe (`PATH_NOT_VERIFIABLE`): avviso, non errore;
|
|
4104
|
+
* - `globalPathUntyped`: percorso globale che porta un contenitore senza dichiararne il tipo:
|
|
4105
|
+
* `PATH_NOT_VERIFIABLE`, e la correzione sta nel catalogo dell'host, non nel flow (§4.1);
|
|
4106
|
+
* - `globalPathInvalid`: si naviga un percorso globale scalare o una collection: `GLOBAL_UNKNOWN`;
|
|
3921
4107
|
* - `unknown`: nessuna corrispondenza: si avvisa, senza bloccare. Ci finisce anche il
|
|
3922
4108
|
* percorso inesistente di uno scope che i percorsi li dichiara, perche' lì il backend
|
|
3923
4109
|
* risponderebbe `GLOBAL_UNKNOWN`.
|
|
@@ -3937,6 +4123,11 @@ class ReferencePickerComponent {
|
|
|
3937
4123
|
}
|
|
3938
4124
|
// Dentro una forma dichiarata il percorso e' verificabile: dirlo "non verificato" sarebbe falso.
|
|
3939
4125
|
if (this.navigableRoot()) {
|
|
4126
|
+
// Il contenitore da solo: non e' fra le proposte (lo avrebbe intercettato `known`), quindi il
|
|
4127
|
+
// filtro del campo lo ha scartato — e il valore giusto e' un suo membro, non lui.
|
|
4128
|
+
if (!this.navigatedPath()) {
|
|
4129
|
+
return 'containerRoot';
|
|
4130
|
+
}
|
|
3940
4131
|
const resolution = this.resolution();
|
|
3941
4132
|
switch (resolution?.status) {
|
|
3942
4133
|
case 'unknown':
|
|
@@ -3944,13 +4135,30 @@ class ReferencePickerComponent {
|
|
|
3944
4135
|
case 'unverifiable':
|
|
3945
4136
|
return 'pathUnverified';
|
|
3946
4137
|
case 'resolved':
|
|
3947
|
-
return this.writableOnly() && !resolution.last?.isWritable
|
|
4138
|
+
return this.writableOnly() && (this.isReadOnlyPath() || !resolution.last?.isWritable)
|
|
4139
|
+
? 'memberNotWritable'
|
|
4140
|
+
: 'member';
|
|
3948
4141
|
default:
|
|
3949
4142
|
// Percorso ancora aperto (`Richiesta.`) o risposta non arrivata: non si accusa nessuno.
|
|
3950
4143
|
return 'empty';
|
|
3951
4144
|
}
|
|
3952
4145
|
}
|
|
3953
|
-
|
|
4146
|
+
/**
|
|
4147
|
+
* §4.1 — un percorso globale dichiarato che non porta a un contenitore. I due esiti sono
|
|
4148
|
+
* diversi e vanno detti diversi: senza `objectType` si sa che c'e' un contenitore ma non di che
|
|
4149
|
+
* tipo (`PATH_NOT_VERIFIABLE`, avviso), mentre su uno scalare o una collection non c'e'
|
|
4150
|
+
* proprio niente da navigare (`GLOBAL_UNKNOWN`, errore).
|
|
4151
|
+
*/
|
|
4152
|
+
const prefix = this.declaredPrefix();
|
|
4153
|
+
if (prefix && prefix.name !== value && isGlobalReference(prefix.name)) {
|
|
4154
|
+
const isContainer = this.dictionaries.isStructure(prefix.dataType) || prefix.dataType === 'Object';
|
|
4155
|
+
return isContainer && !prefix.objectType && !prefix.isCollection
|
|
4156
|
+
? 'globalPathUntyped'
|
|
4157
|
+
: 'globalPathInvalid';
|
|
4158
|
+
}
|
|
4159
|
+
// Fra **tutte** le radici: `Leggi_Ordine` non e' un `String`, ma `Leggi_Ordine.Numero` sì, e
|
|
4160
|
+
// accusarlo di non esistere sarebbe falso (§4.5).
|
|
4161
|
+
if (this.roots().some((reference) => reference.name === root)) {
|
|
3954
4162
|
return 'navigated';
|
|
3955
4163
|
}
|
|
3956
4164
|
// Se l'elenco non e' arrivato, non si accusa nessuno.
|
|
@@ -3976,6 +4184,10 @@ class ReferencePickerComponent {
|
|
|
3976
4184
|
const what = this.tailIsObject() ? 'Campo' : 'Membro';
|
|
3977
4185
|
return `${what} di ${this.tailContainer()}: il percorso e’ verificato fino in fondo.`;
|
|
3978
4186
|
}
|
|
4187
|
+
case 'containerRoot':
|
|
4188
|
+
return this.tailIsObject()
|
|
4189
|
+
? 'Questo e’ il record intero, e il campo vuole un altro tipo: scegli un suo campo.'
|
|
4190
|
+
: 'Questa e’ l’istanza di classe, e il campo vuole un altro tipo: scegli un suo membro.';
|
|
3979
4191
|
case 'memberUnknown': {
|
|
3980
4192
|
if (!resolution) {
|
|
3981
4193
|
return null;
|
|
@@ -3988,19 +4200,29 @@ class ReferencePickerComponent {
|
|
|
3988
4200
|
return names ? `${head} Disponibili: ${names}.` : head;
|
|
3989
4201
|
}
|
|
3990
4202
|
case 'memberNotWritable':
|
|
4203
|
+
// Il motivo cambia la correzione: dentro una globale non c'e' niente da rendere scrivibile.
|
|
4204
|
+
if (this.isReadOnlyPath()) {
|
|
4205
|
+
return 'Le variabili globali sono di sola lettura, membri compresi: assegnarlo e’ TARGET_NOT_WRITABLE.';
|
|
4206
|
+
}
|
|
3991
4207
|
return this.tailIsObject()
|
|
3992
4208
|
? 'Questo campo non si scrive ne’ creando ne’ aggiornando un record: assegnarlo e’ TARGET_NOT_WRITABLE.'
|
|
3993
4209
|
: 'Questo membro e’ calcolato: si legge, ma assegnarlo e’ TARGET_NOT_WRITABLE.';
|
|
3994
4210
|
case 'pathUnverified':
|
|
3995
4211
|
return resolution ? pathNotVerifiableMessage(resolution) : null;
|
|
4212
|
+
case 'globalPathUntyped':
|
|
4213
|
+
return `«${this.declaredPrefix()?.name}» porta un contenitore, ma il catalogo non ne dichiara il tipo: il percorso non e’ verificabile (PATH_NOT_VERIFIABLE).`;
|
|
4214
|
+
case 'globalPathInvalid':
|
|
4215
|
+
return `«${this.declaredPrefix()?.name}» e’ un valore, non un contenitore: dopo il punto non c’e’ niente da navigare (GLOBAL_UNKNOWN).`;
|
|
3996
4216
|
case 'unknown':
|
|
3997
4217
|
return 'Questo nome non e’ fra i riferimenti disponibili: la validazione lo segnalerebbe.';
|
|
3998
4218
|
default:
|
|
3999
4219
|
return null;
|
|
4000
4220
|
}
|
|
4001
4221
|
}, ...(ngDevMode ? [{ debugName: "hint" }] : []));
|
|
4002
|
-
/**
|
|
4003
|
-
|
|
4222
|
+
/** Gli stati che sono avvisi: «non lo so», o un valore da correggere che non blocca. */
|
|
4223
|
+
isHintWarning = computed(() => ['unknown', 'containerRoot', 'globalPathUntyped'].includes(this.valueState()), ...(ngDevMode ? [{ debugName: "isHintWarning" }] : []));
|
|
4224
|
+
/** Gli stati che sono errori e non avvisi: il colore deve dirlo. */
|
|
4225
|
+
isHintError = computed(() => ['memberUnknown', 'memberNotWritable', 'globalPathInvalid'].includes(this.valueState()), ...(ngDevMode ? [{ debugName: "isHintError" }] : []));
|
|
4004
4226
|
errorMessage = computed(() => this.loadError(), ...(ngDevMode ? [{ debugName: "errorMessage" }] : []));
|
|
4005
4227
|
open() {
|
|
4006
4228
|
if (this.disabled()) {
|
|
@@ -4019,6 +4241,11 @@ class ReferencePickerComponent {
|
|
|
4019
4241
|
this.query.set(value);
|
|
4020
4242
|
}
|
|
4021
4243
|
choose(reference) {
|
|
4244
|
+
// Un contenitore fuori tipo non e' un valore: cliccarlo vuol dire entrarci.
|
|
4245
|
+
if (this.isContainerOnly(reference)) {
|
|
4246
|
+
this.descend(reference);
|
|
4247
|
+
return;
|
|
4248
|
+
}
|
|
4022
4249
|
this.valueChange.emit(reference.name);
|
|
4023
4250
|
this.close();
|
|
4024
4251
|
}
|
|
@@ -4056,14 +4283,18 @@ class ReferencePickerComponent {
|
|
|
4056
4283
|
if (reference.description) {
|
|
4057
4284
|
parts.push(reference.description);
|
|
4058
4285
|
}
|
|
4286
|
+
if (this.isContainerOnly(reference)) {
|
|
4287
|
+
// Dirlo: la riga c'e' ma il valore non e' quello, e senza spiegazione sembra un errore.
|
|
4288
|
+
parts.push(reference.dataType === 'Object' ? 'entra e scegli un campo' : 'entra e scegli un membro');
|
|
4289
|
+
}
|
|
4059
4290
|
return parts.join(' · ');
|
|
4060
4291
|
}
|
|
4061
4292
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.27", ngImport: i0, type: ReferencePickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4062
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.27", type: ReferencePickerComponent, isStandalone: true, selector: "fb-reference-picker", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, dataType: { classPropertyName: "dataType", publicName: "dataType", isSignal: true, isRequired: false, transformFunction: null }, isCollection: { classPropertyName: "isCollection", publicName: "isCollection", isSignal: true, isRequired: false, transformFunction: null }, objectType: { classPropertyName: "objectType", publicName: "objectType", isSignal: true, isRequired: false, transformFunction: null }, writableOnly: { classPropertyName: "writableOnly", publicName: "writableOnly", isSignal: true, isRequired: false, transformFunction: null }, elementsOnly: { classPropertyName: "elementsOnly", publicName: "elementsOnly", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange" }, ngImport: i0, template: "<div class=\"fb-ref\" [class.fb-ref--open]=\"isOpen()\">\r\n <div class=\"fb-ref__control\">\r\n <input\r\n class=\"fb-ref__input\"\r\n type=\"text\"\r\n [value]=\"value() || ''\"\r\n [placeholder]=\"placeholder()\"\r\n [disabled]=\"disabled()\"\r\n [attr.aria-label]=\"label()\"\r\n (input)=\"onManualInput($any($event.target).value)\"\r\n (focus)=\"open()\"\r\n />\r\n <button\r\n type=\"button\"\r\n class=\"fb-ref__toggle\"\r\n [disabled]=\"disabled()\"\r\n [attr.aria-expanded]=\"isOpen()\"\r\n aria-label=\"Mostra i riferimenti disponibili\"\r\n (click)=\"toggle()\"\r\n >\r\n \u25BE\r\n </button>\r\n @if (value()) {\r\n <button type=\"button\" class=\"fb-ref__clear\" aria-label=\"Svuota\" (click)=\"clear()\">\u00D7</button>\r\n }\r\n </div>\r\n\r\n @if (hint()) {\r\n <!-- Un membro inesistente o non scrivibile e' un **errore**: mostrarlo come avviso direbbe il falso. -->\r\n <p\r\n class=\"fb-ref__hint\"\r\n [class.fb-ref__hint--warn]=\"
|
|
4293
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.27", type: ReferencePickerComponent, isStandalone: true, selector: "fb-reference-picker", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, dataType: { classPropertyName: "dataType", publicName: "dataType", isSignal: true, isRequired: false, transformFunction: null }, isCollection: { classPropertyName: "isCollection", publicName: "isCollection", isSignal: true, isRequired: false, transformFunction: null }, objectType: { classPropertyName: "objectType", publicName: "objectType", isSignal: true, isRequired: false, transformFunction: null }, writableOnly: { classPropertyName: "writableOnly", publicName: "writableOnly", isSignal: true, isRequired: false, transformFunction: null }, elementsOnly: { classPropertyName: "elementsOnly", publicName: "elementsOnly", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange" }, ngImport: i0, template: "<div class=\"fb-ref\" [class.fb-ref--open]=\"isOpen()\">\r\n <div class=\"fb-ref__control\">\r\n <input\r\n class=\"fb-ref__input\"\r\n type=\"text\"\r\n [value]=\"value() || ''\"\r\n [placeholder]=\"placeholder()\"\r\n [disabled]=\"disabled()\"\r\n [attr.aria-label]=\"label()\"\r\n (input)=\"onManualInput($any($event.target).value)\"\r\n (focus)=\"open()\"\r\n />\r\n <button\r\n type=\"button\"\r\n class=\"fb-ref__toggle\"\r\n [disabled]=\"disabled()\"\r\n [attr.aria-expanded]=\"isOpen()\"\r\n aria-label=\"Mostra i riferimenti disponibili\"\r\n (click)=\"toggle()\"\r\n >\r\n \u25BE\r\n </button>\r\n @if (value()) {\r\n <button type=\"button\" class=\"fb-ref__clear\" aria-label=\"Svuota\" (click)=\"clear()\">\u00D7</button>\r\n }\r\n </div>\r\n\r\n @if (hint()) {\r\n <!-- Un membro inesistente o non scrivibile e' un **errore**: mostrarlo come avviso direbbe il falso. -->\r\n <p\r\n class=\"fb-ref__hint\"\r\n [class.fb-ref__hint--warn]=\"isHintWarning()\"\r\n [class.fb-ref__hint--error]=\"isHintError()\"\r\n >\r\n {{ hint() }}\r\n </p>\r\n }\r\n @if (errorMessage()) {\r\n <p class=\"fb-ref__hint fb-ref__hint--warn\">\r\n Elenco dei riferimenti non disponibile: puoi scrivere il nome a mano.\r\n </p>\r\n }\r\n\r\n @if (isOpen()) {\r\n <div class=\"fb-ref__panel\" role=\"listbox\">\r\n <input\r\n class=\"fb-ref__search\"\r\n type=\"search\"\r\n placeholder=\"Filtra\"\r\n aria-label=\"Filtra i riferimenti\"\r\n (input)=\"onQuery($any($event.target).value)\"\r\n />\r\n @if (groups().length === 0) {\r\n <p class=\"fb-ref__empty\">Nessun riferimento compatibile.</p>\r\n }\r\n @for (group of groups(); track group.kind) {\r\n <div class=\"fb-ref__group\">\r\n <span class=\"fb-ref__group-label\">{{ group.label }}</span>\r\n @for (item of group.items; track item.name) {\r\n <!-- Due bottoni dove il percorso continua: scegliere il riferimento e scendere di un\r\n livello sono cose diverse, e su una Structure o un record servono entrambe. -->\r\n <div class=\"fb-ref__row\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-ref__option\"\r\n role=\"option\"\r\n [attr.aria-selected]=\"item.name === value()\"\r\n [title]=\"isContainerOnly(item) ? 'Il tipo non e\u2019 quello del campo: entra e scegli dentro' : ''\"\r\n (click)=\"choose(item)\"\r\n >\r\n <span class=\"fb-ref__name\">{{ item.name }}</span>\r\n <span class=\"fb-ref__meta\">{{ describe(item) }}</span>\r\n </button>\r\n @if (canDescend(item)) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-ref__into\"\r\n [attr.aria-label]=\"'Entra in ' + item.name\"\r\n title=\"Entra e proponi i segmenti\"\r\n (click)=\"descend(item)\"\r\n >\r\n \u203A\r\n </button>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n <button type=\"button\" class=\"fb-ref__close\" (click)=\"close()\">Chiudi</button>\r\n </div>\r\n }\r\n</div>\r\n", styles: [":host{display:block}.fb-ref{position:relative}.fb-ref__control{display:flex;align-items:stretch;gap:0;border:1px solid var(--fb-border, #d6dae1);border-radius:6px;background:var(--fb-surface, #fff);overflow:hidden}.fb-ref--open .fb-ref__control{border-color:var(--fb-accent, #2f6feb)}.fb-ref__input{flex:1;min-width:0;padding:5px 7px;border:0;background:transparent;color:var(--fb-text, #1d2939);font:inherit;font-size:12px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.fb-ref__input:focus-visible{outline:none}.fb-ref__toggle,.fb-ref__clear{flex:0 0 auto;padding:0 7px;border:0;border-left:1px solid var(--fb-border-subtle, #e6e9ee);background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:12px;cursor:pointer}.fb-ref__toggle:hover,.fb-ref__clear:hover{background:var(--fb-surface-alt, #f8f9fb)}.fb-ref__hint{margin:3px 0 0;font-size:10px;line-height:1.35;color:var(--fb-text-subtle, #98a2b3)}.fb-ref__hint--warn{color:var(--fb-warning, #b7791f)}.fb-ref__hint--error{color:var(--fb-error, #c9372c)}.fb-ref__panel{position:absolute;z-index:30;top:calc(100% + 3px);left:0;right:0;max-height:260px;overflow-y:auto;padding:6px;border:1px solid var(--fb-border, #d6dae1);border-radius:6px;background:var(--fb-surface, #fff);box-shadow:0 6px 18px #10182829}.fb-ref__search{box-sizing:border-box;width:100%;margin-bottom:6px;padding:4px 6px;border:1px solid var(--fb-border, #d6dae1);border-radius:5px;font:inherit;font-size:12px}.fb-ref__group{margin-bottom:6px}.fb-ref__group-label{display:block;padding:2px 4px;font-size:9px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;color:var(--fb-text-subtle, #98a2b3)}.fb-ref__row{display:flex;align-items:stretch;gap:2px}.fb-ref__into{flex:0 0 auto;padding:0 7px;border:0;border-radius:4px;background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:13px;cursor:pointer}.fb-ref__into:hover{background:var(--fb-surface-alt, #f8f9fb);color:var(--fb-accent, #2f6feb)}.fb-ref__option{display:flex;flex:1;flex-direction:column;min-width:0;width:100%;padding:4px 6px;border:0;border-radius:4px;background:transparent;color:var(--fb-text, #1d2939);font:inherit;text-align:left;cursor:pointer}.fb-ref__option:hover,.fb-ref__option[aria-selected=true]{background:var(--fb-surface-alt, #f8f9fb)}.fb-ref__name{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px}.fb-ref__meta{font-size:10px;color:var(--fb-text-muted, #667085)}.fb-ref__empty{margin:4px;font-size:11px;color:var(--fb-text-muted, #667085)}.fb-ref__close{width:100%;margin-top:4px;padding:4px;border:0;border-top:1px solid var(--fb-border-subtle, #e6e9ee);background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:11px;cursor:pointer}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4063
4294
|
}
|
|
4064
4295
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImport: i0, type: ReferencePickerComponent, decorators: [{
|
|
4065
4296
|
type: Component,
|
|
4066
|
-
args: [{ selector: 'fb-reference-picker', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"fb-ref\" [class.fb-ref--open]=\"isOpen()\">\r\n <div class=\"fb-ref__control\">\r\n <input\r\n class=\"fb-ref__input\"\r\n type=\"text\"\r\n [value]=\"value() || ''\"\r\n [placeholder]=\"placeholder()\"\r\n [disabled]=\"disabled()\"\r\n [attr.aria-label]=\"label()\"\r\n (input)=\"onManualInput($any($event.target).value)\"\r\n (focus)=\"open()\"\r\n />\r\n <button\r\n type=\"button\"\r\n class=\"fb-ref__toggle\"\r\n [disabled]=\"disabled()\"\r\n [attr.aria-expanded]=\"isOpen()\"\r\n aria-label=\"Mostra i riferimenti disponibili\"\r\n (click)=\"toggle()\"\r\n >\r\n \u25BE\r\n </button>\r\n @if (value()) {\r\n <button type=\"button\" class=\"fb-ref__clear\" aria-label=\"Svuota\" (click)=\"clear()\">\u00D7</button>\r\n }\r\n </div>\r\n\r\n @if (hint()) {\r\n <!-- Un membro inesistente o non scrivibile e' un **errore**: mostrarlo come avviso direbbe il falso. -->\r\n <p\r\n class=\"fb-ref__hint\"\r\n [class.fb-ref__hint--warn]=\"
|
|
4297
|
+
args: [{ selector: 'fb-reference-picker', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"fb-ref\" [class.fb-ref--open]=\"isOpen()\">\r\n <div class=\"fb-ref__control\">\r\n <input\r\n class=\"fb-ref__input\"\r\n type=\"text\"\r\n [value]=\"value() || ''\"\r\n [placeholder]=\"placeholder()\"\r\n [disabled]=\"disabled()\"\r\n [attr.aria-label]=\"label()\"\r\n (input)=\"onManualInput($any($event.target).value)\"\r\n (focus)=\"open()\"\r\n />\r\n <button\r\n type=\"button\"\r\n class=\"fb-ref__toggle\"\r\n [disabled]=\"disabled()\"\r\n [attr.aria-expanded]=\"isOpen()\"\r\n aria-label=\"Mostra i riferimenti disponibili\"\r\n (click)=\"toggle()\"\r\n >\r\n \u25BE\r\n </button>\r\n @if (value()) {\r\n <button type=\"button\" class=\"fb-ref__clear\" aria-label=\"Svuota\" (click)=\"clear()\">\u00D7</button>\r\n }\r\n </div>\r\n\r\n @if (hint()) {\r\n <!-- Un membro inesistente o non scrivibile e' un **errore**: mostrarlo come avviso direbbe il falso. -->\r\n <p\r\n class=\"fb-ref__hint\"\r\n [class.fb-ref__hint--warn]=\"isHintWarning()\"\r\n [class.fb-ref__hint--error]=\"isHintError()\"\r\n >\r\n {{ hint() }}\r\n </p>\r\n }\r\n @if (errorMessage()) {\r\n <p class=\"fb-ref__hint fb-ref__hint--warn\">\r\n Elenco dei riferimenti non disponibile: puoi scrivere il nome a mano.\r\n </p>\r\n }\r\n\r\n @if (isOpen()) {\r\n <div class=\"fb-ref__panel\" role=\"listbox\">\r\n <input\r\n class=\"fb-ref__search\"\r\n type=\"search\"\r\n placeholder=\"Filtra\"\r\n aria-label=\"Filtra i riferimenti\"\r\n (input)=\"onQuery($any($event.target).value)\"\r\n />\r\n @if (groups().length === 0) {\r\n <p class=\"fb-ref__empty\">Nessun riferimento compatibile.</p>\r\n }\r\n @for (group of groups(); track group.kind) {\r\n <div class=\"fb-ref__group\">\r\n <span class=\"fb-ref__group-label\">{{ group.label }}</span>\r\n @for (item of group.items; track item.name) {\r\n <!-- Due bottoni dove il percorso continua: scegliere il riferimento e scendere di un\r\n livello sono cose diverse, e su una Structure o un record servono entrambe. -->\r\n <div class=\"fb-ref__row\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-ref__option\"\r\n role=\"option\"\r\n [attr.aria-selected]=\"item.name === value()\"\r\n [title]=\"isContainerOnly(item) ? 'Il tipo non e\u2019 quello del campo: entra e scegli dentro' : ''\"\r\n (click)=\"choose(item)\"\r\n >\r\n <span class=\"fb-ref__name\">{{ item.name }}</span>\r\n <span class=\"fb-ref__meta\">{{ describe(item) }}</span>\r\n </button>\r\n @if (canDescend(item)) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-ref__into\"\r\n [attr.aria-label]=\"'Entra in ' + item.name\"\r\n title=\"Entra e proponi i segmenti\"\r\n (click)=\"descend(item)\"\r\n >\r\n \u203A\r\n </button>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n <button type=\"button\" class=\"fb-ref__close\" (click)=\"close()\">Chiudi</button>\r\n </div>\r\n }\r\n</div>\r\n", styles: [":host{display:block}.fb-ref{position:relative}.fb-ref__control{display:flex;align-items:stretch;gap:0;border:1px solid var(--fb-border, #d6dae1);border-radius:6px;background:var(--fb-surface, #fff);overflow:hidden}.fb-ref--open .fb-ref__control{border-color:var(--fb-accent, #2f6feb)}.fb-ref__input{flex:1;min-width:0;padding:5px 7px;border:0;background:transparent;color:var(--fb-text, #1d2939);font:inherit;font-size:12px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.fb-ref__input:focus-visible{outline:none}.fb-ref__toggle,.fb-ref__clear{flex:0 0 auto;padding:0 7px;border:0;border-left:1px solid var(--fb-border-subtle, #e6e9ee);background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:12px;cursor:pointer}.fb-ref__toggle:hover,.fb-ref__clear:hover{background:var(--fb-surface-alt, #f8f9fb)}.fb-ref__hint{margin:3px 0 0;font-size:10px;line-height:1.35;color:var(--fb-text-subtle, #98a2b3)}.fb-ref__hint--warn{color:var(--fb-warning, #b7791f)}.fb-ref__hint--error{color:var(--fb-error, #c9372c)}.fb-ref__panel{position:absolute;z-index:30;top:calc(100% + 3px);left:0;right:0;max-height:260px;overflow-y:auto;padding:6px;border:1px solid var(--fb-border, #d6dae1);border-radius:6px;background:var(--fb-surface, #fff);box-shadow:0 6px 18px #10182829}.fb-ref__search{box-sizing:border-box;width:100%;margin-bottom:6px;padding:4px 6px;border:1px solid var(--fb-border, #d6dae1);border-radius:5px;font:inherit;font-size:12px}.fb-ref__group{margin-bottom:6px}.fb-ref__group-label{display:block;padding:2px 4px;font-size:9px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;color:var(--fb-text-subtle, #98a2b3)}.fb-ref__row{display:flex;align-items:stretch;gap:2px}.fb-ref__into{flex:0 0 auto;padding:0 7px;border:0;border-radius:4px;background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:13px;cursor:pointer}.fb-ref__into:hover{background:var(--fb-surface-alt, #f8f9fb);color:var(--fb-accent, #2f6feb)}.fb-ref__option{display:flex;flex:1;flex-direction:column;min-width:0;width:100%;padding:4px 6px;border:0;border-radius:4px;background:transparent;color:var(--fb-text, #1d2939);font:inherit;text-align:left;cursor:pointer}.fb-ref__option:hover,.fb-ref__option[aria-selected=true]{background:var(--fb-surface-alt, #f8f9fb)}.fb-ref__name{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px}.fb-ref__meta{font-size:10px;color:var(--fb-text-muted, #667085)}.fb-ref__empty{margin:4px;font-size:11px;color:var(--fb-text-muted, #667085)}.fb-ref__close{width:100%;margin-top:4px;padding:4px;border:0;border-top:1px solid var(--fb-border-subtle, #e6e9ee);background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:11px;cursor:pointer}\n"] }]
|
|
4067
4298
|
}], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], dataType: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataType", required: false }] }], isCollection: [{ type: i0.Input, args: [{ isSignal: true, alias: "isCollection", required: false }] }], objectType: [{ type: i0.Input, args: [{ isSignal: true, alias: "objectType", required: false }] }], writableOnly: [{ type: i0.Input, args: [{ isSignal: true, alias: "writableOnly", required: false }] }], elementsOnly: [{ type: i0.Input, args: [{ isSignal: true, alias: "elementsOnly", required: false }] }], valueChange: [{ type: i0.Output, args: ["valueChange"] }] } });
|
|
4068
4299
|
|
|
4069
4300
|
/**
|
|
@@ -5140,7 +5371,7 @@ class ConditionEditorComponent {
|
|
|
5140
5371
|
return;
|
|
5141
5372
|
}
|
|
5142
5373
|
void this.api
|
|
5143
|
-
.getReferences(
|
|
5374
|
+
.getReferences(definition)
|
|
5144
5375
|
.then((list) => this.references.set(list ?? []))
|
|
5145
5376
|
// Senza l'elenco non si segnala nulla: non sapere non e' sapere che e' sbagliato.
|
|
5146
5377
|
.catch(() => this.references.set([]));
|
|
@@ -6118,9 +6349,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImpo
|
|
|
6118
6349
|
* 1. **Le operazioni sono eseguite nell'ordine dichiarato**, quindi devono essere
|
|
6119
6350
|
* riordinabili: un'interfaccia che le presenta come un insieme nasconde la semantica.
|
|
6120
6351
|
* 2. **Cosa puo' stare in `assignToReference`**: una variabile, un campo di una variabile
|
|
6121
|
-
* record, un campo di `$Record` e, fra le globali, `$Flow.CurrentStage
|
|
6122
|
-
* `$Flow.ActiveStages`
|
|
6123
|
-
*
|
|
6352
|
+
* record, un campo di `$Record` e, fra le globali, soltanto `$Flow.CurrentStage` e
|
|
6353
|
+
* `$Flow.ActiveStages` — le propone già `POST /flows/references/writable`. Tutto il resto e'
|
|
6354
|
+
* di sola lettura, comprese le globali dell'host e i loro membri (§5.2).
|
|
6124
6355
|
* 3. Gli operatori marcati `expectsCollection` hanno senso **solo** su destinazioni
|
|
6125
6356
|
* collection: su una variabile singola non falliscono, non fanno nulla.
|
|
6126
6357
|
*/
|
|
@@ -9836,5 +10067,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImpo
|
|
|
9836
10067
|
* Generated bundle index. Do not edit.
|
|
9837
10068
|
*/
|
|
9838
10069
|
|
|
9839
|
-
export { ConditionEditorComponent, ConnectorEditorComponent, DebugPanelComponent, ElementDialogComponent, ElementInspectorComponent, ElementPaletteComponent, FALLBACK_COLLECTION_BY_TYPE, FALLBACK_TYPE_LABEL, FLOW_BUILDER_HTTP_CONFIG, FLOW_ELEMENT_ICONS, FLOW_ELEMENT_VARIANT_FIELDS, FLOW_ELEMENT_VARIANT_ICONS, FLOW_ERROR_FALLBACK_MESSAGE, FLOW_ERROR_HTTP_STATUS, FLOW_NAME_PATTERN, FLOW_NODE_COLLECTIONS, FLOW_NODE_HEIGHT, FLOW_NODE_WIDTH, FLOW_RESOURCE_COLLECTIONS, FieldAssignmentEditorComponent, FieldPickerComponent, FlowApiError, FlowBuilderApi, FlowBuilderComponent, FlowCanvasComponent, FlowCatalogStore, FlowDictionaryStore, FlowDocumentStore, FlowEditorSession, FlowLayoutService, FlowValidationStore, HttpFlowBuilderApi, NamePickerComponent, NodeInspectorBase, ORCHESTRATION_CONDITION_OUTPUT, ObjectPickerComponent, OrchestratedStageInspectorComponent, ParameterEditorComponent, ProblemsPanelComponent, RecordFilterEditorComponent, ReferencePickerComponent, ResourcePanelComponent, SEVERITY_BUCKETS, SEVERITY_ICON, SEVERITY_LABEL, START_NODE_NAME, SelectValueDirective, StartInspectorComponent, StructureMemberPickerComponent, StructurePickerComponent, TYPES_WITH_AUTOMATIC_OUTPUT, TYPE_BY_COLLECTION, UNSUPPORTED_TYPES, ValueEditorComponent, VersionPanelComponent, areTypesComparable, canvasNodeId, checkConditionLogic, checkFlowName, describePathEntry, elementIcon, emptyFlowDefinition, flowNodeWidth, flowNodeWidthClass, isCustomConditionLogic, isGlobalReference, isNumericType, isTypeCheckedOperator, isValidFlowName, loadPathLevel, moveCondition, navigatePath, outletByKey, outletsOf, parseCanvasNodeId, parseInvariantNumber, parseSourceConnectorId, parseTargetConnectorId, pathAvailableNames, pathContainerLabel, pathNotVerifiableMessage, referenceRoot, remapConditionLogic, removeCondition, resolvePath, severityBucket, slugifyFlowName, sourceConnectorId, stageStepNames, stageStepOutputReferenced, stepsOf, targetConnectorId, uniqueFlowName, variantFieldOf, variantOf, variantPresetOf };
|
|
10070
|
+
export { ConditionEditorComponent, ConnectorEditorComponent, DebugPanelComponent, ElementDialogComponent, ElementInspectorComponent, ElementPaletteComponent, FALLBACK_COLLECTION_BY_TYPE, FALLBACK_TYPE_LABEL, FLOW_BUILDER_HTTP_CONFIG, FLOW_ELEMENT_ICONS, FLOW_ELEMENT_VARIANT_FIELDS, FLOW_ELEMENT_VARIANT_ICONS, FLOW_ERROR_FALLBACK_MESSAGE, FLOW_ERROR_HTTP_STATUS, FLOW_NAME_PATTERN, FLOW_NODE_COLLECTIONS, FLOW_NODE_HEIGHT, FLOW_NODE_WIDTH, FLOW_RESOURCE_COLLECTIONS, FieldAssignmentEditorComponent, FieldPickerComponent, FlowApiError, FlowBuilderApi, FlowBuilderComponent, FlowCanvasComponent, FlowCatalogStore, FlowDictionaryStore, FlowDocumentStore, FlowEditorSession, FlowLayoutService, FlowValidationStore, HttpFlowBuilderApi, NamePickerComponent, NodeInspectorBase, ORCHESTRATION_CONDITION_OUTPUT, ObjectPickerComponent, OrchestratedStageInspectorComponent, ParameterEditorComponent, ProblemsPanelComponent, RecordFilterEditorComponent, ReferencePickerComponent, ResourcePanelComponent, SEVERITY_BUCKETS, SEVERITY_ICON, SEVERITY_LABEL, START_NODE_NAME, SelectValueDirective, StartInspectorComponent, StructureMemberPickerComponent, StructurePickerComponent, TYPES_WITH_AUTOMATIC_OUTPUT, TYPE_BY_COLLECTION, UNSUPPORTED_TYPES, ValueEditorComponent, VersionPanelComponent, areTypesComparable, canvasNodeId, checkConditionLogic, checkFlowName, describePathEntry, elementIcon, emptyFlowDefinition, filterReferences, flowNodeWidth, flowNodeWidthClass, isCustomConditionLogic, isEmptyReferenceFilter, isGlobalReference, isNumericType, isTypeCheckedOperator, isValidFlowName, loadPathLevel, matchesReferenceFilter, moveCondition, navigatePath, outletByKey, outletsOf, parseCanvasNodeId, parseInvariantNumber, parseSourceConnectorId, parseTargetConnectorId, pathAvailableNames, pathContainerLabel, pathNotVerifiableMessage, referenceRoot, remapConditionLogic, removeCondition, resolvePath, severityBucket, slugifyFlowName, sourceConnectorId, stageStepNames, stageStepOutputReferenced, stepsOf, targetConnectorId, uniqueFlowName, variantFieldOf, variantOf, variantPresetOf };
|
|
9840
10071
|
//# sourceMappingURL=esfaenza-flow-builder.mjs.map
|