@gnggln/ng-ui-system 1.0.0-alpha.20 → 1.0.0-alpha.21
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/crud-table/lib/components/form-builder/form-builder.component.d.ts +6 -2
- package/crud-table/lib/components/form-builder/types/field.types.d.ts +5 -0
- package/crud-table/lib/components/form-builder/types/validation.types.d.ts +5 -0
- package/esm2022/crud-table/lib/components/form-builder/form-builder.component.mjs +42 -9
- package/esm2022/crud-table/lib/components/form-builder/types/field.types.mjs +1 -1
- package/esm2022/crud-table/lib/components/form-builder/types/validation.types.mjs +6 -2
- package/esm2022/form-builder/lib/components/form-builder/form-builder.component.mjs +42 -9
- package/esm2022/form-builder/lib/components/form-builder/types/field.types.mjs +1 -1
- package/esm2022/form-builder/lib/components/form-builder/types/index.mjs +3 -2
- package/esm2022/form-builder/lib/components/form-builder/types/validation.types.mjs +6 -2
- package/esm2022/form-builder-editor/lib/components/form-builder/form-builder.component.mjs +42 -9
- package/esm2022/form-builder-editor/lib/components/form-builder/types/field.types.mjs +1 -1
- package/esm2022/form-builder-editor/lib/components/form-builder/types/index.mjs +3 -2
- package/esm2022/form-builder-editor/lib/components/form-builder/types/validation.types.mjs +6 -2
- package/esm2022/lib/components/form-builder/form-builder.component.mjs +42 -9
- package/esm2022/lib/components/form-builder/types/field.types.mjs +1 -1
- package/esm2022/lib/components/form-builder/types/index.mjs +3 -2
- package/esm2022/lib/components/form-builder/types/validation.types.mjs +6 -2
- package/esm2022/lib/version/ng-ui-system-version.mjs +2 -2
- package/fesm2022/gnggln-ng-ui-system-crud-table.mjs +50 -8
- package/fesm2022/gnggln-ng-ui-system-crud-table.mjs.map +1 -1
- package/fesm2022/gnggln-ng-ui-system-form-builder-editor.mjs +50 -8
- package/fesm2022/gnggln-ng-ui-system-form-builder-editor.mjs.map +1 -1
- package/fesm2022/gnggln-ng-ui-system-form-builder.mjs +53 -9
- package/fesm2022/gnggln-ng-ui-system-form-builder.mjs.map +1 -1
- package/fesm2022/gnggln-ng-ui-system.mjs +54 -10
- package/fesm2022/gnggln-ng-ui-system.mjs.map +1 -1
- package/form-builder/lib/components/form-builder/form-builder.component.d.ts +6 -2
- package/form-builder/lib/components/form-builder/types/field.types.d.ts +5 -0
- package/form-builder/lib/components/form-builder/types/index.d.ts +1 -1
- package/form-builder/lib/components/form-builder/types/validation.types.d.ts +5 -0
- package/form-builder-editor/lib/components/form-builder/form-builder.component.d.ts +6 -2
- package/form-builder-editor/lib/components/form-builder/types/field.types.d.ts +5 -0
- package/form-builder-editor/lib/components/form-builder/types/index.d.ts +1 -1
- package/form-builder-editor/lib/components/form-builder/types/validation.types.d.ts +5 -0
- package/lib/components/form-builder/form-builder.component.d.ts +6 -2
- package/lib/components/form-builder/types/field.types.d.ts +5 -0
- package/lib/components/form-builder/types/index.d.ts +1 -1
- package/lib/components/form-builder/types/validation.types.d.ts +5 -0
- package/lib/version/ng-ui-system-version.d.ts +1 -1
- package/package.json +7 -7
|
@@ -63,7 +63,7 @@ import { MatCardModule } from '@angular/material/card';
|
|
|
63
63
|
* Versione del pacchetto `@gnggln/ng-ui-system` (allineata a `packages/ng-ui-system/package.json`).
|
|
64
64
|
* Non modificare manualmente: eseguire `npm run version:sync` dalla root del workspace.
|
|
65
65
|
*/
|
|
66
|
-
const NG_UI_SYSTEM_VERSION = '1.0.0-alpha.
|
|
66
|
+
const NG_UI_SYSTEM_VERSION = '1.0.0-alpha.20';
|
|
67
67
|
/**
|
|
68
68
|
* Etichetta da mostrare in UI (prefisso `v` + SemVer npm).
|
|
69
69
|
*/
|
|
@@ -2896,6 +2896,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
2896
2896
|
type: Output
|
|
2897
2897
|
}] } });
|
|
2898
2898
|
|
|
2899
|
+
/**
|
|
2900
|
+
* @module ng-ui-system/form-builder
|
|
2901
|
+
* Tipi relativi alla validazione dei campi.
|
|
2902
|
+
*/
|
|
2903
|
+
/**
|
|
2904
|
+
* Chiave `ValidationErrors` usata dai CVA custom per esporre errori granulari
|
|
2905
|
+
* sui campi interni (atomici) verso l'error-summary del form-builder.
|
|
2906
|
+
*/
|
|
2907
|
+
const UI_FORM_NESTED_FIELD_ERRORS_KEY = 'uiNestedFieldErrors';
|
|
2908
|
+
|
|
2899
2909
|
/**
|
|
2900
2910
|
* Servizio per la valutazione delle condizioni di visibilita,
|
|
2901
2911
|
* required e disable dei campi del form.
|
|
@@ -48891,7 +48901,7 @@ class UiFormBuilderComponent {
|
|
|
48891
48901
|
/**
|
|
48892
48902
|
* Contesto passato a `ngTemplateOutlet` per il campo custom (CVA sul `control`).
|
|
48893
48903
|
*/
|
|
48894
|
-
getCustomFieldOutletContext(field, fg) {
|
|
48904
|
+
getCustomFieldOutletContext(field, fg, nestedErrorKeyPrefix) {
|
|
48895
48905
|
const control = fg.get(field.key);
|
|
48896
48906
|
return {
|
|
48897
48907
|
$implicit: field,
|
|
@@ -48899,6 +48909,7 @@ class UiFormBuilderComponent {
|
|
|
48899
48909
|
formGroup: fg,
|
|
48900
48910
|
control,
|
|
48901
48911
|
config: field.customConfig?.config ?? {},
|
|
48912
|
+
nestedErrorKeyPrefix,
|
|
48902
48913
|
emitCustomEvent: (event, data) => {
|
|
48903
48914
|
this.customEvent.emit({ field: field.key, event, data });
|
|
48904
48915
|
},
|
|
@@ -48907,11 +48918,34 @@ class UiFormBuilderComponent {
|
|
|
48907
48918
|
/**
|
|
48908
48919
|
* Template e contesto in un solo oggetto (evita doppie valutazioni nel template).
|
|
48909
48920
|
*/
|
|
48910
|
-
getCustomFieldOutlet(field, fg) {
|
|
48921
|
+
getCustomFieldOutlet(field, fg, nestedErrorKeyPrefix) {
|
|
48911
48922
|
const template = this.getCustomTemplateRef(field);
|
|
48912
48923
|
if (!template)
|
|
48913
48924
|
return null;
|
|
48914
|
-
return { template, context: this.getCustomFieldOutletContext(field, fg) };
|
|
48925
|
+
return { template, context: this.getCustomFieldOutletContext(field, fg, nestedErrorKeyPrefix) };
|
|
48926
|
+
}
|
|
48927
|
+
/** Prefisso chiavi errore per campi custom annidati (flat o repeatable). */
|
|
48928
|
+
buildNestedErrorKeyPrefix(fieldKey, instanceKeyPrefix) {
|
|
48929
|
+
const base = instanceKeyPrefix ? `${instanceKeyPrefix}${fieldKey}` : fieldKey;
|
|
48930
|
+
return `${base}__`;
|
|
48931
|
+
}
|
|
48932
|
+
/** Merge errori nested da CVA custom nel dettaglio error-summary. */
|
|
48933
|
+
appendCustomNestedFieldErrors(details, field, control, labelPrefix, nestedErrorKeyPrefix) {
|
|
48934
|
+
if (field.type !== 'custom')
|
|
48935
|
+
return;
|
|
48936
|
+
const nested = control.errors?.[UI_FORM_NESTED_FIELD_ERRORS_KEY];
|
|
48937
|
+
if (!Array.isArray(nested) || nested.length === 0)
|
|
48938
|
+
return;
|
|
48939
|
+
for (const item of nested) {
|
|
48940
|
+
if (!item?.errors?.length)
|
|
48941
|
+
continue;
|
|
48942
|
+
const relativeKey = item.fieldKey ?? '';
|
|
48943
|
+
details.push({
|
|
48944
|
+
fieldKey: relativeKey ? `${nestedErrorKeyPrefix}${relativeKey}` : nestedErrorKeyPrefix.slice(0, -2),
|
|
48945
|
+
fieldLabel: `${labelPrefix} > ${item.fieldLabel}`,
|
|
48946
|
+
errors: item.errors,
|
|
48947
|
+
});
|
|
48948
|
+
}
|
|
48915
48949
|
}
|
|
48916
48950
|
isFormGroupValidExcluding(group, excludedKeys) {
|
|
48917
48951
|
for (const key of Object.keys(group.controls)) {
|
|
@@ -48992,6 +49026,11 @@ class UiFormBuilderComponent {
|
|
|
48992
49026
|
const control = this.formGroup.get(field.key);
|
|
48993
49027
|
if (!control?.errors)
|
|
48994
49028
|
continue;
|
|
49029
|
+
const nestedPrefix = this.buildNestedErrorKeyPrefix(field.key);
|
|
49030
|
+
this.appendCustomNestedFieldErrors(details, field, control, field.label, nestedPrefix);
|
|
49031
|
+
if (field.type === 'custom' && control.errors[UI_FORM_NESTED_FIELD_ERRORS_KEY]) {
|
|
49032
|
+
continue;
|
|
49033
|
+
}
|
|
48995
49034
|
const errors = this.getFieldErrors(field.key);
|
|
48996
49035
|
if (errors.length) {
|
|
48997
49036
|
details.push({ fieldKey: field.key, fieldLabel: field.label, errors });
|
|
@@ -49006,6 +49045,7 @@ class UiFormBuilderComponent {
|
|
|
49006
49045
|
for (let i = 0; i < arr.length; i++) {
|
|
49007
49046
|
const instanceFg = arr.at(i);
|
|
49008
49047
|
const instanceLabel = titleTemplate.replace('{index}', String(i + 1));
|
|
49048
|
+
const instanceKeyPrefix = `${section.id}[${i}].`;
|
|
49009
49049
|
for (const field of section.fields) {
|
|
49010
49050
|
if (field.type === 'flag' || field.type === 'divider')
|
|
49011
49051
|
continue;
|
|
@@ -49014,6 +49054,11 @@ class UiFormBuilderComponent {
|
|
|
49014
49054
|
const control = instanceFg.get(field.key);
|
|
49015
49055
|
if (!control?.errors)
|
|
49016
49056
|
continue;
|
|
49057
|
+
const nestedPrefix = this.buildNestedErrorKeyPrefix(field.key, instanceKeyPrefix);
|
|
49058
|
+
this.appendCustomNestedFieldErrors(details, field, control, `${instanceLabel} > ${field.label}`, nestedPrefix);
|
|
49059
|
+
if (field.type === 'custom' && control.errors[UI_FORM_NESTED_FIELD_ERRORS_KEY]) {
|
|
49060
|
+
continue;
|
|
49061
|
+
}
|
|
49017
49062
|
const errors = this.getFieldErrors(field.key, instanceFg);
|
|
49018
49063
|
if (errors.length) {
|
|
49019
49064
|
details.push({
|
|
@@ -49152,16 +49197,13 @@ class UiFormBuilderComponent {
|
|
|
49152
49197
|
}
|
|
49153
49198
|
/** Scrolla al campo e lo evidenzia. */
|
|
49154
49199
|
scrollToField(fieldKey) {
|
|
49155
|
-
// Forza lo stato dirty e touched per mostrare immediatamente gli errori
|
|
49156
49200
|
const control = this.formGroup.get(fieldKey);
|
|
49157
49201
|
if (control) {
|
|
49158
49202
|
control.markAsTouched();
|
|
49159
49203
|
control.markAsDirty();
|
|
49160
49204
|
control.updateValueAndValidity();
|
|
49161
49205
|
}
|
|
49162
|
-
// Delay per lasciare che Angular Material aggiorni il DOM
|
|
49163
49206
|
setTimeout(() => {
|
|
49164
|
-
// Cerca l'elemento tramite diversi selettori in ordine di priorita
|
|
49165
49207
|
const selectors = [`[data-field-key="${fieldKey}"]`, `[formControlName="${fieldKey}"]`];
|
|
49166
49208
|
let targetElement = null;
|
|
49167
49209
|
for (const sel of selectors) {
|
|
@@ -49375,7 +49417,7 @@ class UiFormBuilderComponent {
|
|
|
49375
49417
|
{ provide: DateAdapter, useClass: UiItalianDateAdapter },
|
|
49376
49418
|
// Formati data italiani (DD/MM/YYYY)
|
|
49377
49419
|
{ provide: MAT_DATE_FORMATS, useValue: UI_IT_DATE_FORMATS },
|
|
49378
|
-
], usesOnChanges: true, ngImport: i0, template: "<!--\r\n ============================================================\r\n UI FORM BUILDER - TEMPLATE PRINCIPALE\r\n ============================================================\r\n ARCHITETTURA:\r\n Ogni <ng-template> che contiene direttive [formControlName]\r\n DEVE wrappare il proprio contenuto in un <div [formGroup]>.\r\n Questo perche Angular risolve l'injector di formControlName\r\n dal CONTESTO DI DICHIARAZIONE del template, non dal punto\r\n di inserimento (ngTemplateOutlet).\r\n\r\n Il FormGroup viene passato tramite context per supportare\r\n sia campi piatti (root FormGroup) che campi in istanze\r\n repeatable (instance FormGroup).\r\n\r\n Senza questo wrapper, formControlName non trova il\r\n FormGroupDirective e lancia NG01050.\r\n ============================================================\r\n-->\r\n\r\n@if (schema && formGroup) {\r\n<div class=\"ui-form-builder\" [formGroup]=\"formGroup\" [class]=\"schema.config?.cssClasses?.join(' ')\">\r\n\r\n <!-- ==================== HEADER ==================== -->\r\n @if (schema.title || schema.description) {\r\n <div class=\"ui-form-builder__header\">\r\n @if (schema.title) {\r\n <h2 class=\"ui-form-builder__title\">{{ schema.title }}</h2>\r\n }\r\n @if (schema.description) {\r\n <p class=\"ui-form-builder__description\">{{ schema.description }}</p>\r\n }\r\n </div>\r\n }\r\n\r\n <!-- ==================== SEZIONI ==================== -->\r\n <div class=\"ui-form-builder__sections\">\r\n @for (section of schema.sections; track section.id) {\r\n @if (isSectionVisible(section.id)) {\r\n\r\n <!-- \u2500\u2500\u2500 Sezione REPEATABLE \u2500\u2500\u2500 -->\r\n @if (section.repeatable) {\r\n <div class=\"ui-form-builder__section ui-form-builder__section--repeatable\" [class]=\"section.cssClasses?.join(' ')\">\r\n @if (section.title || section.description) {\r\n <div class=\"ui-form-builder__section-header\">\r\n @if (section.title) {\r\n <h3 class=\"ui-form-builder__section-title\">{{ section.title }}</h3>\r\n }\r\n @if (section.description) {\r\n <p class=\"ui-form-builder__section-description\">{{ section.description }}</p>\r\n }\r\n </div>\r\n }\r\n\r\n <ui-accordion [items]=\"getRepeatableAccordionItems(section.id)\" [itemTemplate]=\"repeatableBodyTpl\"\r\n [actionsTemplate]=\"repeatableActionsTpl\" mode=\"multi\" class=\"ui-form-builder__repeatable-accordion\">\r\n <!-- Contenuto di ogni istanza -->\r\n <ng-template #repeatableBodyTpl let-item>\r\n <div class=\"ui-form-builder__section-content\"\r\n [class.ui-form-builder__grid]=\"schema.config?.layout === 'grid' || !schema.config?.layout\">\r\n @for (field of section.fields; track field.key) {\r\n @if (isFieldVisible(field.key, getInstanceFormGroup(section.id, item.index)!) && field.type !== 'flag') {\r\n <div class=\"ui-form-builder__field-wrapper\" [class]=\"getFieldWrapperClasses(field)\"\r\n [style.order]=\"field.layout?.order || null\" [attr.data-field-key]=\"field.key\">\r\n <ng-container\r\n *ngTemplateOutlet=\"fieldTpl; context: { $implicit: field, formGroup: getInstanceFormGroup(section.id, item.index) }\" />\r\n </div>\r\n }\r\n }\r\n </div>\r\n </ng-template>\r\n\r\n <!-- Azioni per ogni istanza (duplica / elimina) -->\r\n <ng-template #repeatableActionsTpl let-item>\r\n @if (canAddInstance(section.id)) {\r\n <button type=\"button\" class=\"ui-form-builder__repeatable-action\"\r\n (click)=\"duplicateRepeatableInstance(section.id, item.index)\" aria-label=\"Duplica\">\r\n <lucide-icon name=\"copy\" [size]=\"15\" />\r\n </button>\r\n }\r\n @if (canRemoveInstance(section.id)) {\r\n <button type=\"button\" class=\"ui-form-builder__repeatable-action ui-form-builder__repeatable-action--danger\"\r\n (click)=\"removeRepeatableInstance(section.id, item.index)\" aria-label=\"Elimina\">\r\n <lucide-icon name=\"trash-2\" [size]=\"15\" />\r\n </button>\r\n }\r\n </ng-template>\r\n </ui-accordion>\r\n\r\n @if (canAddInstance(section.id)) {\r\n <div class=\"ui-form-builder__repeatable-add\">\r\n <ui-button-area [buttons]=\"getRepeatableAddButtons(section)\" align=\"end\" />\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- \u2500\u2500\u2500 Sezione COLLASSABILE \u2500\u2500\u2500 -->\r\n } @else if (section.collapsible) {\r\n <ui-accordion [items]=\"getSectionAccordionItems(section.id)\" [itemTemplate]=\"collapsibleTpl\" mode=\"multi\"\r\n class=\"ui-form-builder__section ui-form-builder__section--collapsible\" [class]=\"section.cssClasses?.join(' ')\">\r\n <ng-template #collapsibleTpl let-item>\r\n <div class=\"ui-form-builder__section-content\"\r\n [class.ui-form-builder__grid]=\"schema.config?.layout === 'grid' || !schema.config?.layout\">\r\n @for (field of section.fields; track field.key) {\r\n @if (isFieldVisible(field.key) && field.type !== 'flag') {\r\n <div class=\"ui-form-builder__field-wrapper\" [class]=\"getFieldWrapperClasses(field)\"\r\n [style.order]=\"field.layout?.order || null\" [attr.data-field-key]=\"field.key\">\r\n <ng-container *ngTemplateOutlet=\"fieldTpl; context: { $implicit: field, formGroup: formGroup }\" />\r\n </div>\r\n }\r\n }\r\n </div>\r\n </ng-template>\r\n </ui-accordion>\r\n\r\n <!-- \u2500\u2500\u2500 Sezione STANDARD \u2500\u2500\u2500 -->\r\n } @else {\r\n <div class=\"ui-form-builder__section\" [class]=\"section.cssClasses?.join(' ')\">\r\n @if (section.title || section.description) {\r\n <div class=\"ui-form-builder__section-header\">\r\n @if (section.title) {\r\n <h3 class=\"ui-form-builder__section-title\">{{ section.title }}</h3>\r\n }\r\n @if (section.description) {\r\n <p class=\"ui-form-builder__section-description\">{{ section.description }}</p>\r\n }\r\n </div>\r\n }\r\n\r\n <div class=\"ui-form-builder__section-content\"\r\n [class.ui-form-builder__grid]=\"schema.config?.layout === 'grid' || !schema.config?.layout\">\r\n @for (field of section.fields; track field.key) {\r\n @if (isFieldVisible(field.key) && field.type !== 'flag') {\r\n <div class=\"ui-form-builder__field-wrapper\" [class]=\"getFieldWrapperClasses(field)\"\r\n [style.order]=\"field.layout?.order || null\" [attr.data-field-key]=\"field.key\">\r\n <ng-container *ngTemplateOutlet=\"fieldTpl; context: { $implicit: field, formGroup: formGroup }\" />\r\n </div>\r\n }\r\n }\r\n </div>\r\n </div>\r\n }\r\n }\r\n }\r\n </div>\r\n\r\n <!-- ==================== FOOTER ==================== -->\r\n <div class=\"ui-form-builder__footer\">\r\n @if (!schema.config?.hideErrorSummary) {\r\n <ui-form-error-summary [errors]=\"formErrors\" [totalErrorCount]=\"getFormErrorsCount()\"\r\n [validMessage]=\"schema.config?.errorSummaryValidMessage ?? 'Il form \u00E8 compilato correttamente'\"\r\n (fieldClick)=\"scrollToField($event)\" />\r\n }\r\n @if (!schema.config?.hideFooter) {\r\n <ui-button-area [buttons]=\"formButtons\" align=\"end\" [loadingIds]=\"loadingFor\" />\r\n }\r\n </div>\r\n</div>\r\n}\r\n\r\n<!-- ============================================================ -->\r\n<!-- FIELD TEMPLATE -->\r\n<!-- ============================================================ -->\r\n<!--\r\n CRITICO: Questo template e dichiarato FUORI dal <div [formGroup]>.\r\n Angular risolve l'injector dal contesto di DICHIARAZIONE.\r\n Il FormGroup viene passato via context (fg) per supportare\r\n sia il root FormGroup che i FormGroup delle istanze repeatable.\r\n-->\r\n<ng-template #fieldTpl let-field let-fg=\"formGroup\">\r\n @if (fg) {\r\n <div [formGroup]=\"fg\" class=\"ui-fb-field-ctx\">\r\n @switch (field.type) {\r\n\r\n <!-- TEXT / EMAIL / PASSWORD -->\r\n @case ('text') {\r\n <ui-form-text-field\r\n [field]=\"field\"\r\n [formGroup]=\"fg\"\r\n [readonly]=\"readonly\"\r\n [errors]=\"getFieldErrors(field.key, fg)\"\r\n [options]=\"getFieldOptions(field)\"\r\n (optionsFilter)=\"filterOptions(field, $event)\"\r\n />\r\n }\r\n @case ('email') {\r\n <ui-form-text-field\r\n [field]=\"field\"\r\n [formGroup]=\"fg\"\r\n [readonly]=\"readonly\"\r\n [errors]=\"getFieldErrors(field.key, fg)\"\r\n />\r\n }\r\n @case ('password') {\r\n <ui-form-text-field\r\n [field]=\"field\"\r\n [formGroup]=\"fg\"\r\n [readonly]=\"readonly\"\r\n [errors]=\"getFieldErrors(field.key, fg)\"\r\n />\r\n }\r\n\r\n <!-- NUMBER -->\r\n @case ('number') {\r\n <ui-form-number-field\r\n [field]=\"field\"\r\n [formGroup]=\"fg\"\r\n [readonly]=\"readonly\"\r\n [errors]=\"getFieldErrors(field.key, fg)\"\r\n />\r\n }\r\n\r\n <!-- TEXTAREA -->\r\n @case ('textarea') {\r\n <ui-form-textarea-field\r\n [field]=\"field\"\r\n [formGroup]=\"fg\"\r\n [readonly]=\"readonly\"\r\n [errors]=\"getFieldErrors(field.key, fg)\"\r\n />\r\n }\r\n\r\n <!-- SELECT -->\r\n @case ('select') {\r\n <ui-form-select-field\r\n [field]=\"field\"\r\n [formGroup]=\"fg\"\r\n [readonly]=\"readonly\"\r\n [errors]=\"getFieldErrors(field.key, fg)\"\r\n [options]=\"getFieldOptions(field)\"\r\n (optionsFilter)=\"filterOptions(field, $event)\"\r\n />\r\n }\r\n\r\n <!-- MULTISELECT -->\r\n @case ('multiselect') {\r\n <mat-form-field appearance=\"outline\" class=\"ui-form-builder__full-width\">\r\n <mat-label>{{ field.label }}</mat-label>\r\n <mat-select [formControlName]=\"field.key\" multiple [placeholder]=\"field.placeholder || ''\">\r\n @if (field.allowSelectAll) {\r\n <mat-option (click)=\"selectAll(field)\" (keydown.enter)=\"selectAll(field)\" (keydown.space)=\"selectAll(field)\">\r\n <em>Seleziona tutto</em>\r\n </mat-option>\r\n }\r\n @for (opt of getFieldOptions(field); track opt.value) {\r\n <mat-option [value]=\"opt.value\" [disabled]=\"opt.disabled\">\r\n {{ opt.label }}\r\n </mat-option>\r\n }\r\n </mat-select>\r\n <mat-error>\r\n @for (err of getFieldErrors(field.key, fg); track $index) {\r\n <div>{{ err }}</div>\r\n }\r\n </mat-error>\r\n </mat-form-field>\r\n @if (fg.get(field.key)?.value?.length) {\r\n <div class=\"ui-form-builder__chips-preview\">\r\n @for (val of fg.get(field.key)!.value; track val) {\r\n <span class=\"ui-form-builder__chip\">\r\n {{ getOptionLabel(field, val) }}\r\n <button type=\"button\" (click)=\"removeMultiselectChip(field, val)\" class=\"ui-form-builder__chip-remove\"\r\n aria-label=\"Rimuovi\">\r\n <lucide-icon name=\"x\" [size]=\"12\" />\r\n </button>\r\n </span>\r\n }\r\n </div>\r\n }\r\n }\r\n\r\n <!-- FREEMULTISELECT -->\r\n @case ('freemultiselect') {\r\n <mat-form-field appearance=\"outline\" class=\"ui-form-builder__full-width ui-form-builder__free-multi-field\">\r\n <mat-label>{{ field.label }}</mat-label>\r\n <mat-chip-grid #chipGrid [formControlName]=\"field.key\">\r\n @for (chip of fg.get(field.key)?.value || []; track chip) {\r\n <mat-chip-row [removable]=\"!isChipProtected(field, chip)\" (removed)=\"removeFreeChip(field, chip)\">\r\n {{ chip }}\r\n @if (!isChipProtected(field, chip)) {\r\n <button matChipRemove aria-label=\"Rimuovi\">\r\n <lucide-icon name=\"x\" [size]=\"14\" />\r\n </button>\r\n }\r\n </mat-chip-row>\r\n }\r\n </mat-chip-grid>\r\n <input matInput [matChipInputFor]=\"chipGrid\" [placeholder]=\"field.placeholder || 'Aggiungi...'\"\r\n [readonly]=\"field.readonly || readonly\" (matChipInputTokenEnd)=\"addFreeChip(field, $event)\"\r\n (blur)=\"addFreeChipOnBlur(field, $event)\" />\r\n <mat-error>\r\n @for (err of getFieldErrors(field.key, fg); track $index) {\r\n <div>{{ err }}</div>\r\n }\r\n </mat-error>\r\n </mat-form-field>\r\n }\r\n\r\n <!-- CHECKBOX -->\r\n @case ('checkbox') {\r\n @if (field.appearance?.style === 'switch') {\r\n <mat-slide-toggle [formControlName]=\"field.key\" [color]=\"field.appearance?.color || 'primary'\">\r\n {{ field.label }}\r\n </mat-slide-toggle>\r\n } @else {\r\n <mat-checkbox [formControlName]=\"field.key\" [color]=\"field.appearance?.color || 'primary'\">\r\n {{ field.label }}\r\n @if (field.iconTooltip) {\r\n <lucide-icon [name]=\"field.iconTooltip.icon || 'info'\" [size]=\"14\" [matTooltip]=\"field.iconTooltip.text\"\r\n class=\"ui-form-builder__inline-tooltip\" />\r\n }\r\n </mat-checkbox>\r\n }\r\n @if (shouldShowFieldErrors(field.key, fg)) {\r\n <div class=\"ui-form-builder__field-error\">\r\n @for (err of getFieldErrors(field.key, fg); track $index) {\r\n <span>{{ err }}</span>\r\n }\r\n </div>\r\n }\r\n }\r\n\r\n <!-- SWITCH -->\r\n @case ('switch') {\r\n <mat-slide-toggle [formControlName]=\"field.key\" [color]=\"field.appearance?.color || 'primary'\">\r\n {{ field.label }}\r\n @if (field.iconTooltip) {\r\n <lucide-icon [name]=\"field.iconTooltip.icon || 'info'\" [size]=\"14\" [matTooltip]=\"field.iconTooltip.text\"\r\n class=\"ui-form-builder__inline-tooltip\" />\r\n }\r\n </mat-slide-toggle>\r\n }\r\n\r\n <!-- RADIO -->\r\n @case ('radio') {\r\n <ui-form-radio-field\r\n [field]=\"field\"\r\n [formGroup]=\"fg\"\r\n [readonly]=\"readonly\"\r\n [errors]=\"getFieldErrors(field.key, fg)\"\r\n [options]=\"getFieldOptions(field)\"\r\n [required]=\"isFieldRequired(field.key, fg)\"\r\n [showErrors]=\"shouldShowFieldErrors(field.key, fg)\"\r\n />\r\n }\r\n\r\n <!-- DATE -->\r\n @case ('date') {\r\n <mat-form-field appearance=\"outline\" class=\"ui-form-builder__full-width\">\r\n <mat-label>{{ field.label }}</mat-label>\r\n <input matInput [matDatepicker]=\"datepicker\" [formControlName]=\"field.key\"\r\n [placeholder]=\"field.placeholder || 'GG/MM/AAAA'\" [readonly]=\"field.readonly || readonly\" />\r\n <mat-datepicker-toggle matSuffix [for]=\"datepicker\" />\r\n <mat-datepicker #datepicker [startView]=\"field.customConfig?.config?.['monthView'] ? 'year' : 'month'\" />\r\n <mat-error>\r\n @for (err of getFieldErrors(field.key, fg); track $index) {\r\n <div>{{ err }}</div>\r\n }\r\n </mat-error>\r\n </mat-form-field>\r\n }\r\n\r\n <!-- DATETIME -->\r\n @case ('datetime') {\r\n <mat-form-field appearance=\"outline\" class=\"ui-form-builder__full-width\">\r\n <mat-label>{{ field.label }}</mat-label>\r\n <input matInput type=\"datetime-local\" [formControlName]=\"field.key\" [placeholder]=\"field.placeholder || ''\"\r\n [readonly]=\"field.readonly || readonly\" />\r\n <mat-error>\r\n @for (err of getFieldErrors(field.key, fg); track $index) {\r\n <div>{{ err }}</div>\r\n }\r\n </mat-error>\r\n </mat-form-field>\r\n }\r\n\r\n <!-- FILE -->\r\n @case ('file') {\r\n <div class=\"ui-form-builder__file-wrapper\">\r\n <label class=\"ui-form-builder__field-label\">\r\n {{ field.label }}\r\n @if (isFieldRequired(field.key, fg)) { <span class=\"ui-form-builder__required\">*</span> }\r\n </label>\r\n <ui-file-input [formControlName]=\"field.key\" [config]=\"field.fileConfig\" />\r\n @if (shouldShowFieldErrors(field.key, fg)) {\r\n <div class=\"ui-form-builder__field-error\">\r\n @for (err of getFieldErrors(field.key, fg); track $index) {\r\n <span>{{ err }}</span>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n\r\n <!-- DIVIDER -->\r\n @case ('divider') {\r\n <div class=\"ui-form-builder__divider\">\r\n @if (field.label) {\r\n <span class=\"ui-form-builder__divider-label\">{{ field.label }}</span>\r\n }\r\n <hr class=\"ui-form-builder__divider-line\" />\r\n </div>\r\n }\r\n\r\n <!-- LOCATION (selezione territorio) -->\r\n @case ('location') {\r\n <div class=\"ui-form-builder__location-wrapper\">\r\n @if (field.label) {\r\n <label class=\"ui-form-builder__field-label\">\r\n {{ field.label }}\r\n @if (isFieldRequired(field.key, fg)) { <span class=\"ui-form-builder__required\">*</span> }\r\n </label>\r\n }\r\n <ui-specifica-territoriale [formControlName]=\"field.key\" [config]=\"field.customConfig?.config || {}\"\r\n [disabled]=\"readonly || field.disabled || disabled\" />\r\n @if (shouldShowFieldErrors(field.key, fg)) {\r\n <div class=\"ui-form-builder__field-error\">\r\n @for (err of getFieldErrors(field.key, fg); track $index) {\r\n <span>{{ err }}</span>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n\r\n <!-- LOCATION-TABLE (tabella CRUD di location) -->\r\n @case ('location-table') {\r\n <div class=\"ui-form-builder__location-table-wrapper\">\r\n <ui-table-territoriale [formControlName]=\"field.key\" [config]=\"field.customConfig?.config || {}\"\r\n [disabled]=\"readonly || field.disabled || disabled\" />\r\n @if (shouldShowFieldErrors(field.key, fg)) {\r\n <div class=\"ui-form-builder__field-error\">\r\n @for (err of getFieldErrors(field.key, fg); track $index) {\r\n <span>{{ err }}</span>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n\r\n <!-- LOCATION-GEOCODED (ricerca indirizzo Nominatim) -->\r\n @case ('location-geocoded') {\r\n <div class=\"ui-form-builder__location-geocoded-wrapper\">\r\n @if (field.label) {\r\n <label class=\"ui-form-builder__field-label\" [attr.for]=\"'ui-loc-' + field.key\">\r\n {{ field.label }}\r\n @if (isFieldRequired(field.key, fg)) { <span class=\"ui-form-builder__required\">*</span> }\r\n </label>\r\n }\r\n <ui-location-geocoded [formControlName]=\"field.key\" [config]=\"field.locationGeocodedConfig || {}\"\r\n [placeholder]=\"field.placeholder || 'Cerca indirizzo...'\"\r\n [inputAriaLabel]=\"field.label || field.placeholder || 'Indirizzo'\"\r\n [inputId]=\"'ui-loc-' + field.key\"\r\n [readonly]=\"field.readonly || readonly\"\r\n [disabled]=\"readonly || field.disabled || disabled\" />\r\n @if (shouldShowFieldErrors(field.key, fg)) {\r\n <div class=\"ui-form-builder__field-error\">\r\n @for (err of getFieldErrors(field.key, fg); track $index) {\r\n <span>{{ err }}</span>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n\r\n <!-- CUSTOM -->\r\n @case ('custom') {\r\n <div class=\"ui-form-builder__custom-wrapper\" [attr.data-component]=\"field.customConfig?.component\"\r\n [attr.data-template-key]=\"resolveCustomTemplateKey(field)\">\r\n @if (field.label) {\r\n <label class=\"ui-form-builder__field-label\">\r\n {{ field.label }}\r\n @if (isFieldRequired(field.key, fg)) { <span class=\"ui-form-builder__required\">*</span> }\r\n </label>\r\n }\r\n @if (getCustomFieldOutlet(field, fg); as customOutlet) {\r\n <ng-container *ngTemplateOutlet=\"customOutlet.template; context: customOutlet.context\" />\r\n } @else {\r\n <div class=\"ui-form-builder__custom-placeholder\" role=\"status\">\r\n <span>Nessun template registrato per \u00AB{{ resolveCustomTemplateKey(field) }}\u00BB</span>\r\n </div>\r\n }\r\n @if (shouldShowFieldErrors(field.key, fg)) {\r\n <div class=\"ui-form-builder__field-error\">\r\n @for (err of getFieldErrors(field.key, fg); track $index) {\r\n <span>{{ err }}</span>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n }\r\n </div>\r\n }\r\n</ng-template>", styles: [".ui-form-builder{display:flex;flex-direction:column;gap:var(--ui-spacing-6);font-family:var(--ui-font-family)}.ui-form-builder__header{margin-bottom:var(--ui-spacing-2)}.ui-form-builder__title{font-size:var(--ui-font-size-xl);font-weight:var(--ui-font-weight-semibold);color:var(--ui-color-text);margin:0 0 var(--ui-spacing-1)}.ui-form-builder__description{font-size:var(--ui-font-size-sm);color:var(--ui-color-text-secondary);margin:0}.ui-form-builder__sections{display:flex;flex-direction:column;gap:var(--ui-spacing-6)}.ui-form-builder__section--repeatable{display:flex;flex-direction:column;gap:var(--ui-spacing-4)}.ui-form-builder__repeatable-action{appearance:none;border:none;background:transparent;cursor:pointer;padding:var(--ui-spacing-1);border-radius:var(--ui-radius-sm);color:var(--ui-color-text-muted);display:inline-flex;align-items:center;justify-content:center;transition:color var(--ui-transition-fast),background-color var(--ui-transition-fast)}.ui-form-builder__repeatable-action:focus{outline:none}.ui-form-builder__repeatable-action:focus-visible{outline:var(--ui-focus-ring-width) solid var(--ui-focus-ring-color);outline-offset:var(--ui-focus-ring-offset)}.ui-form-builder__repeatable-action:hover{color:var(--ui-color-text);background:var(--ui-color-surface-hover)}.ui-form-builder__repeatable-action--danger:hover{color:var(--ui-color-error, #dc2626);background:color-mix(in srgb,var(--ui-color-error, #dc2626) 8%,transparent)}.ui-form-builder__repeatable-add{display:flex;justify-content:flex-start;padding-top:var(--ui-spacing-2)}.ui-form-builder__section-header{margin-bottom:var(--ui-spacing-4);padding-bottom:var(--ui-spacing-2);border-bottom:1px solid var(--ui-color-border)}.ui-form-builder__section-title{font-size:var(--ui-font-size-lg);font-weight:var(--ui-font-weight-semibold);color:var(--ui-color-text);margin:0 0 var(--ui-spacing-1)}.ui-form-builder__section-description{font-size:var(--ui-font-size-sm);color:var(--ui-color-text-secondary);margin:0}.ui-form-builder__grid{display:grid;grid-template-columns:repeat(12,1fr);gap:var(--ui-spacing-4) var(--ui-spacing-4);align-items:start}.ui-form-builder__field-wrapper{grid-column:span 12;min-width:0}@media (min-width: 768px){.ui-form-builder__field-wrapper{grid-column:span 6}}@media (min-width: 576px){.ui-form-builder__field-wrapper.ui-col-sm-1{grid-column:span 1}.ui-form-builder__field-wrapper.ui-col-sm-2{grid-column:span 2}.ui-form-builder__field-wrapper.ui-col-sm-3{grid-column:span 3}.ui-form-builder__field-wrapper.ui-col-sm-4{grid-column:span 4}.ui-form-builder__field-wrapper.ui-col-sm-5{grid-column:span 5}.ui-form-builder__field-wrapper.ui-col-sm-6{grid-column:span 6}.ui-form-builder__field-wrapper.ui-col-sm-7{grid-column:span 7}.ui-form-builder__field-wrapper.ui-col-sm-8{grid-column:span 8}.ui-form-builder__field-wrapper.ui-col-sm-9{grid-column:span 9}.ui-form-builder__field-wrapper.ui-col-sm-10{grid-column:span 10}.ui-form-builder__field-wrapper.ui-col-sm-11{grid-column:span 11}.ui-form-builder__field-wrapper.ui-col-sm-12{grid-column:span 12}}@media (min-width: 768px){.ui-form-builder__field-wrapper.ui-col-md-1{grid-column:span 1}.ui-form-builder__field-wrapper.ui-col-md-2{grid-column:span 2}.ui-form-builder__field-wrapper.ui-col-md-3{grid-column:span 3}.ui-form-builder__field-wrapper.ui-col-md-4{grid-column:span 4}.ui-form-builder__field-wrapper.ui-col-md-5{grid-column:span 5}.ui-form-builder__field-wrapper.ui-col-md-6{grid-column:span 6}.ui-form-builder__field-wrapper.ui-col-md-7{grid-column:span 7}.ui-form-builder__field-wrapper.ui-col-md-8{grid-column:span 8}.ui-form-builder__field-wrapper.ui-col-md-9{grid-column:span 9}.ui-form-builder__field-wrapper.ui-col-md-10{grid-column:span 10}.ui-form-builder__field-wrapper.ui-col-md-11{grid-column:span 11}.ui-form-builder__field-wrapper.ui-col-md-12{grid-column:span 12}}@media (min-width: 1024px){.ui-form-builder__field-wrapper.ui-col-lg-1{grid-column:span 1}.ui-form-builder__field-wrapper.ui-col-lg-2{grid-column:span 2}.ui-form-builder__field-wrapper.ui-col-lg-3{grid-column:span 3}.ui-form-builder__field-wrapper.ui-col-lg-4{grid-column:span 4}.ui-form-builder__field-wrapper.ui-col-lg-5{grid-column:span 5}.ui-form-builder__field-wrapper.ui-col-lg-6{grid-column:span 6}.ui-form-builder__field-wrapper.ui-col-lg-7{grid-column:span 7}.ui-form-builder__field-wrapper.ui-col-lg-8{grid-column:span 8}.ui-form-builder__field-wrapper.ui-col-lg-9{grid-column:span 9}.ui-form-builder__field-wrapper.ui-col-lg-10{grid-column:span 10}.ui-form-builder__field-wrapper.ui-col-lg-11{grid-column:span 11}.ui-form-builder__field-wrapper.ui-col-lg-12{grid-column:span 12}}@media (min-width: 1280px){.ui-form-builder__field-wrapper.ui-col-xl-1{grid-column:span 1}.ui-form-builder__field-wrapper.ui-col-xl-2{grid-column:span 2}.ui-form-builder__field-wrapper.ui-col-xl-3{grid-column:span 3}.ui-form-builder__field-wrapper.ui-col-xl-4{grid-column:span 4}.ui-form-builder__field-wrapper.ui-col-xl-5{grid-column:span 5}.ui-form-builder__field-wrapper.ui-col-xl-6{grid-column:span 6}.ui-form-builder__field-wrapper.ui-col-xl-7{grid-column:span 7}.ui-form-builder__field-wrapper.ui-col-xl-8{grid-column:span 8}.ui-form-builder__field-wrapper.ui-col-xl-9{grid-column:span 9}.ui-form-builder__field-wrapper.ui-col-xl-10{grid-column:span 10}.ui-form-builder__field-wrapper.ui-col-xl-11{grid-column:span 11}.ui-form-builder__field-wrapper.ui-col-xl-12{grid-column:span 12}}.ui-form-builder__full-width{width:100%}.ui-fb-field-ctx,.ui-fb-text-ctx{display:contents}.ui-form-builder__textarea-wrapper{position:relative}.ui-form-builder mat-hint.ui-form-builder__char-count{font-size:var(--ui-font-size-xs);color:var(--ui-color-text-muted)}.ui-form-builder mat-hint.ui-form-builder__char-count.ui-form-builder__char-count--warn{color:var(--ui-color-warn, #d97706)}.ui-form-builder mat-hint.ui-form-builder__char-count.ui-form-builder__char-count--error{color:var(--ui-color-error, #dc2626);font-weight:var(--ui-font-weight-semibold)}.ui-form-builder__field-label{display:block;font-size:var(--ui-font-size-sm);font-weight:var(--ui-font-weight-medium);color:var(--ui-color-text);margin-bottom:var(--ui-spacing-4)}.ui-form-builder__required{color:var(--ui-color-error, #dc2626);margin-left:2px}.ui-form-builder__field-error{margin-top:var(--ui-spacing-1);font-size:var(--ui-font-size-xs);color:var(--ui-color-error, #dc2626);display:flex;flex-direction:column;gap:2px}.ui-form-builder__radio-wrapper mat-radio-group{display:flex;flex-wrap:wrap;gap:var(--ui-spacing-3)}.ui-form-builder__chips-preview{display:flex;flex-wrap:wrap;gap:var(--ui-spacing-1);margin-top:var(--ui-spacing-1)}.ui-form-builder__chip{display:inline-flex;align-items:center;gap:4px;padding:2px var(--ui-spacing-2);border-radius:var(--ui-radius-full);background:var(--ui-color-primary-subtle, rgba(37, 99, 235, .08));color:var(--ui-color-primary);font-size:var(--ui-font-size-xs)}.ui-form-builder__chip-remove{appearance:none;border:none;background:transparent;cursor:pointer;padding:0;display:flex;color:inherit;opacity:.7}.ui-form-builder__chip-remove:hover{opacity:1}.ui-form-builder__option-icon{margin-right:var(--ui-spacing-2);vertical-align:middle}.ui-form-builder__option-info{margin-left:auto;color:var(--ui-color-text-muted)}.ui-form-builder__inline-tooltip{margin-left:var(--ui-spacing-1);color:var(--ui-color-text-muted);cursor:help}.ui-form-builder__divider{grid-column:1/-1!important;display:flex;align-items:center;gap:var(--ui-spacing-3);padding:var(--ui-spacing-2) 0}.ui-form-builder__divider-label{font-size:var(--ui-font-size-sm);font-weight:var(--ui-font-weight-medium);color:var(--ui-color-text-secondary);white-space:nowrap}.ui-form-builder__divider-line{flex:1;border:none;border-top:1px solid var(--ui-color-border);margin:0}.ui-form-builder__custom-placeholder{display:flex;align-items:center;gap:var(--ui-spacing-2);padding:var(--ui-spacing-4);border:1px dashed var(--ui-color-border);border-radius:var(--ui-radius-md);color:var(--ui-color-text-muted);font-size:var(--ui-font-size-sm)}.ui-form-builder__footer{display:flex;align-items:center;justify-content:space-between;gap:var(--ui-spacing-4);padding-top:var(--ui-spacing-4);border-top:1px solid var(--ui-color-border)}@media (max-width: 767.98px){.ui-form-builder__footer{flex-direction:column;align-items:stretch}}.ui-form-field--highlight{animation:ui-field-highlight-pulse 1s ease-in-out 2;border-radius:var(--ui-radius-md)}@keyframes ui-field-highlight-pulse{0%{box-shadow:0 0 #dc262666;padding:10px}50%{box-shadow:0 0 0 6px #dc262626;padding:10px}to{box-shadow:0 0 #dc262600;padding:10px}}.ui-form-builder mat-error{font-size:var(--ui-font-size-xs, 12px);display:flex;flex-direction:column;transform:translateY(-5px)}.ui-form-builder .mat-mdc-form-field.mat-form-field-invalid .mdc-notched-outline__leading,.ui-form-builder .mat-mdc-form-field.mat-form-field-invalid .mdc-notched-outline__notch,.ui-form-builder .mat-mdc-form-field.mat-form-field-invalid .mdc-notched-outline__trailing{border-color:var(--ui-color-error, #dc2626)!important}.ui-form-builder .mat-mdc-form-field.mat-form-field-invalid .mat-mdc-floating-label{color:var(--ui-color-error, #dc2626)}.ui-form-builder__free-multi-field mat-chip-grid{display:flex;flex-wrap:wrap;gap:var(--ui-spacing-1)}.ui-form-builder__free-multi-field input[matInput]{flex:1;min-width:80px}.ui-form-builder mat-datepicker-toggle{display:inline-flex;align-items:center}.mdc-notched-outline__notch{border-left:none!important}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$2.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i2.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i2.MatLabel, selector: "mat-label" }, { kind: "directive", type: i2.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "directive", type: i2.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i3.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i4$1.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i5.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "component", type: i6$1.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i7.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "ngmodule", type: MatRadioModule }, { kind: "ngmodule", type: MatDatepickerModule }, { kind: "component", type: i8.MatDatepicker, selector: "mat-datepicker", exportAs: ["matDatepicker"] }, { kind: "directive", type: i8.MatDatepickerInput, selector: "input[matDatepicker]", inputs: ["matDatepicker", "min", "max", "matDatepickerFilter"], exportAs: ["matDatepickerInput"] }, { kind: "component", type: i8.MatDatepickerToggle, selector: "mat-datepicker-toggle", inputs: ["for", "tabIndex", "aria-label", "disabled", "disableRipple"], exportAs: ["matDatepickerToggle"] }, { kind: "ngmodule", type: MatNativeDateModule }, { kind: "ngmodule", type: MatAutocompleteModule }, { kind: "ngmodule", type: MatChipsModule }, { kind: "component", type: i9.MatChipGrid, selector: "mat-chip-grid", inputs: ["disabled", "placeholder", "required", "value", "errorStateMatcher"], outputs: ["change", "valueChange"] }, { kind: "directive", type: i9.MatChipInput, selector: "input[matChipInputFor]", inputs: ["matChipInputFor", "matChipInputAddOnBlur", "matChipInputSeparatorKeyCodes", "placeholder", "id", "disabled"], outputs: ["matChipInputTokenEnd"], exportAs: ["matChipInput", "matChipInputFor"] }, { kind: "directive", type: i9.MatChipRemove, selector: "[matChipRemove]" }, { kind: "component", type: i9.MatChipRow, selector: "mat-chip-row, [mat-chip-row], mat-basic-chip-row, [mat-basic-chip-row]", inputs: ["editable"], outputs: ["edited"] }, { kind: "component", type: UiAccordionComponent, selector: "ui-accordion", inputs: ["items", "itemTemplate", "headerTemplate", "actionsTemplate", "mode", "ariaLabel"], outputs: ["itemToggled"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i4.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: LucideAngularModule }, { kind: "component", type: i1.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }, { kind: "component", type: UiButtonAreaComponent, selector: "ui-button-area", inputs: ["buttons", "align", "ariaLabel", "gap", "stackOnMobile", "disableWhileLoading", "loadingIds"] }, { kind: "component", type: UiFormErrorSummaryComponent, selector: "ui-form-error-summary", inputs: ["errors", "totalErrorCount", "validMessage"], outputs: ["fieldClick"] }, { kind: "component", type: UiFileInputComponent, selector: "ui-file-input", inputs: ["config"], outputs: ["fileRemoved", "validationError"] }, { kind: "component", type: UiSpecificaTerritorialeComponent, selector: "ui-specifica-territoriale", inputs: ["config", "disabled"] }, { kind: "component", type: UiTableTerritorialeComponent, selector: "ui-table-territoriale", inputs: ["config", "disabled"] }, { kind: "component", type: UiLocationGeocodedComponent, selector: "ui-location-geocoded", inputs: ["config", "placeholder", "inputAriaLabel", "inputId", "disabled", "readonly"] }, { kind: "component", type: UiFormTextFieldComponent, selector: "ui-form-text-field", inputs: ["field", "formGroup", "readonly", "errors", "control", "label", "placeholder", "maxLength", "inputType", "validationRules", "options"], outputs: ["optionsFilter", "blurred"] }, { kind: "component", type: UiFormTextareaFieldComponent, selector: "ui-form-textarea-field", inputs: ["field", "formGroup", "readonly", "errors", "control", "label", "placeholder", "rows", "maxLength", "validationRules"], outputs: ["blurred"] }, { kind: "component", type: UiFormNumberFieldComponent, selector: "ui-form-number-field", inputs: ["field", "formGroup", "readonly", "errors", "control", "label", "placeholder", "validationRules", "currencyMode", "currencySymbol", "decimalPlaces"], outputs: ["blurred"] }, { kind: "component", type: UiFormSelectFieldComponent, selector: "ui-form-select-field", inputs: ["field", "formGroup", "readonly", "errors", "options", "control", "label", "placeholder", "searchable", "hideEmptyOption", "validationRules"], outputs: ["optionsFilter", "blurred"] }, { kind: "component", type: UiFormRadioFieldComponent, selector: "ui-form-radio-field", inputs: ["field", "formGroup", "readonly", "errors", "options", "required", "showErrors", "errorDisplayOptions", "control", "label", "validationRules", "color"], outputs: ["blurred"] }], encapsulation: i0.ViewEncapsulation.None }); }
|
|
49420
|
+
], usesOnChanges: true, ngImport: i0, template: "<!--\r\n ============================================================\r\n UI FORM BUILDER - TEMPLATE PRINCIPALE\r\n ============================================================\r\n ARCHITETTURA:\r\n Ogni <ng-template> che contiene direttive [formControlName]\r\n DEVE wrappare il proprio contenuto in un <div [formGroup]>.\r\n Questo perche Angular risolve l'injector di formControlName\r\n dal CONTESTO DI DICHIARAZIONE del template, non dal punto\r\n di inserimento (ngTemplateOutlet).\r\n\r\n Il FormGroup viene passato tramite context per supportare\r\n sia campi piatti (root FormGroup) che campi in istanze\r\n repeatable (instance FormGroup).\r\n\r\n Senza questo wrapper, formControlName non trova il\r\n FormGroupDirective e lancia NG01050.\r\n ============================================================\r\n-->\r\n\r\n@if (schema && formGroup) {\r\n<div class=\"ui-form-builder\" [formGroup]=\"formGroup\" [class]=\"schema.config?.cssClasses?.join(' ')\">\r\n\r\n <!-- ==================== HEADER ==================== -->\r\n @if (schema.title || schema.description) {\r\n <div class=\"ui-form-builder__header\">\r\n @if (schema.title) {\r\n <h2 class=\"ui-form-builder__title\">{{ schema.title }}</h2>\r\n }\r\n @if (schema.description) {\r\n <p class=\"ui-form-builder__description\">{{ schema.description }}</p>\r\n }\r\n </div>\r\n }\r\n\r\n <!-- ==================== SEZIONI ==================== -->\r\n <div class=\"ui-form-builder__sections\">\r\n @for (section of schema.sections; track section.id) {\r\n @if (isSectionVisible(section.id)) {\r\n\r\n <!-- \u2500\u2500\u2500 Sezione REPEATABLE \u2500\u2500\u2500 -->\r\n @if (section.repeatable) {\r\n <div class=\"ui-form-builder__section ui-form-builder__section--repeatable\" [class]=\"section.cssClasses?.join(' ')\">\r\n @if (section.title || section.description) {\r\n <div class=\"ui-form-builder__section-header\">\r\n @if (section.title) {\r\n <h3 class=\"ui-form-builder__section-title\">{{ section.title }}</h3>\r\n }\r\n @if (section.description) {\r\n <p class=\"ui-form-builder__section-description\">{{ section.description }}</p>\r\n }\r\n </div>\r\n }\r\n\r\n <ui-accordion [items]=\"getRepeatableAccordionItems(section.id)\" [itemTemplate]=\"repeatableBodyTpl\"\r\n [actionsTemplate]=\"repeatableActionsTpl\" mode=\"multi\" class=\"ui-form-builder__repeatable-accordion\">\r\n <!-- Contenuto di ogni istanza -->\r\n <ng-template #repeatableBodyTpl let-item>\r\n <div class=\"ui-form-builder__section-content\"\r\n [class.ui-form-builder__grid]=\"schema.config?.layout === 'grid' || !schema.config?.layout\">\r\n @for (field of section.fields; track field.key) {\r\n @if (isFieldVisible(field.key, getInstanceFormGroup(section.id, item.index)!) && field.type !== 'flag') {\r\n <div class=\"ui-form-builder__field-wrapper\" [class]=\"getFieldWrapperClasses(field)\"\r\n [style.order]=\"field.layout?.order || null\" [attr.data-field-key]=\"field.key\">\r\n <ng-container\r\n *ngTemplateOutlet=\"fieldTpl; context: { $implicit: field, formGroup: getInstanceFormGroup(section.id, item.index), nestedErrorKeyPrefix: buildNestedErrorKeyPrefix(field.key, section.id + '[' + item.index + '].') }\" />\r\n </div>\r\n }\r\n }\r\n </div>\r\n </ng-template>\r\n\r\n <!-- Azioni per ogni istanza (duplica / elimina) -->\r\n <ng-template #repeatableActionsTpl let-item>\r\n @if (canAddInstance(section.id)) {\r\n <button type=\"button\" class=\"ui-form-builder__repeatable-action\"\r\n (click)=\"duplicateRepeatableInstance(section.id, item.index)\" aria-label=\"Duplica\">\r\n <lucide-icon name=\"copy\" [size]=\"15\" />\r\n </button>\r\n }\r\n @if (canRemoveInstance(section.id)) {\r\n <button type=\"button\" class=\"ui-form-builder__repeatable-action ui-form-builder__repeatable-action--danger\"\r\n (click)=\"removeRepeatableInstance(section.id, item.index)\" aria-label=\"Elimina\">\r\n <lucide-icon name=\"trash-2\" [size]=\"15\" />\r\n </button>\r\n }\r\n </ng-template>\r\n </ui-accordion>\r\n\r\n @if (canAddInstance(section.id)) {\r\n <div class=\"ui-form-builder__repeatable-add\">\r\n <ui-button-area [buttons]=\"getRepeatableAddButtons(section)\" align=\"end\" />\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- \u2500\u2500\u2500 Sezione COLLASSABILE \u2500\u2500\u2500 -->\r\n } @else if (section.collapsible) {\r\n <ui-accordion [items]=\"getSectionAccordionItems(section.id)\" [itemTemplate]=\"collapsibleTpl\" mode=\"multi\"\r\n class=\"ui-form-builder__section ui-form-builder__section--collapsible\" [class]=\"section.cssClasses?.join(' ')\">\r\n <ng-template #collapsibleTpl let-item>\r\n <div class=\"ui-form-builder__section-content\"\r\n [class.ui-form-builder__grid]=\"schema.config?.layout === 'grid' || !schema.config?.layout\">\r\n @for (field of section.fields; track field.key) {\r\n @if (isFieldVisible(field.key) && field.type !== 'flag') {\r\n <div class=\"ui-form-builder__field-wrapper\" [class]=\"getFieldWrapperClasses(field)\"\r\n [style.order]=\"field.layout?.order || null\" [attr.data-field-key]=\"field.key\">\r\n <ng-container *ngTemplateOutlet=\"fieldTpl; context: { $implicit: field, formGroup: formGroup, nestedErrorKeyPrefix: buildNestedErrorKeyPrefix(field.key) }\" />\r\n </div>\r\n }\r\n }\r\n </div>\r\n </ng-template>\r\n </ui-accordion>\r\n\r\n <!-- \u2500\u2500\u2500 Sezione STANDARD \u2500\u2500\u2500 -->\r\n } @else {\r\n <div class=\"ui-form-builder__section\" [class]=\"section.cssClasses?.join(' ')\">\r\n @if (section.title || section.description) {\r\n <div class=\"ui-form-builder__section-header\">\r\n @if (section.title) {\r\n <h3 class=\"ui-form-builder__section-title\">{{ section.title }}</h3>\r\n }\r\n @if (section.description) {\r\n <p class=\"ui-form-builder__section-description\">{{ section.description }}</p>\r\n }\r\n </div>\r\n }\r\n\r\n <div class=\"ui-form-builder__section-content\"\r\n [class.ui-form-builder__grid]=\"schema.config?.layout === 'grid' || !schema.config?.layout\">\r\n @for (field of section.fields; track field.key) {\r\n @if (isFieldVisible(field.key) && field.type !== 'flag') {\r\n <div class=\"ui-form-builder__field-wrapper\" [class]=\"getFieldWrapperClasses(field)\"\r\n [style.order]=\"field.layout?.order || null\" [attr.data-field-key]=\"field.key\">\r\n <ng-container *ngTemplateOutlet=\"fieldTpl; context: { $implicit: field, formGroup: formGroup, nestedErrorKeyPrefix: buildNestedErrorKeyPrefix(field.key) }\" />\r\n </div>\r\n }\r\n }\r\n </div>\r\n </div>\r\n }\r\n }\r\n }\r\n </div>\r\n\r\n <!-- ==================== FOOTER ==================== -->\r\n <div class=\"ui-form-builder__footer\">\r\n @if (!schema.config?.hideErrorSummary) {\r\n <ui-form-error-summary [errors]=\"formErrors\" [totalErrorCount]=\"getFormErrorsCount()\"\r\n [validMessage]=\"schema.config?.errorSummaryValidMessage ?? 'Il form \u00E8 compilato correttamente'\"\r\n (fieldClick)=\"scrollToField($event)\" />\r\n }\r\n @if (!schema.config?.hideFooter) {\r\n <ui-button-area [buttons]=\"formButtons\" align=\"end\" [loadingIds]=\"loadingFor\" />\r\n }\r\n </div>\r\n</div>\r\n}\r\n\r\n<!-- ============================================================ -->\r\n<!-- FIELD TEMPLATE -->\r\n<!-- ============================================================ -->\r\n<!--\r\n CRITICO: Questo template e dichiarato FUORI dal <div [formGroup]>.\r\n Angular risolve l'injector dal contesto di DICHIARAZIONE.\r\n Il FormGroup viene passato via context (fg) per supportare\r\n sia il root FormGroup che i FormGroup delle istanze repeatable.\r\n-->\r\n<ng-template #fieldTpl let-field let-fg=\"formGroup\" let-nestedErrorKeyPrefix=\"nestedErrorKeyPrefix\">\r\n @if (fg) {\r\n <div [formGroup]=\"fg\" class=\"ui-fb-field-ctx\">\r\n @switch (field.type) {\r\n\r\n <!-- TEXT / EMAIL / PASSWORD -->\r\n @case ('text') {\r\n <ui-form-text-field\r\n [field]=\"field\"\r\n [formGroup]=\"fg\"\r\n [readonly]=\"readonly\"\r\n [errors]=\"getFieldErrors(field.key, fg)\"\r\n [options]=\"getFieldOptions(field)\"\r\n (optionsFilter)=\"filterOptions(field, $event)\"\r\n />\r\n }\r\n @case ('email') {\r\n <ui-form-text-field\r\n [field]=\"field\"\r\n [formGroup]=\"fg\"\r\n [readonly]=\"readonly\"\r\n [errors]=\"getFieldErrors(field.key, fg)\"\r\n />\r\n }\r\n @case ('password') {\r\n <ui-form-text-field\r\n [field]=\"field\"\r\n [formGroup]=\"fg\"\r\n [readonly]=\"readonly\"\r\n [errors]=\"getFieldErrors(field.key, fg)\"\r\n />\r\n }\r\n\r\n <!-- NUMBER -->\r\n @case ('number') {\r\n <ui-form-number-field\r\n [field]=\"field\"\r\n [formGroup]=\"fg\"\r\n [readonly]=\"readonly\"\r\n [errors]=\"getFieldErrors(field.key, fg)\"\r\n />\r\n }\r\n\r\n <!-- TEXTAREA -->\r\n @case ('textarea') {\r\n <ui-form-textarea-field\r\n [field]=\"field\"\r\n [formGroup]=\"fg\"\r\n [readonly]=\"readonly\"\r\n [errors]=\"getFieldErrors(field.key, fg)\"\r\n />\r\n }\r\n\r\n <!-- SELECT -->\r\n @case ('select') {\r\n <ui-form-select-field\r\n [field]=\"field\"\r\n [formGroup]=\"fg\"\r\n [readonly]=\"readonly\"\r\n [errors]=\"getFieldErrors(field.key, fg)\"\r\n [options]=\"getFieldOptions(field)\"\r\n (optionsFilter)=\"filterOptions(field, $event)\"\r\n />\r\n }\r\n\r\n <!-- MULTISELECT -->\r\n @case ('multiselect') {\r\n <mat-form-field appearance=\"outline\" class=\"ui-form-builder__full-width\">\r\n <mat-label>{{ field.label }}</mat-label>\r\n <mat-select [formControlName]=\"field.key\" multiple [placeholder]=\"field.placeholder || ''\">\r\n @if (field.allowSelectAll) {\r\n <mat-option (click)=\"selectAll(field)\" (keydown.enter)=\"selectAll(field)\" (keydown.space)=\"selectAll(field)\">\r\n <em>Seleziona tutto</em>\r\n </mat-option>\r\n }\r\n @for (opt of getFieldOptions(field); track opt.value) {\r\n <mat-option [value]=\"opt.value\" [disabled]=\"opt.disabled\">\r\n {{ opt.label }}\r\n </mat-option>\r\n }\r\n </mat-select>\r\n <mat-error>\r\n @for (err of getFieldErrors(field.key, fg); track $index) {\r\n <div>{{ err }}</div>\r\n }\r\n </mat-error>\r\n </mat-form-field>\r\n @if (fg.get(field.key)?.value?.length) {\r\n <div class=\"ui-form-builder__chips-preview\">\r\n @for (val of fg.get(field.key)!.value; track val) {\r\n <span class=\"ui-form-builder__chip\">\r\n {{ getOptionLabel(field, val) }}\r\n <button type=\"button\" (click)=\"removeMultiselectChip(field, val)\" class=\"ui-form-builder__chip-remove\"\r\n aria-label=\"Rimuovi\">\r\n <lucide-icon name=\"x\" [size]=\"12\" />\r\n </button>\r\n </span>\r\n }\r\n </div>\r\n }\r\n }\r\n\r\n <!-- FREEMULTISELECT -->\r\n @case ('freemultiselect') {\r\n <mat-form-field appearance=\"outline\" class=\"ui-form-builder__full-width ui-form-builder__free-multi-field\">\r\n <mat-label>{{ field.label }}</mat-label>\r\n <mat-chip-grid #chipGrid [formControlName]=\"field.key\">\r\n @for (chip of fg.get(field.key)?.value || []; track chip) {\r\n <mat-chip-row [removable]=\"!isChipProtected(field, chip)\" (removed)=\"removeFreeChip(field, chip)\">\r\n {{ chip }}\r\n @if (!isChipProtected(field, chip)) {\r\n <button matChipRemove aria-label=\"Rimuovi\">\r\n <lucide-icon name=\"x\" [size]=\"14\" />\r\n </button>\r\n }\r\n </mat-chip-row>\r\n }\r\n </mat-chip-grid>\r\n <input matInput [matChipInputFor]=\"chipGrid\" [placeholder]=\"field.placeholder || 'Aggiungi...'\"\r\n [readonly]=\"field.readonly || readonly\" (matChipInputTokenEnd)=\"addFreeChip(field, $event)\"\r\n (blur)=\"addFreeChipOnBlur(field, $event)\" />\r\n <mat-error>\r\n @for (err of getFieldErrors(field.key, fg); track $index) {\r\n <div>{{ err }}</div>\r\n }\r\n </mat-error>\r\n </mat-form-field>\r\n }\r\n\r\n <!-- CHECKBOX -->\r\n @case ('checkbox') {\r\n @if (field.appearance?.style === 'switch') {\r\n <mat-slide-toggle [formControlName]=\"field.key\" [color]=\"field.appearance?.color || 'primary'\">\r\n {{ field.label }}\r\n </mat-slide-toggle>\r\n } @else {\r\n <mat-checkbox [formControlName]=\"field.key\" [color]=\"field.appearance?.color || 'primary'\">\r\n {{ field.label }}\r\n @if (field.iconTooltip) {\r\n <lucide-icon [name]=\"field.iconTooltip.icon || 'info'\" [size]=\"14\" [matTooltip]=\"field.iconTooltip.text\"\r\n class=\"ui-form-builder__inline-tooltip\" />\r\n }\r\n </mat-checkbox>\r\n }\r\n @if (shouldShowFieldErrors(field.key, fg)) {\r\n <div class=\"ui-form-builder__field-error\">\r\n @for (err of getFieldErrors(field.key, fg); track $index) {\r\n <span>{{ err }}</span>\r\n }\r\n </div>\r\n }\r\n }\r\n\r\n <!-- SWITCH -->\r\n @case ('switch') {\r\n <mat-slide-toggle [formControlName]=\"field.key\" [color]=\"field.appearance?.color || 'primary'\">\r\n {{ field.label }}\r\n @if (field.iconTooltip) {\r\n <lucide-icon [name]=\"field.iconTooltip.icon || 'info'\" [size]=\"14\" [matTooltip]=\"field.iconTooltip.text\"\r\n class=\"ui-form-builder__inline-tooltip\" />\r\n }\r\n </mat-slide-toggle>\r\n }\r\n\r\n <!-- RADIO -->\r\n @case ('radio') {\r\n <ui-form-radio-field\r\n [field]=\"field\"\r\n [formGroup]=\"fg\"\r\n [readonly]=\"readonly\"\r\n [errors]=\"getFieldErrors(field.key, fg)\"\r\n [options]=\"getFieldOptions(field)\"\r\n [required]=\"isFieldRequired(field.key, fg)\"\r\n [showErrors]=\"shouldShowFieldErrors(field.key, fg)\"\r\n />\r\n }\r\n\r\n <!-- DATE -->\r\n @case ('date') {\r\n <mat-form-field appearance=\"outline\" class=\"ui-form-builder__full-width\">\r\n <mat-label>{{ field.label }}</mat-label>\r\n <input matInput [matDatepicker]=\"datepicker\" [formControlName]=\"field.key\"\r\n [placeholder]=\"field.placeholder || 'GG/MM/AAAA'\" [readonly]=\"field.readonly || readonly\" />\r\n <mat-datepicker-toggle matSuffix [for]=\"datepicker\" />\r\n <mat-datepicker #datepicker [startView]=\"field.customConfig?.config?.['monthView'] ? 'year' : 'month'\" />\r\n <mat-error>\r\n @for (err of getFieldErrors(field.key, fg); track $index) {\r\n <div>{{ err }}</div>\r\n }\r\n </mat-error>\r\n </mat-form-field>\r\n }\r\n\r\n <!-- DATETIME -->\r\n @case ('datetime') {\r\n <mat-form-field appearance=\"outline\" class=\"ui-form-builder__full-width\">\r\n <mat-label>{{ field.label }}</mat-label>\r\n <input matInput type=\"datetime-local\" [formControlName]=\"field.key\" [placeholder]=\"field.placeholder || ''\"\r\n [readonly]=\"field.readonly || readonly\" />\r\n <mat-error>\r\n @for (err of getFieldErrors(field.key, fg); track $index) {\r\n <div>{{ err }}</div>\r\n }\r\n </mat-error>\r\n </mat-form-field>\r\n }\r\n\r\n <!-- FILE -->\r\n @case ('file') {\r\n <div class=\"ui-form-builder__file-wrapper\">\r\n <label class=\"ui-form-builder__field-label\">\r\n {{ field.label }}\r\n @if (isFieldRequired(field.key, fg)) { <span class=\"ui-form-builder__required\">*</span> }\r\n </label>\r\n <ui-file-input [formControlName]=\"field.key\" [config]=\"field.fileConfig\" />\r\n @if (shouldShowFieldErrors(field.key, fg)) {\r\n <div class=\"ui-form-builder__field-error\">\r\n @for (err of getFieldErrors(field.key, fg); track $index) {\r\n <span>{{ err }}</span>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n\r\n <!-- DIVIDER -->\r\n @case ('divider') {\r\n <div class=\"ui-form-builder__divider\">\r\n @if (field.label) {\r\n <span class=\"ui-form-builder__divider-label\">{{ field.label }}</span>\r\n }\r\n <hr class=\"ui-form-builder__divider-line\" />\r\n </div>\r\n }\r\n\r\n <!-- LOCATION (selezione territorio) -->\r\n @case ('location') {\r\n <div class=\"ui-form-builder__location-wrapper\">\r\n @if (field.label) {\r\n <label class=\"ui-form-builder__field-label\">\r\n {{ field.label }}\r\n @if (isFieldRequired(field.key, fg)) { <span class=\"ui-form-builder__required\">*</span> }\r\n </label>\r\n }\r\n <ui-specifica-territoriale [formControlName]=\"field.key\" [config]=\"field.customConfig?.config || {}\"\r\n [disabled]=\"readonly || field.disabled || disabled\" />\r\n @if (shouldShowFieldErrors(field.key, fg)) {\r\n <div class=\"ui-form-builder__field-error\">\r\n @for (err of getFieldErrors(field.key, fg); track $index) {\r\n <span>{{ err }}</span>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n\r\n <!-- LOCATION-TABLE (tabella CRUD di location) -->\r\n @case ('location-table') {\r\n <div class=\"ui-form-builder__location-table-wrapper\">\r\n <ui-table-territoriale [formControlName]=\"field.key\" [config]=\"field.customConfig?.config || {}\"\r\n [disabled]=\"readonly || field.disabled || disabled\" />\r\n @if (shouldShowFieldErrors(field.key, fg)) {\r\n <div class=\"ui-form-builder__field-error\">\r\n @for (err of getFieldErrors(field.key, fg); track $index) {\r\n <span>{{ err }}</span>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n\r\n <!-- LOCATION-GEOCODED (ricerca indirizzo Nominatim) -->\r\n @case ('location-geocoded') {\r\n <div class=\"ui-form-builder__location-geocoded-wrapper\">\r\n @if (field.label) {\r\n <label class=\"ui-form-builder__field-label\" [attr.for]=\"'ui-loc-' + field.key\">\r\n {{ field.label }}\r\n @if (isFieldRequired(field.key, fg)) { <span class=\"ui-form-builder__required\">*</span> }\r\n </label>\r\n }\r\n <ui-location-geocoded [formControlName]=\"field.key\" [config]=\"field.locationGeocodedConfig || {}\"\r\n [placeholder]=\"field.placeholder || 'Cerca indirizzo...'\"\r\n [inputAriaLabel]=\"field.label || field.placeholder || 'Indirizzo'\"\r\n [inputId]=\"'ui-loc-' + field.key\"\r\n [readonly]=\"field.readonly || readonly\"\r\n [disabled]=\"readonly || field.disabled || disabled\" />\r\n @if (shouldShowFieldErrors(field.key, fg)) {\r\n <div class=\"ui-form-builder__field-error\">\r\n @for (err of getFieldErrors(field.key, fg); track $index) {\r\n <span>{{ err }}</span>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n\r\n <!-- CUSTOM -->\r\n @case ('custom') {\r\n <div class=\"ui-form-builder__custom-wrapper\" [attr.data-component]=\"field.customConfig?.component\"\r\n [attr.data-template-key]=\"resolveCustomTemplateKey(field)\">\r\n @if (field.label) {\r\n <label class=\"ui-form-builder__field-label\">\r\n {{ field.label }}\r\n @if (isFieldRequired(field.key, fg)) { <span class=\"ui-form-builder__required\">*</span> }\r\n </label>\r\n }\r\n @if (getCustomFieldOutlet(field, fg, nestedErrorKeyPrefix); as customOutlet) {\r\n <ng-container *ngTemplateOutlet=\"customOutlet.template; context: customOutlet.context\" />\r\n } @else {\r\n <div class=\"ui-form-builder__custom-placeholder\" role=\"status\">\r\n <span>Nessun template registrato per \u00AB{{ resolveCustomTemplateKey(field) }}\u00BB</span>\r\n </div>\r\n }\r\n @if (shouldShowFieldErrors(field.key, fg)) {\r\n <div class=\"ui-form-builder__field-error\">\r\n @for (err of getFieldErrors(field.key, fg); track $index) {\r\n <span>{{ err }}</span>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n }\r\n </div>\r\n }\r\n</ng-template>", styles: [".ui-form-builder{display:flex;flex-direction:column;gap:var(--ui-spacing-6);font-family:var(--ui-font-family)}.ui-form-builder__header{margin-bottom:var(--ui-spacing-2)}.ui-form-builder__title{font-size:var(--ui-font-size-xl);font-weight:var(--ui-font-weight-semibold);color:var(--ui-color-text);margin:0 0 var(--ui-spacing-1)}.ui-form-builder__description{font-size:var(--ui-font-size-sm);color:var(--ui-color-text-secondary);margin:0}.ui-form-builder__sections{display:flex;flex-direction:column;gap:var(--ui-spacing-6)}.ui-form-builder__section--repeatable{display:flex;flex-direction:column;gap:var(--ui-spacing-4)}.ui-form-builder__repeatable-action{appearance:none;border:none;background:transparent;cursor:pointer;padding:var(--ui-spacing-1);border-radius:var(--ui-radius-sm);color:var(--ui-color-text-muted);display:inline-flex;align-items:center;justify-content:center;transition:color var(--ui-transition-fast),background-color var(--ui-transition-fast)}.ui-form-builder__repeatable-action:focus{outline:none}.ui-form-builder__repeatable-action:focus-visible{outline:var(--ui-focus-ring-width) solid var(--ui-focus-ring-color);outline-offset:var(--ui-focus-ring-offset)}.ui-form-builder__repeatable-action:hover{color:var(--ui-color-text);background:var(--ui-color-surface-hover)}.ui-form-builder__repeatable-action--danger:hover{color:var(--ui-color-error, #dc2626);background:color-mix(in srgb,var(--ui-color-error, #dc2626) 8%,transparent)}.ui-form-builder__repeatable-add{display:flex;justify-content:flex-start;padding-top:var(--ui-spacing-2)}.ui-form-builder__section-header{margin-bottom:var(--ui-spacing-4);padding-bottom:var(--ui-spacing-2);border-bottom:1px solid var(--ui-color-border)}.ui-form-builder__section-title{font-size:var(--ui-font-size-lg);font-weight:var(--ui-font-weight-semibold);color:var(--ui-color-text);margin:0 0 var(--ui-spacing-1)}.ui-form-builder__section-description{font-size:var(--ui-font-size-sm);color:var(--ui-color-text-secondary);margin:0}.ui-form-builder__grid{display:grid;grid-template-columns:repeat(12,1fr);gap:var(--ui-spacing-4) var(--ui-spacing-4);align-items:start}.ui-form-builder__field-wrapper{grid-column:span 12;min-width:0}@media (min-width: 768px){.ui-form-builder__field-wrapper{grid-column:span 6}}@media (min-width: 576px){.ui-form-builder__field-wrapper.ui-col-sm-1{grid-column:span 1}.ui-form-builder__field-wrapper.ui-col-sm-2{grid-column:span 2}.ui-form-builder__field-wrapper.ui-col-sm-3{grid-column:span 3}.ui-form-builder__field-wrapper.ui-col-sm-4{grid-column:span 4}.ui-form-builder__field-wrapper.ui-col-sm-5{grid-column:span 5}.ui-form-builder__field-wrapper.ui-col-sm-6{grid-column:span 6}.ui-form-builder__field-wrapper.ui-col-sm-7{grid-column:span 7}.ui-form-builder__field-wrapper.ui-col-sm-8{grid-column:span 8}.ui-form-builder__field-wrapper.ui-col-sm-9{grid-column:span 9}.ui-form-builder__field-wrapper.ui-col-sm-10{grid-column:span 10}.ui-form-builder__field-wrapper.ui-col-sm-11{grid-column:span 11}.ui-form-builder__field-wrapper.ui-col-sm-12{grid-column:span 12}}@media (min-width: 768px){.ui-form-builder__field-wrapper.ui-col-md-1{grid-column:span 1}.ui-form-builder__field-wrapper.ui-col-md-2{grid-column:span 2}.ui-form-builder__field-wrapper.ui-col-md-3{grid-column:span 3}.ui-form-builder__field-wrapper.ui-col-md-4{grid-column:span 4}.ui-form-builder__field-wrapper.ui-col-md-5{grid-column:span 5}.ui-form-builder__field-wrapper.ui-col-md-6{grid-column:span 6}.ui-form-builder__field-wrapper.ui-col-md-7{grid-column:span 7}.ui-form-builder__field-wrapper.ui-col-md-8{grid-column:span 8}.ui-form-builder__field-wrapper.ui-col-md-9{grid-column:span 9}.ui-form-builder__field-wrapper.ui-col-md-10{grid-column:span 10}.ui-form-builder__field-wrapper.ui-col-md-11{grid-column:span 11}.ui-form-builder__field-wrapper.ui-col-md-12{grid-column:span 12}}@media (min-width: 1024px){.ui-form-builder__field-wrapper.ui-col-lg-1{grid-column:span 1}.ui-form-builder__field-wrapper.ui-col-lg-2{grid-column:span 2}.ui-form-builder__field-wrapper.ui-col-lg-3{grid-column:span 3}.ui-form-builder__field-wrapper.ui-col-lg-4{grid-column:span 4}.ui-form-builder__field-wrapper.ui-col-lg-5{grid-column:span 5}.ui-form-builder__field-wrapper.ui-col-lg-6{grid-column:span 6}.ui-form-builder__field-wrapper.ui-col-lg-7{grid-column:span 7}.ui-form-builder__field-wrapper.ui-col-lg-8{grid-column:span 8}.ui-form-builder__field-wrapper.ui-col-lg-9{grid-column:span 9}.ui-form-builder__field-wrapper.ui-col-lg-10{grid-column:span 10}.ui-form-builder__field-wrapper.ui-col-lg-11{grid-column:span 11}.ui-form-builder__field-wrapper.ui-col-lg-12{grid-column:span 12}}@media (min-width: 1280px){.ui-form-builder__field-wrapper.ui-col-xl-1{grid-column:span 1}.ui-form-builder__field-wrapper.ui-col-xl-2{grid-column:span 2}.ui-form-builder__field-wrapper.ui-col-xl-3{grid-column:span 3}.ui-form-builder__field-wrapper.ui-col-xl-4{grid-column:span 4}.ui-form-builder__field-wrapper.ui-col-xl-5{grid-column:span 5}.ui-form-builder__field-wrapper.ui-col-xl-6{grid-column:span 6}.ui-form-builder__field-wrapper.ui-col-xl-7{grid-column:span 7}.ui-form-builder__field-wrapper.ui-col-xl-8{grid-column:span 8}.ui-form-builder__field-wrapper.ui-col-xl-9{grid-column:span 9}.ui-form-builder__field-wrapper.ui-col-xl-10{grid-column:span 10}.ui-form-builder__field-wrapper.ui-col-xl-11{grid-column:span 11}.ui-form-builder__field-wrapper.ui-col-xl-12{grid-column:span 12}}.ui-form-builder__full-width{width:100%}.ui-fb-field-ctx,.ui-fb-text-ctx{display:contents}.ui-form-builder__textarea-wrapper{position:relative}.ui-form-builder mat-hint.ui-form-builder__char-count{font-size:var(--ui-font-size-xs);color:var(--ui-color-text-muted)}.ui-form-builder mat-hint.ui-form-builder__char-count.ui-form-builder__char-count--warn{color:var(--ui-color-warn, #d97706)}.ui-form-builder mat-hint.ui-form-builder__char-count.ui-form-builder__char-count--error{color:var(--ui-color-error, #dc2626);font-weight:var(--ui-font-weight-semibold)}.ui-form-builder__field-label{display:block;font-size:var(--ui-font-size-sm);font-weight:var(--ui-font-weight-medium);color:var(--ui-color-text);margin-bottom:var(--ui-spacing-4)}.ui-form-builder__required{color:var(--ui-color-error, #dc2626);margin-left:2px}.ui-form-builder__field-error{margin-top:var(--ui-spacing-1);font-size:var(--ui-font-size-xs);color:var(--ui-color-error, #dc2626);display:flex;flex-direction:column;gap:2px}.ui-form-builder__radio-wrapper mat-radio-group{display:flex;flex-wrap:wrap;gap:var(--ui-spacing-3)}.ui-form-builder__chips-preview{display:flex;flex-wrap:wrap;gap:var(--ui-spacing-1);margin-top:var(--ui-spacing-1)}.ui-form-builder__chip{display:inline-flex;align-items:center;gap:4px;padding:2px var(--ui-spacing-2);border-radius:var(--ui-radius-full);background:var(--ui-color-primary-subtle, rgba(37, 99, 235, .08));color:var(--ui-color-primary);font-size:var(--ui-font-size-xs)}.ui-form-builder__chip-remove{appearance:none;border:none;background:transparent;cursor:pointer;padding:0;display:flex;color:inherit;opacity:.7}.ui-form-builder__chip-remove:hover{opacity:1}.ui-form-builder__option-icon{margin-right:var(--ui-spacing-2);vertical-align:middle}.ui-form-builder__option-info{margin-left:auto;color:var(--ui-color-text-muted)}.ui-form-builder__inline-tooltip{margin-left:var(--ui-spacing-1);color:var(--ui-color-text-muted);cursor:help}.ui-form-builder__divider{grid-column:1/-1!important;display:flex;align-items:center;gap:var(--ui-spacing-3);padding:var(--ui-spacing-2) 0}.ui-form-builder__divider-label{font-size:var(--ui-font-size-sm);font-weight:var(--ui-font-weight-medium);color:var(--ui-color-text-secondary);white-space:nowrap}.ui-form-builder__divider-line{flex:1;border:none;border-top:1px solid var(--ui-color-border);margin:0}.ui-form-builder__custom-placeholder{display:flex;align-items:center;gap:var(--ui-spacing-2);padding:var(--ui-spacing-4);border:1px dashed var(--ui-color-border);border-radius:var(--ui-radius-md);color:var(--ui-color-text-muted);font-size:var(--ui-font-size-sm)}.ui-form-builder__footer{display:flex;align-items:center;justify-content:space-between;gap:var(--ui-spacing-4);padding-top:var(--ui-spacing-4);border-top:1px solid var(--ui-color-border)}@media (max-width: 767.98px){.ui-form-builder__footer{flex-direction:column;align-items:stretch}}.ui-form-field--highlight{animation:ui-field-highlight-pulse 1s ease-in-out 2;border-radius:var(--ui-radius-md)}@keyframes ui-field-highlight-pulse{0%{box-shadow:0 0 #dc262666;padding:10px}50%{box-shadow:0 0 0 6px #dc262626;padding:10px}to{box-shadow:0 0 #dc262600;padding:10px}}.ui-form-builder mat-error{font-size:var(--ui-font-size-xs, 12px);display:flex;flex-direction:column;transform:translateY(-5px)}.ui-form-builder .mat-mdc-form-field.mat-form-field-invalid .mdc-notched-outline__leading,.ui-form-builder .mat-mdc-form-field.mat-form-field-invalid .mdc-notched-outline__notch,.ui-form-builder .mat-mdc-form-field.mat-form-field-invalid .mdc-notched-outline__trailing{border-color:var(--ui-color-error, #dc2626)!important}.ui-form-builder .mat-mdc-form-field.mat-form-field-invalid .mat-mdc-floating-label{color:var(--ui-color-error, #dc2626)}.ui-form-builder__free-multi-field mat-chip-grid{display:flex;flex-wrap:wrap;gap:var(--ui-spacing-1)}.ui-form-builder__free-multi-field input[matInput]{flex:1;min-width:80px}.ui-form-builder mat-datepicker-toggle{display:inline-flex;align-items:center}.mdc-notched-outline__notch{border-left:none!important}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$2.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i2.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i2.MatLabel, selector: "mat-label" }, { kind: "directive", type: i2.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "directive", type: i2.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i3.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i4$1.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i5.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "component", type: i6$1.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i7.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "ngmodule", type: MatRadioModule }, { kind: "ngmodule", type: MatDatepickerModule }, { kind: "component", type: i8.MatDatepicker, selector: "mat-datepicker", exportAs: ["matDatepicker"] }, { kind: "directive", type: i8.MatDatepickerInput, selector: "input[matDatepicker]", inputs: ["matDatepicker", "min", "max", "matDatepickerFilter"], exportAs: ["matDatepickerInput"] }, { kind: "component", type: i8.MatDatepickerToggle, selector: "mat-datepicker-toggle", inputs: ["for", "tabIndex", "aria-label", "disabled", "disableRipple"], exportAs: ["matDatepickerToggle"] }, { kind: "ngmodule", type: MatNativeDateModule }, { kind: "ngmodule", type: MatAutocompleteModule }, { kind: "ngmodule", type: MatChipsModule }, { kind: "component", type: i9.MatChipGrid, selector: "mat-chip-grid", inputs: ["disabled", "placeholder", "required", "value", "errorStateMatcher"], outputs: ["change", "valueChange"] }, { kind: "directive", type: i9.MatChipInput, selector: "input[matChipInputFor]", inputs: ["matChipInputFor", "matChipInputAddOnBlur", "matChipInputSeparatorKeyCodes", "placeholder", "id", "disabled"], outputs: ["matChipInputTokenEnd"], exportAs: ["matChipInput", "matChipInputFor"] }, { kind: "directive", type: i9.MatChipRemove, selector: "[matChipRemove]" }, { kind: "component", type: i9.MatChipRow, selector: "mat-chip-row, [mat-chip-row], mat-basic-chip-row, [mat-basic-chip-row]", inputs: ["editable"], outputs: ["edited"] }, { kind: "component", type: UiAccordionComponent, selector: "ui-accordion", inputs: ["items", "itemTemplate", "headerTemplate", "actionsTemplate", "mode", "ariaLabel"], outputs: ["itemToggled"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i4.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: LucideAngularModule }, { kind: "component", type: i1.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }, { kind: "component", type: UiButtonAreaComponent, selector: "ui-button-area", inputs: ["buttons", "align", "ariaLabel", "gap", "stackOnMobile", "disableWhileLoading", "loadingIds"] }, { kind: "component", type: UiFormErrorSummaryComponent, selector: "ui-form-error-summary", inputs: ["errors", "totalErrorCount", "validMessage"], outputs: ["fieldClick"] }, { kind: "component", type: UiFileInputComponent, selector: "ui-file-input", inputs: ["config"], outputs: ["fileRemoved", "validationError"] }, { kind: "component", type: UiSpecificaTerritorialeComponent, selector: "ui-specifica-territoriale", inputs: ["config", "disabled"] }, { kind: "component", type: UiTableTerritorialeComponent, selector: "ui-table-territoriale", inputs: ["config", "disabled"] }, { kind: "component", type: UiLocationGeocodedComponent, selector: "ui-location-geocoded", inputs: ["config", "placeholder", "inputAriaLabel", "inputId", "disabled", "readonly"] }, { kind: "component", type: UiFormTextFieldComponent, selector: "ui-form-text-field", inputs: ["field", "formGroup", "readonly", "errors", "control", "label", "placeholder", "maxLength", "inputType", "validationRules", "options"], outputs: ["optionsFilter", "blurred"] }, { kind: "component", type: UiFormTextareaFieldComponent, selector: "ui-form-textarea-field", inputs: ["field", "formGroup", "readonly", "errors", "control", "label", "placeholder", "rows", "maxLength", "validationRules"], outputs: ["blurred"] }, { kind: "component", type: UiFormNumberFieldComponent, selector: "ui-form-number-field", inputs: ["field", "formGroup", "readonly", "errors", "control", "label", "placeholder", "validationRules", "currencyMode", "currencySymbol", "decimalPlaces"], outputs: ["blurred"] }, { kind: "component", type: UiFormSelectFieldComponent, selector: "ui-form-select-field", inputs: ["field", "formGroup", "readonly", "errors", "options", "control", "label", "placeholder", "searchable", "hideEmptyOption", "validationRules"], outputs: ["optionsFilter", "blurred"] }, { kind: "component", type: UiFormRadioFieldComponent, selector: "ui-form-radio-field", inputs: ["field", "formGroup", "readonly", "errors", "options", "required", "showErrors", "errorDisplayOptions", "control", "label", "validationRules", "color"], outputs: ["blurred"] }], encapsulation: i0.ViewEncapsulation.None }); }
|
|
49379
49421
|
}
|
|
49380
49422
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: UiFormBuilderComponent, decorators: [{
|
|
49381
49423
|
type: Component,
|
|
@@ -49415,7 +49457,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
49415
49457
|
{ provide: DateAdapter, useClass: UiItalianDateAdapter },
|
|
49416
49458
|
// Formati data italiani (DD/MM/YYYY)
|
|
49417
49459
|
{ provide: MAT_DATE_FORMATS, useValue: UI_IT_DATE_FORMATS },
|
|
49418
|
-
], encapsulation: ViewEncapsulation.None, host: { class: 'ui-form-builder-host' }, template: "<!--\r\n ============================================================\r\n UI FORM BUILDER - TEMPLATE PRINCIPALE\r\n ============================================================\r\n ARCHITETTURA:\r\n Ogni <ng-template> che contiene direttive [formControlName]\r\n DEVE wrappare il proprio contenuto in un <div [formGroup]>.\r\n Questo perche Angular risolve l'injector di formControlName\r\n dal CONTESTO DI DICHIARAZIONE del template, non dal punto\r\n di inserimento (ngTemplateOutlet).\r\n\r\n Il FormGroup viene passato tramite context per supportare\r\n sia campi piatti (root FormGroup) che campi in istanze\r\n repeatable (instance FormGroup).\r\n\r\n Senza questo wrapper, formControlName non trova il\r\n FormGroupDirective e lancia NG01050.\r\n ============================================================\r\n-->\r\n\r\n@if (schema && formGroup) {\r\n<div class=\"ui-form-builder\" [formGroup]=\"formGroup\" [class]=\"schema.config?.cssClasses?.join(' ')\">\r\n\r\n <!-- ==================== HEADER ==================== -->\r\n @if (schema.title || schema.description) {\r\n <div class=\"ui-form-builder__header\">\r\n @if (schema.title) {\r\n <h2 class=\"ui-form-builder__title\">{{ schema.title }}</h2>\r\n }\r\n @if (schema.description) {\r\n <p class=\"ui-form-builder__description\">{{ schema.description }}</p>\r\n }\r\n </div>\r\n }\r\n\r\n <!-- ==================== SEZIONI ==================== -->\r\n <div class=\"ui-form-builder__sections\">\r\n @for (section of schema.sections; track section.id) {\r\n @if (isSectionVisible(section.id)) {\r\n\r\n <!-- \u2500\u2500\u2500 Sezione REPEATABLE \u2500\u2500\u2500 -->\r\n @if (section.repeatable) {\r\n <div class=\"ui-form-builder__section ui-form-builder__section--repeatable\" [class]=\"section.cssClasses?.join(' ')\">\r\n @if (section.title || section.description) {\r\n <div class=\"ui-form-builder__section-header\">\r\n @if (section.title) {\r\n <h3 class=\"ui-form-builder__section-title\">{{ section.title }}</h3>\r\n }\r\n @if (section.description) {\r\n <p class=\"ui-form-builder__section-description\">{{ section.description }}</p>\r\n }\r\n </div>\r\n }\r\n\r\n <ui-accordion [items]=\"getRepeatableAccordionItems(section.id)\" [itemTemplate]=\"repeatableBodyTpl\"\r\n [actionsTemplate]=\"repeatableActionsTpl\" mode=\"multi\" class=\"ui-form-builder__repeatable-accordion\">\r\n <!-- Contenuto di ogni istanza -->\r\n <ng-template #repeatableBodyTpl let-item>\r\n <div class=\"ui-form-builder__section-content\"\r\n [class.ui-form-builder__grid]=\"schema.config?.layout === 'grid' || !schema.config?.layout\">\r\n @for (field of section.fields; track field.key) {\r\n @if (isFieldVisible(field.key, getInstanceFormGroup(section.id, item.index)!) && field.type !== 'flag') {\r\n <div class=\"ui-form-builder__field-wrapper\" [class]=\"getFieldWrapperClasses(field)\"\r\n [style.order]=\"field.layout?.order || null\" [attr.data-field-key]=\"field.key\">\r\n <ng-container\r\n *ngTemplateOutlet=\"fieldTpl; context: { $implicit: field, formGroup: getInstanceFormGroup(section.id, item.index) }\" />\r\n </div>\r\n }\r\n }\r\n </div>\r\n </ng-template>\r\n\r\n <!-- Azioni per ogni istanza (duplica / elimina) -->\r\n <ng-template #repeatableActionsTpl let-item>\r\n @if (canAddInstance(section.id)) {\r\n <button type=\"button\" class=\"ui-form-builder__repeatable-action\"\r\n (click)=\"duplicateRepeatableInstance(section.id, item.index)\" aria-label=\"Duplica\">\r\n <lucide-icon name=\"copy\" [size]=\"15\" />\r\n </button>\r\n }\r\n @if (canRemoveInstance(section.id)) {\r\n <button type=\"button\" class=\"ui-form-builder__repeatable-action ui-form-builder__repeatable-action--danger\"\r\n (click)=\"removeRepeatableInstance(section.id, item.index)\" aria-label=\"Elimina\">\r\n <lucide-icon name=\"trash-2\" [size]=\"15\" />\r\n </button>\r\n }\r\n </ng-template>\r\n </ui-accordion>\r\n\r\n @if (canAddInstance(section.id)) {\r\n <div class=\"ui-form-builder__repeatable-add\">\r\n <ui-button-area [buttons]=\"getRepeatableAddButtons(section)\" align=\"end\" />\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- \u2500\u2500\u2500 Sezione COLLASSABILE \u2500\u2500\u2500 -->\r\n } @else if (section.collapsible) {\r\n <ui-accordion [items]=\"getSectionAccordionItems(section.id)\" [itemTemplate]=\"collapsibleTpl\" mode=\"multi\"\r\n class=\"ui-form-builder__section ui-form-builder__section--collapsible\" [class]=\"section.cssClasses?.join(' ')\">\r\n <ng-template #collapsibleTpl let-item>\r\n <div class=\"ui-form-builder__section-content\"\r\n [class.ui-form-builder__grid]=\"schema.config?.layout === 'grid' || !schema.config?.layout\">\r\n @for (field of section.fields; track field.key) {\r\n @if (isFieldVisible(field.key) && field.type !== 'flag') {\r\n <div class=\"ui-form-builder__field-wrapper\" [class]=\"getFieldWrapperClasses(field)\"\r\n [style.order]=\"field.layout?.order || null\" [attr.data-field-key]=\"field.key\">\r\n <ng-container *ngTemplateOutlet=\"fieldTpl; context: { $implicit: field, formGroup: formGroup }\" />\r\n </div>\r\n }\r\n }\r\n </div>\r\n </ng-template>\r\n </ui-accordion>\r\n\r\n <!-- \u2500\u2500\u2500 Sezione STANDARD \u2500\u2500\u2500 -->\r\n } @else {\r\n <div class=\"ui-form-builder__section\" [class]=\"section.cssClasses?.join(' ')\">\r\n @if (section.title || section.description) {\r\n <div class=\"ui-form-builder__section-header\">\r\n @if (section.title) {\r\n <h3 class=\"ui-form-builder__section-title\">{{ section.title }}</h3>\r\n }\r\n @if (section.description) {\r\n <p class=\"ui-form-builder__section-description\">{{ section.description }}</p>\r\n }\r\n </div>\r\n }\r\n\r\n <div class=\"ui-form-builder__section-content\"\r\n [class.ui-form-builder__grid]=\"schema.config?.layout === 'grid' || !schema.config?.layout\">\r\n @for (field of section.fields; track field.key) {\r\n @if (isFieldVisible(field.key) && field.type !== 'flag') {\r\n <div class=\"ui-form-builder__field-wrapper\" [class]=\"getFieldWrapperClasses(field)\"\r\n [style.order]=\"field.layout?.order || null\" [attr.data-field-key]=\"field.key\">\r\n <ng-container *ngTemplateOutlet=\"fieldTpl; context: { $implicit: field, formGroup: formGroup }\" />\r\n </div>\r\n }\r\n }\r\n </div>\r\n </div>\r\n }\r\n }\r\n }\r\n </div>\r\n\r\n <!-- ==================== FOOTER ==================== -->\r\n <div class=\"ui-form-builder__footer\">\r\n @if (!schema.config?.hideErrorSummary) {\r\n <ui-form-error-summary [errors]=\"formErrors\" [totalErrorCount]=\"getFormErrorsCount()\"\r\n [validMessage]=\"schema.config?.errorSummaryValidMessage ?? 'Il form \u00E8 compilato correttamente'\"\r\n (fieldClick)=\"scrollToField($event)\" />\r\n }\r\n @if (!schema.config?.hideFooter) {\r\n <ui-button-area [buttons]=\"formButtons\" align=\"end\" [loadingIds]=\"loadingFor\" />\r\n }\r\n </div>\r\n</div>\r\n}\r\n\r\n<!-- ============================================================ -->\r\n<!-- FIELD TEMPLATE -->\r\n<!-- ============================================================ -->\r\n<!--\r\n CRITICO: Questo template e dichiarato FUORI dal <div [formGroup]>.\r\n Angular risolve l'injector dal contesto di DICHIARAZIONE.\r\n Il FormGroup viene passato via context (fg) per supportare\r\n sia il root FormGroup che i FormGroup delle istanze repeatable.\r\n-->\r\n<ng-template #fieldTpl let-field let-fg=\"formGroup\">\r\n @if (fg) {\r\n <div [formGroup]=\"fg\" class=\"ui-fb-field-ctx\">\r\n @switch (field.type) {\r\n\r\n <!-- TEXT / EMAIL / PASSWORD -->\r\n @case ('text') {\r\n <ui-form-text-field\r\n [field]=\"field\"\r\n [formGroup]=\"fg\"\r\n [readonly]=\"readonly\"\r\n [errors]=\"getFieldErrors(field.key, fg)\"\r\n [options]=\"getFieldOptions(field)\"\r\n (optionsFilter)=\"filterOptions(field, $event)\"\r\n />\r\n }\r\n @case ('email') {\r\n <ui-form-text-field\r\n [field]=\"field\"\r\n [formGroup]=\"fg\"\r\n [readonly]=\"readonly\"\r\n [errors]=\"getFieldErrors(field.key, fg)\"\r\n />\r\n }\r\n @case ('password') {\r\n <ui-form-text-field\r\n [field]=\"field\"\r\n [formGroup]=\"fg\"\r\n [readonly]=\"readonly\"\r\n [errors]=\"getFieldErrors(field.key, fg)\"\r\n />\r\n }\r\n\r\n <!-- NUMBER -->\r\n @case ('number') {\r\n <ui-form-number-field\r\n [field]=\"field\"\r\n [formGroup]=\"fg\"\r\n [readonly]=\"readonly\"\r\n [errors]=\"getFieldErrors(field.key, fg)\"\r\n />\r\n }\r\n\r\n <!-- TEXTAREA -->\r\n @case ('textarea') {\r\n <ui-form-textarea-field\r\n [field]=\"field\"\r\n [formGroup]=\"fg\"\r\n [readonly]=\"readonly\"\r\n [errors]=\"getFieldErrors(field.key, fg)\"\r\n />\r\n }\r\n\r\n <!-- SELECT -->\r\n @case ('select') {\r\n <ui-form-select-field\r\n [field]=\"field\"\r\n [formGroup]=\"fg\"\r\n [readonly]=\"readonly\"\r\n [errors]=\"getFieldErrors(field.key, fg)\"\r\n [options]=\"getFieldOptions(field)\"\r\n (optionsFilter)=\"filterOptions(field, $event)\"\r\n />\r\n }\r\n\r\n <!-- MULTISELECT -->\r\n @case ('multiselect') {\r\n <mat-form-field appearance=\"outline\" class=\"ui-form-builder__full-width\">\r\n <mat-label>{{ field.label }}</mat-label>\r\n <mat-select [formControlName]=\"field.key\" multiple [placeholder]=\"field.placeholder || ''\">\r\n @if (field.allowSelectAll) {\r\n <mat-option (click)=\"selectAll(field)\" (keydown.enter)=\"selectAll(field)\" (keydown.space)=\"selectAll(field)\">\r\n <em>Seleziona tutto</em>\r\n </mat-option>\r\n }\r\n @for (opt of getFieldOptions(field); track opt.value) {\r\n <mat-option [value]=\"opt.value\" [disabled]=\"opt.disabled\">\r\n {{ opt.label }}\r\n </mat-option>\r\n }\r\n </mat-select>\r\n <mat-error>\r\n @for (err of getFieldErrors(field.key, fg); track $index) {\r\n <div>{{ err }}</div>\r\n }\r\n </mat-error>\r\n </mat-form-field>\r\n @if (fg.get(field.key)?.value?.length) {\r\n <div class=\"ui-form-builder__chips-preview\">\r\n @for (val of fg.get(field.key)!.value; track val) {\r\n <span class=\"ui-form-builder__chip\">\r\n {{ getOptionLabel(field, val) }}\r\n <button type=\"button\" (click)=\"removeMultiselectChip(field, val)\" class=\"ui-form-builder__chip-remove\"\r\n aria-label=\"Rimuovi\">\r\n <lucide-icon name=\"x\" [size]=\"12\" />\r\n </button>\r\n </span>\r\n }\r\n </div>\r\n }\r\n }\r\n\r\n <!-- FREEMULTISELECT -->\r\n @case ('freemultiselect') {\r\n <mat-form-field appearance=\"outline\" class=\"ui-form-builder__full-width ui-form-builder__free-multi-field\">\r\n <mat-label>{{ field.label }}</mat-label>\r\n <mat-chip-grid #chipGrid [formControlName]=\"field.key\">\r\n @for (chip of fg.get(field.key)?.value || []; track chip) {\r\n <mat-chip-row [removable]=\"!isChipProtected(field, chip)\" (removed)=\"removeFreeChip(field, chip)\">\r\n {{ chip }}\r\n @if (!isChipProtected(field, chip)) {\r\n <button matChipRemove aria-label=\"Rimuovi\">\r\n <lucide-icon name=\"x\" [size]=\"14\" />\r\n </button>\r\n }\r\n </mat-chip-row>\r\n }\r\n </mat-chip-grid>\r\n <input matInput [matChipInputFor]=\"chipGrid\" [placeholder]=\"field.placeholder || 'Aggiungi...'\"\r\n [readonly]=\"field.readonly || readonly\" (matChipInputTokenEnd)=\"addFreeChip(field, $event)\"\r\n (blur)=\"addFreeChipOnBlur(field, $event)\" />\r\n <mat-error>\r\n @for (err of getFieldErrors(field.key, fg); track $index) {\r\n <div>{{ err }}</div>\r\n }\r\n </mat-error>\r\n </mat-form-field>\r\n }\r\n\r\n <!-- CHECKBOX -->\r\n @case ('checkbox') {\r\n @if (field.appearance?.style === 'switch') {\r\n <mat-slide-toggle [formControlName]=\"field.key\" [color]=\"field.appearance?.color || 'primary'\">\r\n {{ field.label }}\r\n </mat-slide-toggle>\r\n } @else {\r\n <mat-checkbox [formControlName]=\"field.key\" [color]=\"field.appearance?.color || 'primary'\">\r\n {{ field.label }}\r\n @if (field.iconTooltip) {\r\n <lucide-icon [name]=\"field.iconTooltip.icon || 'info'\" [size]=\"14\" [matTooltip]=\"field.iconTooltip.text\"\r\n class=\"ui-form-builder__inline-tooltip\" />\r\n }\r\n </mat-checkbox>\r\n }\r\n @if (shouldShowFieldErrors(field.key, fg)) {\r\n <div class=\"ui-form-builder__field-error\">\r\n @for (err of getFieldErrors(field.key, fg); track $index) {\r\n <span>{{ err }}</span>\r\n }\r\n </div>\r\n }\r\n }\r\n\r\n <!-- SWITCH -->\r\n @case ('switch') {\r\n <mat-slide-toggle [formControlName]=\"field.key\" [color]=\"field.appearance?.color || 'primary'\">\r\n {{ field.label }}\r\n @if (field.iconTooltip) {\r\n <lucide-icon [name]=\"field.iconTooltip.icon || 'info'\" [size]=\"14\" [matTooltip]=\"field.iconTooltip.text\"\r\n class=\"ui-form-builder__inline-tooltip\" />\r\n }\r\n </mat-slide-toggle>\r\n }\r\n\r\n <!-- RADIO -->\r\n @case ('radio') {\r\n <ui-form-radio-field\r\n [field]=\"field\"\r\n [formGroup]=\"fg\"\r\n [readonly]=\"readonly\"\r\n [errors]=\"getFieldErrors(field.key, fg)\"\r\n [options]=\"getFieldOptions(field)\"\r\n [required]=\"isFieldRequired(field.key, fg)\"\r\n [showErrors]=\"shouldShowFieldErrors(field.key, fg)\"\r\n />\r\n }\r\n\r\n <!-- DATE -->\r\n @case ('date') {\r\n <mat-form-field appearance=\"outline\" class=\"ui-form-builder__full-width\">\r\n <mat-label>{{ field.label }}</mat-label>\r\n <input matInput [matDatepicker]=\"datepicker\" [formControlName]=\"field.key\"\r\n [placeholder]=\"field.placeholder || 'GG/MM/AAAA'\" [readonly]=\"field.readonly || readonly\" />\r\n <mat-datepicker-toggle matSuffix [for]=\"datepicker\" />\r\n <mat-datepicker #datepicker [startView]=\"field.customConfig?.config?.['monthView'] ? 'year' : 'month'\" />\r\n <mat-error>\r\n @for (err of getFieldErrors(field.key, fg); track $index) {\r\n <div>{{ err }}</div>\r\n }\r\n </mat-error>\r\n </mat-form-field>\r\n }\r\n\r\n <!-- DATETIME -->\r\n @case ('datetime') {\r\n <mat-form-field appearance=\"outline\" class=\"ui-form-builder__full-width\">\r\n <mat-label>{{ field.label }}</mat-label>\r\n <input matInput type=\"datetime-local\" [formControlName]=\"field.key\" [placeholder]=\"field.placeholder || ''\"\r\n [readonly]=\"field.readonly || readonly\" />\r\n <mat-error>\r\n @for (err of getFieldErrors(field.key, fg); track $index) {\r\n <div>{{ err }}</div>\r\n }\r\n </mat-error>\r\n </mat-form-field>\r\n }\r\n\r\n <!-- FILE -->\r\n @case ('file') {\r\n <div class=\"ui-form-builder__file-wrapper\">\r\n <label class=\"ui-form-builder__field-label\">\r\n {{ field.label }}\r\n @if (isFieldRequired(field.key, fg)) { <span class=\"ui-form-builder__required\">*</span> }\r\n </label>\r\n <ui-file-input [formControlName]=\"field.key\" [config]=\"field.fileConfig\" />\r\n @if (shouldShowFieldErrors(field.key, fg)) {\r\n <div class=\"ui-form-builder__field-error\">\r\n @for (err of getFieldErrors(field.key, fg); track $index) {\r\n <span>{{ err }}</span>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n\r\n <!-- DIVIDER -->\r\n @case ('divider') {\r\n <div class=\"ui-form-builder__divider\">\r\n @if (field.label) {\r\n <span class=\"ui-form-builder__divider-label\">{{ field.label }}</span>\r\n }\r\n <hr class=\"ui-form-builder__divider-line\" />\r\n </div>\r\n }\r\n\r\n <!-- LOCATION (selezione territorio) -->\r\n @case ('location') {\r\n <div class=\"ui-form-builder__location-wrapper\">\r\n @if (field.label) {\r\n <label class=\"ui-form-builder__field-label\">\r\n {{ field.label }}\r\n @if (isFieldRequired(field.key, fg)) { <span class=\"ui-form-builder__required\">*</span> }\r\n </label>\r\n }\r\n <ui-specifica-territoriale [formControlName]=\"field.key\" [config]=\"field.customConfig?.config || {}\"\r\n [disabled]=\"readonly || field.disabled || disabled\" />\r\n @if (shouldShowFieldErrors(field.key, fg)) {\r\n <div class=\"ui-form-builder__field-error\">\r\n @for (err of getFieldErrors(field.key, fg); track $index) {\r\n <span>{{ err }}</span>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n\r\n <!-- LOCATION-TABLE (tabella CRUD di location) -->\r\n @case ('location-table') {\r\n <div class=\"ui-form-builder__location-table-wrapper\">\r\n <ui-table-territoriale [formControlName]=\"field.key\" [config]=\"field.customConfig?.config || {}\"\r\n [disabled]=\"readonly || field.disabled || disabled\" />\r\n @if (shouldShowFieldErrors(field.key, fg)) {\r\n <div class=\"ui-form-builder__field-error\">\r\n @for (err of getFieldErrors(field.key, fg); track $index) {\r\n <span>{{ err }}</span>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n\r\n <!-- LOCATION-GEOCODED (ricerca indirizzo Nominatim) -->\r\n @case ('location-geocoded') {\r\n <div class=\"ui-form-builder__location-geocoded-wrapper\">\r\n @if (field.label) {\r\n <label class=\"ui-form-builder__field-label\" [attr.for]=\"'ui-loc-' + field.key\">\r\n {{ field.label }}\r\n @if (isFieldRequired(field.key, fg)) { <span class=\"ui-form-builder__required\">*</span> }\r\n </label>\r\n }\r\n <ui-location-geocoded [formControlName]=\"field.key\" [config]=\"field.locationGeocodedConfig || {}\"\r\n [placeholder]=\"field.placeholder || 'Cerca indirizzo...'\"\r\n [inputAriaLabel]=\"field.label || field.placeholder || 'Indirizzo'\"\r\n [inputId]=\"'ui-loc-' + field.key\"\r\n [readonly]=\"field.readonly || readonly\"\r\n [disabled]=\"readonly || field.disabled || disabled\" />\r\n @if (shouldShowFieldErrors(field.key, fg)) {\r\n <div class=\"ui-form-builder__field-error\">\r\n @for (err of getFieldErrors(field.key, fg); track $index) {\r\n <span>{{ err }}</span>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n\r\n <!-- CUSTOM -->\r\n @case ('custom') {\r\n <div class=\"ui-form-builder__custom-wrapper\" [attr.data-component]=\"field.customConfig?.component\"\r\n [attr.data-template-key]=\"resolveCustomTemplateKey(field)\">\r\n @if (field.label) {\r\n <label class=\"ui-form-builder__field-label\">\r\n {{ field.label }}\r\n @if (isFieldRequired(field.key, fg)) { <span class=\"ui-form-builder__required\">*</span> }\r\n </label>\r\n }\r\n @if (getCustomFieldOutlet(field, fg); as customOutlet) {\r\n <ng-container *ngTemplateOutlet=\"customOutlet.template; context: customOutlet.context\" />\r\n } @else {\r\n <div class=\"ui-form-builder__custom-placeholder\" role=\"status\">\r\n <span>Nessun template registrato per \u00AB{{ resolveCustomTemplateKey(field) }}\u00BB</span>\r\n </div>\r\n }\r\n @if (shouldShowFieldErrors(field.key, fg)) {\r\n <div class=\"ui-form-builder__field-error\">\r\n @for (err of getFieldErrors(field.key, fg); track $index) {\r\n <span>{{ err }}</span>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n }\r\n </div>\r\n }\r\n</ng-template>", styles: [".ui-form-builder{display:flex;flex-direction:column;gap:var(--ui-spacing-6);font-family:var(--ui-font-family)}.ui-form-builder__header{margin-bottom:var(--ui-spacing-2)}.ui-form-builder__title{font-size:var(--ui-font-size-xl);font-weight:var(--ui-font-weight-semibold);color:var(--ui-color-text);margin:0 0 var(--ui-spacing-1)}.ui-form-builder__description{font-size:var(--ui-font-size-sm);color:var(--ui-color-text-secondary);margin:0}.ui-form-builder__sections{display:flex;flex-direction:column;gap:var(--ui-spacing-6)}.ui-form-builder__section--repeatable{display:flex;flex-direction:column;gap:var(--ui-spacing-4)}.ui-form-builder__repeatable-action{appearance:none;border:none;background:transparent;cursor:pointer;padding:var(--ui-spacing-1);border-radius:var(--ui-radius-sm);color:var(--ui-color-text-muted);display:inline-flex;align-items:center;justify-content:center;transition:color var(--ui-transition-fast),background-color var(--ui-transition-fast)}.ui-form-builder__repeatable-action:focus{outline:none}.ui-form-builder__repeatable-action:focus-visible{outline:var(--ui-focus-ring-width) solid var(--ui-focus-ring-color);outline-offset:var(--ui-focus-ring-offset)}.ui-form-builder__repeatable-action:hover{color:var(--ui-color-text);background:var(--ui-color-surface-hover)}.ui-form-builder__repeatable-action--danger:hover{color:var(--ui-color-error, #dc2626);background:color-mix(in srgb,var(--ui-color-error, #dc2626) 8%,transparent)}.ui-form-builder__repeatable-add{display:flex;justify-content:flex-start;padding-top:var(--ui-spacing-2)}.ui-form-builder__section-header{margin-bottom:var(--ui-spacing-4);padding-bottom:var(--ui-spacing-2);border-bottom:1px solid var(--ui-color-border)}.ui-form-builder__section-title{font-size:var(--ui-font-size-lg);font-weight:var(--ui-font-weight-semibold);color:var(--ui-color-text);margin:0 0 var(--ui-spacing-1)}.ui-form-builder__section-description{font-size:var(--ui-font-size-sm);color:var(--ui-color-text-secondary);margin:0}.ui-form-builder__grid{display:grid;grid-template-columns:repeat(12,1fr);gap:var(--ui-spacing-4) var(--ui-spacing-4);align-items:start}.ui-form-builder__field-wrapper{grid-column:span 12;min-width:0}@media (min-width: 768px){.ui-form-builder__field-wrapper{grid-column:span 6}}@media (min-width: 576px){.ui-form-builder__field-wrapper.ui-col-sm-1{grid-column:span 1}.ui-form-builder__field-wrapper.ui-col-sm-2{grid-column:span 2}.ui-form-builder__field-wrapper.ui-col-sm-3{grid-column:span 3}.ui-form-builder__field-wrapper.ui-col-sm-4{grid-column:span 4}.ui-form-builder__field-wrapper.ui-col-sm-5{grid-column:span 5}.ui-form-builder__field-wrapper.ui-col-sm-6{grid-column:span 6}.ui-form-builder__field-wrapper.ui-col-sm-7{grid-column:span 7}.ui-form-builder__field-wrapper.ui-col-sm-8{grid-column:span 8}.ui-form-builder__field-wrapper.ui-col-sm-9{grid-column:span 9}.ui-form-builder__field-wrapper.ui-col-sm-10{grid-column:span 10}.ui-form-builder__field-wrapper.ui-col-sm-11{grid-column:span 11}.ui-form-builder__field-wrapper.ui-col-sm-12{grid-column:span 12}}@media (min-width: 768px){.ui-form-builder__field-wrapper.ui-col-md-1{grid-column:span 1}.ui-form-builder__field-wrapper.ui-col-md-2{grid-column:span 2}.ui-form-builder__field-wrapper.ui-col-md-3{grid-column:span 3}.ui-form-builder__field-wrapper.ui-col-md-4{grid-column:span 4}.ui-form-builder__field-wrapper.ui-col-md-5{grid-column:span 5}.ui-form-builder__field-wrapper.ui-col-md-6{grid-column:span 6}.ui-form-builder__field-wrapper.ui-col-md-7{grid-column:span 7}.ui-form-builder__field-wrapper.ui-col-md-8{grid-column:span 8}.ui-form-builder__field-wrapper.ui-col-md-9{grid-column:span 9}.ui-form-builder__field-wrapper.ui-col-md-10{grid-column:span 10}.ui-form-builder__field-wrapper.ui-col-md-11{grid-column:span 11}.ui-form-builder__field-wrapper.ui-col-md-12{grid-column:span 12}}@media (min-width: 1024px){.ui-form-builder__field-wrapper.ui-col-lg-1{grid-column:span 1}.ui-form-builder__field-wrapper.ui-col-lg-2{grid-column:span 2}.ui-form-builder__field-wrapper.ui-col-lg-3{grid-column:span 3}.ui-form-builder__field-wrapper.ui-col-lg-4{grid-column:span 4}.ui-form-builder__field-wrapper.ui-col-lg-5{grid-column:span 5}.ui-form-builder__field-wrapper.ui-col-lg-6{grid-column:span 6}.ui-form-builder__field-wrapper.ui-col-lg-7{grid-column:span 7}.ui-form-builder__field-wrapper.ui-col-lg-8{grid-column:span 8}.ui-form-builder__field-wrapper.ui-col-lg-9{grid-column:span 9}.ui-form-builder__field-wrapper.ui-col-lg-10{grid-column:span 10}.ui-form-builder__field-wrapper.ui-col-lg-11{grid-column:span 11}.ui-form-builder__field-wrapper.ui-col-lg-12{grid-column:span 12}}@media (min-width: 1280px){.ui-form-builder__field-wrapper.ui-col-xl-1{grid-column:span 1}.ui-form-builder__field-wrapper.ui-col-xl-2{grid-column:span 2}.ui-form-builder__field-wrapper.ui-col-xl-3{grid-column:span 3}.ui-form-builder__field-wrapper.ui-col-xl-4{grid-column:span 4}.ui-form-builder__field-wrapper.ui-col-xl-5{grid-column:span 5}.ui-form-builder__field-wrapper.ui-col-xl-6{grid-column:span 6}.ui-form-builder__field-wrapper.ui-col-xl-7{grid-column:span 7}.ui-form-builder__field-wrapper.ui-col-xl-8{grid-column:span 8}.ui-form-builder__field-wrapper.ui-col-xl-9{grid-column:span 9}.ui-form-builder__field-wrapper.ui-col-xl-10{grid-column:span 10}.ui-form-builder__field-wrapper.ui-col-xl-11{grid-column:span 11}.ui-form-builder__field-wrapper.ui-col-xl-12{grid-column:span 12}}.ui-form-builder__full-width{width:100%}.ui-fb-field-ctx,.ui-fb-text-ctx{display:contents}.ui-form-builder__textarea-wrapper{position:relative}.ui-form-builder mat-hint.ui-form-builder__char-count{font-size:var(--ui-font-size-xs);color:var(--ui-color-text-muted)}.ui-form-builder mat-hint.ui-form-builder__char-count.ui-form-builder__char-count--warn{color:var(--ui-color-warn, #d97706)}.ui-form-builder mat-hint.ui-form-builder__char-count.ui-form-builder__char-count--error{color:var(--ui-color-error, #dc2626);font-weight:var(--ui-font-weight-semibold)}.ui-form-builder__field-label{display:block;font-size:var(--ui-font-size-sm);font-weight:var(--ui-font-weight-medium);color:var(--ui-color-text);margin-bottom:var(--ui-spacing-4)}.ui-form-builder__required{color:var(--ui-color-error, #dc2626);margin-left:2px}.ui-form-builder__field-error{margin-top:var(--ui-spacing-1);font-size:var(--ui-font-size-xs);color:var(--ui-color-error, #dc2626);display:flex;flex-direction:column;gap:2px}.ui-form-builder__radio-wrapper mat-radio-group{display:flex;flex-wrap:wrap;gap:var(--ui-spacing-3)}.ui-form-builder__chips-preview{display:flex;flex-wrap:wrap;gap:var(--ui-spacing-1);margin-top:var(--ui-spacing-1)}.ui-form-builder__chip{display:inline-flex;align-items:center;gap:4px;padding:2px var(--ui-spacing-2);border-radius:var(--ui-radius-full);background:var(--ui-color-primary-subtle, rgba(37, 99, 235, .08));color:var(--ui-color-primary);font-size:var(--ui-font-size-xs)}.ui-form-builder__chip-remove{appearance:none;border:none;background:transparent;cursor:pointer;padding:0;display:flex;color:inherit;opacity:.7}.ui-form-builder__chip-remove:hover{opacity:1}.ui-form-builder__option-icon{margin-right:var(--ui-spacing-2);vertical-align:middle}.ui-form-builder__option-info{margin-left:auto;color:var(--ui-color-text-muted)}.ui-form-builder__inline-tooltip{margin-left:var(--ui-spacing-1);color:var(--ui-color-text-muted);cursor:help}.ui-form-builder__divider{grid-column:1/-1!important;display:flex;align-items:center;gap:var(--ui-spacing-3);padding:var(--ui-spacing-2) 0}.ui-form-builder__divider-label{font-size:var(--ui-font-size-sm);font-weight:var(--ui-font-weight-medium);color:var(--ui-color-text-secondary);white-space:nowrap}.ui-form-builder__divider-line{flex:1;border:none;border-top:1px solid var(--ui-color-border);margin:0}.ui-form-builder__custom-placeholder{display:flex;align-items:center;gap:var(--ui-spacing-2);padding:var(--ui-spacing-4);border:1px dashed var(--ui-color-border);border-radius:var(--ui-radius-md);color:var(--ui-color-text-muted);font-size:var(--ui-font-size-sm)}.ui-form-builder__footer{display:flex;align-items:center;justify-content:space-between;gap:var(--ui-spacing-4);padding-top:var(--ui-spacing-4);border-top:1px solid var(--ui-color-border)}@media (max-width: 767.98px){.ui-form-builder__footer{flex-direction:column;align-items:stretch}}.ui-form-field--highlight{animation:ui-field-highlight-pulse 1s ease-in-out 2;border-radius:var(--ui-radius-md)}@keyframes ui-field-highlight-pulse{0%{box-shadow:0 0 #dc262666;padding:10px}50%{box-shadow:0 0 0 6px #dc262626;padding:10px}to{box-shadow:0 0 #dc262600;padding:10px}}.ui-form-builder mat-error{font-size:var(--ui-font-size-xs, 12px);display:flex;flex-direction:column;transform:translateY(-5px)}.ui-form-builder .mat-mdc-form-field.mat-form-field-invalid .mdc-notched-outline__leading,.ui-form-builder .mat-mdc-form-field.mat-form-field-invalid .mdc-notched-outline__notch,.ui-form-builder .mat-mdc-form-field.mat-form-field-invalid .mdc-notched-outline__trailing{border-color:var(--ui-color-error, #dc2626)!important}.ui-form-builder .mat-mdc-form-field.mat-form-field-invalid .mat-mdc-floating-label{color:var(--ui-color-error, #dc2626)}.ui-form-builder__free-multi-field mat-chip-grid{display:flex;flex-wrap:wrap;gap:var(--ui-spacing-1)}.ui-form-builder__free-multi-field input[matInput]{flex:1;min-width:80px}.ui-form-builder mat-datepicker-toggle{display:inline-flex;align-items:center}.mdc-notched-outline__notch{border-left:none!important}\n"] }]
|
|
49460
|
+
], encapsulation: ViewEncapsulation.None, host: { class: 'ui-form-builder-host' }, template: "<!--\r\n ============================================================\r\n UI FORM BUILDER - TEMPLATE PRINCIPALE\r\n ============================================================\r\n ARCHITETTURA:\r\n Ogni <ng-template> che contiene direttive [formControlName]\r\n DEVE wrappare il proprio contenuto in un <div [formGroup]>.\r\n Questo perche Angular risolve l'injector di formControlName\r\n dal CONTESTO DI DICHIARAZIONE del template, non dal punto\r\n di inserimento (ngTemplateOutlet).\r\n\r\n Il FormGroup viene passato tramite context per supportare\r\n sia campi piatti (root FormGroup) che campi in istanze\r\n repeatable (instance FormGroup).\r\n\r\n Senza questo wrapper, formControlName non trova il\r\n FormGroupDirective e lancia NG01050.\r\n ============================================================\r\n-->\r\n\r\n@if (schema && formGroup) {\r\n<div class=\"ui-form-builder\" [formGroup]=\"formGroup\" [class]=\"schema.config?.cssClasses?.join(' ')\">\r\n\r\n <!-- ==================== HEADER ==================== -->\r\n @if (schema.title || schema.description) {\r\n <div class=\"ui-form-builder__header\">\r\n @if (schema.title) {\r\n <h2 class=\"ui-form-builder__title\">{{ schema.title }}</h2>\r\n }\r\n @if (schema.description) {\r\n <p class=\"ui-form-builder__description\">{{ schema.description }}</p>\r\n }\r\n </div>\r\n }\r\n\r\n <!-- ==================== SEZIONI ==================== -->\r\n <div class=\"ui-form-builder__sections\">\r\n @for (section of schema.sections; track section.id) {\r\n @if (isSectionVisible(section.id)) {\r\n\r\n <!-- \u2500\u2500\u2500 Sezione REPEATABLE \u2500\u2500\u2500 -->\r\n @if (section.repeatable) {\r\n <div class=\"ui-form-builder__section ui-form-builder__section--repeatable\" [class]=\"section.cssClasses?.join(' ')\">\r\n @if (section.title || section.description) {\r\n <div class=\"ui-form-builder__section-header\">\r\n @if (section.title) {\r\n <h3 class=\"ui-form-builder__section-title\">{{ section.title }}</h3>\r\n }\r\n @if (section.description) {\r\n <p class=\"ui-form-builder__section-description\">{{ section.description }}</p>\r\n }\r\n </div>\r\n }\r\n\r\n <ui-accordion [items]=\"getRepeatableAccordionItems(section.id)\" [itemTemplate]=\"repeatableBodyTpl\"\r\n [actionsTemplate]=\"repeatableActionsTpl\" mode=\"multi\" class=\"ui-form-builder__repeatable-accordion\">\r\n <!-- Contenuto di ogni istanza -->\r\n <ng-template #repeatableBodyTpl let-item>\r\n <div class=\"ui-form-builder__section-content\"\r\n [class.ui-form-builder__grid]=\"schema.config?.layout === 'grid' || !schema.config?.layout\">\r\n @for (field of section.fields; track field.key) {\r\n @if (isFieldVisible(field.key, getInstanceFormGroup(section.id, item.index)!) && field.type !== 'flag') {\r\n <div class=\"ui-form-builder__field-wrapper\" [class]=\"getFieldWrapperClasses(field)\"\r\n [style.order]=\"field.layout?.order || null\" [attr.data-field-key]=\"field.key\">\r\n <ng-container\r\n *ngTemplateOutlet=\"fieldTpl; context: { $implicit: field, formGroup: getInstanceFormGroup(section.id, item.index), nestedErrorKeyPrefix: buildNestedErrorKeyPrefix(field.key, section.id + '[' + item.index + '].') }\" />\r\n </div>\r\n }\r\n }\r\n </div>\r\n </ng-template>\r\n\r\n <!-- Azioni per ogni istanza (duplica / elimina) -->\r\n <ng-template #repeatableActionsTpl let-item>\r\n @if (canAddInstance(section.id)) {\r\n <button type=\"button\" class=\"ui-form-builder__repeatable-action\"\r\n (click)=\"duplicateRepeatableInstance(section.id, item.index)\" aria-label=\"Duplica\">\r\n <lucide-icon name=\"copy\" [size]=\"15\" />\r\n </button>\r\n }\r\n @if (canRemoveInstance(section.id)) {\r\n <button type=\"button\" class=\"ui-form-builder__repeatable-action ui-form-builder__repeatable-action--danger\"\r\n (click)=\"removeRepeatableInstance(section.id, item.index)\" aria-label=\"Elimina\">\r\n <lucide-icon name=\"trash-2\" [size]=\"15\" />\r\n </button>\r\n }\r\n </ng-template>\r\n </ui-accordion>\r\n\r\n @if (canAddInstance(section.id)) {\r\n <div class=\"ui-form-builder__repeatable-add\">\r\n <ui-button-area [buttons]=\"getRepeatableAddButtons(section)\" align=\"end\" />\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- \u2500\u2500\u2500 Sezione COLLASSABILE \u2500\u2500\u2500 -->\r\n } @else if (section.collapsible) {\r\n <ui-accordion [items]=\"getSectionAccordionItems(section.id)\" [itemTemplate]=\"collapsibleTpl\" mode=\"multi\"\r\n class=\"ui-form-builder__section ui-form-builder__section--collapsible\" [class]=\"section.cssClasses?.join(' ')\">\r\n <ng-template #collapsibleTpl let-item>\r\n <div class=\"ui-form-builder__section-content\"\r\n [class.ui-form-builder__grid]=\"schema.config?.layout === 'grid' || !schema.config?.layout\">\r\n @for (field of section.fields; track field.key) {\r\n @if (isFieldVisible(field.key) && field.type !== 'flag') {\r\n <div class=\"ui-form-builder__field-wrapper\" [class]=\"getFieldWrapperClasses(field)\"\r\n [style.order]=\"field.layout?.order || null\" [attr.data-field-key]=\"field.key\">\r\n <ng-container *ngTemplateOutlet=\"fieldTpl; context: { $implicit: field, formGroup: formGroup, nestedErrorKeyPrefix: buildNestedErrorKeyPrefix(field.key) }\" />\r\n </div>\r\n }\r\n }\r\n </div>\r\n </ng-template>\r\n </ui-accordion>\r\n\r\n <!-- \u2500\u2500\u2500 Sezione STANDARD \u2500\u2500\u2500 -->\r\n } @else {\r\n <div class=\"ui-form-builder__section\" [class]=\"section.cssClasses?.join(' ')\">\r\n @if (section.title || section.description) {\r\n <div class=\"ui-form-builder__section-header\">\r\n @if (section.title) {\r\n <h3 class=\"ui-form-builder__section-title\">{{ section.title }}</h3>\r\n }\r\n @if (section.description) {\r\n <p class=\"ui-form-builder__section-description\">{{ section.description }}</p>\r\n }\r\n </div>\r\n }\r\n\r\n <div class=\"ui-form-builder__section-content\"\r\n [class.ui-form-builder__grid]=\"schema.config?.layout === 'grid' || !schema.config?.layout\">\r\n @for (field of section.fields; track field.key) {\r\n @if (isFieldVisible(field.key) && field.type !== 'flag') {\r\n <div class=\"ui-form-builder__field-wrapper\" [class]=\"getFieldWrapperClasses(field)\"\r\n [style.order]=\"field.layout?.order || null\" [attr.data-field-key]=\"field.key\">\r\n <ng-container *ngTemplateOutlet=\"fieldTpl; context: { $implicit: field, formGroup: formGroup, nestedErrorKeyPrefix: buildNestedErrorKeyPrefix(field.key) }\" />\r\n </div>\r\n }\r\n }\r\n </div>\r\n </div>\r\n }\r\n }\r\n }\r\n </div>\r\n\r\n <!-- ==================== FOOTER ==================== -->\r\n <div class=\"ui-form-builder__footer\">\r\n @if (!schema.config?.hideErrorSummary) {\r\n <ui-form-error-summary [errors]=\"formErrors\" [totalErrorCount]=\"getFormErrorsCount()\"\r\n [validMessage]=\"schema.config?.errorSummaryValidMessage ?? 'Il form \u00E8 compilato correttamente'\"\r\n (fieldClick)=\"scrollToField($event)\" />\r\n }\r\n @if (!schema.config?.hideFooter) {\r\n <ui-button-area [buttons]=\"formButtons\" align=\"end\" [loadingIds]=\"loadingFor\" />\r\n }\r\n </div>\r\n</div>\r\n}\r\n\r\n<!-- ============================================================ -->\r\n<!-- FIELD TEMPLATE -->\r\n<!-- ============================================================ -->\r\n<!--\r\n CRITICO: Questo template e dichiarato FUORI dal <div [formGroup]>.\r\n Angular risolve l'injector dal contesto di DICHIARAZIONE.\r\n Il FormGroup viene passato via context (fg) per supportare\r\n sia il root FormGroup che i FormGroup delle istanze repeatable.\r\n-->\r\n<ng-template #fieldTpl let-field let-fg=\"formGroup\" let-nestedErrorKeyPrefix=\"nestedErrorKeyPrefix\">\r\n @if (fg) {\r\n <div [formGroup]=\"fg\" class=\"ui-fb-field-ctx\">\r\n @switch (field.type) {\r\n\r\n <!-- TEXT / EMAIL / PASSWORD -->\r\n @case ('text') {\r\n <ui-form-text-field\r\n [field]=\"field\"\r\n [formGroup]=\"fg\"\r\n [readonly]=\"readonly\"\r\n [errors]=\"getFieldErrors(field.key, fg)\"\r\n [options]=\"getFieldOptions(field)\"\r\n (optionsFilter)=\"filterOptions(field, $event)\"\r\n />\r\n }\r\n @case ('email') {\r\n <ui-form-text-field\r\n [field]=\"field\"\r\n [formGroup]=\"fg\"\r\n [readonly]=\"readonly\"\r\n [errors]=\"getFieldErrors(field.key, fg)\"\r\n />\r\n }\r\n @case ('password') {\r\n <ui-form-text-field\r\n [field]=\"field\"\r\n [formGroup]=\"fg\"\r\n [readonly]=\"readonly\"\r\n [errors]=\"getFieldErrors(field.key, fg)\"\r\n />\r\n }\r\n\r\n <!-- NUMBER -->\r\n @case ('number') {\r\n <ui-form-number-field\r\n [field]=\"field\"\r\n [formGroup]=\"fg\"\r\n [readonly]=\"readonly\"\r\n [errors]=\"getFieldErrors(field.key, fg)\"\r\n />\r\n }\r\n\r\n <!-- TEXTAREA -->\r\n @case ('textarea') {\r\n <ui-form-textarea-field\r\n [field]=\"field\"\r\n [formGroup]=\"fg\"\r\n [readonly]=\"readonly\"\r\n [errors]=\"getFieldErrors(field.key, fg)\"\r\n />\r\n }\r\n\r\n <!-- SELECT -->\r\n @case ('select') {\r\n <ui-form-select-field\r\n [field]=\"field\"\r\n [formGroup]=\"fg\"\r\n [readonly]=\"readonly\"\r\n [errors]=\"getFieldErrors(field.key, fg)\"\r\n [options]=\"getFieldOptions(field)\"\r\n (optionsFilter)=\"filterOptions(field, $event)\"\r\n />\r\n }\r\n\r\n <!-- MULTISELECT -->\r\n @case ('multiselect') {\r\n <mat-form-field appearance=\"outline\" class=\"ui-form-builder__full-width\">\r\n <mat-label>{{ field.label }}</mat-label>\r\n <mat-select [formControlName]=\"field.key\" multiple [placeholder]=\"field.placeholder || ''\">\r\n @if (field.allowSelectAll) {\r\n <mat-option (click)=\"selectAll(field)\" (keydown.enter)=\"selectAll(field)\" (keydown.space)=\"selectAll(field)\">\r\n <em>Seleziona tutto</em>\r\n </mat-option>\r\n }\r\n @for (opt of getFieldOptions(field); track opt.value) {\r\n <mat-option [value]=\"opt.value\" [disabled]=\"opt.disabled\">\r\n {{ opt.label }}\r\n </mat-option>\r\n }\r\n </mat-select>\r\n <mat-error>\r\n @for (err of getFieldErrors(field.key, fg); track $index) {\r\n <div>{{ err }}</div>\r\n }\r\n </mat-error>\r\n </mat-form-field>\r\n @if (fg.get(field.key)?.value?.length) {\r\n <div class=\"ui-form-builder__chips-preview\">\r\n @for (val of fg.get(field.key)!.value; track val) {\r\n <span class=\"ui-form-builder__chip\">\r\n {{ getOptionLabel(field, val) }}\r\n <button type=\"button\" (click)=\"removeMultiselectChip(field, val)\" class=\"ui-form-builder__chip-remove\"\r\n aria-label=\"Rimuovi\">\r\n <lucide-icon name=\"x\" [size]=\"12\" />\r\n </button>\r\n </span>\r\n }\r\n </div>\r\n }\r\n }\r\n\r\n <!-- FREEMULTISELECT -->\r\n @case ('freemultiselect') {\r\n <mat-form-field appearance=\"outline\" class=\"ui-form-builder__full-width ui-form-builder__free-multi-field\">\r\n <mat-label>{{ field.label }}</mat-label>\r\n <mat-chip-grid #chipGrid [formControlName]=\"field.key\">\r\n @for (chip of fg.get(field.key)?.value || []; track chip) {\r\n <mat-chip-row [removable]=\"!isChipProtected(field, chip)\" (removed)=\"removeFreeChip(field, chip)\">\r\n {{ chip }}\r\n @if (!isChipProtected(field, chip)) {\r\n <button matChipRemove aria-label=\"Rimuovi\">\r\n <lucide-icon name=\"x\" [size]=\"14\" />\r\n </button>\r\n }\r\n </mat-chip-row>\r\n }\r\n </mat-chip-grid>\r\n <input matInput [matChipInputFor]=\"chipGrid\" [placeholder]=\"field.placeholder || 'Aggiungi...'\"\r\n [readonly]=\"field.readonly || readonly\" (matChipInputTokenEnd)=\"addFreeChip(field, $event)\"\r\n (blur)=\"addFreeChipOnBlur(field, $event)\" />\r\n <mat-error>\r\n @for (err of getFieldErrors(field.key, fg); track $index) {\r\n <div>{{ err }}</div>\r\n }\r\n </mat-error>\r\n </mat-form-field>\r\n }\r\n\r\n <!-- CHECKBOX -->\r\n @case ('checkbox') {\r\n @if (field.appearance?.style === 'switch') {\r\n <mat-slide-toggle [formControlName]=\"field.key\" [color]=\"field.appearance?.color || 'primary'\">\r\n {{ field.label }}\r\n </mat-slide-toggle>\r\n } @else {\r\n <mat-checkbox [formControlName]=\"field.key\" [color]=\"field.appearance?.color || 'primary'\">\r\n {{ field.label }}\r\n @if (field.iconTooltip) {\r\n <lucide-icon [name]=\"field.iconTooltip.icon || 'info'\" [size]=\"14\" [matTooltip]=\"field.iconTooltip.text\"\r\n class=\"ui-form-builder__inline-tooltip\" />\r\n }\r\n </mat-checkbox>\r\n }\r\n @if (shouldShowFieldErrors(field.key, fg)) {\r\n <div class=\"ui-form-builder__field-error\">\r\n @for (err of getFieldErrors(field.key, fg); track $index) {\r\n <span>{{ err }}</span>\r\n }\r\n </div>\r\n }\r\n }\r\n\r\n <!-- SWITCH -->\r\n @case ('switch') {\r\n <mat-slide-toggle [formControlName]=\"field.key\" [color]=\"field.appearance?.color || 'primary'\">\r\n {{ field.label }}\r\n @if (field.iconTooltip) {\r\n <lucide-icon [name]=\"field.iconTooltip.icon || 'info'\" [size]=\"14\" [matTooltip]=\"field.iconTooltip.text\"\r\n class=\"ui-form-builder__inline-tooltip\" />\r\n }\r\n </mat-slide-toggle>\r\n }\r\n\r\n <!-- RADIO -->\r\n @case ('radio') {\r\n <ui-form-radio-field\r\n [field]=\"field\"\r\n [formGroup]=\"fg\"\r\n [readonly]=\"readonly\"\r\n [errors]=\"getFieldErrors(field.key, fg)\"\r\n [options]=\"getFieldOptions(field)\"\r\n [required]=\"isFieldRequired(field.key, fg)\"\r\n [showErrors]=\"shouldShowFieldErrors(field.key, fg)\"\r\n />\r\n }\r\n\r\n <!-- DATE -->\r\n @case ('date') {\r\n <mat-form-field appearance=\"outline\" class=\"ui-form-builder__full-width\">\r\n <mat-label>{{ field.label }}</mat-label>\r\n <input matInput [matDatepicker]=\"datepicker\" [formControlName]=\"field.key\"\r\n [placeholder]=\"field.placeholder || 'GG/MM/AAAA'\" [readonly]=\"field.readonly || readonly\" />\r\n <mat-datepicker-toggle matSuffix [for]=\"datepicker\" />\r\n <mat-datepicker #datepicker [startView]=\"field.customConfig?.config?.['monthView'] ? 'year' : 'month'\" />\r\n <mat-error>\r\n @for (err of getFieldErrors(field.key, fg); track $index) {\r\n <div>{{ err }}</div>\r\n }\r\n </mat-error>\r\n </mat-form-field>\r\n }\r\n\r\n <!-- DATETIME -->\r\n @case ('datetime') {\r\n <mat-form-field appearance=\"outline\" class=\"ui-form-builder__full-width\">\r\n <mat-label>{{ field.label }}</mat-label>\r\n <input matInput type=\"datetime-local\" [formControlName]=\"field.key\" [placeholder]=\"field.placeholder || ''\"\r\n [readonly]=\"field.readonly || readonly\" />\r\n <mat-error>\r\n @for (err of getFieldErrors(field.key, fg); track $index) {\r\n <div>{{ err }}</div>\r\n }\r\n </mat-error>\r\n </mat-form-field>\r\n }\r\n\r\n <!-- FILE -->\r\n @case ('file') {\r\n <div class=\"ui-form-builder__file-wrapper\">\r\n <label class=\"ui-form-builder__field-label\">\r\n {{ field.label }}\r\n @if (isFieldRequired(field.key, fg)) { <span class=\"ui-form-builder__required\">*</span> }\r\n </label>\r\n <ui-file-input [formControlName]=\"field.key\" [config]=\"field.fileConfig\" />\r\n @if (shouldShowFieldErrors(field.key, fg)) {\r\n <div class=\"ui-form-builder__field-error\">\r\n @for (err of getFieldErrors(field.key, fg); track $index) {\r\n <span>{{ err }}</span>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n\r\n <!-- DIVIDER -->\r\n @case ('divider') {\r\n <div class=\"ui-form-builder__divider\">\r\n @if (field.label) {\r\n <span class=\"ui-form-builder__divider-label\">{{ field.label }}</span>\r\n }\r\n <hr class=\"ui-form-builder__divider-line\" />\r\n </div>\r\n }\r\n\r\n <!-- LOCATION (selezione territorio) -->\r\n @case ('location') {\r\n <div class=\"ui-form-builder__location-wrapper\">\r\n @if (field.label) {\r\n <label class=\"ui-form-builder__field-label\">\r\n {{ field.label }}\r\n @if (isFieldRequired(field.key, fg)) { <span class=\"ui-form-builder__required\">*</span> }\r\n </label>\r\n }\r\n <ui-specifica-territoriale [formControlName]=\"field.key\" [config]=\"field.customConfig?.config || {}\"\r\n [disabled]=\"readonly || field.disabled || disabled\" />\r\n @if (shouldShowFieldErrors(field.key, fg)) {\r\n <div class=\"ui-form-builder__field-error\">\r\n @for (err of getFieldErrors(field.key, fg); track $index) {\r\n <span>{{ err }}</span>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n\r\n <!-- LOCATION-TABLE (tabella CRUD di location) -->\r\n @case ('location-table') {\r\n <div class=\"ui-form-builder__location-table-wrapper\">\r\n <ui-table-territoriale [formControlName]=\"field.key\" [config]=\"field.customConfig?.config || {}\"\r\n [disabled]=\"readonly || field.disabled || disabled\" />\r\n @if (shouldShowFieldErrors(field.key, fg)) {\r\n <div class=\"ui-form-builder__field-error\">\r\n @for (err of getFieldErrors(field.key, fg); track $index) {\r\n <span>{{ err }}</span>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n\r\n <!-- LOCATION-GEOCODED (ricerca indirizzo Nominatim) -->\r\n @case ('location-geocoded') {\r\n <div class=\"ui-form-builder__location-geocoded-wrapper\">\r\n @if (field.label) {\r\n <label class=\"ui-form-builder__field-label\" [attr.for]=\"'ui-loc-' + field.key\">\r\n {{ field.label }}\r\n @if (isFieldRequired(field.key, fg)) { <span class=\"ui-form-builder__required\">*</span> }\r\n </label>\r\n }\r\n <ui-location-geocoded [formControlName]=\"field.key\" [config]=\"field.locationGeocodedConfig || {}\"\r\n [placeholder]=\"field.placeholder || 'Cerca indirizzo...'\"\r\n [inputAriaLabel]=\"field.label || field.placeholder || 'Indirizzo'\"\r\n [inputId]=\"'ui-loc-' + field.key\"\r\n [readonly]=\"field.readonly || readonly\"\r\n [disabled]=\"readonly || field.disabled || disabled\" />\r\n @if (shouldShowFieldErrors(field.key, fg)) {\r\n <div class=\"ui-form-builder__field-error\">\r\n @for (err of getFieldErrors(field.key, fg); track $index) {\r\n <span>{{ err }}</span>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n\r\n <!-- CUSTOM -->\r\n @case ('custom') {\r\n <div class=\"ui-form-builder__custom-wrapper\" [attr.data-component]=\"field.customConfig?.component\"\r\n [attr.data-template-key]=\"resolveCustomTemplateKey(field)\">\r\n @if (field.label) {\r\n <label class=\"ui-form-builder__field-label\">\r\n {{ field.label }}\r\n @if (isFieldRequired(field.key, fg)) { <span class=\"ui-form-builder__required\">*</span> }\r\n </label>\r\n }\r\n @if (getCustomFieldOutlet(field, fg, nestedErrorKeyPrefix); as customOutlet) {\r\n <ng-container *ngTemplateOutlet=\"customOutlet.template; context: customOutlet.context\" />\r\n } @else {\r\n <div class=\"ui-form-builder__custom-placeholder\" role=\"status\">\r\n <span>Nessun template registrato per \u00AB{{ resolveCustomTemplateKey(field) }}\u00BB</span>\r\n </div>\r\n }\r\n @if (shouldShowFieldErrors(field.key, fg)) {\r\n <div class=\"ui-form-builder__field-error\">\r\n @for (err of getFieldErrors(field.key, fg); track $index) {\r\n <span>{{ err }}</span>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n }\r\n </div>\r\n }\r\n</ng-template>", styles: [".ui-form-builder{display:flex;flex-direction:column;gap:var(--ui-spacing-6);font-family:var(--ui-font-family)}.ui-form-builder__header{margin-bottom:var(--ui-spacing-2)}.ui-form-builder__title{font-size:var(--ui-font-size-xl);font-weight:var(--ui-font-weight-semibold);color:var(--ui-color-text);margin:0 0 var(--ui-spacing-1)}.ui-form-builder__description{font-size:var(--ui-font-size-sm);color:var(--ui-color-text-secondary);margin:0}.ui-form-builder__sections{display:flex;flex-direction:column;gap:var(--ui-spacing-6)}.ui-form-builder__section--repeatable{display:flex;flex-direction:column;gap:var(--ui-spacing-4)}.ui-form-builder__repeatable-action{appearance:none;border:none;background:transparent;cursor:pointer;padding:var(--ui-spacing-1);border-radius:var(--ui-radius-sm);color:var(--ui-color-text-muted);display:inline-flex;align-items:center;justify-content:center;transition:color var(--ui-transition-fast),background-color var(--ui-transition-fast)}.ui-form-builder__repeatable-action:focus{outline:none}.ui-form-builder__repeatable-action:focus-visible{outline:var(--ui-focus-ring-width) solid var(--ui-focus-ring-color);outline-offset:var(--ui-focus-ring-offset)}.ui-form-builder__repeatable-action:hover{color:var(--ui-color-text);background:var(--ui-color-surface-hover)}.ui-form-builder__repeatable-action--danger:hover{color:var(--ui-color-error, #dc2626);background:color-mix(in srgb,var(--ui-color-error, #dc2626) 8%,transparent)}.ui-form-builder__repeatable-add{display:flex;justify-content:flex-start;padding-top:var(--ui-spacing-2)}.ui-form-builder__section-header{margin-bottom:var(--ui-spacing-4);padding-bottom:var(--ui-spacing-2);border-bottom:1px solid var(--ui-color-border)}.ui-form-builder__section-title{font-size:var(--ui-font-size-lg);font-weight:var(--ui-font-weight-semibold);color:var(--ui-color-text);margin:0 0 var(--ui-spacing-1)}.ui-form-builder__section-description{font-size:var(--ui-font-size-sm);color:var(--ui-color-text-secondary);margin:0}.ui-form-builder__grid{display:grid;grid-template-columns:repeat(12,1fr);gap:var(--ui-spacing-4) var(--ui-spacing-4);align-items:start}.ui-form-builder__field-wrapper{grid-column:span 12;min-width:0}@media (min-width: 768px){.ui-form-builder__field-wrapper{grid-column:span 6}}@media (min-width: 576px){.ui-form-builder__field-wrapper.ui-col-sm-1{grid-column:span 1}.ui-form-builder__field-wrapper.ui-col-sm-2{grid-column:span 2}.ui-form-builder__field-wrapper.ui-col-sm-3{grid-column:span 3}.ui-form-builder__field-wrapper.ui-col-sm-4{grid-column:span 4}.ui-form-builder__field-wrapper.ui-col-sm-5{grid-column:span 5}.ui-form-builder__field-wrapper.ui-col-sm-6{grid-column:span 6}.ui-form-builder__field-wrapper.ui-col-sm-7{grid-column:span 7}.ui-form-builder__field-wrapper.ui-col-sm-8{grid-column:span 8}.ui-form-builder__field-wrapper.ui-col-sm-9{grid-column:span 9}.ui-form-builder__field-wrapper.ui-col-sm-10{grid-column:span 10}.ui-form-builder__field-wrapper.ui-col-sm-11{grid-column:span 11}.ui-form-builder__field-wrapper.ui-col-sm-12{grid-column:span 12}}@media (min-width: 768px){.ui-form-builder__field-wrapper.ui-col-md-1{grid-column:span 1}.ui-form-builder__field-wrapper.ui-col-md-2{grid-column:span 2}.ui-form-builder__field-wrapper.ui-col-md-3{grid-column:span 3}.ui-form-builder__field-wrapper.ui-col-md-4{grid-column:span 4}.ui-form-builder__field-wrapper.ui-col-md-5{grid-column:span 5}.ui-form-builder__field-wrapper.ui-col-md-6{grid-column:span 6}.ui-form-builder__field-wrapper.ui-col-md-7{grid-column:span 7}.ui-form-builder__field-wrapper.ui-col-md-8{grid-column:span 8}.ui-form-builder__field-wrapper.ui-col-md-9{grid-column:span 9}.ui-form-builder__field-wrapper.ui-col-md-10{grid-column:span 10}.ui-form-builder__field-wrapper.ui-col-md-11{grid-column:span 11}.ui-form-builder__field-wrapper.ui-col-md-12{grid-column:span 12}}@media (min-width: 1024px){.ui-form-builder__field-wrapper.ui-col-lg-1{grid-column:span 1}.ui-form-builder__field-wrapper.ui-col-lg-2{grid-column:span 2}.ui-form-builder__field-wrapper.ui-col-lg-3{grid-column:span 3}.ui-form-builder__field-wrapper.ui-col-lg-4{grid-column:span 4}.ui-form-builder__field-wrapper.ui-col-lg-5{grid-column:span 5}.ui-form-builder__field-wrapper.ui-col-lg-6{grid-column:span 6}.ui-form-builder__field-wrapper.ui-col-lg-7{grid-column:span 7}.ui-form-builder__field-wrapper.ui-col-lg-8{grid-column:span 8}.ui-form-builder__field-wrapper.ui-col-lg-9{grid-column:span 9}.ui-form-builder__field-wrapper.ui-col-lg-10{grid-column:span 10}.ui-form-builder__field-wrapper.ui-col-lg-11{grid-column:span 11}.ui-form-builder__field-wrapper.ui-col-lg-12{grid-column:span 12}}@media (min-width: 1280px){.ui-form-builder__field-wrapper.ui-col-xl-1{grid-column:span 1}.ui-form-builder__field-wrapper.ui-col-xl-2{grid-column:span 2}.ui-form-builder__field-wrapper.ui-col-xl-3{grid-column:span 3}.ui-form-builder__field-wrapper.ui-col-xl-4{grid-column:span 4}.ui-form-builder__field-wrapper.ui-col-xl-5{grid-column:span 5}.ui-form-builder__field-wrapper.ui-col-xl-6{grid-column:span 6}.ui-form-builder__field-wrapper.ui-col-xl-7{grid-column:span 7}.ui-form-builder__field-wrapper.ui-col-xl-8{grid-column:span 8}.ui-form-builder__field-wrapper.ui-col-xl-9{grid-column:span 9}.ui-form-builder__field-wrapper.ui-col-xl-10{grid-column:span 10}.ui-form-builder__field-wrapper.ui-col-xl-11{grid-column:span 11}.ui-form-builder__field-wrapper.ui-col-xl-12{grid-column:span 12}}.ui-form-builder__full-width{width:100%}.ui-fb-field-ctx,.ui-fb-text-ctx{display:contents}.ui-form-builder__textarea-wrapper{position:relative}.ui-form-builder mat-hint.ui-form-builder__char-count{font-size:var(--ui-font-size-xs);color:var(--ui-color-text-muted)}.ui-form-builder mat-hint.ui-form-builder__char-count.ui-form-builder__char-count--warn{color:var(--ui-color-warn, #d97706)}.ui-form-builder mat-hint.ui-form-builder__char-count.ui-form-builder__char-count--error{color:var(--ui-color-error, #dc2626);font-weight:var(--ui-font-weight-semibold)}.ui-form-builder__field-label{display:block;font-size:var(--ui-font-size-sm);font-weight:var(--ui-font-weight-medium);color:var(--ui-color-text);margin-bottom:var(--ui-spacing-4)}.ui-form-builder__required{color:var(--ui-color-error, #dc2626);margin-left:2px}.ui-form-builder__field-error{margin-top:var(--ui-spacing-1);font-size:var(--ui-font-size-xs);color:var(--ui-color-error, #dc2626);display:flex;flex-direction:column;gap:2px}.ui-form-builder__radio-wrapper mat-radio-group{display:flex;flex-wrap:wrap;gap:var(--ui-spacing-3)}.ui-form-builder__chips-preview{display:flex;flex-wrap:wrap;gap:var(--ui-spacing-1);margin-top:var(--ui-spacing-1)}.ui-form-builder__chip{display:inline-flex;align-items:center;gap:4px;padding:2px var(--ui-spacing-2);border-radius:var(--ui-radius-full);background:var(--ui-color-primary-subtle, rgba(37, 99, 235, .08));color:var(--ui-color-primary);font-size:var(--ui-font-size-xs)}.ui-form-builder__chip-remove{appearance:none;border:none;background:transparent;cursor:pointer;padding:0;display:flex;color:inherit;opacity:.7}.ui-form-builder__chip-remove:hover{opacity:1}.ui-form-builder__option-icon{margin-right:var(--ui-spacing-2);vertical-align:middle}.ui-form-builder__option-info{margin-left:auto;color:var(--ui-color-text-muted)}.ui-form-builder__inline-tooltip{margin-left:var(--ui-spacing-1);color:var(--ui-color-text-muted);cursor:help}.ui-form-builder__divider{grid-column:1/-1!important;display:flex;align-items:center;gap:var(--ui-spacing-3);padding:var(--ui-spacing-2) 0}.ui-form-builder__divider-label{font-size:var(--ui-font-size-sm);font-weight:var(--ui-font-weight-medium);color:var(--ui-color-text-secondary);white-space:nowrap}.ui-form-builder__divider-line{flex:1;border:none;border-top:1px solid var(--ui-color-border);margin:0}.ui-form-builder__custom-placeholder{display:flex;align-items:center;gap:var(--ui-spacing-2);padding:var(--ui-spacing-4);border:1px dashed var(--ui-color-border);border-radius:var(--ui-radius-md);color:var(--ui-color-text-muted);font-size:var(--ui-font-size-sm)}.ui-form-builder__footer{display:flex;align-items:center;justify-content:space-between;gap:var(--ui-spacing-4);padding-top:var(--ui-spacing-4);border-top:1px solid var(--ui-color-border)}@media (max-width: 767.98px){.ui-form-builder__footer{flex-direction:column;align-items:stretch}}.ui-form-field--highlight{animation:ui-field-highlight-pulse 1s ease-in-out 2;border-radius:var(--ui-radius-md)}@keyframes ui-field-highlight-pulse{0%{box-shadow:0 0 #dc262666;padding:10px}50%{box-shadow:0 0 0 6px #dc262626;padding:10px}to{box-shadow:0 0 #dc262600;padding:10px}}.ui-form-builder mat-error{font-size:var(--ui-font-size-xs, 12px);display:flex;flex-direction:column;transform:translateY(-5px)}.ui-form-builder .mat-mdc-form-field.mat-form-field-invalid .mdc-notched-outline__leading,.ui-form-builder .mat-mdc-form-field.mat-form-field-invalid .mdc-notched-outline__notch,.ui-form-builder .mat-mdc-form-field.mat-form-field-invalid .mdc-notched-outline__trailing{border-color:var(--ui-color-error, #dc2626)!important}.ui-form-builder .mat-mdc-form-field.mat-form-field-invalid .mat-mdc-floating-label{color:var(--ui-color-error, #dc2626)}.ui-form-builder__free-multi-field mat-chip-grid{display:flex;flex-wrap:wrap;gap:var(--ui-spacing-1)}.ui-form-builder__free-multi-field input[matInput]{flex:1;min-width:80px}.ui-form-builder mat-datepicker-toggle{display:inline-flex;align-items:center}.mdc-notched-outline__notch{border-left:none!important}\n"] }]
|
|
49419
49461
|
}], propDecorators: { schema: [{
|
|
49420
49462
|
type: Input
|
|
49421
49463
|
}], initialData: [{
|
|
@@ -51373,6 +51415,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
51373
51415
|
args: ['document:keydown', ['$event']]
|
|
51374
51416
|
}] } });
|
|
51375
51417
|
|
|
51418
|
+
// ── Tipi validazione ──
|
|
51419
|
+
|
|
51376
51420
|
// ── Componenti ──
|
|
51377
51421
|
|
|
51378
51422
|
/**
|
|
@@ -61600,5 +61644,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
61600
61644
|
* Generated bundle index. Do not edit.
|
|
61601
61645
|
*/
|
|
61602
61646
|
|
|
61603
|
-
export { EDITOR_PRESETS, NG_UI_SYSTEM_VERSION, PRESET_ANAGRAFICA, PRESET_SURVEY, PRESET_TICKET, UI_BLACKBOX_DEFAULTS, UI_BREADCRUMB_LABEL_RESOLVER, UI_HEADERS_PROVIDER, UI_HTTP_CONFIG, UI_IT_DATE_FORMATS, UI_LOGGER_DEFAULT_TTL_MS, UI_LOGGER_IDB_NAME, UI_LOGGER_IDB_STORE, UI_LOGGER_IDB_VERSION, UI_LOGGER_LEVELS, UI_LOGGER_MAX_ENTRIES, UI_TOAST_DEFAULTS, UiAccordionComponent, UiBaseLayoutComponent, UiBlackboxDebuggerComponent, UiBlackboxDebuggerService, UiBlackboxFingerprintService, UiBlackboxJsonViewerComponent, UiBlackboxService, UiBlackboxStorageService, UiBreadcrumbService, UiButtonAreaComponent, UiButtonComponent, UiConditionEditorComponent, UiConfirmDialogComponent, UiCrudTableComponent, UiCurrencyInputDirective, UiEditorFieldConfigPanelComponent, UiEditorFieldFactoryService, UiEditorFormValuesPanelComponent, UiEditorPersistenceService, UiEditorPreviewContainerComponent, UiEditorStateService, UiEditorToolbarComponent, UiFieldPaletteComponent, UiFileInputComponent, UiFormBuilderComponent, UiFormBuilderEditorComponent, UiFormBuilderEditorService, UiFormConditionService, UiFormErrorStateMatcher, UiFormErrorSummaryComponent, UiFormFieldErrorService, UiFormNumberFieldComponent, UiFormRadioFieldComponent, UiFormSelectFieldComponent, UiFormTextFieldComponent, UiFormTextareaFieldComponent, UiFormValidationService, UiFormWizardComponent, UiHttpMessageHandler, UiHttpService, UiItalianDateAdapter, UiLayoutBuilderComponent, UiLayoutService, UiLocationGeocodedComponent, UiLocationService, UiLoggerService, UiModalComponent, UiModalService, UiNominatimGeocodingService, UiOptionsEditorComponent, UiPageHeaderComponent, UiPaginatedTableComponent, UiSectionEditorComponent, UiSpecificaTerritorialeComponent, UiTableTerritorialeComponent, UiToastComponent, UiToastContainerComponent, UiToastService, UiTrackDirective, UiValidationEditorComponent, UiWizardSyncService, ngUiSystemVersionLabel, uiBlackboxInterceptor, uiClamp, uiCoerceBoolean, uiFilterNullish, uiQueryParamsToPath, uiUniqueId };
|
|
61647
|
+
export { EDITOR_PRESETS, NG_UI_SYSTEM_VERSION, PRESET_ANAGRAFICA, PRESET_SURVEY, PRESET_TICKET, UI_BLACKBOX_DEFAULTS, UI_BREADCRUMB_LABEL_RESOLVER, UI_FORM_NESTED_FIELD_ERRORS_KEY, UI_HEADERS_PROVIDER, UI_HTTP_CONFIG, UI_IT_DATE_FORMATS, UI_LOGGER_DEFAULT_TTL_MS, UI_LOGGER_IDB_NAME, UI_LOGGER_IDB_STORE, UI_LOGGER_IDB_VERSION, UI_LOGGER_LEVELS, UI_LOGGER_MAX_ENTRIES, UI_TOAST_DEFAULTS, UiAccordionComponent, UiBaseLayoutComponent, UiBlackboxDebuggerComponent, UiBlackboxDebuggerService, UiBlackboxFingerprintService, UiBlackboxJsonViewerComponent, UiBlackboxService, UiBlackboxStorageService, UiBreadcrumbService, UiButtonAreaComponent, UiButtonComponent, UiConditionEditorComponent, UiConfirmDialogComponent, UiCrudTableComponent, UiCurrencyInputDirective, UiEditorFieldConfigPanelComponent, UiEditorFieldFactoryService, UiEditorFormValuesPanelComponent, UiEditorPersistenceService, UiEditorPreviewContainerComponent, UiEditorStateService, UiEditorToolbarComponent, UiFieldPaletteComponent, UiFileInputComponent, UiFormBuilderComponent, UiFormBuilderEditorComponent, UiFormBuilderEditorService, UiFormConditionService, UiFormErrorStateMatcher, UiFormErrorSummaryComponent, UiFormFieldErrorService, UiFormNumberFieldComponent, UiFormRadioFieldComponent, UiFormSelectFieldComponent, UiFormTextFieldComponent, UiFormTextareaFieldComponent, UiFormValidationService, UiFormWizardComponent, UiHttpMessageHandler, UiHttpService, UiItalianDateAdapter, UiLayoutBuilderComponent, UiLayoutService, UiLocationGeocodedComponent, UiLocationService, UiLoggerService, UiModalComponent, UiModalService, UiNominatimGeocodingService, UiOptionsEditorComponent, UiPageHeaderComponent, UiPaginatedTableComponent, UiSectionEditorComponent, UiSpecificaTerritorialeComponent, UiTableTerritorialeComponent, UiToastComponent, UiToastContainerComponent, UiToastService, UiTrackDirective, UiValidationEditorComponent, UiWizardSyncService, ngUiSystemVersionLabel, uiBlackboxInterceptor, uiClamp, uiCoerceBoolean, uiFilterNullish, uiQueryParamsToPath, uiUniqueId };
|
|
61604
61648
|
//# sourceMappingURL=gnggln-ng-ui-system.mjs.map
|