@sdcorejs/angular 20.0.11 → 20.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -23
- package/assets/STYLE-GUIDE.md +58 -72
- package/assets/scss/core/color.scss +56 -45
- package/assets/scss/core/form.scss +20 -8
- package/assets/scss/core/utilities/_base.scss +3 -3
- package/assets/scss/core/utilities/_border.scss +1 -1
- package/assets/scss/themes/default.scss +46 -32
- package/assets/scss/themes/material-theme.scss +8 -87
- package/components/badge/index.d.ts +2 -2
- package/components/button/index.d.ts +1 -1
- package/components/form-generic/index.d.ts +9 -1
- package/components/modal/index.d.ts +1 -1
- package/components/modal-resizable/index.d.ts +1 -1
- package/components/query-bar/index.d.ts +1 -0
- package/components/query-builder/index.d.ts +3 -3
- package/components/section/index.d.ts +1 -5
- package/components/side-drawer/index.d.ts +1 -1
- package/components/table/index.d.ts +0 -2
- package/fesm2022/sdcorejs-angular-components-anchor.mjs +2 -2
- package/fesm2022/sdcorejs-angular-components-anchor.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-autoid-inspector.mjs +8 -8
- package/fesm2022/sdcorejs-angular-components-autoid-inspector.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-badge.mjs +3 -3
- package/fesm2022/sdcorejs-angular-components-badge.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-button.mjs +4 -4
- package/fesm2022/sdcorejs-angular-components-button.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-document-builder.mjs +3 -3
- package/fesm2022/sdcorejs-angular-components-document-builder.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-editor.mjs +2 -2
- package/fesm2022/sdcorejs-angular-components-editor.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-form-generic.mjs +115 -38
- package/fesm2022/sdcorejs-angular-components-form-generic.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-import-excel.mjs +2 -2
- package/fesm2022/sdcorejs-angular-components-import-excel.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-inform.mjs +2 -2
- package/fesm2022/sdcorejs-angular-components-inform.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-mini-editor.mjs +2 -2
- package/fesm2022/sdcorejs-angular-components-mini-editor.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-modal-resizable.mjs +2 -2
- package/fesm2022/sdcorejs-angular-components-modal-resizable.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-modal.mjs +24 -5
- package/fesm2022/sdcorejs-angular-components-modal.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-org-chart.mjs +2 -2
- package/fesm2022/sdcorejs-angular-components-preview.mjs +4 -4
- package/fesm2022/sdcorejs-angular-components-preview.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-query-bar.mjs +5 -2
- package/fesm2022/sdcorejs-angular-components-query-bar.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-query-builder.mjs +4 -3
- package/fesm2022/sdcorejs-angular-components-query-builder.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-section.mjs +7 -11
- package/fesm2022/sdcorejs-angular-components-section.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-side-drawer.mjs +2 -2
- package/fesm2022/sdcorejs-angular-components-side-drawer.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-splitter.mjs +2 -2
- package/fesm2022/sdcorejs-angular-components-splitter.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-stepper.mjs +2 -2
- package/fesm2022/sdcorejs-angular-components-stepper.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-tab.mjs +2 -2
- package/fesm2022/sdcorejs-angular-components-tab.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-table.mjs +11 -11
- package/fesm2022/sdcorejs-angular-components-table.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-tree.mjs +2 -2
- package/fesm2022/sdcorejs-angular-components-tree.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-upload-file.mjs +4 -4
- package/fesm2022/sdcorejs-angular-components-upload-file.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-view.mjs +2 -2
- package/fesm2022/sdcorejs-angular-components-view.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-directives.mjs +1 -1
- package/fesm2022/sdcorejs-angular-directives.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-autocomplete.mjs +2 -2
- package/fesm2022/sdcorejs-angular-forms-autocomplete.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-checkbox.mjs +2 -2
- package/fesm2022/sdcorejs-angular-forms-checkbox.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-chip-calendar.mjs +2 -2
- package/fesm2022/sdcorejs-angular-forms-chip-calendar.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-date-range.mjs +2 -2
- package/fesm2022/sdcorejs-angular-forms-date-range.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-date.mjs +2 -2
- package/fesm2022/sdcorejs-angular-forms-date.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-datetime.mjs +468 -344
- package/fesm2022/sdcorejs-angular-forms-datetime.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-input-number.mjs +2 -2
- package/fesm2022/sdcorejs-angular-forms-input-number.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-input.mjs +2 -2
- package/fesm2022/sdcorejs-angular-forms-input.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-label.mjs +2 -2
- package/fesm2022/sdcorejs-angular-forms-label.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-radio.mjs +2 -2
- package/fesm2022/sdcorejs-angular-forms-radio.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-select.mjs +2 -2
- package/fesm2022/sdcorejs-angular-forms-select.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-switch.mjs +2 -2
- package/fesm2022/sdcorejs-angular-forms-switch.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-textarea.mjs +2 -2
- package/fesm2022/sdcorejs-angular-forms-textarea.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-modules-layout.mjs +11 -9
- package/fesm2022/sdcorejs-angular-modules-layout.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-services-confirm.mjs +84 -3
- package/fesm2022/sdcorejs-angular-services-confirm.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-services-loading.mjs +1 -1
- package/fesm2022/sdcorejs-angular-services-loading.mjs.map +1 -1
- package/forms/datetime/index.d.ts +4 -4
- package/package.json +1 -1
- package/services/confirm/index.d.ts +49 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, inject, Injectable, Pipe, SecurityContext, Input, ChangeDetectionStrategy, Component, ViewChild, EventEmitter, Output, ViewChildren, QueryList, ChangeDetectorRef, viewChild, input, effect, untracked, signal, computed } from '@angular/core';
|
|
2
|
+
import { InjectionToken, inject, Injectable, Pipe, SecurityContext, Input, ChangeDetectionStrategy, Component, ViewChild, EventEmitter, Output, ViewChildren, QueryList, ChangeDetectorRef, viewChild, input, effect, untracked, signal, computed, ElementRef } from '@angular/core';
|
|
3
3
|
import { Utilities, StringUtilities as StringUtilities$1 } from '@sdcorejs/utils/fns';
|
|
4
4
|
import { DateUtilities } from '@sdcorejs/angular/utilities';
|
|
5
5
|
import * as i3 from '@angular/cdk/drag-drop';
|
|
@@ -1473,7 +1473,7 @@ class ChipStringComponent {
|
|
|
1473
1473
|
this.#subscription.unsubscribe();
|
|
1474
1474
|
}
|
|
1475
1475
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: ChipStringComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
1476
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: ChipStringComponent, isStandalone: true, selector: "lib-chip-string", inputs: { setVariables: "setVariables", form: "form", _entity: ["entity", "_entity"], _component: ["component", "_component"], _disabled: ["disabled", "_disabled"], _required: ["required", "_required"], _viewed: ["viewed", "_viewed"] }, ngImport: i0, template: "@if (component && entity) {\n @if (viewed || component.properties?.viewed) {\n <div class=\"text-
|
|
1476
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: ChipStringComponent, isStandalone: true, selector: "lib-chip-string", inputs: { setVariables: "setVariables", form: "form", _entity: ["entity", "_entity"], _component: ["component", "_component"], _disabled: ["disabled", "_disabled"], _required: ["required", "_required"], _viewed: ["viewed", "_viewed"] }, ngImport: i0, template: "@if (component && entity) {\n @if (viewed || component.properties?.viewed) {\n <div class=\"text-secondary\">{{ component.label || component.key }}</div>\n <div class=\"T14M\">{{ entity[component.key!] | componentViewed: entity : component | async }}</div>\n } @else {\n <sd-chip\n [form]=\"form\"\n [label]=\"component.label || component.key\"\n [name]=\"component.key\"\n [autoId]=\"component.key\"\n [(model)]=\"entity[component.key!]\"\n [required]=\"required\"\n [disabled]=\"disabled\"></sd-chip>\n }\n}\n", styles: [":host{display:contents;width:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: SdChip, selector: "sd-chip", inputs: ["autoId", "name", "appearance", "floatLabel", "size", "form", "label", "placeholder", "removable", "hideInlineError", "required", "min", "max", "addable", "disabled", "viewed", "hyperlink", "model"], outputs: ["modelChange", "sdChange"] }, { kind: "pipe", type: i1$2.AsyncPipe, name: "async" }, { kind: "pipe", type:
|
|
1477
1477
|
// Pipe cho phần viewed
|
|
1478
1478
|
ComponentViewedPipe, name: "componentViewed" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1479
1479
|
}
|
|
@@ -1484,7 +1484,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
1484
1484
|
SdChip,
|
|
1485
1485
|
// Pipe cho phần viewed
|
|
1486
1486
|
ComponentViewedPipe,
|
|
1487
|
-
], template: "@if (component && entity) {\n @if (viewed || component.properties?.viewed) {\n <div class=\"text-
|
|
1487
|
+
], template: "@if (component && entity) {\n @if (viewed || component.properties?.viewed) {\n <div class=\"text-secondary\">{{ component.label || component.key }}</div>\n <div class=\"T14M\">{{ entity[component.key!] | componentViewed: entity : component | async }}</div>\n } @else {\n <sd-chip\n [form]=\"form\"\n [label]=\"component.label || component.key\"\n [name]=\"component.key\"\n [autoId]=\"component.key\"\n [(model)]=\"entity[component.key!]\"\n [required]=\"required\"\n [disabled]=\"disabled\"></sd-chip>\n }\n}\n", styles: [":host{display:contents;width:100%}\n"] }]
|
|
1488
1488
|
}], ctorParameters: () => [{ type: i0.ChangeDetectorRef }], propDecorators: { setVariables: [{
|
|
1489
1489
|
type: Input,
|
|
1490
1490
|
args: [{ required: true }]
|
|
@@ -1554,7 +1554,7 @@ class ChipCalendarComponent {
|
|
|
1554
1554
|
this.#subscription.unsubscribe();
|
|
1555
1555
|
}
|
|
1556
1556
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: ChipCalendarComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
1557
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: ChipCalendarComponent, isStandalone: true, selector: "lib-chip-calendar", inputs: { setVariables: "setVariables", form: "form", _entity: ["entity", "_entity"], _component: ["component", "_component"], _disabled: ["disabled", "_disabled"], _required: ["required", "_required"], _viewed: ["viewed", "_viewed"] }, ngImport: i0, template: "@if (component && entity) {\n @if (viewed || component.properties?.viewed) {\n <div class=\"text-
|
|
1557
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: ChipCalendarComponent, isStandalone: true, selector: "lib-chip-calendar", inputs: { setVariables: "setVariables", form: "form", _entity: ["entity", "_entity"], _component: ["component", "_component"], _disabled: ["disabled", "_disabled"], _required: ["required", "_required"], _viewed: ["viewed", "_viewed"] }, ngImport: i0, template: "@if (component && entity) {\n @if (viewed || component.properties?.viewed) {\n <div class=\"text-secondary\">{{ component.label || component.key }}</div>\n <div class=\"T14M\">{{ entity[component.key!] | componentViewed: entity : component | async }}</div>\n } @else {\n <sd-chip-calendar\n [form]=\"form\"\n [label]=\"component.label || component.key\"\n [name]=\"component.key\"\n [autoId]=\"component.key\"\n [(model)]=\"entity[component.key!]\"\n [required]=\"required\"\n [disabled]=\"disabled\"></sd-chip-calendar>\n }\n}\n", styles: [":host{display:contents;width:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: SdChipCalendar, selector: "sd-chip-calendar", inputs: ["autoId", "name", "appearance", "floatLabel", "size", "form", "label", "placeholder", "removable", "hideInlineError", "required", "min", "max", "disabled", "viewed", "hyperlink", "model"], outputs: ["modelChange", "sdChange"] }, { kind: "pipe", type: i1$2.AsyncPipe, name: "async" }, { kind: "pipe", type:
|
|
1558
1558
|
// Pipe cho phần viewed
|
|
1559
1559
|
ComponentViewedPipe, name: "componentViewed" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1560
1560
|
}
|
|
@@ -1565,7 +1565,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
1565
1565
|
SdChipCalendar,
|
|
1566
1566
|
// Pipe cho phần viewed
|
|
1567
1567
|
ComponentViewedPipe,
|
|
1568
|
-
], template: "@if (component && entity) {\n @if (viewed || component.properties?.viewed) {\n <div class=\"text-
|
|
1568
|
+
], template: "@if (component && entity) {\n @if (viewed || component.properties?.viewed) {\n <div class=\"text-secondary\">{{ component.label || component.key }}</div>\n <div class=\"T14M\">{{ entity[component.key!] | componentViewed: entity : component | async }}</div>\n } @else {\n <sd-chip-calendar\n [form]=\"form\"\n [label]=\"component.label || component.key\"\n [name]=\"component.key\"\n [autoId]=\"component.key\"\n [(model)]=\"entity[component.key!]\"\n [required]=\"required\"\n [disabled]=\"disabled\"></sd-chip-calendar>\n }\n}\n", styles: [":host{display:contents;width:100%}\n"] }]
|
|
1569
1569
|
}], ctorParameters: () => [{ type: i0.ChangeDetectorRef }], propDecorators: { setVariables: [{
|
|
1570
1570
|
type: Input,
|
|
1571
1571
|
args: [{ required: true }]
|
|
@@ -2541,7 +2541,7 @@ class TableComponent {
|
|
|
2541
2541
|
}
|
|
2542
2542
|
};
|
|
2543
2543
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: TableComponent, deps: [{ token: i0.ChangeDetectorRef }, { token: FormGenericService }], target: i0.ɵɵFactoryTarget.Component });
|
|
2544
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: TableComponent, isStandalone: true, selector: "lib-table", inputs: { setVariables: "setVariables", form: "form", _component: ["component", "_component"], _required: ["required", "_required"], _viewed: ["viewed", "_viewed"], _model: ["model", "_model"] }, outputs: { modelChange: "modelChange" }, viewQueries: [{ propertyName: "table", first: true, predicate: SdTable, descendants: true }, { propertyName: "sideDrawer", first: true, predicate: SdSideDrawer, descendants: true }, { propertyName: "uploadFiles", predicate: SdUploadFile, descendants: true }], ngImport: i0, template: "@if(component && tableOption) {\n <div style=\"width: 100%\">\n <sd-table [option]=\"tableOption\">\n @for(key of imageKeys; track key) {\n <ng-template [sdTableCellDef]=\"key\" let-item=\"item\">\n <sd-upload-file\n type=\"image\"\n [model]=\"item[key]\"\n [args]=\"getArgs(key)\"\n (modelChange)=\"onFileUploadChange(item, key, $event)\"\n align=\"left\"\n disabled\n previewWidth=\"50px\"\n previewHeight=\"50px\"></sd-upload-file>\n </ng-template>\n }\n @for(key of fileKeys; track key) {\n <ng-template [sdTableCellDef]=\"key\" let-item=\"item\">\n <sd-upload-file\n [model]=\"item[key]\"\n [args]=\"getArgs(key)\"\n (modelChange)=\"onFileUploadChange(item, key, $event)\"\n align=\"left\"\n disabled\n previewWidth=\"50px\"\n previewHeight=\"50px\"></sd-upload-file>\n </ng-template>\n }\n </sd-table>\n @if(!viewed && !component.properties?.viewed) {\n <sd-button\n prefixIcon=\"add\"\n color=\"primary\"\n (click)=\"onDetail()\"\n [title]=\"component.properties?.titleButtonCreate || ('core.component.form-builder.add-new' | translate)\"></sd-button>\n }\n @if(!viewed && !component.properties?.viewed) {\n <sd-side-drawer [title]=\"'core.component.form-builder.detail' | translate\" width=\"500px\">\n <div sdTitle>\n <span>{{ 'core.component.form-builder.create-update' | translate }}</span>\n </div>\n <div sdBody class=\"c-table-create-update\">\n @if(!!component.columns?.length) {\n <div class=\"row mx-0\">\n @for(column of component.columns;track column.key) {\n @let validate = formRenderColumn[column.key].validate;\n <div class=\"col-12 px-0 mt-16\">\n @if(column.type === 'string') {\n <sd-input\n [form]=\"form\"\n [label]=\"column.label\"\n [(model)]=\"row[column.key]\"\n [required]=\"validate?.required\"\n [pattern]=\"column.validate?.pattern\"\n [patternErrorMessage]=\"column.validate?.patternErrorMessage\"></sd-input>\n }@else if(column.type === 'number') {\n <sd-input-number\n [form]=\"form\"\n [label]=\"column.label\"\n [(model)]=\"row[column.key]\"\n [required]=\"validate?.required\"\n [min]=\"column.validate?.min\"></sd-input-number>\n }@else if(column.type === 'radio') {\n <sd-radio\n [form]=\"form\"\n [label]=\"column.label\"\n [name]=\"column.key\"\n [items]=\"column.values\"\n valueField=\"value\"\n displayField=\"display\"\n [required]=\"validate?.required\"></sd-radio>\n }@else if(column.type === 'values') {\n <sd-autocomplete\n [form]=\"form\"\n [label]=\"column.label\"\n [(model)]=\"row[column.key]\"\n [items]=\"columnValues[column.key]\"\n valueField=\"value\"\n displayField=\"display\"\n [required]=\"validate?.required\"></sd-autocomplete>\n }@else if(column.type === 'date') {\n <sd-date\n [form]=\"form\"\n [label]=\"column.label\"\n [(model)]=\"row[column.key]\"\n [required]=\"validate?.required\"></sd-date>\n }@else if(column.type === 'datetime') {\n <sd-datetime\n [form]=\"form\"\n [label]=\"column.label\"\n [(model)]=\"row[column.key]\"\n [required]=\"validate?.required\"></sd-datetime>\n }@else if(column.type === 'image') {\n <sd-upload-file\n [label]=\"column.label\"\n type=\"image\"\n [(model)]=\"row[column.key]\"\n [args]=\"column.args\"\n previewWidth=\"50px\"\n previewHeight=\"50px\"\n align=\"left\"></sd-upload-file>\n }@else if (column.type === 'file') {\n <sd-upload-file\n [label]=\"column.label\"\n type=\"file\"\n [(model)]=\"row[column.key]\"\n [args]=\"column.args\"\n previewWidth=\"50px\"\n previewHeight=\"50px\"\n align=\"left\"></sd-upload-file>\n }\n </div>\n }\n </div>\n }\n \n </div>\n <div
|
|
2544
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: TableComponent, isStandalone: true, selector: "lib-table", inputs: { setVariables: "setVariables", form: "form", _component: ["component", "_component"], _required: ["required", "_required"], _viewed: ["viewed", "_viewed"], _model: ["model", "_model"] }, outputs: { modelChange: "modelChange" }, viewQueries: [{ propertyName: "table", first: true, predicate: SdTable, descendants: true }, { propertyName: "sideDrawer", first: true, predicate: SdSideDrawer, descendants: true }, { propertyName: "uploadFiles", predicate: SdUploadFile, descendants: true }], ngImport: i0, template: "@if(component && tableOption) {\n <div style=\"width: 100%\">\n <sd-table [option]=\"tableOption\">\n @for(key of imageKeys; track key) {\n <ng-template [sdTableCellDef]=\"key\" let-item=\"item\">\n <sd-upload-file\n type=\"image\"\n [model]=\"item[key]\"\n [args]=\"getArgs(key)\"\n (modelChange)=\"onFileUploadChange(item, key, $event)\"\n align=\"left\"\n disabled\n previewWidth=\"50px\"\n previewHeight=\"50px\"></sd-upload-file>\n </ng-template>\n }\n @for(key of fileKeys; track key) {\n <ng-template [sdTableCellDef]=\"key\" let-item=\"item\">\n <sd-upload-file\n [model]=\"item[key]\"\n [args]=\"getArgs(key)\"\n (modelChange)=\"onFileUploadChange(item, key, $event)\"\n align=\"left\"\n disabled\n previewWidth=\"50px\"\n previewHeight=\"50px\"></sd-upload-file>\n </ng-template>\n }\n </sd-table>\n @if(!viewed && !component.properties?.viewed) {\n <sd-button\n prefixIcon=\"add\"\n color=\"primary\"\n (click)=\"onDetail()\"\n [title]=\"component.properties?.titleButtonCreate || ('core.component.form-builder.add-new' | translate)\"></sd-button>\n }\n @if(!viewed && !component.properties?.viewed) {\n <sd-side-drawer [title]=\"'core.component.form-builder.detail' | translate\" width=\"500px\">\n <div sdTitle>\n <span>{{ 'core.component.form-builder.create-update' | translate }}</span>\n </div>\n <div sdBody class=\"c-table-create-update\">\n @if(!!component.columns?.length) {\n <div class=\"row mx-0\">\n @for(column of component.columns;track column.key) {\n @let validate = formRenderColumn[column.key].validate;\n <div class=\"col-12 px-0 mt-16\">\n @if(column.type === 'string') {\n <sd-input\n [form]=\"form\"\n [label]=\"column.label\"\n [(model)]=\"row[column.key]\"\n [required]=\"validate?.required\"\n [pattern]=\"column.validate?.pattern\"\n [patternErrorMessage]=\"column.validate?.patternErrorMessage\"></sd-input>\n }@else if(column.type === 'number') {\n <sd-input-number\n [form]=\"form\"\n [label]=\"column.label\"\n [(model)]=\"row[column.key]\"\n [required]=\"validate?.required\"\n [min]=\"column.validate?.min\"></sd-input-number>\n }@else if(column.type === 'radio') {\n <sd-radio\n [form]=\"form\"\n [label]=\"column.label\"\n [name]=\"column.key\"\n [items]=\"column.values\"\n valueField=\"value\"\n displayField=\"display\"\n [required]=\"validate?.required\"></sd-radio>\n }@else if(column.type === 'values') {\n <sd-autocomplete\n [form]=\"form\"\n [label]=\"column.label\"\n [(model)]=\"row[column.key]\"\n [items]=\"columnValues[column.key]\"\n valueField=\"value\"\n displayField=\"display\"\n [required]=\"validate?.required\"></sd-autocomplete>\n }@else if(column.type === 'date') {\n <sd-date\n [form]=\"form\"\n [label]=\"column.label\"\n [(model)]=\"row[column.key]\"\n [required]=\"validate?.required\"></sd-date>\n }@else if(column.type === 'datetime') {\n <sd-datetime\n [form]=\"form\"\n [label]=\"column.label\"\n [(model)]=\"row[column.key]\"\n [required]=\"validate?.required\"></sd-datetime>\n }@else if(column.type === 'image') {\n <sd-upload-file\n [label]=\"column.label\"\n type=\"image\"\n [(model)]=\"row[column.key]\"\n [args]=\"column.args\"\n previewWidth=\"50px\"\n previewHeight=\"50px\"\n align=\"left\"></sd-upload-file>\n }@else if (column.type === 'file') {\n <sd-upload-file\n [label]=\"column.label\"\n type=\"file\"\n [(model)]=\"row[column.key]\"\n [args]=\"column.args\"\n previewWidth=\"50px\"\n previewHeight=\"50px\"\n align=\"left\"></sd-upload-file>\n }\n </div>\n }\n </div>\n }\n \n </div>\n <div sdFooterRight class=\"text-right pr-16\">\n <sd-button [title]=\"'core.component.form-builder.save' | translate\" prefixIcon=\"save\" color=\"primary\" type=\"fill\" (click)=\"onAccept()\"></sd-button>\n </div>\n </sd-side-drawer>\n }\n </div>\n}\n\n", styles: [":host{display:contents}.c-table-create-update{background:#fff;margin:-16px;padding:16px;height:calc(100vh - 97px)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: SdInput, selector: "sd-input", inputs: ["autoId", "name", "appearance", "floatLabel", "size", "form", "label", "helperText", "placeholder", "type", "hideInlineError", "blurOnEnter", "required", "readonly", "disabled", "viewed", "minlength", "maxlength", "pattern", "patternErrorMessage", "validator", "inlineError", "hyperlink", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "cleared", "sdFocusForceBlur"] }, { kind: "component", type: SdInputNumber$1, selector: "sd-input-number", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "hideInlineError", "blurOnEnter", "required", "readonly", "disabled", "viewed", "type", "precision", "format", "min", "max", "validator", "inlineError", "hyperlink", "appearance", "floatLabel", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "cleared", "sdFocusForceBlur"] }, { kind: "component", type: SdAutocomplete, selector: "sd-autocomplete", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "valueField", "displayField", "disabledField", "limit", "cacheChecksum", "hyperlink", "items", "hideInlineError", "addable", "required", "disabled", "viewed", "clearable", "validator", "inlineError", "appearance", "model"], outputs: ["modelChange", "sdChange", "sdSelection", "sdAdd"] }, { kind: "component", type: SdUploadFile, selector: "sd-upload-file", inputs: ["autoId", "args", "label", "key", "description", "previewWidth", "previewHeight", "align", "upload", "details", "download", "imageValidator", "maxSize", "maxWidth", "maxHeight", "scaleToPixel", "form", "name", "required", "type", "helperText", "max", "maxOfImage", "extensions", "disabled", "model"], outputs: ["loaded", "filesChanged", "modelChange"] }, { kind: "component", type: SdTable, selector: "sd-table", inputs: ["autoId", "option"] }, { kind: "directive", type: SdTableCellDefDirective, selector: "[sdTableCellDef]", inputs: ["sdTableCellDef"] }, { kind: "component", type: SdSideDrawer, selector: "sd-side-drawer", inputs: ["title", "width", "hideClose", "disableBackdropClose", "drawerClass", "autoId"], outputs: ["sdClosed"] }, { kind: "component", type: SdButton, selector: "sd-button", inputs: ["autoId", "type", "color", "size", "fontSet", "title", "width", "tooltip", "prefixIcon", "suffixIcon", "disabled", "loading", "block", "htmlType"], outputs: ["click"] }, { kind: "component", type: SdRadio, selector: "sd-radio", inputs: ["autoId", "name", "form", "label", "placeholder", "display", "items", "valueField", "displayField", "required", "inlineError", "disabled", "viewed", "color", "hyperlink", "model"], outputs: ["modelChange", "sdChange", "sdSelection"] }, { kind: "component", type: SdDate, selector: "sd-date", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "hideInlineError", "required", "disabled", "viewed", "clearable", "inlineError", "hyperlink", "appearance", "floatLabel", "min", "minDate", "max", "maxDate", "model"], outputs: ["modelChange", "sdChange", "sdFocus"] }, { kind: "component", type: SdDatetime, selector: "sd-datetime", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "hideInlineError", "required", "disabled", "viewed", "showSeconds", "clearable", "inlineError", "hyperlink", "appearance", "floatLabel", "min", "minDate", "max", "maxDate", "model"], outputs: ["modelChange", "sdChange", "sdFocus"] }, { kind: "pipe", type:
|
|
2545
2545
|
// Pipe cho phần viewed
|
|
2546
2546
|
TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2547
2547
|
}
|
|
@@ -2563,7 +2563,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
2563
2563
|
SdDatetime,
|
|
2564
2564
|
// Pipe cho phần viewed
|
|
2565
2565
|
TranslatePipe,
|
|
2566
|
-
], template: "@if(component && tableOption) {\n <div style=\"width: 100%\">\n <sd-table [option]=\"tableOption\">\n @for(key of imageKeys; track key) {\n <ng-template [sdTableCellDef]=\"key\" let-item=\"item\">\n <sd-upload-file\n type=\"image\"\n [model]=\"item[key]\"\n [args]=\"getArgs(key)\"\n (modelChange)=\"onFileUploadChange(item, key, $event)\"\n align=\"left\"\n disabled\n previewWidth=\"50px\"\n previewHeight=\"50px\"></sd-upload-file>\n </ng-template>\n }\n @for(key of fileKeys; track key) {\n <ng-template [sdTableCellDef]=\"key\" let-item=\"item\">\n <sd-upload-file\n [model]=\"item[key]\"\n [args]=\"getArgs(key)\"\n (modelChange)=\"onFileUploadChange(item, key, $event)\"\n align=\"left\"\n disabled\n previewWidth=\"50px\"\n previewHeight=\"50px\"></sd-upload-file>\n </ng-template>\n }\n </sd-table>\n @if(!viewed && !component.properties?.viewed) {\n <sd-button\n prefixIcon=\"add\"\n color=\"primary\"\n (click)=\"onDetail()\"\n [title]=\"component.properties?.titleButtonCreate || ('core.component.form-builder.add-new' | translate)\"></sd-button>\n }\n @if(!viewed && !component.properties?.viewed) {\n <sd-side-drawer [title]=\"'core.component.form-builder.detail' | translate\" width=\"500px\">\n <div sdTitle>\n <span>{{ 'core.component.form-builder.create-update' | translate }}</span>\n </div>\n <div sdBody class=\"c-table-create-update\">\n @if(!!component.columns?.length) {\n <div class=\"row mx-0\">\n @for(column of component.columns;track column.key) {\n @let validate = formRenderColumn[column.key].validate;\n <div class=\"col-12 px-0 mt-16\">\n @if(column.type === 'string') {\n <sd-input\n [form]=\"form\"\n [label]=\"column.label\"\n [(model)]=\"row[column.key]\"\n [required]=\"validate?.required\"\n [pattern]=\"column.validate?.pattern\"\n [patternErrorMessage]=\"column.validate?.patternErrorMessage\"></sd-input>\n }@else if(column.type === 'number') {\n <sd-input-number\n [form]=\"form\"\n [label]=\"column.label\"\n [(model)]=\"row[column.key]\"\n [required]=\"validate?.required\"\n [min]=\"column.validate?.min\"></sd-input-number>\n }@else if(column.type === 'radio') {\n <sd-radio\n [form]=\"form\"\n [label]=\"column.label\"\n [name]=\"column.key\"\n [items]=\"column.values\"\n valueField=\"value\"\n displayField=\"display\"\n [required]=\"validate?.required\"></sd-radio>\n }@else if(column.type === 'values') {\n <sd-autocomplete\n [form]=\"form\"\n [label]=\"column.label\"\n [(model)]=\"row[column.key]\"\n [items]=\"columnValues[column.key]\"\n valueField=\"value\"\n displayField=\"display\"\n [required]=\"validate?.required\"></sd-autocomplete>\n }@else if(column.type === 'date') {\n <sd-date\n [form]=\"form\"\n [label]=\"column.label\"\n [(model)]=\"row[column.key]\"\n [required]=\"validate?.required\"></sd-date>\n }@else if(column.type === 'datetime') {\n <sd-datetime\n [form]=\"form\"\n [label]=\"column.label\"\n [(model)]=\"row[column.key]\"\n [required]=\"validate?.required\"></sd-datetime>\n }@else if(column.type === 'image') {\n <sd-upload-file\n [label]=\"column.label\"\n type=\"image\"\n [(model)]=\"row[column.key]\"\n [args]=\"column.args\"\n previewWidth=\"50px\"\n previewHeight=\"50px\"\n align=\"left\"></sd-upload-file>\n }@else if (column.type === 'file') {\n <sd-upload-file\n [label]=\"column.label\"\n type=\"file\"\n [(model)]=\"row[column.key]\"\n [args]=\"column.args\"\n previewWidth=\"50px\"\n previewHeight=\"50px\"\n align=\"left\"></sd-upload-file>\n }\n </div>\n }\n </div>\n }\n \n </div>\n <div
|
|
2566
|
+
], template: "@if(component && tableOption) {\n <div style=\"width: 100%\">\n <sd-table [option]=\"tableOption\">\n @for(key of imageKeys; track key) {\n <ng-template [sdTableCellDef]=\"key\" let-item=\"item\">\n <sd-upload-file\n type=\"image\"\n [model]=\"item[key]\"\n [args]=\"getArgs(key)\"\n (modelChange)=\"onFileUploadChange(item, key, $event)\"\n align=\"left\"\n disabled\n previewWidth=\"50px\"\n previewHeight=\"50px\"></sd-upload-file>\n </ng-template>\n }\n @for(key of fileKeys; track key) {\n <ng-template [sdTableCellDef]=\"key\" let-item=\"item\">\n <sd-upload-file\n [model]=\"item[key]\"\n [args]=\"getArgs(key)\"\n (modelChange)=\"onFileUploadChange(item, key, $event)\"\n align=\"left\"\n disabled\n previewWidth=\"50px\"\n previewHeight=\"50px\"></sd-upload-file>\n </ng-template>\n }\n </sd-table>\n @if(!viewed && !component.properties?.viewed) {\n <sd-button\n prefixIcon=\"add\"\n color=\"primary\"\n (click)=\"onDetail()\"\n [title]=\"component.properties?.titleButtonCreate || ('core.component.form-builder.add-new' | translate)\"></sd-button>\n }\n @if(!viewed && !component.properties?.viewed) {\n <sd-side-drawer [title]=\"'core.component.form-builder.detail' | translate\" width=\"500px\">\n <div sdTitle>\n <span>{{ 'core.component.form-builder.create-update' | translate }}</span>\n </div>\n <div sdBody class=\"c-table-create-update\">\n @if(!!component.columns?.length) {\n <div class=\"row mx-0\">\n @for(column of component.columns;track column.key) {\n @let validate = formRenderColumn[column.key].validate;\n <div class=\"col-12 px-0 mt-16\">\n @if(column.type === 'string') {\n <sd-input\n [form]=\"form\"\n [label]=\"column.label\"\n [(model)]=\"row[column.key]\"\n [required]=\"validate?.required\"\n [pattern]=\"column.validate?.pattern\"\n [patternErrorMessage]=\"column.validate?.patternErrorMessage\"></sd-input>\n }@else if(column.type === 'number') {\n <sd-input-number\n [form]=\"form\"\n [label]=\"column.label\"\n [(model)]=\"row[column.key]\"\n [required]=\"validate?.required\"\n [min]=\"column.validate?.min\"></sd-input-number>\n }@else if(column.type === 'radio') {\n <sd-radio\n [form]=\"form\"\n [label]=\"column.label\"\n [name]=\"column.key\"\n [items]=\"column.values\"\n valueField=\"value\"\n displayField=\"display\"\n [required]=\"validate?.required\"></sd-radio>\n }@else if(column.type === 'values') {\n <sd-autocomplete\n [form]=\"form\"\n [label]=\"column.label\"\n [(model)]=\"row[column.key]\"\n [items]=\"columnValues[column.key]\"\n valueField=\"value\"\n displayField=\"display\"\n [required]=\"validate?.required\"></sd-autocomplete>\n }@else if(column.type === 'date') {\n <sd-date\n [form]=\"form\"\n [label]=\"column.label\"\n [(model)]=\"row[column.key]\"\n [required]=\"validate?.required\"></sd-date>\n }@else if(column.type === 'datetime') {\n <sd-datetime\n [form]=\"form\"\n [label]=\"column.label\"\n [(model)]=\"row[column.key]\"\n [required]=\"validate?.required\"></sd-datetime>\n }@else if(column.type === 'image') {\n <sd-upload-file\n [label]=\"column.label\"\n type=\"image\"\n [(model)]=\"row[column.key]\"\n [args]=\"column.args\"\n previewWidth=\"50px\"\n previewHeight=\"50px\"\n align=\"left\"></sd-upload-file>\n }@else if (column.type === 'file') {\n <sd-upload-file\n [label]=\"column.label\"\n type=\"file\"\n [(model)]=\"row[column.key]\"\n [args]=\"column.args\"\n previewWidth=\"50px\"\n previewHeight=\"50px\"\n align=\"left\"></sd-upload-file>\n }\n </div>\n }\n </div>\n }\n \n </div>\n <div sdFooterRight class=\"text-right pr-16\">\n <sd-button [title]=\"'core.component.form-builder.save' | translate\" prefixIcon=\"save\" color=\"primary\" type=\"fill\" (click)=\"onAccept()\"></sd-button>\n </div>\n </sd-side-drawer>\n }\n </div>\n}\n\n", styles: [":host{display:contents}.c-table-create-update{background:#fff;margin:-16px;padding:16px;height:calc(100vh - 97px)}\n"] }]
|
|
2567
2567
|
}], ctorParameters: () => [{ type: i0.ChangeDetectorRef }, { type: FormGenericService }], propDecorators: { uploadFiles: [{
|
|
2568
2568
|
type: ViewChildren,
|
|
2569
2569
|
args: [SdUploadFile]
|
|
@@ -3070,7 +3070,7 @@ class SdFormRender {
|
|
|
3070
3070
|
});
|
|
3071
3071
|
}
|
|
3072
3072
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdFormRender, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3073
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdFormRender, isStandalone: true, selector: "sd-form-render", inputs: { form: "form", _configuration: ["configuration", "_configuration"], _default: ["defaultEntity", "_default"], _entity: ["entity", "_entity"], _properties: ["properties", "_properties"], _viewed: ["viewed", "_viewed"] }, viewQueries: [{ propertyName: "formRenderItems", predicate: LibItemComponent, descendants: true }], ngImport: i0, template: "@if (form && loadCompleted && hashedValues) {\n <div class=\"row mx-0\">\n @for (component of configuration.components; track component.id) {\n @let expression = hashedValues | whenExpression: component : formValue;\n @if (!expression.hidden) {\n @if (component.type === 'group') {\n @if (!!component.components.length) {\n <div class=\"col-12 mt-16\">\n <sd-section [title]=\"component.label\" [icon]=\"component.properties.icon\">\n <div class=\"row mx-0\">\n @for (childComponent of component.components; track childComponent.id) {\n @let childExpression = hashedValues | whenExpression: childComponent : formValue;\n @if (!childExpression.hidden) {\n <lib-item\n [setVariables]=\"setVariables\"\n [form]=\"form\"\n [entity]=\"entity\"\n [component]=\"childComponent\"\n [required]=\"childExpression.required\"\n [disabled]=\"childExpression.disabled\"\n [viewed]=\"viewed\"></lib-item>\n }\n }\n </div>\n </sd-section>\n </div>\n }\n } @else {\n <lib-item\n [setVariables]=\"setVariables\"\n [form]=\"form\"\n [entity]=\"entity\"\n [component]=\"component\"\n [required]=\"expression.required\"\n [disabled]=\"expression.disabled\"\n [viewed]=\"viewed\"></lib-item>\n }\n }\n }\n <variable [variables]=\"configuration.variables\" [setVariables]=\"setVariables\" [form]=\"form\" [entity]=\"entity\" ></variable>\n </div>\n}\n", styles: [":host{display:contents}\n"], dependencies: [{ kind: "component", type: SdSection, selector: "sd-section", inputs: ["title", "subTitle", "icon", "iconColor", "collapsed", "collapsible", "
|
|
3073
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdFormRender, isStandalone: true, selector: "sd-form-render", inputs: { form: "form", _configuration: ["configuration", "_configuration"], _default: ["defaultEntity", "_default"], _entity: ["entity", "_entity"], _properties: ["properties", "_properties"], _viewed: ["viewed", "_viewed"] }, viewQueries: [{ propertyName: "formRenderItems", predicate: LibItemComponent, descendants: true }], ngImport: i0, template: "@if (form && loadCompleted && hashedValues) {\n <div class=\"row mx-0\">\n @for (component of configuration.components; track component.id) {\n @let expression = hashedValues | whenExpression: component : formValue;\n @if (!expression.hidden) {\n @if (component.type === 'group') {\n @if (!!component.components.length) {\n <div class=\"col-12 mt-16\">\n <sd-section [title]=\"component.label\" [icon]=\"component.properties.icon\">\n <div class=\"row mx-0\">\n @for (childComponent of component.components; track childComponent.id) {\n @let childExpression = hashedValues | whenExpression: childComponent : formValue;\n @if (!childExpression.hidden) {\n <lib-item\n [setVariables]=\"setVariables\"\n [form]=\"form\"\n [entity]=\"entity\"\n [component]=\"childComponent\"\n [required]=\"childExpression.required\"\n [disabled]=\"childExpression.disabled\"\n [viewed]=\"viewed\"></lib-item>\n }\n }\n </div>\n </sd-section>\n </div>\n }\n } @else {\n <lib-item\n [setVariables]=\"setVariables\"\n [form]=\"form\"\n [entity]=\"entity\"\n [component]=\"component\"\n [required]=\"expression.required\"\n [disabled]=\"expression.disabled\"\n [viewed]=\"viewed\"></lib-item>\n }\n }\n }\n <variable [variables]=\"configuration.variables\" [setVariables]=\"setVariables\" [form]=\"form\" [entity]=\"entity\" ></variable>\n </div>\n}\n", styles: [":host{display:contents}\n"], dependencies: [{ kind: "component", type: SdSection, selector: "sd-section", inputs: ["title", "subTitle", "icon", "iconColor", "collapsed", "collapsible", "hideHeader"], outputs: ["collapsedChange"] }, { kind: "component", type: LibItemComponent, selector: "lib-item", inputs: ["setVariables", "form", "entity", "component", "disabled", "required", "viewed", "validator"] }, { kind: "component", type: VariableComponent, selector: "variable", inputs: ["variables", "setVariables", "form", "entity"] }, { kind: "pipe", type: WhenExpressionPipe, name: "whenExpression" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3074
3074
|
}
|
|
3075
3075
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdFormRender, decorators: [{
|
|
3076
3076
|
type: Component,
|
|
@@ -3329,11 +3329,11 @@ class AttributeExpression {
|
|
|
3329
3329
|
return attributes;
|
|
3330
3330
|
};
|
|
3331
3331
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: AttributeExpression, deps: [{ token: i0.ChangeDetectorRef }, { token: FormGenericService }], target: i0.ɵɵFactoryTarget.Component });
|
|
3332
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: AttributeExpression, isStandalone: true, selector: "attribute-expression", inputs: { components: "components", variables: "variables", label: "label", _model: ["model", "_model"] }, outputs: { modelChange: "modelChange", sdChange: "sdChange" }, viewQueries: [{ propertyName: "modal", first: true, predicate: SdModal, descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"fb-field\">\n @if (label) {\n <div class=\"fb-field__label\">{{ label }}</div>\n }\n @let exprText = model | expressionQuery;\n <div class=\"fb-expr\" (click)=\"onEdit()\" role=\"button\" tabindex=\"0\" (keydown.enter)=\"onEdit()\" (keydown.space)=\"onEdit()\">\n <span class=\"fb-expr__badge\">QB</span>\n @if (exprText) {\n <span class=\"fb-expr__code mono\">{{ exprText }}</span>\n } @else {\n <span class=\"empty\">{{ 'core.component.form-builder.set-expression' | translate }}</span>\n }\n <button type=\"button\" class=\"fb-icon-btn\" (click)=\"onEdit(); $event.stopPropagation()\" aria-label=\"edit expression\">\n <span class=\"msi\">account_tree</span>\n </button>\n </div>\n</div>\n\n<sd-modal [title]=\"'core.component.form-builder.set-expression' | translate\" width=\"920px\">\n <div class=\"fb-expression-query\">\n <sd-query-builder [fields]=\"queryFields\" [(value)]=\"draftFilter\"></sd-query-builder>\n </div>\n <
|
|
3332
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: AttributeExpression, isStandalone: true, selector: "attribute-expression", inputs: { components: "components", variables: "variables", label: "label", _model: ["model", "_model"] }, outputs: { modelChange: "modelChange", sdChange: "sdChange" }, viewQueries: [{ propertyName: "modal", first: true, predicate: SdModal, descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"fb-field\">\n @if (label) {\n <div class=\"fb-field__label\">{{ label }}</div>\n }\n @let exprText = model | expressionQuery;\n <div class=\"fb-expr\" (click)=\"onEdit()\" role=\"button\" tabindex=\"0\" (keydown.enter)=\"onEdit()\" (keydown.space)=\"onEdit()\">\n <span class=\"fb-expr__badge\">QB</span>\n @if (exprText) {\n <span class=\"fb-expr__code mono\">{{ exprText }}</span>\n } @else {\n <span class=\"empty\">{{ 'core.component.form-builder.set-expression' | translate }}</span>\n }\n <button type=\"button\" class=\"fb-icon-btn\" (click)=\"onEdit(); $event.stopPropagation()\" aria-label=\"edit expression\">\n <span class=\"msi\">account_tree</span>\n </button>\n </div>\n</div>\n\n<sd-modal [title]=\"'core.component.form-builder.set-expression' | translate\" width=\"920px\" modalClass=\"sd-form-builder-dialog\">\n <div class=\"fb-expression-query\">\n <sd-query-builder [fields]=\"queryFields\" [(value)]=\"draftFilter\"></sd-query-builder>\n </div>\n <sd-button\n sdFooterRight\n prefixIcon=\"check\"\n [title]=\"'core.component.form-builder.confirm' | translate\"\n (click)=\"onAccept()\"\n color=\"primary\"\n type=\"fill\"></sd-button>\n</sd-modal>\n", styles: [":host{display:block;padding:8px 0}.fb-field{display:flex;flex-direction:column;gap:4px}.fb-field__label{color:var(--md-sys-color-on-surface-variant);font-size:11px;font-weight:500}.fb-expr{display:flex;align-items:center;gap:6px;min-height:32px;border:1px solid var(--md-sys-color-outline-variant);border-radius:var(--r-xs, 4px);background:var(--md-sys-color-surface-container-lowest);color:var(--md-sys-color-on-surface-variant);cursor:pointer;overflow:hidden}.fb-expr:focus-visible{outline:2px solid color-mix(in srgb,var(--md-sys-color-primary) 72%,transparent);outline-offset:2px}.fb-expr__badge{align-self:stretch;display:inline-flex;align-items:center;justify-content:center;min-width:34px;padding:0 6px;background:var(--md-sys-color-primary-container);color:var(--md-sys-color-primary);font-family:Roboto Mono,monospace;font-size:10px;font-weight:600}.fb-expr__code,.empty{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-expr__code{color:var(--md-sys-color-on-surface);font-family:Roboto Mono,monospace;font-size:11px}.empty{font-size:12px}.fb-icon-btn{width:28px;height:28px;border:0;border-radius:999px;display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;margin-right:2px;color:var(--md-sys-color-primary);background:transparent;cursor:pointer}.msi{font-family:Material Icons Outlined;font-size:18px;font-weight:400;line-height:1;letter-spacing:normal;text-transform:none;white-space:nowrap;font-feature-settings:\"liga\";-webkit-font-feature-settings:\"liga\";-webkit-font-smoothing:antialiased}.fb-expression-query{width:100%;min-height:112px;max-height:min(52vh,560px);overflow:auto}\n"], dependencies: [{ kind: "component", type: SdButton, selector: "sd-button", inputs: ["autoId", "type", "color", "size", "fontSet", "title", "width", "tooltip", "prefixIcon", "suffixIcon", "disabled", "loading", "block", "htmlType"], outputs: ["click"] }, { kind: "component", type: SdModal, selector: "sd-modal", inputs: ["autoId", "title", "color", "width", "height", "view", "modalClass", "lazyLoadContent", "hideClose", "disableBackdropClose"], outputs: ["sdClosed"] }, { kind: "component", type: SdQueryBuilder, selector: "sd-query-builder", inputs: ["option", "fields", "mode", "comparisonMode", "disabled", "autoId", "value", "filters", "rootLogic"], outputs: ["valueChange", "filtersChange", "rootLogicChange"] }, { kind: "pipe", type: ExpressionQueryPipe, name: "expressionQuery" }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3333
3333
|
}
|
|
3334
3334
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: AttributeExpression, decorators: [{
|
|
3335
3335
|
type: Component,
|
|
3336
|
-
args: [{ selector: 'attribute-expression', changeDetection: ChangeDetectionStrategy.OnPush, imports: [SdButton, SdModal, SdQueryBuilder, ExpressionQueryPipe, TranslatePipe], template: "<div class=\"fb-field\">\n @if (label) {\n <div class=\"fb-field__label\">{{ label }}</div>\n }\n @let exprText = model | expressionQuery;\n <div class=\"fb-expr\" (click)=\"onEdit()\" role=\"button\" tabindex=\"0\" (keydown.enter)=\"onEdit()\" (keydown.space)=\"onEdit()\">\n <span class=\"fb-expr__badge\">QB</span>\n @if (exprText) {\n <span class=\"fb-expr__code mono\">{{ exprText }}</span>\n } @else {\n <span class=\"empty\">{{ 'core.component.form-builder.set-expression' | translate }}</span>\n }\n <button type=\"button\" class=\"fb-icon-btn\" (click)=\"onEdit(); $event.stopPropagation()\" aria-label=\"edit expression\">\n <span class=\"msi\">account_tree</span>\n </button>\n </div>\n</div>\n\n<sd-modal [title]=\"'core.component.form-builder.set-expression' | translate\" width=\"920px\">\n <div class=\"fb-expression-query\">\n <sd-query-builder [fields]=\"queryFields\" [(value)]=\"draftFilter\"></sd-query-builder>\n </div>\n <
|
|
3336
|
+
args: [{ selector: 'attribute-expression', changeDetection: ChangeDetectionStrategy.OnPush, imports: [SdButton, SdModal, SdQueryBuilder, ExpressionQueryPipe, TranslatePipe], template: "<div class=\"fb-field\">\n @if (label) {\n <div class=\"fb-field__label\">{{ label }}</div>\n }\n @let exprText = model | expressionQuery;\n <div class=\"fb-expr\" (click)=\"onEdit()\" role=\"button\" tabindex=\"0\" (keydown.enter)=\"onEdit()\" (keydown.space)=\"onEdit()\">\n <span class=\"fb-expr__badge\">QB</span>\n @if (exprText) {\n <span class=\"fb-expr__code mono\">{{ exprText }}</span>\n } @else {\n <span class=\"empty\">{{ 'core.component.form-builder.set-expression' | translate }}</span>\n }\n <button type=\"button\" class=\"fb-icon-btn\" (click)=\"onEdit(); $event.stopPropagation()\" aria-label=\"edit expression\">\n <span class=\"msi\">account_tree</span>\n </button>\n </div>\n</div>\n\n<sd-modal [title]=\"'core.component.form-builder.set-expression' | translate\" width=\"920px\" modalClass=\"sd-form-builder-dialog\">\n <div class=\"fb-expression-query\">\n <sd-query-builder [fields]=\"queryFields\" [(value)]=\"draftFilter\"></sd-query-builder>\n </div>\n <sd-button\n sdFooterRight\n prefixIcon=\"check\"\n [title]=\"'core.component.form-builder.confirm' | translate\"\n (click)=\"onAccept()\"\n color=\"primary\"\n type=\"fill\"></sd-button>\n</sd-modal>\n", styles: [":host{display:block;padding:8px 0}.fb-field{display:flex;flex-direction:column;gap:4px}.fb-field__label{color:var(--md-sys-color-on-surface-variant);font-size:11px;font-weight:500}.fb-expr{display:flex;align-items:center;gap:6px;min-height:32px;border:1px solid var(--md-sys-color-outline-variant);border-radius:var(--r-xs, 4px);background:var(--md-sys-color-surface-container-lowest);color:var(--md-sys-color-on-surface-variant);cursor:pointer;overflow:hidden}.fb-expr:focus-visible{outline:2px solid color-mix(in srgb,var(--md-sys-color-primary) 72%,transparent);outline-offset:2px}.fb-expr__badge{align-self:stretch;display:inline-flex;align-items:center;justify-content:center;min-width:34px;padding:0 6px;background:var(--md-sys-color-primary-container);color:var(--md-sys-color-primary);font-family:Roboto Mono,monospace;font-size:10px;font-weight:600}.fb-expr__code,.empty{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-expr__code{color:var(--md-sys-color-on-surface);font-family:Roboto Mono,monospace;font-size:11px}.empty{font-size:12px}.fb-icon-btn{width:28px;height:28px;border:0;border-radius:999px;display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;margin-right:2px;color:var(--md-sys-color-primary);background:transparent;cursor:pointer}.msi{font-family:Material Icons Outlined;font-size:18px;font-weight:400;line-height:1;letter-spacing:normal;text-transform:none;white-space:nowrap;font-feature-settings:\"liga\";-webkit-font-feature-settings:\"liga\";-webkit-font-smoothing:antialiased}.fb-expression-query{width:100%;min-height:112px;max-height:min(52vh,560px);overflow:auto}\n"] }]
|
|
3337
3337
|
}], ctorParameters: () => [{ type: i0.ChangeDetectorRef }, { type: FormGenericService }], propDecorators: { modal: [{ type: i0.ViewChild, args: [i0.forwardRef(() => SdModal), { isSignal: true }] }], components: [{
|
|
3338
3338
|
type: Input,
|
|
3339
3339
|
args: [{ required: true }]
|
|
@@ -4336,11 +4336,11 @@ let BuildQueries$1 = class BuildQueries {
|
|
|
4336
4336
|
this.ref.markForCheck();
|
|
4337
4337
|
};
|
|
4338
4338
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: BuildQueries, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
4339
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: BuildQueries, isStandalone: true, selector: "build-queries", inputs: { components: "components", variables: "variables", label: "label", selections: "selections", _valuesKey: ["valuesKey", "_valuesKey"], _model: ["model", "_model"] }, outputs: { modelChange: "modelChange" }, viewQueries: [{ propertyName: "modal", first: true, predicate: SdModal, descendants: true }], ngImport: i0, template: "@if (!!selection?.queries?.items?.length) {\n <div class=\"d-flex align-items-center py-4\">\n @if (label) {\n <div class=\"T14R\" style=\"flex: 1\">{{ label }}</div>\n }\n <div style=\"width: 200px\" class=\"d-flex align-items-center\">\n <a class=\"T12R\" href=\"javascript:;\" (click)=\"edit()\">{{ queryString }}</a>\n </div>\n </div>\n <sd-modal [title]=\"'core.component.form-builder.set-query-value' | translate\" width=\"400px\">\n <div class=\"c-builder\">\n @for (property of leftProperties; track property.value) {\n <div class=\"d-flex align-items-center py-8\" style=\"gap: 8px\">\n <div style=\"width: 150px\">\n <div class=\"text-secondary\">{{ property.value }}</div>\n <div class=\"T16M\">{{ property.display }}</div>\n </div>\n <sd-autocomplete\n [label]=\"'core.component.form-builder.value-source-field' | translate\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"model![property.value]\"\n [items]=\"rightProperties\"\n valueField=\"value\"\n displayField=\"display\"\n hideInlineError></sd-autocomplete>\n </div>\n }\n </div>\n <ng-container
|
|
4339
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: BuildQueries, isStandalone: true, selector: "build-queries", inputs: { components: "components", variables: "variables", label: "label", selections: "selections", _valuesKey: ["valuesKey", "_valuesKey"], _model: ["model", "_model"] }, outputs: { modelChange: "modelChange" }, viewQueries: [{ propertyName: "modal", first: true, predicate: SdModal, descendants: true }], ngImport: i0, template: "@if (!!selection?.queries?.items?.length) {\n <div class=\"d-flex align-items-center py-4\">\n @if (label) {\n <div class=\"T14R\" style=\"flex: 1\">{{ label }}</div>\n }\n <div style=\"width: 200px\" class=\"d-flex align-items-center\">\n <a class=\"T12R\" href=\"javascript:;\" (click)=\"edit()\">{{ queryString }}</a>\n </div>\n </div>\n <sd-modal [title]=\"'core.component.form-builder.set-query-value' | translate\" width=\"400px\" modalClass=\"sd-form-builder-dialog\">\n <div class=\"c-builder\">\n @for (property of leftProperties; track property.value) {\n <div class=\"d-flex align-items-center py-8\" style=\"gap: 8px\">\n <div style=\"width: 150px\">\n <div class=\"text-secondary\">{{ property.value }}</div>\n <div class=\"T16M\">{{ property.display }}</div>\n </div>\n <sd-autocomplete\n [label]=\"'core.component.form-builder.value-source-field' | translate\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"model![property.value]\"\n [items]=\"rightProperties\"\n valueField=\"value\"\n displayField=\"display\"\n hideInlineError></sd-autocomplete>\n </div>\n }\n </div>\n <ng-container sdFooterRight>\n <sd-button prefixIcon=\"save\" [title]=\"'core.component.form-builder.confirm' | translate\" (click)=\"onAccept()\" color=\"primary\" type=\"fill\"></sd-button>\n </ng-container>\n </sd-modal>\n}\n", styles: [".c-builder{padding:8px;width:100%;border:1px solid #e6e6e6;border-radius:4px}.c-builder .c-condition{width:80px}.c-builder .c-condition .c-where{width:80px;border:1px solid #e6e6e6;background-color:#f2f2f2;color:#757575;border-radius:4px;padding:4px;text-align:center}.c-builder .c-condition .c-select{margin-left:10px;width:70px}\n"], dependencies: [{ kind: "component", type: SdAutocomplete$1, selector: "sd-autocomplete", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "valueField", "displayField", "disabledField", "limit", "cacheChecksum", "hyperlink", "items", "hideInlineError", "addable", "required", "disabled", "viewed", "clearable", "validator", "inlineError", "appearance", "model"], outputs: ["modelChange", "sdChange", "sdSelection", "sdAdd"] }, { kind: "component", type: SdButton, selector: "sd-button", inputs: ["autoId", "type", "color", "size", "fontSet", "title", "width", "tooltip", "prefixIcon", "suffixIcon", "disabled", "loading", "block", "htmlType"], outputs: ["click"] }, { kind: "component", type: SdModal, selector: "sd-modal", inputs: ["autoId", "title", "color", "width", "height", "view", "modalClass", "lazyLoadContent", "hideClose", "disableBackdropClose"], outputs: ["sdClosed"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4340
4340
|
};
|
|
4341
4341
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: BuildQueries$1, decorators: [{
|
|
4342
4342
|
type: Component,
|
|
4343
|
-
args: [{ selector: 'build-queries', changeDetection: ChangeDetectionStrategy.OnPush, imports: [SdAutocomplete$1, SdButton, SdModal, TranslatePipe], template: "@if (!!selection?.queries?.items?.length) {\n <div class=\"d-flex align-items-center py-4\">\n @if (label) {\n <div class=\"T14R\" style=\"flex: 1\">{{ label }}</div>\n }\n <div style=\"width: 200px\" class=\"d-flex align-items-center\">\n <a class=\"T12R\" href=\"javascript:;\" (click)=\"edit()\">{{ queryString }}</a>\n </div>\n </div>\n <sd-modal [title]=\"'core.component.form-builder.set-query-value' | translate\" width=\"400px\">\n <div class=\"c-builder\">\n @for (property of leftProperties; track property.value) {\n <div class=\"d-flex align-items-center py-8\" style=\"gap: 8px\">\n <div style=\"width: 150px\">\n <div class=\"text-secondary\">{{ property.value }}</div>\n <div class=\"T16M\">{{ property.display }}</div>\n </div>\n <sd-autocomplete\n [label]=\"'core.component.form-builder.value-source-field' | translate\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"model![property.value]\"\n [items]=\"rightProperties\"\n valueField=\"value\"\n displayField=\"display\"\n hideInlineError></sd-autocomplete>\n </div>\n }\n </div>\n <ng-container
|
|
4343
|
+
args: [{ selector: 'build-queries', changeDetection: ChangeDetectionStrategy.OnPush, imports: [SdAutocomplete$1, SdButton, SdModal, TranslatePipe], template: "@if (!!selection?.queries?.items?.length) {\n <div class=\"d-flex align-items-center py-4\">\n @if (label) {\n <div class=\"T14R\" style=\"flex: 1\">{{ label }}</div>\n }\n <div style=\"width: 200px\" class=\"d-flex align-items-center\">\n <a class=\"T12R\" href=\"javascript:;\" (click)=\"edit()\">{{ queryString }}</a>\n </div>\n </div>\n <sd-modal [title]=\"'core.component.form-builder.set-query-value' | translate\" width=\"400px\" modalClass=\"sd-form-builder-dialog\">\n <div class=\"c-builder\">\n @for (property of leftProperties; track property.value) {\n <div class=\"d-flex align-items-center py-8\" style=\"gap: 8px\">\n <div style=\"width: 150px\">\n <div class=\"text-secondary\">{{ property.value }}</div>\n <div class=\"T16M\">{{ property.display }}</div>\n </div>\n <sd-autocomplete\n [label]=\"'core.component.form-builder.value-source-field' | translate\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"model![property.value]\"\n [items]=\"rightProperties\"\n valueField=\"value\"\n displayField=\"display\"\n hideInlineError></sd-autocomplete>\n </div>\n }\n </div>\n <ng-container sdFooterRight>\n <sd-button prefixIcon=\"save\" [title]=\"'core.component.form-builder.confirm' | translate\" (click)=\"onAccept()\" color=\"primary\" type=\"fill\"></sd-button>\n </ng-container>\n </sd-modal>\n}\n", styles: [".c-builder{padding:8px;width:100%;border:1px solid #e6e6e6;border-radius:4px}.c-builder .c-condition{width:80px}.c-builder .c-condition .c-where{width:80px;border:1px solid #e6e6e6;background-color:#f2f2f2;color:#757575;border-radius:4px;padding:4px;text-align:center}.c-builder .c-condition .c-select{margin-left:10px;width:70px}\n"] }]
|
|
4344
4344
|
}], ctorParameters: () => [{ type: i0.ChangeDetectorRef }], propDecorators: { modal: [{
|
|
4345
4345
|
type: ViewChild,
|
|
4346
4346
|
args: [SdModal]
|
|
@@ -4449,11 +4449,11 @@ class BuildVariables {
|
|
|
4449
4449
|
this.ref.markForCheck();
|
|
4450
4450
|
};
|
|
4451
4451
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: BuildVariables, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
4452
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: BuildVariables, isStandalone: true, selector: "build-variables", inputs: { components: "components", variables: "variables", label: "label", selections: "selections", _valuesKey: ["valuesKey", "_valuesKey"], _model: ["model", "_model"] }, outputs: { modelChange: "modelChange" }, viewQueries: [{ propertyName: "modal", first: true, predicate: SdModal, descendants: true }], ngImport: i0, template: "@if (!!selection?.variables?.items?.length) {\n <div class=\"d-flex align-items-center py-4\">\n @if (label) {\n <div class=\"T14R\" style=\"flex: 1\">{{ label }}</div>\n }\n <div style=\"width: 200px\" class=\"d-flex align-items-center\">\n <a class=\"T12R\" href=\"javascript:;\" (click)=\"edit()\">{{ queryString }}</a>\n </div>\n </div>\n <sd-modal [title]=\"'core.component.form-builder.set-on-change-assign' | translate\" width=\"500px\">\n <div class=\"c-builder\">\n <div class=\"d-flex justify-content-end\">\n <sd-button prefixIcon=\"add\" [title]=\"'core.component.form-builder.add-field' | translate\" color=\"primary\" (click)=\"addField()\"></sd-button>\n </div>\n <!-- C\u1EA5p 1 -->\n @for (item of items; track item.id; let idx = $index) {\n <div class=\"d-flex align-items-center py-8\" style=\"gap: 8px\">\n <sd-autocomplete\n [label]=\"'core.component.form-builder.target-field' | translate\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"item.key\"\n [items]=\"leftProperties\"\n valueField=\"value\"\n displayField=\"display\"\n required\n hideInlineError></sd-autocomplete>\n <sd-autocomplete\n [label]=\"'core.component.form-builder.target-value' | translate\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"item.value\"\n [items]=\"rightProperties\"\n valueField=\"value\"\n displayField=\"display\"\n required\n hideInlineError></sd-autocomplete>\n </div>\n }\n </div>\n <ng-container
|
|
4452
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: BuildVariables, isStandalone: true, selector: "build-variables", inputs: { components: "components", variables: "variables", label: "label", selections: "selections", _valuesKey: ["valuesKey", "_valuesKey"], _model: ["model", "_model"] }, outputs: { modelChange: "modelChange" }, viewQueries: [{ propertyName: "modal", first: true, predicate: SdModal, descendants: true }], ngImport: i0, template: "@if (!!selection?.variables?.items?.length) {\n <div class=\"d-flex align-items-center py-4\">\n @if (label) {\n <div class=\"T14R\" style=\"flex: 1\">{{ label }}</div>\n }\n <div style=\"width: 200px\" class=\"d-flex align-items-center\">\n <a class=\"T12R\" href=\"javascript:;\" (click)=\"edit()\">{{ queryString }}</a>\n </div>\n </div>\n <sd-modal [title]=\"'core.component.form-builder.set-on-change-assign' | translate\" width=\"500px\" modalClass=\"sd-form-builder-dialog\">\n <div class=\"c-builder\">\n <div class=\"d-flex justify-content-end\">\n <sd-button prefixIcon=\"add\" [title]=\"'core.component.form-builder.add-field' | translate\" color=\"primary\" (click)=\"addField()\"></sd-button>\n </div>\n <!-- C\u1EA5p 1 -->\n @for (item of items; track item.id; let idx = $index) {\n <div class=\"d-flex align-items-center py-8\" style=\"gap: 8px\">\n <sd-autocomplete\n [label]=\"'core.component.form-builder.target-field' | translate\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"item.key\"\n [items]=\"leftProperties\"\n valueField=\"value\"\n displayField=\"display\"\n required\n hideInlineError></sd-autocomplete>\n <sd-autocomplete\n [label]=\"'core.component.form-builder.target-value' | translate\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"item.value\"\n [items]=\"rightProperties\"\n valueField=\"value\"\n displayField=\"display\"\n required\n hideInlineError></sd-autocomplete>\n </div>\n }\n </div>\n <ng-container sdFooterRight>\n <sd-button prefixIcon=\"save\" [title]=\"'core.component.form-builder.confirm' | translate\" (click)=\"onAccept()\" color=\"primary\" type=\"fill\"></sd-button>\n </ng-container>\n </sd-modal>\n}\n", styles: [".c-builder{padding:8px;width:100%;border:1px solid #e6e6e6;border-radius:4px}.c-builder .c-condition{width:80px}.c-builder .c-condition .c-where{width:80px;border:1px solid #e6e6e6;background-color:#f2f2f2;color:#757575;border-radius:4px;padding:4px;text-align:center}.c-builder .c-condition .c-select{margin-left:10px;width:70px}\n"], dependencies: [{ kind: "component", type: SdAutocomplete$1, selector: "sd-autocomplete", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "valueField", "displayField", "disabledField", "limit", "cacheChecksum", "hyperlink", "items", "hideInlineError", "addable", "required", "disabled", "viewed", "clearable", "validator", "inlineError", "appearance", "model"], outputs: ["modelChange", "sdChange", "sdSelection", "sdAdd"] }, { kind: "component", type: SdButton, selector: "sd-button", inputs: ["autoId", "type", "color", "size", "fontSet", "title", "width", "tooltip", "prefixIcon", "suffixIcon", "disabled", "loading", "block", "htmlType"], outputs: ["click"] }, { kind: "component", type: SdModal, selector: "sd-modal", inputs: ["autoId", "title", "color", "width", "height", "view", "modalClass", "lazyLoadContent", "hideClose", "disableBackdropClose"], outputs: ["sdClosed"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4453
4453
|
}
|
|
4454
4454
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: BuildVariables, decorators: [{
|
|
4455
4455
|
type: Component,
|
|
4456
|
-
args: [{ selector: 'build-variables', changeDetection: ChangeDetectionStrategy.OnPush, imports: [SdAutocomplete$1, SdButton, SdModal, TranslatePipe], template: "@if (!!selection?.variables?.items?.length) {\n <div class=\"d-flex align-items-center py-4\">\n @if (label) {\n <div class=\"T14R\" style=\"flex: 1\">{{ label }}</div>\n }\n <div style=\"width: 200px\" class=\"d-flex align-items-center\">\n <a class=\"T12R\" href=\"javascript:;\" (click)=\"edit()\">{{ queryString }}</a>\n </div>\n </div>\n <sd-modal [title]=\"'core.component.form-builder.set-on-change-assign' | translate\" width=\"500px\">\n <div class=\"c-builder\">\n <div class=\"d-flex justify-content-end\">\n <sd-button prefixIcon=\"add\" [title]=\"'core.component.form-builder.add-field' | translate\" color=\"primary\" (click)=\"addField()\"></sd-button>\n </div>\n <!-- C\u1EA5p 1 -->\n @for (item of items; track item.id; let idx = $index) {\n <div class=\"d-flex align-items-center py-8\" style=\"gap: 8px\">\n <sd-autocomplete\n [label]=\"'core.component.form-builder.target-field' | translate\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"item.key\"\n [items]=\"leftProperties\"\n valueField=\"value\"\n displayField=\"display\"\n required\n hideInlineError></sd-autocomplete>\n <sd-autocomplete\n [label]=\"'core.component.form-builder.target-value' | translate\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"item.value\"\n [items]=\"rightProperties\"\n valueField=\"value\"\n displayField=\"display\"\n required\n hideInlineError></sd-autocomplete>\n </div>\n }\n </div>\n <ng-container
|
|
4456
|
+
args: [{ selector: 'build-variables', changeDetection: ChangeDetectionStrategy.OnPush, imports: [SdAutocomplete$1, SdButton, SdModal, TranslatePipe], template: "@if (!!selection?.variables?.items?.length) {\n <div class=\"d-flex align-items-center py-4\">\n @if (label) {\n <div class=\"T14R\" style=\"flex: 1\">{{ label }}</div>\n }\n <div style=\"width: 200px\" class=\"d-flex align-items-center\">\n <a class=\"T12R\" href=\"javascript:;\" (click)=\"edit()\">{{ queryString }}</a>\n </div>\n </div>\n <sd-modal [title]=\"'core.component.form-builder.set-on-change-assign' | translate\" width=\"500px\" modalClass=\"sd-form-builder-dialog\">\n <div class=\"c-builder\">\n <div class=\"d-flex justify-content-end\">\n <sd-button prefixIcon=\"add\" [title]=\"'core.component.form-builder.add-field' | translate\" color=\"primary\" (click)=\"addField()\"></sd-button>\n </div>\n <!-- C\u1EA5p 1 -->\n @for (item of items; track item.id; let idx = $index) {\n <div class=\"d-flex align-items-center py-8\" style=\"gap: 8px\">\n <sd-autocomplete\n [label]=\"'core.component.form-builder.target-field' | translate\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"item.key\"\n [items]=\"leftProperties\"\n valueField=\"value\"\n displayField=\"display\"\n required\n hideInlineError></sd-autocomplete>\n <sd-autocomplete\n [label]=\"'core.component.form-builder.target-value' | translate\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"item.value\"\n [items]=\"rightProperties\"\n valueField=\"value\"\n displayField=\"display\"\n required\n hideInlineError></sd-autocomplete>\n </div>\n }\n </div>\n <ng-container sdFooterRight>\n <sd-button prefixIcon=\"save\" [title]=\"'core.component.form-builder.confirm' | translate\" (click)=\"onAccept()\" color=\"primary\" type=\"fill\"></sd-button>\n </ng-container>\n </sd-modal>\n}\n", styles: [".c-builder{padding:8px;width:100%;border:1px solid #e6e6e6;border-radius:4px}.c-builder .c-condition{width:80px}.c-builder .c-condition .c-where{width:80px;border:1px solid #e6e6e6;background-color:#f2f2f2;color:#757575;border-radius:4px;padding:4px;text-align:center}.c-builder .c-condition .c-select{margin-left:10px;width:70px}\n"] }]
|
|
4457
4457
|
}], ctorParameters: () => [{ type: i0.ChangeDetectorRef }], propDecorators: { modal: [{
|
|
4458
4458
|
type: ViewChild,
|
|
4459
4459
|
args: [SdModal]
|
|
@@ -4545,7 +4545,7 @@ class AttributeSelection {
|
|
|
4545
4545
|
this.ref.markForCheck();
|
|
4546
4546
|
};
|
|
4547
4547
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: AttributeSelection, deps: [{ token: i0.ChangeDetectorRef }, { token: FormGenericService }], target: i0.ɵɵFactoryTarget.Component });
|
|
4548
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: AttributeSelection, isStandalone: true, selector: "attribute-selection", inputs: { components: "components", variables: "variables", component: "component", _valuesKey: ["valuesKey", "_valuesKey"], _values: ["values", "_values"] }, outputs: { valuesKeyChange: "valuesKeyChange", valuesChange: "valuesChange", sdChange: "sdChange" }, ngImport: i0, template: "<sd-section class=\"mt-8\" [title]=\"'core.component.form-builder.data' | translate\" icon=\"format_list_bulleted\">\n @if (selections.length) {\n <div class=\"d-flex align-items-center py-4\">\n <div class=\"T14M\" style=\"flex: 1\">{{ 'core.component.form-builder.data-api' | translate }}</div>\n <sd-autocomplete\n style=\"width: 200px\"\n size=\"sm\"\n [(model)]=\"valuesKey\"\n [items]=\"selections\"\n valueField=\"value\"\n displayField=\"display\"\n (sdChange)=\"onChangeValuesKey($event)\"\n hideInlineError></sd-autocomplete>\n </div>\n <!-- N\u1EBFu l\u00E0 select t\u1EEB API th\u00EC cho ph\u00E9p truy\u1EC1n th\u00EAm Query -->\n\n @if (component.type === 'select' && !!valuesKey) {\n <build-queries\n [label]=\"'core.component.form-builder.query-condition' | translate\"\n [selections]=\"selections\"\n [valuesKey]=\"valuesKey\"\n [components]=\"components\"\n [variables]=\"variables\"\n [(model)]=\"component.properties!.query\"></build-queries>\n <build-variables\n [label]=\"'core.component.form-builder.assign-value' | translate\"\n [selections]=\"selections\"\n [valuesKey]=\"valuesKey\"\n [components]=\"components\"\n [variables]=\"variables\"\n [(model)]=\"component.properties!.setVariables\"></build-variables>\n <!-- <div class=\"d-flex align-items-center py-4\">\n <div class=\"T14M\" style=\"flex: 1\">\u0110i\u1EC1u ki\u1EC7n truy v\u1EA5n</div>\n <sd-input style=\"width: 200px\" size=\"sm\" [(model)]=\"component.properties!.query\" hideInlineError></sd-input>\n </div> -->\n }\n }\n @if (!valuesKey) {\n <div class=\"d-flex flex-column\" style=\"gap: 4px\">\n <div class=\"T14M\">{{ 'core.component.form-builder.data-static' | translate }}</div>\n @for (item of values; track item.value; let idx = $index) {\n <div class=\"d-flex align-items-end py-4\" style=\"gap: 4px\">\n <sd-input style=\"width: 200px\" size=\"sm\" [(model)]=\"item.value\" (sdChange)=\"onChangeValues()\" hideInlineError></sd-input>\n <sd-input style=\"width: 200px\" size=\"sm\" [(model)]=\"item.label\" (sdChange)=\"onChangeValues()\" hideInlineError></sd-input>\n <sd-button prefixIcon=\"delete\" (click)=\"removeItem(idx)\" color=\"error\"></sd-button>\n </div>\n }\n <sd-button prefixIcon=\"add\" [title]=\"'core.component.form-builder.add-data' | translate\" (click)=\"addItem()\" color=\"primary\"></sd-button>\n </div>\n }\n</sd-section>\n", dependencies: [{ kind: "component", type: SdInput$1, selector: "sd-input", inputs: ["autoId", "name", "appearance", "floatLabel", "size", "form", "label", "helperText", "placeholder", "type", "hideInlineError", "blurOnEnter", "required", "readonly", "disabled", "viewed", "minlength", "maxlength", "pattern", "patternErrorMessage", "validator", "inlineError", "hyperlink", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "cleared", "sdFocusForceBlur"] }, { kind: "component", type: SdAutocomplete$1, selector: "sd-autocomplete", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "valueField", "displayField", "disabledField", "limit", "cacheChecksum", "hyperlink", "items", "hideInlineError", "addable", "required", "disabled", "viewed", "clearable", "validator", "inlineError", "appearance", "model"], outputs: ["modelChange", "sdChange", "sdSelection", "sdAdd"] }, { kind: "component", type: SdButton, selector: "sd-button", inputs: ["autoId", "type", "color", "size", "fontSet", "title", "width", "tooltip", "prefixIcon", "suffixIcon", "disabled", "loading", "block", "htmlType"], outputs: ["click"] }, { kind: "component", type: SdSection, selector: "sd-section", inputs: ["title", "subTitle", "icon", "iconColor", "collapsed", "collapsible", "
|
|
4548
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: AttributeSelection, isStandalone: true, selector: "attribute-selection", inputs: { components: "components", variables: "variables", component: "component", _valuesKey: ["valuesKey", "_valuesKey"], _values: ["values", "_values"] }, outputs: { valuesKeyChange: "valuesKeyChange", valuesChange: "valuesChange", sdChange: "sdChange" }, ngImport: i0, template: "<sd-section class=\"mt-8\" [title]=\"'core.component.form-builder.data' | translate\" icon=\"format_list_bulleted\">\n @if (selections.length) {\n <div class=\"d-flex align-items-center py-4\">\n <div class=\"T14M\" style=\"flex: 1\">{{ 'core.component.form-builder.data-api' | translate }}</div>\n <sd-autocomplete\n style=\"width: 200px\"\n size=\"sm\"\n [(model)]=\"valuesKey\"\n [items]=\"selections\"\n valueField=\"value\"\n displayField=\"display\"\n (sdChange)=\"onChangeValuesKey($event)\"\n hideInlineError></sd-autocomplete>\n </div>\n <!-- N\u1EBFu l\u00E0 select t\u1EEB API th\u00EC cho ph\u00E9p truy\u1EC1n th\u00EAm Query -->\n\n @if (component.type === 'select' && !!valuesKey) {\n <build-queries\n [label]=\"'core.component.form-builder.query-condition' | translate\"\n [selections]=\"selections\"\n [valuesKey]=\"valuesKey\"\n [components]=\"components\"\n [variables]=\"variables\"\n [(model)]=\"component.properties!.query\"></build-queries>\n <build-variables\n [label]=\"'core.component.form-builder.assign-value' | translate\"\n [selections]=\"selections\"\n [valuesKey]=\"valuesKey\"\n [components]=\"components\"\n [variables]=\"variables\"\n [(model)]=\"component.properties!.setVariables\"></build-variables>\n <!-- <div class=\"d-flex align-items-center py-4\">\n <div class=\"T14M\" style=\"flex: 1\">\u0110i\u1EC1u ki\u1EC7n truy v\u1EA5n</div>\n <sd-input style=\"width: 200px\" size=\"sm\" [(model)]=\"component.properties!.query\" hideInlineError></sd-input>\n </div> -->\n }\n }\n @if (!valuesKey) {\n <div class=\"d-flex flex-column\" style=\"gap: 4px\">\n <div class=\"T14M\">{{ 'core.component.form-builder.data-static' | translate }}</div>\n @for (item of values; track item.value; let idx = $index) {\n <div class=\"d-flex align-items-end py-4\" style=\"gap: 4px\">\n <sd-input style=\"width: 200px\" size=\"sm\" [(model)]=\"item.value\" (sdChange)=\"onChangeValues()\" hideInlineError></sd-input>\n <sd-input style=\"width: 200px\" size=\"sm\" [(model)]=\"item.label\" (sdChange)=\"onChangeValues()\" hideInlineError></sd-input>\n <sd-button prefixIcon=\"delete\" (click)=\"removeItem(idx)\" color=\"error\"></sd-button>\n </div>\n }\n <sd-button prefixIcon=\"add\" [title]=\"'core.component.form-builder.add-data' | translate\" (click)=\"addItem()\" color=\"primary\"></sd-button>\n </div>\n }\n</sd-section>\n", dependencies: [{ kind: "component", type: SdInput$1, selector: "sd-input", inputs: ["autoId", "name", "appearance", "floatLabel", "size", "form", "label", "helperText", "placeholder", "type", "hideInlineError", "blurOnEnter", "required", "readonly", "disabled", "viewed", "minlength", "maxlength", "pattern", "patternErrorMessage", "validator", "inlineError", "hyperlink", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "cleared", "sdFocusForceBlur"] }, { kind: "component", type: SdAutocomplete$1, selector: "sd-autocomplete", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "valueField", "displayField", "disabledField", "limit", "cacheChecksum", "hyperlink", "items", "hideInlineError", "addable", "required", "disabled", "viewed", "clearable", "validator", "inlineError", "appearance", "model"], outputs: ["modelChange", "sdChange", "sdSelection", "sdAdd"] }, { kind: "component", type: SdButton, selector: "sd-button", inputs: ["autoId", "type", "color", "size", "fontSet", "title", "width", "tooltip", "prefixIcon", "suffixIcon", "disabled", "loading", "block", "htmlType"], outputs: ["click"] }, { kind: "component", type: SdSection, selector: "sd-section", inputs: ["title", "subTitle", "icon", "iconColor", "collapsed", "collapsible", "hideHeader"], outputs: ["collapsedChange"] }, { kind: "component", type: BuildQueries$1, selector: "build-queries", inputs: ["components", "variables", "label", "selections", "valuesKey", "model"], outputs: ["modelChange"] }, { kind: "component", type: BuildVariables, selector: "build-variables", inputs: ["components", "variables", "label", "selections", "valuesKey", "model"], outputs: ["modelChange"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4549
4549
|
}
|
|
4550
4550
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: AttributeSelection, decorators: [{
|
|
4551
4551
|
type: Component,
|
|
@@ -4918,11 +4918,11 @@ class BuildQueries {
|
|
|
4918
4918
|
this.ref.markForCheck();
|
|
4919
4919
|
};
|
|
4920
4920
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: BuildQueries, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
4921
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: BuildQueries, isStandalone: true, selector: "build-queries", inputs: { label: "label", _queries: ["queries", "_queries"], components: "components", variables: "variables", _model: ["model", "_model"] }, outputs: { modelChange: "modelChange" }, viewQueries: [{ propertyName: "modal", first: true, predicate: SdModal, descendants: true }], ngImport: i0, template: "@if (!!leftProperties?.length) {\n <div class=\"d-flex align-items-center py-4\">\n @if (label) {\n <div class=\"T14R\" style=\"flex: 1\">{{ label }}</div>\n }\n <div style=\"width: 200px\" class=\"d-flex align-items-center\">\n <a class=\"T12R\" href=\"javascript:;\" (click)=\"edit()\">{{ queryString }}</a>\n </div>\n </div>\n <sd-modal [title]=\"'core.component.form-builder.set-query-value' | translate\" width=\"400px\">\n <div class=\"c-builder\">\n @for (property of leftProperties; track property.value) {\n <div class=\"d-flex align-items-center py-8\" style=\"gap: 8px\">\n <div class=\"T14M\" style=\"width: 150px\">{{ property.display }}</div>\n <sd-autocomplete\n [label]=\"'core.component.form-builder.value-source-field' | translate\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"model![property.value]\"\n [items]=\"rightProperties\"\n valueField=\"value\"\n displayField=\"display\"\n required\n hideInlineError></sd-autocomplete>\n </div>\n }\n </div>\n <ng-container
|
|
4921
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: BuildQueries, isStandalone: true, selector: "build-queries", inputs: { label: "label", _queries: ["queries", "_queries"], components: "components", variables: "variables", _model: ["model", "_model"] }, outputs: { modelChange: "modelChange" }, viewQueries: [{ propertyName: "modal", first: true, predicate: SdModal, descendants: true }], ngImport: i0, template: "@if (!!leftProperties?.length) {\n <div class=\"d-flex align-items-center py-4\">\n @if (label) {\n <div class=\"T14R\" style=\"flex: 1\">{{ label }}</div>\n }\n <div style=\"width: 200px\" class=\"d-flex align-items-center\">\n <a class=\"T12R\" href=\"javascript:;\" (click)=\"edit()\">{{ queryString }}</a>\n </div>\n </div>\n <sd-modal [title]=\"'core.component.form-builder.set-query-value' | translate\" width=\"400px\" modalClass=\"sd-form-builder-dialog\">\n <div class=\"c-builder\">\n @for (property of leftProperties; track property.value) {\n <div class=\"d-flex align-items-center py-8\" style=\"gap: 8px\">\n <div class=\"T14M\" style=\"width: 150px\">{{ property.display }}</div>\n <sd-autocomplete\n [label]=\"'core.component.form-builder.value-source-field' | translate\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"model![property.value]\"\n [items]=\"rightProperties\"\n valueField=\"value\"\n displayField=\"display\"\n required\n hideInlineError></sd-autocomplete>\n </div>\n }\n </div>\n <ng-container sdFooterRight>\n <sd-button prefixIcon=\"save\" [title]=\"'core.component.form-builder.confirm' | translate\" (click)=\"onAccept()\" color=\"primary\" type=\"fill\"></sd-button>\n </ng-container>\n </sd-modal>\n}\n", styles: [".c-builder{padding:8px;width:100%;border:1px solid #e6e6e6;border-radius:4px}.c-builder .c-condition{width:80px}.c-builder .c-condition .c-where{width:80px;border:1px solid #e6e6e6;background-color:#f2f2f2;color:#757575;border-radius:4px;padding:4px;text-align:center}.c-builder .c-condition .c-select{margin-left:10px;width:70px}\n"], dependencies: [{ kind: "component", type: SdAutocomplete$1, selector: "sd-autocomplete", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "valueField", "displayField", "disabledField", "limit", "cacheChecksum", "hyperlink", "items", "hideInlineError", "addable", "required", "disabled", "viewed", "clearable", "validator", "inlineError", "appearance", "model"], outputs: ["modelChange", "sdChange", "sdSelection", "sdAdd"] }, { kind: "component", type: SdButton, selector: "sd-button", inputs: ["autoId", "type", "color", "size", "fontSet", "title", "width", "tooltip", "prefixIcon", "suffixIcon", "disabled", "loading", "block", "htmlType"], outputs: ["click"] }, { kind: "component", type: SdModal, selector: "sd-modal", inputs: ["autoId", "title", "color", "width", "height", "view", "modalClass", "lazyLoadContent", "hideClose", "disableBackdropClose"], outputs: ["sdClosed"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4922
4922
|
}
|
|
4923
4923
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: BuildQueries, decorators: [{
|
|
4924
4924
|
type: Component,
|
|
4925
|
-
args: [{ selector: 'build-queries', changeDetection: ChangeDetectionStrategy.OnPush, imports: [SdAutocomplete$1, SdButton, SdModal, TranslatePipe], template: "@if (!!leftProperties?.length) {\n <div class=\"d-flex align-items-center py-4\">\n @if (label) {\n <div class=\"T14R\" style=\"flex: 1\">{{ label }}</div>\n }\n <div style=\"width: 200px\" class=\"d-flex align-items-center\">\n <a class=\"T12R\" href=\"javascript:;\" (click)=\"edit()\">{{ queryString }}</a>\n </div>\n </div>\n <sd-modal [title]=\"'core.component.form-builder.set-query-value' | translate\" width=\"400px\">\n <div class=\"c-builder\">\n @for (property of leftProperties; track property.value) {\n <div class=\"d-flex align-items-center py-8\" style=\"gap: 8px\">\n <div class=\"T14M\" style=\"width: 150px\">{{ property.display }}</div>\n <sd-autocomplete\n [label]=\"'core.component.form-builder.value-source-field' | translate\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"model![property.value]\"\n [items]=\"rightProperties\"\n valueField=\"value\"\n displayField=\"display\"\n required\n hideInlineError></sd-autocomplete>\n </div>\n }\n </div>\n <ng-container
|
|
4925
|
+
args: [{ selector: 'build-queries', changeDetection: ChangeDetectionStrategy.OnPush, imports: [SdAutocomplete$1, SdButton, SdModal, TranslatePipe], template: "@if (!!leftProperties?.length) {\n <div class=\"d-flex align-items-center py-4\">\n @if (label) {\n <div class=\"T14R\" style=\"flex: 1\">{{ label }}</div>\n }\n <div style=\"width: 200px\" class=\"d-flex align-items-center\">\n <a class=\"T12R\" href=\"javascript:;\" (click)=\"edit()\">{{ queryString }}</a>\n </div>\n </div>\n <sd-modal [title]=\"'core.component.form-builder.set-query-value' | translate\" width=\"400px\" modalClass=\"sd-form-builder-dialog\">\n <div class=\"c-builder\">\n @for (property of leftProperties; track property.value) {\n <div class=\"d-flex align-items-center py-8\" style=\"gap: 8px\">\n <div class=\"T14M\" style=\"width: 150px\">{{ property.display }}</div>\n <sd-autocomplete\n [label]=\"'core.component.form-builder.value-source-field' | translate\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"model![property.value]\"\n [items]=\"rightProperties\"\n valueField=\"value\"\n displayField=\"display\"\n required\n hideInlineError></sd-autocomplete>\n </div>\n }\n </div>\n <ng-container sdFooterRight>\n <sd-button prefixIcon=\"save\" [title]=\"'core.component.form-builder.confirm' | translate\" (click)=\"onAccept()\" color=\"primary\" type=\"fill\"></sd-button>\n </ng-container>\n </sd-modal>\n}\n", styles: [".c-builder{padding:8px;width:100%;border:1px solid #e6e6e6;border-radius:4px}.c-builder .c-condition{width:80px}.c-builder .c-condition .c-where{width:80px;border:1px solid #e6e6e6;background-color:#f2f2f2;color:#757575;border-radius:4px;padding:4px;text-align:center}.c-builder .c-condition .c-select{margin-left:10px;width:70px}\n"] }]
|
|
4926
4926
|
}], ctorParameters: () => [{ type: i0.ChangeDetectorRef }], propDecorators: { modal: [{
|
|
4927
4927
|
type: ViewChild,
|
|
4928
4928
|
args: [SdModal]
|
|
@@ -5146,11 +5146,11 @@ class AttributeParameter {
|
|
|
5146
5146
|
this.ref.markForCheck();
|
|
5147
5147
|
};
|
|
5148
5148
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: AttributeParameter, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
5149
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: AttributeParameter, isStandalone: true, selector: "attribute-parameter", inputs: { components: "components", label: "label", _model: ["model", "_model"] }, outputs: { modelChange: "modelChange" }, viewQueries: [{ propertyName: "modal", first: true, predicate: SdModal, descendants: true }], ngImport: i0, template: "<div class=\"d-flex align-items-center py-4\">\n @if (label) {\n <div class=\"T14R\" style=\"flex: 1\">{{ label }}</div>\n }\n <div style=\"width: 200px\" class=\"d-flex align-items-center\">\n <a class=\"T12R\" href=\"javascript:;\" (click)=\"edit()\">{{ queryString }}</a>\n </div>\n</div>\n<sd-modal [title]=\"'core.component.form-builder.assign-parameter-value' | translate\" width=\"500px\">\n <div class=\"c-builder\">\n <div class=\"d-flex justify-content-end\">\n <sd-button prefixIcon=\"add\" [title]=\"'core.component.form-builder.add-field' | translate\" color=\"primary\" (click)=\"addField()\"></sd-button>\n </div>\n <!-- C\u1EA5p 1 -->\n @for (item of items; track item.id; let idx = $index) {\n <div class=\"d-flex align-items-center py-8\" style=\"gap: 8px\">\n <sd-input [label]=\"'core.component.form-builder.parameter-name' | translate\" [form]=\"form\" style=\"flex: 1\" [(model)]=\"item.key\" required hideInlineError></sd-input>\n <sd-input [label]=\"'core.component.form-builder.parameter-value' | translate\" [form]=\"form\" style=\"flex: 1\" [(model)]=\"item.value\" required hideInlineError></sd-input>\n <div style=\"width: 32px; min-width: 32px\">\n <sd-button\n prefixIcon=\"delete\"\n [tooltip]=\"'core.component.form-builder.delete' | translate\"\n (click)=\"remove(idx)\"\n size=\"sm\"\n class=\"p-0\"\n ></sd-button>\n </div>\n </div>\n }\n </div>\n <ng-container
|
|
5149
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: AttributeParameter, isStandalone: true, selector: "attribute-parameter", inputs: { components: "components", label: "label", _model: ["model", "_model"] }, outputs: { modelChange: "modelChange" }, viewQueries: [{ propertyName: "modal", first: true, predicate: SdModal, descendants: true }], ngImport: i0, template: "<div class=\"d-flex align-items-center py-4\">\n @if (label) {\n <div class=\"T14R\" style=\"flex: 1\">{{ label }}</div>\n }\n <div style=\"width: 200px\" class=\"d-flex align-items-center\">\n <a class=\"T12R\" href=\"javascript:;\" (click)=\"edit()\">{{ queryString }}</a>\n </div>\n</div>\n<sd-modal [title]=\"'core.component.form-builder.assign-parameter-value' | translate\" width=\"500px\" modalClass=\"sd-form-builder-dialog\">\n <div class=\"c-builder\">\n <div class=\"d-flex justify-content-end\">\n <sd-button prefixIcon=\"add\" [title]=\"'core.component.form-builder.add-field' | translate\" color=\"primary\" (click)=\"addField()\"></sd-button>\n </div>\n <!-- C\u1EA5p 1 -->\n @for (item of items; track item.id; let idx = $index) {\n <div class=\"d-flex align-items-center py-8\" style=\"gap: 8px\">\n <sd-input [label]=\"'core.component.form-builder.parameter-name' | translate\" [form]=\"form\" style=\"flex: 1\" [(model)]=\"item.key\" required hideInlineError></sd-input>\n <sd-input [label]=\"'core.component.form-builder.parameter-value' | translate\" [form]=\"form\" style=\"flex: 1\" [(model)]=\"item.value\" required hideInlineError></sd-input>\n <div style=\"width: 32px; min-width: 32px\">\n <sd-button\n prefixIcon=\"delete\"\n [tooltip]=\"'core.component.form-builder.delete' | translate\"\n (click)=\"remove(idx)\"\n size=\"sm\"\n class=\"p-0\"\n ></sd-button>\n </div>\n </div>\n }\n </div>\n <ng-container sdFooterRight>\n <sd-button prefixIcon=\"save\" [title]=\"'core.component.form-builder.confirm' | translate\" (click)=\"onAccept()\" color=\"primary\" type=\"fill\"></sd-button>\n </ng-container>\n</sd-modal>\n", styles: [".c-builder{padding:8px;width:100%;border:1px solid #e6e6e6;border-radius:4px}.c-builder .c-condition{width:80px}.c-builder .c-condition .c-where{width:80px;border:1px solid #e6e6e6;background-color:#f2f2f2;color:#757575;border-radius:4px;padding:4px;text-align:center}.c-builder .c-condition .c-select{margin-left:10px;width:70px}\n"], dependencies: [{ kind: "component", type: SdButton, selector: "sd-button", inputs: ["autoId", "type", "color", "size", "fontSet", "title", "width", "tooltip", "prefixIcon", "suffixIcon", "disabled", "loading", "block", "htmlType"], outputs: ["click"] }, { kind: "component", type: SdModal, selector: "sd-modal", inputs: ["autoId", "title", "color", "width", "height", "view", "modalClass", "lazyLoadContent", "hideClose", "disableBackdropClose"], outputs: ["sdClosed"] }, { kind: "component", type: SdInput, selector: "sd-input", inputs: ["autoId", "name", "appearance", "floatLabel", "size", "form", "label", "helperText", "placeholder", "type", "hideInlineError", "blurOnEnter", "required", "readonly", "disabled", "viewed", "minlength", "maxlength", "pattern", "patternErrorMessage", "validator", "inlineError", "hyperlink", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "cleared", "sdFocusForceBlur"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
5150
5150
|
}
|
|
5151
5151
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: AttributeParameter, decorators: [{
|
|
5152
5152
|
type: Component,
|
|
5153
|
-
args: [{ selector: 'attribute-parameter', changeDetection: ChangeDetectionStrategy.OnPush, imports: [SdButton, SdModal, SdInput, TranslatePipe], template: "<div class=\"d-flex align-items-center py-4\">\n @if (label) {\n <div class=\"T14R\" style=\"flex: 1\">{{ label }}</div>\n }\n <div style=\"width: 200px\" class=\"d-flex align-items-center\">\n <a class=\"T12R\" href=\"javascript:;\" (click)=\"edit()\">{{ queryString }}</a>\n </div>\n</div>\n<sd-modal [title]=\"'core.component.form-builder.assign-parameter-value' | translate\" width=\"500px\">\n <div class=\"c-builder\">\n <div class=\"d-flex justify-content-end\">\n <sd-button prefixIcon=\"add\" [title]=\"'core.component.form-builder.add-field' | translate\" color=\"primary\" (click)=\"addField()\"></sd-button>\n </div>\n <!-- C\u1EA5p 1 -->\n @for (item of items; track item.id; let idx = $index) {\n <div class=\"d-flex align-items-center py-8\" style=\"gap: 8px\">\n <sd-input [label]=\"'core.component.form-builder.parameter-name' | translate\" [form]=\"form\" style=\"flex: 1\" [(model)]=\"item.key\" required hideInlineError></sd-input>\n <sd-input [label]=\"'core.component.form-builder.parameter-value' | translate\" [form]=\"form\" style=\"flex: 1\" [(model)]=\"item.value\" required hideInlineError></sd-input>\n <div style=\"width: 32px; min-width: 32px\">\n <sd-button\n prefixIcon=\"delete\"\n [tooltip]=\"'core.component.form-builder.delete' | translate\"\n (click)=\"remove(idx)\"\n size=\"sm\"\n class=\"p-0\"\n ></sd-button>\n </div>\n </div>\n }\n </div>\n <ng-container
|
|
5153
|
+
args: [{ selector: 'attribute-parameter', changeDetection: ChangeDetectionStrategy.OnPush, imports: [SdButton, SdModal, SdInput, TranslatePipe], template: "<div class=\"d-flex align-items-center py-4\">\n @if (label) {\n <div class=\"T14R\" style=\"flex: 1\">{{ label }}</div>\n }\n <div style=\"width: 200px\" class=\"d-flex align-items-center\">\n <a class=\"T12R\" href=\"javascript:;\" (click)=\"edit()\">{{ queryString }}</a>\n </div>\n</div>\n<sd-modal [title]=\"'core.component.form-builder.assign-parameter-value' | translate\" width=\"500px\" modalClass=\"sd-form-builder-dialog\">\n <div class=\"c-builder\">\n <div class=\"d-flex justify-content-end\">\n <sd-button prefixIcon=\"add\" [title]=\"'core.component.form-builder.add-field' | translate\" color=\"primary\" (click)=\"addField()\"></sd-button>\n </div>\n <!-- C\u1EA5p 1 -->\n @for (item of items; track item.id; let idx = $index) {\n <div class=\"d-flex align-items-center py-8\" style=\"gap: 8px\">\n <sd-input [label]=\"'core.component.form-builder.parameter-name' | translate\" [form]=\"form\" style=\"flex: 1\" [(model)]=\"item.key\" required hideInlineError></sd-input>\n <sd-input [label]=\"'core.component.form-builder.parameter-value' | translate\" [form]=\"form\" style=\"flex: 1\" [(model)]=\"item.value\" required hideInlineError></sd-input>\n <div style=\"width: 32px; min-width: 32px\">\n <sd-button\n prefixIcon=\"delete\"\n [tooltip]=\"'core.component.form-builder.delete' | translate\"\n (click)=\"remove(idx)\"\n size=\"sm\"\n class=\"p-0\"\n ></sd-button>\n </div>\n </div>\n }\n </div>\n <ng-container sdFooterRight>\n <sd-button prefixIcon=\"save\" [title]=\"'core.component.form-builder.confirm' | translate\" (click)=\"onAccept()\" color=\"primary\" type=\"fill\"></sd-button>\n </ng-container>\n</sd-modal>\n", styles: [".c-builder{padding:8px;width:100%;border:1px solid #e6e6e6;border-radius:4px}.c-builder .c-condition{width:80px}.c-builder .c-condition .c-where{width:80px;border:1px solid #e6e6e6;background-color:#f2f2f2;color:#757575;border-radius:4px;padding:4px;text-align:center}.c-builder .c-condition .c-select{margin-left:10px;width:70px}\n"] }]
|
|
5154
5154
|
}], ctorParameters: () => [{ type: i0.ChangeDetectorRef }], propDecorators: { modal: [{
|
|
5155
5155
|
type: ViewChild,
|
|
5156
5156
|
args: [SdModal]
|
|
@@ -5394,7 +5394,7 @@ class AttributeTable {
|
|
|
5394
5394
|
this.modal?.close();
|
|
5395
5395
|
};
|
|
5396
5396
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: AttributeTable, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
5397
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: AttributeTable, isStandalone: true, selector: "attribute-table", inputs: { components: "components", variables: "variables", _columnsKey: ["columnsKey", "_columnsKey"], _columns: ["columns", "_columns"] }, outputs: { columnsKeyChange: "columnsKeyChange", columnsChange: "columnsChange" }, viewQueries: [{ propertyName: "modal", first: true, predicate: SdModal, descendants: true }], ngImport: i0, template: "<details class=\"fb-section\" open>\n <summary class=\"fb-section__head\">\n <span class=\"msi\">view_column</span>\n <span class=\"fb-section__title\">{{ 'core.component.form-builder.column-list' | translate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n @if (tables.length) {\n <sd-autocomplete\n class=\"py-8\"\n [label]=\"'core.component.form-builder.column-data' | translate\"\n [(model)]=\"columnsKey\"\n [items]=\"tables\"\n valueField=\"value\"\n displayField=\"display\"\n (sdChange)=\"onChangeColumnsKey($event)\"\n hideInlineError></sd-autocomplete>\n }\n @if (!columnsKey) {\n <div class=\"fb-columns-list\">\n @for (item of columns; track item.key; let idx = $index) {\n <div class=\"fb-column-row\" [class.is-active]=\"idx === selectedIdx\">\n <span class=\"fb-column-row__type-icon\"><span class=\"msi\">{{ columnTypeSymbol(item.type) }}</span></span>\n <div class=\"fb-column-row__meta\">\n <div class=\"fb-column-row__label\">\n {{ item.label }}\n @if (item.validate?.required) {\n <span class=\"fb-column-row__req\">*</span>\n }\n </div>\n <div class=\"fb-column-row__sub mono\">{{ item.key }} \u00B7 {{ item.type }}</div>\n </div>\n <button type=\"button\" class=\"fb-icon-btn\" [attr.aria-label]=\"'core.component.form-builder.column-config' | translate\" (click)=\"editColumn(idx)\">\n <span class=\"msi\">edit</span>\n </button>\n <button type=\"button\" class=\"fb-icon-btn danger\" [attr.aria-label]=\"'core.component.form-builder.delete' | translate\" (click)=\"removeColumn(idx)\">\n <span class=\"msi\">delete</span>\n </button>\n </div>\n }\n <a class=\"fb-add-link\" (click)=\"addColumn()\">\n <span class=\"msi\">add</span>\n {{ 'core.component.form-builder.add-column' | translate }}\n </a>\n </div>\n }\n </div>\n</details>\n\n<sd-modal [title]=\"'core.component.form-builder.column-config' | translate\" width=\"600px\">\n @if (column) {\n <details class=\"fb-section\" open>\n <summary class=\"fb-section__head\">\n <span class=\"msi\">info</span>\n <span class=\"fb-section__title\">{{ 'core.component.form-builder.general' | translate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-input [form]=\"form\" [label]=\"'core.component.form-builder.field-key' | translate\" [(model)]=\"column.key\"></attribute-input>\n <attribute-input [form]=\"form\" [label]=\"'core.component.form-builder.column-name' | translate\" [(model)]=\"column.label\"></attribute-input>\n <attribute-input [form]=\"form\" [label]=\"'core.component.form-builder.width' | translate\" [(model)]=\"column.width\"></attribute-input>\n <attribute-switch [form]=\"form\" [label]=\"'core.component.form-builder.required' | translate\" [(model)]=\"column.validate!.required\"></attribute-switch>\n <attribute-select [form]=\"form\" [label]=\"'core.component.form-builder.data-type' | translate\" [(model)]=\"column.type\" [items]=\"types\"></attribute-select>\n </div>\n </details>\n @if (column.type === 'boolean') {\n <details class=\"fb-section\" open>\n <summary class=\"fb-section__head\">\n <span class=\"msi\">visibility</span>\n <span class=\"fb-section__title\">{{ 'core.component.form-builder.display' | translate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-input [form]=\"form\" [label]=\"'core.component.form-builder.show-when-true' | translate\" [(model)]=\"column.displayOnTrue\"></attribute-input>\n <attribute-input [form]=\"form\" [label]=\"'core.component.form-builder.show-when-false' | translate\" [(model)]=\"column.displayOnFalse\"></attribute-input>\n </div>\n </details>\n } @else if (column.type === 'values') {\n <attribute-selection\n [components]=\"components\"\n [variables]=\"variables\"\n [component]=\"column\"\n [(valuesKey)]=\"column.valuesKey\"\n [(values)]=\"column.values\"></attribute-selection>\n } @else if (column.type === 'file' || column.type === 'image') {\n <details class=\"fb-section\" open>\n <summary class=\"fb-section__head\">\n <span class=\"msi\">settings</span>\n <span class=\"fb-section__title\">{{ 'core.component.form-builder.parameter' | translate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-parameter [label]=\"'core.component.form-builder.assign-parameter' | translate\" [components]=\"components\" [(model)]=\"column!.args\"></attribute-parameter>\n </div>\n </details>\n }\n }\n <ng-container
|
|
5397
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: AttributeTable, isStandalone: true, selector: "attribute-table", inputs: { components: "components", variables: "variables", _columnsKey: ["columnsKey", "_columnsKey"], _columns: ["columns", "_columns"] }, outputs: { columnsKeyChange: "columnsKeyChange", columnsChange: "columnsChange" }, viewQueries: [{ propertyName: "modal", first: true, predicate: SdModal, descendants: true }], ngImport: i0, template: "<details class=\"fb-section\" open>\n <summary class=\"fb-section__head\">\n <span class=\"msi\">view_column</span>\n <span class=\"fb-section__title\">{{ 'core.component.form-builder.column-list' | translate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n @if (tables.length) {\n <sd-autocomplete\n class=\"py-8\"\n [label]=\"'core.component.form-builder.column-data' | translate\"\n [(model)]=\"columnsKey\"\n [items]=\"tables\"\n valueField=\"value\"\n displayField=\"display\"\n (sdChange)=\"onChangeColumnsKey($event)\"\n hideInlineError></sd-autocomplete>\n }\n @if (!columnsKey) {\n <div class=\"fb-columns-list\">\n @for (item of columns; track item.key; let idx = $index) {\n <div class=\"fb-column-row\" [class.is-active]=\"idx === selectedIdx\">\n <span class=\"fb-column-row__type-icon\"><span class=\"msi\">{{ columnTypeSymbol(item.type) }}</span></span>\n <div class=\"fb-column-row__meta\">\n <div class=\"fb-column-row__label\">\n {{ item.label }}\n @if (item.validate?.required) {\n <span class=\"fb-column-row__req\">*</span>\n }\n </div>\n <div class=\"fb-column-row__sub mono\">{{ item.key }} \u00B7 {{ item.type }}</div>\n </div>\n <button type=\"button\" class=\"fb-icon-btn\" [attr.aria-label]=\"'core.component.form-builder.column-config' | translate\" (click)=\"editColumn(idx)\">\n <span class=\"msi\">edit</span>\n </button>\n <button type=\"button\" class=\"fb-icon-btn danger\" [attr.aria-label]=\"'core.component.form-builder.delete' | translate\" (click)=\"removeColumn(idx)\">\n <span class=\"msi\">delete</span>\n </button>\n </div>\n }\n <a class=\"fb-add-link\" (click)=\"addColumn()\">\n <span class=\"msi\">add</span>\n {{ 'core.component.form-builder.add-column' | translate }}\n </a>\n </div>\n }\n </div>\n</details>\n\n<sd-modal [title]=\"'core.component.form-builder.column-config' | translate\" width=\"600px\" modalClass=\"sd-form-builder-dialog\">\n @if (column) {\n <details class=\"fb-section\" open>\n <summary class=\"fb-section__head\">\n <span class=\"msi\">info</span>\n <span class=\"fb-section__title\">{{ 'core.component.form-builder.general' | translate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-input [form]=\"form\" [label]=\"'core.component.form-builder.field-key' | translate\" [(model)]=\"column.key\"></attribute-input>\n <attribute-input [form]=\"form\" [label]=\"'core.component.form-builder.column-name' | translate\" [(model)]=\"column.label\"></attribute-input>\n <attribute-input [form]=\"form\" [label]=\"'core.component.form-builder.width' | translate\" [(model)]=\"column.width\"></attribute-input>\n <attribute-switch [form]=\"form\" [label]=\"'core.component.form-builder.required' | translate\" [(model)]=\"column.validate!.required\"></attribute-switch>\n <attribute-select [form]=\"form\" [label]=\"'core.component.form-builder.data-type' | translate\" [(model)]=\"column.type\" [items]=\"types\"></attribute-select>\n </div>\n </details>\n @if (column.type === 'boolean') {\n <details class=\"fb-section\" open>\n <summary class=\"fb-section__head\">\n <span class=\"msi\">visibility</span>\n <span class=\"fb-section__title\">{{ 'core.component.form-builder.display' | translate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-input [form]=\"form\" [label]=\"'core.component.form-builder.show-when-true' | translate\" [(model)]=\"column.displayOnTrue\"></attribute-input>\n <attribute-input [form]=\"form\" [label]=\"'core.component.form-builder.show-when-false' | translate\" [(model)]=\"column.displayOnFalse\"></attribute-input>\n </div>\n </details>\n } @else if (column.type === 'values') {\n <attribute-selection\n [components]=\"components\"\n [variables]=\"variables\"\n [component]=\"column\"\n [(valuesKey)]=\"column.valuesKey\"\n [(values)]=\"column.values\"></attribute-selection>\n } @else if (column.type === 'file' || column.type === 'image') {\n <details class=\"fb-section\" open>\n <summary class=\"fb-section__head\">\n <span class=\"msi\">settings</span>\n <span class=\"fb-section__title\">{{ 'core.component.form-builder.parameter' | translate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-parameter [label]=\"'core.component.form-builder.assign-parameter' | translate\" [components]=\"components\" [(model)]=\"column!.args\"></attribute-parameter>\n </div>\n </details>\n }\n }\n <ng-container sdFooterRight>\n <sd-button prefixIcon=\"save\" [title]=\"'core.component.form-builder.save' | translate\" (click)=\"onConfirm()\" color=\"primary\"></sd-button>\n </ng-container>\n</sd-modal>\n", dependencies: [{ kind: "ngmodule", type: MatDividerModule }, { kind: "component", type: SdAutocomplete$1, selector: "sd-autocomplete", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "valueField", "displayField", "disabledField", "limit", "cacheChecksum", "hyperlink", "items", "hideInlineError", "addable", "required", "disabled", "viewed", "clearable", "validator", "inlineError", "appearance", "model"], outputs: ["modelChange", "sdChange", "sdSelection", "sdAdd"] }, { kind: "component", type: SdButton, selector: "sd-button", inputs: ["autoId", "type", "color", "size", "fontSet", "title", "width", "tooltip", "prefixIcon", "suffixIcon", "disabled", "loading", "block", "htmlType"], outputs: ["click"] }, { kind: "component", type: SdModal, selector: "sd-modal", inputs: ["autoId", "title", "color", "width", "height", "view", "modalClass", "lazyLoadContent", "hideClose", "disableBackdropClose"], outputs: ["sdClosed"] }, { kind: "component", type: AttributeSelection, selector: "attribute-selection", inputs: ["components", "variables", "component", "valuesKey", "values"], outputs: ["valuesKeyChange", "valuesChange", "sdChange"] }, { kind: "component", type: AttributeInput, selector: "attribute-input", inputs: ["form", "label", "disabled", "model"], outputs: ["modelChange", "sdChange"] }, { kind: "component", type: AttributeSelect, selector: "attribute-select", inputs: ["form", "label", "disabled", "multiple", "items", "model"], outputs: ["modelChange", "sdChange"] }, { kind: "component", type: AttributeSwitch, selector: "attribute-switch", inputs: ["form", "label", "model"], outputs: ["modelChange"] }, { kind: "component", type: AttributeParameter, selector: "attribute-parameter", inputs: ["components", "label", "model"], outputs: ["modelChange"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
|
|
5398
5398
|
}
|
|
5399
5399
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: AttributeTable, decorators: [{
|
|
5400
5400
|
type: Component,
|
|
@@ -5410,7 +5410,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
5410
5410
|
AttributeSwitch,
|
|
5411
5411
|
AttributeParameter,
|
|
5412
5412
|
TranslatePipe,
|
|
5413
|
-
], template: "<details class=\"fb-section\" open>\n <summary class=\"fb-section__head\">\n <span class=\"msi\">view_column</span>\n <span class=\"fb-section__title\">{{ 'core.component.form-builder.column-list' | translate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n @if (tables.length) {\n <sd-autocomplete\n class=\"py-8\"\n [label]=\"'core.component.form-builder.column-data' | translate\"\n [(model)]=\"columnsKey\"\n [items]=\"tables\"\n valueField=\"value\"\n displayField=\"display\"\n (sdChange)=\"onChangeColumnsKey($event)\"\n hideInlineError></sd-autocomplete>\n }\n @if (!columnsKey) {\n <div class=\"fb-columns-list\">\n @for (item of columns; track item.key; let idx = $index) {\n <div class=\"fb-column-row\" [class.is-active]=\"idx === selectedIdx\">\n <span class=\"fb-column-row__type-icon\"><span class=\"msi\">{{ columnTypeSymbol(item.type) }}</span></span>\n <div class=\"fb-column-row__meta\">\n <div class=\"fb-column-row__label\">\n {{ item.label }}\n @if (item.validate?.required) {\n <span class=\"fb-column-row__req\">*</span>\n }\n </div>\n <div class=\"fb-column-row__sub mono\">{{ item.key }} \u00B7 {{ item.type }}</div>\n </div>\n <button type=\"button\" class=\"fb-icon-btn\" [attr.aria-label]=\"'core.component.form-builder.column-config' | translate\" (click)=\"editColumn(idx)\">\n <span class=\"msi\">edit</span>\n </button>\n <button type=\"button\" class=\"fb-icon-btn danger\" [attr.aria-label]=\"'core.component.form-builder.delete' | translate\" (click)=\"removeColumn(idx)\">\n <span class=\"msi\">delete</span>\n </button>\n </div>\n }\n <a class=\"fb-add-link\" (click)=\"addColumn()\">\n <span class=\"msi\">add</span>\n {{ 'core.component.form-builder.add-column' | translate }}\n </a>\n </div>\n }\n </div>\n</details>\n\n<sd-modal [title]=\"'core.component.form-builder.column-config' | translate\" width=\"600px\">\n @if (column) {\n <details class=\"fb-section\" open>\n <summary class=\"fb-section__head\">\n <span class=\"msi\">info</span>\n <span class=\"fb-section__title\">{{ 'core.component.form-builder.general' | translate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-input [form]=\"form\" [label]=\"'core.component.form-builder.field-key' | translate\" [(model)]=\"column.key\"></attribute-input>\n <attribute-input [form]=\"form\" [label]=\"'core.component.form-builder.column-name' | translate\" [(model)]=\"column.label\"></attribute-input>\n <attribute-input [form]=\"form\" [label]=\"'core.component.form-builder.width' | translate\" [(model)]=\"column.width\"></attribute-input>\n <attribute-switch [form]=\"form\" [label]=\"'core.component.form-builder.required' | translate\" [(model)]=\"column.validate!.required\"></attribute-switch>\n <attribute-select [form]=\"form\" [label]=\"'core.component.form-builder.data-type' | translate\" [(model)]=\"column.type\" [items]=\"types\"></attribute-select>\n </div>\n </details>\n @if (column.type === 'boolean') {\n <details class=\"fb-section\" open>\n <summary class=\"fb-section__head\">\n <span class=\"msi\">visibility</span>\n <span class=\"fb-section__title\">{{ 'core.component.form-builder.display' | translate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-input [form]=\"form\" [label]=\"'core.component.form-builder.show-when-true' | translate\" [(model)]=\"column.displayOnTrue\"></attribute-input>\n <attribute-input [form]=\"form\" [label]=\"'core.component.form-builder.show-when-false' | translate\" [(model)]=\"column.displayOnFalse\"></attribute-input>\n </div>\n </details>\n } @else if (column.type === 'values') {\n <attribute-selection\n [components]=\"components\"\n [variables]=\"variables\"\n [component]=\"column\"\n [(valuesKey)]=\"column.valuesKey\"\n [(values)]=\"column.values\"></attribute-selection>\n } @else if (column.type === 'file' || column.type === 'image') {\n <details class=\"fb-section\" open>\n <summary class=\"fb-section__head\">\n <span class=\"msi\">settings</span>\n <span class=\"fb-section__title\">{{ 'core.component.form-builder.parameter' | translate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-parameter [label]=\"'core.component.form-builder.assign-parameter' | translate\" [components]=\"components\" [(model)]=\"column!.args\"></attribute-parameter>\n </div>\n </details>\n }\n }\n <ng-container
|
|
5413
|
+
], template: "<details class=\"fb-section\" open>\n <summary class=\"fb-section__head\">\n <span class=\"msi\">view_column</span>\n <span class=\"fb-section__title\">{{ 'core.component.form-builder.column-list' | translate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n @if (tables.length) {\n <sd-autocomplete\n class=\"py-8\"\n [label]=\"'core.component.form-builder.column-data' | translate\"\n [(model)]=\"columnsKey\"\n [items]=\"tables\"\n valueField=\"value\"\n displayField=\"display\"\n (sdChange)=\"onChangeColumnsKey($event)\"\n hideInlineError></sd-autocomplete>\n }\n @if (!columnsKey) {\n <div class=\"fb-columns-list\">\n @for (item of columns; track item.key; let idx = $index) {\n <div class=\"fb-column-row\" [class.is-active]=\"idx === selectedIdx\">\n <span class=\"fb-column-row__type-icon\"><span class=\"msi\">{{ columnTypeSymbol(item.type) }}</span></span>\n <div class=\"fb-column-row__meta\">\n <div class=\"fb-column-row__label\">\n {{ item.label }}\n @if (item.validate?.required) {\n <span class=\"fb-column-row__req\">*</span>\n }\n </div>\n <div class=\"fb-column-row__sub mono\">{{ item.key }} \u00B7 {{ item.type }}</div>\n </div>\n <button type=\"button\" class=\"fb-icon-btn\" [attr.aria-label]=\"'core.component.form-builder.column-config' | translate\" (click)=\"editColumn(idx)\">\n <span class=\"msi\">edit</span>\n </button>\n <button type=\"button\" class=\"fb-icon-btn danger\" [attr.aria-label]=\"'core.component.form-builder.delete' | translate\" (click)=\"removeColumn(idx)\">\n <span class=\"msi\">delete</span>\n </button>\n </div>\n }\n <a class=\"fb-add-link\" (click)=\"addColumn()\">\n <span class=\"msi\">add</span>\n {{ 'core.component.form-builder.add-column' | translate }}\n </a>\n </div>\n }\n </div>\n</details>\n\n<sd-modal [title]=\"'core.component.form-builder.column-config' | translate\" width=\"600px\" modalClass=\"sd-form-builder-dialog\">\n @if (column) {\n <details class=\"fb-section\" open>\n <summary class=\"fb-section__head\">\n <span class=\"msi\">info</span>\n <span class=\"fb-section__title\">{{ 'core.component.form-builder.general' | translate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-input [form]=\"form\" [label]=\"'core.component.form-builder.field-key' | translate\" [(model)]=\"column.key\"></attribute-input>\n <attribute-input [form]=\"form\" [label]=\"'core.component.form-builder.column-name' | translate\" [(model)]=\"column.label\"></attribute-input>\n <attribute-input [form]=\"form\" [label]=\"'core.component.form-builder.width' | translate\" [(model)]=\"column.width\"></attribute-input>\n <attribute-switch [form]=\"form\" [label]=\"'core.component.form-builder.required' | translate\" [(model)]=\"column.validate!.required\"></attribute-switch>\n <attribute-select [form]=\"form\" [label]=\"'core.component.form-builder.data-type' | translate\" [(model)]=\"column.type\" [items]=\"types\"></attribute-select>\n </div>\n </details>\n @if (column.type === 'boolean') {\n <details class=\"fb-section\" open>\n <summary class=\"fb-section__head\">\n <span class=\"msi\">visibility</span>\n <span class=\"fb-section__title\">{{ 'core.component.form-builder.display' | translate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-input [form]=\"form\" [label]=\"'core.component.form-builder.show-when-true' | translate\" [(model)]=\"column.displayOnTrue\"></attribute-input>\n <attribute-input [form]=\"form\" [label]=\"'core.component.form-builder.show-when-false' | translate\" [(model)]=\"column.displayOnFalse\"></attribute-input>\n </div>\n </details>\n } @else if (column.type === 'values') {\n <attribute-selection\n [components]=\"components\"\n [variables]=\"variables\"\n [component]=\"column\"\n [(valuesKey)]=\"column.valuesKey\"\n [(values)]=\"column.values\"></attribute-selection>\n } @else if (column.type === 'file' || column.type === 'image') {\n <details class=\"fb-section\" open>\n <summary class=\"fb-section__head\">\n <span class=\"msi\">settings</span>\n <span class=\"fb-section__title\">{{ 'core.component.form-builder.parameter' | translate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-parameter [label]=\"'core.component.form-builder.assign-parameter' | translate\" [components]=\"components\" [(model)]=\"column!.args\"></attribute-parameter>\n </div>\n </details>\n }\n }\n <ng-container sdFooterRight>\n <sd-button prefixIcon=\"save\" [title]=\"'core.component.form-builder.save' | translate\" (click)=\"onConfirm()\" color=\"primary\"></sd-button>\n </ng-container>\n</sd-modal>\n" }]
|
|
5414
5414
|
}], propDecorators: { modal: [{
|
|
5415
5415
|
type: ViewChild,
|
|
5416
5416
|
args: [SdModal]
|
|
@@ -5563,11 +5563,11 @@ class TableControl {
|
|
|
5563
5563
|
return rowIndex === 1 ? 'Sample value' : '—';
|
|
5564
5564
|
};
|
|
5565
5565
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: TableControl, deps: [{ token: i0.ChangeDetectorRef }, { token: BuilderService }], target: i0.ɵɵFactoryTarget.Component });
|
|
5566
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: TableControl, isStandalone: true, selector: "table-control", inputs: { _component: ["component", "_component"] }, ngImport: i0, template: "<div class=\"fb-table-control\" [class.is-viewed]=\"component.properties?.viewed\">\n <div class=\"fb-table-control__label\">\n {{ component.label }}\n @if (component.validate?.required) {\n <span>*</span>\n }\n </div>\n\n @if (component.columns?.length) {\n <div class=\"fb-table-control__frame\">\n <div class=\"fb-table-control__header\">\n @for (column of component.columns; track column.key) {\n <div class=\"fb-table-control__cell\" [style.width]=\"column.width\">\n <span class=\"fb-table-control__icon msi\">{{ columnIcon(column.type) }}</span>\n <span>{{ column.label }}</span>\n @if (column.validate?.required) {\n <span class=\"fb-table-control__required\">*</span>\n }\n </div>\n }\n </div>\n\n <div class=\"fb-table-control__body\">\n @for (row of sampleRows(); track row) {\n <div class=\"fb-table-control__row\">\n @for (column of component.columns; track column.key) {\n <div class=\"fb-table-control__cell\" [style.width]=\"column.width\">\n {{ cellPreview(column, row) }}\n </div>\n }\n </div>\n }\n </div>\n </div>\n\n <button type=\"button\" class=\"fb-table-control__add\">\n <span>+</span>\n Add row\n </button>\n }\n</div>\n", styles: [":host{display:block;width:100%;max-width:100%;min-width:0}.fb-table-control{display:flex;flex-direction:column;gap:8px;width:100%;max-width:100%;min-width:0;overflow:hidden;color:var(--sd-
|
|
5566
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: TableControl, isStandalone: true, selector: "table-control", inputs: { _component: ["component", "_component"] }, ngImport: i0, template: "<div class=\"fb-table-control\" [class.is-viewed]=\"component.properties?.viewed\">\n <div class=\"fb-table-control__label\">\n {{ component.label }}\n @if (component.validate?.required) {\n <span>*</span>\n }\n </div>\n\n @if (component.columns?.length) {\n <div class=\"fb-table-control__frame\">\n <div class=\"fb-table-control__header\">\n @for (column of component.columns; track column.key) {\n <div class=\"fb-table-control__cell\" [style.width]=\"column.width\">\n <span class=\"fb-table-control__icon msi\">{{ columnIcon(column.type) }}</span>\n <span>{{ column.label }}</span>\n @if (column.validate?.required) {\n <span class=\"fb-table-control__required\">*</span>\n }\n </div>\n }\n </div>\n\n <div class=\"fb-table-control__body\">\n @for (row of sampleRows(); track row) {\n <div class=\"fb-table-control__row\">\n @for (column of component.columns; track column.key) {\n <div class=\"fb-table-control__cell\" [style.width]=\"column.width\">\n {{ cellPreview(column, row) }}\n </div>\n }\n </div>\n }\n </div>\n </div>\n\n <button type=\"button\" class=\"fb-table-control__add\">\n <span>+</span>\n Add row\n </button>\n }\n</div>\n", styles: [":host{display:block;width:100%;max-width:100%;min-width:0}.fb-table-control{display:flex;flex-direction:column;gap:8px;width:100%;max-width:100%;min-width:0;overflow:hidden;color:var(--sd-text, #212121)}.fb-table-control__label{font-size:14px;line-height:20px;font-weight:600;color:var(--sd-text-secondary, #757575)}.fb-table-control__label span{color:var(--sd-error, #ba1a1a)}.fb-table-control__frame{width:100%;max-width:100%;min-width:0;overflow-x:auto;overflow-y:hidden;border:1px solid var(--sd-border, #E6E6E6);border-radius:10px;background:#fff;overscroll-behavior-x:contain}.fb-table-control__header,.fb-table-control__row{display:flex;width:max-content;min-width:max-content}.fb-table-control__header{min-height:44px;background:color-mix(in srgb,var(--sd-surface-muted, #F2F2F2) 70%,#fff);border-bottom:1px solid var(--sd-border, #E6E6E6)}.fb-table-control__row{min-height:42px;border-bottom:1px solid var(--sd-border, #E6E6E6)}.fb-table-control__row:last-child{border-bottom:0}.fb-table-control__cell{width:136px;flex:0 0 auto;min-width:96px;display:inline-flex;align-items:center;gap:7px;padding:10px 14px;color:var(--sd-text-secondary, #757575);font-size:13px;line-height:18px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;border-right:0}.fb-table-control__cell:last-child{border-right:0}.fb-table-control__header .fb-table-control__cell{color:var(--sd-text-secondary, #757575);font-weight:700}.fb-table-control__icon{width:16px;height:16px;flex:0 0 16px;display:inline-flex;align-items:center;justify-content:center;color:var(--sd-text-secondary, #757575);font-family:Material Icons Outlined;font-size:16px;line-height:1;letter-spacing:normal;text-transform:none;white-space:nowrap;font-feature-settings:\"liga\";-webkit-font-feature-settings:\"liga\";-webkit-font-smoothing:antialiased}.fb-table-control__required{color:var(--sd-error, #ba1a1a)}.fb-table-control__add{align-self:flex-start;height:28px;border:0;background:transparent;color:var(--sd-primary, #005cbb);display:inline-flex;align-items:center;gap:8px;padding:0 6px;font-size:14px;font-weight:600;cursor:default}.fb-table-control__add span{font-size:18px;line-height:1}.fb-table-control.is-viewed .fb-table-control__header{background:var(--sd-surface-muted, #F2F2F2)}.fb-table-control.is-viewed .fb-table-control__add{display:none}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
5567
5567
|
}
|
|
5568
5568
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: TableControl, decorators: [{
|
|
5569
5569
|
type: Component,
|
|
5570
|
-
args: [{ selector: 'table-control', changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule], template: "<div class=\"fb-table-control\" [class.is-viewed]=\"component.properties?.viewed\">\n <div class=\"fb-table-control__label\">\n {{ component.label }}\n @if (component.validate?.required) {\n <span>*</span>\n }\n </div>\n\n @if (component.columns?.length) {\n <div class=\"fb-table-control__frame\">\n <div class=\"fb-table-control__header\">\n @for (column of component.columns; track column.key) {\n <div class=\"fb-table-control__cell\" [style.width]=\"column.width\">\n <span class=\"fb-table-control__icon msi\">{{ columnIcon(column.type) }}</span>\n <span>{{ column.label }}</span>\n @if (column.validate?.required) {\n <span class=\"fb-table-control__required\">*</span>\n }\n </div>\n }\n </div>\n\n <div class=\"fb-table-control__body\">\n @for (row of sampleRows(); track row) {\n <div class=\"fb-table-control__row\">\n @for (column of component.columns; track column.key) {\n <div class=\"fb-table-control__cell\" [style.width]=\"column.width\">\n {{ cellPreview(column, row) }}\n </div>\n }\n </div>\n }\n </div>\n </div>\n\n <button type=\"button\" class=\"fb-table-control__add\">\n <span>+</span>\n Add row\n </button>\n }\n</div>\n", styles: [":host{display:block;width:100%;max-width:100%;min-width:0}.fb-table-control{display:flex;flex-direction:column;gap:8px;width:100%;max-width:100%;min-width:0;overflow:hidden;color:var(--sd-
|
|
5570
|
+
args: [{ selector: 'table-control', changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule], template: "<div class=\"fb-table-control\" [class.is-viewed]=\"component.properties?.viewed\">\n <div class=\"fb-table-control__label\">\n {{ component.label }}\n @if (component.validate?.required) {\n <span>*</span>\n }\n </div>\n\n @if (component.columns?.length) {\n <div class=\"fb-table-control__frame\">\n <div class=\"fb-table-control__header\">\n @for (column of component.columns; track column.key) {\n <div class=\"fb-table-control__cell\" [style.width]=\"column.width\">\n <span class=\"fb-table-control__icon msi\">{{ columnIcon(column.type) }}</span>\n <span>{{ column.label }}</span>\n @if (column.validate?.required) {\n <span class=\"fb-table-control__required\">*</span>\n }\n </div>\n }\n </div>\n\n <div class=\"fb-table-control__body\">\n @for (row of sampleRows(); track row) {\n <div class=\"fb-table-control__row\">\n @for (column of component.columns; track column.key) {\n <div class=\"fb-table-control__cell\" [style.width]=\"column.width\">\n {{ cellPreview(column, row) }}\n </div>\n }\n </div>\n }\n </div>\n </div>\n\n <button type=\"button\" class=\"fb-table-control__add\">\n <span>+</span>\n Add row\n </button>\n }\n</div>\n", styles: [":host{display:block;width:100%;max-width:100%;min-width:0}.fb-table-control{display:flex;flex-direction:column;gap:8px;width:100%;max-width:100%;min-width:0;overflow:hidden;color:var(--sd-text, #212121)}.fb-table-control__label{font-size:14px;line-height:20px;font-weight:600;color:var(--sd-text-secondary, #757575)}.fb-table-control__label span{color:var(--sd-error, #ba1a1a)}.fb-table-control__frame{width:100%;max-width:100%;min-width:0;overflow-x:auto;overflow-y:hidden;border:1px solid var(--sd-border, #E6E6E6);border-radius:10px;background:#fff;overscroll-behavior-x:contain}.fb-table-control__header,.fb-table-control__row{display:flex;width:max-content;min-width:max-content}.fb-table-control__header{min-height:44px;background:color-mix(in srgb,var(--sd-surface-muted, #F2F2F2) 70%,#fff);border-bottom:1px solid var(--sd-border, #E6E6E6)}.fb-table-control__row{min-height:42px;border-bottom:1px solid var(--sd-border, #E6E6E6)}.fb-table-control__row:last-child{border-bottom:0}.fb-table-control__cell{width:136px;flex:0 0 auto;min-width:96px;display:inline-flex;align-items:center;gap:7px;padding:10px 14px;color:var(--sd-text-secondary, #757575);font-size:13px;line-height:18px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;border-right:0}.fb-table-control__cell:last-child{border-right:0}.fb-table-control__header .fb-table-control__cell{color:var(--sd-text-secondary, #757575);font-weight:700}.fb-table-control__icon{width:16px;height:16px;flex:0 0 16px;display:inline-flex;align-items:center;justify-content:center;color:var(--sd-text-secondary, #757575);font-family:Material Icons Outlined;font-size:16px;line-height:1;letter-spacing:normal;text-transform:none;white-space:nowrap;font-feature-settings:\"liga\";-webkit-font-feature-settings:\"liga\";-webkit-font-smoothing:antialiased}.fb-table-control__required{color:var(--sd-error, #ba1a1a)}.fb-table-control__add{align-self:flex-start;height:28px;border:0;background:transparent;color:var(--sd-primary, #005cbb);display:inline-flex;align-items:center;gap:8px;padding:0 6px;font-size:14px;font-weight:600;cursor:default}.fb-table-control__add span{font-size:18px;line-height:1}.fb-table-control.is-viewed .fb-table-control__header{background:var(--sd-surface-muted, #F2F2F2)}.fb-table-control.is-viewed .fb-table-control__add{display:none}\n"] }]
|
|
5571
5571
|
}], ctorParameters: () => [{ type: i0.ChangeDetectorRef }, { type: BuilderService }], propDecorators: { _component: [{
|
|
5572
5572
|
type: Input,
|
|
5573
5573
|
args: [{ alias: 'component', required: true }]
|
|
@@ -5616,12 +5616,15 @@ class GroupAttribute {
|
|
|
5616
5616
|
symbolForChild = (c) => {
|
|
5617
5617
|
return this.componentIcons[c.type]?.symbol ?? 'help';
|
|
5618
5618
|
};
|
|
5619
|
+
labelForChild = (c) => {
|
|
5620
|
+
return 'label' in c ? c.label : c.key || c.id;
|
|
5621
|
+
};
|
|
5619
5622
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: GroupAttribute, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
5620
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: GroupAttribute, isStandalone: true, selector: "group-attribute", inputs: { group: { classPropertyName: "group", publicName: "group", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "@let _group = group();\n\n<details class=\"fb-section\" open>\n <summary class=\"fb-section__head\">\n <span class=\"msi\">info</span>\n <span class=\"fb-section__title\">{{ 'core.component.form-builder.general' | translate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-input [form]=\"form\" [(model)]=\"_group.label\" [label]=\"'core.component.form-builder.label' | translate\"></attribute-input>\n\n <div class=\"fb-field\">\n <div class=\"fb-field__label\">{{ 'core.component.form-builder.group-icon' | translate }}</div>\n <div class=\"fb-icon-picker\">\n @for (icon of iconPresets; track icon) {\n <button type=\"button\" class=\"fb-icon-picker__btn\"\n [class.is-active]=\"_group.properties.icon === icon\"\n (click)=\"pickIcon(icon)\">\n <span class=\"msi\">{{ icon }}</span>\n </button>\n }\n </div>\n </div>\n\n <div class=\"fb-field\">\n <div class=\"fb-field__label\">{{ 'core.component.form-builder.group-accent-color' | translate }}</div>\n <div class=\"fb-color-swatches\">\n @for (color of colorPresets; track color) {\n <span class=\"fb-color-swatch\"\n [class]=\"color\"\n [class.is-active]=\"_group.properties.color === color\"\n (click)=\"pickColor(color)\"\n [attr.aria-label]=\"color\"></span>\n }\n </div>\n </div>\n </div>\n</details>\n\n<details class=\"fb-section\" open>\n <summary class=\"fb-section__head\">\n <span class=\"msi\">account_tree</span>\n <span class=\"fb-section__title\">{{ 'core.component.form-builder.group-children' | translate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n @if (!_group.components?.length) {\n <div class=\"fb-children-empty\">{{ 'core.component.form-builder.group-no-children' | translate }}</div>\n } @else {\n <div class=\"fb-children-list\">\n @for (child of _group.components; track child.id) {\n <div class=\"fb-child-row\">\n <span class=\"fb-child-row__icon\"><span class=\"msi\">{{ symbolForChild(child) }}</span></span>\n <span class=\"fb-child-row__label\">{{ child
|
|
5623
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: GroupAttribute, isStandalone: true, selector: "group-attribute", inputs: { group: { classPropertyName: "group", publicName: "group", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "@let _group = group();\n\n<details class=\"fb-section\" open>\n <summary class=\"fb-section__head\">\n <span class=\"msi\">info</span>\n <span class=\"fb-section__title\">{{ 'core.component.form-builder.general' | translate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-input [form]=\"form\" [(model)]=\"_group.label\" [label]=\"'core.component.form-builder.label' | translate\"></attribute-input>\n\n <div class=\"fb-field\">\n <div class=\"fb-field__label\">{{ 'core.component.form-builder.group-icon' | translate }}</div>\n <div class=\"fb-icon-picker\">\n @for (icon of iconPresets; track icon) {\n <button type=\"button\" class=\"fb-icon-picker__btn\"\n [class.is-active]=\"_group.properties.icon === icon\"\n (click)=\"pickIcon(icon)\">\n <span class=\"msi\">{{ icon }}</span>\n </button>\n }\n </div>\n </div>\n\n <div class=\"fb-field\">\n <div class=\"fb-field__label\">{{ 'core.component.form-builder.group-accent-color' | translate }}</div>\n <div class=\"fb-color-swatches\">\n @for (color of colorPresets; track color) {\n <span class=\"fb-color-swatch\"\n [class]=\"color\"\n [class.is-active]=\"_group.properties.color === color\"\n (click)=\"pickColor(color)\"\n [attr.aria-label]=\"color\"></span>\n }\n </div>\n </div>\n </div>\n</details>\n\n<details class=\"fb-section\" open>\n <summary class=\"fb-section__head\">\n <span class=\"msi\">account_tree</span>\n <span class=\"fb-section__title\">{{ 'core.component.form-builder.group-children' | translate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n @if (!_group.components?.length) {\n <div class=\"fb-children-empty\">{{ 'core.component.form-builder.group-no-children' | translate }}</div>\n } @else {\n <div class=\"fb-children-list\">\n @for (child of _group.components; track child.id) {\n <div class=\"fb-child-row\">\n <span class=\"fb-child-row__icon\"><span class=\"msi\">{{ symbolForChild(child) }}</span></span>\n <span class=\"fb-child-row__label\">{{ labelForChild(child) }}</span>\n <span class=\"fb-child-row__key\">{{ child.key }}</span>\n </div>\n }\n </div>\n }\n </div>\n</details>\n\n<details class=\"fb-section\" open>\n <summary class=\"fb-section__head\">\n <span class=\"msi\">visibility</span>\n <span class=\"fb-section__title\">{{ 'core.component.form-builder.display' | translate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-expression\n [components]=\"[]\"\n [variables]=\"[]\"\n [(model)]=\"_group.properties.visibleWhenExpression\"\n [label]=\"'core.component.form-builder.visible-when' | translate\">\n </attribute-expression>\n <attribute-expression\n [components]=\"[]\"\n [variables]=\"[]\"\n [(model)]=\"_group.properties.hiddenWhenExpression\"\n [label]=\"'core.component.form-builder.hidden-when' | translate\">\n </attribute-expression>\n </div>\n</details>\n", styles: [":host{display:block}.fb-children-empty{font-size:12px;color:var(--md-sys-color-on-surface-variant);font-style:italic;padding:8px 0}\n"], dependencies: [{ kind: "component", type: AttributeInput, selector: "attribute-input", inputs: ["form", "label", "disabled", "model"], outputs: ["modelChange", "sdChange"] }, { kind: "component", type: AttributeExpression, selector: "attribute-expression", inputs: ["components", "variables", "label", "model"], outputs: ["modelChange", "sdChange"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
5621
5624
|
}
|
|
5622
5625
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: GroupAttribute, decorators: [{
|
|
5623
5626
|
type: Component,
|
|
5624
|
-
args: [{ selector: 'group-attribute', changeDetection: ChangeDetectionStrategy.OnPush, imports: [AttributeInput, AttributeExpression, TranslatePipe], template: "@let _group = group();\n\n<details class=\"fb-section\" open>\n <summary class=\"fb-section__head\">\n <span class=\"msi\">info</span>\n <span class=\"fb-section__title\">{{ 'core.component.form-builder.general' | translate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-input [form]=\"form\" [(model)]=\"_group.label\" [label]=\"'core.component.form-builder.label' | translate\"></attribute-input>\n\n <div class=\"fb-field\">\n <div class=\"fb-field__label\">{{ 'core.component.form-builder.group-icon' | translate }}</div>\n <div class=\"fb-icon-picker\">\n @for (icon of iconPresets; track icon) {\n <button type=\"button\" class=\"fb-icon-picker__btn\"\n [class.is-active]=\"_group.properties.icon === icon\"\n (click)=\"pickIcon(icon)\">\n <span class=\"msi\">{{ icon }}</span>\n </button>\n }\n </div>\n </div>\n\n <div class=\"fb-field\">\n <div class=\"fb-field__label\">{{ 'core.component.form-builder.group-accent-color' | translate }}</div>\n <div class=\"fb-color-swatches\">\n @for (color of colorPresets; track color) {\n <span class=\"fb-color-swatch\"\n [class]=\"color\"\n [class.is-active]=\"_group.properties.color === color\"\n (click)=\"pickColor(color)\"\n [attr.aria-label]=\"color\"></span>\n }\n </div>\n </div>\n </div>\n</details>\n\n<details class=\"fb-section\" open>\n <summary class=\"fb-section__head\">\n <span class=\"msi\">account_tree</span>\n <span class=\"fb-section__title\">{{ 'core.component.form-builder.group-children' | translate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n @if (!_group.components?.length) {\n <div class=\"fb-children-empty\">{{ 'core.component.form-builder.group-no-children' | translate }}</div>\n } @else {\n <div class=\"fb-children-list\">\n @for (child of _group.components; track child.id) {\n <div class=\"fb-child-row\">\n <span class=\"fb-child-row__icon\"><span class=\"msi\">{{ symbolForChild(child) }}</span></span>\n <span class=\"fb-child-row__label\">{{ child
|
|
5627
|
+
args: [{ selector: 'group-attribute', changeDetection: ChangeDetectionStrategy.OnPush, imports: [AttributeInput, AttributeExpression, TranslatePipe], template: "@let _group = group();\n\n<details class=\"fb-section\" open>\n <summary class=\"fb-section__head\">\n <span class=\"msi\">info</span>\n <span class=\"fb-section__title\">{{ 'core.component.form-builder.general' | translate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-input [form]=\"form\" [(model)]=\"_group.label\" [label]=\"'core.component.form-builder.label' | translate\"></attribute-input>\n\n <div class=\"fb-field\">\n <div class=\"fb-field__label\">{{ 'core.component.form-builder.group-icon' | translate }}</div>\n <div class=\"fb-icon-picker\">\n @for (icon of iconPresets; track icon) {\n <button type=\"button\" class=\"fb-icon-picker__btn\"\n [class.is-active]=\"_group.properties.icon === icon\"\n (click)=\"pickIcon(icon)\">\n <span class=\"msi\">{{ icon }}</span>\n </button>\n }\n </div>\n </div>\n\n <div class=\"fb-field\">\n <div class=\"fb-field__label\">{{ 'core.component.form-builder.group-accent-color' | translate }}</div>\n <div class=\"fb-color-swatches\">\n @for (color of colorPresets; track color) {\n <span class=\"fb-color-swatch\"\n [class]=\"color\"\n [class.is-active]=\"_group.properties.color === color\"\n (click)=\"pickColor(color)\"\n [attr.aria-label]=\"color\"></span>\n }\n </div>\n </div>\n </div>\n</details>\n\n<details class=\"fb-section\" open>\n <summary class=\"fb-section__head\">\n <span class=\"msi\">account_tree</span>\n <span class=\"fb-section__title\">{{ 'core.component.form-builder.group-children' | translate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n @if (!_group.components?.length) {\n <div class=\"fb-children-empty\">{{ 'core.component.form-builder.group-no-children' | translate }}</div>\n } @else {\n <div class=\"fb-children-list\">\n @for (child of _group.components; track child.id) {\n <div class=\"fb-child-row\">\n <span class=\"fb-child-row__icon\"><span class=\"msi\">{{ symbolForChild(child) }}</span></span>\n <span class=\"fb-child-row__label\">{{ labelForChild(child) }}</span>\n <span class=\"fb-child-row__key\">{{ child.key }}</span>\n </div>\n }\n </div>\n }\n </div>\n</details>\n\n<details class=\"fb-section\" open>\n <summary class=\"fb-section__head\">\n <span class=\"msi\">visibility</span>\n <span class=\"fb-section__title\">{{ 'core.component.form-builder.display' | translate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-expression\n [components]=\"[]\"\n [variables]=\"[]\"\n [(model)]=\"_group.properties.visibleWhenExpression\"\n [label]=\"'core.component.form-builder.visible-when' | translate\">\n </attribute-expression>\n <attribute-expression\n [components]=\"[]\"\n [variables]=\"[]\"\n [(model)]=\"_group.properties.hiddenWhenExpression\"\n [label]=\"'core.component.form-builder.hidden-when' | translate\">\n </attribute-expression>\n </div>\n</details>\n", styles: [":host{display:block}.fb-children-empty{font-size:12px;color:var(--md-sys-color-on-surface-variant);font-style:italic;padding:8px 0}\n"] }]
|
|
5625
5628
|
}], ctorParameters: () => [], propDecorators: { group: [{ type: i0.Input, args: [{ isSignal: true, alias: "group", required: true }] }] } });
|
|
5626
5629
|
|
|
5627
5630
|
const ValidationAlerts = [
|
|
@@ -5756,7 +5759,7 @@ class ExpressionBuilderComponent {
|
|
|
5756
5759
|
}
|
|
5757
5760
|
};
|
|
5758
5761
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: ExpressionBuilderComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
5759
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: ExpressionBuilderComponent, isStandalone: true, selector: "expression-builder", inputs: { label: "label", _attributes: ["attributes", "_attributes"], _model: ["model", "_model"] }, outputs: { modelChange: "modelChange", sdChange: "sdChange", edit: "edit" }, viewQueries: [{ propertyName: "modal", first: true, predicate: SdModal, descendants: true }], ngImport: i0, template: "<!--\n FEEL-style expression trigger pill (M3 redesign).\n B\u1EA5m v\u00E0o pill m\u1EDF modal builder hi\u1EC7n t\u1EA1i (onEdit).\n-->\n<div class=\"fb-field\">\n @if (label) {\n <div class=\"fb-field__label\">{{ label }}</div>\n }\n @let exprText = model | expressionQuery;\n <div class=\"fb-expr\" (click)=\"onEdit()\" role=\"button\" tabindex=\"0\">\n <span class=\"fb-expr__badge\">FEEL</span>\n @if (exprText) {\n <span class=\"fb-expr__code mono\">{{ exprText }}</span>\n } @else {\n <span class=\"empty\">{{ 'core.component.form-builder.set-expression' | translate }}</span>\n }\n <button type=\"button\" class=\"fb-icon-btn\" (click)=\"onEdit(); $event.stopPropagation()\" aria-label=\"edit expression\">\n <span class=\"msi\">functions</span>\n </button>\n </div>\n</div>\n@if (expression) {\n <sd-modal [title]=\"'core.component.form-builder.set-expression' | translate\">\n <div class=\"c-builder\">\n <div class=\"d-flex justify-content-end\">\n <sd-button\n prefixIcon=\"calculate\"\n class=\"mr-4\"\n [title]=\"'core.component.form-builder.condition-group' | translate\"\n color=\"primary\"\n (click)=\"addCombinator(expression.conditions)\"></sd-button>\n <sd-button\n prefixIcon=\"add\"\n [title]=\"'core.component.form-builder.condition' | translate\"\n color=\"primary\"\n (click)=\"addCondition(expression.conditions)\"\n type=\"fill\"></sd-button>\n </div>\n <!-- C\u1EA5p 1 -->\n @for (conditionLv1 of expression.conditions; track conditionLv1.key; let idxLv1 = $index) {\n <div class=\"d-flex align-items-end py-8\" style=\"gap: 8px\">\n <div class=\"c-condition\">\n <!-- D\u00F2ng 1 ch\u1EC9 hi\u1EC7n WHERE -->\n @if (idxLv1 === 0) {\n <div class=\"c-where\">WHERE</div>\n }\n <!-- D\u00F2ng 2 hi\u1EC3n th\u1ECB combinator -->\n @if (idxLv1 === 1) {\n <div class=\"c-select\">\n <sd-select\n size=\"sm\"\n [(model)]=\"expression.combinator\"\n [items]=\"[\n { value: '&&', display: 'AND' },\n { value: '||', display: 'OR' }\n ]\"\n valueField=\"value\"\n displayField=\"display\"\n required\n hideInlineError=\"\"></sd-select>\n </div>\n }\n </div>\n @if (conditionLv1.type === 'condition') {\n <sd-autocomplete\n size=\"sm\"\n [form]=\"form\"\n style=\"width: 200px\"\n [(model)]=\"conditionLv1.field\"\n [items]=\"attributes\"\n (sdChange)=\"onChangeAttribute(conditionLv1)\"\n valueField=\"value\"\n displayField=\"display\"\n required\n hideInlineError></sd-autocomplete>\n @let fieldAttribute = attribute[conditionLv1.field!];\n <sd-select\n size=\"sm\"\n [form]=\"form\"\n style=\"width: 100px\"\n [(model)]=\"conditionLv1.operator\"\n [items]=\"attributeOperators[fieldAttribute?.type || 'string']\"\n valueField=\"value\"\n displayField=\"display\"\n [disabled]=\"!fieldAttribute?.type\"\n required\n hideInlineError></sd-select>\n @if (\n !!fieldAttribute?.type && conditionLv1.operator && conditionLv1.operator !== 'NULL' && conditionLv1.operator !== 'NOT_NULL'\n ) {\n @if (fieldAttribute.type === 'string') {\n <sd-input size=\"sm\" [form]=\"form\" style=\"flex: 1\" [(model)]=\"conditionLv1.value\" required hideInlineError></sd-input>\n } @else if (fieldAttribute.type === 'number') {\n <sd-input-number size=\"sm\" [form]=\"form\" style=\"flex: 1\" [(model)]=\"conditionLv1.value\" hideInlineError></sd-input-number>\n <sd-autocomplete\n size=\"sm\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"conditionLv1.value\"\n [items]=\"numberAttributes\"\n valueField=\"value\"\n displayField=\"display\"\n hideInlineError></sd-autocomplete>\n } @else if (fieldAttribute.type === 'datetime' && conditionLv1.dayInfo) {\n <sd-select\n size=\"sm\"\n [placeholder]=\"'core.component.form-builder.time' | translate\"\n [form]=\"form\"\n style=\"width: 150px\"\n [items]=\"types\"\n valueField=\"value\"\n displayField=\"display\"\n [(model)]=\"conditionLv1.dayInfo.type\"\n (sdChange)=\"onChangeDateInfo(conditionLv1)\"\n required\n hideInlineError>\n </sd-select>\n @if (conditionLv1.dayInfo.type === 'RELATED') {\n <sd-input-number\n size=\"sm\"\n [form]=\"form\"\n style=\"width: 80px\"\n [(model)]=\"conditionLv1.dayInfo.relatedValue\"\n (sdChange)=\"onChangeDateInfo(conditionLv1)\"\n [min]=\"1\"\n required\n hideInlineError>\n </sd-input-number>\n <sd-select\n size=\"sm\"\n [form]=\"form\"\n style=\"flex: 1\"\n [items]=\"previouses\"\n valueField=\"value\"\n displayField=\"display\"\n [(model)]=\"conditionLv1.dayInfo.related\"\n (sdChange)=\"onChangeDateInfo(conditionLv1)\"\n required\n hideInlineError>\n </sd-select>\n } @else if (conditionLv1.dayInfo.type === 'DATETIME') {\n <sd-datetime\n size=\"sm\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"conditionLv1.value\"\n (sdChange)=\"onChangeDateInfo(conditionLv1)\"\n required\n hideInlineError>\n </sd-datetime>\n } @else if (conditionLv1.dayInfo.type === 'ATTRIBUTE') {\n <sd-autocomplete\n size=\"sm\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"conditionLv1.value\"\n [items]=\"dateAttributes\"\n valueField=\"value\"\n displayField=\"display\"\n hideInlineError></sd-autocomplete>\n }\n } @else if (fieldAttribute.type === 'boolean') {\n <sd-select\n size=\"sm\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"conditionLv1.value\"\n [items]=\"[\n { value: true, display: fieldAttribute.displayOnTrue },\n { value: false, display: fieldAttribute.displayOnFalse }\n ]\"\n valueField=\"value\"\n displayField=\"display\"\n required\n hideInlineError></sd-select>\n } @else if (fieldAttribute.type === 'values') {\n <sd-autocomplete\n size=\"sm\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"conditionLv1.value\"\n [items]=\"fieldAttribute.values\"\n valueField=\"value\"\n displayField=\"display\"\n required\n hideInlineError></sd-autocomplete>\n }\n }\n <sd-button class=\"ml-auto\" prefixIcon=\"delete\" [tooltip]=\"'core.component.form-builder.delete' | translate\" (click)=\"remove(expression.conditions, idxLv1)\"></sd-button>\n } @else if (conditionLv1.type === 'combinator') {\n <div class=\"c-builder\">\n <div class=\"d-flex justify-content-end\">\n <sd-button class=\"mr-4\" prefixIcon=\"delete\" [tooltip]=\"'core.component.form-builder.delete' | translate\" (click)=\"remove(expression.conditions, idxLv1)\"></sd-button>\n <sd-button\n prefixIcon=\"add\"\n [title]=\"'core.component.form-builder.condition' | translate\"\n size=\"sm\"\n color=\"primary\"\n (click)=\"addCondition(conditionLv1.conditions)\"\n type=\"fill\"></sd-button>\n </div>\n <!-- C\u1EA5p 2 -->\n @for (conditionLv2 of conditionLv1.conditions; track conditionLv2.key; let idxLv2 = $index) {\n <div class=\"d-flex align-items-end py-8\" style=\"gap: 8px\">\n <div class=\"c-condition\">\n <!-- D\u00F2ng 1 ch\u1EC9 hi\u1EC7n WHERE -->\n @if (idxLv2 === 0) {\n <div class=\"c-where\">WHERE</div>\n }\n <!-- D\u00F2ng 2 hi\u1EC3n th\u1ECB combinator -->\n @if (idxLv2 === 1) {\n <div class=\"c-select\">\n <sd-select\n size=\"sm\"\n [(model)]=\"conditionLv1.combinator\"\n [items]=\"[\n { value: '&&', display: 'AND' },\n { value: '||', display: 'OR' }\n ]\"\n valueField=\"value\"\n displayField=\"display\"\n required\n hideInlineError=\"\"></sd-select>\n </div>\n }\n </div>\n @if (conditionLv2.type === 'condition') {\n <sd-autocomplete\n size=\"sm\"\n [form]=\"form\"\n style=\"width: 200px\"\n [(model)]=\"conditionLv2.field\"\n [items]=\"attributes\"\n (sdChange)=\"onChangeAttribute(conditionLv2)\"\n valueField=\"value\"\n displayField=\"display\"\n required\n hideInlineError></sd-autocomplete>\n @let fieldAttribute = attribute[conditionLv2.field!];\n <sd-select\n size=\"sm\"\n [form]=\"form\"\n style=\"width: 100px\"\n [(model)]=\"conditionLv2.operator\"\n [items]=\"attributeOperators[fieldAttribute?.type || 'string']\"\n valueField=\"value\"\n displayField=\"display\"\n [disabled]=\"!fieldAttribute?.type\"\n required\n hideInlineError></sd-select>\n @if (\n !!fieldAttribute?.type &&\n conditionLv2.operator &&\n conditionLv2.operator !== 'NULL' &&\n conditionLv2.operator !== 'NOT_NULL'\n ) {\n @if (fieldAttribute.type === 'string') {\n <sd-input\n size=\"sm\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"conditionLv2.value\"\n required\n hideInlineError></sd-input>\n } @else if (fieldAttribute.type === 'number') {\n <sd-input-number\n size=\"sm\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"conditionLv2.value\"\n hideInlineError></sd-input-number>\n <sd-autocomplete\n size=\"sm\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"conditionLv2.value\"\n [items]=\"numberAttributes\"\n valueField=\"value\"\n displayField=\"display\"\n hideInlineError></sd-autocomplete>\n } @else if (fieldAttribute.type === 'datetime' && conditionLv2.dayInfo) {\n <sd-select\n size=\"sm\"\n [form]=\"form\"\n [placeholder]=\"'core.component.form-builder.time' | translate\"\n style=\"width: 150px\"\n [items]=\"types\"\n valueField=\"value\"\n displayField=\"display\"\n [(model)]=\"conditionLv2.dayInfo.type\"\n (sdChange)=\"onChangeDateInfo(conditionLv2)\"\n required\n hideInlineError>\n </sd-select>\n @if (conditionLv2.dayInfo.type === 'RELATED') {\n <sd-input-number\n size=\"sm\"\n [form]=\"form\"\n style=\"width: 80px\"\n [(model)]=\"conditionLv2.dayInfo.relatedValue\"\n (sdChange)=\"onChangeDateInfo(conditionLv2)\"\n [min]=\"1\"\n required\n hideInlineError>\n </sd-input-number>\n <sd-select\n size=\"sm\"\n [form]=\"form\"\n style=\"flex: 1\"\n [items]=\"previouses\"\n valueField=\"value\"\n displayField=\"display\"\n [(model)]=\"conditionLv2.dayInfo.related\"\n (sdChange)=\"onChangeDateInfo(conditionLv2)\"\n required\n hideInlineError>\n </sd-select>\n } @else if (conditionLv2.dayInfo.type === 'DATETIME') {\n <sd-datetime\n size=\"sm\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"conditionLv2.value\"\n (sdChange)=\"onChangeDateInfo(conditionLv2)\"\n required\n hideInlineError>\n </sd-datetime>\n } @else if (conditionLv2.dayInfo.type === 'ATTRIBUTE') {\n <sd-autocomplete\n size=\"sm\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"conditionLv2.value\"\n [items]=\"dateAttributes\"\n valueField=\"value\"\n displayField=\"display\"\n hideInlineError></sd-autocomplete>\n }\n } @else if (fieldAttribute.type === 'boolean') {\n <sd-select\n size=\"sm\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"conditionLv2.value\"\n [items]=\"[\n { value: true, display: fieldAttribute.displayOnTrue },\n { value: false, display: fieldAttribute.displayOnFalse }\n ]\"\n valueField=\"value\"\n displayField=\"display\"\n required\n hideInlineError></sd-select>\n } @else if (fieldAttribute.type === 'values') {\n <sd-autocomplete\n size=\"sm\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"conditionLv2.value\"\n [items]=\"fieldAttribute.values\"\n valueField=\"value\"\n displayField=\"display\"\n required\n hideInlineError></sd-autocomplete>\n }\n }\n <sd-button\n class=\"ml-auto\"\n prefixIcon=\"delete\"\n [tooltip]=\"'core.component.form-builder.delete' | translate\"\n (click)=\"remove(conditionLv1.conditions, idxLv1)\"></sd-button>\n }\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\n <ng-container sdFooter>\n <sd-button prefixIcon=\"save\" [title]=\"'core.component.form-builder.confirm' | translate\" (click)=\"onAccept()\" color=\"primary\" type=\"fill\"></sd-button>\n </ng-container>\n </sd-modal>\n}\n", styles: [".c-builder{padding:8px;width:100%;border:1px solid #e6e6e6;border-radius:4px}.c-builder .c-condition{width:80px}.c-builder .c-condition .c-where{width:80px;border:1px solid #e6e6e6;background-color:#f2f2f2;color:#757575;border-radius:4px;padding:4px;text-align:center}.c-builder .c-condition .c-select{margin-left:10px;width:70px}\n"], dependencies: [{ kind: "component", type: SdInput$1, selector: "sd-input", inputs: ["autoId", "name", "appearance", "floatLabel", "size", "form", "label", "helperText", "placeholder", "type", "hideInlineError", "blurOnEnter", "required", "readonly", "disabled", "viewed", "minlength", "maxlength", "pattern", "patternErrorMessage", "validator", "inlineError", "hyperlink", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "cleared", "sdFocusForceBlur"] }, { kind: "component", type: SdInputNumber, selector: "sd-input-number", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "hideInlineError", "blurOnEnter", "required", "readonly", "disabled", "viewed", "type", "precision", "format", "min", "max", "validator", "inlineError", "hyperlink", "appearance", "floatLabel", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "cleared", "sdFocusForceBlur"] }, { kind: "component", type: SdSelect$1, selector: "sd-select", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "valueField", "displayField", "disabledField", "cacheChecksum", "limit", "hyperlink", "minWidthPanel", "hideInlineError", "required", "disabled", "viewed", "multiple", "clearable", "validator", "inlineError", "appearance", "floatLabel", "items", "model"], outputs: ["modelChange", "sdChange", "sdSelection"] }, { kind: "component", type: SdAutocomplete$1, selector: "sd-autocomplete", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "valueField", "displayField", "disabledField", "limit", "cacheChecksum", "hyperlink", "items", "hideInlineError", "addable", "required", "disabled", "viewed", "clearable", "validator", "inlineError", "appearance", "model"], outputs: ["modelChange", "sdChange", "sdSelection", "sdAdd"] }, { kind: "component", type: SdButton, selector: "sd-button", inputs: ["autoId", "type", "color", "size", "fontSet", "title", "width", "tooltip", "prefixIcon", "suffixIcon", "disabled", "loading", "block", "htmlType"], outputs: ["click"] }, { kind: "component", type: SdModal, selector: "sd-modal", inputs: ["autoId", "title", "color", "width", "height", "view", "modalClass", "lazyLoadContent", "hideClose", "disableBackdropClose"], outputs: ["sdClosed"] }, { kind: "component", type: SdDatetime, selector: "sd-datetime", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "hideInlineError", "required", "disabled", "viewed", "showSeconds", "clearable", "inlineError", "hyperlink", "appearance", "floatLabel", "min", "minDate", "max", "maxDate", "model"], outputs: ["modelChange", "sdChange", "sdFocus"] }, { kind: "pipe", type: ExpressionQueryPipe, name: "expressionQuery" }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
5762
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: ExpressionBuilderComponent, isStandalone: true, selector: "expression-builder", inputs: { label: "label", _attributes: ["attributes", "_attributes"], _model: ["model", "_model"] }, outputs: { modelChange: "modelChange", sdChange: "sdChange", edit: "edit" }, viewQueries: [{ propertyName: "modal", first: true, predicate: SdModal, descendants: true }], ngImport: i0, template: "<!--\n FEEL-style expression trigger pill (M3 redesign).\n B\u1EA5m v\u00E0o pill m\u1EDF modal builder hi\u1EC7n t\u1EA1i (onEdit).\n-->\n<div class=\"fb-field\">\n @if (label) {\n <div class=\"fb-field__label\">{{ label }}</div>\n }\n @let exprText = model | expressionQuery;\n <div class=\"fb-expr\" (click)=\"onEdit()\" role=\"button\" tabindex=\"0\">\n <span class=\"fb-expr__badge\">FEEL</span>\n @if (exprText) {\n <span class=\"fb-expr__code mono\">{{ exprText }}</span>\n } @else {\n <span class=\"empty\">{{ 'core.component.form-builder.set-expression' | translate }}</span>\n }\n <button type=\"button\" class=\"fb-icon-btn\" (click)=\"onEdit(); $event.stopPropagation()\" aria-label=\"edit expression\">\n <span class=\"msi\">functions</span>\n </button>\n </div>\n</div>\n@if (expression) {\n <sd-modal [title]=\"'core.component.form-builder.set-expression' | translate\" modalClass=\"sd-form-builder-dialog\">\n <div class=\"c-builder\">\n <div class=\"d-flex justify-content-end\">\n <sd-button\n prefixIcon=\"calculate\"\n class=\"mr-4\"\n [title]=\"'core.component.form-builder.condition-group' | translate\"\n color=\"primary\"\n (click)=\"addCombinator(expression.conditions)\"></sd-button>\n <sd-button\n prefixIcon=\"add\"\n [title]=\"'core.component.form-builder.condition' | translate\"\n color=\"primary\"\n (click)=\"addCondition(expression.conditions)\"\n type=\"fill\"></sd-button>\n </div>\n <!-- C\u1EA5p 1 -->\n @for (conditionLv1 of expression.conditions; track conditionLv1.key; let idxLv1 = $index) {\n <div class=\"d-flex align-items-end py-8\" style=\"gap: 8px\">\n <div class=\"c-condition\">\n <!-- D\u00F2ng 1 ch\u1EC9 hi\u1EC7n WHERE -->\n @if (idxLv1 === 0) {\n <div class=\"c-where\">WHERE</div>\n }\n <!-- D\u00F2ng 2 hi\u1EC3n th\u1ECB combinator -->\n @if (idxLv1 === 1) {\n <div class=\"c-select\">\n <sd-select\n size=\"sm\"\n [(model)]=\"expression.combinator\"\n [items]=\"[\n { value: '&&', display: 'AND' },\n { value: '||', display: 'OR' }\n ]\"\n valueField=\"value\"\n displayField=\"display\"\n required\n hideInlineError=\"\"></sd-select>\n </div>\n }\n </div>\n @if (conditionLv1.type === 'condition') {\n <sd-autocomplete\n size=\"sm\"\n [form]=\"form\"\n style=\"width: 200px\"\n [(model)]=\"conditionLv1.field\"\n [items]=\"attributes\"\n (sdChange)=\"onChangeAttribute(conditionLv1)\"\n valueField=\"value\"\n displayField=\"display\"\n required\n hideInlineError></sd-autocomplete>\n @let fieldAttribute = attribute[conditionLv1.field!];\n <sd-select\n size=\"sm\"\n [form]=\"form\"\n style=\"width: 100px\"\n [(model)]=\"conditionLv1.operator\"\n [items]=\"attributeOperators[fieldAttribute?.type || 'string']\"\n valueField=\"value\"\n displayField=\"display\"\n [disabled]=\"!fieldAttribute?.type\"\n required\n hideInlineError></sd-select>\n @if (\n !!fieldAttribute?.type && conditionLv1.operator && conditionLv1.operator !== 'NULL' && conditionLv1.operator !== 'NOT_NULL'\n ) {\n @if (fieldAttribute.type === 'string') {\n <sd-input size=\"sm\" [form]=\"form\" style=\"flex: 1\" [(model)]=\"conditionLv1.value\" required hideInlineError></sd-input>\n } @else if (fieldAttribute.type === 'number') {\n <sd-input-number size=\"sm\" [form]=\"form\" style=\"flex: 1\" [(model)]=\"conditionLv1.value\" hideInlineError></sd-input-number>\n <sd-autocomplete\n size=\"sm\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"conditionLv1.value\"\n [items]=\"numberAttributes\"\n valueField=\"value\"\n displayField=\"display\"\n hideInlineError></sd-autocomplete>\n } @else if (fieldAttribute.type === 'datetime' && conditionLv1.dayInfo) {\n <sd-select\n size=\"sm\"\n [placeholder]=\"'core.component.form-builder.time' | translate\"\n [form]=\"form\"\n style=\"width: 150px\"\n [items]=\"types\"\n valueField=\"value\"\n displayField=\"display\"\n [(model)]=\"conditionLv1.dayInfo.type\"\n (sdChange)=\"onChangeDateInfo(conditionLv1)\"\n required\n hideInlineError>\n </sd-select>\n @if (conditionLv1.dayInfo.type === 'RELATED') {\n <sd-input-number\n size=\"sm\"\n [form]=\"form\"\n style=\"width: 80px\"\n [(model)]=\"conditionLv1.dayInfo.relatedValue\"\n (sdChange)=\"onChangeDateInfo(conditionLv1)\"\n [min]=\"1\"\n required\n hideInlineError>\n </sd-input-number>\n <sd-select\n size=\"sm\"\n [form]=\"form\"\n style=\"flex: 1\"\n [items]=\"previouses\"\n valueField=\"value\"\n displayField=\"display\"\n [(model)]=\"conditionLv1.dayInfo.related\"\n (sdChange)=\"onChangeDateInfo(conditionLv1)\"\n required\n hideInlineError>\n </sd-select>\n } @else if (conditionLv1.dayInfo.type === 'DATETIME') {\n <sd-datetime\n size=\"sm\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"conditionLv1.value\"\n (sdChange)=\"onChangeDateInfo(conditionLv1)\"\n required\n hideInlineError>\n </sd-datetime>\n } @else if (conditionLv1.dayInfo.type === 'ATTRIBUTE') {\n <sd-autocomplete\n size=\"sm\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"conditionLv1.value\"\n [items]=\"dateAttributes\"\n valueField=\"value\"\n displayField=\"display\"\n hideInlineError></sd-autocomplete>\n }\n } @else if (fieldAttribute.type === 'boolean') {\n <sd-select\n size=\"sm\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"conditionLv1.value\"\n [items]=\"[\n { value: true, display: fieldAttribute.displayOnTrue },\n { value: false, display: fieldAttribute.displayOnFalse }\n ]\"\n valueField=\"value\"\n displayField=\"display\"\n required\n hideInlineError></sd-select>\n } @else if (fieldAttribute.type === 'values') {\n <sd-autocomplete\n size=\"sm\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"conditionLv1.value\"\n [items]=\"fieldAttribute.values\"\n valueField=\"value\"\n displayField=\"display\"\n required\n hideInlineError></sd-autocomplete>\n }\n }\n <sd-button class=\"ml-auto\" prefixIcon=\"delete\" [tooltip]=\"'core.component.form-builder.delete' | translate\" (click)=\"remove(expression.conditions, idxLv1)\"></sd-button>\n } @else if (conditionLv1.type === 'combinator') {\n <div class=\"c-builder\">\n <div class=\"d-flex justify-content-end\">\n <sd-button class=\"mr-4\" prefixIcon=\"delete\" [tooltip]=\"'core.component.form-builder.delete' | translate\" (click)=\"remove(expression.conditions, idxLv1)\"></sd-button>\n <sd-button\n prefixIcon=\"add\"\n [title]=\"'core.component.form-builder.condition' | translate\"\n size=\"sm\"\n color=\"primary\"\n (click)=\"addCondition(conditionLv1.conditions)\"\n type=\"fill\"></sd-button>\n </div>\n <!-- C\u1EA5p 2 -->\n @for (conditionLv2 of conditionLv1.conditions; track conditionLv2.key; let idxLv2 = $index) {\n <div class=\"d-flex align-items-end py-8\" style=\"gap: 8px\">\n <div class=\"c-condition\">\n <!-- D\u00F2ng 1 ch\u1EC9 hi\u1EC7n WHERE -->\n @if (idxLv2 === 0) {\n <div class=\"c-where\">WHERE</div>\n }\n <!-- D\u00F2ng 2 hi\u1EC3n th\u1ECB combinator -->\n @if (idxLv2 === 1) {\n <div class=\"c-select\">\n <sd-select\n size=\"sm\"\n [(model)]=\"conditionLv1.combinator\"\n [items]=\"[\n { value: '&&', display: 'AND' },\n { value: '||', display: 'OR' }\n ]\"\n valueField=\"value\"\n displayField=\"display\"\n required\n hideInlineError=\"\"></sd-select>\n </div>\n }\n </div>\n @if (conditionLv2.type === 'condition') {\n <sd-autocomplete\n size=\"sm\"\n [form]=\"form\"\n style=\"width: 200px\"\n [(model)]=\"conditionLv2.field\"\n [items]=\"attributes\"\n (sdChange)=\"onChangeAttribute(conditionLv2)\"\n valueField=\"value\"\n displayField=\"display\"\n required\n hideInlineError></sd-autocomplete>\n @let fieldAttribute = attribute[conditionLv2.field!];\n <sd-select\n size=\"sm\"\n [form]=\"form\"\n style=\"width: 100px\"\n [(model)]=\"conditionLv2.operator\"\n [items]=\"attributeOperators[fieldAttribute?.type || 'string']\"\n valueField=\"value\"\n displayField=\"display\"\n [disabled]=\"!fieldAttribute?.type\"\n required\n hideInlineError></sd-select>\n @if (\n !!fieldAttribute?.type &&\n conditionLv2.operator &&\n conditionLv2.operator !== 'NULL' &&\n conditionLv2.operator !== 'NOT_NULL'\n ) {\n @if (fieldAttribute.type === 'string') {\n <sd-input\n size=\"sm\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"conditionLv2.value\"\n required\n hideInlineError></sd-input>\n } @else if (fieldAttribute.type === 'number') {\n <sd-input-number\n size=\"sm\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"conditionLv2.value\"\n hideInlineError></sd-input-number>\n <sd-autocomplete\n size=\"sm\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"conditionLv2.value\"\n [items]=\"numberAttributes\"\n valueField=\"value\"\n displayField=\"display\"\n hideInlineError></sd-autocomplete>\n } @else if (fieldAttribute.type === 'datetime' && conditionLv2.dayInfo) {\n <sd-select\n size=\"sm\"\n [form]=\"form\"\n [placeholder]=\"'core.component.form-builder.time' | translate\"\n style=\"width: 150px\"\n [items]=\"types\"\n valueField=\"value\"\n displayField=\"display\"\n [(model)]=\"conditionLv2.dayInfo.type\"\n (sdChange)=\"onChangeDateInfo(conditionLv2)\"\n required\n hideInlineError>\n </sd-select>\n @if (conditionLv2.dayInfo.type === 'RELATED') {\n <sd-input-number\n size=\"sm\"\n [form]=\"form\"\n style=\"width: 80px\"\n [(model)]=\"conditionLv2.dayInfo.relatedValue\"\n (sdChange)=\"onChangeDateInfo(conditionLv2)\"\n [min]=\"1\"\n required\n hideInlineError>\n </sd-input-number>\n <sd-select\n size=\"sm\"\n [form]=\"form\"\n style=\"flex: 1\"\n [items]=\"previouses\"\n valueField=\"value\"\n displayField=\"display\"\n [(model)]=\"conditionLv2.dayInfo.related\"\n (sdChange)=\"onChangeDateInfo(conditionLv2)\"\n required\n hideInlineError>\n </sd-select>\n } @else if (conditionLv2.dayInfo.type === 'DATETIME') {\n <sd-datetime\n size=\"sm\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"conditionLv2.value\"\n (sdChange)=\"onChangeDateInfo(conditionLv2)\"\n required\n hideInlineError>\n </sd-datetime>\n } @else if (conditionLv2.dayInfo.type === 'ATTRIBUTE') {\n <sd-autocomplete\n size=\"sm\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"conditionLv2.value\"\n [items]=\"dateAttributes\"\n valueField=\"value\"\n displayField=\"display\"\n hideInlineError></sd-autocomplete>\n }\n } @else if (fieldAttribute.type === 'boolean') {\n <sd-select\n size=\"sm\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"conditionLv2.value\"\n [items]=\"[\n { value: true, display: fieldAttribute.displayOnTrue },\n { value: false, display: fieldAttribute.displayOnFalse }\n ]\"\n valueField=\"value\"\n displayField=\"display\"\n required\n hideInlineError></sd-select>\n } @else if (fieldAttribute.type === 'values') {\n <sd-autocomplete\n size=\"sm\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"conditionLv2.value\"\n [items]=\"fieldAttribute.values\"\n valueField=\"value\"\n displayField=\"display\"\n required\n hideInlineError></sd-autocomplete>\n }\n }\n <sd-button\n class=\"ml-auto\"\n prefixIcon=\"delete\"\n [tooltip]=\"'core.component.form-builder.delete' | translate\"\n (click)=\"remove(conditionLv1.conditions, idxLv1)\"></sd-button>\n }\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\n <ng-container sdFooterRight>\n <sd-button prefixIcon=\"save\" [title]=\"'core.component.form-builder.confirm' | translate\" (click)=\"onAccept()\" color=\"primary\" type=\"fill\"></sd-button>\n </ng-container>\n </sd-modal>\n}\n", styles: [".c-builder{padding:8px;width:100%;border:1px solid #e6e6e6;border-radius:4px}.c-builder .c-condition{width:80px}.c-builder .c-condition .c-where{width:80px;border:1px solid #e6e6e6;background-color:#f2f2f2;color:#757575;border-radius:4px;padding:4px;text-align:center}.c-builder .c-condition .c-select{margin-left:10px;width:70px}\n"], dependencies: [{ kind: "component", type: SdInput$1, selector: "sd-input", inputs: ["autoId", "name", "appearance", "floatLabel", "size", "form", "label", "helperText", "placeholder", "type", "hideInlineError", "blurOnEnter", "required", "readonly", "disabled", "viewed", "minlength", "maxlength", "pattern", "patternErrorMessage", "validator", "inlineError", "hyperlink", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "cleared", "sdFocusForceBlur"] }, { kind: "component", type: SdInputNumber, selector: "sd-input-number", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "hideInlineError", "blurOnEnter", "required", "readonly", "disabled", "viewed", "type", "precision", "format", "min", "max", "validator", "inlineError", "hyperlink", "appearance", "floatLabel", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "cleared", "sdFocusForceBlur"] }, { kind: "component", type: SdSelect$1, selector: "sd-select", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "valueField", "displayField", "disabledField", "cacheChecksum", "limit", "hyperlink", "minWidthPanel", "hideInlineError", "required", "disabled", "viewed", "multiple", "clearable", "validator", "inlineError", "appearance", "floatLabel", "items", "model"], outputs: ["modelChange", "sdChange", "sdSelection"] }, { kind: "component", type: SdAutocomplete$1, selector: "sd-autocomplete", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "valueField", "displayField", "disabledField", "limit", "cacheChecksum", "hyperlink", "items", "hideInlineError", "addable", "required", "disabled", "viewed", "clearable", "validator", "inlineError", "appearance", "model"], outputs: ["modelChange", "sdChange", "sdSelection", "sdAdd"] }, { kind: "component", type: SdButton, selector: "sd-button", inputs: ["autoId", "type", "color", "size", "fontSet", "title", "width", "tooltip", "prefixIcon", "suffixIcon", "disabled", "loading", "block", "htmlType"], outputs: ["click"] }, { kind: "component", type: SdModal, selector: "sd-modal", inputs: ["autoId", "title", "color", "width", "height", "view", "modalClass", "lazyLoadContent", "hideClose", "disableBackdropClose"], outputs: ["sdClosed"] }, { kind: "component", type: SdDatetime, selector: "sd-datetime", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "hideInlineError", "required", "disabled", "viewed", "showSeconds", "clearable", "inlineError", "hyperlink", "appearance", "floatLabel", "min", "minDate", "max", "maxDate", "model"], outputs: ["modelChange", "sdChange", "sdFocus"] }, { kind: "pipe", type: ExpressionQueryPipe, name: "expressionQuery" }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
5760
5763
|
}
|
|
5761
5764
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: ExpressionBuilderComponent, decorators: [{
|
|
5762
5765
|
type: Component,
|
|
@@ -5772,7 +5775,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
5772
5775
|
SdDatetime,
|
|
5773
5776
|
SdSuffixDefDirective,
|
|
5774
5777
|
TranslatePipe,
|
|
5775
|
-
], template: "<!--\n FEEL-style expression trigger pill (M3 redesign).\n B\u1EA5m v\u00E0o pill m\u1EDF modal builder hi\u1EC7n t\u1EA1i (onEdit).\n-->\n<div class=\"fb-field\">\n @if (label) {\n <div class=\"fb-field__label\">{{ label }}</div>\n }\n @let exprText = model | expressionQuery;\n <div class=\"fb-expr\" (click)=\"onEdit()\" role=\"button\" tabindex=\"0\">\n <span class=\"fb-expr__badge\">FEEL</span>\n @if (exprText) {\n <span class=\"fb-expr__code mono\">{{ exprText }}</span>\n } @else {\n <span class=\"empty\">{{ 'core.component.form-builder.set-expression' | translate }}</span>\n }\n <button type=\"button\" class=\"fb-icon-btn\" (click)=\"onEdit(); $event.stopPropagation()\" aria-label=\"edit expression\">\n <span class=\"msi\">functions</span>\n </button>\n </div>\n</div>\n@if (expression) {\n <sd-modal [title]=\"'core.component.form-builder.set-expression' | translate\">\n <div class=\"c-builder\">\n <div class=\"d-flex justify-content-end\">\n <sd-button\n prefixIcon=\"calculate\"\n class=\"mr-4\"\n [title]=\"'core.component.form-builder.condition-group' | translate\"\n color=\"primary\"\n (click)=\"addCombinator(expression.conditions)\"></sd-button>\n <sd-button\n prefixIcon=\"add\"\n [title]=\"'core.component.form-builder.condition' | translate\"\n color=\"primary\"\n (click)=\"addCondition(expression.conditions)\"\n type=\"fill\"></sd-button>\n </div>\n <!-- C\u1EA5p 1 -->\n @for (conditionLv1 of expression.conditions; track conditionLv1.key; let idxLv1 = $index) {\n <div class=\"d-flex align-items-end py-8\" style=\"gap: 8px\">\n <div class=\"c-condition\">\n <!-- D\u00F2ng 1 ch\u1EC9 hi\u1EC7n WHERE -->\n @if (idxLv1 === 0) {\n <div class=\"c-where\">WHERE</div>\n }\n <!-- D\u00F2ng 2 hi\u1EC3n th\u1ECB combinator -->\n @if (idxLv1 === 1) {\n <div class=\"c-select\">\n <sd-select\n size=\"sm\"\n [(model)]=\"expression.combinator\"\n [items]=\"[\n { value: '&&', display: 'AND' },\n { value: '||', display: 'OR' }\n ]\"\n valueField=\"value\"\n displayField=\"display\"\n required\n hideInlineError=\"\"></sd-select>\n </div>\n }\n </div>\n @if (conditionLv1.type === 'condition') {\n <sd-autocomplete\n size=\"sm\"\n [form]=\"form\"\n style=\"width: 200px\"\n [(model)]=\"conditionLv1.field\"\n [items]=\"attributes\"\n (sdChange)=\"onChangeAttribute(conditionLv1)\"\n valueField=\"value\"\n displayField=\"display\"\n required\n hideInlineError></sd-autocomplete>\n @let fieldAttribute = attribute[conditionLv1.field!];\n <sd-select\n size=\"sm\"\n [form]=\"form\"\n style=\"width: 100px\"\n [(model)]=\"conditionLv1.operator\"\n [items]=\"attributeOperators[fieldAttribute?.type || 'string']\"\n valueField=\"value\"\n displayField=\"display\"\n [disabled]=\"!fieldAttribute?.type\"\n required\n hideInlineError></sd-select>\n @if (\n !!fieldAttribute?.type && conditionLv1.operator && conditionLv1.operator !== 'NULL' && conditionLv1.operator !== 'NOT_NULL'\n ) {\n @if (fieldAttribute.type === 'string') {\n <sd-input size=\"sm\" [form]=\"form\" style=\"flex: 1\" [(model)]=\"conditionLv1.value\" required hideInlineError></sd-input>\n } @else if (fieldAttribute.type === 'number') {\n <sd-input-number size=\"sm\" [form]=\"form\" style=\"flex: 1\" [(model)]=\"conditionLv1.value\" hideInlineError></sd-input-number>\n <sd-autocomplete\n size=\"sm\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"conditionLv1.value\"\n [items]=\"numberAttributes\"\n valueField=\"value\"\n displayField=\"display\"\n hideInlineError></sd-autocomplete>\n } @else if (fieldAttribute.type === 'datetime' && conditionLv1.dayInfo) {\n <sd-select\n size=\"sm\"\n [placeholder]=\"'core.component.form-builder.time' | translate\"\n [form]=\"form\"\n style=\"width: 150px\"\n [items]=\"types\"\n valueField=\"value\"\n displayField=\"display\"\n [(model)]=\"conditionLv1.dayInfo.type\"\n (sdChange)=\"onChangeDateInfo(conditionLv1)\"\n required\n hideInlineError>\n </sd-select>\n @if (conditionLv1.dayInfo.type === 'RELATED') {\n <sd-input-number\n size=\"sm\"\n [form]=\"form\"\n style=\"width: 80px\"\n [(model)]=\"conditionLv1.dayInfo.relatedValue\"\n (sdChange)=\"onChangeDateInfo(conditionLv1)\"\n [min]=\"1\"\n required\n hideInlineError>\n </sd-input-number>\n <sd-select\n size=\"sm\"\n [form]=\"form\"\n style=\"flex: 1\"\n [items]=\"previouses\"\n valueField=\"value\"\n displayField=\"display\"\n [(model)]=\"conditionLv1.dayInfo.related\"\n (sdChange)=\"onChangeDateInfo(conditionLv1)\"\n required\n hideInlineError>\n </sd-select>\n } @else if (conditionLv1.dayInfo.type === 'DATETIME') {\n <sd-datetime\n size=\"sm\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"conditionLv1.value\"\n (sdChange)=\"onChangeDateInfo(conditionLv1)\"\n required\n hideInlineError>\n </sd-datetime>\n } @else if (conditionLv1.dayInfo.type === 'ATTRIBUTE') {\n <sd-autocomplete\n size=\"sm\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"conditionLv1.value\"\n [items]=\"dateAttributes\"\n valueField=\"value\"\n displayField=\"display\"\n hideInlineError></sd-autocomplete>\n }\n } @else if (fieldAttribute.type === 'boolean') {\n <sd-select\n size=\"sm\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"conditionLv1.value\"\n [items]=\"[\n { value: true, display: fieldAttribute.displayOnTrue },\n { value: false, display: fieldAttribute.displayOnFalse }\n ]\"\n valueField=\"value\"\n displayField=\"display\"\n required\n hideInlineError></sd-select>\n } @else if (fieldAttribute.type === 'values') {\n <sd-autocomplete\n size=\"sm\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"conditionLv1.value\"\n [items]=\"fieldAttribute.values\"\n valueField=\"value\"\n displayField=\"display\"\n required\n hideInlineError></sd-autocomplete>\n }\n }\n <sd-button class=\"ml-auto\" prefixIcon=\"delete\" [tooltip]=\"'core.component.form-builder.delete' | translate\" (click)=\"remove(expression.conditions, idxLv1)\"></sd-button>\n } @else if (conditionLv1.type === 'combinator') {\n <div class=\"c-builder\">\n <div class=\"d-flex justify-content-end\">\n <sd-button class=\"mr-4\" prefixIcon=\"delete\" [tooltip]=\"'core.component.form-builder.delete' | translate\" (click)=\"remove(expression.conditions, idxLv1)\"></sd-button>\n <sd-button\n prefixIcon=\"add\"\n [title]=\"'core.component.form-builder.condition' | translate\"\n size=\"sm\"\n color=\"primary\"\n (click)=\"addCondition(conditionLv1.conditions)\"\n type=\"fill\"></sd-button>\n </div>\n <!-- C\u1EA5p 2 -->\n @for (conditionLv2 of conditionLv1.conditions; track conditionLv2.key; let idxLv2 = $index) {\n <div class=\"d-flex align-items-end py-8\" style=\"gap: 8px\">\n <div class=\"c-condition\">\n <!-- D\u00F2ng 1 ch\u1EC9 hi\u1EC7n WHERE -->\n @if (idxLv2 === 0) {\n <div class=\"c-where\">WHERE</div>\n }\n <!-- D\u00F2ng 2 hi\u1EC3n th\u1ECB combinator -->\n @if (idxLv2 === 1) {\n <div class=\"c-select\">\n <sd-select\n size=\"sm\"\n [(model)]=\"conditionLv1.combinator\"\n [items]=\"[\n { value: '&&', display: 'AND' },\n { value: '||', display: 'OR' }\n ]\"\n valueField=\"value\"\n displayField=\"display\"\n required\n hideInlineError=\"\"></sd-select>\n </div>\n }\n </div>\n @if (conditionLv2.type === 'condition') {\n <sd-autocomplete\n size=\"sm\"\n [form]=\"form\"\n style=\"width: 200px\"\n [(model)]=\"conditionLv2.field\"\n [items]=\"attributes\"\n (sdChange)=\"onChangeAttribute(conditionLv2)\"\n valueField=\"value\"\n displayField=\"display\"\n required\n hideInlineError></sd-autocomplete>\n @let fieldAttribute = attribute[conditionLv2.field!];\n <sd-select\n size=\"sm\"\n [form]=\"form\"\n style=\"width: 100px\"\n [(model)]=\"conditionLv2.operator\"\n [items]=\"attributeOperators[fieldAttribute?.type || 'string']\"\n valueField=\"value\"\n displayField=\"display\"\n [disabled]=\"!fieldAttribute?.type\"\n required\n hideInlineError></sd-select>\n @if (\n !!fieldAttribute?.type &&\n conditionLv2.operator &&\n conditionLv2.operator !== 'NULL' &&\n conditionLv2.operator !== 'NOT_NULL'\n ) {\n @if (fieldAttribute.type === 'string') {\n <sd-input\n size=\"sm\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"conditionLv2.value\"\n required\n hideInlineError></sd-input>\n } @else if (fieldAttribute.type === 'number') {\n <sd-input-number\n size=\"sm\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"conditionLv2.value\"\n hideInlineError></sd-input-number>\n <sd-autocomplete\n size=\"sm\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"conditionLv2.value\"\n [items]=\"numberAttributes\"\n valueField=\"value\"\n displayField=\"display\"\n hideInlineError></sd-autocomplete>\n } @else if (fieldAttribute.type === 'datetime' && conditionLv2.dayInfo) {\n <sd-select\n size=\"sm\"\n [form]=\"form\"\n [placeholder]=\"'core.component.form-builder.time' | translate\"\n style=\"width: 150px\"\n [items]=\"types\"\n valueField=\"value\"\n displayField=\"display\"\n [(model)]=\"conditionLv2.dayInfo.type\"\n (sdChange)=\"onChangeDateInfo(conditionLv2)\"\n required\n hideInlineError>\n </sd-select>\n @if (conditionLv2.dayInfo.type === 'RELATED') {\n <sd-input-number\n size=\"sm\"\n [form]=\"form\"\n style=\"width: 80px\"\n [(model)]=\"conditionLv2.dayInfo.relatedValue\"\n (sdChange)=\"onChangeDateInfo(conditionLv2)\"\n [min]=\"1\"\n required\n hideInlineError>\n </sd-input-number>\n <sd-select\n size=\"sm\"\n [form]=\"form\"\n style=\"flex: 1\"\n [items]=\"previouses\"\n valueField=\"value\"\n displayField=\"display\"\n [(model)]=\"conditionLv2.dayInfo.related\"\n (sdChange)=\"onChangeDateInfo(conditionLv2)\"\n required\n hideInlineError>\n </sd-select>\n } @else if (conditionLv2.dayInfo.type === 'DATETIME') {\n <sd-datetime\n size=\"sm\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"conditionLv2.value\"\n (sdChange)=\"onChangeDateInfo(conditionLv2)\"\n required\n hideInlineError>\n </sd-datetime>\n } @else if (conditionLv2.dayInfo.type === 'ATTRIBUTE') {\n <sd-autocomplete\n size=\"sm\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"conditionLv2.value\"\n [items]=\"dateAttributes\"\n valueField=\"value\"\n displayField=\"display\"\n hideInlineError></sd-autocomplete>\n }\n } @else if (fieldAttribute.type === 'boolean') {\n <sd-select\n size=\"sm\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"conditionLv2.value\"\n [items]=\"[\n { value: true, display: fieldAttribute.displayOnTrue },\n { value: false, display: fieldAttribute.displayOnFalse }\n ]\"\n valueField=\"value\"\n displayField=\"display\"\n required\n hideInlineError></sd-select>\n } @else if (fieldAttribute.type === 'values') {\n <sd-autocomplete\n size=\"sm\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"conditionLv2.value\"\n [items]=\"fieldAttribute.values\"\n valueField=\"value\"\n displayField=\"display\"\n required\n hideInlineError></sd-autocomplete>\n }\n }\n <sd-button\n class=\"ml-auto\"\n prefixIcon=\"delete\"\n [tooltip]=\"'core.component.form-builder.delete' | translate\"\n (click)=\"remove(conditionLv1.conditions, idxLv1)\"></sd-button>\n }\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\n <ng-container sdFooter>\n <sd-button prefixIcon=\"save\" [title]=\"'core.component.form-builder.confirm' | translate\" (click)=\"onAccept()\" color=\"primary\" type=\"fill\"></sd-button>\n </ng-container>\n </sd-modal>\n}\n", styles: [".c-builder{padding:8px;width:100%;border:1px solid #e6e6e6;border-radius:4px}.c-builder .c-condition{width:80px}.c-builder .c-condition .c-where{width:80px;border:1px solid #e6e6e6;background-color:#f2f2f2;color:#757575;border-radius:4px;padding:4px;text-align:center}.c-builder .c-condition .c-select{margin-left:10px;width:70px}\n"] }]
|
|
5778
|
+
], template: "<!--\n FEEL-style expression trigger pill (M3 redesign).\n B\u1EA5m v\u00E0o pill m\u1EDF modal builder hi\u1EC7n t\u1EA1i (onEdit).\n-->\n<div class=\"fb-field\">\n @if (label) {\n <div class=\"fb-field__label\">{{ label }}</div>\n }\n @let exprText = model | expressionQuery;\n <div class=\"fb-expr\" (click)=\"onEdit()\" role=\"button\" tabindex=\"0\">\n <span class=\"fb-expr__badge\">FEEL</span>\n @if (exprText) {\n <span class=\"fb-expr__code mono\">{{ exprText }}</span>\n } @else {\n <span class=\"empty\">{{ 'core.component.form-builder.set-expression' | translate }}</span>\n }\n <button type=\"button\" class=\"fb-icon-btn\" (click)=\"onEdit(); $event.stopPropagation()\" aria-label=\"edit expression\">\n <span class=\"msi\">functions</span>\n </button>\n </div>\n</div>\n@if (expression) {\n <sd-modal [title]=\"'core.component.form-builder.set-expression' | translate\" modalClass=\"sd-form-builder-dialog\">\n <div class=\"c-builder\">\n <div class=\"d-flex justify-content-end\">\n <sd-button\n prefixIcon=\"calculate\"\n class=\"mr-4\"\n [title]=\"'core.component.form-builder.condition-group' | translate\"\n color=\"primary\"\n (click)=\"addCombinator(expression.conditions)\"></sd-button>\n <sd-button\n prefixIcon=\"add\"\n [title]=\"'core.component.form-builder.condition' | translate\"\n color=\"primary\"\n (click)=\"addCondition(expression.conditions)\"\n type=\"fill\"></sd-button>\n </div>\n <!-- C\u1EA5p 1 -->\n @for (conditionLv1 of expression.conditions; track conditionLv1.key; let idxLv1 = $index) {\n <div class=\"d-flex align-items-end py-8\" style=\"gap: 8px\">\n <div class=\"c-condition\">\n <!-- D\u00F2ng 1 ch\u1EC9 hi\u1EC7n WHERE -->\n @if (idxLv1 === 0) {\n <div class=\"c-where\">WHERE</div>\n }\n <!-- D\u00F2ng 2 hi\u1EC3n th\u1ECB combinator -->\n @if (idxLv1 === 1) {\n <div class=\"c-select\">\n <sd-select\n size=\"sm\"\n [(model)]=\"expression.combinator\"\n [items]=\"[\n { value: '&&', display: 'AND' },\n { value: '||', display: 'OR' }\n ]\"\n valueField=\"value\"\n displayField=\"display\"\n required\n hideInlineError=\"\"></sd-select>\n </div>\n }\n </div>\n @if (conditionLv1.type === 'condition') {\n <sd-autocomplete\n size=\"sm\"\n [form]=\"form\"\n style=\"width: 200px\"\n [(model)]=\"conditionLv1.field\"\n [items]=\"attributes\"\n (sdChange)=\"onChangeAttribute(conditionLv1)\"\n valueField=\"value\"\n displayField=\"display\"\n required\n hideInlineError></sd-autocomplete>\n @let fieldAttribute = attribute[conditionLv1.field!];\n <sd-select\n size=\"sm\"\n [form]=\"form\"\n style=\"width: 100px\"\n [(model)]=\"conditionLv1.operator\"\n [items]=\"attributeOperators[fieldAttribute?.type || 'string']\"\n valueField=\"value\"\n displayField=\"display\"\n [disabled]=\"!fieldAttribute?.type\"\n required\n hideInlineError></sd-select>\n @if (\n !!fieldAttribute?.type && conditionLv1.operator && conditionLv1.operator !== 'NULL' && conditionLv1.operator !== 'NOT_NULL'\n ) {\n @if (fieldAttribute.type === 'string') {\n <sd-input size=\"sm\" [form]=\"form\" style=\"flex: 1\" [(model)]=\"conditionLv1.value\" required hideInlineError></sd-input>\n } @else if (fieldAttribute.type === 'number') {\n <sd-input-number size=\"sm\" [form]=\"form\" style=\"flex: 1\" [(model)]=\"conditionLv1.value\" hideInlineError></sd-input-number>\n <sd-autocomplete\n size=\"sm\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"conditionLv1.value\"\n [items]=\"numberAttributes\"\n valueField=\"value\"\n displayField=\"display\"\n hideInlineError></sd-autocomplete>\n } @else if (fieldAttribute.type === 'datetime' && conditionLv1.dayInfo) {\n <sd-select\n size=\"sm\"\n [placeholder]=\"'core.component.form-builder.time' | translate\"\n [form]=\"form\"\n style=\"width: 150px\"\n [items]=\"types\"\n valueField=\"value\"\n displayField=\"display\"\n [(model)]=\"conditionLv1.dayInfo.type\"\n (sdChange)=\"onChangeDateInfo(conditionLv1)\"\n required\n hideInlineError>\n </sd-select>\n @if (conditionLv1.dayInfo.type === 'RELATED') {\n <sd-input-number\n size=\"sm\"\n [form]=\"form\"\n style=\"width: 80px\"\n [(model)]=\"conditionLv1.dayInfo.relatedValue\"\n (sdChange)=\"onChangeDateInfo(conditionLv1)\"\n [min]=\"1\"\n required\n hideInlineError>\n </sd-input-number>\n <sd-select\n size=\"sm\"\n [form]=\"form\"\n style=\"flex: 1\"\n [items]=\"previouses\"\n valueField=\"value\"\n displayField=\"display\"\n [(model)]=\"conditionLv1.dayInfo.related\"\n (sdChange)=\"onChangeDateInfo(conditionLv1)\"\n required\n hideInlineError>\n </sd-select>\n } @else if (conditionLv1.dayInfo.type === 'DATETIME') {\n <sd-datetime\n size=\"sm\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"conditionLv1.value\"\n (sdChange)=\"onChangeDateInfo(conditionLv1)\"\n required\n hideInlineError>\n </sd-datetime>\n } @else if (conditionLv1.dayInfo.type === 'ATTRIBUTE') {\n <sd-autocomplete\n size=\"sm\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"conditionLv1.value\"\n [items]=\"dateAttributes\"\n valueField=\"value\"\n displayField=\"display\"\n hideInlineError></sd-autocomplete>\n }\n } @else if (fieldAttribute.type === 'boolean') {\n <sd-select\n size=\"sm\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"conditionLv1.value\"\n [items]=\"[\n { value: true, display: fieldAttribute.displayOnTrue },\n { value: false, display: fieldAttribute.displayOnFalse }\n ]\"\n valueField=\"value\"\n displayField=\"display\"\n required\n hideInlineError></sd-select>\n } @else if (fieldAttribute.type === 'values') {\n <sd-autocomplete\n size=\"sm\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"conditionLv1.value\"\n [items]=\"fieldAttribute.values\"\n valueField=\"value\"\n displayField=\"display\"\n required\n hideInlineError></sd-autocomplete>\n }\n }\n <sd-button class=\"ml-auto\" prefixIcon=\"delete\" [tooltip]=\"'core.component.form-builder.delete' | translate\" (click)=\"remove(expression.conditions, idxLv1)\"></sd-button>\n } @else if (conditionLv1.type === 'combinator') {\n <div class=\"c-builder\">\n <div class=\"d-flex justify-content-end\">\n <sd-button class=\"mr-4\" prefixIcon=\"delete\" [tooltip]=\"'core.component.form-builder.delete' | translate\" (click)=\"remove(expression.conditions, idxLv1)\"></sd-button>\n <sd-button\n prefixIcon=\"add\"\n [title]=\"'core.component.form-builder.condition' | translate\"\n size=\"sm\"\n color=\"primary\"\n (click)=\"addCondition(conditionLv1.conditions)\"\n type=\"fill\"></sd-button>\n </div>\n <!-- C\u1EA5p 2 -->\n @for (conditionLv2 of conditionLv1.conditions; track conditionLv2.key; let idxLv2 = $index) {\n <div class=\"d-flex align-items-end py-8\" style=\"gap: 8px\">\n <div class=\"c-condition\">\n <!-- D\u00F2ng 1 ch\u1EC9 hi\u1EC7n WHERE -->\n @if (idxLv2 === 0) {\n <div class=\"c-where\">WHERE</div>\n }\n <!-- D\u00F2ng 2 hi\u1EC3n th\u1ECB combinator -->\n @if (idxLv2 === 1) {\n <div class=\"c-select\">\n <sd-select\n size=\"sm\"\n [(model)]=\"conditionLv1.combinator\"\n [items]=\"[\n { value: '&&', display: 'AND' },\n { value: '||', display: 'OR' }\n ]\"\n valueField=\"value\"\n displayField=\"display\"\n required\n hideInlineError=\"\"></sd-select>\n </div>\n }\n </div>\n @if (conditionLv2.type === 'condition') {\n <sd-autocomplete\n size=\"sm\"\n [form]=\"form\"\n style=\"width: 200px\"\n [(model)]=\"conditionLv2.field\"\n [items]=\"attributes\"\n (sdChange)=\"onChangeAttribute(conditionLv2)\"\n valueField=\"value\"\n displayField=\"display\"\n required\n hideInlineError></sd-autocomplete>\n @let fieldAttribute = attribute[conditionLv2.field!];\n <sd-select\n size=\"sm\"\n [form]=\"form\"\n style=\"width: 100px\"\n [(model)]=\"conditionLv2.operator\"\n [items]=\"attributeOperators[fieldAttribute?.type || 'string']\"\n valueField=\"value\"\n displayField=\"display\"\n [disabled]=\"!fieldAttribute?.type\"\n required\n hideInlineError></sd-select>\n @if (\n !!fieldAttribute?.type &&\n conditionLv2.operator &&\n conditionLv2.operator !== 'NULL' &&\n conditionLv2.operator !== 'NOT_NULL'\n ) {\n @if (fieldAttribute.type === 'string') {\n <sd-input\n size=\"sm\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"conditionLv2.value\"\n required\n hideInlineError></sd-input>\n } @else if (fieldAttribute.type === 'number') {\n <sd-input-number\n size=\"sm\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"conditionLv2.value\"\n hideInlineError></sd-input-number>\n <sd-autocomplete\n size=\"sm\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"conditionLv2.value\"\n [items]=\"numberAttributes\"\n valueField=\"value\"\n displayField=\"display\"\n hideInlineError></sd-autocomplete>\n } @else if (fieldAttribute.type === 'datetime' && conditionLv2.dayInfo) {\n <sd-select\n size=\"sm\"\n [form]=\"form\"\n [placeholder]=\"'core.component.form-builder.time' | translate\"\n style=\"width: 150px\"\n [items]=\"types\"\n valueField=\"value\"\n displayField=\"display\"\n [(model)]=\"conditionLv2.dayInfo.type\"\n (sdChange)=\"onChangeDateInfo(conditionLv2)\"\n required\n hideInlineError>\n </sd-select>\n @if (conditionLv2.dayInfo.type === 'RELATED') {\n <sd-input-number\n size=\"sm\"\n [form]=\"form\"\n style=\"width: 80px\"\n [(model)]=\"conditionLv2.dayInfo.relatedValue\"\n (sdChange)=\"onChangeDateInfo(conditionLv2)\"\n [min]=\"1\"\n required\n hideInlineError>\n </sd-input-number>\n <sd-select\n size=\"sm\"\n [form]=\"form\"\n style=\"flex: 1\"\n [items]=\"previouses\"\n valueField=\"value\"\n displayField=\"display\"\n [(model)]=\"conditionLv2.dayInfo.related\"\n (sdChange)=\"onChangeDateInfo(conditionLv2)\"\n required\n hideInlineError>\n </sd-select>\n } @else if (conditionLv2.dayInfo.type === 'DATETIME') {\n <sd-datetime\n size=\"sm\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"conditionLv2.value\"\n (sdChange)=\"onChangeDateInfo(conditionLv2)\"\n required\n hideInlineError>\n </sd-datetime>\n } @else if (conditionLv2.dayInfo.type === 'ATTRIBUTE') {\n <sd-autocomplete\n size=\"sm\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"conditionLv2.value\"\n [items]=\"dateAttributes\"\n valueField=\"value\"\n displayField=\"display\"\n hideInlineError></sd-autocomplete>\n }\n } @else if (fieldAttribute.type === 'boolean') {\n <sd-select\n size=\"sm\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"conditionLv2.value\"\n [items]=\"[\n { value: true, display: fieldAttribute.displayOnTrue },\n { value: false, display: fieldAttribute.displayOnFalse }\n ]\"\n valueField=\"value\"\n displayField=\"display\"\n required\n hideInlineError></sd-select>\n } @else if (fieldAttribute.type === 'values') {\n <sd-autocomplete\n size=\"sm\"\n [form]=\"form\"\n style=\"flex: 1\"\n [(model)]=\"conditionLv2.value\"\n [items]=\"fieldAttribute.values\"\n valueField=\"value\"\n displayField=\"display\"\n required\n hideInlineError></sd-autocomplete>\n }\n }\n <sd-button\n class=\"ml-auto\"\n prefixIcon=\"delete\"\n [tooltip]=\"'core.component.form-builder.delete' | translate\"\n (click)=\"remove(conditionLv1.conditions, idxLv1)\"></sd-button>\n }\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\n <ng-container sdFooterRight>\n <sd-button prefixIcon=\"save\" [title]=\"'core.component.form-builder.confirm' | translate\" (click)=\"onAccept()\" color=\"primary\" type=\"fill\"></sd-button>\n </ng-container>\n </sd-modal>\n}\n", styles: [".c-builder{padding:8px;width:100%;border:1px solid #e6e6e6;border-radius:4px}.c-builder .c-condition{width:80px}.c-builder .c-condition .c-where{width:80px;border:1px solid #e6e6e6;background-color:#f2f2f2;color:#757575;border-radius:4px;padding:4px;text-align:center}.c-builder .c-condition .c-select{margin-left:10px;width:70px}\n"] }]
|
|
5776
5779
|
}], ctorParameters: () => [{ type: i0.ChangeDetectorRef }], propDecorators: { modal: [{
|
|
5777
5780
|
type: ViewChild,
|
|
5778
5781
|
args: [SdModal]
|
|
@@ -5860,11 +5863,11 @@ class ConfigureValidationComponent {
|
|
|
5860
5863
|
this.modal?.close();
|
|
5861
5864
|
};
|
|
5862
5865
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: ConfigureValidationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
5863
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: ConfigureValidationComponent, isStandalone: true, selector: "configure-validation", outputs: { accept: "accept" }, viewQueries: [{ propertyName: "modal", first: true, predicate: SdModal, descendants: true }], ngImport: i0, template: "<sd-modal [title]=\"'core.component.form-builder.config-validation-error-warning' | translate\" view=\"dialog\" width=\"800px\" #popupConfigureVariables>\n @for (validation of validations; track $index; let idx = $index) {\n <div class=\"mat-elevation-z2 position-relative p-8 mb-16\" style=\"border-radius: 8px;\">\n <div class=\"row mx-0\">\n <sd-select\n class=\"col-3\"\n [label]=\"'core.component.form-builder.message-type' | translate\"\n [(model)]=\"validation.alert\"\n [items]=\"alerts\"\n valueField=\"value\"\n displayField=\"display\"\n required></sd-select>\n @if (validation.type === 'expression') {\n <sd-input class=\"col-9\" [form]=\"form\" [label]=\"'core.component.form-builder.message-display' | translate\" [(model)]=\"validation.message\" required></sd-input>\n <expression-builder class=\"col-12\" [label]=\"'core.component.form-builder.condition-expression' | translate\" [(model)]=\"validation.expression\" [attributes]=\"attributes\"></expression-builder>\n } @else if (validation.type === 'function') {\n <sd-select\n class=\"col-9\"\n [form]=\"form\"\n [(model)]=\"validation.code\"\n [items]=\"functions\"\n valueField=\"value\"\n displayField=\"display\"\n required></sd-select>\n }\n <sd-icon class=\"c-close-icon\" (click)=\"removaValidation(idx)\" name=\"delete\"></sd-icon>\n <!-- <sd-button\n\n prefixIcon=\"delete\"\n (click)=\"removaValidation(idx)\"\n color=\"error\"\n type=\"link\"></sd-button> -->\n </div>\n </div>\n }\n <sd-button [title]=\"'core.component.form-builder.add-validation' | translate\" [matMenuTriggerFor]=\"menu\" prefixIcon=\"add\" color=\"primary\" type=\"link\"></sd-button>\n <mat-menu #menu=\"matMenu\">\n <button (click)=\"addValidation('expression')\" mat-menu-item type=\"button\">\n <!-- <sd-icon fontIcon=\"warning\"></sd-icon> -->\n <span>{{ 'core.component.form-builder.expression' | translate }}</span>\n </button>\n <button (click)=\"addValidation('function')\" mat-menu-item type=\"button\">\n <!-- <sd-icon fontIcon=\"error\"></sd-icon> -->\n <span>{{ 'core.component.form-builder.available-methods' | translate }}</span>\n </button>\n </mat-menu>\n <ng-container sdFooter>\n <sd-button type=\"fill\" [title]=\"'core.component.form-builder.save' | translate\" (click)=\"updateValidations()\" prefixIcon=\"save\" color=\"primary\"></sd-button>\n </ng-container>\n</sd-modal>\n", styles: [".text-primary{color:var(--sd-primary)!important}.bg-primary{background:var(--sd-primary)!important}.border-primary{border-color:var(--sd-primary)!important}.text-primary-light{color:var(--sd-primary-light)!important}.bg-primary-light{background:var(--sd-primary-light)!important}.border-primary-light{border-color:var(--sd-primary-light)!important}.text-primary-dark{color:var(--sd-primary-dark)!important}.bg-primary-dark{background:var(--sd-primary-dark)!important}.border-primary-dark{border-color:var(--sd-primary-dark)!important}.text-info{color:var(--sd-info)!important}.bg-info{background:var(--sd-info)!important}.border-info{border-color:var(--sd-info)!important}.text-info-light{color:var(--sd-info-light)!important}.bg-info-light{background:var(--sd-info-light)!important}.border-info-light{border-color:var(--sd-info-light)!important}.text-info-dark{color:var(--sd-info-dark)!important}.bg-info-dark{background:var(--sd-info-dark)!important}.border-info-dark{border-color:var(--sd-info-dark)!important}.text-success{color:var(--sd-success)!important}.bg-success{background:var(--sd-success)!important}.border-success{border-color:var(--sd-success)!important}.text-success-light{color:var(--sd-success-light)!important}.bg-success-light{background:var(--sd-success-light)!important}.border-success-light{border-color:var(--sd-success-light)!important}.text-success-dark{color:var(--sd-success-dark)!important}.bg-success-dark{background:var(--sd-success-dark)!important}.border-success-dark{border-color:var(--sd-success-dark)!important}.text-warning{color:var(--sd-warning)!important}.bg-warning{background:var(--sd-warning)!important}.border-warning{border-color:var(--sd-warning)!important}.text-warning-light{color:var(--sd-warning-light)!important}.bg-warning-light{background:var(--sd-warning-light)!important}.border-warning-light{border-color:var(--sd-warning-light)!important}.text-warning-dark{color:var(--sd-warning-dark)!important}.bg-warning-dark{background:var(--sd-warning-dark)!important}.border-warning-dark{border-color:var(--sd-warning-dark)!important}.text-error{color:var(--sd-error)!important}.bg-error{background:var(--sd-error)!important}.border-error{border-color:var(--sd-error)!important}.text-error-light{color:var(--sd-error-light)!important}.bg-error-light{background:var(--sd-error-light)!important}.border-error-light{border-color:var(--sd-error-light)!important}.text-error-dark{color:var(--sd-error-dark)!important}.bg-error-dark{background:var(--sd-error-dark)!important}.border-error-dark{border-color:var(--sd-error-dark)!important}.text-secondary{color:var(--sd-secondary)!important}.bg-secondary{background:var(--sd-secondary)!important}.border-secondary{border-color:var(--sd-secondary)!important}.text-secondary-light{color:var(--sd-secondary-light)!important}.bg-secondary-light{background:var(--sd-secondary-light)!important}.border-secondary-light{border-color:var(--sd-secondary-light)!important}.text-secondary-dark{color:var(--sd-secondary-dark)!important}.bg-secondary-dark{background:var(--sd-secondary-dark)!important}.border-secondary-dark{border-color:var(--sd-secondary-dark)!important}.text-light{color:var(--sd-light)!important}.bg-light{background:var(--sd-light)!important}.border-light{border-color:var(--sd-light)!important}.text-dark{color:var(--sd-dark)!important}.bg-dark{background:var(--sd-dark)!important}.border-dark{border-color:var(--sd-dark)!important}.text-black500{color:var(--sd-black500)!important}.bg-black500{background:var(--sd-black500)!important}.border-black500{border-color:var(--sd-black500)!important}.text-black400{color:var(--sd-black400)!important}.bg-black400{background:var(--sd-black400)!important}.border-black400{border-color:var(--sd-black400)!important}.text-black300{color:var(--sd-black300)!important}.bg-black300{background:var(--sd-black300)!important}.border-black300{border-color:var(--sd-black300)!important}.text-black200{color:var(--sd-black200)!important}.bg-black200{background:var(--sd-black200)!important}.border-black200{border-color:var(--sd-black200)!important}.text-black100{color:var(--sd-black100)!important}.bg-black100{background:var(--sd-black100)!important}.border-black100{border-color:var(--sd-black100)!important}.text-white{color:#fff!important}.bg-white{background:#fff!important}.border-white{border-color:#fff!important}.text-black{color:#000!important}.bg-black{background:#000!important}.border-black{border-color:#000!important}.c-close-icon{margin-left:auto;font-size:16px;width:16px;height:16px;cursor:pointer;border-radius:50%;color:var(--sd-error);top:4px;right:4px;position:absolute}.c-close-icon:hover{background-color:var(--sd-black200)}\n"], dependencies: [{ kind: "component", type: SdIcon, selector: "sd-icon", inputs: ["name", "fontIcon", "color", "set", "fontSet", "size", "strokeWidth", "absoluteStrokeWidth", "ariaLabel"] }, { kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i1$4.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i1$4.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i1$4.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "component", type: SdModal, selector: "sd-modal", inputs: ["autoId", "title", "color", "width", "height", "view", "modalClass", "lazyLoadContent", "hideClose", "disableBackdropClose"], outputs: ["sdClosed"] }, { kind: "component", type: SdButton, selector: "sd-button", inputs: ["autoId", "type", "color", "size", "fontSet", "title", "width", "tooltip", "prefixIcon", "suffixIcon", "disabled", "loading", "block", "htmlType"], outputs: ["click"] }, { kind: "component", type: SdInput, selector: "sd-input", inputs: ["autoId", "name", "appearance", "floatLabel", "size", "form", "label", "helperText", "placeholder", "type", "hideInlineError", "blurOnEnter", "required", "readonly", "disabled", "viewed", "minlength", "maxlength", "pattern", "patternErrorMessage", "validator", "inlineError", "hyperlink", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "cleared", "sdFocusForceBlur"] }, { kind: "component", type: SdSelect, selector: "sd-select", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "valueField", "displayField", "disabledField", "cacheChecksum", "limit", "hyperlink", "minWidthPanel", "hideInlineError", "required", "disabled", "viewed", "multiple", "clearable", "validator", "inlineError", "appearance", "floatLabel", "items", "model"], outputs: ["modelChange", "sdChange", "sdSelection"] }, { kind: "component", type: ExpressionBuilderComponent, selector: "expression-builder", inputs: ["label", "attributes", "model"], outputs: ["modelChange", "sdChange", "edit"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
5866
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: ConfigureValidationComponent, isStandalone: true, selector: "configure-validation", outputs: { accept: "accept" }, viewQueries: [{ propertyName: "modal", first: true, predicate: SdModal, descendants: true }], ngImport: i0, template: "<sd-modal\n [title]=\"'core.component.form-builder.config-validation-error-warning' | translate\"\n view=\"dialog\"\n width=\"800px\"\n modalClass=\"sd-form-builder-dialog\"\n #popupConfigureVariables>\n @for (validation of validations; track $index; let idx = $index) {\n <div class=\"mat-elevation-z2 position-relative p-8 mb-16\" style=\"border-radius: 8px;\">\n <div class=\"row mx-0\">\n <sd-select\n class=\"col-3\"\n [label]=\"'core.component.form-builder.message-type' | translate\"\n [(model)]=\"validation.alert\"\n [items]=\"alerts\"\n valueField=\"value\"\n displayField=\"display\"\n required></sd-select>\n @if (validation.type === 'expression') {\n <sd-input class=\"col-9\" [form]=\"form\" [label]=\"'core.component.form-builder.message-display' | translate\" [(model)]=\"validation.message\" required></sd-input>\n <expression-builder class=\"col-12\" [label]=\"'core.component.form-builder.condition-expression' | translate\" [(model)]=\"validation.expression\" [attributes]=\"attributes\"></expression-builder>\n } @else if (validation.type === 'function') {\n <sd-select\n class=\"col-9\"\n [form]=\"form\"\n [(model)]=\"validation.code\"\n [items]=\"functions\"\n valueField=\"value\"\n displayField=\"display\"\n required></sd-select>\n }\n <sd-icon class=\"c-close-icon\" (click)=\"removaValidation(idx)\" name=\"delete\"></sd-icon>\n <!-- <sd-button\n\n prefixIcon=\"delete\"\n (click)=\"removaValidation(idx)\"\n color=\"error\"\n type=\"text\"></sd-button> -->\n </div>\n </div>\n }\n <sd-button [title]=\"'core.component.form-builder.add-validation' | translate\" [matMenuTriggerFor]=\"menu\" prefixIcon=\"add\" color=\"primary\" type=\"text\"></sd-button>\n <mat-menu #menu=\"matMenu\" yPosition=\"above\" panelClass=\"fb-validation-menu\">\n <button (click)=\"addValidation('expression')\" mat-menu-item type=\"button\">\n <!-- <sd-icon fontIcon=\"warning\"></sd-icon> -->\n <span>{{ 'core.component.form-builder.expression' | translate }}</span>\n </button>\n <button (click)=\"addValidation('function')\" mat-menu-item type=\"button\">\n <!-- <sd-icon fontIcon=\"error\"></sd-icon> -->\n <span>{{ 'core.component.form-builder.available-methods' | translate }}</span>\n </button>\n </mat-menu>\n <ng-container sdFooterRight>\n <sd-button type=\"fill\" [title]=\"'core.component.form-builder.save' | translate\" (click)=\"updateValidations()\" prefixIcon=\"save\" color=\"primary\"></sd-button>\n </ng-container>\n</sd-modal>\n", styles: [".text-primary{color:var(--sd-primary)!important}.bg-primary{background:var(--sd-primary)!important}.border-primary{border-color:var(--sd-primary)!important}.text-primary-light{color:var(--sd-primary-light)!important}.bg-primary-light{background:var(--sd-primary-light)!important}.border-primary-light{border-color:var(--sd-primary-light)!important}.text-primary-dark{color:var(--sd-primary-dark)!important}.bg-primary-dark{background:var(--sd-primary-dark)!important}.border-primary-dark{border-color:var(--sd-primary-dark)!important}.text-primary-contrast{color:var(--sd-primary-contrast)!important}.bg-primary-contrast{background:var(--sd-primary-contrast)!important}.border-primary-contrast{border-color:var(--sd-primary-contrast)!important}.text-secondary{color:var(--sd-secondary)!important}.bg-secondary{background:var(--sd-secondary)!important}.border-secondary{border-color:var(--sd-secondary)!important}.text-secondary-light{color:var(--sd-secondary-light)!important}.bg-secondary-light{background:var(--sd-secondary-light)!important}.border-secondary-light{border-color:var(--sd-secondary-light)!important}.text-secondary-dark{color:var(--sd-secondary-dark)!important}.bg-secondary-dark{background:var(--sd-secondary-dark)!important}.border-secondary-dark{border-color:var(--sd-secondary-dark)!important}.text-secondary-contrast{color:var(--sd-secondary-contrast)!important}.bg-secondary-contrast{background:var(--sd-secondary-contrast)!important}.border-secondary-contrast{border-color:var(--sd-secondary-contrast)!important}.text-info{color:var(--sd-info)!important}.bg-info{background:var(--sd-info)!important}.border-info{border-color:var(--sd-info)!important}.text-info-light{color:var(--sd-info-light)!important}.bg-info-light{background:var(--sd-info-light)!important}.border-info-light{border-color:var(--sd-info-light)!important}.text-info-dark{color:var(--sd-info-dark)!important}.bg-info-dark{background:var(--sd-info-dark)!important}.border-info-dark{border-color:var(--sd-info-dark)!important}.text-info-contrast{color:var(--sd-info-contrast)!important}.bg-info-contrast{background:var(--sd-info-contrast)!important}.border-info-contrast{border-color:var(--sd-info-contrast)!important}.text-success{color:var(--sd-success)!important}.bg-success{background:var(--sd-success)!important}.border-success{border-color:var(--sd-success)!important}.text-success-light{color:var(--sd-success-light)!important}.bg-success-light{background:var(--sd-success-light)!important}.border-success-light{border-color:var(--sd-success-light)!important}.text-success-dark{color:var(--sd-success-dark)!important}.bg-success-dark{background:var(--sd-success-dark)!important}.border-success-dark{border-color:var(--sd-success-dark)!important}.text-success-contrast{color:var(--sd-success-contrast)!important}.bg-success-contrast{background:var(--sd-success-contrast)!important}.border-success-contrast{border-color:var(--sd-success-contrast)!important}.text-warning{color:var(--sd-warning)!important}.bg-warning{background:var(--sd-warning)!important}.border-warning{border-color:var(--sd-warning)!important}.text-warning-light{color:var(--sd-warning-light)!important}.bg-warning-light{background:var(--sd-warning-light)!important}.border-warning-light{border-color:var(--sd-warning-light)!important}.text-warning-dark{color:var(--sd-warning-dark)!important}.bg-warning-dark{background:var(--sd-warning-dark)!important}.border-warning-dark{border-color:var(--sd-warning-dark)!important}.text-warning-contrast{color:var(--sd-warning-contrast)!important}.bg-warning-contrast{background:var(--sd-warning-contrast)!important}.border-warning-contrast{border-color:var(--sd-warning-contrast)!important}.text-error{color:var(--sd-error)!important}.bg-error{background:var(--sd-error)!important}.border-error{border-color:var(--sd-error)!important}.text-error-light{color:var(--sd-error-light)!important}.bg-error-light{background:var(--sd-error-light)!important}.border-error-light{border-color:var(--sd-error-light)!important}.text-error-dark{color:var(--sd-error-dark)!important}.bg-error-dark{background:var(--sd-error-dark)!important}.border-error-dark{border-color:var(--sd-error-dark)!important}.text-error-contrast{color:var(--sd-error-contrast)!important}.bg-error-contrast{background:var(--sd-error-contrast)!important}.border-error-contrast{border-color:var(--sd-error-contrast)!important}.text-surface{color:var(--sd-surface)!important}.bg-surface{background:var(--sd-surface)!important}.border-surface{border-color:var(--sd-surface)!important}.text-surface-muted{color:var(--sd-surface-muted)!important}.bg-surface-muted{background:var(--sd-surface-muted)!important}.border-surface-muted{border-color:var(--sd-surface-muted)!important}.text-text{color:var(--sd-text)!important}.bg-text{background:var(--sd-text)!important}.border-text{border-color:var(--sd-text)!important}.text-text-secondary{color:var(--sd-text-secondary)!important}.bg-text-secondary{background:var(--sd-text-secondary)!important}.border-text-secondary{border-color:var(--sd-text-secondary)!important}.text-text-muted{color:var(--sd-text-muted)!important}.bg-text-muted{background:var(--sd-text-muted)!important}.border-text-muted{border-color:var(--sd-text-muted)!important}.text-border{color:var(--sd-border)!important}.bg-border{background:var(--sd-border)!important}.border-border{border-color:var(--sd-border)!important}.text-border-strong{color:var(--sd-border-strong)!important}.bg-border-strong{background:var(--sd-border-strong)!important}.border-border-strong{border-color:var(--sd-border-strong)!important}.text-disabled-bg{color:var(--sd-disabled-bg)!important}.bg-disabled-bg{background:var(--sd-disabled-bg)!important}.border-disabled-bg{border-color:var(--sd-disabled-bg)!important}.text-disabled-text{color:var(--sd-disabled-text)!important}.bg-disabled-text{background:var(--sd-disabled-text)!important}.border-disabled-text{border-color:var(--sd-disabled-text)!important}.text-white{color:#fff!important}.bg-white{background:#fff!important}.border-white{border-color:#fff!important}.text-black{color:#000!important}.bg-black{background:#000!important}.border-black{border-color:#000!important}.text-secondary{color:var(--sd-text-secondary)!important}.text-muted{color:var(--sd-text-muted)!important}.text-body{color:var(--sd-text)!important}.bg-muted{background:var(--sd-surface-muted)!important}.c-close-icon{margin-left:auto;font-size:16px;width:16px;height:16px;cursor:pointer;border-radius:50%;color:var(--sd-error);top:4px;right:4px;position:absolute}.c-close-icon:hover{background-color:var(--sd-border)}\n"], dependencies: [{ kind: "component", type: SdIcon, selector: "sd-icon", inputs: ["name", "fontIcon", "color", "set", "fontSet", "size", "strokeWidth", "absoluteStrokeWidth", "ariaLabel"] }, { kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i1$4.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i1$4.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i1$4.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "component", type: SdModal, selector: "sd-modal", inputs: ["autoId", "title", "color", "width", "height", "view", "modalClass", "lazyLoadContent", "hideClose", "disableBackdropClose"], outputs: ["sdClosed"] }, { kind: "component", type: SdButton, selector: "sd-button", inputs: ["autoId", "type", "color", "size", "fontSet", "title", "width", "tooltip", "prefixIcon", "suffixIcon", "disabled", "loading", "block", "htmlType"], outputs: ["click"] }, { kind: "component", type: SdInput, selector: "sd-input", inputs: ["autoId", "name", "appearance", "floatLabel", "size", "form", "label", "helperText", "placeholder", "type", "hideInlineError", "blurOnEnter", "required", "readonly", "disabled", "viewed", "minlength", "maxlength", "pattern", "patternErrorMessage", "validator", "inlineError", "hyperlink", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "cleared", "sdFocusForceBlur"] }, { kind: "component", type: SdSelect, selector: "sd-select", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "valueField", "displayField", "disabledField", "cacheChecksum", "limit", "hyperlink", "minWidthPanel", "hideInlineError", "required", "disabled", "viewed", "multiple", "clearable", "validator", "inlineError", "appearance", "floatLabel", "items", "model"], outputs: ["modelChange", "sdChange", "sdSelection"] }, { kind: "component", type: ExpressionBuilderComponent, selector: "expression-builder", inputs: ["label", "attributes", "model"], outputs: ["modelChange", "sdChange", "edit"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
5864
5867
|
}
|
|
5865
5868
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: ConfigureValidationComponent, decorators: [{
|
|
5866
5869
|
type: Component,
|
|
5867
|
-
args: [{ selector: 'configure-validation', changeDetection: ChangeDetectionStrategy.OnPush, imports: [SdIcon, MatMenuModule, SdModal, SdButton, SdInput, SdSelect, ExpressionBuilderComponent, TranslatePipe], template: "<sd-modal
|
|
5870
|
+
args: [{ selector: 'configure-validation', changeDetection: ChangeDetectionStrategy.OnPush, imports: [SdIcon, MatMenuModule, SdModal, SdButton, SdInput, SdSelect, ExpressionBuilderComponent, TranslatePipe], template: "<sd-modal\n [title]=\"'core.component.form-builder.config-validation-error-warning' | translate\"\n view=\"dialog\"\n width=\"800px\"\n modalClass=\"sd-form-builder-dialog\"\n #popupConfigureVariables>\n @for (validation of validations; track $index; let idx = $index) {\n <div class=\"mat-elevation-z2 position-relative p-8 mb-16\" style=\"border-radius: 8px;\">\n <div class=\"row mx-0\">\n <sd-select\n class=\"col-3\"\n [label]=\"'core.component.form-builder.message-type' | translate\"\n [(model)]=\"validation.alert\"\n [items]=\"alerts\"\n valueField=\"value\"\n displayField=\"display\"\n required></sd-select>\n @if (validation.type === 'expression') {\n <sd-input class=\"col-9\" [form]=\"form\" [label]=\"'core.component.form-builder.message-display' | translate\" [(model)]=\"validation.message\" required></sd-input>\n <expression-builder class=\"col-12\" [label]=\"'core.component.form-builder.condition-expression' | translate\" [(model)]=\"validation.expression\" [attributes]=\"attributes\"></expression-builder>\n } @else if (validation.type === 'function') {\n <sd-select\n class=\"col-9\"\n [form]=\"form\"\n [(model)]=\"validation.code\"\n [items]=\"functions\"\n valueField=\"value\"\n displayField=\"display\"\n required></sd-select>\n }\n <sd-icon class=\"c-close-icon\" (click)=\"removaValidation(idx)\" name=\"delete\"></sd-icon>\n <!-- <sd-button\n\n prefixIcon=\"delete\"\n (click)=\"removaValidation(idx)\"\n color=\"error\"\n type=\"text\"></sd-button> -->\n </div>\n </div>\n }\n <sd-button [title]=\"'core.component.form-builder.add-validation' | translate\" [matMenuTriggerFor]=\"menu\" prefixIcon=\"add\" color=\"primary\" type=\"text\"></sd-button>\n <mat-menu #menu=\"matMenu\" yPosition=\"above\" panelClass=\"fb-validation-menu\">\n <button (click)=\"addValidation('expression')\" mat-menu-item type=\"button\">\n <!-- <sd-icon fontIcon=\"warning\"></sd-icon> -->\n <span>{{ 'core.component.form-builder.expression' | translate }}</span>\n </button>\n <button (click)=\"addValidation('function')\" mat-menu-item type=\"button\">\n <!-- <sd-icon fontIcon=\"error\"></sd-icon> -->\n <span>{{ 'core.component.form-builder.available-methods' | translate }}</span>\n </button>\n </mat-menu>\n <ng-container sdFooterRight>\n <sd-button type=\"fill\" [title]=\"'core.component.form-builder.save' | translate\" (click)=\"updateValidations()\" prefixIcon=\"save\" color=\"primary\"></sd-button>\n </ng-container>\n</sd-modal>\n", styles: [".text-primary{color:var(--sd-primary)!important}.bg-primary{background:var(--sd-primary)!important}.border-primary{border-color:var(--sd-primary)!important}.text-primary-light{color:var(--sd-primary-light)!important}.bg-primary-light{background:var(--sd-primary-light)!important}.border-primary-light{border-color:var(--sd-primary-light)!important}.text-primary-dark{color:var(--sd-primary-dark)!important}.bg-primary-dark{background:var(--sd-primary-dark)!important}.border-primary-dark{border-color:var(--sd-primary-dark)!important}.text-primary-contrast{color:var(--sd-primary-contrast)!important}.bg-primary-contrast{background:var(--sd-primary-contrast)!important}.border-primary-contrast{border-color:var(--sd-primary-contrast)!important}.text-secondary{color:var(--sd-secondary)!important}.bg-secondary{background:var(--sd-secondary)!important}.border-secondary{border-color:var(--sd-secondary)!important}.text-secondary-light{color:var(--sd-secondary-light)!important}.bg-secondary-light{background:var(--sd-secondary-light)!important}.border-secondary-light{border-color:var(--sd-secondary-light)!important}.text-secondary-dark{color:var(--sd-secondary-dark)!important}.bg-secondary-dark{background:var(--sd-secondary-dark)!important}.border-secondary-dark{border-color:var(--sd-secondary-dark)!important}.text-secondary-contrast{color:var(--sd-secondary-contrast)!important}.bg-secondary-contrast{background:var(--sd-secondary-contrast)!important}.border-secondary-contrast{border-color:var(--sd-secondary-contrast)!important}.text-info{color:var(--sd-info)!important}.bg-info{background:var(--sd-info)!important}.border-info{border-color:var(--sd-info)!important}.text-info-light{color:var(--sd-info-light)!important}.bg-info-light{background:var(--sd-info-light)!important}.border-info-light{border-color:var(--sd-info-light)!important}.text-info-dark{color:var(--sd-info-dark)!important}.bg-info-dark{background:var(--sd-info-dark)!important}.border-info-dark{border-color:var(--sd-info-dark)!important}.text-info-contrast{color:var(--sd-info-contrast)!important}.bg-info-contrast{background:var(--sd-info-contrast)!important}.border-info-contrast{border-color:var(--sd-info-contrast)!important}.text-success{color:var(--sd-success)!important}.bg-success{background:var(--sd-success)!important}.border-success{border-color:var(--sd-success)!important}.text-success-light{color:var(--sd-success-light)!important}.bg-success-light{background:var(--sd-success-light)!important}.border-success-light{border-color:var(--sd-success-light)!important}.text-success-dark{color:var(--sd-success-dark)!important}.bg-success-dark{background:var(--sd-success-dark)!important}.border-success-dark{border-color:var(--sd-success-dark)!important}.text-success-contrast{color:var(--sd-success-contrast)!important}.bg-success-contrast{background:var(--sd-success-contrast)!important}.border-success-contrast{border-color:var(--sd-success-contrast)!important}.text-warning{color:var(--sd-warning)!important}.bg-warning{background:var(--sd-warning)!important}.border-warning{border-color:var(--sd-warning)!important}.text-warning-light{color:var(--sd-warning-light)!important}.bg-warning-light{background:var(--sd-warning-light)!important}.border-warning-light{border-color:var(--sd-warning-light)!important}.text-warning-dark{color:var(--sd-warning-dark)!important}.bg-warning-dark{background:var(--sd-warning-dark)!important}.border-warning-dark{border-color:var(--sd-warning-dark)!important}.text-warning-contrast{color:var(--sd-warning-contrast)!important}.bg-warning-contrast{background:var(--sd-warning-contrast)!important}.border-warning-contrast{border-color:var(--sd-warning-contrast)!important}.text-error{color:var(--sd-error)!important}.bg-error{background:var(--sd-error)!important}.border-error{border-color:var(--sd-error)!important}.text-error-light{color:var(--sd-error-light)!important}.bg-error-light{background:var(--sd-error-light)!important}.border-error-light{border-color:var(--sd-error-light)!important}.text-error-dark{color:var(--sd-error-dark)!important}.bg-error-dark{background:var(--sd-error-dark)!important}.border-error-dark{border-color:var(--sd-error-dark)!important}.text-error-contrast{color:var(--sd-error-contrast)!important}.bg-error-contrast{background:var(--sd-error-contrast)!important}.border-error-contrast{border-color:var(--sd-error-contrast)!important}.text-surface{color:var(--sd-surface)!important}.bg-surface{background:var(--sd-surface)!important}.border-surface{border-color:var(--sd-surface)!important}.text-surface-muted{color:var(--sd-surface-muted)!important}.bg-surface-muted{background:var(--sd-surface-muted)!important}.border-surface-muted{border-color:var(--sd-surface-muted)!important}.text-text{color:var(--sd-text)!important}.bg-text{background:var(--sd-text)!important}.border-text{border-color:var(--sd-text)!important}.text-text-secondary{color:var(--sd-text-secondary)!important}.bg-text-secondary{background:var(--sd-text-secondary)!important}.border-text-secondary{border-color:var(--sd-text-secondary)!important}.text-text-muted{color:var(--sd-text-muted)!important}.bg-text-muted{background:var(--sd-text-muted)!important}.border-text-muted{border-color:var(--sd-text-muted)!important}.text-border{color:var(--sd-border)!important}.bg-border{background:var(--sd-border)!important}.border-border{border-color:var(--sd-border)!important}.text-border-strong{color:var(--sd-border-strong)!important}.bg-border-strong{background:var(--sd-border-strong)!important}.border-border-strong{border-color:var(--sd-border-strong)!important}.text-disabled-bg{color:var(--sd-disabled-bg)!important}.bg-disabled-bg{background:var(--sd-disabled-bg)!important}.border-disabled-bg{border-color:var(--sd-disabled-bg)!important}.text-disabled-text{color:var(--sd-disabled-text)!important}.bg-disabled-text{background:var(--sd-disabled-text)!important}.border-disabled-text{border-color:var(--sd-disabled-text)!important}.text-white{color:#fff!important}.bg-white{background:#fff!important}.border-white{border-color:#fff!important}.text-black{color:#000!important}.bg-black{background:#000!important}.border-black{border-color:#000!important}.text-secondary{color:var(--sd-text-secondary)!important}.text-muted{color:var(--sd-text-muted)!important}.text-body{color:var(--sd-text)!important}.bg-muted{background:var(--sd-surface-muted)!important}.c-close-icon{margin-left:auto;font-size:16px;width:16px;height:16px;cursor:pointer;border-radius:50%;color:var(--sd-error);top:4px;right:4px;position:absolute}.c-close-icon:hover{background-color:var(--sd-border)}\n"] }]
|
|
5868
5871
|
}], ctorParameters: () => [], propDecorators: { modal: [{
|
|
5869
5872
|
type: ViewChild,
|
|
5870
5873
|
args: [SdModal]
|
|
@@ -6021,6 +6024,9 @@ class SdFormBuilder {
|
|
|
6021
6024
|
* Trigger class `.fb-shell--dragging` để ẩn hover/actions/resize toàn diện, không phụ thuộc :has(). */
|
|
6022
6025
|
isAnyDragging = signal(false, ...(ngDevMode ? [{ debugName: "isAnyDragging" }] : []));
|
|
6023
6026
|
dragSource = signal(undefined, ...(ngDevMode ? [{ debugName: "dragSource" }] : []));
|
|
6027
|
+
draggedPaletteItem = signal(undefined, ...(ngDevMode ? [{ debugName: "draggedPaletteItem" }] : []));
|
|
6028
|
+
rowInsertionEdge = signal('after', ...(ngDevMode ? [{ debugName: "rowInsertionEdge" }] : []));
|
|
6029
|
+
inlineDropTargetRow = signal(undefined, ...(ngDevMode ? [{ debugName: "inlineDropTargetRow" }] : []));
|
|
6024
6030
|
resizeState = signal(undefined, ...(ngDevMode ? [{ debugName: "resizeState" }] : []));
|
|
6025
6031
|
isResizing = computed(() => !!this.resizeState(), ...(ngDevMode ? [{ debugName: "isResizing" }] : []));
|
|
6026
6032
|
expand = true;
|
|
@@ -6028,11 +6034,13 @@ class SdFormBuilder {
|
|
|
6028
6034
|
isDragging = false;
|
|
6029
6035
|
targetItem = undefined;
|
|
6030
6036
|
lastDragPointer;
|
|
6037
|
+
#host = inject(ElementRef);
|
|
6031
6038
|
/** Handler chung cho mọi cdkDragStarted — set signal global true. */
|
|
6032
6039
|
onAnyDragStarted = () => {
|
|
6033
6040
|
this.isAnyDragging.set(true);
|
|
6034
6041
|
};
|
|
6035
|
-
onPaletteDragStarted = () => {
|
|
6042
|
+
onPaletteDragStarted = (item) => {
|
|
6043
|
+
this.draggedPaletteItem.set(item);
|
|
6036
6044
|
this.dragSource.set('palette');
|
|
6037
6045
|
this.onAnyDragStarted();
|
|
6038
6046
|
};
|
|
@@ -6042,10 +6050,13 @@ class SdFormBuilder {
|
|
|
6042
6050
|
};
|
|
6043
6051
|
onAnyDragMoved = (event) => {
|
|
6044
6052
|
this.lastDragPointer = event.pointerPosition;
|
|
6045
|
-
const
|
|
6046
|
-
|
|
6047
|
-
|
|
6048
|
-
|
|
6053
|
+
const hit = this.#rowHitFromPointer(event.pointerPosition);
|
|
6054
|
+
if (hit?.row && (this.targetItem !== hit.row || this.rowInsertionEdge() !== hit.edge)) {
|
|
6055
|
+
if (this.targetItem !== hit.row) {
|
|
6056
|
+
this.inlineDropTargetRow.set(undefined);
|
|
6057
|
+
}
|
|
6058
|
+
this.targetItem = hit.row;
|
|
6059
|
+
this.rowInsertionEdge.set(hit.edge);
|
|
6049
6060
|
this.#ref.markForCheck();
|
|
6050
6061
|
}
|
|
6051
6062
|
};
|
|
@@ -6055,6 +6066,9 @@ class SdFormBuilder {
|
|
|
6055
6066
|
setTimeout(() => {
|
|
6056
6067
|
this.isAnyDragging.set(false);
|
|
6057
6068
|
this.dragSource.set(undefined);
|
|
6069
|
+
this.draggedPaletteItem.set(undefined);
|
|
6070
|
+
this.inlineDropTargetRow.set(undefined);
|
|
6071
|
+
this.rowInsertionEdge.set('after');
|
|
6058
6072
|
this.lastDragPointer = undefined;
|
|
6059
6073
|
this.targetItem = undefined;
|
|
6060
6074
|
this.#ref.markForCheck();
|
|
@@ -6204,6 +6218,19 @@ class SdFormBuilder {
|
|
|
6204
6218
|
return `${item.items.length} field${item.items.length === 1 ? '' : 's'}`;
|
|
6205
6219
|
return this.typeLabelFor(item);
|
|
6206
6220
|
};
|
|
6221
|
+
placeholderColumnsFor = (item) => {
|
|
6222
|
+
if (!item)
|
|
6223
|
+
return 12;
|
|
6224
|
+
if ('items' in item)
|
|
6225
|
+
return 12;
|
|
6226
|
+
if (this.#isPaletteComponent(item)) {
|
|
6227
|
+
const row = this.targetItem;
|
|
6228
|
+
if (row && !this.isRowInlineDropLocked(row))
|
|
6229
|
+
return Math.max(2, this.#availableColumns(row));
|
|
6230
|
+
return item.type === 'break' ? 12 : 6;
|
|
6231
|
+
}
|
|
6232
|
+
return +(item.layout?.columns || 12);
|
|
6233
|
+
};
|
|
6207
6234
|
/** True nếu component có expression điều kiện — drives the "conditional" status chip. */
|
|
6208
6235
|
hasConditional = (item) => {
|
|
6209
6236
|
const p = item.properties || {};
|
|
@@ -6357,6 +6384,23 @@ class SdFormBuilder {
|
|
|
6357
6384
|
};
|
|
6358
6385
|
isRowFull = (row) => this.#usedColumns(row) >= 12;
|
|
6359
6386
|
isRowInlineDropLocked = (row) => this.#availableColumns(row) < 2;
|
|
6387
|
+
shouldShowRowInsertionPlaceholder = (row, edge = this.rowInsertionEdge()) => {
|
|
6388
|
+
if (this.dragSource() !== 'palette' || this.targetItem !== row || this.rowInsertionEdge() !== edge)
|
|
6389
|
+
return false;
|
|
6390
|
+
return this.inlineDropTargetRow() !== row || this.isRowInlineDropLocked(row);
|
|
6391
|
+
};
|
|
6392
|
+
onRowItemsDropEntered = (row) => {
|
|
6393
|
+
if (this.dragSource() === 'palette' && !this.isRowInlineDropLocked(row)) {
|
|
6394
|
+
this.inlineDropTargetRow.set(row);
|
|
6395
|
+
this.#ref.markForCheck();
|
|
6396
|
+
}
|
|
6397
|
+
};
|
|
6398
|
+
onRowItemsDropExited = (row) => {
|
|
6399
|
+
if (this.inlineDropTargetRow() === row) {
|
|
6400
|
+
this.inlineDropTargetRow.set(undefined);
|
|
6401
|
+
this.#ref.markForCheck();
|
|
6402
|
+
}
|
|
6403
|
+
};
|
|
6360
6404
|
canEnterRowDropList = (drag, drop) => {
|
|
6361
6405
|
const row = this.#rowForItems(drop.data);
|
|
6362
6406
|
if (!row)
|
|
@@ -6612,7 +6656,40 @@ class SdFormBuilder {
|
|
|
6612
6656
|
const lastIndex = lastItem ? scope.findIndex(component => component.id === lastItem.id) : -1;
|
|
6613
6657
|
return lastIndex >= 0 ? lastIndex + 1 : scope.length;
|
|
6614
6658
|
};
|
|
6659
|
+
#scopeIndexBeforeRow = (row) => {
|
|
6660
|
+
const scope = this.#scope();
|
|
6661
|
+
const firstItem = row.items[0];
|
|
6662
|
+
const firstIndex = firstItem ? scope.findIndex(component => component.id === firstItem.id) : -1;
|
|
6663
|
+
return firstIndex >= 0 ? firstIndex : scope.length;
|
|
6664
|
+
};
|
|
6665
|
+
#rowHitFromPointer = (pointer) => {
|
|
6666
|
+
const directRowEl = document.elementFromPoint(pointer.x, pointer.y)?.closest('.fb-row');
|
|
6667
|
+
const rowElements = directRowEl ? [directRowEl] : Array.from(this.#host.nativeElement.querySelectorAll('.fb-row[id]'));
|
|
6668
|
+
let best;
|
|
6669
|
+
for (const rowEl of rowElements) {
|
|
6670
|
+
const rect = rowEl.getBoundingClientRect();
|
|
6671
|
+
const edge = pointer.y < rect.top + rect.height / 2 ? 'before' : 'after';
|
|
6672
|
+
const distance = pointer.y < rect.top ? rect.top - pointer.y : pointer.y > rect.bottom ? pointer.y - rect.bottom : 0;
|
|
6673
|
+
if (!best || distance < best.distance) {
|
|
6674
|
+
best = { rowEl, edge, distance };
|
|
6675
|
+
}
|
|
6676
|
+
}
|
|
6677
|
+
if (!best?.rowEl.id)
|
|
6678
|
+
return undefined;
|
|
6679
|
+
const row = this.dragDropRows.find(item => item.id === best.rowEl.id);
|
|
6680
|
+
return row ? { row, edge: best.edge } : undefined;
|
|
6681
|
+
};
|
|
6615
6682
|
#paletteDropPlacement = (containerData, currentIndex, item) => {
|
|
6683
|
+
if (containerData === this.dragDropRows) {
|
|
6684
|
+
const targetRow = this.targetItem;
|
|
6685
|
+
if (targetRow) {
|
|
6686
|
+
return {
|
|
6687
|
+
index: this.rowInsertionEdge() === 'before' ? this.#scopeIndexBeforeRow(targetRow) : this.#scopeIndexAfterRow(targetRow),
|
|
6688
|
+
columns: item.type === 'break' ? undefined : '12',
|
|
6689
|
+
};
|
|
6690
|
+
}
|
|
6691
|
+
return { index: this.#scopeIndexFromDrop(containerData, currentIndex), columns: item.type === 'break' ? undefined : '12' };
|
|
6692
|
+
}
|
|
6616
6693
|
const row = this.#rowForItems(containerData);
|
|
6617
6694
|
if (!row || item.type === 'break') {
|
|
6618
6695
|
return { index: this.#scopeIndexFromDrop(containerData, currentIndex) };
|
|
@@ -6662,7 +6739,7 @@ class SdFormBuilder {
|
|
|
6662
6739
|
component.key = GenerateKey();
|
|
6663
6740
|
};
|
|
6664
6741
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdFormBuilder, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
6665
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdFormBuilder, isStandalone: true, selector: "sd-form-builder", inputs: { formGeneric: { classPropertyName: "formGeneric", publicName: "formGeneric", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "popupViewJSON", first: true, predicate: ["popupViewJSON"], descendants: true, isSignal: true }, { propertyName: "popupConfigureVariables", first: true, predicate: ["popupConfigureVariables"], descendants: true, isSignal: true }, { propertyName: "configureValidation", first: true, predicate: ConfigureValidationComponent, descendants: true, isSignal: true }, { propertyName: "formRender", first: true, predicate: SdFormRender, descendants: true, isSignal: true }], ngImport: i0, template: "<!-- cdkDropListGroup: auto-connect t\u1EA5t c\u1EA3 cdkDropList trong palette, canvas rows, group bodies.\n [class.fb-shell--dragging]: JS-driven flag \u0111\u1EC3 \u1EA9n hover/actions/resize to\u00E0n di\u1EC7n\n trong l\u00FAc drag (CSS :has() kh\u00F4ng b\u1EAFt 100% tr\u01B0\u1EDDng h\u1EE3p v\u1EDBi CDK preview clone). -->\n<div\n class=\"fb-shell\"\n cdkDropListGroup\n [class.fb-shell--dragging]=\"isAnyDragging()\"\n [class.fb-shell--palette-dragging]=\"dragSource() === 'palette'\"\n [class.fb-shell--resizing]=\"isResizing()\">\n <ng-template #dragPreviewTemplate>\n <span class=\"fb-drag-preview__glyph\" aria-hidden=\"true\"></span>\n <span class=\"fb-drag-preview__body\" aria-hidden=\"true\">\n <span class=\"fb-drag-preview__line is-strong\"></span>\n <span class=\"fb-drag-preview__line\"></span>\n </span>\n </ng-template>\n <ng-template #dropPlaceholderTemplate let-item>\n <span class=\"fb-drop-placeholder__icon\" aria-hidden=\"true\">\n <span class=\"msi\">{{ placeholderSymbolFor(item) }}</span>\n </span>\n <span class=\"fb-drop-placeholder__body\" aria-hidden=\"true\">\n <span class=\"fb-drop-placeholder__title\">{{ placeholderTitleFor(item) }}</span>\n <span class=\"fb-drop-placeholder__meta\">{{ placeholderMetaFor(item) }}</span>\n </span>\n </ng-template>\n\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 LEFT PALETTE \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <aside class=\"fb-palette\">\n <div class=\"fb-panel-header\">\n <div class=\"fb-panel-header__title\">{{ 'core.component.form-builder.components' | translate }}</div>\n </div>\n\n <!-- Search pill -->\n <div class=\"fb-palette__search\">\n <span class=\"msi\" aria-hidden=\"true\">search</span>\n <input\n type=\"text\"\n [placeholder]=\"'core.component.form-builder.search-components' | translate\"\n [value]=\"paletteSearch()\"\n (input)=\"paletteSearch.set($any($event.target).value)\" />\n <span class=\"kbd\">\u2318K</span>\n </div>\n\n <div class=\"fb-palette__scroll\">\n @for (group of paletteGroups(); track group.key) {\n <div class=\"fb-palette__group-label\">{{ group.label }}</div>\n <div class=\"fb-palette__list\" cdkDropList [cdkDropListData]=\"group.items\" [cdkDropListEnterPredicate]=\"noReturnPredicate\">\n @for (formBuilderComponent of group.items; track formBuilderComponent.type) {\n <div\n cdkDrag\n [cdkDragData]=\"formBuilderComponent\"\n class=\"fb-palette-item\"\n [matTooltip]=\"formBuilderComponent.name\"\n matTooltipPosition=\"right\"\n (click)=\"addComponent(formBuilderComponent)\"\n (cdkDragStarted)=\"onPaletteDragStarted()\"\n (cdkDragMoved)=\"onAnyDragMoved($event)\"\n (cdkDragEnded)=\"onAnyDragEnded()\"\n aria-hidden=\"true\">\n <span class=\"fb-palette-item__icon\"\n ><span class=\"msi\">{{ formBuilderComponent.symbol }}</span></span\n >\n <span class=\"fb-palette-item__name\">{{ formBuilderComponent.name }}</span>\n <span class=\"fb-palette-item__drag\"><span class=\"msi\">drag_indicator</span></span>\n <div *cdkDragPreview class=\"fb-drag-preview\" aria-hidden=\"true\">\n <ng-container *ngTemplateOutlet=\"dragPreviewTemplate\"></ng-container>\n </div>\n <div *cdkDragPlaceholder class=\"fb-drop-placeholder\" aria-hidden=\"true\">\n <ng-container *ngTemplateOutlet=\"dropPlaceholderTemplate; context: { $implicit: formBuilderComponent }\"></ng-container>\n </div>\n </div>\n }\n </div>\n }\n </div>\n </aside>\n\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 CENTER CANVAS \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <section class=\"fb-canvas\">\n @let _editingGroup = editingGroup();\n @if (_editingGroup) {\n <!-- \u2500\u2500 Drill-in header: \u0111ang thi\u1EBFt k\u1EBF children c\u1EE7a 1 group \u2500\u2500 -->\n <header class=\"fb-canvas__header fb-canvas__header--group\">\n <div class=\"fb-canvas__title fb-groupedit__crumb\">\n <button\n type=\"button\"\n class=\"fb-icon-btn\"\n [matTooltip]=\"'core.component.form-builder.back-to-design' | translate\"\n (click)=\"cancelGroupEdit()\">\n <span class=\"msi\">chevron_left</span>\n </button>\n <span class=\"fb-groupedit__icon\"\n ><span class=\"msi\">{{ _editingGroup.properties?.icon || 'category' }}</span></span\n >\n <span>{{ _editingGroup.label || ('core.component.form-builder.edit-group' | translate) }}</span>\n <span class=\"fb-groupedit__hint\">{{ 'core.component.form-builder.edit-group' | translate }}</span>\n </div>\n <div class=\"fb-canvas__actions\">\n <sd-button type=\"outline\" [title]=\"'core.component.form-builder.cancel' | translate\" (click)=\"cancelGroupEdit()\"></sd-button>\n <sd-button\n type=\"fill\"\n color=\"primary\"\n prefixIcon=\"check\"\n [title]=\"'core.component.form-builder.confirm' | translate\"\n (click)=\"confirmGroupEdit()\"></sd-button>\n </div>\n </header>\n } @else {\n <header class=\"fb-canvas__header\">\n <div class=\"fb-canvas__title\">\n <span>{{ 'core.component.form-builder.layout' | translate }}</span>\n </div>\n <div class=\"fb-canvas__actions\">\n <!-- Toolbar cluster: view JSON / variables / validation -->\n <button type=\"button\" class=\"fb-icon-btn\" [matTooltip]=\"'core.component.form-builder.view-json' | translate\" (click)=\"viewJSON()\">\n <span class=\"msi\">data_object</span>\n </button>\n <button\n type=\"button\"\n class=\"fb-icon-btn\"\n [matTooltip]=\"'core.component.form-builder.config-variable' | translate\"\n (click)=\"configureVariables()\">\n <span class=\"msi\">data_array</span>\n </button>\n <button\n type=\"button\"\n class=\"fb-icon-btn\"\n [matTooltip]=\"'core.component.form-builder.config-validation-warning-error' | translate\"\n (click)=\"openConfigureValidation()\">\n <span class=\"msi\">rule</span>\n </button>\n\n <span class=\"fb-divider\"></span>\n\n <!-- M3 segmented Design/Preview toggle -->\n <div class=\"fb-mode-toggle\" role=\"tablist\">\n <button type=\"button\" role=\"tab\" [class.is-active]=\"!isPreview()\" [attr.aria-selected]=\"!isPreview()\" (click)=\"setMode(false)\">\n <span class=\"msi\">edit</span>\n <span>{{ 'core.component.form-builder.design' | translate }}</span>\n </button>\n <button type=\"button\" role=\"tab\" [class.is-active]=\"isPreview()\" [attr.aria-selected]=\"isPreview()\" (click)=\"setMode(true)\">\n <span class=\"msi\">visibility</span>\n <span>{{ 'core.component.form-builder.preview' | translate }}</span>\n </button>\n </div>\n </div>\n </header>\n }\n\n <div class=\"fb-canvas__body\" [class.is-preview]=\"isPreview()\" id=\"frmComponent\">\n @if (isPreview()) {\n <!-- \u2500\u2500 Preview mode \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"fb-preview-card\">\n <div class=\"fb-preview-card__chip\">\n <span class=\"msi\">visibility</span>\n {{ 'core.component.form-builder.preview-runtime' | translate }}\n </div>\n <sd-form-render [configuration]=\"{ components: this.components, validations: this.validations }\"></sd-form-render>\n <div class=\"fb-preview-card__footer\">\n <sd-button type=\"outline\" [title]=\"'core.component.form-builder.cancel' | translate\" (click)=\"setMode(false)\"></sd-button>\n <sd-button\n type=\"fill\"\n color=\"primary\"\n [title]=\"'core.component.form-builder.submit' | translate\"\n (click)=\"onValidate()\"></sd-button>\n </div>\n </div>\n } @else {\n <!-- \u2500\u2500 Design mode \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (!dragDropRows.length) {\n <div class=\"fb-empty\">\n <div class=\"fb-empty__art\"><span class=\"msi\">dashboard_customize</span></div>\n <div class=\"fb-empty__title\">{{ 'core.component.form-builder.empty-title' | translate }}</div>\n <div class=\"fb-empty__hint\">\n {{ 'core.component.form-builder.empty-hint' | translate }}\n <span class=\"fb-empty__kbd\"><span class=\"msi\">keyboard</span>\u2318 K</span>\n </div>\n </div>\n }\n\n <div\n cdkDropList\n cdkDropListOrientation=\"vertical\"\n [cdkDropListData]=\"dragDropRows\"\n (cdkDropListDropped)=\"drop($event)\"\n (click)=\"clickFormContentEmpty()\"\n aria-hidden=\"true\">\n @for (row of dragDropRows; track row.id) {\n <div\n cdkDrag\n [cdkDragData]=\"row\"\n class=\"fb-row\"\n (mouseover)=\"onMouseover($event, row)\"\n (focus)=\"onFocus($event)\"\n (cdkDragStarted)=\"onRowDragStarted()\"\n (cdkDragMoved)=\"onAnyDragMoved($event)\"\n (cdkDragEnded)=\"onAnyDragEnded()\"\n [class.is-drag-target]=\"targetItem === row\"\n [class.is-row-full]=\"isRowFull(row)\"\n [class.is-row-inline-locked]=\"isRowInlineDropLocked(row)\"\n [class.is-resizing-row]=\"resizeState()?.rowId === row.id\"\n [id]=\"row.id\"\n aria-hidden=\"true\">\n <div class=\"fb-row__drag\" [matTooltip]=\"'core.component.form-builder.drag-vertically' | translate\" cdkDragHandle>\n <span class=\"msi\">drag_indicator</span>\n </div>\n <div *cdkDragPreview class=\"fb-drag-preview\" aria-hidden=\"true\">\n <ng-container *ngTemplateOutlet=\"dragPreviewTemplate\"></ng-container>\n </div>\n <div *cdkDragPlaceholder class=\"fb-drop-placeholder fb-drop-placeholder--row\" aria-hidden=\"true\">\n <ng-container *ngTemplateOutlet=\"dropPlaceholderTemplate; context: { $implicit: row }\"></ng-container>\n </div>\n <!-- Quick-add break button \u2014 show on row hover, insert break after this row -->\n <button\n type=\"button\"\n class=\"fb-row-break-btn\"\n [matTooltip]=\"'core.component.form-builder.break' | translate\"\n matTooltipPosition=\"right\"\n (click)=\"insertBreakAfter(row); $event.stopPropagation()\">\n <span class=\"msi\">horizontal_rule</span>\n </button>\n\n <div\n class=\"fb-row__items\"\n style=\"display: flex; flex: 1; gap: var(--gap-2)\"\n cdkDropList\n cdkDropListOrientation=\"horizontal\"\n [cdkDropListData]=\"row.items\"\n [cdkDropListEnterPredicate]=\"canEnterRowDropList\"\n (cdkDropListDropped)=\"drop($event)\">\n @for (item of row.items; track item.id; let i = $index) {\n <!-- Group component renders as outlined card with colored header bar -->\n @if (item.type === 'group') {\n @let g = $any(item);\n @let cv = groupColorVars(g.properties?.color);\n <div\n cdkDrag\n [cdkDragData]=\"item\"\n [id]=\"item.id\"\n class=\"fb-group column-{{ item.layout?.columns || 12 }}\"\n [class.fb-item]=\"false\"\n [class.is-selected]=\"item.id === selectedComponent()?.id\"\n [class.is-hidden]=\"item.properties?.hidden\"\n [style.flex]=\"'0 0 calc(' + +(item.layout?.columns || 12) + '/12 * 100% - var(--gap-2))'\"\n [style.maxWidth]=\"'calc(' + +(item.layout?.columns || 12) + '/12 * 100% - var(--gap-2))'\"\n (click)=\"selectComponent(item); $event.stopPropagation()\"\n (cdkDragStarted)=\"dragStartComponentItem($event)\"\n (cdkDragMoved)=\"onAnyDragMoved($event)\"\n (cdkDragEnded)=\"dragEndComponentItem($event); onAnyDragEnded()\"\n aria-hidden=\"true\">\n <div *cdkDragPreview class=\"fb-drag-preview\" aria-hidden=\"true\">\n <ng-container *ngTemplateOutlet=\"dragPreviewTemplate\"></ng-container>\n </div>\n <div *cdkDragPlaceholder class=\"fb-drop-placeholder\" aria-hidden=\"true\">\n <ng-container *ngTemplateOutlet=\"dropPlaceholderTemplate; context: { $implicit: item }\"></ng-container>\n </div>\n <!-- cdkDragHandle: ch\u1EC9 cho ph\u00E9p k\u00E9o group qua header \u2014 body free cho click v\u00E0o child. -->\n <div class=\"fb-group__header\" cdkDragHandle [style.background]=\"cv.bg\" [style.color]=\"cv.fg\">\n <span class=\"fb-group__icon\"\n ><span class=\"msi\">{{ g.properties?.icon || 'category' }}</span></span\n >\n <span class=\"fb-group__label\">{{ item.label }}</span>\n @if (hasConditional(item)) {\n <span class=\"fb-group__conditional-chip\">\n <span class=\"msi\" style=\"font-size: 12px\">bolt</span>\n {{ 'core.component.form-builder.conditional' | translate }}\n </span>\n }\n <span class=\"fb-group__count\">{{ g.components?.length || 0 }} fields</span>\n </div>\n <!-- Floating action toolbar -->\n <div class=\"fb-actions\">\n <!-- why: Detail = m\u1EDF canvas thi\u1EBFt k\u1EBF ri\u00EAng cho group (thay cho drag/drop trong group). -->\n <button\n type=\"button\"\n class=\"fb-icon-btn\"\n [matTooltip]=\"'core.component.form-builder.edit-group' | translate\"\n (click)=\"enterGroupEdit(g); $event.stopPropagation()\">\n <span class=\"msi\">open_in_full</span>\n </button>\n <button\n type=\"button\"\n class=\"fb-icon-btn\"\n [matTooltip]=\"'core.component.form-builder.duplicate' | translate\"\n (click)=\"onDuplicate(item); $event.stopPropagation()\">\n <span class=\"msi\">content_copy</span>\n </button>\n <button\n type=\"button\"\n class=\"fb-icon-btn\"\n [class.warning]=\"item.properties?.hidden\"\n [class.on]=\"item.properties?.hidden\"\n [matTooltip]=\"\n item.properties?.hidden\n ? ('core.component.form-builder.show' | translate)\n : ('core.component.form-builder.hide' | translate)\n \"\n (click)=\"onChangeHidden(item); $event.stopPropagation()\">\n <span class=\"msi\">{{ item.properties?.hidden ? 'visibility_off' : 'visibility' }}</span>\n </button>\n <button\n type=\"button\"\n class=\"fb-icon-btn danger\"\n [matTooltip]=\"'core.component.form-builder.delete' | translate\"\n (click)=\"removeComponent(item.id); $event.stopPropagation()\">\n <span class=\"msi\">delete</span>\n </button>\n </div>\n\n <!-- Static preview: children render read-only (kh\u00F4ng drag/resize/select).\n S\u1EEDa n\u1ED9i dung group qua n\u00FAt Detail (enterGroupEdit). -->\n <div class=\"fb-group__body fb-group__body--static\" (click)=\"enterGroupEdit(g); $event.stopPropagation()\">\n @if (!g.components?.length) {\n <div class=\"fb-group__empty\">\n <span class=\"msi\" style=\"font-size: 16px; margin-right: 6px\">open_in_full</span>\n {{ 'core.component.form-builder.group-empty-hint' | translate }}\n </div>\n }\n @for (child of g.components; track child.id) {\n <div\n class=\"fb-item is-child fb-item--static column-{{ child.layout?.columns || 12 }}\"\n [class.is-hidden]=\"child.properties?.hidden\">\n <ng-container *ngTemplateOutlet=\"controlTemplate; context: { $implicit: child }\"> </ng-container>\n </div>\n }\n </div>\n </div>\n } @else if (item.type === 'break') {\n <!-- Break: row separator c\u1ED1 \u0111\u1ECBnh. Render 12-col line, kh\u00F4ng c\u00F3 resize/edit attribute. -->\n <div\n cdkDrag\n [cdkDragData]=\"item\"\n [id]=\"item.id\"\n class=\"fb-item is-break column-12\"\n [class.is-selected]=\"item.id === selectedComponent()?.id\"\n [class.is-hidden]=\"item.properties?.hidden\"\n (click)=\"selectComponent(item); $event.stopPropagation()\"\n (cdkDragStarted)=\"dragStartComponentItem($event)\"\n (cdkDragMoved)=\"onAnyDragMoved($event)\"\n (cdkDragEnded)=\"dragEndComponentItem($event); onAnyDragEnded()\"\n aria-hidden=\"true\">\n <div *cdkDragPreview class=\"fb-drag-preview\" aria-hidden=\"true\">\n <ng-container *ngTemplateOutlet=\"dragPreviewTemplate\"></ng-container>\n </div>\n <div *cdkDragPlaceholder class=\"fb-drop-placeholder\" aria-hidden=\"true\">\n <ng-container *ngTemplateOutlet=\"dropPlaceholderTemplate; context: { $implicit: item }\"></ng-container>\n </div>\n <div class=\"fb-break-line\">\n <span class=\"msi\">horizontal_rule</span>\n <span class=\"fb-break-label\">{{ 'core.component.form-builder.break' | translate }}</span>\n </div>\n <div class=\"fb-actions\">\n <button\n type=\"button\"\n class=\"fb-icon-btn\"\n [class.warning]=\"item.properties?.hidden\"\n [class.on]=\"item.properties?.hidden\"\n [matTooltip]=\"\n item.properties?.hidden\n ? ('core.component.form-builder.show' | translate)\n : ('core.component.form-builder.hide' | translate)\n \"\n (click)=\"onChangeHidden(item); $event.stopPropagation()\">\n <span class=\"msi\">{{ item.properties?.hidden ? 'visibility_off' : 'visibility' }}</span>\n </button>\n <button\n type=\"button\"\n class=\"fb-icon-btn danger\"\n [matTooltip]=\"'core.component.form-builder.delete' | translate\"\n (click)=\"removeComponent(item.id); $event.stopPropagation()\">\n <span class=\"msi\">delete</span>\n </button>\n </div>\n </div>\n } @else {\n <!-- Regular form component -->\n <div\n cdkDrag\n [cdkDragData]=\"item\"\n [id]=\"item.id\"\n class=\"fb-item column-{{ item.layout?.columns || 12 }}\"\n [class.is-selected]=\"item.id === selectedComponent()?.id\"\n [class.is-hidden]=\"item.properties?.hidden\"\n [class.is-readonly]=\"item.properties?.viewed\"\n [class.is-resizing]=\"resizeState()?.itemId === item.id\"\n (click)=\"selectComponent(item); $event.stopPropagation()\"\n (cdkDragStarted)=\"dragStartComponentItem($event)\"\n (cdkDragMoved)=\"onAnyDragMoved($event)\"\n (cdkDragEnded)=\"dragEndComponentItem($event); onAnyDragEnded()\"\n aria-hidden=\"true\">\n <div *cdkDragPreview class=\"fb-drag-preview\" aria-hidden=\"true\">\n <ng-container *ngTemplateOutlet=\"dragPreviewTemplate\"></ng-container>\n </div>\n <div *cdkDragPlaceholder class=\"fb-drop-placeholder\" aria-hidden=\"true\">\n <ng-container *ngTemplateOutlet=\"dropPlaceholderTemplate; context: { $implicit: item }\"></ng-container>\n </div>\n\n <!-- Status chip (mutually exclusive priority: hidden > readonly > conditional) -->\n @if (item.properties?.hidden) {\n <span class=\"fb-status-chip hidden\">\n <span class=\"msi\">visibility_off</span>\n {{ 'core.component.form-builder.hidden' | translate }}\n </span>\n } @else if (item.properties?.viewed) {\n <span class=\"fb-status-chip readonly\">\n <span class=\"msi\">edit_off</span>\n {{ 'core.component.form-builder.read-only' | translate }}\n </span>\n } @else if (hasConditional(item)) {\n <span class=\"fb-status-chip conditional\">\n <span class=\"msi\">bolt</span>\n {{ 'core.component.form-builder.conditional' | translate }}\n </span>\n }\n\n <!-- Floating action toolbar pill -->\n <div class=\"fb-actions\">\n <button\n type=\"button\"\n class=\"fb-icon-btn\"\n [class.warning]=\"item.properties?.viewed\"\n [class.on]=\"item.properties?.viewed\"\n [matTooltip]=\"\n item.properties?.viewed\n ? ('core.component.form-builder.unset-readonly' | translate)\n : ('core.component.form-builder.set-readonly' | translate)\n \"\n (click)=\"onChangeViewed(item); $event.stopPropagation()\">\n <span class=\"msi\">edit_off</span>\n </button>\n <button\n type=\"button\"\n class=\"fb-icon-btn\"\n [matTooltip]=\"'core.component.form-builder.duplicate' | translate\"\n (click)=\"onDuplicate(item); $event.stopPropagation()\">\n <span class=\"msi\">content_copy</span>\n </button>\n <button\n type=\"button\"\n class=\"fb-icon-btn\"\n [class.warning]=\"item.properties?.hidden\"\n [class.on]=\"item.properties?.hidden\"\n [matTooltip]=\"\n item.properties?.hidden\n ? ('core.component.form-builder.show' | translate)\n : ('core.component.form-builder.hide' | translate)\n \"\n (click)=\"onChangeHidden(item); $event.stopPropagation()\">\n <span class=\"msi\">{{ item.properties?.hidden ? 'visibility_off' : 'visibility' }}</span>\n </button>\n <button\n type=\"button\"\n class=\"fb-icon-btn danger\"\n [matTooltip]=\"'core.component.form-builder.delete' | translate\"\n (click)=\"removeComponent(item.id); $event.stopPropagation()\">\n <span class=\"msi\">delete</span>\n </button>\n </div>\n\n <ng-container *ngTemplateOutlet=\"controlTemplate; context: { $implicit: item }\"> </ng-container>\n\n @if (resizeState()?.itemId === item.id) {\n <span class=\"fb-resize-chip\">{{ resizeState()?.columns }}/12</span>\n }\n\n <!-- Resize handle (pill) \u2014 cdkDragLockAxis=\"x\" \u0111\u1EC3 ch\u1EC9 k\u00E9o ngang.\n Wrapping cdkDropList c\u00F3 enterPredicate=false, kh\u00F4ng nh\u1EADn drop. -->\n <div cdkDropList cdkDropListOrientation=\"horizontal\" [cdkDropListEnterPredicate]=\"noReturnPredicate\">\n <div\n cdkDrag\n cdkDragLockAxis=\"x\"\n class=\"fb-resize\"\n (cdkDragStarted)=\"startResizeControl(item, row)\"\n (cdkDragMoved)=\"changeSizeControl($event, item, row.items, i)\"\n (cdkDragEnded)=\"endResizeControl($event)\"\n [matTooltip]=\"'core.component.form-builder.flex' | translate\"\n aria-label=\"resize\"></div>\n </div>\n </div>\n }\n }\n </div>\n </div>\n }\n </div>\n }\n </div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 RIGHT ATTRIBUTES \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <aside class=\"fb-attrs\">\n <div class=\"fb-panel-header\">\n <div class=\"fb-panel-header__title\">{{ 'core.component.form-builder.attribute' | translate }}</div>\n </div>\n\n @let _selectedComponent = selectedComponent();\n @if (_selectedComponent) {\n <!-- Selection header: type icon + name + key/type -->\n <div class=\"fb-attrs__selection\">\n <span class=\"fb-attrs__sel-icon\"\n ><span class=\"msi\">{{ symbolFor(_selectedComponent) }}</span></span\n >\n <div class=\"fb-attrs__sel-meta\">\n <div class=\"fb-attrs__sel-name\">{{ $any(_selectedComponent).label || ('core.component.form-builder.untitled' | translate) }}</div>\n <div class=\"fb-attrs__sel-key\">\n @if ($any(_selectedComponent).key) {\n {{ $any(_selectedComponent).key }} \u00B7 {{ typeLabelFor(_selectedComponent) }}\n } @else {\n {{ typeLabelFor(_selectedComponent) }}\n }\n </div>\n </div>\n </div>\n\n <div class=\"fb-attrs__body\" aria-hidden=\"true\">\n @if (_selectedComponent.type === 'textfield') {\n <textfield-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"_selectedComponent\"></textfield-attribute>\n } @else if (_selectedComponent.type === 'textarea') {\n <textarea-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"_selectedComponent\"></textarea-attribute>\n } @else if (_selectedComponent.type === 'chip-string') {\n <chip-string-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"_selectedComponent\"></chip-string-attribute>\n } @else if (_selectedComponent.type === 'chip-calendar') {\n <chip-calendar-attribute\n [components]=\"components\"\n [variables]=\"variables\"\n [component]=\"_selectedComponent\"></chip-calendar-attribute>\n } @else if (_selectedComponent.type === 'number') {\n <number-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"_selectedComponent\"></number-attribute>\n } @else if (_selectedComponent.type === 'datetime') {\n <datetime-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"_selectedComponent\"></datetime-attribute>\n } @else if (_selectedComponent.type === 'select') {\n <select-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"_selectedComponent\"></select-attribute>\n } @else if (_selectedComponent.type === 'radio') {\n <radio-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"_selectedComponent\"></radio-attribute>\n } @else if (_selectedComponent.type === 'checkbox') {\n <checkbox-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"_selectedComponent\"></checkbox-attribute>\n } @else if (_selectedComponent.type === 'html') {\n <html-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"_selectedComponent\"></html-attribute>\n } @else if (_selectedComponent.type === 'upload') {\n <upload-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"_selectedComponent\"></upload-attribute>\n } @else if (_selectedComponent.type === 'table') {\n <table-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"_selectedComponent\"></table-attribute>\n } @else if (_selectedComponent.type === 'group') {\n <group-attribute [group]=\"$any(_selectedComponent)\"></group-attribute>\n }\n </div>\n } @else {\n <div class=\"fb-attrs__placeholder\">\n <span class=\"msi\">ads_click</span>\n <div class=\"title\">{{ 'core.component.form-builder.no-selection-title' | translate }}</div>\n <div class=\"hint\">{{ 'core.component.form-builder.no-selection-hint' | translate }}</div>\n </div>\n }\n </aside>\n</div>\n\n<!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 Reusable control template \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n<ng-template #controlTemplate let-item>\n @if (item.type === 'textfield') {\n <textfield-control [component]=\"item\"></textfield-control>\n } @else if (item.type === 'textarea') {\n <textarea-control [component]=\"item\"></textarea-control>\n } @else if (item.type === 'chip-string') {\n <chip-string-control [component]=\"item\"></chip-string-control>\n } @else if (item.type === 'chip-calendar') {\n <chip-calendar-control [component]=\"item\"></chip-calendar-control>\n } @else if (item.type === 'number') {\n <number-control [component]=\"item\"></number-control>\n } @else if (item.type === 'datetime') {\n <datetime-control [component]=\"item\"></datetime-control>\n } @else if (item.type === 'select') {\n <select-control [component]=\"item\"></select-control>\n } @else if (item.type === 'radio') {\n <radio-control [component]=\"item\"></radio-control>\n } @else if (item.type === 'checkbox') {\n <checkbox-control [component]=\"item\"></checkbox-control>\n } @else if (item.type === 'html') {\n <html-control [component]=\"item\"></html-control>\n } @else if (item.type === 'upload') {\n <upload-control [component]=\"item\"></upload-control>\n } @else if (item.type === 'table') {\n <table-control [component]=\"item\"></table-control>\n }\n</ng-template>\n\n<!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 JSON / variables modals \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n<sd-modal title=\"JSON\" view=\"dialog\" width=\"80%\" #popupViewJSON>\n <sd-textarea [(model)]=\"jsonString\"></sd-textarea>\n <ng-container sdFooter>\n <sd-button\n type=\"fill\"\n [title]=\"'core.component.form-builder.save' | translate\"\n (click)=\"updateJSON()\"\n prefixIcon=\"save\"\n color=\"primary\"></sd-button>\n </ng-container>\n</sd-modal>\n<sd-modal [title]=\"'core.component.form-builder.config-variable' | translate\" view=\"dialog\" width=\"800px\" #popupConfigureVariables>\n @for (variable of clonedVariables; track variable.id) {\n <div class=\"d-flex align-items-center\" style=\"gap: 8px\">\n <sd-input [form]=\"form\" [label]=\"'core.component.form-builder.field-key' | translate\" [(model)]=\"variable.key\" required></sd-input>\n <sd-input\n [form]=\"form\"\n [label]=\"'core.component.form-builder.display-label' | translate\"\n [(model)]=\"variable.label\"\n required></sd-input>\n <sd-button prefixIcon=\"delete\" (click)=\"removeVariables(variable.id)\" color=\"error\"></sd-button>\n </div>\n }\n <a href=\"javascript:;\" (click)=\"addVariables()\">{{ 'core.component.form-builder.add-variable' | translate }}</a>\n <ng-container sdFooter>\n <sd-button\n type=\"fill\"\n [title]=\"'core.component.form-builder.save' | translate\"\n (click)=\"updateVariables()\"\n prefixIcon=\"save\"\n color=\"primary\"></sd-button>\n </ng-container>\n</sd-modal>\n<configure-validation (accept)=\"onUpdateValidations($event)\"></configure-validation>\n", styles: ["@charset \"UTF-8\";:host{--md-sys-color-primary: var(--sd-primary, #2a66f4);--md-sys-color-on-primary: #ffffff;--md-sys-color-primary-container: var(--sd-primary-light, #eaf1ff);--md-sys-color-on-primary-container: var(--sd-primary, #2a66f4);--md-sys-color-info: var(--sd-info, #2962ff);--md-sys-color-info-container: var(--sd-info-light, #e7e9ff);--md-sys-color-secondary: #625b71;--md-sys-color-secondary-container: #e8def8;--md-sys-color-on-secondary-container: #1d192b;--md-sys-color-surface: #fbfafc;--md-sys-color-surface-container-lowest: #ffffff;--md-sys-color-surface-container-low: #f6f4f8;--md-sys-color-surface-container: #f1eef3;--md-sys-color-surface-container-high: #ebe8ee;--md-sys-color-surface-container-highest: #e6e2ea;--md-sys-color-on-surface: #1c1b1f;--md-sys-color-on-surface-variant: #49454e;--md-sys-color-outline: #79747e;--md-sys-color-outline-variant: #d6d2da;--md-sys-color-error: #b3261e;--md-sys-color-error-container: #f9dedc;--md-sys-color-warning: #8a5400;--md-sys-color-warning-container: #ffddb1;--md-sys-color-success: #1f6f43;--md-sys-color-success-container: #b7f0cb;--h-row: 28px;--h-field: 32px;--h-palette: 36px;--h-header: 36px;--h-section: 28px;--h-action-btn: 24px;--gap-1: 4px;--gap-2: 8px;--gap-3: 12px;--gap-4: 16px;--gap-5: 24px;--r-xs: 4px;--r-sm: 8px;--r-md: 12px;--r-lg: 16px;--r-xl: 28px;--r-pill: 999px;--elev-1: 0 1px 2px 0 rgba(0, 0, 0, .06);--elev-2: 0 2px 6px 0 rgba(0, 0, 0, .08), 0 1px 2px 0 rgba(0, 0, 0, .04);--elev-3: 0 6px 14px 0 rgba(0, 0, 0, .1), 0 2px 4px 0 rgba(0, 0, 0, .06);display:block;width:100%;height:100%;font-family:Roboto,system-ui,sans-serif;font-size:13px;color:var(--md-sys-color-on-surface);background:var(--md-sys-color-surface)}:host ::ng-deep .msi{font-family:Material Icons Outlined;font-weight:400;font-style:normal;line-height:1;letter-spacing:normal;text-transform:none;display:inline-block;white-space:nowrap;word-wrap:normal;direction:ltr;font-feature-settings:\"liga\";-webkit-font-feature-settings:\"liga\";-webkit-font-smoothing:antialiased}:host ::ng-deep .msi.filled{font-family:Material Icons}:host ::ng-deep .mono{font-family:Roboto Mono,ui-monospace,monospace}:host ::ng-deep .fb-icon-btn{width:28px;height:28px;border-radius:var(--r-sm);border:none;background:transparent;color:var(--md-sys-color-on-surface-variant);display:inline-flex;align-items:center;justify-content:center;cursor:pointer;transition:background .12s ease,color .12s ease}:host ::ng-deep .fb-icon-btn:hover{background:color-mix(in srgb,var(--md-sys-color-on-surface) 8%,transparent);color:var(--md-sys-color-on-surface)}:host ::ng-deep .fb-icon-btn.is-active{color:var(--md-sys-color-primary);background:var(--md-sys-color-primary-container)}:host ::ng-deep .fb-icon-btn.danger:hover{color:var(--md-sys-color-error);background:var(--md-sys-color-error-container)}:host ::ng-deep .fb-icon-btn.warning.on{color:var(--md-sys-color-warning);background:var(--md-sys-color-warning-container)}:host ::ng-deep .fb-icon-btn .msi{font-size:18px}:host ::ng-deep .fb-field{display:flex;flex-direction:column;gap:4px}:host ::ng-deep .fb-field__label{font-size:11px;font-weight:500;color:var(--md-sys-color-on-surface-variant)}:host ::ng-deep .fb-field__input{height:var(--h-row);padding:0 10px;border-radius:var(--r-xs);background:var(--md-sys-color-surface-container-lowest);border:1px solid var(--md-sys-color-outline-variant);font-size:12px;display:flex;align-items:center;justify-content:space-between;color:var(--md-sys-color-on-surface)}:host ::ng-deep .fb-field__input.placeholder{color:var(--md-sys-color-on-surface-variant)}:host ::ng-deep .fb-field__input:focus-within,:host ::ng-deep .fb-field__input.focused{border-color:var(--md-sys-color-primary);box-shadow:0 0 0 2px color-mix(in srgb,var(--md-sys-color-primary) 18%,transparent)}:host ::ng-deep .fb-field__input .msi{font-size:16px;color:var(--md-sys-color-on-surface-variant)}:host ::ng-deep .fb-field-row{display:flex;align-items:center;gap:var(--gap-2);justify-content:space-between}:host ::ng-deep .fb-switch{display:inline-flex;width:34px;height:18px;border-radius:999px;background:var(--md-sys-color-surface-container-highest);border:1.5px solid var(--md-sys-color-outline);padding:1px;align-items:center;cursor:pointer;position:relative;flex-shrink:0}:host ::ng-deep .fb-switch .fb-switch__thumb{width:12px;height:12px;border-radius:50%;background:var(--md-sys-color-outline)}:host ::ng-deep .fb-switch.on{background:var(--md-sys-color-primary);border-color:var(--md-sys-color-primary);justify-content:flex-end}:host ::ng-deep .fb-switch.on .fb-switch__thumb{background:var(--md-sys-color-on-primary);width:14px;height:14px}:host ::ng-deep .fb-expr{display:flex;align-items:center;gap:6px;height:var(--h-row);padding:0 6px 0 0;border-radius:var(--r-xs);background:var(--md-sys-color-surface-container-lowest);border:1px solid var(--md-sys-color-outline-variant);font-size:12px;color:var(--md-sys-color-on-surface-variant)}:host ::ng-deep .fb-expr__badge{height:100%;display:inline-flex;align-items:center;justify-content:center;padding:0 6px;background:var(--md-sys-color-primary-container);color:var(--md-sys-color-primary);font-family:Roboto Mono,monospace;font-size:10px;font-weight:500;border-radius:var(--r-xs) 0 0 var(--r-xs)}:host ::ng-deep .fb-expr__code{font-family:Roboto Mono,monospace;flex:1;font-size:11px;color:var(--md-sys-color-on-surface)}:host ::ng-deep .fb-expr__code .var{color:var(--md-sys-color-primary);font-weight:500}:host ::ng-deep .fb-expr__code .op{color:var(--md-sys-color-error)}:host ::ng-deep .fb-expr__code .lit{color:var(--md-sys-color-success)}:host ::ng-deep .fb-expr .empty{color:var(--md-sys-color-on-surface-variant);font-style:italic;padding-left:6px;flex:1}:host ::ng-deep details.fb-section{border-bottom:1px solid var(--md-sys-color-outline-variant);margin:0;padding:0;background:transparent}:host ::ng-deep details.fb-section>summary{list-style:none}:host ::ng-deep details.fb-section>summary::-webkit-details-marker{display:none}:host ::ng-deep .fb-section__head{height:var(--h-section);padding:0 var(--gap-4);display:flex;align-items:center;gap:var(--gap-2);cursor:pointer;font-size:12px;font-weight:500;color:var(--md-sys-color-on-surface);-webkit-user-select:none;user-select:none}:host ::ng-deep .fb-section__head .msi{font-size:16px;color:var(--md-sys-color-on-surface-variant)}:host ::ng-deep .fb-section__head .fb-section__title{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host ::ng-deep .fb-section__head .chev{margin-left:auto;transition:transform .16s ease}:host ::ng-deep details.fb-section[open] .fb-section__head .chev{transform:rotate(90deg)}:host ::ng-deep .fb-section__body{padding:4px var(--gap-4) var(--gap-3);display:flex;flex-direction:column;gap:var(--gap-2)}:host ::ng-deep .fb-columns-list{display:flex;flex-direction:column;gap:var(--gap-1);padding-top:var(--gap-1)}:host ::ng-deep .fb-column-row{display:flex;align-items:center;gap:var(--gap-2);height:40px;padding:6px 10px;border-radius:var(--r-sm);background:var(--md-sys-color-surface-container-lowest);border:1px solid var(--md-sys-color-outline-variant);font-size:12px;cursor:pointer;transition:background .12s ease,border-color .12s ease,box-shadow .12s ease}:host ::ng-deep .fb-column-row:hover{background:color-mix(in srgb,var(--md-sys-color-primary) 4%,transparent);border-color:color-mix(in srgb,var(--md-sys-color-primary) 30%,transparent)}:host ::ng-deep .fb-column-row.is-active{border-color:var(--md-sys-color-primary);background:color-mix(in srgb,var(--md-sys-color-primary) 10%,transparent);box-shadow:0 0 0 1px color-mix(in srgb,var(--md-sys-color-primary) 30%,transparent)}:host ::ng-deep .fb-column-row .fb-column-row__type-icon{width:28px;height:28px;border-radius:var(--r-sm);background:var(--md-sys-color-primary-container);color:var(--md-sys-color-primary);display:inline-flex;align-items:center;justify-content:center;flex-shrink:0}:host ::ng-deep .fb-column-row .fb-column-row__type-icon .msi{font-size:16px}:host ::ng-deep .fb-column-row .fb-column-row__meta{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}:host ::ng-deep .fb-column-row .fb-column-row__meta .fb-column-row__label{font-size:13px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--md-sys-color-on-surface)}:host ::ng-deep .fb-column-row .fb-column-row__meta .fb-column-row__req{color:var(--md-sys-color-error);margin-left:2px;font-weight:600}:host ::ng-deep .fb-column-row .fb-column-row__meta .fb-column-row__sub{font-size:11px;color:var(--md-sys-color-on-surface-variant);font-family:Roboto Mono,ui-monospace,monospace;letter-spacing:.02em}:host ::ng-deep .cdk-drag-preview.fb-drag-preview,:host ::ng-deep .fb-drag-preview{width:132px;height:44px;box-sizing:border-box;border-radius:14px;background:var(--md-sys-color-surface-container-lowest, #ffffff);display:inline-flex;align-items:center;gap:10px;padding:8px 12px;box-shadow:0 14px 32px #0000003d,0 5px 14px #00000029;border:1.5px solid color-mix(in srgb,var(--md-sys-color-primary) 46%,var(--md-sys-color-outline-variant));cursor:grabbing;pointer-events:none;-webkit-user-select:none;user-select:none;opacity:1;overflow:hidden;z-index:10000}:host ::ng-deep .cdk-drag-preview.fb-drag-preview .fb-drag-preview__glyph,:host ::ng-deep .fb-drag-preview .fb-drag-preview__glyph{width:28px;height:28px;border-radius:50%;background:var(--md-sys-color-primary);flex-shrink:0;box-shadow:inset 0 0 0 5px color-mix(in srgb,var(--md-sys-color-on-primary) 18%,transparent)}:host ::ng-deep .cdk-drag-preview.fb-drag-preview .fb-drag-preview__body,:host ::ng-deep .fb-drag-preview .fb-drag-preview__body{flex:1;min-width:0;display:grid;gap:7px}:host ::ng-deep .cdk-drag-preview.fb-drag-preview .fb-drag-preview__line,:host ::ng-deep .fb-drag-preview .fb-drag-preview__line{height:7px;border-radius:999px;background:color-mix(in srgb,var(--md-sys-color-outline-variant) 72%,transparent)}:host ::ng-deep .cdk-drag-preview.fb-drag-preview .fb-drag-preview__line.is-strong,:host ::ng-deep .fb-drag-preview .fb-drag-preview__line.is-strong{width:74%;background:color-mix(in srgb,var(--md-sys-color-primary) 26%,var(--md-sys-color-outline-variant))}:host ::ng-deep .cdk-drag-placeholder.fb-drop-placeholder,:host ::ng-deep .fb-drop-placeholder{box-sizing:border-box;min-height:46px;border:1.5px dashed color-mix(in srgb,var(--md-sys-color-primary) 76%,var(--md-sys-color-outline-variant));border-radius:var(--r-sm);background:linear-gradient(135deg,color-mix(in srgb,var(--md-sys-color-primary) 10%,transparent),transparent 62%),color-mix(in srgb,var(--md-sys-color-primary) 4%,var(--md-sys-color-surface-container-lowest));color:var(--md-sys-color-primary);display:flex;align-items:center;gap:var(--gap-2);padding:8px 10px;pointer-events:none;-webkit-user-select:none;user-select:none;opacity:1!important;transform:none!important;animation:none!important;box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--md-sys-color-primary) 10%,transparent),0 2px 8px color-mix(in srgb,var(--md-sys-color-primary) 10%,transparent)}:host ::ng-deep .fb-drop-placeholder__icon{width:28px;height:28px;border-radius:var(--r-sm);background:var(--md-sys-color-primary-container);color:var(--md-sys-color-primary);display:inline-flex;align-items:center;justify-content:center;flex:0 0 28px}:host ::ng-deep .fb-drop-placeholder__icon .msi{font-size:17px}:host ::ng-deep .fb-drop-placeholder__body{min-width:0;display:flex;flex-direction:column;gap:2px}:host ::ng-deep .fb-drop-placeholder__title{color:var(--md-sys-color-on-surface);font-size:12px;font-weight:600;line-height:1.2;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host ::ng-deep .fb-drop-placeholder__meta{color:var(--md-sys-color-primary);font-size:10px;font-weight:500;line-height:1.2;text-transform:uppercase;letter-spacing:0}:host ::ng-deep .fb-palette__list>.fb-drop-placeholder{display:none!important}:host ::ng-deep .fb-row__items>.fb-drop-placeholder,:host ::ng-deep .fb-group__body>.fb-drop-placeholder{flex:1 1 160px;min-width:min(180px,100%);max-width:100%;align-self:stretch;margin:0 2px}:host ::ng-deep [cdkDropListOrientation=vertical]>.fb-drop-placeholder{width:auto;min-height:50px;margin:6px 14px;align-self:stretch}:host ::ng-deep .fb-drop-placeholder--row{min-height:56px}:host ::ng-deep .cdk-drop-list-receiving{background:color-mix(in srgb,var(--md-sys-color-primary) 3%,transparent);transition:background .16s ease}:host ::ng-deep .fb-group__body.cdk-drop-list-receiving,:host ::ng-deep .fb-group__body.cdk-drop-list-dragging{background:color-mix(in srgb,var(--md-sys-color-primary) 7%,transparent)!important;outline:1px solid color-mix(in srgb,var(--md-sys-color-primary) 72%,transparent);outline-offset:-6px;border-radius:var(--r-sm)}:host ::ng-deep .fb-resize.cdk-drag-preview{display:none!important}:host ::ng-deep .fb-shell--dragging .fb-item:hover,:host ::ng-deep .fb-shell--dragging .fb-group:hover,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-item:hover,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-group:hover{border-color:transparent!important;background:transparent!important}:host ::ng-deep .fb-shell--dragging .fb-row:hover,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-row:hover{outline-color:transparent!important;background:transparent!important}:host ::ng-deep .fb-shell--dragging .fb-row:hover .fb-row__drag,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-row:hover .fb-row__drag{opacity:0!important}:host ::ng-deep .fb-shell--dragging .fb-row.is-drag-target,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-row.is-drag-target{outline-color:color-mix(in srgb,var(--md-sys-color-info) 44%,transparent)!important;background:color-mix(in srgb,var(--md-sys-color-info) 4%,transparent)!important}:host ::ng-deep .fb-shell--dragging .fb-row__drag,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-row__drag{opacity:0!important}:host ::ng-deep .fb-shell--dragging .fb-actions,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-actions{opacity:0!important;pointer-events:none!important;transform:translateY(-2px)!important}:host ::ng-deep .fb-shell--dragging .fb-resize,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-resize{opacity:0!important;pointer-events:none!important}:host ::ng-deep .fb-shell--dragging .fb-row-break-btn,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-row-break-btn{opacity:0!important;pointer-events:none!important}:host ::ng-deep .fb-shell--dragging .fb-palette-item:hover,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-palette-item:hover{background:transparent!important}:host ::ng-deep .fb-shell--dragging .fb-palette-item:hover .fb-palette-item__drag,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-palette-item:hover .fb-palette-item__drag{opacity:0!important}:host ::ng-deep .fb-shell--dragging .fb-group__body,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-group__body{min-height:72px!important;background:color-mix(in srgb,var(--md-sys-color-primary) 4%,transparent)!important;outline:1px dashed color-mix(in srgb,var(--md-sys-color-primary) 48%,transparent)!important;outline-offset:-6px;border-radius:var(--r-sm);transition:background .16s ease,outline .16s ease,min-height .16s ease}:host ::ng-deep .fb-shell--dragging .fb-group__empty,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-group__empty{opacity:1;border-color:color-mix(in srgb,var(--md-sys-color-primary) 55%,transparent);background:color-mix(in srgb,var(--md-sys-color-primary) 7%,transparent);color:var(--md-sys-color-primary)}:host ::ng-deep .fb-shell--dragging .fb-group__body.cdk-drop-list-receiving,:host ::ng-deep .fb-shell--dragging .fb-group__body.cdk-drop-list-dragging,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-group__body.cdk-drop-list-receiving,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-group__body.cdk-drop-list-dragging{background:color-mix(in srgb,var(--md-sys-color-primary) 9%,transparent)!important;outline:1.5px solid var(--md-sys-color-primary)!important}:host ::ng-deep .fb-shell--resizing .fb-row.is-resizing-row{outline-color:color-mix(in srgb,var(--md-sys-color-primary) 42%,transparent);background:color-mix(in srgb,var(--md-sys-color-primary) 3%,transparent)}:host ::ng-deep .fb-shell--resizing .fb-row.is-resizing-row .fb-row__items:before{content:\"\";position:absolute;inset:-3px 0;border-radius:var(--r-sm);background:repeating-linear-gradient(90deg,transparent 0,transparent calc(8.333% - 1px),color-mix(in srgb,var(--md-sys-color-primary) 18%,transparent) calc(8.333% - 1px),color-mix(in srgb,var(--md-sys-color-primary) 18%,transparent) 8.333%),color-mix(in srgb,var(--md-sys-color-primary) 3%,transparent);pointer-events:none;z-index:0}:host ::ng-deep .fb-shell--resizing .fb-row.is-resizing-row .fb-row__items>*{position:relative;z-index:1}:host ::ng-deep .fb-shell--resizing .fb-item.is-resizing{border-color:var(--md-sys-color-primary)!important;background:color-mix(in srgb,var(--md-sys-color-primary) 6%,var(--md-sys-color-surface-container-lowest));box-shadow:0 0 0 2px color-mix(in srgb,var(--md-sys-color-primary) 18%,transparent),inset 0 0 0 1px color-mix(in srgb,var(--md-sys-color-primary) 18%,transparent);cursor:col-resize;z-index:2}:host ::ng-deep .fb-shell--resizing .fb-item.is-resizing .fb-resize{opacity:1!important;pointer-events:auto!important;background:var(--md-sys-color-primary);box-shadow:0 0 0 4px color-mix(in srgb,var(--md-sys-color-primary) 16%,transparent)}:host ::ng-deep .cdk-drag-animating{transition:transform .22s cubic-bezier(.2,0,0,1)!important}:host ::ng-deep .cdk-drag-dragging{transition:none!important}:host ::ng-deep .fb-item:not(.cdk-drag-dragging){transition:flex .12s cubic-bezier(.2,0,0,1),max-width .12s cubic-bezier(.2,0,0,1),border-color .12s,background .12s}::ng-deep .cdk-drag-preview.fb-drag-preview{width:132px;height:44px;box-sizing:border-box;border-radius:14px;background:#fff;display:inline-flex;align-items:center;gap:10px;padding:8px 12px;box-shadow:0 14px 32px #0000003d,0 5px 14px #00000029;border:1.5px solid color-mix(in srgb,var(--sd-primary, #2a66f4) 46%,#d6d2da);cursor:grabbing;pointer-events:none;-webkit-user-select:none;user-select:none;opacity:1;overflow:hidden;z-index:10000}::ng-deep .cdk-drag-preview.fb-drag-preview .fb-drag-preview__glyph{width:28px;height:28px;border-radius:50%;background:var(--sd-primary, #2a66f4);flex-shrink:0;box-shadow:inset 0 0 0 5px #ffffff2e}::ng-deep .cdk-drag-preview.fb-drag-preview .fb-drag-preview__body{flex:1;min-width:0;display:grid;gap:7px}::ng-deep .cdk-drag-preview.fb-drag-preview .fb-drag-preview__line{height:7px;border-radius:999px;background:#d6d2dab8}::ng-deep .cdk-drag-preview.fb-drag-preview .fb-drag-preview__line.is-strong{width:74%;background:color-mix(in srgb,var(--sd-primary, #2a66f4) 26%,#d6d2da)}.fb-shell{width:100%;height:100%;display:flex;background:var(--md-sys-color-surface);overflow:hidden;min-height:0}.fb-panel-header{height:var(--h-header);display:flex;align-items:center;justify-content:space-between;padding:0 var(--gap-3);border-bottom:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-low);flex-shrink:0}.fb-panel-header__title{font-size:11px;font-weight:500;letter-spacing:.08em;text-transform:uppercase;color:var(--md-sys-color-on-surface-variant)}.fb-panel-header__actions{display:flex;align-items:center;gap:2px}.fb-palette{width:220px;flex-shrink:0;display:flex;flex-direction:column;background:var(--md-sys-color-surface-container-low);border-right:1px solid var(--md-sys-color-outline-variant)}.fb-palette__scroll{flex:1;overflow-y:auto;padding-bottom:var(--gap-3)}.fb-palette__search{margin:var(--gap-3);height:32px;display:flex;align-items:center;gap:var(--gap-2);padding:0 var(--gap-3);border-radius:var(--r-pill);background:var(--md-sys-color-surface-container-high);color:var(--md-sys-color-on-surface-variant);font-size:13px;flex-shrink:0}.fb-palette__search .msi{font-size:18px}.fb-palette__search input{flex:1;border:none;outline:none;background:transparent;color:var(--md-sys-color-on-surface);font-size:13px;font-family:inherit}.fb-palette__search input::placeholder{color:var(--md-sys-color-on-surface-variant)}.fb-palette__search .kbd{font-family:Roboto Mono,monospace;font-size:10px;padding:1px 6px;border-radius:var(--r-xs);background:var(--md-sys-color-surface-container-lowest);border:1px solid var(--md-sys-color-outline-variant);color:var(--md-sys-color-on-surface-variant)}.fb-palette__group-label{font-size:11px;font-weight:500;letter-spacing:.04em;color:var(--md-sys-color-on-surface-variant);padding:var(--gap-3) var(--gap-4) var(--gap-1);text-transform:uppercase}.fb-palette__list{display:flex;flex-direction:column;padding:0 var(--gap-2)}.fb-palette-item{height:var(--h-palette);display:flex;align-items:center;gap:var(--gap-3);padding:0 var(--gap-3);border-radius:var(--r-sm);cursor:grab;color:var(--md-sys-color-on-surface);position:relative;-webkit-user-select:none;user-select:none}.fb-palette-item:hover{background:color-mix(in srgb,var(--md-sys-color-primary) 8%,transparent)}.fb-palette-item .fb-palette-item__icon{width:24px;height:24px;border-radius:var(--r-xs);display:inline-flex;align-items:center;justify-content:center;color:var(--md-sys-color-primary);background:var(--md-sys-color-primary-container);flex-shrink:0}.fb-palette-item .fb-palette-item__icon .msi{font-size:16px}.fb-palette-item .fb-palette-item__name{font-size:13px;font-weight:400}.fb-palette-item .fb-palette-item__drag{margin-left:auto;color:var(--md-sys-color-outline);opacity:0;transition:opacity .12s ease}.fb-palette-item .fb-palette-item__drag .msi{font-size:16px}.fb-palette-item:hover .fb-palette-item__drag{opacity:1}.fb-canvas{flex:1 1 0;min-width:0;display:flex;flex-direction:column;background:var(--md-sys-color-surface);overflow:hidden}.fb-canvas__header{height:var(--h-header);display:flex;align-items:center;justify-content:space-between;padding:0 var(--gap-4);border-bottom:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface);gap:var(--gap-3);flex-shrink:0}.fb-canvas__title{display:flex;align-items:center;gap:var(--gap-2);font-size:11px;font-weight:500;letter-spacing:.08em;text-transform:uppercase;color:var(--md-sys-color-on-surface-variant)}.fb-canvas__title .breadcrumb-sep{color:var(--md-sys-color-outline)}.fb-canvas__actions{display:flex;align-items:center;gap:var(--gap-2)}.fb-divider{width:1px;height:18px;background:var(--md-sys-color-outline-variant)}.fb-canvas__body{flex:1 1 0;overflow-y:auto;overflow-x:hidden;padding:var(--gap-4);position:relative;background:var(--md-sys-color-surface);min-width:0}.fb-canvas__body.is-preview{background:var(--md-sys-color-surface-container-low);padding:32px}.fb-mode-toggle{display:inline-flex;height:32px;padding:2px;border:1px solid var(--md-sys-color-outline-variant);border-radius:var(--r-pill);background:var(--md-sys-color-surface-container-lowest);flex-shrink:0;gap:2px;box-shadow:var(--elev-1)}.fb-mode-toggle button{border:0;background:transparent;border-radius:var(--r-pill);padding:0 12px;font-size:12px;font-weight:500;display:inline-flex;align-items:center;gap:6px;color:var(--md-sys-color-on-surface-variant);cursor:pointer;transition:background .12s ease,color .12s ease,box-shadow .12s ease}.fb-mode-toggle button .msi{font-size:16px}.fb-mode-toggle button:hover{background:color-mix(in srgb,var(--md-sys-color-primary) 7%,transparent);color:var(--md-sys-color-on-surface)}.fb-mode-toggle button:focus-visible{outline:2px solid color-mix(in srgb,var(--md-sys-color-primary) 48%,transparent);outline-offset:1px}.fb-mode-toggle button.is-active{background:var(--md-sys-color-primary-container);color:var(--md-sys-color-primary);box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--md-sys-color-primary) 18%,transparent)}.fb-row{position:relative;display:flex;gap:var(--gap-2);padding:6px var(--gap-2);border-radius:var(--r-sm);margin-bottom:6px;min-width:0;transition:outline .12s ease,background .12s ease;outline:1px dashed transparent;outline-offset:-1px}.fb-row:hover{outline-color:var(--md-sys-color-outline-variant);background:color-mix(in srgb,var(--md-sys-color-primary) 3%,transparent)}.fb-row:hover .fb-row__drag{opacity:1}.fb-row.is-drag-target{outline-color:color-mix(in srgb,var(--md-sys-color-info) 44%,transparent);background:color-mix(in srgb,var(--md-sys-color-info) 4%,transparent)}.fb-row__items{position:relative;min-width:0}.fb-row__drag{position:absolute;left:-22px;top:50%;transform:translateY(-50%);width:18px;height:22px;border-radius:var(--r-xs);display:inline-flex;align-items:center;justify-content:center;color:var(--md-sys-color-on-surface-variant);background:var(--md-sys-color-surface-container-high);opacity:0;cursor:grab;z-index:2}.fb-row__drag .msi{font-size:14px}.fb-row-break-btn{position:absolute;right:-28px;top:50%;transform:translateY(-50%);width:22px;height:22px;border-radius:var(--r-pill);display:inline-flex;align-items:center;justify-content:center;color:var(--md-sys-color-on-surface-variant);background:var(--md-sys-color-surface-container-high);border:1px solid var(--md-sys-color-outline-variant);cursor:pointer;opacity:0;z-index:2;transition:opacity .12s ease,background .12s ease,color .12s ease}.fb-row-break-btn .msi{font-size:14px}.fb-row-break-btn:hover{background:var(--md-sys-color-primary-container);color:var(--md-sys-color-primary);border-color:transparent}.fb-row:hover .fb-row-break-btn{opacity:1}.fb-item{position:relative;border:1.5px dashed transparent;border-radius:var(--r-md);padding:var(--gap-2) var(--gap-3);min-height:56px;min-width:0;transition:border-color .12s ease,background .12s ease;background:transparent;cursor:grab;display:flex;flex-direction:column;gap:6px}.fb-item:hover{border-color:color-mix(in srgb,var(--md-sys-color-primary) 60%,transparent)}.fb-item.is-selected{border:1.5px solid var(--md-sys-color-primary);background:color-mix(in srgb,var(--md-sys-color-primary) 4%,transparent);box-shadow:0 0 0 2px color-mix(in srgb,var(--md-sys-color-primary) 18%,transparent),inset 3px 0 0 var(--md-sys-color-primary)}.fb-item.is-hidden{opacity:.45}.fb-item.is-readonly:after{content:\"\";position:absolute;inset:0;border-radius:var(--r-md);background:repeating-linear-gradient(135deg,transparent 0 6px,color-mix(in srgb,var(--md-sys-color-on-surface) 4%,transparent) 6px 7px);pointer-events:none}.fb-item.column-2{flex:0 0 calc(16.6666666667% - var(--gap-2));max-width:calc(16.6666666667% - var(--gap-2))}.fb-item.column-3{flex:0 0 calc(25% - var(--gap-2));max-width:calc(25% - var(--gap-2))}.fb-item.column-4{flex:0 0 calc(33.3333333333% - var(--gap-2));max-width:calc(33.3333333333% - var(--gap-2))}.fb-item.column-5{flex:0 0 calc(41.6666666667% - var(--gap-2));max-width:calc(41.6666666667% - var(--gap-2))}.fb-item.column-6{flex:0 0 calc(50% - var(--gap-2));max-width:calc(50% - var(--gap-2))}.fb-item.column-7{flex:0 0 calc(58.3333333333% - var(--gap-2));max-width:calc(58.3333333333% - var(--gap-2))}.fb-item.column-8{flex:0 0 calc(66.6666666667% - var(--gap-2));max-width:calc(66.6666666667% - var(--gap-2))}.fb-item.column-9{flex:0 0 calc(75% - var(--gap-2));max-width:calc(75% - var(--gap-2))}.fb-item.column-10{flex:0 0 calc(83.3333333333% - var(--gap-2));max-width:calc(83.3333333333% - var(--gap-2))}.fb-item.column-11{flex:0 0 calc(91.6666666667% - var(--gap-2));max-width:calc(91.6666666667% - var(--gap-2))}.fb-item.column-12{flex:1 1 100%;max-width:100%}.fb-actions{position:absolute;top:-14px;right:8px;display:flex;align-items:center;gap:2px;padding:2px;border-radius:var(--r-pill);background:var(--md-sys-color-surface-container-lowest);box-shadow:var(--elev-2);border:1px solid var(--md-sys-color-outline-variant);z-index:4;opacity:0;transform:translateY(-2px);pointer-events:none;transition:opacity .12s ease,transform .12s ease}.fb-actions .fb-icon-btn{width:24px;height:24px}.fb-actions .fb-icon-btn .msi{font-size:16px}.fb-item:hover .fb-actions,.fb-item.is-selected .fb-actions{opacity:1;transform:translateY(0);pointer-events:auto}table-control{display:block;width:100%;max-width:100%;min-width:0}.fb-group>.fb-actions{top:6px;right:8px}.fb-group:hover>.fb-actions,.fb-group.is-selected>.fb-actions{opacity:1;transform:translateY(0);pointer-events:auto}.fb-status-chip{display:inline-flex;align-items:center;gap:4px;padding:2px 8px;height:18px;border-radius:var(--r-pill);font-size:10px;font-weight:500;letter-spacing:.02em;position:absolute;top:-9px;left:12px;z-index:3;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-lowest);color:var(--md-sys-color-on-surface-variant)}.fb-status-chip .msi{font-size:12px}.fb-status-chip.readonly{color:var(--md-sys-color-warning);background:var(--md-sys-color-warning-container);border-color:transparent}.fb-status-chip.hidden{color:var(--md-sys-color-on-surface-variant)}.fb-status-chip.conditional{color:var(--md-sys-color-primary);background:var(--md-sys-color-primary-container);border-color:transparent}.fb-resize-chip{position:absolute;right:14px;bottom:-18px;z-index:5;display:inline-flex;align-items:center;height:20px;padding:0 8px;border-radius:var(--r-pill);background:var(--md-sys-color-primary);color:var(--md-sys-color-on-primary);box-shadow:var(--elev-2);font-size:11px;font-weight:700;letter-spacing:0;pointer-events:none}.fb-resize{position:absolute;top:10px;bottom:10px;right:-6px;width:8px;border-radius:999px;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 28%,transparent);background:var(--md-sys-color-surface-container-lowest);display:flex;align-items:center;justify-content:center;cursor:col-resize;opacity:0;transition:opacity .12s ease,background .12s ease,box-shadow .12s ease;z-index:3}.fb-resize:before{content:\"\";width:2px;height:20px;background:var(--md-sys-color-primary);border-radius:999px}.fb-resize:hover{background:var(--md-sys-color-primary-container);box-shadow:0 0 0 3px color-mix(in srgb,var(--md-sys-color-primary) 12%,transparent)}.fb-item:hover .fb-resize{opacity:1}.fb-item.is-break{min-height:28px;padding:8px var(--gap-3);justify-content:center}.fb-break-line{display:flex;align-items:center;gap:var(--gap-2);color:var(--md-sys-color-on-surface-variant);font-size:11px;font-weight:500;letter-spacing:.06em;text-transform:uppercase}.fb-break-line:before,.fb-break-line:after{content:\"\";flex:1;height:1px;background:var(--md-sys-color-outline-variant)}.fb-break-line .msi{display:none}.fb-break-line .fb-break-label{color:var(--md-sys-color-on-surface-variant)}.fb-drop-hint{height:36px;border-radius:var(--r-sm);border:1.5px dashed var(--md-sys-color-primary);background:color-mix(in srgb,var(--md-sys-color-primary) 6%,transparent);color:var(--md-sys-color-primary);display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:500;gap:6px;margin:4px 0}.fb-drop-hint .msi{font-size:16px}.fb-group{background:var(--md-sys-color-surface-container-lowest);border-radius:var(--r-lg);border:1.5px dashed transparent;transition:border-color .12s ease,background .12s ease;position:relative;display:flex;flex-direction:column;overflow:visible;cursor:default}.fb-group:hover{border-color:color-mix(in srgb,var(--md-sys-color-primary) 60%,transparent)}.fb-group.is-selected{border:1.5px solid var(--md-sys-color-primary);box-shadow:0 0 0 2px color-mix(in srgb,var(--md-sys-color-primary) 18%,transparent),inset 3px 0 0 var(--md-sys-color-primary)}.fb-group.is-hidden{opacity:.55}.fb-group__header{display:flex;align-items:center;gap:var(--gap-2);padding:8px 12px;border-top-left-radius:12px;border-top-right-radius:12px;cursor:grab}.fb-group__header:active{cursor:grabbing}.fb-group__header .fb-group__icon .msi{font-size:18px}.fb-group__header .fb-group__label{font-size:13px;font-weight:500}.fb-group__header .fb-group__count{margin-left:auto;font-size:10px;opacity:.75}.fb-group__conditional-chip{display:inline-flex;align-items:center;gap:4px;font-family:Roboto Mono,monospace;font-size:10px;padding:2px 8px;border-radius:var(--r-pill);background:color-mix(in srgb,currentColor 12%,transparent)}.fb-group__body{padding:12px;display:flex;flex-wrap:wrap;gap:var(--gap-2);min-height:60px;position:relative}.fb-group__empty{display:flex;align-items:center;justify-content:center;height:56px;border-radius:var(--r-sm);border:1.5px dashed var(--md-sys-color-outline-variant);color:var(--md-sys-color-on-surface-variant);font-size:12px;font-style:italic}.fb-empty{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--gap-3);color:var(--md-sys-color-on-surface-variant);text-align:center;padding:24px;pointer-events:none}.fb-empty__art{width:96px;height:96px;border-radius:var(--r-xl);background:var(--md-sys-color-primary-container);color:var(--md-sys-color-primary);display:inline-flex;align-items:center;justify-content:center}.fb-empty__art .msi{font-size:44px}.fb-empty__title{font-size:16px;font-weight:500;color:var(--md-sys-color-on-surface)}.fb-empty__hint{font-size:13px;max-width:320px;line-height:1.5}.fb-empty__kbd{display:inline-flex;align-items:center;gap:6px;font-family:Roboto Mono,monospace;font-size:11px;padding:4px 8px;border-radius:var(--r-xs);border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container);color:var(--md-sys-color-on-surface-variant)}.fb-empty__kbd .msi{font-size:12px}.fb-attrs{width:320px;flex-shrink:0;display:flex;flex-direction:column;background:var(--md-sys-color-surface-container-low);border-left:1px solid var(--md-sys-color-outline-variant)}.fb-attrs__selection{padding:var(--gap-3) var(--gap-4);display:flex;align-items:center;gap:var(--gap-2);border-bottom:1px solid var(--md-sys-color-outline-variant)}.fb-attrs__sel-icon{width:28px;height:28px;border-radius:var(--r-sm);background:var(--md-sys-color-primary-container);color:var(--md-sys-color-primary);display:inline-flex;align-items:center;justify-content:center;flex-shrink:0}.fb-attrs__sel-icon .msi{font-size:18px}.fb-attrs__sel-meta{display:flex;flex-direction:column;min-width:0}.fb-attrs__sel-name{font-size:13px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.fb-attrs__sel-key{font-family:Roboto Mono,monospace;font-size:11px;color:var(--md-sys-color-on-surface-variant)}.fb-attrs__body{flex:1 1 0;overflow-y:auto;overflow-x:hidden;min-width:0}.fb-attrs__placeholder{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--gap-2);padding:32px;color:var(--md-sys-color-on-surface-variant);text-align:center}.fb-attrs__placeholder .msi{font-size:32px;color:var(--md-sys-color-outline)}.fb-attrs__placeholder .title{font-size:13px;font-weight:500;color:var(--md-sys-color-on-surface)}.fb-attrs__placeholder .hint{font-size:12px;max-width:220px;line-height:1.5}.fb-preview-card{max-width:760px;margin:0 auto;background:var(--md-sys-color-surface-container-lowest);border-radius:var(--r-lg);padding:32px;box-shadow:var(--elev-1)}.fb-preview-card__chip{display:inline-flex;align-items:center;gap:6px;padding:2px 10px;height:22px;border-radius:var(--r-pill);background:var(--md-sys-color-primary-container);color:var(--md-sys-color-primary);font-size:10px;font-weight:500;letter-spacing:.08em;text-transform:uppercase;margin-bottom:var(--gap-3)}.fb-preview-card__title{font-size:20px;font-weight:500;margin:0 0 var(--gap-4);color:var(--md-sys-color-on-surface)}.fb-preview-card__footer{display:flex;justify-content:flex-end;gap:var(--gap-2);margin-top:var(--gap-4);padding-top:var(--gap-4);border-top:1px solid var(--md-sys-color-outline-variant)}.fb-icon-picker{display:grid;grid-template-columns:repeat(6,1fr);gap:4px;margin-top:4px}.fb-icon-picker .fb-icon-picker__btn{height:28px;border-radius:var(--r-xs);border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-lowest);color:var(--md-sys-color-on-surface-variant);display:inline-flex;align-items:center;justify-content:center;cursor:pointer}.fb-icon-picker .fb-icon-picker__btn .msi{font-size:16px}.fb-icon-picker .fb-icon-picker__btn.is-active{border-color:var(--md-sys-color-primary);background:var(--md-sys-color-primary-container);color:var(--md-sys-color-primary)}.fb-icon-picker .fb-icon-picker__btn:hover{background:color-mix(in srgb,var(--md-sys-color-primary) 8%,transparent)}.fb-color-swatches{display:flex;gap:var(--gap-2);margin-top:4px}.fb-color-swatches .fb-color-swatch{width:24px;height:24px;border-radius:50%;cursor:pointer;border:2px solid transparent}.fb-color-swatches .fb-color-swatch.is-active{border-color:var(--md-sys-color-on-surface)}.fb-color-swatches .fb-color-swatch.primary{background:var(--md-sys-color-primary)}.fb-color-swatches .fb-color-swatch.secondary{background:var(--md-sys-color-secondary)}.fb-color-swatches .fb-color-swatch.success{background:var(--md-sys-color-success)}.fb-color-swatches .fb-color-swatch.warning{background:var(--md-sys-color-warning)}.fb-color-swatches .fb-color-swatch.error{background:var(--md-sys-color-error)}.fb-children-list{display:flex;flex-direction:column;gap:4px}.fb-child-row{display:flex;align-items:center;gap:var(--gap-2);height:30px;padding:0 8px;border-radius:var(--r-xs);background:var(--md-sys-color-surface-container-lowest);border:1px solid var(--md-sys-color-outline-variant);font-size:12px}.fb-child-row .fb-child-row__icon{width:22px;height:22px;border-radius:var(--r-xs);background:var(--md-sys-color-primary-container);color:var(--md-sys-color-primary);display:inline-flex;align-items:center;justify-content:center;flex-shrink:0}.fb-child-row .fb-child-row__icon .msi{font-size:14px}.fb-child-row .fb-child-row__label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-child-row .fb-child-row__key{font-family:Roboto Mono,monospace;font-size:10px;color:var(--md-sys-color-on-surface-variant)}.fb-child-row:hover{background:color-mix(in srgb,var(--md-sys-color-primary) 4%,transparent)}.fb-add-link{display:inline-flex;align-items:center;gap:4px;font-size:12px;color:var(--md-sys-color-primary);cursor:pointer;padding:4px 0;text-decoration:none}.fb-add-link .msi{font-size:14px}.fb-add-link:hover{text-decoration:underline}.fb-group__body--static{display:flex;flex-wrap:wrap;gap:var(--gap-2, 8px);cursor:pointer;min-height:48px}.fb-group__body--static .fb-item--static{pointer-events:none;position:relative}.fb-group__body--static .fb-item--static .fb-resize,.fb-group__body--static .fb-item--static .fb-actions,.fb-group__body--static .fb-item--static .fb-status-chip{display:none!important}.fb-canvas__header--group .fb-groupedit__crumb{display:flex;align-items:center;gap:6px;font-weight:600}.fb-canvas__header--group .fb-groupedit__icon .msi{font-size:18px;color:var(--md-sys-color-primary)}.fb-canvas__header--group .fb-groupedit__hint{font-weight:400;font-size:12px;color:var(--md-sys-color-on-surface-variant, #6b6b6b);padding:1px 8px;border-radius:999px;background:color-mix(in srgb,var(--md-sys-color-primary) 8%,transparent)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i2.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i3.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i3.CdkDropListGroup, selector: "[cdkDropListGroup]", inputs: ["cdkDropListGroupDisabled"], exportAs: ["cdkDropListGroup"] }, { kind: "directive", type: i3.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i3.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "directive", type: i3.CdkDragPreview, selector: "ng-template[cdkDragPreview]", inputs: ["data", "matchSize"] }, { kind: "directive", type: i3.CdkDragPlaceholder, selector: "ng-template[cdkDragPlaceholder]", inputs: ["data"] }, { kind: "component", type:
|
|
6742
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdFormBuilder, isStandalone: true, selector: "sd-form-builder", inputs: { formGeneric: { classPropertyName: "formGeneric", publicName: "formGeneric", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "popupViewJSON", first: true, predicate: ["popupViewJSON"], descendants: true, isSignal: true }, { propertyName: "popupConfigureVariables", first: true, predicate: ["popupConfigureVariables"], descendants: true, isSignal: true }, { propertyName: "configureValidation", first: true, predicate: ConfigureValidationComponent, descendants: true, isSignal: true }, { propertyName: "formRender", first: true, predicate: SdFormRender, descendants: true, isSignal: true }], ngImport: i0, template: "<!-- cdkDropListGroup: auto-connect t\u1EA5t c\u1EA3 cdkDropList trong palette, canvas rows, group bodies.\n [class.fb-shell--dragging]: JS-driven flag \u0111\u1EC3 \u1EA9n hover/actions/resize to\u00E0n di\u1EC7n\n trong l\u00FAc drag (CSS :has() kh\u00F4ng b\u1EAFt 100% tr\u01B0\u1EDDng h\u1EE3p v\u1EDBi CDK preview clone). -->\n<div\n class=\"fb-shell\"\n cdkDropListGroup\n [class.fb-shell--dragging]=\"isAnyDragging()\"\n [class.fb-shell--palette-dragging]=\"dragSource() === 'palette'\"\n [class.fb-shell--resizing]=\"isResizing()\">\n <ng-template #dragPreviewTemplate>\n <span class=\"fb-drag-preview__glyph\" aria-hidden=\"true\"></span>\n <span class=\"fb-drag-preview__body\" aria-hidden=\"true\">\n <span class=\"fb-drag-preview__line is-strong\"></span>\n <span class=\"fb-drag-preview__line\"></span>\n </span>\n </ng-template>\n <ng-template #dropPlaceholderTemplate let-item>\n <span class=\"fb-drop-placeholder__icon\" aria-hidden=\"true\">\n <span class=\"msi\">{{ placeholderSymbolFor(item) }}</span>\n </span>\n <span class=\"fb-drop-placeholder__body\" aria-hidden=\"true\">\n <span class=\"fb-drop-placeholder__title\">{{ placeholderTitleFor(item) }}</span>\n <span class=\"fb-drop-placeholder__meta\">{{ placeholderMetaFor(item) }}</span>\n </span>\n </ng-template>\n\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 LEFT PALETTE \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <aside class=\"fb-palette\">\n <div class=\"fb-panel-header\">\n <div class=\"fb-panel-header__title\">{{ 'core.component.form-builder.components' | translate }}</div>\n </div>\n\n <!-- Search pill -->\n <div class=\"fb-palette__search\">\n <span class=\"msi\" aria-hidden=\"true\">search</span>\n <input\n type=\"text\"\n [placeholder]=\"'core.component.form-builder.search-components' | translate\"\n [value]=\"paletteSearch()\"\n (input)=\"paletteSearch.set($any($event.target).value)\" />\n <span class=\"kbd\">\u2318K</span>\n </div>\n\n <div class=\"fb-palette__scroll\">\n @for (group of paletteGroups(); track group.key) {\n <div class=\"fb-palette__group-label\">{{ group.label }}</div>\n <div class=\"fb-palette__list\" cdkDropList [cdkDropListData]=\"group.items\" [cdkDropListEnterPredicate]=\"noReturnPredicate\">\n @for (formBuilderComponent of group.items; track formBuilderComponent.type) {\n <div\n cdkDrag\n [cdkDragData]=\"formBuilderComponent\"\n class=\"fb-palette-item\"\n [matTooltip]=\"formBuilderComponent.name\"\n matTooltipPosition=\"right\"\n (click)=\"addComponent(formBuilderComponent)\"\n (cdkDragStarted)=\"onPaletteDragStarted(formBuilderComponent)\"\n (cdkDragMoved)=\"onAnyDragMoved($event)\"\n (cdkDragEnded)=\"onAnyDragEnded()\"\n aria-hidden=\"true\">\n <span class=\"fb-palette-item__icon\"\n ><span class=\"msi\">{{ formBuilderComponent.symbol }}</span></span\n >\n <span class=\"fb-palette-item__name\">{{ formBuilderComponent.name }}</span>\n <span class=\"fb-palette-item__drag\"><span class=\"msi\">drag_indicator</span></span>\n <div *cdkDragPreview class=\"fb-drag-preview\" aria-hidden=\"true\">\n <ng-container *ngTemplateOutlet=\"dragPreviewTemplate\"></ng-container>\n </div>\n <div\n *cdkDragPlaceholder\n class=\"fb-drop-placeholder\"\n [style.--fb-placeholder-columns]=\"placeholderColumnsFor(formBuilderComponent)\"\n aria-hidden=\"true\">\n <ng-container *ngTemplateOutlet=\"dropPlaceholderTemplate; context: { $implicit: formBuilderComponent }\"></ng-container>\n </div>\n </div>\n }\n </div>\n }\n </div>\n </aside>\n\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 CENTER CANVAS \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <section class=\"fb-canvas\">\n @let _editingGroup = editingGroup();\n @if (_editingGroup) {\n <!-- \u2500\u2500 Drill-in header: \u0111ang thi\u1EBFt k\u1EBF children c\u1EE7a 1 group \u2500\u2500 -->\n <header class=\"fb-canvas__header fb-canvas__header--group\">\n <div class=\"fb-canvas__title fb-groupedit__crumb\">\n <button\n type=\"button\"\n class=\"fb-icon-btn\"\n [matTooltip]=\"'core.component.form-builder.back-to-design' | translate\"\n (click)=\"cancelGroupEdit()\">\n <span class=\"msi\">chevron_left</span>\n </button>\n <span class=\"fb-groupedit__icon\"\n ><span class=\"msi\">{{ _editingGroup.properties?.icon || 'category' }}</span></span\n >\n <span>{{ _editingGroup.label || ('core.component.form-builder.edit-group' | translate) }}</span>\n <span class=\"fb-groupedit__hint\">{{ 'core.component.form-builder.edit-group' | translate }}</span>\n </div>\n <div class=\"fb-canvas__actions\">\n <sd-button type=\"text\" [title]=\"'core.component.form-builder.cancel' | translate\" (click)=\"cancelGroupEdit()\"></sd-button>\n <sd-button\n type=\"fill\"\n color=\"primary\"\n prefixIcon=\"check\"\n [title]=\"'core.component.form-builder.confirm' | translate\"\n (click)=\"confirmGroupEdit()\"></sd-button>\n </div>\n </header>\n } @else {\n <header class=\"fb-canvas__header\">\n <div class=\"fb-canvas__title\">\n <span>{{ 'core.component.form-builder.layout' | translate }}</span>\n </div>\n <div class=\"fb-canvas__actions\">\n <!-- Toolbar cluster: view JSON / variables / validation -->\n <button type=\"button\" class=\"fb-icon-btn\" [matTooltip]=\"'core.component.form-builder.view-json' | translate\" (click)=\"viewJSON()\">\n <span class=\"msi\">data_object</span>\n </button>\n <button\n type=\"button\"\n class=\"fb-icon-btn\"\n [matTooltip]=\"'core.component.form-builder.config-variable' | translate\"\n (click)=\"configureVariables()\">\n <span class=\"msi\">data_array</span>\n </button>\n <button\n type=\"button\"\n class=\"fb-icon-btn\"\n [matTooltip]=\"'core.component.form-builder.config-validation-warning-error' | translate\"\n (click)=\"openConfigureValidation()\">\n <span class=\"msi\">rule</span>\n </button>\n\n <span class=\"fb-divider\"></span>\n\n <!-- M3 segmented Design/Preview toggle -->\n <div class=\"fb-mode-toggle\" role=\"tablist\">\n <button type=\"button\" role=\"tab\" [class.is-active]=\"!isPreview()\" [attr.aria-selected]=\"!isPreview()\" (click)=\"setMode(false)\">\n <span class=\"msi\">edit</span>\n <span>{{ 'core.component.form-builder.design' | translate }}</span>\n </button>\n <button type=\"button\" role=\"tab\" [class.is-active]=\"isPreview()\" [attr.aria-selected]=\"isPreview()\" (click)=\"setMode(true)\">\n <span class=\"msi\">visibility</span>\n <span>{{ 'core.component.form-builder.preview' | translate }}</span>\n </button>\n </div>\n </div>\n </header>\n }\n\n <div class=\"fb-canvas__body\" [class.is-preview]=\"isPreview()\" id=\"frmComponent\">\n @if (isPreview()) {\n <!-- \u2500\u2500 Preview mode \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"fb-preview-card\">\n <div class=\"fb-preview-card__chip\">\n <span class=\"msi\">visibility</span>\n {{ 'core.component.form-builder.preview-runtime' | translate }}\n </div>\n <sd-form-render [configuration]=\"{ components: this.components, validations: this.validations }\"></sd-form-render>\n <div class=\"fb-preview-card__footer\">\n <sd-button type=\"text\" [title]=\"'core.component.form-builder.cancel' | translate\" (click)=\"setMode(false)\"></sd-button>\n <sd-button\n type=\"fill\"\n color=\"primary\"\n [title]=\"'core.component.form-builder.submit' | translate\"\n (click)=\"onValidate()\"></sd-button>\n </div>\n </div>\n } @else {\n <!-- \u2500\u2500 Design mode \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (!dragDropRows.length) {\n <div class=\"fb-empty\">\n <div class=\"fb-empty__art\"><span class=\"msi\">dashboard_customize</span></div>\n <div class=\"fb-empty__title\">{{ 'core.component.form-builder.empty-title' | translate }}</div>\n <div class=\"fb-empty__hint\">\n {{ 'core.component.form-builder.empty-hint' | translate }}\n <span class=\"fb-empty__kbd\"><span class=\"msi\">keyboard</span>\u2318 K</span>\n </div>\n </div>\n }\n\n <div\n cdkDropList\n cdkDropListOrientation=\"vertical\"\n [cdkDropListData]=\"dragDropRows\"\n (cdkDropListDropped)=\"drop($event)\"\n (click)=\"clickFormContentEmpty()\"\n aria-hidden=\"true\">\n @for (row of dragDropRows; track row.id) {\n @if (shouldShowRowInsertionPlaceholder(row, 'before')) {\n <div\n class=\"fb-row-insert-placeholder\"\n [style.--fb-placeholder-columns]=\"12\"\n aria-hidden=\"true\">\n <ng-container *ngTemplateOutlet=\"dropPlaceholderTemplate; context: { $implicit: draggedPaletteItem() || row }\"></ng-container>\n </div>\n }\n <div\n cdkDrag\n [cdkDragData]=\"row\"\n class=\"fb-row\"\n (mouseover)=\"onMouseover($event, row)\"\n (focus)=\"onFocus($event)\"\n (cdkDragStarted)=\"onRowDragStarted()\"\n (cdkDragMoved)=\"onAnyDragMoved($event)\"\n (cdkDragEnded)=\"onAnyDragEnded()\"\n [class.is-drag-target]=\"targetItem === row\"\n [class.is-row-full]=\"isRowFull(row)\"\n [class.is-row-inline-locked]=\"isRowInlineDropLocked(row)\"\n [class.is-resizing-row]=\"resizeState()?.rowId === row.id\"\n [id]=\"row.id\"\n aria-hidden=\"true\">\n <div class=\"fb-row__drag\" [matTooltip]=\"'core.component.form-builder.drag-vertically' | translate\" cdkDragHandle>\n <span class=\"msi\">drag_indicator</span>\n </div>\n <div *cdkDragPreview class=\"fb-drag-preview\" aria-hidden=\"true\">\n <ng-container *ngTemplateOutlet=\"dragPreviewTemplate\"></ng-container>\n </div>\n <div\n *cdkDragPlaceholder\n class=\"fb-drop-placeholder fb-drop-placeholder--row\"\n [style.--fb-placeholder-columns]=\"12\"\n aria-hidden=\"true\">\n <ng-container *ngTemplateOutlet=\"dropPlaceholderTemplate; context: { $implicit: row }\"></ng-container>\n </div>\n <!-- Quick-add break button \u2014 show on row hover, insert break after this row -->\n <button\n type=\"button\"\n class=\"fb-row-break-btn\"\n [matTooltip]=\"'core.component.form-builder.break' | translate\"\n matTooltipPosition=\"right\"\n (click)=\"insertBreakAfter(row); $event.stopPropagation()\">\n <span class=\"msi\">horizontal_rule</span>\n </button>\n\n <div\n class=\"fb-row__items\"\n style=\"display: flex; flex: 1; gap: var(--gap-2)\"\n cdkDropList\n cdkDropListOrientation=\"horizontal\"\n [cdkDropListData]=\"row.items\"\n [cdkDropListEnterPredicate]=\"canEnterRowDropList\"\n (cdkDropListEntered)=\"onRowItemsDropEntered(row)\"\n (cdkDropListExited)=\"onRowItemsDropExited(row)\"\n (cdkDropListDropped)=\"drop($event)\">\n @for (item of row.items; track item.id; let i = $index) {\n <!-- Group component renders as outlined card with colored header bar -->\n @if (item.type === 'group') {\n @let g = $any(item);\n @let cv = groupColorVars(g.properties?.color);\n <div\n cdkDrag\n [cdkDragData]=\"item\"\n [id]=\"item.id\"\n class=\"fb-group column-{{ item.layout?.columns || 12 }}\"\n [class.fb-item]=\"false\"\n [class.is-selected]=\"item.id === selectedComponent()?.id\"\n [class.is-hidden]=\"item.properties?.hidden\"\n [style.flex]=\"'0 0 calc(' + +(item.layout?.columns || 12) + '/12 * 100% - var(--gap-2))'\"\n [style.maxWidth]=\"'calc(' + +(item.layout?.columns || 12) + '/12 * 100% - var(--gap-2))'\"\n (click)=\"selectComponent(item); $event.stopPropagation()\"\n (cdkDragStarted)=\"dragStartComponentItem($event)\"\n (cdkDragMoved)=\"onAnyDragMoved($event)\"\n (cdkDragEnded)=\"dragEndComponentItem($event); onAnyDragEnded()\"\n aria-hidden=\"true\">\n <div *cdkDragPreview class=\"fb-drag-preview\" aria-hidden=\"true\">\n <ng-container *ngTemplateOutlet=\"dragPreviewTemplate\"></ng-container>\n </div>\n <div\n *cdkDragPlaceholder\n class=\"fb-drop-placeholder\"\n [style.--fb-placeholder-columns]=\"placeholderColumnsFor(item)\"\n aria-hidden=\"true\">\n <ng-container *ngTemplateOutlet=\"dropPlaceholderTemplate; context: { $implicit: item }\"></ng-container>\n </div>\n <!-- cdkDragHandle: ch\u1EC9 cho ph\u00E9p k\u00E9o group qua header \u2014 body free cho click v\u00E0o child. -->\n <div class=\"fb-group__header\" cdkDragHandle [style.background]=\"cv.bg\" [style.color]=\"cv.fg\">\n <span class=\"fb-group__icon\"\n ><span class=\"msi\">{{ g.properties?.icon || 'category' }}</span></span\n >\n <span class=\"fb-group__label\">{{ item.label }}</span>\n @if (hasConditional(item)) {\n <span class=\"fb-group__conditional-chip\">\n <span class=\"msi\" style=\"font-size: 12px\">bolt</span>\n {{ 'core.component.form-builder.conditional' | translate }}\n </span>\n }\n <span class=\"fb-group__count\">{{ g.components?.length || 0 }} fields</span>\n </div>\n <!-- Floating action toolbar -->\n <div class=\"fb-actions\">\n <!-- why: Detail = m\u1EDF canvas thi\u1EBFt k\u1EBF ri\u00EAng cho group (thay cho drag/drop trong group). -->\n <button\n type=\"button\"\n class=\"fb-icon-btn\"\n [matTooltip]=\"'core.component.form-builder.edit-group' | translate\"\n (click)=\"enterGroupEdit(g); $event.stopPropagation()\">\n <span class=\"msi\">open_in_full</span>\n </button>\n <button\n type=\"button\"\n class=\"fb-icon-btn\"\n [matTooltip]=\"'core.component.form-builder.duplicate' | translate\"\n (click)=\"onDuplicate(item); $event.stopPropagation()\">\n <span class=\"msi\">content_copy</span>\n </button>\n <button\n type=\"button\"\n class=\"fb-icon-btn\"\n [class.warning]=\"item.properties?.hidden\"\n [class.on]=\"item.properties?.hidden\"\n [matTooltip]=\"\n item.properties?.hidden\n ? ('core.component.form-builder.show' | translate)\n : ('core.component.form-builder.hide' | translate)\n \"\n (click)=\"onChangeHidden(item); $event.stopPropagation()\">\n <span class=\"msi\">{{ item.properties?.hidden ? 'visibility_off' : 'visibility' }}</span>\n </button>\n <button\n type=\"button\"\n class=\"fb-icon-btn danger\"\n [matTooltip]=\"'core.component.form-builder.delete' | translate\"\n (click)=\"removeComponent(item.id); $event.stopPropagation()\">\n <span class=\"msi\">delete</span>\n </button>\n </div>\n\n <!-- Static preview: children render read-only (kh\u00F4ng drag/resize/select).\n S\u1EEDa n\u1ED9i dung group qua n\u00FAt Detail (enterGroupEdit). -->\n <div class=\"fb-group__body fb-group__body--static\" (click)=\"enterGroupEdit(g); $event.stopPropagation()\">\n @if (!g.components?.length) {\n <div class=\"fb-group__empty\">\n <span class=\"msi\" style=\"font-size: 16px; margin-right: 6px\">open_in_full</span>\n {{ 'core.component.form-builder.group-empty-hint' | translate }}\n </div>\n }\n @for (child of g.components; track child.id) {\n <div\n class=\"fb-item is-child fb-item--static column-{{ child.layout?.columns || 12 }}\"\n [class.is-hidden]=\"child.properties?.hidden\">\n <ng-container *ngTemplateOutlet=\"controlTemplate; context: { $implicit: child }\"> </ng-container>\n </div>\n }\n </div>\n </div>\n } @else if (item.type === 'break') {\n <!-- Break: row separator c\u1ED1 \u0111\u1ECBnh. Render 12-col line, kh\u00F4ng c\u00F3 resize/edit attribute. -->\n <div\n cdkDrag\n [cdkDragData]=\"item\"\n [id]=\"item.id\"\n class=\"fb-item is-break column-12\"\n [class.is-selected]=\"item.id === selectedComponent()?.id\"\n [class.is-hidden]=\"item.properties?.hidden\"\n (click)=\"selectComponent(item); $event.stopPropagation()\"\n (cdkDragStarted)=\"dragStartComponentItem($event)\"\n (cdkDragMoved)=\"onAnyDragMoved($event)\"\n (cdkDragEnded)=\"dragEndComponentItem($event); onAnyDragEnded()\"\n aria-hidden=\"true\">\n <div *cdkDragPreview class=\"fb-drag-preview\" aria-hidden=\"true\">\n <ng-container *ngTemplateOutlet=\"dragPreviewTemplate\"></ng-container>\n </div>\n <div\n *cdkDragPlaceholder\n class=\"fb-drop-placeholder\"\n [style.--fb-placeholder-columns]=\"12\"\n aria-hidden=\"true\">\n <ng-container *ngTemplateOutlet=\"dropPlaceholderTemplate; context: { $implicit: item }\"></ng-container>\n </div>\n <div class=\"fb-break-line\">\n <span class=\"msi\">horizontal_rule</span>\n <span class=\"fb-break-label\">{{ 'core.component.form-builder.break' | translate }}</span>\n </div>\n <div class=\"fb-actions\">\n <button\n type=\"button\"\n class=\"fb-icon-btn\"\n [class.warning]=\"item.properties?.hidden\"\n [class.on]=\"item.properties?.hidden\"\n [matTooltip]=\"\n item.properties?.hidden\n ? ('core.component.form-builder.show' | translate)\n : ('core.component.form-builder.hide' | translate)\n \"\n (click)=\"onChangeHidden(item); $event.stopPropagation()\">\n <span class=\"msi\">{{ item.properties?.hidden ? 'visibility_off' : 'visibility' }}</span>\n </button>\n <button\n type=\"button\"\n class=\"fb-icon-btn danger\"\n [matTooltip]=\"'core.component.form-builder.delete' | translate\"\n (click)=\"removeComponent(item.id); $event.stopPropagation()\">\n <span class=\"msi\">delete</span>\n </button>\n </div>\n </div>\n } @else {\n <!-- Regular form component -->\n <div\n cdkDrag\n [cdkDragData]=\"item\"\n [id]=\"item.id\"\n class=\"fb-item column-{{ item.layout?.columns || 12 }}\"\n [class.is-selected]=\"item.id === selectedComponent()?.id\"\n [class.is-hidden]=\"item.properties?.hidden\"\n [class.is-readonly]=\"item.properties?.viewed\"\n [class.is-resizing]=\"resizeState()?.itemId === item.id\"\n (click)=\"selectComponent(item); $event.stopPropagation()\"\n (cdkDragStarted)=\"dragStartComponentItem($event)\"\n (cdkDragMoved)=\"onAnyDragMoved($event)\"\n (cdkDragEnded)=\"dragEndComponentItem($event); onAnyDragEnded()\"\n aria-hidden=\"true\">\n <div *cdkDragPreview class=\"fb-drag-preview\" aria-hidden=\"true\">\n <ng-container *ngTemplateOutlet=\"dragPreviewTemplate\"></ng-container>\n </div>\n <div\n *cdkDragPlaceholder\n class=\"fb-drop-placeholder\"\n [style.--fb-placeholder-columns]=\"placeholderColumnsFor(item)\"\n aria-hidden=\"true\">\n <ng-container *ngTemplateOutlet=\"dropPlaceholderTemplate; context: { $implicit: item }\"></ng-container>\n </div>\n\n <!-- Status chip (mutually exclusive priority: hidden > readonly > conditional) -->\n @if (item.properties?.hidden) {\n <span class=\"fb-status-chip hidden\">\n <span class=\"msi\">visibility_off</span>\n {{ 'core.component.form-builder.hidden' | translate }}\n </span>\n } @else if (item.properties?.viewed) {\n <span class=\"fb-status-chip readonly\">\n <span class=\"msi\">edit_off</span>\n {{ 'core.component.form-builder.read-only' | translate }}\n </span>\n } @else if (hasConditional(item)) {\n <span class=\"fb-status-chip conditional\">\n <span class=\"msi\">bolt</span>\n {{ 'core.component.form-builder.conditional' | translate }}\n </span>\n }\n\n <!-- Floating action toolbar pill -->\n <div class=\"fb-actions\">\n <button\n type=\"button\"\n class=\"fb-icon-btn\"\n [class.warning]=\"item.properties?.viewed\"\n [class.on]=\"item.properties?.viewed\"\n [matTooltip]=\"\n item.properties?.viewed\n ? ('core.component.form-builder.unset-readonly' | translate)\n : ('core.component.form-builder.set-readonly' | translate)\n \"\n (click)=\"onChangeViewed(item); $event.stopPropagation()\">\n <span class=\"msi\">edit_off</span>\n </button>\n <button\n type=\"button\"\n class=\"fb-icon-btn\"\n [matTooltip]=\"'core.component.form-builder.duplicate' | translate\"\n (click)=\"onDuplicate(item); $event.stopPropagation()\">\n <span class=\"msi\">content_copy</span>\n </button>\n <button\n type=\"button\"\n class=\"fb-icon-btn\"\n [class.warning]=\"item.properties?.hidden\"\n [class.on]=\"item.properties?.hidden\"\n [matTooltip]=\"\n item.properties?.hidden\n ? ('core.component.form-builder.show' | translate)\n : ('core.component.form-builder.hide' | translate)\n \"\n (click)=\"onChangeHidden(item); $event.stopPropagation()\">\n <span class=\"msi\">{{ item.properties?.hidden ? 'visibility_off' : 'visibility' }}</span>\n </button>\n <button\n type=\"button\"\n class=\"fb-icon-btn danger\"\n [matTooltip]=\"'core.component.form-builder.delete' | translate\"\n (click)=\"removeComponent(item.id); $event.stopPropagation()\">\n <span class=\"msi\">delete</span>\n </button>\n </div>\n\n <ng-container *ngTemplateOutlet=\"controlTemplate; context: { $implicit: item }\"> </ng-container>\n\n @if (resizeState()?.itemId === item.id) {\n <span class=\"fb-resize-chip\">{{ resizeState()?.columns }}/12</span>\n }\n\n <!-- Resize handle (pill) \u2014 cdkDragLockAxis=\"x\" \u0111\u1EC3 ch\u1EC9 k\u00E9o ngang.\n Wrapping cdkDropList c\u00F3 enterPredicate=false, kh\u00F4ng nh\u1EADn drop. -->\n <div cdkDropList cdkDropListOrientation=\"horizontal\" [cdkDropListEnterPredicate]=\"noReturnPredicate\">\n <div\n cdkDrag\n cdkDragLockAxis=\"x\"\n class=\"fb-resize\"\n (cdkDragStarted)=\"startResizeControl(item, row)\"\n (cdkDragMoved)=\"changeSizeControl($event, item, row.items, i)\"\n (cdkDragEnded)=\"endResizeControl($event)\"\n [matTooltip]=\"'core.component.form-builder.flex' | translate\"\n aria-label=\"resize\"></div>\n </div>\n </div>\n }\n }\n </div>\n </div>\n @if (shouldShowRowInsertionPlaceholder(row, 'after')) {\n <div\n class=\"fb-row-insert-placeholder\"\n [style.--fb-placeholder-columns]=\"12\"\n aria-hidden=\"true\">\n <ng-container *ngTemplateOutlet=\"dropPlaceholderTemplate; context: { $implicit: draggedPaletteItem() || row }\"></ng-container>\n </div>\n }\n }\n </div>\n }\n </div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 RIGHT ATTRIBUTES \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <aside class=\"fb-attrs\">\n <div class=\"fb-panel-header\">\n <div class=\"fb-panel-header__title\">{{ 'core.component.form-builder.attribute' | translate }}</div>\n </div>\n\n @let _selectedComponent = selectedComponent();\n @if (_selectedComponent) {\n <!-- Selection header: type icon + name + key/type -->\n <div class=\"fb-attrs__selection\">\n <span class=\"fb-attrs__sel-icon\"\n ><span class=\"msi\">{{ symbolFor(_selectedComponent) }}</span></span\n >\n <div class=\"fb-attrs__sel-meta\">\n <div class=\"fb-attrs__sel-name\">{{ $any(_selectedComponent).label || ('core.component.form-builder.untitled' | translate) }}</div>\n <div class=\"fb-attrs__sel-key\">\n @if ($any(_selectedComponent).key) {\n {{ $any(_selectedComponent).key }} \u00B7 {{ typeLabelFor(_selectedComponent) }}\n } @else {\n {{ typeLabelFor(_selectedComponent) }}\n }\n </div>\n </div>\n </div>\n\n <div class=\"fb-attrs__body\" aria-hidden=\"true\">\n @if (_selectedComponent.type === 'textfield') {\n <textfield-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"_selectedComponent\"></textfield-attribute>\n } @else if (_selectedComponent.type === 'textarea') {\n <textarea-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"_selectedComponent\"></textarea-attribute>\n } @else if (_selectedComponent.type === 'chip-string') {\n <chip-string-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"_selectedComponent\"></chip-string-attribute>\n } @else if (_selectedComponent.type === 'chip-calendar') {\n <chip-calendar-attribute\n [components]=\"components\"\n [variables]=\"variables\"\n [component]=\"_selectedComponent\"></chip-calendar-attribute>\n } @else if (_selectedComponent.type === 'number') {\n <number-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"_selectedComponent\"></number-attribute>\n } @else if (_selectedComponent.type === 'datetime') {\n <datetime-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"_selectedComponent\"></datetime-attribute>\n } @else if (_selectedComponent.type === 'select') {\n <select-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"_selectedComponent\"></select-attribute>\n } @else if (_selectedComponent.type === 'radio') {\n <radio-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"_selectedComponent\"></radio-attribute>\n } @else if (_selectedComponent.type === 'checkbox') {\n <checkbox-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"_selectedComponent\"></checkbox-attribute>\n } @else if (_selectedComponent.type === 'html') {\n <html-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"_selectedComponent\"></html-attribute>\n } @else if (_selectedComponent.type === 'upload') {\n <upload-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"_selectedComponent\"></upload-attribute>\n } @else if (_selectedComponent.type === 'table') {\n <table-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"_selectedComponent\"></table-attribute>\n } @else if (_selectedComponent.type === 'group') {\n <group-attribute [group]=\"$any(_selectedComponent)\"></group-attribute>\n }\n </div>\n } @else {\n <div class=\"fb-attrs__placeholder\">\n <span class=\"msi\">ads_click</span>\n <div class=\"title\">{{ 'core.component.form-builder.no-selection-title' | translate }}</div>\n <div class=\"hint\">{{ 'core.component.form-builder.no-selection-hint' | translate }}</div>\n </div>\n }\n </aside>\n</div>\n\n<!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 Reusable control template \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n<ng-template #controlTemplate let-item>\n @if (item.type === 'textfield') {\n <textfield-control [component]=\"item\"></textfield-control>\n } @else if (item.type === 'textarea') {\n <textarea-control [component]=\"item\"></textarea-control>\n } @else if (item.type === 'chip-string') {\n <chip-string-control [component]=\"item\"></chip-string-control>\n } @else if (item.type === 'chip-calendar') {\n <chip-calendar-control [component]=\"item\"></chip-calendar-control>\n } @else if (item.type === 'number') {\n <number-control [component]=\"item\"></number-control>\n } @else if (item.type === 'datetime') {\n <datetime-control [component]=\"item\"></datetime-control>\n } @else if (item.type === 'select') {\n <select-control [component]=\"item\"></select-control>\n } @else if (item.type === 'radio') {\n <radio-control [component]=\"item\"></radio-control>\n } @else if (item.type === 'checkbox') {\n <checkbox-control [component]=\"item\"></checkbox-control>\n } @else if (item.type === 'html') {\n <html-control [component]=\"item\"></html-control>\n } @else if (item.type === 'upload') {\n <upload-control [component]=\"item\"></upload-control>\n } @else if (item.type === 'table') {\n <table-control [component]=\"item\"></table-control>\n }\n</ng-template>\n\n<!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 JSON / variables modals \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n<sd-modal title=\"JSON\" view=\"dialog\" width=\"80%\" modalClass=\"sd-form-builder-dialog\" #popupViewJSON>\n <sd-textarea [(model)]=\"jsonString\"></sd-textarea>\n <ng-container sdFooterRight>\n <sd-button\n type=\"fill\"\n [title]=\"'core.component.form-builder.save' | translate\"\n (click)=\"updateJSON()\"\n prefixIcon=\"save\"\n color=\"primary\"></sd-button>\n </ng-container>\n</sd-modal>\n<sd-modal\n [title]=\"'core.component.form-builder.config-variable' | translate\"\n view=\"dialog\"\n width=\"800px\"\n modalClass=\"sd-form-builder-dialog\"\n #popupConfigureVariables>\n @for (variable of clonedVariables; track variable.id) {\n <div class=\"d-flex align-items-center\" style=\"gap: 8px\">\n <sd-input [form]=\"form\" [label]=\"'core.component.form-builder.field-key' | translate\" [(model)]=\"variable.key\" required></sd-input>\n <sd-input\n [form]=\"form\"\n [label]=\"'core.component.form-builder.display-label' | translate\"\n [(model)]=\"variable.label\"\n required></sd-input>\n <sd-button prefixIcon=\"delete\" (click)=\"removeVariables(variable.id)\" color=\"error\"></sd-button>\n </div>\n }\n <a href=\"javascript:;\" (click)=\"addVariables()\">{{ 'core.component.form-builder.add-variable' | translate }}</a>\n <ng-container sdFooterRight>\n <sd-button\n type=\"fill\"\n [title]=\"'core.component.form-builder.save' | translate\"\n (click)=\"updateVariables()\"\n prefixIcon=\"save\"\n color=\"primary\"></sd-button>\n </ng-container>\n</sd-modal>\n<configure-validation (accept)=\"onUpdateValidations($event)\"></configure-validation>\n", styles: ["@charset \"UTF-8\";:host{--md-sys-color-primary: var(--sd-primary, #005cbb);--md-sys-color-on-primary: var(--sd-primary-contrast, #ffffff);--md-sys-color-primary-container: var(--sd-primary-light, #d7e3ff);--md-sys-color-on-primary-container: var(--sd-primary-dark, #001b3f);--md-sys-color-info: var(--sd-info, #006a6a);--md-sys-color-info-container: var(--sd-info-light, #d4f7f6);--md-sys-color-secondary: #625b71;--md-sys-color-secondary-container: #e8def8;--md-sys-color-on-secondary-container: #1d192b;--md-sys-color-surface: var(--sd-surface, #fdfbff);--md-sys-color-surface-container-lowest: var(--mat-sys-surface-container-lowest, #ffffff);--md-sys-color-surface-container-low: var(--mat-sys-surface-container-low, #f7f8fc);--md-sys-color-surface-container: var(--sd-surface-muted, #f0f0f4);--md-sys-color-surface-container-high: var(--mat-sys-surface-container-high, #e9e9ee);--md-sys-color-surface-container-highest: var(--sd-surface-muted, #e7e8ed);--md-sys-color-on-surface: var(--sd-text, #1a1b1f);--md-sys-color-on-surface-variant: var(--sd-secondary, #44474f);--md-sys-color-outline: var(--sd-border-strong, #74777f);--md-sys-color-outline-variant: var(--sd-border, #c4c6d0);--md-sys-color-error: var(--sd-error, #ba1a1a);--md-sys-color-error-container: var(--sd-error-light, #ffdad6);--md-sys-color-warning: #8a5400;--md-sys-color-warning-container: #ffddb1;--md-sys-color-success: #1f6f43;--md-sys-color-success-container: #b7f0cb;--h-row: 28px;--h-field: 32px;--h-palette: 36px;--h-header: 36px;--h-section: 28px;--h-action-btn: 24px;--gap-1: 4px;--gap-2: 8px;--gap-3: 12px;--gap-4: 16px;--gap-5: 24px;--r-xs: 4px;--r-sm: 8px;--r-md: 12px;--r-lg: 16px;--r-xl: 28px;--r-pill: 999px;--elev-1: 0 1px 2px 0 rgba(0, 0, 0, .06);--elev-2: 0 2px 6px 0 rgba(0, 0, 0, .08), 0 1px 2px 0 rgba(0, 0, 0, .04);--elev-3: 0 6px 14px 0 rgba(0, 0, 0, .1), 0 2px 4px 0 rgba(0, 0, 0, .06);display:block;width:100%;height:100%;font-family:Roboto,system-ui,sans-serif;font-size:13px;color:var(--md-sys-color-on-surface);background:var(--md-sys-color-surface)}:host ::ng-deep .msi{font-family:Material Icons Outlined;font-weight:400;font-style:normal;line-height:1;letter-spacing:normal;text-transform:none;display:inline-block;white-space:nowrap;word-wrap:normal;direction:ltr;font-feature-settings:\"liga\";-webkit-font-feature-settings:\"liga\";-webkit-font-smoothing:antialiased}:host ::ng-deep .msi.filled{font-family:Material Icons}:host ::ng-deep .mono{font-family:Roboto Mono,ui-monospace,monospace}:host ::ng-deep .fb-icon-btn{width:28px;height:28px;border-radius:var(--r-sm);border:none;background:transparent;color:var(--md-sys-color-on-surface-variant);display:inline-flex;align-items:center;justify-content:center;cursor:pointer;transition:background .12s ease,color .12s ease}:host ::ng-deep .fb-icon-btn:hover{background:color-mix(in srgb,var(--md-sys-color-on-surface) 8%,transparent);color:var(--md-sys-color-on-surface)}:host ::ng-deep .fb-icon-btn.is-active{color:var(--md-sys-color-primary);background:var(--md-sys-color-primary-container)}:host ::ng-deep .fb-icon-btn.danger:hover{color:var(--md-sys-color-error);background:var(--md-sys-color-error-container)}:host ::ng-deep .fb-icon-btn.warning.on{color:var(--md-sys-color-warning);background:var(--md-sys-color-warning-container)}:host ::ng-deep .fb-icon-btn .msi{font-size:18px}:host ::ng-deep .fb-field{display:flex;flex-direction:column;gap:4px}:host ::ng-deep .fb-field__label{font-size:11px;font-weight:500;color:var(--md-sys-color-on-surface-variant)}:host ::ng-deep .fb-field__input{height:var(--h-row);padding:0 10px;border-radius:var(--r-xs);background:var(--md-sys-color-surface-container-lowest);border:1px solid var(--md-sys-color-outline-variant);font-size:12px;display:flex;align-items:center;justify-content:space-between;color:var(--md-sys-color-on-surface)}:host ::ng-deep .fb-field__input.placeholder{color:var(--md-sys-color-on-surface-variant)}:host ::ng-deep .fb-field__input:focus-within,:host ::ng-deep .fb-field__input.focused{border-color:var(--md-sys-color-primary);box-shadow:0 0 0 2px color-mix(in srgb,var(--md-sys-color-primary) 18%,transparent)}:host ::ng-deep .fb-field__input .msi{font-size:16px;color:var(--md-sys-color-on-surface-variant)}:host ::ng-deep .fb-field-row{display:flex;align-items:center;gap:var(--gap-2);justify-content:space-between}:host ::ng-deep .fb-switch{display:inline-flex;width:34px;height:18px;border-radius:999px;background:var(--md-sys-color-surface-container-highest);border:1.5px solid var(--md-sys-color-outline);padding:1px;align-items:center;cursor:pointer;position:relative;flex-shrink:0}:host ::ng-deep .fb-switch .fb-switch__thumb{width:12px;height:12px;border-radius:50%;background:var(--md-sys-color-outline)}:host ::ng-deep .fb-switch.on{background:var(--md-sys-color-primary);border-color:var(--md-sys-color-primary);justify-content:flex-end}:host ::ng-deep .fb-switch.on .fb-switch__thumb{background:var(--md-sys-color-on-primary);width:14px;height:14px}:host ::ng-deep .fb-expr{display:flex;align-items:center;gap:6px;height:var(--h-row);padding:0 6px 0 0;border-radius:var(--r-xs);background:var(--md-sys-color-surface-container-lowest);border:1px solid var(--md-sys-color-outline-variant);font-size:12px;color:var(--md-sys-color-on-surface-variant)}:host ::ng-deep .fb-expr__badge{height:100%;display:inline-flex;align-items:center;justify-content:center;padding:0 6px;background:var(--md-sys-color-primary-container);color:var(--md-sys-color-primary);font-family:Roboto Mono,monospace;font-size:10px;font-weight:500;border-radius:var(--r-xs) 0 0 var(--r-xs)}:host ::ng-deep .fb-expr__code{font-family:Roboto Mono,monospace;flex:1;font-size:11px;color:var(--md-sys-color-on-surface)}:host ::ng-deep .fb-expr__code .var{color:var(--md-sys-color-primary);font-weight:500}:host ::ng-deep .fb-expr__code .op{color:var(--md-sys-color-error)}:host ::ng-deep .fb-expr__code .lit{color:var(--md-sys-color-success)}:host ::ng-deep .fb-expr .empty{color:var(--md-sys-color-on-surface-variant);font-style:italic;padding-left:6px;flex:1}:host ::ng-deep details.fb-section{border-bottom:1px solid var(--md-sys-color-outline-variant);margin:0;padding:0;background:transparent}:host ::ng-deep details.fb-section>summary{list-style:none}:host ::ng-deep details.fb-section>summary::-webkit-details-marker{display:none}:host ::ng-deep .fb-section__head{height:var(--h-section);padding:0 var(--gap-4);display:flex;align-items:center;gap:var(--gap-2);cursor:pointer;font-size:12px;font-weight:500;color:var(--md-sys-color-on-surface);-webkit-user-select:none;user-select:none}:host ::ng-deep .fb-section__head .msi{font-size:16px;color:var(--md-sys-color-on-surface-variant)}:host ::ng-deep .fb-section__head .fb-section__title{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host ::ng-deep .fb-section__head .chev{margin-left:auto;transition:transform .16s ease}:host ::ng-deep details.fb-section[open] .fb-section__head .chev{transform:rotate(90deg)}:host ::ng-deep .fb-section__body{padding:4px var(--gap-4) var(--gap-3);display:flex;flex-direction:column;gap:var(--gap-2)}:host ::ng-deep .fb-columns-list{display:flex;flex-direction:column;gap:var(--gap-1);padding-top:var(--gap-1)}:host ::ng-deep .fb-column-row{display:flex;align-items:center;gap:var(--gap-2);height:40px;padding:6px 10px;border-radius:var(--r-sm);background:var(--md-sys-color-surface-container-lowest);border:1px solid var(--md-sys-color-outline-variant);font-size:12px;cursor:pointer;transition:background .12s ease,border-color .12s ease,box-shadow .12s ease}:host ::ng-deep .fb-column-row:hover{background:color-mix(in srgb,var(--md-sys-color-primary) 4%,transparent);border-color:color-mix(in srgb,var(--md-sys-color-primary) 30%,transparent)}:host ::ng-deep .fb-column-row.is-active{border-color:var(--md-sys-color-primary);background:color-mix(in srgb,var(--md-sys-color-primary) 10%,transparent);box-shadow:0 0 0 1px color-mix(in srgb,var(--md-sys-color-primary) 30%,transparent)}:host ::ng-deep .fb-column-row .fb-column-row__type-icon{width:28px;height:28px;border-radius:var(--r-sm);background:var(--md-sys-color-primary-container);color:var(--md-sys-color-primary);display:inline-flex;align-items:center;justify-content:center;flex-shrink:0}:host ::ng-deep .fb-column-row .fb-column-row__type-icon .msi{font-size:16px}:host ::ng-deep .fb-column-row .fb-column-row__meta{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}:host ::ng-deep .fb-column-row .fb-column-row__meta .fb-column-row__label{font-size:13px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--md-sys-color-on-surface)}:host ::ng-deep .fb-column-row .fb-column-row__meta .fb-column-row__req{color:var(--md-sys-color-error);margin-left:2px;font-weight:600}:host ::ng-deep .fb-column-row .fb-column-row__meta .fb-column-row__sub{font-size:11px;color:var(--md-sys-color-on-surface-variant);font-family:Roboto Mono,ui-monospace,monospace;letter-spacing:.02em}:host ::ng-deep .cdk-drag-preview.fb-drag-preview,:host ::ng-deep .fb-drag-preview{width:132px;height:44px;box-sizing:border-box;border-radius:14px;background:var(--md-sys-color-surface-container-lowest, #ffffff);display:inline-flex;align-items:center;gap:10px;padding:8px 12px;box-shadow:0 14px 32px #0000003d,0 5px 14px #00000029;border:1.5px solid color-mix(in srgb,var(--md-sys-color-primary) 46%,var(--md-sys-color-outline-variant));cursor:grabbing;pointer-events:none;-webkit-user-select:none;user-select:none;opacity:1;overflow:hidden;z-index:10000}:host ::ng-deep .cdk-drag-preview.fb-drag-preview .fb-drag-preview__glyph,:host ::ng-deep .fb-drag-preview .fb-drag-preview__glyph{width:28px;height:28px;border-radius:50%;background:var(--md-sys-color-primary);flex-shrink:0;box-shadow:inset 0 0 0 5px color-mix(in srgb,var(--md-sys-color-on-primary) 18%,transparent)}:host ::ng-deep .cdk-drag-preview.fb-drag-preview .fb-drag-preview__body,:host ::ng-deep .fb-drag-preview .fb-drag-preview__body{flex:1;min-width:0;display:grid;gap:7px}:host ::ng-deep .cdk-drag-preview.fb-drag-preview .fb-drag-preview__line,:host ::ng-deep .fb-drag-preview .fb-drag-preview__line{height:7px;border-radius:999px;background:color-mix(in srgb,var(--md-sys-color-outline-variant) 72%,transparent)}:host ::ng-deep .cdk-drag-preview.fb-drag-preview .fb-drag-preview__line.is-strong,:host ::ng-deep .fb-drag-preview .fb-drag-preview__line.is-strong{width:74%;background:color-mix(in srgb,var(--md-sys-color-primary) 26%,var(--md-sys-color-outline-variant))}:host ::ng-deep .cdk-drag-placeholder.fb-drop-placeholder,:host ::ng-deep .fb-drop-placeholder{box-sizing:border-box;min-height:46px;border:1.5px dashed color-mix(in srgb,var(--md-sys-color-primary) 76%,var(--md-sys-color-outline-variant));border-radius:var(--r-sm);background:linear-gradient(135deg,color-mix(in srgb,var(--md-sys-color-primary) 10%,transparent),transparent 62%),color-mix(in srgb,var(--md-sys-color-primary) 4%,var(--md-sys-color-surface-container-lowest));color:var(--md-sys-color-primary);display:flex;align-items:center;gap:var(--gap-2);padding:8px 10px;pointer-events:none;-webkit-user-select:none;user-select:none;opacity:1!important;transform:none!important;animation:none!important;position:relative;z-index:6;isolation:isolate;box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--md-sys-color-primary) 10%,transparent),0 2px 8px color-mix(in srgb,var(--md-sys-color-primary) 10%,transparent)}:host ::ng-deep .fb-drop-placeholder__icon{width:28px;height:28px;border-radius:var(--r-sm);background:var(--md-sys-color-primary-container);color:var(--md-sys-color-primary);display:inline-flex;align-items:center;justify-content:center;flex:0 0 28px}:host ::ng-deep .fb-drop-placeholder__icon .msi{font-size:17px}:host ::ng-deep .fb-drop-placeholder__body{min-width:0;display:flex;flex-direction:column;gap:2px}:host ::ng-deep .fb-drop-placeholder__title{color:var(--md-sys-color-on-surface);font-size:12px;font-weight:600;line-height:1.2;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host ::ng-deep .fb-drop-placeholder__meta{color:var(--md-sys-color-primary);font-size:10px;font-weight:500;line-height:1.2;text-transform:uppercase;letter-spacing:0}:host ::ng-deep .fb-palette__list>.fb-drop-placeholder{display:none!important}:host ::ng-deep .fb-row__items>.fb-drop-placeholder,:host ::ng-deep .fb-group__body>.fb-drop-placeholder{flex:0 0 calc(var(--fb-placeholder-columns, 6) / 12 * 100% - var(--gap-2));min-width:min(144px,100%);max-width:100%;align-self:stretch;margin:0 2px}:host ::ng-deep [cdkDropListOrientation=vertical]>.fb-drop-placeholder{width:auto;min-height:50px;margin:6px 14px;align-self:stretch}:host ::ng-deep .fb-drop-placeholder--row{min-height:56px}:host ::ng-deep .fb-row-insert-placeholder{--fb-placeholder-columns: 12;box-sizing:border-box;min-height:58px;margin:0 var(--gap-2) var(--gap-2);border:1.5px dashed color-mix(in srgb,var(--md-sys-color-primary) 68%,var(--md-sys-color-outline-variant));border-radius:var(--r-sm);background:linear-gradient(135deg,color-mix(in srgb,var(--md-sys-color-primary) 9%,transparent),transparent 68%),color-mix(in srgb,var(--md-sys-color-primary) 3%,var(--md-sys-color-surface-container-lowest));color:var(--md-sys-color-primary);display:flex;align-items:center;gap:var(--gap-2);padding:8px 10px;box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--md-sys-color-primary) 8%,transparent),0 1px 6px color-mix(in srgb,var(--md-sys-color-primary) 8%,transparent);pointer-events:none;position:relative;z-index:6;isolation:isolate}:host ::ng-deep .cdk-drop-list-receiving{background:color-mix(in srgb,var(--md-sys-color-primary) 3%,transparent);transition:background .16s ease}:host ::ng-deep .fb-group__body.cdk-drop-list-receiving,:host ::ng-deep .fb-group__body.cdk-drop-list-dragging{background:color-mix(in srgb,var(--md-sys-color-primary) 7%,transparent)!important;outline:1px solid color-mix(in srgb,var(--md-sys-color-primary) 72%,transparent);outline-offset:-6px;border-radius:var(--r-sm)}:host ::ng-deep .fb-resize.cdk-drag-preview{display:none!important}:host ::ng-deep .fb-shell--dragging .fb-item:hover,:host ::ng-deep .fb-shell--dragging .fb-group:hover,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-item:hover,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-group:hover{border-color:transparent!important;background:transparent!important}:host ::ng-deep .fb-shell--dragging .fb-row:hover,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-row:hover{outline-color:transparent!important;background:transparent!important}:host ::ng-deep .fb-shell--dragging .fb-row:hover .fb-row__drag,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-row:hover .fb-row__drag{opacity:0!important}:host ::ng-deep .fb-shell--dragging .fb-row.is-drag-target,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-row.is-drag-target{outline-color:color-mix(in srgb,var(--md-sys-color-info) 44%,transparent)!important;background:color-mix(in srgb,var(--md-sys-color-info) 4%,transparent)!important}:host ::ng-deep .fb-shell--dragging .fb-row__drag,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-row__drag{opacity:0!important}:host ::ng-deep .fb-shell--dragging .fb-actions,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-actions{opacity:0!important;pointer-events:none!important;transform:translateY(-2px)!important}:host ::ng-deep .fb-shell--dragging .fb-resize,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-resize{opacity:0!important;pointer-events:none!important}:host ::ng-deep .fb-shell--dragging .fb-status-chip,:host ::ng-deep .fb-shell--dragging .fb-resize-chip,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-status-chip,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-resize-chip{opacity:0!important;pointer-events:none!important}:host ::ng-deep .fb-shell--dragging .fb-row-break-btn,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-row-break-btn{opacity:0!important;pointer-events:none!important}:host ::ng-deep .fb-shell--dragging .fb-palette-item:hover,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-palette-item:hover{background:transparent!important}:host ::ng-deep .fb-shell--dragging .fb-palette-item:hover .fb-palette-item__drag,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-palette-item:hover .fb-palette-item__drag{opacity:0!important}:host ::ng-deep .fb-shell--dragging .fb-group__body,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-group__body{min-height:72px!important;background:color-mix(in srgb,var(--md-sys-color-primary) 4%,transparent)!important;outline:1px dashed color-mix(in srgb,var(--md-sys-color-primary) 48%,transparent)!important;outline-offset:-6px;border-radius:var(--r-sm);transition:background .16s ease,outline .16s ease,min-height .16s ease}:host ::ng-deep .fb-shell--dragging .fb-group__empty,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-group__empty{opacity:1;border-color:color-mix(in srgb,var(--md-sys-color-primary) 55%,transparent);background:color-mix(in srgb,var(--md-sys-color-primary) 7%,transparent);color:var(--md-sys-color-primary)}:host ::ng-deep .fb-shell--dragging .fb-group__body.cdk-drop-list-receiving,:host ::ng-deep .fb-shell--dragging .fb-group__body.cdk-drop-list-dragging,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-group__body.cdk-drop-list-receiving,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-group__body.cdk-drop-list-dragging{background:color-mix(in srgb,var(--md-sys-color-primary) 9%,transparent)!important;outline:1.5px solid var(--md-sys-color-primary)!important}:host ::ng-deep .fb-shell--resizing .fb-row.is-resizing-row{outline-color:color-mix(in srgb,var(--md-sys-color-primary) 42%,transparent);background:color-mix(in srgb,var(--md-sys-color-primary) 3%,transparent)}:host ::ng-deep .fb-shell--resizing .fb-row.is-resizing-row .fb-row__items:before{content:\"\";position:absolute;inset:-3px 0;border-radius:var(--r-sm);background:repeating-linear-gradient(90deg,transparent 0,transparent calc(8.333% - 1px),color-mix(in srgb,var(--md-sys-color-primary) 18%,transparent) calc(8.333% - 1px),color-mix(in srgb,var(--md-sys-color-primary) 18%,transparent) 8.333%),color-mix(in srgb,var(--md-sys-color-primary) 3%,transparent);pointer-events:none;z-index:0}:host ::ng-deep .fb-shell--resizing .fb-row.is-resizing-row .fb-row__items>*{position:relative;z-index:1}:host ::ng-deep .fb-shell--resizing .fb-item.is-resizing{border-color:var(--md-sys-color-primary)!important;background:color-mix(in srgb,var(--md-sys-color-primary) 6%,var(--md-sys-color-surface-container-lowest));box-shadow:0 0 0 2px color-mix(in srgb,var(--md-sys-color-primary) 18%,transparent),inset 0 0 0 1px color-mix(in srgb,var(--md-sys-color-primary) 18%,transparent);cursor:col-resize;z-index:2}:host ::ng-deep .fb-shell--resizing .fb-item.is-resizing .fb-resize{opacity:1!important;pointer-events:auto!important;background:var(--md-sys-color-primary);box-shadow:0 0 0 4px color-mix(in srgb,var(--md-sys-color-primary) 16%,transparent)}:host ::ng-deep .cdk-drag-animating{transition:transform .22s cubic-bezier(.2,0,0,1)!important}:host ::ng-deep .cdk-drag-dragging{transition:none!important}:host ::ng-deep .fb-item:not(.cdk-drag-dragging){transition:flex .12s cubic-bezier(.2,0,0,1),max-width .12s cubic-bezier(.2,0,0,1),border-color .12s,background .12s}::ng-deep .cdk-drag-preview.fb-drag-preview{width:132px;height:44px;box-sizing:border-box;border-radius:14px;background:#fff;display:inline-flex;align-items:center;gap:10px;padding:8px 12px;box-shadow:0 14px 32px #0000003d,0 5px 14px #00000029;border:1.5px solid color-mix(in srgb,var(--sd-primary, #005cbb) 46%,#d6d2da);cursor:grabbing;pointer-events:none;-webkit-user-select:none;user-select:none;opacity:1;overflow:hidden;z-index:10000}::ng-deep .cdk-drag-preview.fb-drag-preview .fb-drag-preview__glyph{width:28px;height:28px;border-radius:50%;background:var(--sd-primary, #005cbb);flex-shrink:0;box-shadow:inset 0 0 0 5px #ffffff2e}::ng-deep .cdk-drag-preview.fb-drag-preview .fb-drag-preview__body{flex:1;min-width:0;display:grid;gap:7px}::ng-deep .cdk-drag-preview.fb-drag-preview .fb-drag-preview__line{height:7px;border-radius:999px;background:#d6d2dab8}::ng-deep .cdk-drag-preview.fb-drag-preview .fb-drag-preview__line.is-strong{width:74%;background:color-mix(in srgb,var(--sd-primary, #005cbb) 26%,#d6d2da)}::ng-deep .sd-form-builder-dialog .mat-mdc-dialog-surface,::ng-deep .sd-form-builder-dialog .mdc-dialog__surface,::ng-deep .sd-form-builder-dialog .mat-dialog-container{max-height:calc(100vh - 40px)}::ng-deep .sd-form-builder-dialog .sd-modal-body{padding:16px;overscroll-behavior:contain;scroll-padding-bottom:24px}::ng-deep .sd-form-builder-dialog .sd-modal-footer{min-height:64px;padding:12px 16px;background:var(--sd-surface, #fff);box-shadow:0 -1px 0 var(--sd-border, #dde0e5)}::ng-deep .fb-validation-menu.mat-mdc-menu-panel{border-radius:12px;box-shadow:0 8px 24px #0f172a2e;overflow:hidden}.fb-shell{width:100%;height:100%;display:flex;background:var(--md-sys-color-surface);overflow:hidden;min-height:0}.fb-panel-header{height:var(--h-header);display:flex;align-items:center;justify-content:space-between;padding:0 var(--gap-3);border-bottom:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-low);flex-shrink:0}.fb-panel-header__title{font-size:11px;font-weight:500;letter-spacing:.08em;text-transform:uppercase;color:var(--md-sys-color-on-surface-variant)}.fb-panel-header__actions{display:flex;align-items:center;gap:2px}.fb-palette{width:220px;flex-shrink:0;display:flex;flex-direction:column;background:var(--md-sys-color-surface-container-low);border-right:1px solid var(--md-sys-color-outline-variant)}.fb-palette__scroll{flex:1;overflow-y:auto;padding-bottom:var(--gap-3)}.fb-palette__search{margin:var(--gap-3);height:32px;display:flex;align-items:center;gap:var(--gap-2);padding:0 var(--gap-3);border-radius:var(--r-pill);background:var(--md-sys-color-surface-container-high);color:var(--md-sys-color-on-surface-variant);font-size:13px;flex-shrink:0}.fb-palette__search .msi{font-size:18px}.fb-palette__search input{flex:1;border:none;outline:none;background:transparent;color:var(--md-sys-color-on-surface);font-size:13px;font-family:inherit}.fb-palette__search input::placeholder{color:var(--md-sys-color-on-surface-variant)}.fb-palette__search .kbd{font-family:Roboto Mono,monospace;font-size:10px;padding:1px 6px;border-radius:var(--r-xs);background:var(--md-sys-color-surface-container-lowest);border:1px solid var(--md-sys-color-outline-variant);color:var(--md-sys-color-on-surface-variant)}.fb-palette__group-label{font-size:11px;font-weight:500;letter-spacing:.04em;color:var(--md-sys-color-on-surface-variant);padding:var(--gap-3) var(--gap-4) var(--gap-1);text-transform:uppercase}.fb-palette__list{display:flex;flex-direction:column;padding:0 var(--gap-2)}.fb-palette-item{height:var(--h-palette);display:flex;align-items:center;gap:var(--gap-3);padding:0 var(--gap-3);border-radius:var(--r-sm);cursor:grab;color:var(--md-sys-color-on-surface);position:relative;-webkit-user-select:none;user-select:none}.fb-palette-item:hover{background:color-mix(in srgb,var(--md-sys-color-primary) 8%,transparent)}.fb-palette-item .fb-palette-item__icon{width:24px;height:24px;border-radius:var(--r-xs);display:inline-flex;align-items:center;justify-content:center;color:var(--md-sys-color-primary);background:var(--md-sys-color-primary-container);flex-shrink:0}.fb-palette-item .fb-palette-item__icon .msi{font-size:16px}.fb-palette-item .fb-palette-item__name{font-size:13px;font-weight:400}.fb-palette-item .fb-palette-item__drag{margin-left:auto;color:var(--md-sys-color-outline);opacity:0;transition:opacity .12s ease}.fb-palette-item .fb-palette-item__drag .msi{font-size:16px}.fb-palette-item:hover .fb-palette-item__drag{opacity:1}.fb-canvas{flex:1 1 0;min-width:0;display:flex;flex-direction:column;background:var(--md-sys-color-surface);overflow:hidden}.fb-canvas__header{height:var(--h-header);display:flex;align-items:center;justify-content:space-between;padding:0 var(--gap-4);border-bottom:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface);gap:var(--gap-3);flex-shrink:0}.fb-canvas__title{display:flex;align-items:center;gap:var(--gap-2);font-size:11px;font-weight:500;letter-spacing:.08em;text-transform:uppercase;color:var(--md-sys-color-on-surface-variant)}.fb-canvas__title .breadcrumb-sep{color:var(--md-sys-color-outline)}.fb-canvas__actions{display:flex;align-items:center;gap:var(--gap-2)}.fb-divider{width:1px;height:18px;background:var(--md-sys-color-outline-variant)}.fb-canvas__body{flex:1 1 0;overflow-y:auto;overflow-x:hidden;padding:var(--gap-4);position:relative;background:var(--md-sys-color-surface);min-width:0}.fb-canvas__body.is-preview{background:var(--md-sys-color-surface-container-low);padding:32px}.fb-mode-toggle{display:inline-flex;height:32px;padding:2px;border:1px solid var(--md-sys-color-outline-variant);border-radius:var(--r-pill);background:var(--md-sys-color-surface-container-lowest);flex-shrink:0;gap:2px;box-shadow:var(--elev-1)}.fb-mode-toggle button{border:0;background:transparent;border-radius:var(--r-pill);padding:0 12px;font-size:12px;font-weight:500;display:inline-flex;align-items:center;gap:6px;color:var(--md-sys-color-on-surface-variant);cursor:pointer;transition:background .12s ease,color .12s ease,box-shadow .12s ease}.fb-mode-toggle button .msi{font-size:16px}.fb-mode-toggle button:hover{background:color-mix(in srgb,var(--md-sys-color-primary) 7%,transparent);color:var(--md-sys-color-on-surface)}.fb-mode-toggle button:focus-visible{outline:2px solid color-mix(in srgb,var(--md-sys-color-primary) 48%,transparent);outline-offset:1px}.fb-mode-toggle button.is-active{background:var(--md-sys-color-primary-container);color:var(--md-sys-color-primary);box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--md-sys-color-primary) 18%,transparent)}.fb-row{position:relative;display:flex;gap:var(--gap-2);padding:6px var(--gap-2);border-radius:var(--r-sm);margin-bottom:6px;min-width:0;transition:outline .12s ease,background .12s ease;outline:1px dashed transparent;outline-offset:-1px}.fb-row:hover{outline-color:var(--md-sys-color-outline-variant);background:color-mix(in srgb,var(--md-sys-color-primary) 3%,transparent)}.fb-row:hover .fb-row__drag{opacity:1}.fb-row.is-drag-target{outline-color:color-mix(in srgb,var(--md-sys-color-info) 44%,transparent);background:color-mix(in srgb,var(--md-sys-color-info) 4%,transparent)}.fb-row__items{position:relative;min-width:0}.fb-row__drag{position:absolute;left:-22px;top:50%;transform:translateY(-50%);width:18px;height:22px;border-radius:var(--r-xs);display:inline-flex;align-items:center;justify-content:center;color:var(--md-sys-color-on-surface-variant);background:var(--md-sys-color-surface-container-high);opacity:0;cursor:grab;z-index:2}.fb-row__drag .msi{font-size:14px}.fb-row-break-btn{position:absolute;right:-28px;top:50%;transform:translateY(-50%);width:22px;height:22px;border-radius:var(--r-pill);display:inline-flex;align-items:center;justify-content:center;color:var(--md-sys-color-on-surface-variant);background:var(--md-sys-color-surface-container-high);border:1px solid var(--md-sys-color-outline-variant);cursor:pointer;opacity:0;z-index:2;transition:opacity .12s ease,background .12s ease,color .12s ease}.fb-row-break-btn .msi{font-size:14px}.fb-row-break-btn:hover{background:var(--md-sys-color-primary-container);color:var(--md-sys-color-primary);border-color:transparent}.fb-row:hover .fb-row-break-btn{opacity:1}.fb-item{position:relative;border:1.5px dashed transparent;border-radius:var(--r-md);padding:var(--gap-2) var(--gap-3);min-height:56px;min-width:0;transition:border-color .12s ease,background .12s ease;background:transparent;cursor:grab;display:flex;flex-direction:column;gap:6px}.fb-item:hover{border-color:color-mix(in srgb,var(--md-sys-color-primary) 60%,transparent)}.fb-item.is-selected{border:1.5px solid var(--md-sys-color-primary);background:color-mix(in srgb,var(--md-sys-color-primary) 4%,transparent);box-shadow:0 0 0 2px color-mix(in srgb,var(--md-sys-color-primary) 18%,transparent),inset 3px 0 0 var(--md-sys-color-primary)}.fb-item.is-hidden{opacity:.45}.fb-item.is-readonly:after{content:\"\";position:absolute;inset:0;border-radius:var(--r-md);background:repeating-linear-gradient(135deg,transparent 0 6px,color-mix(in srgb,var(--md-sys-color-on-surface) 4%,transparent) 6px 7px);pointer-events:none}.fb-item.column-2{flex:0 0 calc(16.6666666667% - var(--gap-2));max-width:calc(16.6666666667% - var(--gap-2))}.fb-item.column-3{flex:0 0 calc(25% - var(--gap-2));max-width:calc(25% - var(--gap-2))}.fb-item.column-4{flex:0 0 calc(33.3333333333% - var(--gap-2));max-width:calc(33.3333333333% - var(--gap-2))}.fb-item.column-5{flex:0 0 calc(41.6666666667% - var(--gap-2));max-width:calc(41.6666666667% - var(--gap-2))}.fb-item.column-6{flex:0 0 calc(50% - var(--gap-2));max-width:calc(50% - var(--gap-2))}.fb-item.column-7{flex:0 0 calc(58.3333333333% - var(--gap-2));max-width:calc(58.3333333333% - var(--gap-2))}.fb-item.column-8{flex:0 0 calc(66.6666666667% - var(--gap-2));max-width:calc(66.6666666667% - var(--gap-2))}.fb-item.column-9{flex:0 0 calc(75% - var(--gap-2));max-width:calc(75% - var(--gap-2))}.fb-item.column-10{flex:0 0 calc(83.3333333333% - var(--gap-2));max-width:calc(83.3333333333% - var(--gap-2))}.fb-item.column-11{flex:0 0 calc(91.6666666667% - var(--gap-2));max-width:calc(91.6666666667% - var(--gap-2))}.fb-item.column-12{flex:1 1 100%;max-width:100%}.fb-actions{position:absolute;top:-14px;right:8px;display:flex;align-items:center;gap:2px;padding:2px;border-radius:var(--r-pill);background:var(--md-sys-color-surface-container-lowest);box-shadow:var(--elev-2);border:1px solid var(--md-sys-color-outline-variant);z-index:4;opacity:0;transform:translateY(-2px);pointer-events:none;transition:opacity .12s ease,transform .12s ease}.fb-actions .fb-icon-btn{width:24px;height:24px}.fb-actions .fb-icon-btn .msi{font-size:16px}.fb-item:hover .fb-actions,.fb-item.is-selected .fb-actions{opacity:1;transform:translateY(0);pointer-events:auto}table-control{display:block;width:100%;max-width:100%;min-width:0}.fb-group>.fb-actions{top:6px;right:8px}.fb-group:hover>.fb-actions,.fb-group.is-selected>.fb-actions{opacity:1;transform:translateY(0);pointer-events:auto}.fb-status-chip{display:inline-flex;align-items:center;gap:4px;padding:2px 8px;height:18px;border-radius:var(--r-pill);font-size:10px;font-weight:500;letter-spacing:.02em;position:absolute;top:-9px;left:12px;z-index:3;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-lowest);color:var(--md-sys-color-on-surface-variant)}.fb-status-chip .msi{font-size:12px}.fb-status-chip.readonly{color:var(--md-sys-color-warning);background:var(--md-sys-color-warning-container);border-color:transparent}.fb-status-chip.hidden{color:var(--md-sys-color-on-surface-variant)}.fb-status-chip.conditional{color:var(--md-sys-color-primary);background:var(--md-sys-color-primary-container);border-color:transparent}.fb-resize-chip{position:absolute;right:14px;bottom:-18px;z-index:5;display:inline-flex;align-items:center;height:20px;padding:0 8px;border-radius:var(--r-pill);background:var(--md-sys-color-primary);color:var(--md-sys-color-on-primary);box-shadow:var(--elev-2);font-size:11px;font-weight:700;letter-spacing:0;pointer-events:none}.fb-resize{position:absolute;top:10px;bottom:10px;right:-6px;width:8px;border-radius:999px;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 28%,transparent);background:var(--md-sys-color-surface-container-lowest);display:flex;align-items:center;justify-content:center;cursor:col-resize;opacity:0;transition:opacity .12s ease,background .12s ease,box-shadow .12s ease;z-index:3}.fb-resize:before{content:\"\";width:2px;height:20px;background:var(--md-sys-color-primary);border-radius:999px}.fb-resize:hover{background:var(--md-sys-color-primary-container);box-shadow:0 0 0 3px color-mix(in srgb,var(--md-sys-color-primary) 12%,transparent)}.fb-item:hover .fb-resize{opacity:1}.fb-item.is-break{min-height:28px;padding:8px var(--gap-3);justify-content:center}.fb-break-line{display:flex;align-items:center;gap:var(--gap-2);color:var(--md-sys-color-on-surface-variant);font-size:11px;font-weight:500;letter-spacing:.06em;text-transform:uppercase}.fb-break-line:before,.fb-break-line:after{content:\"\";flex:1;height:1px;background:var(--md-sys-color-outline-variant)}.fb-break-line .msi{display:none}.fb-break-line .fb-break-label{color:var(--md-sys-color-on-surface-variant)}.fb-drop-hint{height:36px;border-radius:var(--r-sm);border:1.5px dashed var(--md-sys-color-primary);background:color-mix(in srgb,var(--md-sys-color-primary) 6%,transparent);color:var(--md-sys-color-primary);display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:500;gap:6px;margin:4px 0}.fb-drop-hint .msi{font-size:16px}.fb-group{background:var(--md-sys-color-surface-container-lowest);border-radius:var(--r-lg);border:1.5px dashed transparent;transition:border-color .12s ease,background .12s ease;position:relative;display:flex;flex-direction:column;overflow:visible;cursor:default}.fb-group:hover{border-color:color-mix(in srgb,var(--md-sys-color-primary) 60%,transparent)}.fb-group.is-selected{border:1.5px solid var(--md-sys-color-primary);box-shadow:0 0 0 2px color-mix(in srgb,var(--md-sys-color-primary) 18%,transparent),inset 3px 0 0 var(--md-sys-color-primary)}.fb-group.is-hidden{opacity:.55}.fb-group__header{display:flex;align-items:center;gap:var(--gap-2);padding:8px 12px;border-top-left-radius:12px;border-top-right-radius:12px;cursor:grab}.fb-group__header:active{cursor:grabbing}.fb-group__header .fb-group__icon .msi{font-size:18px}.fb-group__header .fb-group__label{font-size:13px;font-weight:500}.fb-group__header .fb-group__count{margin-left:auto;font-size:10px;opacity:.75}.fb-group__conditional-chip{display:inline-flex;align-items:center;gap:4px;font-family:Roboto Mono,monospace;font-size:10px;padding:2px 8px;border-radius:var(--r-pill);background:color-mix(in srgb,currentColor 12%,transparent)}.fb-group__body{padding:12px;display:flex;flex-wrap:wrap;gap:var(--gap-2);min-height:60px;position:relative}.fb-group__empty{display:flex;align-items:center;justify-content:center;height:56px;border-radius:var(--r-sm);border:1.5px dashed var(--md-sys-color-outline-variant);color:var(--md-sys-color-on-surface-variant);font-size:12px;font-style:italic}.fb-empty{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--gap-3);color:var(--md-sys-color-on-surface-variant);text-align:center;padding:24px;pointer-events:none}.fb-empty__art{width:96px;height:96px;border-radius:var(--r-xl);background:var(--md-sys-color-primary-container);color:var(--md-sys-color-primary);display:inline-flex;align-items:center;justify-content:center}.fb-empty__art .msi{font-size:44px}.fb-empty__title{font-size:16px;font-weight:500;color:var(--md-sys-color-on-surface)}.fb-empty__hint{font-size:13px;max-width:320px;line-height:1.5}.fb-empty__kbd{display:inline-flex;align-items:center;gap:6px;font-family:Roboto Mono,monospace;font-size:11px;padding:4px 8px;border-radius:var(--r-xs);border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container);color:var(--md-sys-color-on-surface-variant)}.fb-empty__kbd .msi{font-size:12px}.fb-attrs{width:320px;flex-shrink:0;display:flex;flex-direction:column;background:var(--md-sys-color-surface-container-low);border-left:1px solid var(--md-sys-color-outline-variant)}.fb-attrs__selection{padding:var(--gap-3) var(--gap-4);display:flex;align-items:center;gap:var(--gap-2);border-bottom:1px solid var(--md-sys-color-outline-variant)}.fb-attrs__sel-icon{width:28px;height:28px;border-radius:var(--r-sm);background:var(--md-sys-color-primary-container);color:var(--md-sys-color-primary);display:inline-flex;align-items:center;justify-content:center;flex-shrink:0}.fb-attrs__sel-icon .msi{font-size:18px}.fb-attrs__sel-meta{display:flex;flex-direction:column;min-width:0}.fb-attrs__sel-name{font-size:13px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.fb-attrs__sel-key{font-family:Roboto Mono,monospace;font-size:11px;color:var(--md-sys-color-on-surface-variant)}.fb-attrs__body{flex:1 1 0;overflow-y:auto;overflow-x:hidden;min-width:0}.fb-attrs__placeholder{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--gap-2);padding:32px;color:var(--md-sys-color-on-surface-variant);text-align:center}.fb-attrs__placeholder .msi{font-size:32px;color:var(--md-sys-color-outline)}.fb-attrs__placeholder .title{font-size:13px;font-weight:500;color:var(--md-sys-color-on-surface)}.fb-attrs__placeholder .hint{font-size:12px;max-width:220px;line-height:1.5}.fb-preview-card{max-width:760px;margin:0 auto;background:var(--md-sys-color-surface-container-lowest);border-radius:var(--r-lg);padding:32px;box-shadow:var(--elev-1)}.fb-preview-card__chip{display:inline-flex;align-items:center;gap:6px;padding:2px 10px;height:22px;border-radius:var(--r-pill);background:var(--md-sys-color-primary-container);color:var(--md-sys-color-primary);font-size:10px;font-weight:500;letter-spacing:.08em;text-transform:uppercase;margin-bottom:var(--gap-3)}.fb-preview-card__title{font-size:20px;font-weight:500;margin:0 0 var(--gap-4);color:var(--md-sys-color-on-surface)}.fb-preview-card__footer{display:flex;justify-content:flex-end;gap:var(--gap-2);margin-top:var(--gap-4);padding-top:var(--gap-4);border-top:1px solid var(--md-sys-color-outline-variant)}.fb-icon-picker{display:grid;grid-template-columns:repeat(6,1fr);gap:4px;margin-top:4px}.fb-icon-picker .fb-icon-picker__btn{height:28px;border-radius:var(--r-xs);border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-lowest);color:var(--md-sys-color-on-surface-variant);display:inline-flex;align-items:center;justify-content:center;cursor:pointer}.fb-icon-picker .fb-icon-picker__btn .msi{font-size:16px}.fb-icon-picker .fb-icon-picker__btn.is-active{border-color:var(--md-sys-color-primary);background:var(--md-sys-color-primary-container);color:var(--md-sys-color-primary)}.fb-icon-picker .fb-icon-picker__btn:hover{background:color-mix(in srgb,var(--md-sys-color-primary) 8%,transparent)}.fb-color-swatches{display:flex;gap:var(--gap-2);margin-top:4px}.fb-color-swatches .fb-color-swatch{width:24px;height:24px;border-radius:50%;cursor:pointer;border:2px solid transparent}.fb-color-swatches .fb-color-swatch.is-active{border-color:var(--md-sys-color-on-surface)}.fb-color-swatches .fb-color-swatch.primary{background:var(--md-sys-color-primary)}.fb-color-swatches .fb-color-swatch.secondary{background:var(--md-sys-color-secondary)}.fb-color-swatches .fb-color-swatch.success{background:var(--md-sys-color-success)}.fb-color-swatches .fb-color-swatch.warning{background:var(--md-sys-color-warning)}.fb-color-swatches .fb-color-swatch.error{background:var(--md-sys-color-error)}.fb-children-list{display:flex;flex-direction:column;gap:4px}.fb-child-row{display:flex;align-items:center;gap:var(--gap-2);height:30px;padding:0 8px;border-radius:var(--r-xs);background:var(--md-sys-color-surface-container-lowest);border:1px solid var(--md-sys-color-outline-variant);font-size:12px}.fb-child-row .fb-child-row__icon{width:22px;height:22px;border-radius:var(--r-xs);background:var(--md-sys-color-primary-container);color:var(--md-sys-color-primary);display:inline-flex;align-items:center;justify-content:center;flex-shrink:0}.fb-child-row .fb-child-row__icon .msi{font-size:14px}.fb-child-row .fb-child-row__label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-child-row .fb-child-row__key{font-family:Roboto Mono,monospace;font-size:10px;color:var(--md-sys-color-on-surface-variant)}.fb-child-row:hover{background:color-mix(in srgb,var(--md-sys-color-primary) 4%,transparent)}.fb-add-link{display:inline-flex;align-items:center;gap:4px;font-size:12px;color:var(--md-sys-color-primary);cursor:pointer;padding:4px 0;text-decoration:none}.fb-add-link .msi{font-size:14px}.fb-add-link:hover{text-decoration:underline}.fb-group__body--static{display:flex;flex-wrap:wrap;gap:var(--gap-2, 8px);cursor:pointer;min-height:48px}.fb-group__body--static .fb-item--static{pointer-events:none;position:relative}.fb-group__body--static .fb-item--static .fb-resize,.fb-group__body--static .fb-item--static .fb-actions,.fb-group__body--static .fb-item--static .fb-status-chip{display:none!important}.fb-canvas__header--group .fb-groupedit__crumb{display:flex;align-items:center;gap:6px;font-weight:600}.fb-canvas__header--group .fb-groupedit__icon .msi{font-size:18px;color:var(--md-sys-color-primary)}.fb-canvas__header--group .fb-groupedit__hint{font-weight:400;font-size:12px;color:var(--md-sys-color-on-surface-variant, #6b6b6b);padding:1px 8px;border-radius:999px;background:color-mix(in srgb,var(--md-sys-color-primary) 8%,transparent)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i2.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i3.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i3.CdkDropListGroup, selector: "[cdkDropListGroup]", inputs: ["cdkDropListGroupDisabled"], exportAs: ["cdkDropListGroup"] }, { kind: "directive", type: i3.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i3.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "directive", type: i3.CdkDragPreview, selector: "ng-template[cdkDragPreview]", inputs: ["data", "matchSize"] }, { kind: "directive", type: i3.CdkDragPlaceholder, selector: "ng-template[cdkDragPlaceholder]", inputs: ["data"] }, { kind: "component", type:
|
|
6666
6743
|
// Controls
|
|
6667
6744
|
TextFieldControl, selector: "textfield-control", inputs: ["component"] }, { kind: "component", type: TextfieldAttribute, selector: "textfield-attribute", inputs: ["components", "variables", "component"] }, { kind: "component", type: TextareaControl, selector: "textarea-control", inputs: ["component"] }, { kind: "component", type: TextareaAttribute, selector: "textarea-attribute", inputs: ["components", "variables", "component"] }, { kind: "component", type: ChipStringControl, selector: "chip-string-control", inputs: ["component"] }, { kind: "component", type: ChipStringAttribute, selector: "chip-string-attribute", inputs: ["components", "variables", "component"] }, { kind: "component", type: ChipCalendarControl, selector: "chip-calendar-control", inputs: ["component"] }, { kind: "component", type: ChipCalendarAttribute, selector: "chip-calendar-attribute", inputs: ["components", "variables", "component"] }, { kind: "component", type: NumberControl, selector: "number-control", inputs: ["component"] }, { kind: "component", type: NumberAttribute, selector: "number-attribute", inputs: ["components", "variables", "component"] }, { kind: "component", type: SelectControl, selector: "select-control", inputs: ["component"] }, { kind: "component", type: SelectAttribute, selector: "select-attribute", inputs: ["components", "variables", "component"] }, { kind: "component", type: DatetimeControl, selector: "datetime-control", inputs: ["component"] }, { kind: "component", type: DatetimeAttribute, selector: "datetime-attribute", inputs: ["components", "variables", "component"] }, { kind: "component", type: RadioControl, selector: "radio-control", inputs: ["component"] }, { kind: "component", type: RadioAttribute, selector: "radio-attribute", inputs: ["components", "variables", "component"] }, { kind: "component", type: CheckboxControl, selector: "checkbox-control", inputs: ["component"] }, { kind: "component", type: CheckboxAttribute, selector: "checkbox-attribute", inputs: ["components", "variables", "component"] }, { kind: "component", type: HtmlControl, selector: "html-control", inputs: ["component"] }, { kind: "component", type: HtmlAttribute, selector: "html-attribute", inputs: ["components", "variables", "component"], outputs: ["attributeChanges"] }, { kind: "component", type: UploadControl, selector: "upload-control", inputs: ["component"] }, { kind: "component", type: UploadAttribute, selector: "upload-attribute", inputs: ["components", "variables", "component"] }, { kind: "component", type: TableControl, selector: "table-control", inputs: ["component"] }, { kind: "component", type: TableAttribute, selector: "table-attribute", inputs: ["components", "variables", "component"] }, { kind: "component", type: GroupAttribute, selector: "group-attribute", inputs: ["group"] }, { kind: "component", type: SdModal, selector: "sd-modal", inputs: ["autoId", "title", "color", "width", "height", "view", "modalClass", "lazyLoadContent", "hideClose", "disableBackdropClose"], outputs: ["sdClosed"] }, { kind: "component", type: SdInput$1, selector: "sd-input", inputs: ["autoId", "name", "appearance", "floatLabel", "size", "form", "label", "helperText", "placeholder", "type", "hideInlineError", "blurOnEnter", "required", "readonly", "disabled", "viewed", "minlength", "maxlength", "pattern", "patternErrorMessage", "validator", "inlineError", "hyperlink", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "cleared", "sdFocusForceBlur"] }, { kind: "component", type: SdTextarea, selector: "sd-textarea", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "rows", "hideInlineError", "required", "disabled", "viewed", "autoHeight", "maxlength", "pattern", "validator", "inlineError", "appearance", "floatLabel", "model"], outputs: ["modelChange", "sdChange"] }, { kind: "component", type: SdButton, selector: "sd-button", inputs: ["autoId", "type", "color", "size", "fontSet", "title", "width", "tooltip", "prefixIcon", "suffixIcon", "disabled", "loading", "block", "htmlType"], outputs: ["click"] }, { kind: "component", type: SdFormRender, selector: "sd-form-render", inputs: ["form", "configuration", "defaultEntity", "entity", "properties", "viewed"] }, { kind: "component", type: ConfigureValidationComponent, selector: "configure-validation", outputs: ["accept"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
|
|
6668
6745
|
}
|
|
@@ -6705,7 +6782,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
6705
6782
|
SdFormRender,
|
|
6706
6783
|
ConfigureValidationComponent,
|
|
6707
6784
|
TranslatePipe,
|
|
6708
|
-
], template: "<!-- cdkDropListGroup: auto-connect t\u1EA5t c\u1EA3 cdkDropList trong palette, canvas rows, group bodies.\n [class.fb-shell--dragging]: JS-driven flag \u0111\u1EC3 \u1EA9n hover/actions/resize to\u00E0n di\u1EC7n\n trong l\u00FAc drag (CSS :has() kh\u00F4ng b\u1EAFt 100% tr\u01B0\u1EDDng h\u1EE3p v\u1EDBi CDK preview clone). -->\n<div\n class=\"fb-shell\"\n cdkDropListGroup\n [class.fb-shell--dragging]=\"isAnyDragging()\"\n [class.fb-shell--palette-dragging]=\"dragSource() === 'palette'\"\n [class.fb-shell--resizing]=\"isResizing()\">\n <ng-template #dragPreviewTemplate>\n <span class=\"fb-drag-preview__glyph\" aria-hidden=\"true\"></span>\n <span class=\"fb-drag-preview__body\" aria-hidden=\"true\">\n <span class=\"fb-drag-preview__line is-strong\"></span>\n <span class=\"fb-drag-preview__line\"></span>\n </span>\n </ng-template>\n <ng-template #dropPlaceholderTemplate let-item>\n <span class=\"fb-drop-placeholder__icon\" aria-hidden=\"true\">\n <span class=\"msi\">{{ placeholderSymbolFor(item) }}</span>\n </span>\n <span class=\"fb-drop-placeholder__body\" aria-hidden=\"true\">\n <span class=\"fb-drop-placeholder__title\">{{ placeholderTitleFor(item) }}</span>\n <span class=\"fb-drop-placeholder__meta\">{{ placeholderMetaFor(item) }}</span>\n </span>\n </ng-template>\n\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 LEFT PALETTE \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <aside class=\"fb-palette\">\n <div class=\"fb-panel-header\">\n <div class=\"fb-panel-header__title\">{{ 'core.component.form-builder.components' | translate }}</div>\n </div>\n\n <!-- Search pill -->\n <div class=\"fb-palette__search\">\n <span class=\"msi\" aria-hidden=\"true\">search</span>\n <input\n type=\"text\"\n [placeholder]=\"'core.component.form-builder.search-components' | translate\"\n [value]=\"paletteSearch()\"\n (input)=\"paletteSearch.set($any($event.target).value)\" />\n <span class=\"kbd\">\u2318K</span>\n </div>\n\n <div class=\"fb-palette__scroll\">\n @for (group of paletteGroups(); track group.key) {\n <div class=\"fb-palette__group-label\">{{ group.label }}</div>\n <div class=\"fb-palette__list\" cdkDropList [cdkDropListData]=\"group.items\" [cdkDropListEnterPredicate]=\"noReturnPredicate\">\n @for (formBuilderComponent of group.items; track formBuilderComponent.type) {\n <div\n cdkDrag\n [cdkDragData]=\"formBuilderComponent\"\n class=\"fb-palette-item\"\n [matTooltip]=\"formBuilderComponent.name\"\n matTooltipPosition=\"right\"\n (click)=\"addComponent(formBuilderComponent)\"\n (cdkDragStarted)=\"onPaletteDragStarted()\"\n (cdkDragMoved)=\"onAnyDragMoved($event)\"\n (cdkDragEnded)=\"onAnyDragEnded()\"\n aria-hidden=\"true\">\n <span class=\"fb-palette-item__icon\"\n ><span class=\"msi\">{{ formBuilderComponent.symbol }}</span></span\n >\n <span class=\"fb-palette-item__name\">{{ formBuilderComponent.name }}</span>\n <span class=\"fb-palette-item__drag\"><span class=\"msi\">drag_indicator</span></span>\n <div *cdkDragPreview class=\"fb-drag-preview\" aria-hidden=\"true\">\n <ng-container *ngTemplateOutlet=\"dragPreviewTemplate\"></ng-container>\n </div>\n <div *cdkDragPlaceholder class=\"fb-drop-placeholder\" aria-hidden=\"true\">\n <ng-container *ngTemplateOutlet=\"dropPlaceholderTemplate; context: { $implicit: formBuilderComponent }\"></ng-container>\n </div>\n </div>\n }\n </div>\n }\n </div>\n </aside>\n\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 CENTER CANVAS \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <section class=\"fb-canvas\">\n @let _editingGroup = editingGroup();\n @if (_editingGroup) {\n <!-- \u2500\u2500 Drill-in header: \u0111ang thi\u1EBFt k\u1EBF children c\u1EE7a 1 group \u2500\u2500 -->\n <header class=\"fb-canvas__header fb-canvas__header--group\">\n <div class=\"fb-canvas__title fb-groupedit__crumb\">\n <button\n type=\"button\"\n class=\"fb-icon-btn\"\n [matTooltip]=\"'core.component.form-builder.back-to-design' | translate\"\n (click)=\"cancelGroupEdit()\">\n <span class=\"msi\">chevron_left</span>\n </button>\n <span class=\"fb-groupedit__icon\"\n ><span class=\"msi\">{{ _editingGroup.properties?.icon || 'category' }}</span></span\n >\n <span>{{ _editingGroup.label || ('core.component.form-builder.edit-group' | translate) }}</span>\n <span class=\"fb-groupedit__hint\">{{ 'core.component.form-builder.edit-group' | translate }}</span>\n </div>\n <div class=\"fb-canvas__actions\">\n <sd-button type=\"outline\" [title]=\"'core.component.form-builder.cancel' | translate\" (click)=\"cancelGroupEdit()\"></sd-button>\n <sd-button\n type=\"fill\"\n color=\"primary\"\n prefixIcon=\"check\"\n [title]=\"'core.component.form-builder.confirm' | translate\"\n (click)=\"confirmGroupEdit()\"></sd-button>\n </div>\n </header>\n } @else {\n <header class=\"fb-canvas__header\">\n <div class=\"fb-canvas__title\">\n <span>{{ 'core.component.form-builder.layout' | translate }}</span>\n </div>\n <div class=\"fb-canvas__actions\">\n <!-- Toolbar cluster: view JSON / variables / validation -->\n <button type=\"button\" class=\"fb-icon-btn\" [matTooltip]=\"'core.component.form-builder.view-json' | translate\" (click)=\"viewJSON()\">\n <span class=\"msi\">data_object</span>\n </button>\n <button\n type=\"button\"\n class=\"fb-icon-btn\"\n [matTooltip]=\"'core.component.form-builder.config-variable' | translate\"\n (click)=\"configureVariables()\">\n <span class=\"msi\">data_array</span>\n </button>\n <button\n type=\"button\"\n class=\"fb-icon-btn\"\n [matTooltip]=\"'core.component.form-builder.config-validation-warning-error' | translate\"\n (click)=\"openConfigureValidation()\">\n <span class=\"msi\">rule</span>\n </button>\n\n <span class=\"fb-divider\"></span>\n\n <!-- M3 segmented Design/Preview toggle -->\n <div class=\"fb-mode-toggle\" role=\"tablist\">\n <button type=\"button\" role=\"tab\" [class.is-active]=\"!isPreview()\" [attr.aria-selected]=\"!isPreview()\" (click)=\"setMode(false)\">\n <span class=\"msi\">edit</span>\n <span>{{ 'core.component.form-builder.design' | translate }}</span>\n </button>\n <button type=\"button\" role=\"tab\" [class.is-active]=\"isPreview()\" [attr.aria-selected]=\"isPreview()\" (click)=\"setMode(true)\">\n <span class=\"msi\">visibility</span>\n <span>{{ 'core.component.form-builder.preview' | translate }}</span>\n </button>\n </div>\n </div>\n </header>\n }\n\n <div class=\"fb-canvas__body\" [class.is-preview]=\"isPreview()\" id=\"frmComponent\">\n @if (isPreview()) {\n <!-- \u2500\u2500 Preview mode \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"fb-preview-card\">\n <div class=\"fb-preview-card__chip\">\n <span class=\"msi\">visibility</span>\n {{ 'core.component.form-builder.preview-runtime' | translate }}\n </div>\n <sd-form-render [configuration]=\"{ components: this.components, validations: this.validations }\"></sd-form-render>\n <div class=\"fb-preview-card__footer\">\n <sd-button type=\"outline\" [title]=\"'core.component.form-builder.cancel' | translate\" (click)=\"setMode(false)\"></sd-button>\n <sd-button\n type=\"fill\"\n color=\"primary\"\n [title]=\"'core.component.form-builder.submit' | translate\"\n (click)=\"onValidate()\"></sd-button>\n </div>\n </div>\n } @else {\n <!-- \u2500\u2500 Design mode \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (!dragDropRows.length) {\n <div class=\"fb-empty\">\n <div class=\"fb-empty__art\"><span class=\"msi\">dashboard_customize</span></div>\n <div class=\"fb-empty__title\">{{ 'core.component.form-builder.empty-title' | translate }}</div>\n <div class=\"fb-empty__hint\">\n {{ 'core.component.form-builder.empty-hint' | translate }}\n <span class=\"fb-empty__kbd\"><span class=\"msi\">keyboard</span>\u2318 K</span>\n </div>\n </div>\n }\n\n <div\n cdkDropList\n cdkDropListOrientation=\"vertical\"\n [cdkDropListData]=\"dragDropRows\"\n (cdkDropListDropped)=\"drop($event)\"\n (click)=\"clickFormContentEmpty()\"\n aria-hidden=\"true\">\n @for (row of dragDropRows; track row.id) {\n <div\n cdkDrag\n [cdkDragData]=\"row\"\n class=\"fb-row\"\n (mouseover)=\"onMouseover($event, row)\"\n (focus)=\"onFocus($event)\"\n (cdkDragStarted)=\"onRowDragStarted()\"\n (cdkDragMoved)=\"onAnyDragMoved($event)\"\n (cdkDragEnded)=\"onAnyDragEnded()\"\n [class.is-drag-target]=\"targetItem === row\"\n [class.is-row-full]=\"isRowFull(row)\"\n [class.is-row-inline-locked]=\"isRowInlineDropLocked(row)\"\n [class.is-resizing-row]=\"resizeState()?.rowId === row.id\"\n [id]=\"row.id\"\n aria-hidden=\"true\">\n <div class=\"fb-row__drag\" [matTooltip]=\"'core.component.form-builder.drag-vertically' | translate\" cdkDragHandle>\n <span class=\"msi\">drag_indicator</span>\n </div>\n <div *cdkDragPreview class=\"fb-drag-preview\" aria-hidden=\"true\">\n <ng-container *ngTemplateOutlet=\"dragPreviewTemplate\"></ng-container>\n </div>\n <div *cdkDragPlaceholder class=\"fb-drop-placeholder fb-drop-placeholder--row\" aria-hidden=\"true\">\n <ng-container *ngTemplateOutlet=\"dropPlaceholderTemplate; context: { $implicit: row }\"></ng-container>\n </div>\n <!-- Quick-add break button \u2014 show on row hover, insert break after this row -->\n <button\n type=\"button\"\n class=\"fb-row-break-btn\"\n [matTooltip]=\"'core.component.form-builder.break' | translate\"\n matTooltipPosition=\"right\"\n (click)=\"insertBreakAfter(row); $event.stopPropagation()\">\n <span class=\"msi\">horizontal_rule</span>\n </button>\n\n <div\n class=\"fb-row__items\"\n style=\"display: flex; flex: 1; gap: var(--gap-2)\"\n cdkDropList\n cdkDropListOrientation=\"horizontal\"\n [cdkDropListData]=\"row.items\"\n [cdkDropListEnterPredicate]=\"canEnterRowDropList\"\n (cdkDropListDropped)=\"drop($event)\">\n @for (item of row.items; track item.id; let i = $index) {\n <!-- Group component renders as outlined card with colored header bar -->\n @if (item.type === 'group') {\n @let g = $any(item);\n @let cv = groupColorVars(g.properties?.color);\n <div\n cdkDrag\n [cdkDragData]=\"item\"\n [id]=\"item.id\"\n class=\"fb-group column-{{ item.layout?.columns || 12 }}\"\n [class.fb-item]=\"false\"\n [class.is-selected]=\"item.id === selectedComponent()?.id\"\n [class.is-hidden]=\"item.properties?.hidden\"\n [style.flex]=\"'0 0 calc(' + +(item.layout?.columns || 12) + '/12 * 100% - var(--gap-2))'\"\n [style.maxWidth]=\"'calc(' + +(item.layout?.columns || 12) + '/12 * 100% - var(--gap-2))'\"\n (click)=\"selectComponent(item); $event.stopPropagation()\"\n (cdkDragStarted)=\"dragStartComponentItem($event)\"\n (cdkDragMoved)=\"onAnyDragMoved($event)\"\n (cdkDragEnded)=\"dragEndComponentItem($event); onAnyDragEnded()\"\n aria-hidden=\"true\">\n <div *cdkDragPreview class=\"fb-drag-preview\" aria-hidden=\"true\">\n <ng-container *ngTemplateOutlet=\"dragPreviewTemplate\"></ng-container>\n </div>\n <div *cdkDragPlaceholder class=\"fb-drop-placeholder\" aria-hidden=\"true\">\n <ng-container *ngTemplateOutlet=\"dropPlaceholderTemplate; context: { $implicit: item }\"></ng-container>\n </div>\n <!-- cdkDragHandle: ch\u1EC9 cho ph\u00E9p k\u00E9o group qua header \u2014 body free cho click v\u00E0o child. -->\n <div class=\"fb-group__header\" cdkDragHandle [style.background]=\"cv.bg\" [style.color]=\"cv.fg\">\n <span class=\"fb-group__icon\"\n ><span class=\"msi\">{{ g.properties?.icon || 'category' }}</span></span\n >\n <span class=\"fb-group__label\">{{ item.label }}</span>\n @if (hasConditional(item)) {\n <span class=\"fb-group__conditional-chip\">\n <span class=\"msi\" style=\"font-size: 12px\">bolt</span>\n {{ 'core.component.form-builder.conditional' | translate }}\n </span>\n }\n <span class=\"fb-group__count\">{{ g.components?.length || 0 }} fields</span>\n </div>\n <!-- Floating action toolbar -->\n <div class=\"fb-actions\">\n <!-- why: Detail = m\u1EDF canvas thi\u1EBFt k\u1EBF ri\u00EAng cho group (thay cho drag/drop trong group). -->\n <button\n type=\"button\"\n class=\"fb-icon-btn\"\n [matTooltip]=\"'core.component.form-builder.edit-group' | translate\"\n (click)=\"enterGroupEdit(g); $event.stopPropagation()\">\n <span class=\"msi\">open_in_full</span>\n </button>\n <button\n type=\"button\"\n class=\"fb-icon-btn\"\n [matTooltip]=\"'core.component.form-builder.duplicate' | translate\"\n (click)=\"onDuplicate(item); $event.stopPropagation()\">\n <span class=\"msi\">content_copy</span>\n </button>\n <button\n type=\"button\"\n class=\"fb-icon-btn\"\n [class.warning]=\"item.properties?.hidden\"\n [class.on]=\"item.properties?.hidden\"\n [matTooltip]=\"\n item.properties?.hidden\n ? ('core.component.form-builder.show' | translate)\n : ('core.component.form-builder.hide' | translate)\n \"\n (click)=\"onChangeHidden(item); $event.stopPropagation()\">\n <span class=\"msi\">{{ item.properties?.hidden ? 'visibility_off' : 'visibility' }}</span>\n </button>\n <button\n type=\"button\"\n class=\"fb-icon-btn danger\"\n [matTooltip]=\"'core.component.form-builder.delete' | translate\"\n (click)=\"removeComponent(item.id); $event.stopPropagation()\">\n <span class=\"msi\">delete</span>\n </button>\n </div>\n\n <!-- Static preview: children render read-only (kh\u00F4ng drag/resize/select).\n S\u1EEDa n\u1ED9i dung group qua n\u00FAt Detail (enterGroupEdit). -->\n <div class=\"fb-group__body fb-group__body--static\" (click)=\"enterGroupEdit(g); $event.stopPropagation()\">\n @if (!g.components?.length) {\n <div class=\"fb-group__empty\">\n <span class=\"msi\" style=\"font-size: 16px; margin-right: 6px\">open_in_full</span>\n {{ 'core.component.form-builder.group-empty-hint' | translate }}\n </div>\n }\n @for (child of g.components; track child.id) {\n <div\n class=\"fb-item is-child fb-item--static column-{{ child.layout?.columns || 12 }}\"\n [class.is-hidden]=\"child.properties?.hidden\">\n <ng-container *ngTemplateOutlet=\"controlTemplate; context: { $implicit: child }\"> </ng-container>\n </div>\n }\n </div>\n </div>\n } @else if (item.type === 'break') {\n <!-- Break: row separator c\u1ED1 \u0111\u1ECBnh. Render 12-col line, kh\u00F4ng c\u00F3 resize/edit attribute. -->\n <div\n cdkDrag\n [cdkDragData]=\"item\"\n [id]=\"item.id\"\n class=\"fb-item is-break column-12\"\n [class.is-selected]=\"item.id === selectedComponent()?.id\"\n [class.is-hidden]=\"item.properties?.hidden\"\n (click)=\"selectComponent(item); $event.stopPropagation()\"\n (cdkDragStarted)=\"dragStartComponentItem($event)\"\n (cdkDragMoved)=\"onAnyDragMoved($event)\"\n (cdkDragEnded)=\"dragEndComponentItem($event); onAnyDragEnded()\"\n aria-hidden=\"true\">\n <div *cdkDragPreview class=\"fb-drag-preview\" aria-hidden=\"true\">\n <ng-container *ngTemplateOutlet=\"dragPreviewTemplate\"></ng-container>\n </div>\n <div *cdkDragPlaceholder class=\"fb-drop-placeholder\" aria-hidden=\"true\">\n <ng-container *ngTemplateOutlet=\"dropPlaceholderTemplate; context: { $implicit: item }\"></ng-container>\n </div>\n <div class=\"fb-break-line\">\n <span class=\"msi\">horizontal_rule</span>\n <span class=\"fb-break-label\">{{ 'core.component.form-builder.break' | translate }}</span>\n </div>\n <div class=\"fb-actions\">\n <button\n type=\"button\"\n class=\"fb-icon-btn\"\n [class.warning]=\"item.properties?.hidden\"\n [class.on]=\"item.properties?.hidden\"\n [matTooltip]=\"\n item.properties?.hidden\n ? ('core.component.form-builder.show' | translate)\n : ('core.component.form-builder.hide' | translate)\n \"\n (click)=\"onChangeHidden(item); $event.stopPropagation()\">\n <span class=\"msi\">{{ item.properties?.hidden ? 'visibility_off' : 'visibility' }}</span>\n </button>\n <button\n type=\"button\"\n class=\"fb-icon-btn danger\"\n [matTooltip]=\"'core.component.form-builder.delete' | translate\"\n (click)=\"removeComponent(item.id); $event.stopPropagation()\">\n <span class=\"msi\">delete</span>\n </button>\n </div>\n </div>\n } @else {\n <!-- Regular form component -->\n <div\n cdkDrag\n [cdkDragData]=\"item\"\n [id]=\"item.id\"\n class=\"fb-item column-{{ item.layout?.columns || 12 }}\"\n [class.is-selected]=\"item.id === selectedComponent()?.id\"\n [class.is-hidden]=\"item.properties?.hidden\"\n [class.is-readonly]=\"item.properties?.viewed\"\n [class.is-resizing]=\"resizeState()?.itemId === item.id\"\n (click)=\"selectComponent(item); $event.stopPropagation()\"\n (cdkDragStarted)=\"dragStartComponentItem($event)\"\n (cdkDragMoved)=\"onAnyDragMoved($event)\"\n (cdkDragEnded)=\"dragEndComponentItem($event); onAnyDragEnded()\"\n aria-hidden=\"true\">\n <div *cdkDragPreview class=\"fb-drag-preview\" aria-hidden=\"true\">\n <ng-container *ngTemplateOutlet=\"dragPreviewTemplate\"></ng-container>\n </div>\n <div *cdkDragPlaceholder class=\"fb-drop-placeholder\" aria-hidden=\"true\">\n <ng-container *ngTemplateOutlet=\"dropPlaceholderTemplate; context: { $implicit: item }\"></ng-container>\n </div>\n\n <!-- Status chip (mutually exclusive priority: hidden > readonly > conditional) -->\n @if (item.properties?.hidden) {\n <span class=\"fb-status-chip hidden\">\n <span class=\"msi\">visibility_off</span>\n {{ 'core.component.form-builder.hidden' | translate }}\n </span>\n } @else if (item.properties?.viewed) {\n <span class=\"fb-status-chip readonly\">\n <span class=\"msi\">edit_off</span>\n {{ 'core.component.form-builder.read-only' | translate }}\n </span>\n } @else if (hasConditional(item)) {\n <span class=\"fb-status-chip conditional\">\n <span class=\"msi\">bolt</span>\n {{ 'core.component.form-builder.conditional' | translate }}\n </span>\n }\n\n <!-- Floating action toolbar pill -->\n <div class=\"fb-actions\">\n <button\n type=\"button\"\n class=\"fb-icon-btn\"\n [class.warning]=\"item.properties?.viewed\"\n [class.on]=\"item.properties?.viewed\"\n [matTooltip]=\"\n item.properties?.viewed\n ? ('core.component.form-builder.unset-readonly' | translate)\n : ('core.component.form-builder.set-readonly' | translate)\n \"\n (click)=\"onChangeViewed(item); $event.stopPropagation()\">\n <span class=\"msi\">edit_off</span>\n </button>\n <button\n type=\"button\"\n class=\"fb-icon-btn\"\n [matTooltip]=\"'core.component.form-builder.duplicate' | translate\"\n (click)=\"onDuplicate(item); $event.stopPropagation()\">\n <span class=\"msi\">content_copy</span>\n </button>\n <button\n type=\"button\"\n class=\"fb-icon-btn\"\n [class.warning]=\"item.properties?.hidden\"\n [class.on]=\"item.properties?.hidden\"\n [matTooltip]=\"\n item.properties?.hidden\n ? ('core.component.form-builder.show' | translate)\n : ('core.component.form-builder.hide' | translate)\n \"\n (click)=\"onChangeHidden(item); $event.stopPropagation()\">\n <span class=\"msi\">{{ item.properties?.hidden ? 'visibility_off' : 'visibility' }}</span>\n </button>\n <button\n type=\"button\"\n class=\"fb-icon-btn danger\"\n [matTooltip]=\"'core.component.form-builder.delete' | translate\"\n (click)=\"removeComponent(item.id); $event.stopPropagation()\">\n <span class=\"msi\">delete</span>\n </button>\n </div>\n\n <ng-container *ngTemplateOutlet=\"controlTemplate; context: { $implicit: item }\"> </ng-container>\n\n @if (resizeState()?.itemId === item.id) {\n <span class=\"fb-resize-chip\">{{ resizeState()?.columns }}/12</span>\n }\n\n <!-- Resize handle (pill) \u2014 cdkDragLockAxis=\"x\" \u0111\u1EC3 ch\u1EC9 k\u00E9o ngang.\n Wrapping cdkDropList c\u00F3 enterPredicate=false, kh\u00F4ng nh\u1EADn drop. -->\n <div cdkDropList cdkDropListOrientation=\"horizontal\" [cdkDropListEnterPredicate]=\"noReturnPredicate\">\n <div\n cdkDrag\n cdkDragLockAxis=\"x\"\n class=\"fb-resize\"\n (cdkDragStarted)=\"startResizeControl(item, row)\"\n (cdkDragMoved)=\"changeSizeControl($event, item, row.items, i)\"\n (cdkDragEnded)=\"endResizeControl($event)\"\n [matTooltip]=\"'core.component.form-builder.flex' | translate\"\n aria-label=\"resize\"></div>\n </div>\n </div>\n }\n }\n </div>\n </div>\n }\n </div>\n }\n </div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 RIGHT ATTRIBUTES \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <aside class=\"fb-attrs\">\n <div class=\"fb-panel-header\">\n <div class=\"fb-panel-header__title\">{{ 'core.component.form-builder.attribute' | translate }}</div>\n </div>\n\n @let _selectedComponent = selectedComponent();\n @if (_selectedComponent) {\n <!-- Selection header: type icon + name + key/type -->\n <div class=\"fb-attrs__selection\">\n <span class=\"fb-attrs__sel-icon\"\n ><span class=\"msi\">{{ symbolFor(_selectedComponent) }}</span></span\n >\n <div class=\"fb-attrs__sel-meta\">\n <div class=\"fb-attrs__sel-name\">{{ $any(_selectedComponent).label || ('core.component.form-builder.untitled' | translate) }}</div>\n <div class=\"fb-attrs__sel-key\">\n @if ($any(_selectedComponent).key) {\n {{ $any(_selectedComponent).key }} \u00B7 {{ typeLabelFor(_selectedComponent) }}\n } @else {\n {{ typeLabelFor(_selectedComponent) }}\n }\n </div>\n </div>\n </div>\n\n <div class=\"fb-attrs__body\" aria-hidden=\"true\">\n @if (_selectedComponent.type === 'textfield') {\n <textfield-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"_selectedComponent\"></textfield-attribute>\n } @else if (_selectedComponent.type === 'textarea') {\n <textarea-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"_selectedComponent\"></textarea-attribute>\n } @else if (_selectedComponent.type === 'chip-string') {\n <chip-string-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"_selectedComponent\"></chip-string-attribute>\n } @else if (_selectedComponent.type === 'chip-calendar') {\n <chip-calendar-attribute\n [components]=\"components\"\n [variables]=\"variables\"\n [component]=\"_selectedComponent\"></chip-calendar-attribute>\n } @else if (_selectedComponent.type === 'number') {\n <number-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"_selectedComponent\"></number-attribute>\n } @else if (_selectedComponent.type === 'datetime') {\n <datetime-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"_selectedComponent\"></datetime-attribute>\n } @else if (_selectedComponent.type === 'select') {\n <select-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"_selectedComponent\"></select-attribute>\n } @else if (_selectedComponent.type === 'radio') {\n <radio-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"_selectedComponent\"></radio-attribute>\n } @else if (_selectedComponent.type === 'checkbox') {\n <checkbox-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"_selectedComponent\"></checkbox-attribute>\n } @else if (_selectedComponent.type === 'html') {\n <html-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"_selectedComponent\"></html-attribute>\n } @else if (_selectedComponent.type === 'upload') {\n <upload-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"_selectedComponent\"></upload-attribute>\n } @else if (_selectedComponent.type === 'table') {\n <table-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"_selectedComponent\"></table-attribute>\n } @else if (_selectedComponent.type === 'group') {\n <group-attribute [group]=\"$any(_selectedComponent)\"></group-attribute>\n }\n </div>\n } @else {\n <div class=\"fb-attrs__placeholder\">\n <span class=\"msi\">ads_click</span>\n <div class=\"title\">{{ 'core.component.form-builder.no-selection-title' | translate }}</div>\n <div class=\"hint\">{{ 'core.component.form-builder.no-selection-hint' | translate }}</div>\n </div>\n }\n </aside>\n</div>\n\n<!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 Reusable control template \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n<ng-template #controlTemplate let-item>\n @if (item.type === 'textfield') {\n <textfield-control [component]=\"item\"></textfield-control>\n } @else if (item.type === 'textarea') {\n <textarea-control [component]=\"item\"></textarea-control>\n } @else if (item.type === 'chip-string') {\n <chip-string-control [component]=\"item\"></chip-string-control>\n } @else if (item.type === 'chip-calendar') {\n <chip-calendar-control [component]=\"item\"></chip-calendar-control>\n } @else if (item.type === 'number') {\n <number-control [component]=\"item\"></number-control>\n } @else if (item.type === 'datetime') {\n <datetime-control [component]=\"item\"></datetime-control>\n } @else if (item.type === 'select') {\n <select-control [component]=\"item\"></select-control>\n } @else if (item.type === 'radio') {\n <radio-control [component]=\"item\"></radio-control>\n } @else if (item.type === 'checkbox') {\n <checkbox-control [component]=\"item\"></checkbox-control>\n } @else if (item.type === 'html') {\n <html-control [component]=\"item\"></html-control>\n } @else if (item.type === 'upload') {\n <upload-control [component]=\"item\"></upload-control>\n } @else if (item.type === 'table') {\n <table-control [component]=\"item\"></table-control>\n }\n</ng-template>\n\n<!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 JSON / variables modals \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n<sd-modal title=\"JSON\" view=\"dialog\" width=\"80%\" #popupViewJSON>\n <sd-textarea [(model)]=\"jsonString\"></sd-textarea>\n <ng-container sdFooter>\n <sd-button\n type=\"fill\"\n [title]=\"'core.component.form-builder.save' | translate\"\n (click)=\"updateJSON()\"\n prefixIcon=\"save\"\n color=\"primary\"></sd-button>\n </ng-container>\n</sd-modal>\n<sd-modal [title]=\"'core.component.form-builder.config-variable' | translate\" view=\"dialog\" width=\"800px\" #popupConfigureVariables>\n @for (variable of clonedVariables; track variable.id) {\n <div class=\"d-flex align-items-center\" style=\"gap: 8px\">\n <sd-input [form]=\"form\" [label]=\"'core.component.form-builder.field-key' | translate\" [(model)]=\"variable.key\" required></sd-input>\n <sd-input\n [form]=\"form\"\n [label]=\"'core.component.form-builder.display-label' | translate\"\n [(model)]=\"variable.label\"\n required></sd-input>\n <sd-button prefixIcon=\"delete\" (click)=\"removeVariables(variable.id)\" color=\"error\"></sd-button>\n </div>\n }\n <a href=\"javascript:;\" (click)=\"addVariables()\">{{ 'core.component.form-builder.add-variable' | translate }}</a>\n <ng-container sdFooter>\n <sd-button\n type=\"fill\"\n [title]=\"'core.component.form-builder.save' | translate\"\n (click)=\"updateVariables()\"\n prefixIcon=\"save\"\n color=\"primary\"></sd-button>\n </ng-container>\n</sd-modal>\n<configure-validation (accept)=\"onUpdateValidations($event)\"></configure-validation>\n", styles: ["@charset \"UTF-8\";:host{--md-sys-color-primary: var(--sd-primary, #2a66f4);--md-sys-color-on-primary: #ffffff;--md-sys-color-primary-container: var(--sd-primary-light, #eaf1ff);--md-sys-color-on-primary-container: var(--sd-primary, #2a66f4);--md-sys-color-info: var(--sd-info, #2962ff);--md-sys-color-info-container: var(--sd-info-light, #e7e9ff);--md-sys-color-secondary: #625b71;--md-sys-color-secondary-container: #e8def8;--md-sys-color-on-secondary-container: #1d192b;--md-sys-color-surface: #fbfafc;--md-sys-color-surface-container-lowest: #ffffff;--md-sys-color-surface-container-low: #f6f4f8;--md-sys-color-surface-container: #f1eef3;--md-sys-color-surface-container-high: #ebe8ee;--md-sys-color-surface-container-highest: #e6e2ea;--md-sys-color-on-surface: #1c1b1f;--md-sys-color-on-surface-variant: #49454e;--md-sys-color-outline: #79747e;--md-sys-color-outline-variant: #d6d2da;--md-sys-color-error: #b3261e;--md-sys-color-error-container: #f9dedc;--md-sys-color-warning: #8a5400;--md-sys-color-warning-container: #ffddb1;--md-sys-color-success: #1f6f43;--md-sys-color-success-container: #b7f0cb;--h-row: 28px;--h-field: 32px;--h-palette: 36px;--h-header: 36px;--h-section: 28px;--h-action-btn: 24px;--gap-1: 4px;--gap-2: 8px;--gap-3: 12px;--gap-4: 16px;--gap-5: 24px;--r-xs: 4px;--r-sm: 8px;--r-md: 12px;--r-lg: 16px;--r-xl: 28px;--r-pill: 999px;--elev-1: 0 1px 2px 0 rgba(0, 0, 0, .06);--elev-2: 0 2px 6px 0 rgba(0, 0, 0, .08), 0 1px 2px 0 rgba(0, 0, 0, .04);--elev-3: 0 6px 14px 0 rgba(0, 0, 0, .1), 0 2px 4px 0 rgba(0, 0, 0, .06);display:block;width:100%;height:100%;font-family:Roboto,system-ui,sans-serif;font-size:13px;color:var(--md-sys-color-on-surface);background:var(--md-sys-color-surface)}:host ::ng-deep .msi{font-family:Material Icons Outlined;font-weight:400;font-style:normal;line-height:1;letter-spacing:normal;text-transform:none;display:inline-block;white-space:nowrap;word-wrap:normal;direction:ltr;font-feature-settings:\"liga\";-webkit-font-feature-settings:\"liga\";-webkit-font-smoothing:antialiased}:host ::ng-deep .msi.filled{font-family:Material Icons}:host ::ng-deep .mono{font-family:Roboto Mono,ui-monospace,monospace}:host ::ng-deep .fb-icon-btn{width:28px;height:28px;border-radius:var(--r-sm);border:none;background:transparent;color:var(--md-sys-color-on-surface-variant);display:inline-flex;align-items:center;justify-content:center;cursor:pointer;transition:background .12s ease,color .12s ease}:host ::ng-deep .fb-icon-btn:hover{background:color-mix(in srgb,var(--md-sys-color-on-surface) 8%,transparent);color:var(--md-sys-color-on-surface)}:host ::ng-deep .fb-icon-btn.is-active{color:var(--md-sys-color-primary);background:var(--md-sys-color-primary-container)}:host ::ng-deep .fb-icon-btn.danger:hover{color:var(--md-sys-color-error);background:var(--md-sys-color-error-container)}:host ::ng-deep .fb-icon-btn.warning.on{color:var(--md-sys-color-warning);background:var(--md-sys-color-warning-container)}:host ::ng-deep .fb-icon-btn .msi{font-size:18px}:host ::ng-deep .fb-field{display:flex;flex-direction:column;gap:4px}:host ::ng-deep .fb-field__label{font-size:11px;font-weight:500;color:var(--md-sys-color-on-surface-variant)}:host ::ng-deep .fb-field__input{height:var(--h-row);padding:0 10px;border-radius:var(--r-xs);background:var(--md-sys-color-surface-container-lowest);border:1px solid var(--md-sys-color-outline-variant);font-size:12px;display:flex;align-items:center;justify-content:space-between;color:var(--md-sys-color-on-surface)}:host ::ng-deep .fb-field__input.placeholder{color:var(--md-sys-color-on-surface-variant)}:host ::ng-deep .fb-field__input:focus-within,:host ::ng-deep .fb-field__input.focused{border-color:var(--md-sys-color-primary);box-shadow:0 0 0 2px color-mix(in srgb,var(--md-sys-color-primary) 18%,transparent)}:host ::ng-deep .fb-field__input .msi{font-size:16px;color:var(--md-sys-color-on-surface-variant)}:host ::ng-deep .fb-field-row{display:flex;align-items:center;gap:var(--gap-2);justify-content:space-between}:host ::ng-deep .fb-switch{display:inline-flex;width:34px;height:18px;border-radius:999px;background:var(--md-sys-color-surface-container-highest);border:1.5px solid var(--md-sys-color-outline);padding:1px;align-items:center;cursor:pointer;position:relative;flex-shrink:0}:host ::ng-deep .fb-switch .fb-switch__thumb{width:12px;height:12px;border-radius:50%;background:var(--md-sys-color-outline)}:host ::ng-deep .fb-switch.on{background:var(--md-sys-color-primary);border-color:var(--md-sys-color-primary);justify-content:flex-end}:host ::ng-deep .fb-switch.on .fb-switch__thumb{background:var(--md-sys-color-on-primary);width:14px;height:14px}:host ::ng-deep .fb-expr{display:flex;align-items:center;gap:6px;height:var(--h-row);padding:0 6px 0 0;border-radius:var(--r-xs);background:var(--md-sys-color-surface-container-lowest);border:1px solid var(--md-sys-color-outline-variant);font-size:12px;color:var(--md-sys-color-on-surface-variant)}:host ::ng-deep .fb-expr__badge{height:100%;display:inline-flex;align-items:center;justify-content:center;padding:0 6px;background:var(--md-sys-color-primary-container);color:var(--md-sys-color-primary);font-family:Roboto Mono,monospace;font-size:10px;font-weight:500;border-radius:var(--r-xs) 0 0 var(--r-xs)}:host ::ng-deep .fb-expr__code{font-family:Roboto Mono,monospace;flex:1;font-size:11px;color:var(--md-sys-color-on-surface)}:host ::ng-deep .fb-expr__code .var{color:var(--md-sys-color-primary);font-weight:500}:host ::ng-deep .fb-expr__code .op{color:var(--md-sys-color-error)}:host ::ng-deep .fb-expr__code .lit{color:var(--md-sys-color-success)}:host ::ng-deep .fb-expr .empty{color:var(--md-sys-color-on-surface-variant);font-style:italic;padding-left:6px;flex:1}:host ::ng-deep details.fb-section{border-bottom:1px solid var(--md-sys-color-outline-variant);margin:0;padding:0;background:transparent}:host ::ng-deep details.fb-section>summary{list-style:none}:host ::ng-deep details.fb-section>summary::-webkit-details-marker{display:none}:host ::ng-deep .fb-section__head{height:var(--h-section);padding:0 var(--gap-4);display:flex;align-items:center;gap:var(--gap-2);cursor:pointer;font-size:12px;font-weight:500;color:var(--md-sys-color-on-surface);-webkit-user-select:none;user-select:none}:host ::ng-deep .fb-section__head .msi{font-size:16px;color:var(--md-sys-color-on-surface-variant)}:host ::ng-deep .fb-section__head .fb-section__title{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host ::ng-deep .fb-section__head .chev{margin-left:auto;transition:transform .16s ease}:host ::ng-deep details.fb-section[open] .fb-section__head .chev{transform:rotate(90deg)}:host ::ng-deep .fb-section__body{padding:4px var(--gap-4) var(--gap-3);display:flex;flex-direction:column;gap:var(--gap-2)}:host ::ng-deep .fb-columns-list{display:flex;flex-direction:column;gap:var(--gap-1);padding-top:var(--gap-1)}:host ::ng-deep .fb-column-row{display:flex;align-items:center;gap:var(--gap-2);height:40px;padding:6px 10px;border-radius:var(--r-sm);background:var(--md-sys-color-surface-container-lowest);border:1px solid var(--md-sys-color-outline-variant);font-size:12px;cursor:pointer;transition:background .12s ease,border-color .12s ease,box-shadow .12s ease}:host ::ng-deep .fb-column-row:hover{background:color-mix(in srgb,var(--md-sys-color-primary) 4%,transparent);border-color:color-mix(in srgb,var(--md-sys-color-primary) 30%,transparent)}:host ::ng-deep .fb-column-row.is-active{border-color:var(--md-sys-color-primary);background:color-mix(in srgb,var(--md-sys-color-primary) 10%,transparent);box-shadow:0 0 0 1px color-mix(in srgb,var(--md-sys-color-primary) 30%,transparent)}:host ::ng-deep .fb-column-row .fb-column-row__type-icon{width:28px;height:28px;border-radius:var(--r-sm);background:var(--md-sys-color-primary-container);color:var(--md-sys-color-primary);display:inline-flex;align-items:center;justify-content:center;flex-shrink:0}:host ::ng-deep .fb-column-row .fb-column-row__type-icon .msi{font-size:16px}:host ::ng-deep .fb-column-row .fb-column-row__meta{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}:host ::ng-deep .fb-column-row .fb-column-row__meta .fb-column-row__label{font-size:13px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--md-sys-color-on-surface)}:host ::ng-deep .fb-column-row .fb-column-row__meta .fb-column-row__req{color:var(--md-sys-color-error);margin-left:2px;font-weight:600}:host ::ng-deep .fb-column-row .fb-column-row__meta .fb-column-row__sub{font-size:11px;color:var(--md-sys-color-on-surface-variant);font-family:Roboto Mono,ui-monospace,monospace;letter-spacing:.02em}:host ::ng-deep .cdk-drag-preview.fb-drag-preview,:host ::ng-deep .fb-drag-preview{width:132px;height:44px;box-sizing:border-box;border-radius:14px;background:var(--md-sys-color-surface-container-lowest, #ffffff);display:inline-flex;align-items:center;gap:10px;padding:8px 12px;box-shadow:0 14px 32px #0000003d,0 5px 14px #00000029;border:1.5px solid color-mix(in srgb,var(--md-sys-color-primary) 46%,var(--md-sys-color-outline-variant));cursor:grabbing;pointer-events:none;-webkit-user-select:none;user-select:none;opacity:1;overflow:hidden;z-index:10000}:host ::ng-deep .cdk-drag-preview.fb-drag-preview .fb-drag-preview__glyph,:host ::ng-deep .fb-drag-preview .fb-drag-preview__glyph{width:28px;height:28px;border-radius:50%;background:var(--md-sys-color-primary);flex-shrink:0;box-shadow:inset 0 0 0 5px color-mix(in srgb,var(--md-sys-color-on-primary) 18%,transparent)}:host ::ng-deep .cdk-drag-preview.fb-drag-preview .fb-drag-preview__body,:host ::ng-deep .fb-drag-preview .fb-drag-preview__body{flex:1;min-width:0;display:grid;gap:7px}:host ::ng-deep .cdk-drag-preview.fb-drag-preview .fb-drag-preview__line,:host ::ng-deep .fb-drag-preview .fb-drag-preview__line{height:7px;border-radius:999px;background:color-mix(in srgb,var(--md-sys-color-outline-variant) 72%,transparent)}:host ::ng-deep .cdk-drag-preview.fb-drag-preview .fb-drag-preview__line.is-strong,:host ::ng-deep .fb-drag-preview .fb-drag-preview__line.is-strong{width:74%;background:color-mix(in srgb,var(--md-sys-color-primary) 26%,var(--md-sys-color-outline-variant))}:host ::ng-deep .cdk-drag-placeholder.fb-drop-placeholder,:host ::ng-deep .fb-drop-placeholder{box-sizing:border-box;min-height:46px;border:1.5px dashed color-mix(in srgb,var(--md-sys-color-primary) 76%,var(--md-sys-color-outline-variant));border-radius:var(--r-sm);background:linear-gradient(135deg,color-mix(in srgb,var(--md-sys-color-primary) 10%,transparent),transparent 62%),color-mix(in srgb,var(--md-sys-color-primary) 4%,var(--md-sys-color-surface-container-lowest));color:var(--md-sys-color-primary);display:flex;align-items:center;gap:var(--gap-2);padding:8px 10px;pointer-events:none;-webkit-user-select:none;user-select:none;opacity:1!important;transform:none!important;animation:none!important;box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--md-sys-color-primary) 10%,transparent),0 2px 8px color-mix(in srgb,var(--md-sys-color-primary) 10%,transparent)}:host ::ng-deep .fb-drop-placeholder__icon{width:28px;height:28px;border-radius:var(--r-sm);background:var(--md-sys-color-primary-container);color:var(--md-sys-color-primary);display:inline-flex;align-items:center;justify-content:center;flex:0 0 28px}:host ::ng-deep .fb-drop-placeholder__icon .msi{font-size:17px}:host ::ng-deep .fb-drop-placeholder__body{min-width:0;display:flex;flex-direction:column;gap:2px}:host ::ng-deep .fb-drop-placeholder__title{color:var(--md-sys-color-on-surface);font-size:12px;font-weight:600;line-height:1.2;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host ::ng-deep .fb-drop-placeholder__meta{color:var(--md-sys-color-primary);font-size:10px;font-weight:500;line-height:1.2;text-transform:uppercase;letter-spacing:0}:host ::ng-deep .fb-palette__list>.fb-drop-placeholder{display:none!important}:host ::ng-deep .fb-row__items>.fb-drop-placeholder,:host ::ng-deep .fb-group__body>.fb-drop-placeholder{flex:1 1 160px;min-width:min(180px,100%);max-width:100%;align-self:stretch;margin:0 2px}:host ::ng-deep [cdkDropListOrientation=vertical]>.fb-drop-placeholder{width:auto;min-height:50px;margin:6px 14px;align-self:stretch}:host ::ng-deep .fb-drop-placeholder--row{min-height:56px}:host ::ng-deep .cdk-drop-list-receiving{background:color-mix(in srgb,var(--md-sys-color-primary) 3%,transparent);transition:background .16s ease}:host ::ng-deep .fb-group__body.cdk-drop-list-receiving,:host ::ng-deep .fb-group__body.cdk-drop-list-dragging{background:color-mix(in srgb,var(--md-sys-color-primary) 7%,transparent)!important;outline:1px solid color-mix(in srgb,var(--md-sys-color-primary) 72%,transparent);outline-offset:-6px;border-radius:var(--r-sm)}:host ::ng-deep .fb-resize.cdk-drag-preview{display:none!important}:host ::ng-deep .fb-shell--dragging .fb-item:hover,:host ::ng-deep .fb-shell--dragging .fb-group:hover,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-item:hover,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-group:hover{border-color:transparent!important;background:transparent!important}:host ::ng-deep .fb-shell--dragging .fb-row:hover,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-row:hover{outline-color:transparent!important;background:transparent!important}:host ::ng-deep .fb-shell--dragging .fb-row:hover .fb-row__drag,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-row:hover .fb-row__drag{opacity:0!important}:host ::ng-deep .fb-shell--dragging .fb-row.is-drag-target,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-row.is-drag-target{outline-color:color-mix(in srgb,var(--md-sys-color-info) 44%,transparent)!important;background:color-mix(in srgb,var(--md-sys-color-info) 4%,transparent)!important}:host ::ng-deep .fb-shell--dragging .fb-row__drag,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-row__drag{opacity:0!important}:host ::ng-deep .fb-shell--dragging .fb-actions,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-actions{opacity:0!important;pointer-events:none!important;transform:translateY(-2px)!important}:host ::ng-deep .fb-shell--dragging .fb-resize,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-resize{opacity:0!important;pointer-events:none!important}:host ::ng-deep .fb-shell--dragging .fb-row-break-btn,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-row-break-btn{opacity:0!important;pointer-events:none!important}:host ::ng-deep .fb-shell--dragging .fb-palette-item:hover,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-palette-item:hover{background:transparent!important}:host ::ng-deep .fb-shell--dragging .fb-palette-item:hover .fb-palette-item__drag,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-palette-item:hover .fb-palette-item__drag{opacity:0!important}:host ::ng-deep .fb-shell--dragging .fb-group__body,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-group__body{min-height:72px!important;background:color-mix(in srgb,var(--md-sys-color-primary) 4%,transparent)!important;outline:1px dashed color-mix(in srgb,var(--md-sys-color-primary) 48%,transparent)!important;outline-offset:-6px;border-radius:var(--r-sm);transition:background .16s ease,outline .16s ease,min-height .16s ease}:host ::ng-deep .fb-shell--dragging .fb-group__empty,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-group__empty{opacity:1;border-color:color-mix(in srgb,var(--md-sys-color-primary) 55%,transparent);background:color-mix(in srgb,var(--md-sys-color-primary) 7%,transparent);color:var(--md-sys-color-primary)}:host ::ng-deep .fb-shell--dragging .fb-group__body.cdk-drop-list-receiving,:host ::ng-deep .fb-shell--dragging .fb-group__body.cdk-drop-list-dragging,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-group__body.cdk-drop-list-receiving,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-group__body.cdk-drop-list-dragging{background:color-mix(in srgb,var(--md-sys-color-primary) 9%,transparent)!important;outline:1.5px solid var(--md-sys-color-primary)!important}:host ::ng-deep .fb-shell--resizing .fb-row.is-resizing-row{outline-color:color-mix(in srgb,var(--md-sys-color-primary) 42%,transparent);background:color-mix(in srgb,var(--md-sys-color-primary) 3%,transparent)}:host ::ng-deep .fb-shell--resizing .fb-row.is-resizing-row .fb-row__items:before{content:\"\";position:absolute;inset:-3px 0;border-radius:var(--r-sm);background:repeating-linear-gradient(90deg,transparent 0,transparent calc(8.333% - 1px),color-mix(in srgb,var(--md-sys-color-primary) 18%,transparent) calc(8.333% - 1px),color-mix(in srgb,var(--md-sys-color-primary) 18%,transparent) 8.333%),color-mix(in srgb,var(--md-sys-color-primary) 3%,transparent);pointer-events:none;z-index:0}:host ::ng-deep .fb-shell--resizing .fb-row.is-resizing-row .fb-row__items>*{position:relative;z-index:1}:host ::ng-deep .fb-shell--resizing .fb-item.is-resizing{border-color:var(--md-sys-color-primary)!important;background:color-mix(in srgb,var(--md-sys-color-primary) 6%,var(--md-sys-color-surface-container-lowest));box-shadow:0 0 0 2px color-mix(in srgb,var(--md-sys-color-primary) 18%,transparent),inset 0 0 0 1px color-mix(in srgb,var(--md-sys-color-primary) 18%,transparent);cursor:col-resize;z-index:2}:host ::ng-deep .fb-shell--resizing .fb-item.is-resizing .fb-resize{opacity:1!important;pointer-events:auto!important;background:var(--md-sys-color-primary);box-shadow:0 0 0 4px color-mix(in srgb,var(--md-sys-color-primary) 16%,transparent)}:host ::ng-deep .cdk-drag-animating{transition:transform .22s cubic-bezier(.2,0,0,1)!important}:host ::ng-deep .cdk-drag-dragging{transition:none!important}:host ::ng-deep .fb-item:not(.cdk-drag-dragging){transition:flex .12s cubic-bezier(.2,0,0,1),max-width .12s cubic-bezier(.2,0,0,1),border-color .12s,background .12s}::ng-deep .cdk-drag-preview.fb-drag-preview{width:132px;height:44px;box-sizing:border-box;border-radius:14px;background:#fff;display:inline-flex;align-items:center;gap:10px;padding:8px 12px;box-shadow:0 14px 32px #0000003d,0 5px 14px #00000029;border:1.5px solid color-mix(in srgb,var(--sd-primary, #2a66f4) 46%,#d6d2da);cursor:grabbing;pointer-events:none;-webkit-user-select:none;user-select:none;opacity:1;overflow:hidden;z-index:10000}::ng-deep .cdk-drag-preview.fb-drag-preview .fb-drag-preview__glyph{width:28px;height:28px;border-radius:50%;background:var(--sd-primary, #2a66f4);flex-shrink:0;box-shadow:inset 0 0 0 5px #ffffff2e}::ng-deep .cdk-drag-preview.fb-drag-preview .fb-drag-preview__body{flex:1;min-width:0;display:grid;gap:7px}::ng-deep .cdk-drag-preview.fb-drag-preview .fb-drag-preview__line{height:7px;border-radius:999px;background:#d6d2dab8}::ng-deep .cdk-drag-preview.fb-drag-preview .fb-drag-preview__line.is-strong{width:74%;background:color-mix(in srgb,var(--sd-primary, #2a66f4) 26%,#d6d2da)}.fb-shell{width:100%;height:100%;display:flex;background:var(--md-sys-color-surface);overflow:hidden;min-height:0}.fb-panel-header{height:var(--h-header);display:flex;align-items:center;justify-content:space-between;padding:0 var(--gap-3);border-bottom:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-low);flex-shrink:0}.fb-panel-header__title{font-size:11px;font-weight:500;letter-spacing:.08em;text-transform:uppercase;color:var(--md-sys-color-on-surface-variant)}.fb-panel-header__actions{display:flex;align-items:center;gap:2px}.fb-palette{width:220px;flex-shrink:0;display:flex;flex-direction:column;background:var(--md-sys-color-surface-container-low);border-right:1px solid var(--md-sys-color-outline-variant)}.fb-palette__scroll{flex:1;overflow-y:auto;padding-bottom:var(--gap-3)}.fb-palette__search{margin:var(--gap-3);height:32px;display:flex;align-items:center;gap:var(--gap-2);padding:0 var(--gap-3);border-radius:var(--r-pill);background:var(--md-sys-color-surface-container-high);color:var(--md-sys-color-on-surface-variant);font-size:13px;flex-shrink:0}.fb-palette__search .msi{font-size:18px}.fb-palette__search input{flex:1;border:none;outline:none;background:transparent;color:var(--md-sys-color-on-surface);font-size:13px;font-family:inherit}.fb-palette__search input::placeholder{color:var(--md-sys-color-on-surface-variant)}.fb-palette__search .kbd{font-family:Roboto Mono,monospace;font-size:10px;padding:1px 6px;border-radius:var(--r-xs);background:var(--md-sys-color-surface-container-lowest);border:1px solid var(--md-sys-color-outline-variant);color:var(--md-sys-color-on-surface-variant)}.fb-palette__group-label{font-size:11px;font-weight:500;letter-spacing:.04em;color:var(--md-sys-color-on-surface-variant);padding:var(--gap-3) var(--gap-4) var(--gap-1);text-transform:uppercase}.fb-palette__list{display:flex;flex-direction:column;padding:0 var(--gap-2)}.fb-palette-item{height:var(--h-palette);display:flex;align-items:center;gap:var(--gap-3);padding:0 var(--gap-3);border-radius:var(--r-sm);cursor:grab;color:var(--md-sys-color-on-surface);position:relative;-webkit-user-select:none;user-select:none}.fb-palette-item:hover{background:color-mix(in srgb,var(--md-sys-color-primary) 8%,transparent)}.fb-palette-item .fb-palette-item__icon{width:24px;height:24px;border-radius:var(--r-xs);display:inline-flex;align-items:center;justify-content:center;color:var(--md-sys-color-primary);background:var(--md-sys-color-primary-container);flex-shrink:0}.fb-palette-item .fb-palette-item__icon .msi{font-size:16px}.fb-palette-item .fb-palette-item__name{font-size:13px;font-weight:400}.fb-palette-item .fb-palette-item__drag{margin-left:auto;color:var(--md-sys-color-outline);opacity:0;transition:opacity .12s ease}.fb-palette-item .fb-palette-item__drag .msi{font-size:16px}.fb-palette-item:hover .fb-palette-item__drag{opacity:1}.fb-canvas{flex:1 1 0;min-width:0;display:flex;flex-direction:column;background:var(--md-sys-color-surface);overflow:hidden}.fb-canvas__header{height:var(--h-header);display:flex;align-items:center;justify-content:space-between;padding:0 var(--gap-4);border-bottom:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface);gap:var(--gap-3);flex-shrink:0}.fb-canvas__title{display:flex;align-items:center;gap:var(--gap-2);font-size:11px;font-weight:500;letter-spacing:.08em;text-transform:uppercase;color:var(--md-sys-color-on-surface-variant)}.fb-canvas__title .breadcrumb-sep{color:var(--md-sys-color-outline)}.fb-canvas__actions{display:flex;align-items:center;gap:var(--gap-2)}.fb-divider{width:1px;height:18px;background:var(--md-sys-color-outline-variant)}.fb-canvas__body{flex:1 1 0;overflow-y:auto;overflow-x:hidden;padding:var(--gap-4);position:relative;background:var(--md-sys-color-surface);min-width:0}.fb-canvas__body.is-preview{background:var(--md-sys-color-surface-container-low);padding:32px}.fb-mode-toggle{display:inline-flex;height:32px;padding:2px;border:1px solid var(--md-sys-color-outline-variant);border-radius:var(--r-pill);background:var(--md-sys-color-surface-container-lowest);flex-shrink:0;gap:2px;box-shadow:var(--elev-1)}.fb-mode-toggle button{border:0;background:transparent;border-radius:var(--r-pill);padding:0 12px;font-size:12px;font-weight:500;display:inline-flex;align-items:center;gap:6px;color:var(--md-sys-color-on-surface-variant);cursor:pointer;transition:background .12s ease,color .12s ease,box-shadow .12s ease}.fb-mode-toggle button .msi{font-size:16px}.fb-mode-toggle button:hover{background:color-mix(in srgb,var(--md-sys-color-primary) 7%,transparent);color:var(--md-sys-color-on-surface)}.fb-mode-toggle button:focus-visible{outline:2px solid color-mix(in srgb,var(--md-sys-color-primary) 48%,transparent);outline-offset:1px}.fb-mode-toggle button.is-active{background:var(--md-sys-color-primary-container);color:var(--md-sys-color-primary);box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--md-sys-color-primary) 18%,transparent)}.fb-row{position:relative;display:flex;gap:var(--gap-2);padding:6px var(--gap-2);border-radius:var(--r-sm);margin-bottom:6px;min-width:0;transition:outline .12s ease,background .12s ease;outline:1px dashed transparent;outline-offset:-1px}.fb-row:hover{outline-color:var(--md-sys-color-outline-variant);background:color-mix(in srgb,var(--md-sys-color-primary) 3%,transparent)}.fb-row:hover .fb-row__drag{opacity:1}.fb-row.is-drag-target{outline-color:color-mix(in srgb,var(--md-sys-color-info) 44%,transparent);background:color-mix(in srgb,var(--md-sys-color-info) 4%,transparent)}.fb-row__items{position:relative;min-width:0}.fb-row__drag{position:absolute;left:-22px;top:50%;transform:translateY(-50%);width:18px;height:22px;border-radius:var(--r-xs);display:inline-flex;align-items:center;justify-content:center;color:var(--md-sys-color-on-surface-variant);background:var(--md-sys-color-surface-container-high);opacity:0;cursor:grab;z-index:2}.fb-row__drag .msi{font-size:14px}.fb-row-break-btn{position:absolute;right:-28px;top:50%;transform:translateY(-50%);width:22px;height:22px;border-radius:var(--r-pill);display:inline-flex;align-items:center;justify-content:center;color:var(--md-sys-color-on-surface-variant);background:var(--md-sys-color-surface-container-high);border:1px solid var(--md-sys-color-outline-variant);cursor:pointer;opacity:0;z-index:2;transition:opacity .12s ease,background .12s ease,color .12s ease}.fb-row-break-btn .msi{font-size:14px}.fb-row-break-btn:hover{background:var(--md-sys-color-primary-container);color:var(--md-sys-color-primary);border-color:transparent}.fb-row:hover .fb-row-break-btn{opacity:1}.fb-item{position:relative;border:1.5px dashed transparent;border-radius:var(--r-md);padding:var(--gap-2) var(--gap-3);min-height:56px;min-width:0;transition:border-color .12s ease,background .12s ease;background:transparent;cursor:grab;display:flex;flex-direction:column;gap:6px}.fb-item:hover{border-color:color-mix(in srgb,var(--md-sys-color-primary) 60%,transparent)}.fb-item.is-selected{border:1.5px solid var(--md-sys-color-primary);background:color-mix(in srgb,var(--md-sys-color-primary) 4%,transparent);box-shadow:0 0 0 2px color-mix(in srgb,var(--md-sys-color-primary) 18%,transparent),inset 3px 0 0 var(--md-sys-color-primary)}.fb-item.is-hidden{opacity:.45}.fb-item.is-readonly:after{content:\"\";position:absolute;inset:0;border-radius:var(--r-md);background:repeating-linear-gradient(135deg,transparent 0 6px,color-mix(in srgb,var(--md-sys-color-on-surface) 4%,transparent) 6px 7px);pointer-events:none}.fb-item.column-2{flex:0 0 calc(16.6666666667% - var(--gap-2));max-width:calc(16.6666666667% - var(--gap-2))}.fb-item.column-3{flex:0 0 calc(25% - var(--gap-2));max-width:calc(25% - var(--gap-2))}.fb-item.column-4{flex:0 0 calc(33.3333333333% - var(--gap-2));max-width:calc(33.3333333333% - var(--gap-2))}.fb-item.column-5{flex:0 0 calc(41.6666666667% - var(--gap-2));max-width:calc(41.6666666667% - var(--gap-2))}.fb-item.column-6{flex:0 0 calc(50% - var(--gap-2));max-width:calc(50% - var(--gap-2))}.fb-item.column-7{flex:0 0 calc(58.3333333333% - var(--gap-2));max-width:calc(58.3333333333% - var(--gap-2))}.fb-item.column-8{flex:0 0 calc(66.6666666667% - var(--gap-2));max-width:calc(66.6666666667% - var(--gap-2))}.fb-item.column-9{flex:0 0 calc(75% - var(--gap-2));max-width:calc(75% - var(--gap-2))}.fb-item.column-10{flex:0 0 calc(83.3333333333% - var(--gap-2));max-width:calc(83.3333333333% - var(--gap-2))}.fb-item.column-11{flex:0 0 calc(91.6666666667% - var(--gap-2));max-width:calc(91.6666666667% - var(--gap-2))}.fb-item.column-12{flex:1 1 100%;max-width:100%}.fb-actions{position:absolute;top:-14px;right:8px;display:flex;align-items:center;gap:2px;padding:2px;border-radius:var(--r-pill);background:var(--md-sys-color-surface-container-lowest);box-shadow:var(--elev-2);border:1px solid var(--md-sys-color-outline-variant);z-index:4;opacity:0;transform:translateY(-2px);pointer-events:none;transition:opacity .12s ease,transform .12s ease}.fb-actions .fb-icon-btn{width:24px;height:24px}.fb-actions .fb-icon-btn .msi{font-size:16px}.fb-item:hover .fb-actions,.fb-item.is-selected .fb-actions{opacity:1;transform:translateY(0);pointer-events:auto}table-control{display:block;width:100%;max-width:100%;min-width:0}.fb-group>.fb-actions{top:6px;right:8px}.fb-group:hover>.fb-actions,.fb-group.is-selected>.fb-actions{opacity:1;transform:translateY(0);pointer-events:auto}.fb-status-chip{display:inline-flex;align-items:center;gap:4px;padding:2px 8px;height:18px;border-radius:var(--r-pill);font-size:10px;font-weight:500;letter-spacing:.02em;position:absolute;top:-9px;left:12px;z-index:3;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-lowest);color:var(--md-sys-color-on-surface-variant)}.fb-status-chip .msi{font-size:12px}.fb-status-chip.readonly{color:var(--md-sys-color-warning);background:var(--md-sys-color-warning-container);border-color:transparent}.fb-status-chip.hidden{color:var(--md-sys-color-on-surface-variant)}.fb-status-chip.conditional{color:var(--md-sys-color-primary);background:var(--md-sys-color-primary-container);border-color:transparent}.fb-resize-chip{position:absolute;right:14px;bottom:-18px;z-index:5;display:inline-flex;align-items:center;height:20px;padding:0 8px;border-radius:var(--r-pill);background:var(--md-sys-color-primary);color:var(--md-sys-color-on-primary);box-shadow:var(--elev-2);font-size:11px;font-weight:700;letter-spacing:0;pointer-events:none}.fb-resize{position:absolute;top:10px;bottom:10px;right:-6px;width:8px;border-radius:999px;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 28%,transparent);background:var(--md-sys-color-surface-container-lowest);display:flex;align-items:center;justify-content:center;cursor:col-resize;opacity:0;transition:opacity .12s ease,background .12s ease,box-shadow .12s ease;z-index:3}.fb-resize:before{content:\"\";width:2px;height:20px;background:var(--md-sys-color-primary);border-radius:999px}.fb-resize:hover{background:var(--md-sys-color-primary-container);box-shadow:0 0 0 3px color-mix(in srgb,var(--md-sys-color-primary) 12%,transparent)}.fb-item:hover .fb-resize{opacity:1}.fb-item.is-break{min-height:28px;padding:8px var(--gap-3);justify-content:center}.fb-break-line{display:flex;align-items:center;gap:var(--gap-2);color:var(--md-sys-color-on-surface-variant);font-size:11px;font-weight:500;letter-spacing:.06em;text-transform:uppercase}.fb-break-line:before,.fb-break-line:after{content:\"\";flex:1;height:1px;background:var(--md-sys-color-outline-variant)}.fb-break-line .msi{display:none}.fb-break-line .fb-break-label{color:var(--md-sys-color-on-surface-variant)}.fb-drop-hint{height:36px;border-radius:var(--r-sm);border:1.5px dashed var(--md-sys-color-primary);background:color-mix(in srgb,var(--md-sys-color-primary) 6%,transparent);color:var(--md-sys-color-primary);display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:500;gap:6px;margin:4px 0}.fb-drop-hint .msi{font-size:16px}.fb-group{background:var(--md-sys-color-surface-container-lowest);border-radius:var(--r-lg);border:1.5px dashed transparent;transition:border-color .12s ease,background .12s ease;position:relative;display:flex;flex-direction:column;overflow:visible;cursor:default}.fb-group:hover{border-color:color-mix(in srgb,var(--md-sys-color-primary) 60%,transparent)}.fb-group.is-selected{border:1.5px solid var(--md-sys-color-primary);box-shadow:0 0 0 2px color-mix(in srgb,var(--md-sys-color-primary) 18%,transparent),inset 3px 0 0 var(--md-sys-color-primary)}.fb-group.is-hidden{opacity:.55}.fb-group__header{display:flex;align-items:center;gap:var(--gap-2);padding:8px 12px;border-top-left-radius:12px;border-top-right-radius:12px;cursor:grab}.fb-group__header:active{cursor:grabbing}.fb-group__header .fb-group__icon .msi{font-size:18px}.fb-group__header .fb-group__label{font-size:13px;font-weight:500}.fb-group__header .fb-group__count{margin-left:auto;font-size:10px;opacity:.75}.fb-group__conditional-chip{display:inline-flex;align-items:center;gap:4px;font-family:Roboto Mono,monospace;font-size:10px;padding:2px 8px;border-radius:var(--r-pill);background:color-mix(in srgb,currentColor 12%,transparent)}.fb-group__body{padding:12px;display:flex;flex-wrap:wrap;gap:var(--gap-2);min-height:60px;position:relative}.fb-group__empty{display:flex;align-items:center;justify-content:center;height:56px;border-radius:var(--r-sm);border:1.5px dashed var(--md-sys-color-outline-variant);color:var(--md-sys-color-on-surface-variant);font-size:12px;font-style:italic}.fb-empty{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--gap-3);color:var(--md-sys-color-on-surface-variant);text-align:center;padding:24px;pointer-events:none}.fb-empty__art{width:96px;height:96px;border-radius:var(--r-xl);background:var(--md-sys-color-primary-container);color:var(--md-sys-color-primary);display:inline-flex;align-items:center;justify-content:center}.fb-empty__art .msi{font-size:44px}.fb-empty__title{font-size:16px;font-weight:500;color:var(--md-sys-color-on-surface)}.fb-empty__hint{font-size:13px;max-width:320px;line-height:1.5}.fb-empty__kbd{display:inline-flex;align-items:center;gap:6px;font-family:Roboto Mono,monospace;font-size:11px;padding:4px 8px;border-radius:var(--r-xs);border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container);color:var(--md-sys-color-on-surface-variant)}.fb-empty__kbd .msi{font-size:12px}.fb-attrs{width:320px;flex-shrink:0;display:flex;flex-direction:column;background:var(--md-sys-color-surface-container-low);border-left:1px solid var(--md-sys-color-outline-variant)}.fb-attrs__selection{padding:var(--gap-3) var(--gap-4);display:flex;align-items:center;gap:var(--gap-2);border-bottom:1px solid var(--md-sys-color-outline-variant)}.fb-attrs__sel-icon{width:28px;height:28px;border-radius:var(--r-sm);background:var(--md-sys-color-primary-container);color:var(--md-sys-color-primary);display:inline-flex;align-items:center;justify-content:center;flex-shrink:0}.fb-attrs__sel-icon .msi{font-size:18px}.fb-attrs__sel-meta{display:flex;flex-direction:column;min-width:0}.fb-attrs__sel-name{font-size:13px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.fb-attrs__sel-key{font-family:Roboto Mono,monospace;font-size:11px;color:var(--md-sys-color-on-surface-variant)}.fb-attrs__body{flex:1 1 0;overflow-y:auto;overflow-x:hidden;min-width:0}.fb-attrs__placeholder{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--gap-2);padding:32px;color:var(--md-sys-color-on-surface-variant);text-align:center}.fb-attrs__placeholder .msi{font-size:32px;color:var(--md-sys-color-outline)}.fb-attrs__placeholder .title{font-size:13px;font-weight:500;color:var(--md-sys-color-on-surface)}.fb-attrs__placeholder .hint{font-size:12px;max-width:220px;line-height:1.5}.fb-preview-card{max-width:760px;margin:0 auto;background:var(--md-sys-color-surface-container-lowest);border-radius:var(--r-lg);padding:32px;box-shadow:var(--elev-1)}.fb-preview-card__chip{display:inline-flex;align-items:center;gap:6px;padding:2px 10px;height:22px;border-radius:var(--r-pill);background:var(--md-sys-color-primary-container);color:var(--md-sys-color-primary);font-size:10px;font-weight:500;letter-spacing:.08em;text-transform:uppercase;margin-bottom:var(--gap-3)}.fb-preview-card__title{font-size:20px;font-weight:500;margin:0 0 var(--gap-4);color:var(--md-sys-color-on-surface)}.fb-preview-card__footer{display:flex;justify-content:flex-end;gap:var(--gap-2);margin-top:var(--gap-4);padding-top:var(--gap-4);border-top:1px solid var(--md-sys-color-outline-variant)}.fb-icon-picker{display:grid;grid-template-columns:repeat(6,1fr);gap:4px;margin-top:4px}.fb-icon-picker .fb-icon-picker__btn{height:28px;border-radius:var(--r-xs);border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-lowest);color:var(--md-sys-color-on-surface-variant);display:inline-flex;align-items:center;justify-content:center;cursor:pointer}.fb-icon-picker .fb-icon-picker__btn .msi{font-size:16px}.fb-icon-picker .fb-icon-picker__btn.is-active{border-color:var(--md-sys-color-primary);background:var(--md-sys-color-primary-container);color:var(--md-sys-color-primary)}.fb-icon-picker .fb-icon-picker__btn:hover{background:color-mix(in srgb,var(--md-sys-color-primary) 8%,transparent)}.fb-color-swatches{display:flex;gap:var(--gap-2);margin-top:4px}.fb-color-swatches .fb-color-swatch{width:24px;height:24px;border-radius:50%;cursor:pointer;border:2px solid transparent}.fb-color-swatches .fb-color-swatch.is-active{border-color:var(--md-sys-color-on-surface)}.fb-color-swatches .fb-color-swatch.primary{background:var(--md-sys-color-primary)}.fb-color-swatches .fb-color-swatch.secondary{background:var(--md-sys-color-secondary)}.fb-color-swatches .fb-color-swatch.success{background:var(--md-sys-color-success)}.fb-color-swatches .fb-color-swatch.warning{background:var(--md-sys-color-warning)}.fb-color-swatches .fb-color-swatch.error{background:var(--md-sys-color-error)}.fb-children-list{display:flex;flex-direction:column;gap:4px}.fb-child-row{display:flex;align-items:center;gap:var(--gap-2);height:30px;padding:0 8px;border-radius:var(--r-xs);background:var(--md-sys-color-surface-container-lowest);border:1px solid var(--md-sys-color-outline-variant);font-size:12px}.fb-child-row .fb-child-row__icon{width:22px;height:22px;border-radius:var(--r-xs);background:var(--md-sys-color-primary-container);color:var(--md-sys-color-primary);display:inline-flex;align-items:center;justify-content:center;flex-shrink:0}.fb-child-row .fb-child-row__icon .msi{font-size:14px}.fb-child-row .fb-child-row__label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-child-row .fb-child-row__key{font-family:Roboto Mono,monospace;font-size:10px;color:var(--md-sys-color-on-surface-variant)}.fb-child-row:hover{background:color-mix(in srgb,var(--md-sys-color-primary) 4%,transparent)}.fb-add-link{display:inline-flex;align-items:center;gap:4px;font-size:12px;color:var(--md-sys-color-primary);cursor:pointer;padding:4px 0;text-decoration:none}.fb-add-link .msi{font-size:14px}.fb-add-link:hover{text-decoration:underline}.fb-group__body--static{display:flex;flex-wrap:wrap;gap:var(--gap-2, 8px);cursor:pointer;min-height:48px}.fb-group__body--static .fb-item--static{pointer-events:none;position:relative}.fb-group__body--static .fb-item--static .fb-resize,.fb-group__body--static .fb-item--static .fb-actions,.fb-group__body--static .fb-item--static .fb-status-chip{display:none!important}.fb-canvas__header--group .fb-groupedit__crumb{display:flex;align-items:center;gap:6px;font-weight:600}.fb-canvas__header--group .fb-groupedit__icon .msi{font-size:18px;color:var(--md-sys-color-primary)}.fb-canvas__header--group .fb-groupedit__hint{font-weight:400;font-size:12px;color:var(--md-sys-color-on-surface-variant, #6b6b6b);padding:1px 8px;border-radius:999px;background:color-mix(in srgb,var(--md-sys-color-primary) 8%,transparent)}\n"] }]
|
|
6785
|
+
], template: "<!-- cdkDropListGroup: auto-connect t\u1EA5t c\u1EA3 cdkDropList trong palette, canvas rows, group bodies.\n [class.fb-shell--dragging]: JS-driven flag \u0111\u1EC3 \u1EA9n hover/actions/resize to\u00E0n di\u1EC7n\n trong l\u00FAc drag (CSS :has() kh\u00F4ng b\u1EAFt 100% tr\u01B0\u1EDDng h\u1EE3p v\u1EDBi CDK preview clone). -->\n<div\n class=\"fb-shell\"\n cdkDropListGroup\n [class.fb-shell--dragging]=\"isAnyDragging()\"\n [class.fb-shell--palette-dragging]=\"dragSource() === 'palette'\"\n [class.fb-shell--resizing]=\"isResizing()\">\n <ng-template #dragPreviewTemplate>\n <span class=\"fb-drag-preview__glyph\" aria-hidden=\"true\"></span>\n <span class=\"fb-drag-preview__body\" aria-hidden=\"true\">\n <span class=\"fb-drag-preview__line is-strong\"></span>\n <span class=\"fb-drag-preview__line\"></span>\n </span>\n </ng-template>\n <ng-template #dropPlaceholderTemplate let-item>\n <span class=\"fb-drop-placeholder__icon\" aria-hidden=\"true\">\n <span class=\"msi\">{{ placeholderSymbolFor(item) }}</span>\n </span>\n <span class=\"fb-drop-placeholder__body\" aria-hidden=\"true\">\n <span class=\"fb-drop-placeholder__title\">{{ placeholderTitleFor(item) }}</span>\n <span class=\"fb-drop-placeholder__meta\">{{ placeholderMetaFor(item) }}</span>\n </span>\n </ng-template>\n\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 LEFT PALETTE \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <aside class=\"fb-palette\">\n <div class=\"fb-panel-header\">\n <div class=\"fb-panel-header__title\">{{ 'core.component.form-builder.components' | translate }}</div>\n </div>\n\n <!-- Search pill -->\n <div class=\"fb-palette__search\">\n <span class=\"msi\" aria-hidden=\"true\">search</span>\n <input\n type=\"text\"\n [placeholder]=\"'core.component.form-builder.search-components' | translate\"\n [value]=\"paletteSearch()\"\n (input)=\"paletteSearch.set($any($event.target).value)\" />\n <span class=\"kbd\">\u2318K</span>\n </div>\n\n <div class=\"fb-palette__scroll\">\n @for (group of paletteGroups(); track group.key) {\n <div class=\"fb-palette__group-label\">{{ group.label }}</div>\n <div class=\"fb-palette__list\" cdkDropList [cdkDropListData]=\"group.items\" [cdkDropListEnterPredicate]=\"noReturnPredicate\">\n @for (formBuilderComponent of group.items; track formBuilderComponent.type) {\n <div\n cdkDrag\n [cdkDragData]=\"formBuilderComponent\"\n class=\"fb-palette-item\"\n [matTooltip]=\"formBuilderComponent.name\"\n matTooltipPosition=\"right\"\n (click)=\"addComponent(formBuilderComponent)\"\n (cdkDragStarted)=\"onPaletteDragStarted(formBuilderComponent)\"\n (cdkDragMoved)=\"onAnyDragMoved($event)\"\n (cdkDragEnded)=\"onAnyDragEnded()\"\n aria-hidden=\"true\">\n <span class=\"fb-palette-item__icon\"\n ><span class=\"msi\">{{ formBuilderComponent.symbol }}</span></span\n >\n <span class=\"fb-palette-item__name\">{{ formBuilderComponent.name }}</span>\n <span class=\"fb-palette-item__drag\"><span class=\"msi\">drag_indicator</span></span>\n <div *cdkDragPreview class=\"fb-drag-preview\" aria-hidden=\"true\">\n <ng-container *ngTemplateOutlet=\"dragPreviewTemplate\"></ng-container>\n </div>\n <div\n *cdkDragPlaceholder\n class=\"fb-drop-placeholder\"\n [style.--fb-placeholder-columns]=\"placeholderColumnsFor(formBuilderComponent)\"\n aria-hidden=\"true\">\n <ng-container *ngTemplateOutlet=\"dropPlaceholderTemplate; context: { $implicit: formBuilderComponent }\"></ng-container>\n </div>\n </div>\n }\n </div>\n }\n </div>\n </aside>\n\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 CENTER CANVAS \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <section class=\"fb-canvas\">\n @let _editingGroup = editingGroup();\n @if (_editingGroup) {\n <!-- \u2500\u2500 Drill-in header: \u0111ang thi\u1EBFt k\u1EBF children c\u1EE7a 1 group \u2500\u2500 -->\n <header class=\"fb-canvas__header fb-canvas__header--group\">\n <div class=\"fb-canvas__title fb-groupedit__crumb\">\n <button\n type=\"button\"\n class=\"fb-icon-btn\"\n [matTooltip]=\"'core.component.form-builder.back-to-design' | translate\"\n (click)=\"cancelGroupEdit()\">\n <span class=\"msi\">chevron_left</span>\n </button>\n <span class=\"fb-groupedit__icon\"\n ><span class=\"msi\">{{ _editingGroup.properties?.icon || 'category' }}</span></span\n >\n <span>{{ _editingGroup.label || ('core.component.form-builder.edit-group' | translate) }}</span>\n <span class=\"fb-groupedit__hint\">{{ 'core.component.form-builder.edit-group' | translate }}</span>\n </div>\n <div class=\"fb-canvas__actions\">\n <sd-button type=\"text\" [title]=\"'core.component.form-builder.cancel' | translate\" (click)=\"cancelGroupEdit()\"></sd-button>\n <sd-button\n type=\"fill\"\n color=\"primary\"\n prefixIcon=\"check\"\n [title]=\"'core.component.form-builder.confirm' | translate\"\n (click)=\"confirmGroupEdit()\"></sd-button>\n </div>\n </header>\n } @else {\n <header class=\"fb-canvas__header\">\n <div class=\"fb-canvas__title\">\n <span>{{ 'core.component.form-builder.layout' | translate }}</span>\n </div>\n <div class=\"fb-canvas__actions\">\n <!-- Toolbar cluster: view JSON / variables / validation -->\n <button type=\"button\" class=\"fb-icon-btn\" [matTooltip]=\"'core.component.form-builder.view-json' | translate\" (click)=\"viewJSON()\">\n <span class=\"msi\">data_object</span>\n </button>\n <button\n type=\"button\"\n class=\"fb-icon-btn\"\n [matTooltip]=\"'core.component.form-builder.config-variable' | translate\"\n (click)=\"configureVariables()\">\n <span class=\"msi\">data_array</span>\n </button>\n <button\n type=\"button\"\n class=\"fb-icon-btn\"\n [matTooltip]=\"'core.component.form-builder.config-validation-warning-error' | translate\"\n (click)=\"openConfigureValidation()\">\n <span class=\"msi\">rule</span>\n </button>\n\n <span class=\"fb-divider\"></span>\n\n <!-- M3 segmented Design/Preview toggle -->\n <div class=\"fb-mode-toggle\" role=\"tablist\">\n <button type=\"button\" role=\"tab\" [class.is-active]=\"!isPreview()\" [attr.aria-selected]=\"!isPreview()\" (click)=\"setMode(false)\">\n <span class=\"msi\">edit</span>\n <span>{{ 'core.component.form-builder.design' | translate }}</span>\n </button>\n <button type=\"button\" role=\"tab\" [class.is-active]=\"isPreview()\" [attr.aria-selected]=\"isPreview()\" (click)=\"setMode(true)\">\n <span class=\"msi\">visibility</span>\n <span>{{ 'core.component.form-builder.preview' | translate }}</span>\n </button>\n </div>\n </div>\n </header>\n }\n\n <div class=\"fb-canvas__body\" [class.is-preview]=\"isPreview()\" id=\"frmComponent\">\n @if (isPreview()) {\n <!-- \u2500\u2500 Preview mode \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"fb-preview-card\">\n <div class=\"fb-preview-card__chip\">\n <span class=\"msi\">visibility</span>\n {{ 'core.component.form-builder.preview-runtime' | translate }}\n </div>\n <sd-form-render [configuration]=\"{ components: this.components, validations: this.validations }\"></sd-form-render>\n <div class=\"fb-preview-card__footer\">\n <sd-button type=\"text\" [title]=\"'core.component.form-builder.cancel' | translate\" (click)=\"setMode(false)\"></sd-button>\n <sd-button\n type=\"fill\"\n color=\"primary\"\n [title]=\"'core.component.form-builder.submit' | translate\"\n (click)=\"onValidate()\"></sd-button>\n </div>\n </div>\n } @else {\n <!-- \u2500\u2500 Design mode \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (!dragDropRows.length) {\n <div class=\"fb-empty\">\n <div class=\"fb-empty__art\"><span class=\"msi\">dashboard_customize</span></div>\n <div class=\"fb-empty__title\">{{ 'core.component.form-builder.empty-title' | translate }}</div>\n <div class=\"fb-empty__hint\">\n {{ 'core.component.form-builder.empty-hint' | translate }}\n <span class=\"fb-empty__kbd\"><span class=\"msi\">keyboard</span>\u2318 K</span>\n </div>\n </div>\n }\n\n <div\n cdkDropList\n cdkDropListOrientation=\"vertical\"\n [cdkDropListData]=\"dragDropRows\"\n (cdkDropListDropped)=\"drop($event)\"\n (click)=\"clickFormContentEmpty()\"\n aria-hidden=\"true\">\n @for (row of dragDropRows; track row.id) {\n @if (shouldShowRowInsertionPlaceholder(row, 'before')) {\n <div\n class=\"fb-row-insert-placeholder\"\n [style.--fb-placeholder-columns]=\"12\"\n aria-hidden=\"true\">\n <ng-container *ngTemplateOutlet=\"dropPlaceholderTemplate; context: { $implicit: draggedPaletteItem() || row }\"></ng-container>\n </div>\n }\n <div\n cdkDrag\n [cdkDragData]=\"row\"\n class=\"fb-row\"\n (mouseover)=\"onMouseover($event, row)\"\n (focus)=\"onFocus($event)\"\n (cdkDragStarted)=\"onRowDragStarted()\"\n (cdkDragMoved)=\"onAnyDragMoved($event)\"\n (cdkDragEnded)=\"onAnyDragEnded()\"\n [class.is-drag-target]=\"targetItem === row\"\n [class.is-row-full]=\"isRowFull(row)\"\n [class.is-row-inline-locked]=\"isRowInlineDropLocked(row)\"\n [class.is-resizing-row]=\"resizeState()?.rowId === row.id\"\n [id]=\"row.id\"\n aria-hidden=\"true\">\n <div class=\"fb-row__drag\" [matTooltip]=\"'core.component.form-builder.drag-vertically' | translate\" cdkDragHandle>\n <span class=\"msi\">drag_indicator</span>\n </div>\n <div *cdkDragPreview class=\"fb-drag-preview\" aria-hidden=\"true\">\n <ng-container *ngTemplateOutlet=\"dragPreviewTemplate\"></ng-container>\n </div>\n <div\n *cdkDragPlaceholder\n class=\"fb-drop-placeholder fb-drop-placeholder--row\"\n [style.--fb-placeholder-columns]=\"12\"\n aria-hidden=\"true\">\n <ng-container *ngTemplateOutlet=\"dropPlaceholderTemplate; context: { $implicit: row }\"></ng-container>\n </div>\n <!-- Quick-add break button \u2014 show on row hover, insert break after this row -->\n <button\n type=\"button\"\n class=\"fb-row-break-btn\"\n [matTooltip]=\"'core.component.form-builder.break' | translate\"\n matTooltipPosition=\"right\"\n (click)=\"insertBreakAfter(row); $event.stopPropagation()\">\n <span class=\"msi\">horizontal_rule</span>\n </button>\n\n <div\n class=\"fb-row__items\"\n style=\"display: flex; flex: 1; gap: var(--gap-2)\"\n cdkDropList\n cdkDropListOrientation=\"horizontal\"\n [cdkDropListData]=\"row.items\"\n [cdkDropListEnterPredicate]=\"canEnterRowDropList\"\n (cdkDropListEntered)=\"onRowItemsDropEntered(row)\"\n (cdkDropListExited)=\"onRowItemsDropExited(row)\"\n (cdkDropListDropped)=\"drop($event)\">\n @for (item of row.items; track item.id; let i = $index) {\n <!-- Group component renders as outlined card with colored header bar -->\n @if (item.type === 'group') {\n @let g = $any(item);\n @let cv = groupColorVars(g.properties?.color);\n <div\n cdkDrag\n [cdkDragData]=\"item\"\n [id]=\"item.id\"\n class=\"fb-group column-{{ item.layout?.columns || 12 }}\"\n [class.fb-item]=\"false\"\n [class.is-selected]=\"item.id === selectedComponent()?.id\"\n [class.is-hidden]=\"item.properties?.hidden\"\n [style.flex]=\"'0 0 calc(' + +(item.layout?.columns || 12) + '/12 * 100% - var(--gap-2))'\"\n [style.maxWidth]=\"'calc(' + +(item.layout?.columns || 12) + '/12 * 100% - var(--gap-2))'\"\n (click)=\"selectComponent(item); $event.stopPropagation()\"\n (cdkDragStarted)=\"dragStartComponentItem($event)\"\n (cdkDragMoved)=\"onAnyDragMoved($event)\"\n (cdkDragEnded)=\"dragEndComponentItem($event); onAnyDragEnded()\"\n aria-hidden=\"true\">\n <div *cdkDragPreview class=\"fb-drag-preview\" aria-hidden=\"true\">\n <ng-container *ngTemplateOutlet=\"dragPreviewTemplate\"></ng-container>\n </div>\n <div\n *cdkDragPlaceholder\n class=\"fb-drop-placeholder\"\n [style.--fb-placeholder-columns]=\"placeholderColumnsFor(item)\"\n aria-hidden=\"true\">\n <ng-container *ngTemplateOutlet=\"dropPlaceholderTemplate; context: { $implicit: item }\"></ng-container>\n </div>\n <!-- cdkDragHandle: ch\u1EC9 cho ph\u00E9p k\u00E9o group qua header \u2014 body free cho click v\u00E0o child. -->\n <div class=\"fb-group__header\" cdkDragHandle [style.background]=\"cv.bg\" [style.color]=\"cv.fg\">\n <span class=\"fb-group__icon\"\n ><span class=\"msi\">{{ g.properties?.icon || 'category' }}</span></span\n >\n <span class=\"fb-group__label\">{{ item.label }}</span>\n @if (hasConditional(item)) {\n <span class=\"fb-group__conditional-chip\">\n <span class=\"msi\" style=\"font-size: 12px\">bolt</span>\n {{ 'core.component.form-builder.conditional' | translate }}\n </span>\n }\n <span class=\"fb-group__count\">{{ g.components?.length || 0 }} fields</span>\n </div>\n <!-- Floating action toolbar -->\n <div class=\"fb-actions\">\n <!-- why: Detail = m\u1EDF canvas thi\u1EBFt k\u1EBF ri\u00EAng cho group (thay cho drag/drop trong group). -->\n <button\n type=\"button\"\n class=\"fb-icon-btn\"\n [matTooltip]=\"'core.component.form-builder.edit-group' | translate\"\n (click)=\"enterGroupEdit(g); $event.stopPropagation()\">\n <span class=\"msi\">open_in_full</span>\n </button>\n <button\n type=\"button\"\n class=\"fb-icon-btn\"\n [matTooltip]=\"'core.component.form-builder.duplicate' | translate\"\n (click)=\"onDuplicate(item); $event.stopPropagation()\">\n <span class=\"msi\">content_copy</span>\n </button>\n <button\n type=\"button\"\n class=\"fb-icon-btn\"\n [class.warning]=\"item.properties?.hidden\"\n [class.on]=\"item.properties?.hidden\"\n [matTooltip]=\"\n item.properties?.hidden\n ? ('core.component.form-builder.show' | translate)\n : ('core.component.form-builder.hide' | translate)\n \"\n (click)=\"onChangeHidden(item); $event.stopPropagation()\">\n <span class=\"msi\">{{ item.properties?.hidden ? 'visibility_off' : 'visibility' }}</span>\n </button>\n <button\n type=\"button\"\n class=\"fb-icon-btn danger\"\n [matTooltip]=\"'core.component.form-builder.delete' | translate\"\n (click)=\"removeComponent(item.id); $event.stopPropagation()\">\n <span class=\"msi\">delete</span>\n </button>\n </div>\n\n <!-- Static preview: children render read-only (kh\u00F4ng drag/resize/select).\n S\u1EEDa n\u1ED9i dung group qua n\u00FAt Detail (enterGroupEdit). -->\n <div class=\"fb-group__body fb-group__body--static\" (click)=\"enterGroupEdit(g); $event.stopPropagation()\">\n @if (!g.components?.length) {\n <div class=\"fb-group__empty\">\n <span class=\"msi\" style=\"font-size: 16px; margin-right: 6px\">open_in_full</span>\n {{ 'core.component.form-builder.group-empty-hint' | translate }}\n </div>\n }\n @for (child of g.components; track child.id) {\n <div\n class=\"fb-item is-child fb-item--static column-{{ child.layout?.columns || 12 }}\"\n [class.is-hidden]=\"child.properties?.hidden\">\n <ng-container *ngTemplateOutlet=\"controlTemplate; context: { $implicit: child }\"> </ng-container>\n </div>\n }\n </div>\n </div>\n } @else if (item.type === 'break') {\n <!-- Break: row separator c\u1ED1 \u0111\u1ECBnh. Render 12-col line, kh\u00F4ng c\u00F3 resize/edit attribute. -->\n <div\n cdkDrag\n [cdkDragData]=\"item\"\n [id]=\"item.id\"\n class=\"fb-item is-break column-12\"\n [class.is-selected]=\"item.id === selectedComponent()?.id\"\n [class.is-hidden]=\"item.properties?.hidden\"\n (click)=\"selectComponent(item); $event.stopPropagation()\"\n (cdkDragStarted)=\"dragStartComponentItem($event)\"\n (cdkDragMoved)=\"onAnyDragMoved($event)\"\n (cdkDragEnded)=\"dragEndComponentItem($event); onAnyDragEnded()\"\n aria-hidden=\"true\">\n <div *cdkDragPreview class=\"fb-drag-preview\" aria-hidden=\"true\">\n <ng-container *ngTemplateOutlet=\"dragPreviewTemplate\"></ng-container>\n </div>\n <div\n *cdkDragPlaceholder\n class=\"fb-drop-placeholder\"\n [style.--fb-placeholder-columns]=\"12\"\n aria-hidden=\"true\">\n <ng-container *ngTemplateOutlet=\"dropPlaceholderTemplate; context: { $implicit: item }\"></ng-container>\n </div>\n <div class=\"fb-break-line\">\n <span class=\"msi\">horizontal_rule</span>\n <span class=\"fb-break-label\">{{ 'core.component.form-builder.break' | translate }}</span>\n </div>\n <div class=\"fb-actions\">\n <button\n type=\"button\"\n class=\"fb-icon-btn\"\n [class.warning]=\"item.properties?.hidden\"\n [class.on]=\"item.properties?.hidden\"\n [matTooltip]=\"\n item.properties?.hidden\n ? ('core.component.form-builder.show' | translate)\n : ('core.component.form-builder.hide' | translate)\n \"\n (click)=\"onChangeHidden(item); $event.stopPropagation()\">\n <span class=\"msi\">{{ item.properties?.hidden ? 'visibility_off' : 'visibility' }}</span>\n </button>\n <button\n type=\"button\"\n class=\"fb-icon-btn danger\"\n [matTooltip]=\"'core.component.form-builder.delete' | translate\"\n (click)=\"removeComponent(item.id); $event.stopPropagation()\">\n <span class=\"msi\">delete</span>\n </button>\n </div>\n </div>\n } @else {\n <!-- Regular form component -->\n <div\n cdkDrag\n [cdkDragData]=\"item\"\n [id]=\"item.id\"\n class=\"fb-item column-{{ item.layout?.columns || 12 }}\"\n [class.is-selected]=\"item.id === selectedComponent()?.id\"\n [class.is-hidden]=\"item.properties?.hidden\"\n [class.is-readonly]=\"item.properties?.viewed\"\n [class.is-resizing]=\"resizeState()?.itemId === item.id\"\n (click)=\"selectComponent(item); $event.stopPropagation()\"\n (cdkDragStarted)=\"dragStartComponentItem($event)\"\n (cdkDragMoved)=\"onAnyDragMoved($event)\"\n (cdkDragEnded)=\"dragEndComponentItem($event); onAnyDragEnded()\"\n aria-hidden=\"true\">\n <div *cdkDragPreview class=\"fb-drag-preview\" aria-hidden=\"true\">\n <ng-container *ngTemplateOutlet=\"dragPreviewTemplate\"></ng-container>\n </div>\n <div\n *cdkDragPlaceholder\n class=\"fb-drop-placeholder\"\n [style.--fb-placeholder-columns]=\"placeholderColumnsFor(item)\"\n aria-hidden=\"true\">\n <ng-container *ngTemplateOutlet=\"dropPlaceholderTemplate; context: { $implicit: item }\"></ng-container>\n </div>\n\n <!-- Status chip (mutually exclusive priority: hidden > readonly > conditional) -->\n @if (item.properties?.hidden) {\n <span class=\"fb-status-chip hidden\">\n <span class=\"msi\">visibility_off</span>\n {{ 'core.component.form-builder.hidden' | translate }}\n </span>\n } @else if (item.properties?.viewed) {\n <span class=\"fb-status-chip readonly\">\n <span class=\"msi\">edit_off</span>\n {{ 'core.component.form-builder.read-only' | translate }}\n </span>\n } @else if (hasConditional(item)) {\n <span class=\"fb-status-chip conditional\">\n <span class=\"msi\">bolt</span>\n {{ 'core.component.form-builder.conditional' | translate }}\n </span>\n }\n\n <!-- Floating action toolbar pill -->\n <div class=\"fb-actions\">\n <button\n type=\"button\"\n class=\"fb-icon-btn\"\n [class.warning]=\"item.properties?.viewed\"\n [class.on]=\"item.properties?.viewed\"\n [matTooltip]=\"\n item.properties?.viewed\n ? ('core.component.form-builder.unset-readonly' | translate)\n : ('core.component.form-builder.set-readonly' | translate)\n \"\n (click)=\"onChangeViewed(item); $event.stopPropagation()\">\n <span class=\"msi\">edit_off</span>\n </button>\n <button\n type=\"button\"\n class=\"fb-icon-btn\"\n [matTooltip]=\"'core.component.form-builder.duplicate' | translate\"\n (click)=\"onDuplicate(item); $event.stopPropagation()\">\n <span class=\"msi\">content_copy</span>\n </button>\n <button\n type=\"button\"\n class=\"fb-icon-btn\"\n [class.warning]=\"item.properties?.hidden\"\n [class.on]=\"item.properties?.hidden\"\n [matTooltip]=\"\n item.properties?.hidden\n ? ('core.component.form-builder.show' | translate)\n : ('core.component.form-builder.hide' | translate)\n \"\n (click)=\"onChangeHidden(item); $event.stopPropagation()\">\n <span class=\"msi\">{{ item.properties?.hidden ? 'visibility_off' : 'visibility' }}</span>\n </button>\n <button\n type=\"button\"\n class=\"fb-icon-btn danger\"\n [matTooltip]=\"'core.component.form-builder.delete' | translate\"\n (click)=\"removeComponent(item.id); $event.stopPropagation()\">\n <span class=\"msi\">delete</span>\n </button>\n </div>\n\n <ng-container *ngTemplateOutlet=\"controlTemplate; context: { $implicit: item }\"> </ng-container>\n\n @if (resizeState()?.itemId === item.id) {\n <span class=\"fb-resize-chip\">{{ resizeState()?.columns }}/12</span>\n }\n\n <!-- Resize handle (pill) \u2014 cdkDragLockAxis=\"x\" \u0111\u1EC3 ch\u1EC9 k\u00E9o ngang.\n Wrapping cdkDropList c\u00F3 enterPredicate=false, kh\u00F4ng nh\u1EADn drop. -->\n <div cdkDropList cdkDropListOrientation=\"horizontal\" [cdkDropListEnterPredicate]=\"noReturnPredicate\">\n <div\n cdkDrag\n cdkDragLockAxis=\"x\"\n class=\"fb-resize\"\n (cdkDragStarted)=\"startResizeControl(item, row)\"\n (cdkDragMoved)=\"changeSizeControl($event, item, row.items, i)\"\n (cdkDragEnded)=\"endResizeControl($event)\"\n [matTooltip]=\"'core.component.form-builder.flex' | translate\"\n aria-label=\"resize\"></div>\n </div>\n </div>\n }\n }\n </div>\n </div>\n @if (shouldShowRowInsertionPlaceholder(row, 'after')) {\n <div\n class=\"fb-row-insert-placeholder\"\n [style.--fb-placeholder-columns]=\"12\"\n aria-hidden=\"true\">\n <ng-container *ngTemplateOutlet=\"dropPlaceholderTemplate; context: { $implicit: draggedPaletteItem() || row }\"></ng-container>\n </div>\n }\n }\n </div>\n }\n </div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 RIGHT ATTRIBUTES \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <aside class=\"fb-attrs\">\n <div class=\"fb-panel-header\">\n <div class=\"fb-panel-header__title\">{{ 'core.component.form-builder.attribute' | translate }}</div>\n </div>\n\n @let _selectedComponent = selectedComponent();\n @if (_selectedComponent) {\n <!-- Selection header: type icon + name + key/type -->\n <div class=\"fb-attrs__selection\">\n <span class=\"fb-attrs__sel-icon\"\n ><span class=\"msi\">{{ symbolFor(_selectedComponent) }}</span></span\n >\n <div class=\"fb-attrs__sel-meta\">\n <div class=\"fb-attrs__sel-name\">{{ $any(_selectedComponent).label || ('core.component.form-builder.untitled' | translate) }}</div>\n <div class=\"fb-attrs__sel-key\">\n @if ($any(_selectedComponent).key) {\n {{ $any(_selectedComponent).key }} \u00B7 {{ typeLabelFor(_selectedComponent) }}\n } @else {\n {{ typeLabelFor(_selectedComponent) }}\n }\n </div>\n </div>\n </div>\n\n <div class=\"fb-attrs__body\" aria-hidden=\"true\">\n @if (_selectedComponent.type === 'textfield') {\n <textfield-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"_selectedComponent\"></textfield-attribute>\n } @else if (_selectedComponent.type === 'textarea') {\n <textarea-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"_selectedComponent\"></textarea-attribute>\n } @else if (_selectedComponent.type === 'chip-string') {\n <chip-string-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"_selectedComponent\"></chip-string-attribute>\n } @else if (_selectedComponent.type === 'chip-calendar') {\n <chip-calendar-attribute\n [components]=\"components\"\n [variables]=\"variables\"\n [component]=\"_selectedComponent\"></chip-calendar-attribute>\n } @else if (_selectedComponent.type === 'number') {\n <number-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"_selectedComponent\"></number-attribute>\n } @else if (_selectedComponent.type === 'datetime') {\n <datetime-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"_selectedComponent\"></datetime-attribute>\n } @else if (_selectedComponent.type === 'select') {\n <select-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"_selectedComponent\"></select-attribute>\n } @else if (_selectedComponent.type === 'radio') {\n <radio-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"_selectedComponent\"></radio-attribute>\n } @else if (_selectedComponent.type === 'checkbox') {\n <checkbox-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"_selectedComponent\"></checkbox-attribute>\n } @else if (_selectedComponent.type === 'html') {\n <html-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"_selectedComponent\"></html-attribute>\n } @else if (_selectedComponent.type === 'upload') {\n <upload-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"_selectedComponent\"></upload-attribute>\n } @else if (_selectedComponent.type === 'table') {\n <table-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"_selectedComponent\"></table-attribute>\n } @else if (_selectedComponent.type === 'group') {\n <group-attribute [group]=\"$any(_selectedComponent)\"></group-attribute>\n }\n </div>\n } @else {\n <div class=\"fb-attrs__placeholder\">\n <span class=\"msi\">ads_click</span>\n <div class=\"title\">{{ 'core.component.form-builder.no-selection-title' | translate }}</div>\n <div class=\"hint\">{{ 'core.component.form-builder.no-selection-hint' | translate }}</div>\n </div>\n }\n </aside>\n</div>\n\n<!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 Reusable control template \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n<ng-template #controlTemplate let-item>\n @if (item.type === 'textfield') {\n <textfield-control [component]=\"item\"></textfield-control>\n } @else if (item.type === 'textarea') {\n <textarea-control [component]=\"item\"></textarea-control>\n } @else if (item.type === 'chip-string') {\n <chip-string-control [component]=\"item\"></chip-string-control>\n } @else if (item.type === 'chip-calendar') {\n <chip-calendar-control [component]=\"item\"></chip-calendar-control>\n } @else if (item.type === 'number') {\n <number-control [component]=\"item\"></number-control>\n } @else if (item.type === 'datetime') {\n <datetime-control [component]=\"item\"></datetime-control>\n } @else if (item.type === 'select') {\n <select-control [component]=\"item\"></select-control>\n } @else if (item.type === 'radio') {\n <radio-control [component]=\"item\"></radio-control>\n } @else if (item.type === 'checkbox') {\n <checkbox-control [component]=\"item\"></checkbox-control>\n } @else if (item.type === 'html') {\n <html-control [component]=\"item\"></html-control>\n } @else if (item.type === 'upload') {\n <upload-control [component]=\"item\"></upload-control>\n } @else if (item.type === 'table') {\n <table-control [component]=\"item\"></table-control>\n }\n</ng-template>\n\n<!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 JSON / variables modals \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n<sd-modal title=\"JSON\" view=\"dialog\" width=\"80%\" modalClass=\"sd-form-builder-dialog\" #popupViewJSON>\n <sd-textarea [(model)]=\"jsonString\"></sd-textarea>\n <ng-container sdFooterRight>\n <sd-button\n type=\"fill\"\n [title]=\"'core.component.form-builder.save' | translate\"\n (click)=\"updateJSON()\"\n prefixIcon=\"save\"\n color=\"primary\"></sd-button>\n </ng-container>\n</sd-modal>\n<sd-modal\n [title]=\"'core.component.form-builder.config-variable' | translate\"\n view=\"dialog\"\n width=\"800px\"\n modalClass=\"sd-form-builder-dialog\"\n #popupConfigureVariables>\n @for (variable of clonedVariables; track variable.id) {\n <div class=\"d-flex align-items-center\" style=\"gap: 8px\">\n <sd-input [form]=\"form\" [label]=\"'core.component.form-builder.field-key' | translate\" [(model)]=\"variable.key\" required></sd-input>\n <sd-input\n [form]=\"form\"\n [label]=\"'core.component.form-builder.display-label' | translate\"\n [(model)]=\"variable.label\"\n required></sd-input>\n <sd-button prefixIcon=\"delete\" (click)=\"removeVariables(variable.id)\" color=\"error\"></sd-button>\n </div>\n }\n <a href=\"javascript:;\" (click)=\"addVariables()\">{{ 'core.component.form-builder.add-variable' | translate }}</a>\n <ng-container sdFooterRight>\n <sd-button\n type=\"fill\"\n [title]=\"'core.component.form-builder.save' | translate\"\n (click)=\"updateVariables()\"\n prefixIcon=\"save\"\n color=\"primary\"></sd-button>\n </ng-container>\n</sd-modal>\n<configure-validation (accept)=\"onUpdateValidations($event)\"></configure-validation>\n", styles: ["@charset \"UTF-8\";:host{--md-sys-color-primary: var(--sd-primary, #005cbb);--md-sys-color-on-primary: var(--sd-primary-contrast, #ffffff);--md-sys-color-primary-container: var(--sd-primary-light, #d7e3ff);--md-sys-color-on-primary-container: var(--sd-primary-dark, #001b3f);--md-sys-color-info: var(--sd-info, #006a6a);--md-sys-color-info-container: var(--sd-info-light, #d4f7f6);--md-sys-color-secondary: #625b71;--md-sys-color-secondary-container: #e8def8;--md-sys-color-on-secondary-container: #1d192b;--md-sys-color-surface: var(--sd-surface, #fdfbff);--md-sys-color-surface-container-lowest: var(--mat-sys-surface-container-lowest, #ffffff);--md-sys-color-surface-container-low: var(--mat-sys-surface-container-low, #f7f8fc);--md-sys-color-surface-container: var(--sd-surface-muted, #f0f0f4);--md-sys-color-surface-container-high: var(--mat-sys-surface-container-high, #e9e9ee);--md-sys-color-surface-container-highest: var(--sd-surface-muted, #e7e8ed);--md-sys-color-on-surface: var(--sd-text, #1a1b1f);--md-sys-color-on-surface-variant: var(--sd-secondary, #44474f);--md-sys-color-outline: var(--sd-border-strong, #74777f);--md-sys-color-outline-variant: var(--sd-border, #c4c6d0);--md-sys-color-error: var(--sd-error, #ba1a1a);--md-sys-color-error-container: var(--sd-error-light, #ffdad6);--md-sys-color-warning: #8a5400;--md-sys-color-warning-container: #ffddb1;--md-sys-color-success: #1f6f43;--md-sys-color-success-container: #b7f0cb;--h-row: 28px;--h-field: 32px;--h-palette: 36px;--h-header: 36px;--h-section: 28px;--h-action-btn: 24px;--gap-1: 4px;--gap-2: 8px;--gap-3: 12px;--gap-4: 16px;--gap-5: 24px;--r-xs: 4px;--r-sm: 8px;--r-md: 12px;--r-lg: 16px;--r-xl: 28px;--r-pill: 999px;--elev-1: 0 1px 2px 0 rgba(0, 0, 0, .06);--elev-2: 0 2px 6px 0 rgba(0, 0, 0, .08), 0 1px 2px 0 rgba(0, 0, 0, .04);--elev-3: 0 6px 14px 0 rgba(0, 0, 0, .1), 0 2px 4px 0 rgba(0, 0, 0, .06);display:block;width:100%;height:100%;font-family:Roboto,system-ui,sans-serif;font-size:13px;color:var(--md-sys-color-on-surface);background:var(--md-sys-color-surface)}:host ::ng-deep .msi{font-family:Material Icons Outlined;font-weight:400;font-style:normal;line-height:1;letter-spacing:normal;text-transform:none;display:inline-block;white-space:nowrap;word-wrap:normal;direction:ltr;font-feature-settings:\"liga\";-webkit-font-feature-settings:\"liga\";-webkit-font-smoothing:antialiased}:host ::ng-deep .msi.filled{font-family:Material Icons}:host ::ng-deep .mono{font-family:Roboto Mono,ui-monospace,monospace}:host ::ng-deep .fb-icon-btn{width:28px;height:28px;border-radius:var(--r-sm);border:none;background:transparent;color:var(--md-sys-color-on-surface-variant);display:inline-flex;align-items:center;justify-content:center;cursor:pointer;transition:background .12s ease,color .12s ease}:host ::ng-deep .fb-icon-btn:hover{background:color-mix(in srgb,var(--md-sys-color-on-surface) 8%,transparent);color:var(--md-sys-color-on-surface)}:host ::ng-deep .fb-icon-btn.is-active{color:var(--md-sys-color-primary);background:var(--md-sys-color-primary-container)}:host ::ng-deep .fb-icon-btn.danger:hover{color:var(--md-sys-color-error);background:var(--md-sys-color-error-container)}:host ::ng-deep .fb-icon-btn.warning.on{color:var(--md-sys-color-warning);background:var(--md-sys-color-warning-container)}:host ::ng-deep .fb-icon-btn .msi{font-size:18px}:host ::ng-deep .fb-field{display:flex;flex-direction:column;gap:4px}:host ::ng-deep .fb-field__label{font-size:11px;font-weight:500;color:var(--md-sys-color-on-surface-variant)}:host ::ng-deep .fb-field__input{height:var(--h-row);padding:0 10px;border-radius:var(--r-xs);background:var(--md-sys-color-surface-container-lowest);border:1px solid var(--md-sys-color-outline-variant);font-size:12px;display:flex;align-items:center;justify-content:space-between;color:var(--md-sys-color-on-surface)}:host ::ng-deep .fb-field__input.placeholder{color:var(--md-sys-color-on-surface-variant)}:host ::ng-deep .fb-field__input:focus-within,:host ::ng-deep .fb-field__input.focused{border-color:var(--md-sys-color-primary);box-shadow:0 0 0 2px color-mix(in srgb,var(--md-sys-color-primary) 18%,transparent)}:host ::ng-deep .fb-field__input .msi{font-size:16px;color:var(--md-sys-color-on-surface-variant)}:host ::ng-deep .fb-field-row{display:flex;align-items:center;gap:var(--gap-2);justify-content:space-between}:host ::ng-deep .fb-switch{display:inline-flex;width:34px;height:18px;border-radius:999px;background:var(--md-sys-color-surface-container-highest);border:1.5px solid var(--md-sys-color-outline);padding:1px;align-items:center;cursor:pointer;position:relative;flex-shrink:0}:host ::ng-deep .fb-switch .fb-switch__thumb{width:12px;height:12px;border-radius:50%;background:var(--md-sys-color-outline)}:host ::ng-deep .fb-switch.on{background:var(--md-sys-color-primary);border-color:var(--md-sys-color-primary);justify-content:flex-end}:host ::ng-deep .fb-switch.on .fb-switch__thumb{background:var(--md-sys-color-on-primary);width:14px;height:14px}:host ::ng-deep .fb-expr{display:flex;align-items:center;gap:6px;height:var(--h-row);padding:0 6px 0 0;border-radius:var(--r-xs);background:var(--md-sys-color-surface-container-lowest);border:1px solid var(--md-sys-color-outline-variant);font-size:12px;color:var(--md-sys-color-on-surface-variant)}:host ::ng-deep .fb-expr__badge{height:100%;display:inline-flex;align-items:center;justify-content:center;padding:0 6px;background:var(--md-sys-color-primary-container);color:var(--md-sys-color-primary);font-family:Roboto Mono,monospace;font-size:10px;font-weight:500;border-radius:var(--r-xs) 0 0 var(--r-xs)}:host ::ng-deep .fb-expr__code{font-family:Roboto Mono,monospace;flex:1;font-size:11px;color:var(--md-sys-color-on-surface)}:host ::ng-deep .fb-expr__code .var{color:var(--md-sys-color-primary);font-weight:500}:host ::ng-deep .fb-expr__code .op{color:var(--md-sys-color-error)}:host ::ng-deep .fb-expr__code .lit{color:var(--md-sys-color-success)}:host ::ng-deep .fb-expr .empty{color:var(--md-sys-color-on-surface-variant);font-style:italic;padding-left:6px;flex:1}:host ::ng-deep details.fb-section{border-bottom:1px solid var(--md-sys-color-outline-variant);margin:0;padding:0;background:transparent}:host ::ng-deep details.fb-section>summary{list-style:none}:host ::ng-deep details.fb-section>summary::-webkit-details-marker{display:none}:host ::ng-deep .fb-section__head{height:var(--h-section);padding:0 var(--gap-4);display:flex;align-items:center;gap:var(--gap-2);cursor:pointer;font-size:12px;font-weight:500;color:var(--md-sys-color-on-surface);-webkit-user-select:none;user-select:none}:host ::ng-deep .fb-section__head .msi{font-size:16px;color:var(--md-sys-color-on-surface-variant)}:host ::ng-deep .fb-section__head .fb-section__title{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host ::ng-deep .fb-section__head .chev{margin-left:auto;transition:transform .16s ease}:host ::ng-deep details.fb-section[open] .fb-section__head .chev{transform:rotate(90deg)}:host ::ng-deep .fb-section__body{padding:4px var(--gap-4) var(--gap-3);display:flex;flex-direction:column;gap:var(--gap-2)}:host ::ng-deep .fb-columns-list{display:flex;flex-direction:column;gap:var(--gap-1);padding-top:var(--gap-1)}:host ::ng-deep .fb-column-row{display:flex;align-items:center;gap:var(--gap-2);height:40px;padding:6px 10px;border-radius:var(--r-sm);background:var(--md-sys-color-surface-container-lowest);border:1px solid var(--md-sys-color-outline-variant);font-size:12px;cursor:pointer;transition:background .12s ease,border-color .12s ease,box-shadow .12s ease}:host ::ng-deep .fb-column-row:hover{background:color-mix(in srgb,var(--md-sys-color-primary) 4%,transparent);border-color:color-mix(in srgb,var(--md-sys-color-primary) 30%,transparent)}:host ::ng-deep .fb-column-row.is-active{border-color:var(--md-sys-color-primary);background:color-mix(in srgb,var(--md-sys-color-primary) 10%,transparent);box-shadow:0 0 0 1px color-mix(in srgb,var(--md-sys-color-primary) 30%,transparent)}:host ::ng-deep .fb-column-row .fb-column-row__type-icon{width:28px;height:28px;border-radius:var(--r-sm);background:var(--md-sys-color-primary-container);color:var(--md-sys-color-primary);display:inline-flex;align-items:center;justify-content:center;flex-shrink:0}:host ::ng-deep .fb-column-row .fb-column-row__type-icon .msi{font-size:16px}:host ::ng-deep .fb-column-row .fb-column-row__meta{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}:host ::ng-deep .fb-column-row .fb-column-row__meta .fb-column-row__label{font-size:13px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--md-sys-color-on-surface)}:host ::ng-deep .fb-column-row .fb-column-row__meta .fb-column-row__req{color:var(--md-sys-color-error);margin-left:2px;font-weight:600}:host ::ng-deep .fb-column-row .fb-column-row__meta .fb-column-row__sub{font-size:11px;color:var(--md-sys-color-on-surface-variant);font-family:Roboto Mono,ui-monospace,monospace;letter-spacing:.02em}:host ::ng-deep .cdk-drag-preview.fb-drag-preview,:host ::ng-deep .fb-drag-preview{width:132px;height:44px;box-sizing:border-box;border-radius:14px;background:var(--md-sys-color-surface-container-lowest, #ffffff);display:inline-flex;align-items:center;gap:10px;padding:8px 12px;box-shadow:0 14px 32px #0000003d,0 5px 14px #00000029;border:1.5px solid color-mix(in srgb,var(--md-sys-color-primary) 46%,var(--md-sys-color-outline-variant));cursor:grabbing;pointer-events:none;-webkit-user-select:none;user-select:none;opacity:1;overflow:hidden;z-index:10000}:host ::ng-deep .cdk-drag-preview.fb-drag-preview .fb-drag-preview__glyph,:host ::ng-deep .fb-drag-preview .fb-drag-preview__glyph{width:28px;height:28px;border-radius:50%;background:var(--md-sys-color-primary);flex-shrink:0;box-shadow:inset 0 0 0 5px color-mix(in srgb,var(--md-sys-color-on-primary) 18%,transparent)}:host ::ng-deep .cdk-drag-preview.fb-drag-preview .fb-drag-preview__body,:host ::ng-deep .fb-drag-preview .fb-drag-preview__body{flex:1;min-width:0;display:grid;gap:7px}:host ::ng-deep .cdk-drag-preview.fb-drag-preview .fb-drag-preview__line,:host ::ng-deep .fb-drag-preview .fb-drag-preview__line{height:7px;border-radius:999px;background:color-mix(in srgb,var(--md-sys-color-outline-variant) 72%,transparent)}:host ::ng-deep .cdk-drag-preview.fb-drag-preview .fb-drag-preview__line.is-strong,:host ::ng-deep .fb-drag-preview .fb-drag-preview__line.is-strong{width:74%;background:color-mix(in srgb,var(--md-sys-color-primary) 26%,var(--md-sys-color-outline-variant))}:host ::ng-deep .cdk-drag-placeholder.fb-drop-placeholder,:host ::ng-deep .fb-drop-placeholder{box-sizing:border-box;min-height:46px;border:1.5px dashed color-mix(in srgb,var(--md-sys-color-primary) 76%,var(--md-sys-color-outline-variant));border-radius:var(--r-sm);background:linear-gradient(135deg,color-mix(in srgb,var(--md-sys-color-primary) 10%,transparent),transparent 62%),color-mix(in srgb,var(--md-sys-color-primary) 4%,var(--md-sys-color-surface-container-lowest));color:var(--md-sys-color-primary);display:flex;align-items:center;gap:var(--gap-2);padding:8px 10px;pointer-events:none;-webkit-user-select:none;user-select:none;opacity:1!important;transform:none!important;animation:none!important;position:relative;z-index:6;isolation:isolate;box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--md-sys-color-primary) 10%,transparent),0 2px 8px color-mix(in srgb,var(--md-sys-color-primary) 10%,transparent)}:host ::ng-deep .fb-drop-placeholder__icon{width:28px;height:28px;border-radius:var(--r-sm);background:var(--md-sys-color-primary-container);color:var(--md-sys-color-primary);display:inline-flex;align-items:center;justify-content:center;flex:0 0 28px}:host ::ng-deep .fb-drop-placeholder__icon .msi{font-size:17px}:host ::ng-deep .fb-drop-placeholder__body{min-width:0;display:flex;flex-direction:column;gap:2px}:host ::ng-deep .fb-drop-placeholder__title{color:var(--md-sys-color-on-surface);font-size:12px;font-weight:600;line-height:1.2;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host ::ng-deep .fb-drop-placeholder__meta{color:var(--md-sys-color-primary);font-size:10px;font-weight:500;line-height:1.2;text-transform:uppercase;letter-spacing:0}:host ::ng-deep .fb-palette__list>.fb-drop-placeholder{display:none!important}:host ::ng-deep .fb-row__items>.fb-drop-placeholder,:host ::ng-deep .fb-group__body>.fb-drop-placeholder{flex:0 0 calc(var(--fb-placeholder-columns, 6) / 12 * 100% - var(--gap-2));min-width:min(144px,100%);max-width:100%;align-self:stretch;margin:0 2px}:host ::ng-deep [cdkDropListOrientation=vertical]>.fb-drop-placeholder{width:auto;min-height:50px;margin:6px 14px;align-self:stretch}:host ::ng-deep .fb-drop-placeholder--row{min-height:56px}:host ::ng-deep .fb-row-insert-placeholder{--fb-placeholder-columns: 12;box-sizing:border-box;min-height:58px;margin:0 var(--gap-2) var(--gap-2);border:1.5px dashed color-mix(in srgb,var(--md-sys-color-primary) 68%,var(--md-sys-color-outline-variant));border-radius:var(--r-sm);background:linear-gradient(135deg,color-mix(in srgb,var(--md-sys-color-primary) 9%,transparent),transparent 68%),color-mix(in srgb,var(--md-sys-color-primary) 3%,var(--md-sys-color-surface-container-lowest));color:var(--md-sys-color-primary);display:flex;align-items:center;gap:var(--gap-2);padding:8px 10px;box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--md-sys-color-primary) 8%,transparent),0 1px 6px color-mix(in srgb,var(--md-sys-color-primary) 8%,transparent);pointer-events:none;position:relative;z-index:6;isolation:isolate}:host ::ng-deep .cdk-drop-list-receiving{background:color-mix(in srgb,var(--md-sys-color-primary) 3%,transparent);transition:background .16s ease}:host ::ng-deep .fb-group__body.cdk-drop-list-receiving,:host ::ng-deep .fb-group__body.cdk-drop-list-dragging{background:color-mix(in srgb,var(--md-sys-color-primary) 7%,transparent)!important;outline:1px solid color-mix(in srgb,var(--md-sys-color-primary) 72%,transparent);outline-offset:-6px;border-radius:var(--r-sm)}:host ::ng-deep .fb-resize.cdk-drag-preview{display:none!important}:host ::ng-deep .fb-shell--dragging .fb-item:hover,:host ::ng-deep .fb-shell--dragging .fb-group:hover,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-item:hover,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-group:hover{border-color:transparent!important;background:transparent!important}:host ::ng-deep .fb-shell--dragging .fb-row:hover,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-row:hover{outline-color:transparent!important;background:transparent!important}:host ::ng-deep .fb-shell--dragging .fb-row:hover .fb-row__drag,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-row:hover .fb-row__drag{opacity:0!important}:host ::ng-deep .fb-shell--dragging .fb-row.is-drag-target,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-row.is-drag-target{outline-color:color-mix(in srgb,var(--md-sys-color-info) 44%,transparent)!important;background:color-mix(in srgb,var(--md-sys-color-info) 4%,transparent)!important}:host ::ng-deep .fb-shell--dragging .fb-row__drag,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-row__drag{opacity:0!important}:host ::ng-deep .fb-shell--dragging .fb-actions,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-actions{opacity:0!important;pointer-events:none!important;transform:translateY(-2px)!important}:host ::ng-deep .fb-shell--dragging .fb-resize,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-resize{opacity:0!important;pointer-events:none!important}:host ::ng-deep .fb-shell--dragging .fb-status-chip,:host ::ng-deep .fb-shell--dragging .fb-resize-chip,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-status-chip,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-resize-chip{opacity:0!important;pointer-events:none!important}:host ::ng-deep .fb-shell--dragging .fb-row-break-btn,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-row-break-btn{opacity:0!important;pointer-events:none!important}:host ::ng-deep .fb-shell--dragging .fb-palette-item:hover,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-palette-item:hover{background:transparent!important}:host ::ng-deep .fb-shell--dragging .fb-palette-item:hover .fb-palette-item__drag,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-palette-item:hover .fb-palette-item__drag{opacity:0!important}:host ::ng-deep .fb-shell--dragging .fb-group__body,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-group__body{min-height:72px!important;background:color-mix(in srgb,var(--md-sys-color-primary) 4%,transparent)!important;outline:1px dashed color-mix(in srgb,var(--md-sys-color-primary) 48%,transparent)!important;outline-offset:-6px;border-radius:var(--r-sm);transition:background .16s ease,outline .16s ease,min-height .16s ease}:host ::ng-deep .fb-shell--dragging .fb-group__empty,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-group__empty{opacity:1;border-color:color-mix(in srgb,var(--md-sys-color-primary) 55%,transparent);background:color-mix(in srgb,var(--md-sys-color-primary) 7%,transparent);color:var(--md-sys-color-primary)}:host ::ng-deep .fb-shell--dragging .fb-group__body.cdk-drop-list-receiving,:host ::ng-deep .fb-shell--dragging .fb-group__body.cdk-drop-list-dragging,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-group__body.cdk-drop-list-receiving,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-group__body.cdk-drop-list-dragging{background:color-mix(in srgb,var(--md-sys-color-primary) 9%,transparent)!important;outline:1.5px solid var(--md-sys-color-primary)!important}:host ::ng-deep .fb-shell--resizing .fb-row.is-resizing-row{outline-color:color-mix(in srgb,var(--md-sys-color-primary) 42%,transparent);background:color-mix(in srgb,var(--md-sys-color-primary) 3%,transparent)}:host ::ng-deep .fb-shell--resizing .fb-row.is-resizing-row .fb-row__items:before{content:\"\";position:absolute;inset:-3px 0;border-radius:var(--r-sm);background:repeating-linear-gradient(90deg,transparent 0,transparent calc(8.333% - 1px),color-mix(in srgb,var(--md-sys-color-primary) 18%,transparent) calc(8.333% - 1px),color-mix(in srgb,var(--md-sys-color-primary) 18%,transparent) 8.333%),color-mix(in srgb,var(--md-sys-color-primary) 3%,transparent);pointer-events:none;z-index:0}:host ::ng-deep .fb-shell--resizing .fb-row.is-resizing-row .fb-row__items>*{position:relative;z-index:1}:host ::ng-deep .fb-shell--resizing .fb-item.is-resizing{border-color:var(--md-sys-color-primary)!important;background:color-mix(in srgb,var(--md-sys-color-primary) 6%,var(--md-sys-color-surface-container-lowest));box-shadow:0 0 0 2px color-mix(in srgb,var(--md-sys-color-primary) 18%,transparent),inset 0 0 0 1px color-mix(in srgb,var(--md-sys-color-primary) 18%,transparent);cursor:col-resize;z-index:2}:host ::ng-deep .fb-shell--resizing .fb-item.is-resizing .fb-resize{opacity:1!important;pointer-events:auto!important;background:var(--md-sys-color-primary);box-shadow:0 0 0 4px color-mix(in srgb,var(--md-sys-color-primary) 16%,transparent)}:host ::ng-deep .cdk-drag-animating{transition:transform .22s cubic-bezier(.2,0,0,1)!important}:host ::ng-deep .cdk-drag-dragging{transition:none!important}:host ::ng-deep .fb-item:not(.cdk-drag-dragging){transition:flex .12s cubic-bezier(.2,0,0,1),max-width .12s cubic-bezier(.2,0,0,1),border-color .12s,background .12s}::ng-deep .cdk-drag-preview.fb-drag-preview{width:132px;height:44px;box-sizing:border-box;border-radius:14px;background:#fff;display:inline-flex;align-items:center;gap:10px;padding:8px 12px;box-shadow:0 14px 32px #0000003d,0 5px 14px #00000029;border:1.5px solid color-mix(in srgb,var(--sd-primary, #005cbb) 46%,#d6d2da);cursor:grabbing;pointer-events:none;-webkit-user-select:none;user-select:none;opacity:1;overflow:hidden;z-index:10000}::ng-deep .cdk-drag-preview.fb-drag-preview .fb-drag-preview__glyph{width:28px;height:28px;border-radius:50%;background:var(--sd-primary, #005cbb);flex-shrink:0;box-shadow:inset 0 0 0 5px #ffffff2e}::ng-deep .cdk-drag-preview.fb-drag-preview .fb-drag-preview__body{flex:1;min-width:0;display:grid;gap:7px}::ng-deep .cdk-drag-preview.fb-drag-preview .fb-drag-preview__line{height:7px;border-radius:999px;background:#d6d2dab8}::ng-deep .cdk-drag-preview.fb-drag-preview .fb-drag-preview__line.is-strong{width:74%;background:color-mix(in srgb,var(--sd-primary, #005cbb) 26%,#d6d2da)}::ng-deep .sd-form-builder-dialog .mat-mdc-dialog-surface,::ng-deep .sd-form-builder-dialog .mdc-dialog__surface,::ng-deep .sd-form-builder-dialog .mat-dialog-container{max-height:calc(100vh - 40px)}::ng-deep .sd-form-builder-dialog .sd-modal-body{padding:16px;overscroll-behavior:contain;scroll-padding-bottom:24px}::ng-deep .sd-form-builder-dialog .sd-modal-footer{min-height:64px;padding:12px 16px;background:var(--sd-surface, #fff);box-shadow:0 -1px 0 var(--sd-border, #dde0e5)}::ng-deep .fb-validation-menu.mat-mdc-menu-panel{border-radius:12px;box-shadow:0 8px 24px #0f172a2e;overflow:hidden}.fb-shell{width:100%;height:100%;display:flex;background:var(--md-sys-color-surface);overflow:hidden;min-height:0}.fb-panel-header{height:var(--h-header);display:flex;align-items:center;justify-content:space-between;padding:0 var(--gap-3);border-bottom:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-low);flex-shrink:0}.fb-panel-header__title{font-size:11px;font-weight:500;letter-spacing:.08em;text-transform:uppercase;color:var(--md-sys-color-on-surface-variant)}.fb-panel-header__actions{display:flex;align-items:center;gap:2px}.fb-palette{width:220px;flex-shrink:0;display:flex;flex-direction:column;background:var(--md-sys-color-surface-container-low);border-right:1px solid var(--md-sys-color-outline-variant)}.fb-palette__scroll{flex:1;overflow-y:auto;padding-bottom:var(--gap-3)}.fb-palette__search{margin:var(--gap-3);height:32px;display:flex;align-items:center;gap:var(--gap-2);padding:0 var(--gap-3);border-radius:var(--r-pill);background:var(--md-sys-color-surface-container-high);color:var(--md-sys-color-on-surface-variant);font-size:13px;flex-shrink:0}.fb-palette__search .msi{font-size:18px}.fb-palette__search input{flex:1;border:none;outline:none;background:transparent;color:var(--md-sys-color-on-surface);font-size:13px;font-family:inherit}.fb-palette__search input::placeholder{color:var(--md-sys-color-on-surface-variant)}.fb-palette__search .kbd{font-family:Roboto Mono,monospace;font-size:10px;padding:1px 6px;border-radius:var(--r-xs);background:var(--md-sys-color-surface-container-lowest);border:1px solid var(--md-sys-color-outline-variant);color:var(--md-sys-color-on-surface-variant)}.fb-palette__group-label{font-size:11px;font-weight:500;letter-spacing:.04em;color:var(--md-sys-color-on-surface-variant);padding:var(--gap-3) var(--gap-4) var(--gap-1);text-transform:uppercase}.fb-palette__list{display:flex;flex-direction:column;padding:0 var(--gap-2)}.fb-palette-item{height:var(--h-palette);display:flex;align-items:center;gap:var(--gap-3);padding:0 var(--gap-3);border-radius:var(--r-sm);cursor:grab;color:var(--md-sys-color-on-surface);position:relative;-webkit-user-select:none;user-select:none}.fb-palette-item:hover{background:color-mix(in srgb,var(--md-sys-color-primary) 8%,transparent)}.fb-palette-item .fb-palette-item__icon{width:24px;height:24px;border-radius:var(--r-xs);display:inline-flex;align-items:center;justify-content:center;color:var(--md-sys-color-primary);background:var(--md-sys-color-primary-container);flex-shrink:0}.fb-palette-item .fb-palette-item__icon .msi{font-size:16px}.fb-palette-item .fb-palette-item__name{font-size:13px;font-weight:400}.fb-palette-item .fb-palette-item__drag{margin-left:auto;color:var(--md-sys-color-outline);opacity:0;transition:opacity .12s ease}.fb-palette-item .fb-palette-item__drag .msi{font-size:16px}.fb-palette-item:hover .fb-palette-item__drag{opacity:1}.fb-canvas{flex:1 1 0;min-width:0;display:flex;flex-direction:column;background:var(--md-sys-color-surface);overflow:hidden}.fb-canvas__header{height:var(--h-header);display:flex;align-items:center;justify-content:space-between;padding:0 var(--gap-4);border-bottom:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface);gap:var(--gap-3);flex-shrink:0}.fb-canvas__title{display:flex;align-items:center;gap:var(--gap-2);font-size:11px;font-weight:500;letter-spacing:.08em;text-transform:uppercase;color:var(--md-sys-color-on-surface-variant)}.fb-canvas__title .breadcrumb-sep{color:var(--md-sys-color-outline)}.fb-canvas__actions{display:flex;align-items:center;gap:var(--gap-2)}.fb-divider{width:1px;height:18px;background:var(--md-sys-color-outline-variant)}.fb-canvas__body{flex:1 1 0;overflow-y:auto;overflow-x:hidden;padding:var(--gap-4);position:relative;background:var(--md-sys-color-surface);min-width:0}.fb-canvas__body.is-preview{background:var(--md-sys-color-surface-container-low);padding:32px}.fb-mode-toggle{display:inline-flex;height:32px;padding:2px;border:1px solid var(--md-sys-color-outline-variant);border-radius:var(--r-pill);background:var(--md-sys-color-surface-container-lowest);flex-shrink:0;gap:2px;box-shadow:var(--elev-1)}.fb-mode-toggle button{border:0;background:transparent;border-radius:var(--r-pill);padding:0 12px;font-size:12px;font-weight:500;display:inline-flex;align-items:center;gap:6px;color:var(--md-sys-color-on-surface-variant);cursor:pointer;transition:background .12s ease,color .12s ease,box-shadow .12s ease}.fb-mode-toggle button .msi{font-size:16px}.fb-mode-toggle button:hover{background:color-mix(in srgb,var(--md-sys-color-primary) 7%,transparent);color:var(--md-sys-color-on-surface)}.fb-mode-toggle button:focus-visible{outline:2px solid color-mix(in srgb,var(--md-sys-color-primary) 48%,transparent);outline-offset:1px}.fb-mode-toggle button.is-active{background:var(--md-sys-color-primary-container);color:var(--md-sys-color-primary);box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--md-sys-color-primary) 18%,transparent)}.fb-row{position:relative;display:flex;gap:var(--gap-2);padding:6px var(--gap-2);border-radius:var(--r-sm);margin-bottom:6px;min-width:0;transition:outline .12s ease,background .12s ease;outline:1px dashed transparent;outline-offset:-1px}.fb-row:hover{outline-color:var(--md-sys-color-outline-variant);background:color-mix(in srgb,var(--md-sys-color-primary) 3%,transparent)}.fb-row:hover .fb-row__drag{opacity:1}.fb-row.is-drag-target{outline-color:color-mix(in srgb,var(--md-sys-color-info) 44%,transparent);background:color-mix(in srgb,var(--md-sys-color-info) 4%,transparent)}.fb-row__items{position:relative;min-width:0}.fb-row__drag{position:absolute;left:-22px;top:50%;transform:translateY(-50%);width:18px;height:22px;border-radius:var(--r-xs);display:inline-flex;align-items:center;justify-content:center;color:var(--md-sys-color-on-surface-variant);background:var(--md-sys-color-surface-container-high);opacity:0;cursor:grab;z-index:2}.fb-row__drag .msi{font-size:14px}.fb-row-break-btn{position:absolute;right:-28px;top:50%;transform:translateY(-50%);width:22px;height:22px;border-radius:var(--r-pill);display:inline-flex;align-items:center;justify-content:center;color:var(--md-sys-color-on-surface-variant);background:var(--md-sys-color-surface-container-high);border:1px solid var(--md-sys-color-outline-variant);cursor:pointer;opacity:0;z-index:2;transition:opacity .12s ease,background .12s ease,color .12s ease}.fb-row-break-btn .msi{font-size:14px}.fb-row-break-btn:hover{background:var(--md-sys-color-primary-container);color:var(--md-sys-color-primary);border-color:transparent}.fb-row:hover .fb-row-break-btn{opacity:1}.fb-item{position:relative;border:1.5px dashed transparent;border-radius:var(--r-md);padding:var(--gap-2) var(--gap-3);min-height:56px;min-width:0;transition:border-color .12s ease,background .12s ease;background:transparent;cursor:grab;display:flex;flex-direction:column;gap:6px}.fb-item:hover{border-color:color-mix(in srgb,var(--md-sys-color-primary) 60%,transparent)}.fb-item.is-selected{border:1.5px solid var(--md-sys-color-primary);background:color-mix(in srgb,var(--md-sys-color-primary) 4%,transparent);box-shadow:0 0 0 2px color-mix(in srgb,var(--md-sys-color-primary) 18%,transparent),inset 3px 0 0 var(--md-sys-color-primary)}.fb-item.is-hidden{opacity:.45}.fb-item.is-readonly:after{content:\"\";position:absolute;inset:0;border-radius:var(--r-md);background:repeating-linear-gradient(135deg,transparent 0 6px,color-mix(in srgb,var(--md-sys-color-on-surface) 4%,transparent) 6px 7px);pointer-events:none}.fb-item.column-2{flex:0 0 calc(16.6666666667% - var(--gap-2));max-width:calc(16.6666666667% - var(--gap-2))}.fb-item.column-3{flex:0 0 calc(25% - var(--gap-2));max-width:calc(25% - var(--gap-2))}.fb-item.column-4{flex:0 0 calc(33.3333333333% - var(--gap-2));max-width:calc(33.3333333333% - var(--gap-2))}.fb-item.column-5{flex:0 0 calc(41.6666666667% - var(--gap-2));max-width:calc(41.6666666667% - var(--gap-2))}.fb-item.column-6{flex:0 0 calc(50% - var(--gap-2));max-width:calc(50% - var(--gap-2))}.fb-item.column-7{flex:0 0 calc(58.3333333333% - var(--gap-2));max-width:calc(58.3333333333% - var(--gap-2))}.fb-item.column-8{flex:0 0 calc(66.6666666667% - var(--gap-2));max-width:calc(66.6666666667% - var(--gap-2))}.fb-item.column-9{flex:0 0 calc(75% - var(--gap-2));max-width:calc(75% - var(--gap-2))}.fb-item.column-10{flex:0 0 calc(83.3333333333% - var(--gap-2));max-width:calc(83.3333333333% - var(--gap-2))}.fb-item.column-11{flex:0 0 calc(91.6666666667% - var(--gap-2));max-width:calc(91.6666666667% - var(--gap-2))}.fb-item.column-12{flex:1 1 100%;max-width:100%}.fb-actions{position:absolute;top:-14px;right:8px;display:flex;align-items:center;gap:2px;padding:2px;border-radius:var(--r-pill);background:var(--md-sys-color-surface-container-lowest);box-shadow:var(--elev-2);border:1px solid var(--md-sys-color-outline-variant);z-index:4;opacity:0;transform:translateY(-2px);pointer-events:none;transition:opacity .12s ease,transform .12s ease}.fb-actions .fb-icon-btn{width:24px;height:24px}.fb-actions .fb-icon-btn .msi{font-size:16px}.fb-item:hover .fb-actions,.fb-item.is-selected .fb-actions{opacity:1;transform:translateY(0);pointer-events:auto}table-control{display:block;width:100%;max-width:100%;min-width:0}.fb-group>.fb-actions{top:6px;right:8px}.fb-group:hover>.fb-actions,.fb-group.is-selected>.fb-actions{opacity:1;transform:translateY(0);pointer-events:auto}.fb-status-chip{display:inline-flex;align-items:center;gap:4px;padding:2px 8px;height:18px;border-radius:var(--r-pill);font-size:10px;font-weight:500;letter-spacing:.02em;position:absolute;top:-9px;left:12px;z-index:3;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-lowest);color:var(--md-sys-color-on-surface-variant)}.fb-status-chip .msi{font-size:12px}.fb-status-chip.readonly{color:var(--md-sys-color-warning);background:var(--md-sys-color-warning-container);border-color:transparent}.fb-status-chip.hidden{color:var(--md-sys-color-on-surface-variant)}.fb-status-chip.conditional{color:var(--md-sys-color-primary);background:var(--md-sys-color-primary-container);border-color:transparent}.fb-resize-chip{position:absolute;right:14px;bottom:-18px;z-index:5;display:inline-flex;align-items:center;height:20px;padding:0 8px;border-radius:var(--r-pill);background:var(--md-sys-color-primary);color:var(--md-sys-color-on-primary);box-shadow:var(--elev-2);font-size:11px;font-weight:700;letter-spacing:0;pointer-events:none}.fb-resize{position:absolute;top:10px;bottom:10px;right:-6px;width:8px;border-radius:999px;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 28%,transparent);background:var(--md-sys-color-surface-container-lowest);display:flex;align-items:center;justify-content:center;cursor:col-resize;opacity:0;transition:opacity .12s ease,background .12s ease,box-shadow .12s ease;z-index:3}.fb-resize:before{content:\"\";width:2px;height:20px;background:var(--md-sys-color-primary);border-radius:999px}.fb-resize:hover{background:var(--md-sys-color-primary-container);box-shadow:0 0 0 3px color-mix(in srgb,var(--md-sys-color-primary) 12%,transparent)}.fb-item:hover .fb-resize{opacity:1}.fb-item.is-break{min-height:28px;padding:8px var(--gap-3);justify-content:center}.fb-break-line{display:flex;align-items:center;gap:var(--gap-2);color:var(--md-sys-color-on-surface-variant);font-size:11px;font-weight:500;letter-spacing:.06em;text-transform:uppercase}.fb-break-line:before,.fb-break-line:after{content:\"\";flex:1;height:1px;background:var(--md-sys-color-outline-variant)}.fb-break-line .msi{display:none}.fb-break-line .fb-break-label{color:var(--md-sys-color-on-surface-variant)}.fb-drop-hint{height:36px;border-radius:var(--r-sm);border:1.5px dashed var(--md-sys-color-primary);background:color-mix(in srgb,var(--md-sys-color-primary) 6%,transparent);color:var(--md-sys-color-primary);display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:500;gap:6px;margin:4px 0}.fb-drop-hint .msi{font-size:16px}.fb-group{background:var(--md-sys-color-surface-container-lowest);border-radius:var(--r-lg);border:1.5px dashed transparent;transition:border-color .12s ease,background .12s ease;position:relative;display:flex;flex-direction:column;overflow:visible;cursor:default}.fb-group:hover{border-color:color-mix(in srgb,var(--md-sys-color-primary) 60%,transparent)}.fb-group.is-selected{border:1.5px solid var(--md-sys-color-primary);box-shadow:0 0 0 2px color-mix(in srgb,var(--md-sys-color-primary) 18%,transparent),inset 3px 0 0 var(--md-sys-color-primary)}.fb-group.is-hidden{opacity:.55}.fb-group__header{display:flex;align-items:center;gap:var(--gap-2);padding:8px 12px;border-top-left-radius:12px;border-top-right-radius:12px;cursor:grab}.fb-group__header:active{cursor:grabbing}.fb-group__header .fb-group__icon .msi{font-size:18px}.fb-group__header .fb-group__label{font-size:13px;font-weight:500}.fb-group__header .fb-group__count{margin-left:auto;font-size:10px;opacity:.75}.fb-group__conditional-chip{display:inline-flex;align-items:center;gap:4px;font-family:Roboto Mono,monospace;font-size:10px;padding:2px 8px;border-radius:var(--r-pill);background:color-mix(in srgb,currentColor 12%,transparent)}.fb-group__body{padding:12px;display:flex;flex-wrap:wrap;gap:var(--gap-2);min-height:60px;position:relative}.fb-group__empty{display:flex;align-items:center;justify-content:center;height:56px;border-radius:var(--r-sm);border:1.5px dashed var(--md-sys-color-outline-variant);color:var(--md-sys-color-on-surface-variant);font-size:12px;font-style:italic}.fb-empty{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--gap-3);color:var(--md-sys-color-on-surface-variant);text-align:center;padding:24px;pointer-events:none}.fb-empty__art{width:96px;height:96px;border-radius:var(--r-xl);background:var(--md-sys-color-primary-container);color:var(--md-sys-color-primary);display:inline-flex;align-items:center;justify-content:center}.fb-empty__art .msi{font-size:44px}.fb-empty__title{font-size:16px;font-weight:500;color:var(--md-sys-color-on-surface)}.fb-empty__hint{font-size:13px;max-width:320px;line-height:1.5}.fb-empty__kbd{display:inline-flex;align-items:center;gap:6px;font-family:Roboto Mono,monospace;font-size:11px;padding:4px 8px;border-radius:var(--r-xs);border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container);color:var(--md-sys-color-on-surface-variant)}.fb-empty__kbd .msi{font-size:12px}.fb-attrs{width:320px;flex-shrink:0;display:flex;flex-direction:column;background:var(--md-sys-color-surface-container-low);border-left:1px solid var(--md-sys-color-outline-variant)}.fb-attrs__selection{padding:var(--gap-3) var(--gap-4);display:flex;align-items:center;gap:var(--gap-2);border-bottom:1px solid var(--md-sys-color-outline-variant)}.fb-attrs__sel-icon{width:28px;height:28px;border-radius:var(--r-sm);background:var(--md-sys-color-primary-container);color:var(--md-sys-color-primary);display:inline-flex;align-items:center;justify-content:center;flex-shrink:0}.fb-attrs__sel-icon .msi{font-size:18px}.fb-attrs__sel-meta{display:flex;flex-direction:column;min-width:0}.fb-attrs__sel-name{font-size:13px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.fb-attrs__sel-key{font-family:Roboto Mono,monospace;font-size:11px;color:var(--md-sys-color-on-surface-variant)}.fb-attrs__body{flex:1 1 0;overflow-y:auto;overflow-x:hidden;min-width:0}.fb-attrs__placeholder{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--gap-2);padding:32px;color:var(--md-sys-color-on-surface-variant);text-align:center}.fb-attrs__placeholder .msi{font-size:32px;color:var(--md-sys-color-outline)}.fb-attrs__placeholder .title{font-size:13px;font-weight:500;color:var(--md-sys-color-on-surface)}.fb-attrs__placeholder .hint{font-size:12px;max-width:220px;line-height:1.5}.fb-preview-card{max-width:760px;margin:0 auto;background:var(--md-sys-color-surface-container-lowest);border-radius:var(--r-lg);padding:32px;box-shadow:var(--elev-1)}.fb-preview-card__chip{display:inline-flex;align-items:center;gap:6px;padding:2px 10px;height:22px;border-radius:var(--r-pill);background:var(--md-sys-color-primary-container);color:var(--md-sys-color-primary);font-size:10px;font-weight:500;letter-spacing:.08em;text-transform:uppercase;margin-bottom:var(--gap-3)}.fb-preview-card__title{font-size:20px;font-weight:500;margin:0 0 var(--gap-4);color:var(--md-sys-color-on-surface)}.fb-preview-card__footer{display:flex;justify-content:flex-end;gap:var(--gap-2);margin-top:var(--gap-4);padding-top:var(--gap-4);border-top:1px solid var(--md-sys-color-outline-variant)}.fb-icon-picker{display:grid;grid-template-columns:repeat(6,1fr);gap:4px;margin-top:4px}.fb-icon-picker .fb-icon-picker__btn{height:28px;border-radius:var(--r-xs);border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-lowest);color:var(--md-sys-color-on-surface-variant);display:inline-flex;align-items:center;justify-content:center;cursor:pointer}.fb-icon-picker .fb-icon-picker__btn .msi{font-size:16px}.fb-icon-picker .fb-icon-picker__btn.is-active{border-color:var(--md-sys-color-primary);background:var(--md-sys-color-primary-container);color:var(--md-sys-color-primary)}.fb-icon-picker .fb-icon-picker__btn:hover{background:color-mix(in srgb,var(--md-sys-color-primary) 8%,transparent)}.fb-color-swatches{display:flex;gap:var(--gap-2);margin-top:4px}.fb-color-swatches .fb-color-swatch{width:24px;height:24px;border-radius:50%;cursor:pointer;border:2px solid transparent}.fb-color-swatches .fb-color-swatch.is-active{border-color:var(--md-sys-color-on-surface)}.fb-color-swatches .fb-color-swatch.primary{background:var(--md-sys-color-primary)}.fb-color-swatches .fb-color-swatch.secondary{background:var(--md-sys-color-secondary)}.fb-color-swatches .fb-color-swatch.success{background:var(--md-sys-color-success)}.fb-color-swatches .fb-color-swatch.warning{background:var(--md-sys-color-warning)}.fb-color-swatches .fb-color-swatch.error{background:var(--md-sys-color-error)}.fb-children-list{display:flex;flex-direction:column;gap:4px}.fb-child-row{display:flex;align-items:center;gap:var(--gap-2);height:30px;padding:0 8px;border-radius:var(--r-xs);background:var(--md-sys-color-surface-container-lowest);border:1px solid var(--md-sys-color-outline-variant);font-size:12px}.fb-child-row .fb-child-row__icon{width:22px;height:22px;border-radius:var(--r-xs);background:var(--md-sys-color-primary-container);color:var(--md-sys-color-primary);display:inline-flex;align-items:center;justify-content:center;flex-shrink:0}.fb-child-row .fb-child-row__icon .msi{font-size:14px}.fb-child-row .fb-child-row__label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-child-row .fb-child-row__key{font-family:Roboto Mono,monospace;font-size:10px;color:var(--md-sys-color-on-surface-variant)}.fb-child-row:hover{background:color-mix(in srgb,var(--md-sys-color-primary) 4%,transparent)}.fb-add-link{display:inline-flex;align-items:center;gap:4px;font-size:12px;color:var(--md-sys-color-primary);cursor:pointer;padding:4px 0;text-decoration:none}.fb-add-link .msi{font-size:14px}.fb-add-link:hover{text-decoration:underline}.fb-group__body--static{display:flex;flex-wrap:wrap;gap:var(--gap-2, 8px);cursor:pointer;min-height:48px}.fb-group__body--static .fb-item--static{pointer-events:none;position:relative}.fb-group__body--static .fb-item--static .fb-resize,.fb-group__body--static .fb-item--static .fb-actions,.fb-group__body--static .fb-item--static .fb-status-chip{display:none!important}.fb-canvas__header--group .fb-groupedit__crumb{display:flex;align-items:center;gap:6px;font-weight:600}.fb-canvas__header--group .fb-groupedit__icon .msi{font-size:18px;color:var(--md-sys-color-primary)}.fb-canvas__header--group .fb-groupedit__hint{font-weight:400;font-size:12px;color:var(--md-sys-color-on-surface-variant, #6b6b6b);padding:1px 8px;border-radius:999px;background:color-mix(in srgb,var(--md-sys-color-primary) 8%,transparent)}\n"] }]
|
|
6709
6786
|
}], ctorParameters: () => [], propDecorators: { popupViewJSON: [{ type: i0.ViewChild, args: ['popupViewJSON', { isSignal: true }] }], popupConfigureVariables: [{ type: i0.ViewChild, args: ['popupConfigureVariables', { isSignal: true }] }], configureValidation: [{ type: i0.ViewChild, args: [i0.forwardRef(() => ConfigureValidationComponent), { isSignal: true }] }], formRender: [{ type: i0.ViewChild, args: [i0.forwardRef(() => SdFormRender), { isSignal: true }] }], formGeneric: [{ type: i0.Input, args: [{ isSignal: true, alias: "formGeneric", required: false }] }] } });
|
|
6710
6787
|
|
|
6711
6788
|
// Template là các mẫu do Portal định nghĩa sẵn (key, label ....) để người dùng chọn nhanh
|
|
@@ -6870,11 +6947,11 @@ class SdFeelExpression {
|
|
|
6870
6947
|
return attributes;
|
|
6871
6948
|
};
|
|
6872
6949
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdFeelExpression, deps: [{ token: i0.ChangeDetectorRef }, { token: ExpressionFeelPipe }, { token: FormGenericService }], target: i0.ɵɵFactoryTarget.Component });
|
|
6873
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdFeelExpression, isStandalone: true, selector: "sd-feel-expression", inputs: { components: "components", _expression: ["expression", "_expression"], _model: ["model", "_model"] }, outputs: { expressionChange: "expressionChange", modelChange: "modelChange", sdChange: "sdChange" }, viewQueries: [{ propertyName: "modal", first: true, predicate: SdModal, descendants: true }], ngImport: i0, template: "<a class=\"T12R\" href=\"javascript:;\" (click)=\"edit()\">{{model || '--'}}</a>\n@if (expression) {\n<sd-modal [title]=\"'core.component.form-builder.set-expression' | translate\">\n <div class=\"c-builder\">\n <div class=\"d-flex justify-content-end\">\n <sd-button prefixIcon=\"calculate\" class=\"mr-4\" [title]=\"'core.component.form-builder.condition-group' | translate\" color=\"primary\" (click)=\"addCombinator(expression.conditions)\"></sd-button>\n <sd-button prefixIcon=\"add\" [title]=\"'core.component.form-builder.condition' | translate\" color=\"primary\" (click)=\"addCondition(expression.conditions)\" type=\"fill\"></sd-button>\n </div>\n <!-- C\u1EA5p 1 -->\n @for (conditionLv1 of expression.conditions; track conditionLv1.key; let idxLv1 = $index) {\n <div class=\"d-flex align-items-end py-8\" style=\"gap: 8px;\">\n <div class=\"c-condition\">\n <!-- D\u00F2ng 1 ch\u1EC9 hi\u1EC7n WHERE -->\n @if (idxLv1 === 0) {\n <div class=\"c-where\">WHERE</div>\n }\n <!-- D\u00F2ng 2 hi\u1EC3n th\u1ECB combinator -->\n @if (idxLv1 === 1) {\n <div class=\"c-select\">\n <sd-select\n size=\"sm\"\n [(model)]=\"expression.combinator\"\n [items]=\"[\n { value: '&&', display: 'AND' },\n { value: '||', display: 'OR' }\n ]\"\n valueField=\"value\"\n displayField=\"display\"\n required\n hideInlineError=\"\"></sd-select>\n </div>\n }\n </div>\n @if (conditionLv1.type === 'condition') {\n <sd-autocomplete size=\"sm\" [form]=\"form\" style=\"width: 200px;\" [(model)]=\"conditionLv1.field\" [items]=\"attributes\" valueField=\"value\" displayField=\"display\" required hideInlineError></sd-autocomplete>\n @let fieldAttribute = attribute[conditionLv1.field!];\n <sd-select size=\"sm\" [form]=\"form\" style=\"width: 100px;\" [(model)]=\"conditionLv1.operator\" [items]=\"attributeOperators[fieldAttribute?.type || 'string']\" valueField=\"value\" displayField=\"display\" [disabled]=\"!fieldAttribute?.type\" required hideInlineError></sd-select>\n @if(!!fieldAttribute?.type && conditionLv1.operator && conditionLv1.operator !== 'NULL' && conditionLv1.operator !== 'NOT_NULL') {\n @if(fieldAttribute.type === 'string') {\n <sd-input size=\"sm\" [form]=\"form\" style=\"flex:1\" [(model)]=\"conditionLv1.value\" required hideInlineError></sd-input>\n } @else if(fieldAttribute.type === 'number') {\n <sd-input-number size=\"sm\" [form]=\"form\" style=\"flex:1\" [(model)]=\"conditionLv1.value\" required hideInlineError></sd-input-number>\n } @else if(fieldAttribute.type === 'boolean') {\n <sd-select size=\"sm\" [form]=\"form\" style=\"flex:1\" [(model)]=\"conditionLv1.value\" [items]=\"[{value: true, display: fieldAttribute.displayOnTrue}, {value: false, display: fieldAttribute.displayOnFalse}]\" valueField=\"value\" displayField=\"display\" required hideInlineError></sd-select>\n }@else if(fieldAttribute.type === 'values') {\n <sd-autocomplete size=\"sm\" [form]=\"form\" style=\"flex:1\" [(model)]=\"conditionLv1.value\" [items]=\"fieldAttribute.values\" valueField=\"value\" displayField=\"display\" required hideInlineError></sd-autocomplete>\n }\n }\n <sd-button\n class=\"ml-auto\"\n prefixIcon=\"delete\"\n [tooltip]=\"'core.component.form-builder.delete' | translate\"\n (click)=\"remove(expression.conditions, idxLv1)\"></sd-button>\n \n } @else if (conditionLv1.type === 'combinator') {\n <div class=\"c-builder\">\n <div class=\"d-flex justify-content-end\">\n <sd-button\n class=\"mr-4\"\n prefixIcon=\"delete\"\n [tooltip]=\"'core.component.form-builder.delete' | translate\"\n (click)=\"remove(expression.conditions, idxLv1)\"></sd-button>\n <sd-button prefixIcon=\"add\" [title]=\"'core.component.form-builder.condition' | translate\" size=\"sm\" color=\"primary\" (click)=\"addCondition(conditionLv1.conditions)\" type=\"fill\"></sd-button>\n </div>\n <!-- C\u1EA5p 2 -->\n @for (conditionLv2 of conditionLv1.conditions; track conditionLv2.key; let idxLv2 = $index) {\n <div class=\"d-flex align-items-end py-8\" style=\"gap: 8px;\">\n <div class=\"c-condition\">\n <!-- D\u00F2ng 1 ch\u1EC9 hi\u1EC7n WHERE -->\n @if (idxLv2 === 0) {\n <div class=\"c-where\">WHERE</div>\n }\n <!-- D\u00F2ng 2 hi\u1EC3n th\u1ECB combinator -->\n @if (idxLv2 === 1) {\n <div class=\"c-select\">\n <sd-select\n size=\"sm\"\n [(model)]=\"conditionLv1.combinator\"\n [items]=\"[\n { value: '&&', display: 'AND' },\n { value: '||', display: 'OR' }\n ]\"\n valueField=\"value\"\n displayField=\"display\"\n required\n hideInlineError=\"\"></sd-select>\n </div>\n }\n </div>\n @if (conditionLv2.type === 'condition') {\n <sd-autocomplete size=\"sm\" [form]=\"form\" style=\"width: 200px;\" [(model)]=\"conditionLv2.field\" [items]=\"attributes\" valueField=\"value\" displayField=\"display\" required hideInlineError></sd-autocomplete>\n @let fieldAttribute = attribute[conditionLv2.field!];\n <sd-select size=\"sm\" [form]=\"form\" style=\"width: 100px;\" [(model)]=\"conditionLv2.operator\" [items]=\"attributeOperators[fieldAttribute?.type || 'string']\" valueField=\"value\" displayField=\"display\" [disabled]=\"!fieldAttribute?.type\" required hideInlineError></sd-select>\n @if(!!fieldAttribute?.type && conditionLv2.operator && conditionLv2.operator !== 'NULL' && conditionLv2.operator !== 'NOT_NULL') {\n @if(fieldAttribute.type === 'string') {\n <sd-input size=\"sm\" [form]=\"form\" style=\"flex:1\" [(model)]=\"conditionLv2.value\" required hideInlineError></sd-input>\n } @else if(fieldAttribute.type === 'number') {\n <sd-input-number size=\"sm\" [form]=\"form\" style=\"flex:1\" [(model)]=\"conditionLv2.value\" required hideInlineError></sd-input-number>\n } @else if(fieldAttribute.type === 'boolean') {\n <sd-select size=\"sm\" [form]=\"form\" style=\"flex:1\" [(model)]=\"conditionLv2.value\" [items]=\"[{value: true, display: fieldAttribute.displayOnTrue}, {value: false, display: fieldAttribute.displayOnFalse}]\" valueField=\"value\" displayField=\"display\" required hideInlineError></sd-select>\n }@else if(fieldAttribute.type === 'values') {\n <sd-autocomplete size=\"sm\" [form]=\"form\" style=\"flex:1\" [(model)]=\"conditionLv2.value\" [items]=\"fieldAttribute.values\" valueField=\"value\" displayField=\"display\" required hideInlineError></sd-autocomplete>\n }\n }\n <sd-button\n class=\"ml-auto\"\n prefixIcon=\"delete\"\n [tooltip]=\"'core.component.form-builder.delete' | translate\"\n (click)=\"remove(conditionLv1.conditions, idxLv1)\"></sd-button>\n \n }\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\n <ng-container sdFooter>\n <sd-button prefixIcon=\"save\" [title]=\"'core.component.form-builder.confirm' | translate\" (click)=\"onAccept()\" color=\"primary\" type=\"fill\"></sd-button>\n </ng-container>\n</sd-modal>\n}", styles: [".c-builder{padding:8px;width:100%;border:1px solid #e6e6e6;border-radius:4px}.c-builder .c-condition{width:80px}.c-builder .c-condition .c-where{width:80px;border:1px solid #e6e6e6;background-color:#f2f2f2;color:#757575;border-radius:4px;padding:4px;text-align:center}.c-builder .c-condition .c-select{margin-left:10px;width:70px}\n"], dependencies: [{ kind: "component", type: SdInput$1, selector: "sd-input", inputs: ["autoId", "name", "appearance", "floatLabel", "size", "form", "label", "helperText", "placeholder", "type", "hideInlineError", "blurOnEnter", "required", "readonly", "disabled", "viewed", "minlength", "maxlength", "pattern", "patternErrorMessage", "validator", "inlineError", "hyperlink", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "cleared", "sdFocusForceBlur"] }, { kind: "component", type: SdInputNumber, selector: "sd-input-number", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "hideInlineError", "blurOnEnter", "required", "readonly", "disabled", "viewed", "type", "precision", "format", "min", "max", "validator", "inlineError", "hyperlink", "appearance", "floatLabel", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "cleared", "sdFocusForceBlur"] }, { kind: "component", type: SdSelect$1, selector: "sd-select", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "valueField", "displayField", "disabledField", "cacheChecksum", "limit", "hyperlink", "minWidthPanel", "hideInlineError", "required", "disabled", "viewed", "multiple", "clearable", "validator", "inlineError", "appearance", "floatLabel", "items", "model"], outputs: ["modelChange", "sdChange", "sdSelection"] }, { kind: "component", type: SdAutocomplete$1, selector: "sd-autocomplete", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "valueField", "displayField", "disabledField", "limit", "cacheChecksum", "hyperlink", "items", "hideInlineError", "addable", "required", "disabled", "viewed", "clearable", "validator", "inlineError", "appearance", "model"], outputs: ["modelChange", "sdChange", "sdSelection", "sdAdd"] }, { kind: "component", type: SdButton, selector: "sd-button", inputs: ["autoId", "type", "color", "size", "fontSet", "title", "width", "tooltip", "prefixIcon", "suffixIcon", "disabled", "loading", "block", "htmlType"], outputs: ["click"] }, { kind: "component", type: SdModal, selector: "sd-modal", inputs: ["autoId", "title", "color", "width", "height", "view", "modalClass", "lazyLoadContent", "hideClose", "disableBackdropClose"], outputs: ["sdClosed"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
6950
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdFeelExpression, isStandalone: true, selector: "sd-feel-expression", inputs: { components: "components", _expression: ["expression", "_expression"], _model: ["model", "_model"] }, outputs: { expressionChange: "expressionChange", modelChange: "modelChange", sdChange: "sdChange" }, viewQueries: [{ propertyName: "modal", first: true, predicate: SdModal, descendants: true }], ngImport: i0, template: "<a class=\"T12R\" href=\"javascript:;\" (click)=\"edit()\">{{model || '--'}}</a>\n@if (expression) {\n<sd-modal [title]=\"'core.component.form-builder.set-expression' | translate\" modalClass=\"sd-form-builder-dialog\">\n <div class=\"c-builder\">\n <div class=\"d-flex justify-content-end\">\n <sd-button prefixIcon=\"calculate\" class=\"mr-4\" [title]=\"'core.component.form-builder.condition-group' | translate\" color=\"primary\" (click)=\"addCombinator(expression.conditions)\"></sd-button>\n <sd-button prefixIcon=\"add\" [title]=\"'core.component.form-builder.condition' | translate\" color=\"primary\" (click)=\"addCondition(expression.conditions)\" type=\"fill\"></sd-button>\n </div>\n <!-- C\u1EA5p 1 -->\n @for (conditionLv1 of expression.conditions; track conditionLv1.key; let idxLv1 = $index) {\n <div class=\"d-flex align-items-end py-8\" style=\"gap: 8px;\">\n <div class=\"c-condition\">\n <!-- D\u00F2ng 1 ch\u1EC9 hi\u1EC7n WHERE -->\n @if (idxLv1 === 0) {\n <div class=\"c-where\">WHERE</div>\n }\n <!-- D\u00F2ng 2 hi\u1EC3n th\u1ECB combinator -->\n @if (idxLv1 === 1) {\n <div class=\"c-select\">\n <sd-select\n size=\"sm\"\n [(model)]=\"expression.combinator\"\n [items]=\"[\n { value: '&&', display: 'AND' },\n { value: '||', display: 'OR' }\n ]\"\n valueField=\"value\"\n displayField=\"display\"\n required\n hideInlineError=\"\"></sd-select>\n </div>\n }\n </div>\n @if (conditionLv1.type === 'condition') {\n <sd-autocomplete size=\"sm\" [form]=\"form\" style=\"width: 200px;\" [(model)]=\"conditionLv1.field\" [items]=\"attributes\" valueField=\"value\" displayField=\"display\" required hideInlineError></sd-autocomplete>\n @let fieldAttribute = attribute[conditionLv1.field!];\n <sd-select size=\"sm\" [form]=\"form\" style=\"width: 100px;\" [(model)]=\"conditionLv1.operator\" [items]=\"attributeOperators[fieldAttribute?.type || 'string']\" valueField=\"value\" displayField=\"display\" [disabled]=\"!fieldAttribute?.type\" required hideInlineError></sd-select>\n @if(!!fieldAttribute?.type && conditionLv1.operator && conditionLv1.operator !== 'NULL' && conditionLv1.operator !== 'NOT_NULL') {\n @if(fieldAttribute.type === 'string') {\n <sd-input size=\"sm\" [form]=\"form\" style=\"flex:1\" [(model)]=\"conditionLv1.value\" required hideInlineError></sd-input>\n } @else if(fieldAttribute.type === 'number') {\n <sd-input-number size=\"sm\" [form]=\"form\" style=\"flex:1\" [(model)]=\"conditionLv1.value\" required hideInlineError></sd-input-number>\n } @else if(fieldAttribute.type === 'boolean') {\n <sd-select size=\"sm\" [form]=\"form\" style=\"flex:1\" [(model)]=\"conditionLv1.value\" [items]=\"[{value: true, display: fieldAttribute.displayOnTrue}, {value: false, display: fieldAttribute.displayOnFalse}]\" valueField=\"value\" displayField=\"display\" required hideInlineError></sd-select>\n }@else if(fieldAttribute.type === 'values') {\n <sd-autocomplete size=\"sm\" [form]=\"form\" style=\"flex:1\" [(model)]=\"conditionLv1.value\" [items]=\"fieldAttribute.values\" valueField=\"value\" displayField=\"display\" required hideInlineError></sd-autocomplete>\n }\n }\n <sd-button\n class=\"ml-auto\"\n prefixIcon=\"delete\"\n [tooltip]=\"'core.component.form-builder.delete' | translate\"\n (click)=\"remove(expression.conditions, idxLv1)\"></sd-button>\n \n } @else if (conditionLv1.type === 'combinator') {\n <div class=\"c-builder\">\n <div class=\"d-flex justify-content-end\">\n <sd-button\n class=\"mr-4\"\n prefixIcon=\"delete\"\n [tooltip]=\"'core.component.form-builder.delete' | translate\"\n (click)=\"remove(expression.conditions, idxLv1)\"></sd-button>\n <sd-button prefixIcon=\"add\" [title]=\"'core.component.form-builder.condition' | translate\" size=\"sm\" color=\"primary\" (click)=\"addCondition(conditionLv1.conditions)\" type=\"fill\"></sd-button>\n </div>\n <!-- C\u1EA5p 2 -->\n @for (conditionLv2 of conditionLv1.conditions; track conditionLv2.key; let idxLv2 = $index) {\n <div class=\"d-flex align-items-end py-8\" style=\"gap: 8px;\">\n <div class=\"c-condition\">\n <!-- D\u00F2ng 1 ch\u1EC9 hi\u1EC7n WHERE -->\n @if (idxLv2 === 0) {\n <div class=\"c-where\">WHERE</div>\n }\n <!-- D\u00F2ng 2 hi\u1EC3n th\u1ECB combinator -->\n @if (idxLv2 === 1) {\n <div class=\"c-select\">\n <sd-select\n size=\"sm\"\n [(model)]=\"conditionLv1.combinator\"\n [items]=\"[\n { value: '&&', display: 'AND' },\n { value: '||', display: 'OR' }\n ]\"\n valueField=\"value\"\n displayField=\"display\"\n required\n hideInlineError=\"\"></sd-select>\n </div>\n }\n </div>\n @if (conditionLv2.type === 'condition') {\n <sd-autocomplete size=\"sm\" [form]=\"form\" style=\"width: 200px;\" [(model)]=\"conditionLv2.field\" [items]=\"attributes\" valueField=\"value\" displayField=\"display\" required hideInlineError></sd-autocomplete>\n @let fieldAttribute = attribute[conditionLv2.field!];\n <sd-select size=\"sm\" [form]=\"form\" style=\"width: 100px;\" [(model)]=\"conditionLv2.operator\" [items]=\"attributeOperators[fieldAttribute?.type || 'string']\" valueField=\"value\" displayField=\"display\" [disabled]=\"!fieldAttribute?.type\" required hideInlineError></sd-select>\n @if(!!fieldAttribute?.type && conditionLv2.operator && conditionLv2.operator !== 'NULL' && conditionLv2.operator !== 'NOT_NULL') {\n @if(fieldAttribute.type === 'string') {\n <sd-input size=\"sm\" [form]=\"form\" style=\"flex:1\" [(model)]=\"conditionLv2.value\" required hideInlineError></sd-input>\n } @else if(fieldAttribute.type === 'number') {\n <sd-input-number size=\"sm\" [form]=\"form\" style=\"flex:1\" [(model)]=\"conditionLv2.value\" required hideInlineError></sd-input-number>\n } @else if(fieldAttribute.type === 'boolean') {\n <sd-select size=\"sm\" [form]=\"form\" style=\"flex:1\" [(model)]=\"conditionLv2.value\" [items]=\"[{value: true, display: fieldAttribute.displayOnTrue}, {value: false, display: fieldAttribute.displayOnFalse}]\" valueField=\"value\" displayField=\"display\" required hideInlineError></sd-select>\n }@else if(fieldAttribute.type === 'values') {\n <sd-autocomplete size=\"sm\" [form]=\"form\" style=\"flex:1\" [(model)]=\"conditionLv2.value\" [items]=\"fieldAttribute.values\" valueField=\"value\" displayField=\"display\" required hideInlineError></sd-autocomplete>\n }\n }\n <sd-button\n class=\"ml-auto\"\n prefixIcon=\"delete\"\n [tooltip]=\"'core.component.form-builder.delete' | translate\"\n (click)=\"remove(conditionLv1.conditions, idxLv1)\"></sd-button>\n \n }\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\n <sd-button sdFooterRight prefixIcon=\"check\" [title]=\"'core.component.form-builder.confirm' | translate\" (click)=\"onAccept()\" color=\"primary\" type=\"fill\"></sd-button>\n</sd-modal>\n}\n", styles: [".c-builder{padding:8px;width:100%;border:1px solid #e6e6e6;border-radius:4px}.c-builder .c-condition{width:80px}.c-builder .c-condition .c-where{width:80px;border:1px solid #e6e6e6;background-color:#f2f2f2;color:#757575;border-radius:4px;padding:4px;text-align:center}.c-builder .c-condition .c-select{margin-left:10px;width:70px}\n"], dependencies: [{ kind: "component", type: SdInput$1, selector: "sd-input", inputs: ["autoId", "name", "appearance", "floatLabel", "size", "form", "label", "helperText", "placeholder", "type", "hideInlineError", "blurOnEnter", "required", "readonly", "disabled", "viewed", "minlength", "maxlength", "pattern", "patternErrorMessage", "validator", "inlineError", "hyperlink", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "cleared", "sdFocusForceBlur"] }, { kind: "component", type: SdInputNumber, selector: "sd-input-number", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "hideInlineError", "blurOnEnter", "required", "readonly", "disabled", "viewed", "type", "precision", "format", "min", "max", "validator", "inlineError", "hyperlink", "appearance", "floatLabel", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "cleared", "sdFocusForceBlur"] }, { kind: "component", type: SdSelect$1, selector: "sd-select", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "valueField", "displayField", "disabledField", "cacheChecksum", "limit", "hyperlink", "minWidthPanel", "hideInlineError", "required", "disabled", "viewed", "multiple", "clearable", "validator", "inlineError", "appearance", "floatLabel", "items", "model"], outputs: ["modelChange", "sdChange", "sdSelection"] }, { kind: "component", type: SdAutocomplete$1, selector: "sd-autocomplete", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "valueField", "displayField", "disabledField", "limit", "cacheChecksum", "hyperlink", "items", "hideInlineError", "addable", "required", "disabled", "viewed", "clearable", "validator", "inlineError", "appearance", "model"], outputs: ["modelChange", "sdChange", "sdSelection", "sdAdd"] }, { kind: "component", type: SdButton, selector: "sd-button", inputs: ["autoId", "type", "color", "size", "fontSet", "title", "width", "tooltip", "prefixIcon", "suffixIcon", "disabled", "loading", "block", "htmlType"], outputs: ["click"] }, { kind: "component", type: SdModal, selector: "sd-modal", inputs: ["autoId", "title", "color", "width", "height", "view", "modalClass", "lazyLoadContent", "hideClose", "disableBackdropClose"], outputs: ["sdClosed"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
6874
6951
|
}
|
|
6875
6952
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdFeelExpression, decorators: [{
|
|
6876
6953
|
type: Component,
|
|
6877
|
-
args: [{ selector: 'sd-feel-expression', changeDetection: ChangeDetectionStrategy.OnPush, imports: [SdInput$1, SdInputNumber, SdSelect$1, SdAutocomplete$1, SdButton, SdSelect$1, SdModal, TranslatePipe], template: "<a class=\"T12R\" href=\"javascript:;\" (click)=\"edit()\">{{model || '--'}}</a>\n@if (expression) {\n<sd-modal [title]=\"'core.component.form-builder.set-expression' | translate\">\n <div class=\"c-builder\">\n <div class=\"d-flex justify-content-end\">\n <sd-button prefixIcon=\"calculate\" class=\"mr-4\" [title]=\"'core.component.form-builder.condition-group' | translate\" color=\"primary\" (click)=\"addCombinator(expression.conditions)\"></sd-button>\n <sd-button prefixIcon=\"add\" [title]=\"'core.component.form-builder.condition' | translate\" color=\"primary\" (click)=\"addCondition(expression.conditions)\" type=\"fill\"></sd-button>\n </div>\n <!-- C\u1EA5p 1 -->\n @for (conditionLv1 of expression.conditions; track conditionLv1.key; let idxLv1 = $index) {\n <div class=\"d-flex align-items-end py-8\" style=\"gap: 8px;\">\n <div class=\"c-condition\">\n <!-- D\u00F2ng 1 ch\u1EC9 hi\u1EC7n WHERE -->\n @if (idxLv1 === 0) {\n <div class=\"c-where\">WHERE</div>\n }\n <!-- D\u00F2ng 2 hi\u1EC3n th\u1ECB combinator -->\n @if (idxLv1 === 1) {\n <div class=\"c-select\">\n <sd-select\n size=\"sm\"\n [(model)]=\"expression.combinator\"\n [items]=\"[\n { value: '&&', display: 'AND' },\n { value: '||', display: 'OR' }\n ]\"\n valueField=\"value\"\n displayField=\"display\"\n required\n hideInlineError=\"\"></sd-select>\n </div>\n }\n </div>\n @if (conditionLv1.type === 'condition') {\n <sd-autocomplete size=\"sm\" [form]=\"form\" style=\"width: 200px;\" [(model)]=\"conditionLv1.field\" [items]=\"attributes\" valueField=\"value\" displayField=\"display\" required hideInlineError></sd-autocomplete>\n @let fieldAttribute = attribute[conditionLv1.field!];\n <sd-select size=\"sm\" [form]=\"form\" style=\"width: 100px;\" [(model)]=\"conditionLv1.operator\" [items]=\"attributeOperators[fieldAttribute?.type || 'string']\" valueField=\"value\" displayField=\"display\" [disabled]=\"!fieldAttribute?.type\" required hideInlineError></sd-select>\n @if(!!fieldAttribute?.type && conditionLv1.operator && conditionLv1.operator !== 'NULL' && conditionLv1.operator !== 'NOT_NULL') {\n @if(fieldAttribute.type === 'string') {\n <sd-input size=\"sm\" [form]=\"form\" style=\"flex:1\" [(model)]=\"conditionLv1.value\" required hideInlineError></sd-input>\n } @else if(fieldAttribute.type === 'number') {\n <sd-input-number size=\"sm\" [form]=\"form\" style=\"flex:1\" [(model)]=\"conditionLv1.value\" required hideInlineError></sd-input-number>\n } @else if(fieldAttribute.type === 'boolean') {\n <sd-select size=\"sm\" [form]=\"form\" style=\"flex:1\" [(model)]=\"conditionLv1.value\" [items]=\"[{value: true, display: fieldAttribute.displayOnTrue}, {value: false, display: fieldAttribute.displayOnFalse}]\" valueField=\"value\" displayField=\"display\" required hideInlineError></sd-select>\n }@else if(fieldAttribute.type === 'values') {\n <sd-autocomplete size=\"sm\" [form]=\"form\" style=\"flex:1\" [(model)]=\"conditionLv1.value\" [items]=\"fieldAttribute.values\" valueField=\"value\" displayField=\"display\" required hideInlineError></sd-autocomplete>\n }\n }\n <sd-button\n class=\"ml-auto\"\n prefixIcon=\"delete\"\n [tooltip]=\"'core.component.form-builder.delete' | translate\"\n (click)=\"remove(expression.conditions, idxLv1)\"></sd-button>\n \n } @else if (conditionLv1.type === 'combinator') {\n <div class=\"c-builder\">\n <div class=\"d-flex justify-content-end\">\n <sd-button\n class=\"mr-4\"\n prefixIcon=\"delete\"\n [tooltip]=\"'core.component.form-builder.delete' | translate\"\n (click)=\"remove(expression.conditions, idxLv1)\"></sd-button>\n <sd-button prefixIcon=\"add\" [title]=\"'core.component.form-builder.condition' | translate\" size=\"sm\" color=\"primary\" (click)=\"addCondition(conditionLv1.conditions)\" type=\"fill\"></sd-button>\n </div>\n <!-- C\u1EA5p 2 -->\n @for (conditionLv2 of conditionLv1.conditions; track conditionLv2.key; let idxLv2 = $index) {\n <div class=\"d-flex align-items-end py-8\" style=\"gap: 8px;\">\n <div class=\"c-condition\">\n <!-- D\u00F2ng 1 ch\u1EC9 hi\u1EC7n WHERE -->\n @if (idxLv2 === 0) {\n <div class=\"c-where\">WHERE</div>\n }\n <!-- D\u00F2ng 2 hi\u1EC3n th\u1ECB combinator -->\n @if (idxLv2 === 1) {\n <div class=\"c-select\">\n <sd-select\n size=\"sm\"\n [(model)]=\"conditionLv1.combinator\"\n [items]=\"[\n { value: '&&', display: 'AND' },\n { value: '||', display: 'OR' }\n ]\"\n valueField=\"value\"\n displayField=\"display\"\n required\n hideInlineError=\"\"></sd-select>\n </div>\n }\n </div>\n @if (conditionLv2.type === 'condition') {\n <sd-autocomplete size=\"sm\" [form]=\"form\" style=\"width: 200px;\" [(model)]=\"conditionLv2.field\" [items]=\"attributes\" valueField=\"value\" displayField=\"display\" required hideInlineError></sd-autocomplete>\n @let fieldAttribute = attribute[conditionLv2.field!];\n <sd-select size=\"sm\" [form]=\"form\" style=\"width: 100px;\" [(model)]=\"conditionLv2.operator\" [items]=\"attributeOperators[fieldAttribute?.type || 'string']\" valueField=\"value\" displayField=\"display\" [disabled]=\"!fieldAttribute?.type\" required hideInlineError></sd-select>\n @if(!!fieldAttribute?.type && conditionLv2.operator && conditionLv2.operator !== 'NULL' && conditionLv2.operator !== 'NOT_NULL') {\n @if(fieldAttribute.type === 'string') {\n <sd-input size=\"sm\" [form]=\"form\" style=\"flex:1\" [(model)]=\"conditionLv2.value\" required hideInlineError></sd-input>\n } @else if(fieldAttribute.type === 'number') {\n <sd-input-number size=\"sm\" [form]=\"form\" style=\"flex:1\" [(model)]=\"conditionLv2.value\" required hideInlineError></sd-input-number>\n } @else if(fieldAttribute.type === 'boolean') {\n <sd-select size=\"sm\" [form]=\"form\" style=\"flex:1\" [(model)]=\"conditionLv2.value\" [items]=\"[{value: true, display: fieldAttribute.displayOnTrue}, {value: false, display: fieldAttribute.displayOnFalse}]\" valueField=\"value\" displayField=\"display\" required hideInlineError></sd-select>\n }@else if(fieldAttribute.type === 'values') {\n <sd-autocomplete size=\"sm\" [form]=\"form\" style=\"flex:1\" [(model)]=\"conditionLv2.value\" [items]=\"fieldAttribute.values\" valueField=\"value\" displayField=\"display\" required hideInlineError></sd-autocomplete>\n }\n }\n <sd-button\n class=\"ml-auto\"\n prefixIcon=\"delete\"\n [tooltip]=\"'core.component.form-builder.delete' | translate\"\n (click)=\"remove(conditionLv1.conditions, idxLv1)\"></sd-button>\n \n }\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\n <
|
|
6954
|
+
args: [{ selector: 'sd-feel-expression', changeDetection: ChangeDetectionStrategy.OnPush, imports: [SdInput$1, SdInputNumber, SdSelect$1, SdAutocomplete$1, SdButton, SdSelect$1, SdModal, TranslatePipe], template: "<a class=\"T12R\" href=\"javascript:;\" (click)=\"edit()\">{{model || '--'}}</a>\n@if (expression) {\n<sd-modal [title]=\"'core.component.form-builder.set-expression' | translate\" modalClass=\"sd-form-builder-dialog\">\n <div class=\"c-builder\">\n <div class=\"d-flex justify-content-end\">\n <sd-button prefixIcon=\"calculate\" class=\"mr-4\" [title]=\"'core.component.form-builder.condition-group' | translate\" color=\"primary\" (click)=\"addCombinator(expression.conditions)\"></sd-button>\n <sd-button prefixIcon=\"add\" [title]=\"'core.component.form-builder.condition' | translate\" color=\"primary\" (click)=\"addCondition(expression.conditions)\" type=\"fill\"></sd-button>\n </div>\n <!-- C\u1EA5p 1 -->\n @for (conditionLv1 of expression.conditions; track conditionLv1.key; let idxLv1 = $index) {\n <div class=\"d-flex align-items-end py-8\" style=\"gap: 8px;\">\n <div class=\"c-condition\">\n <!-- D\u00F2ng 1 ch\u1EC9 hi\u1EC7n WHERE -->\n @if (idxLv1 === 0) {\n <div class=\"c-where\">WHERE</div>\n }\n <!-- D\u00F2ng 2 hi\u1EC3n th\u1ECB combinator -->\n @if (idxLv1 === 1) {\n <div class=\"c-select\">\n <sd-select\n size=\"sm\"\n [(model)]=\"expression.combinator\"\n [items]=\"[\n { value: '&&', display: 'AND' },\n { value: '||', display: 'OR' }\n ]\"\n valueField=\"value\"\n displayField=\"display\"\n required\n hideInlineError=\"\"></sd-select>\n </div>\n }\n </div>\n @if (conditionLv1.type === 'condition') {\n <sd-autocomplete size=\"sm\" [form]=\"form\" style=\"width: 200px;\" [(model)]=\"conditionLv1.field\" [items]=\"attributes\" valueField=\"value\" displayField=\"display\" required hideInlineError></sd-autocomplete>\n @let fieldAttribute = attribute[conditionLv1.field!];\n <sd-select size=\"sm\" [form]=\"form\" style=\"width: 100px;\" [(model)]=\"conditionLv1.operator\" [items]=\"attributeOperators[fieldAttribute?.type || 'string']\" valueField=\"value\" displayField=\"display\" [disabled]=\"!fieldAttribute?.type\" required hideInlineError></sd-select>\n @if(!!fieldAttribute?.type && conditionLv1.operator && conditionLv1.operator !== 'NULL' && conditionLv1.operator !== 'NOT_NULL') {\n @if(fieldAttribute.type === 'string') {\n <sd-input size=\"sm\" [form]=\"form\" style=\"flex:1\" [(model)]=\"conditionLv1.value\" required hideInlineError></sd-input>\n } @else if(fieldAttribute.type === 'number') {\n <sd-input-number size=\"sm\" [form]=\"form\" style=\"flex:1\" [(model)]=\"conditionLv1.value\" required hideInlineError></sd-input-number>\n } @else if(fieldAttribute.type === 'boolean') {\n <sd-select size=\"sm\" [form]=\"form\" style=\"flex:1\" [(model)]=\"conditionLv1.value\" [items]=\"[{value: true, display: fieldAttribute.displayOnTrue}, {value: false, display: fieldAttribute.displayOnFalse}]\" valueField=\"value\" displayField=\"display\" required hideInlineError></sd-select>\n }@else if(fieldAttribute.type === 'values') {\n <sd-autocomplete size=\"sm\" [form]=\"form\" style=\"flex:1\" [(model)]=\"conditionLv1.value\" [items]=\"fieldAttribute.values\" valueField=\"value\" displayField=\"display\" required hideInlineError></sd-autocomplete>\n }\n }\n <sd-button\n class=\"ml-auto\"\n prefixIcon=\"delete\"\n [tooltip]=\"'core.component.form-builder.delete' | translate\"\n (click)=\"remove(expression.conditions, idxLv1)\"></sd-button>\n \n } @else if (conditionLv1.type === 'combinator') {\n <div class=\"c-builder\">\n <div class=\"d-flex justify-content-end\">\n <sd-button\n class=\"mr-4\"\n prefixIcon=\"delete\"\n [tooltip]=\"'core.component.form-builder.delete' | translate\"\n (click)=\"remove(expression.conditions, idxLv1)\"></sd-button>\n <sd-button prefixIcon=\"add\" [title]=\"'core.component.form-builder.condition' | translate\" size=\"sm\" color=\"primary\" (click)=\"addCondition(conditionLv1.conditions)\" type=\"fill\"></sd-button>\n </div>\n <!-- C\u1EA5p 2 -->\n @for (conditionLv2 of conditionLv1.conditions; track conditionLv2.key; let idxLv2 = $index) {\n <div class=\"d-flex align-items-end py-8\" style=\"gap: 8px;\">\n <div class=\"c-condition\">\n <!-- D\u00F2ng 1 ch\u1EC9 hi\u1EC7n WHERE -->\n @if (idxLv2 === 0) {\n <div class=\"c-where\">WHERE</div>\n }\n <!-- D\u00F2ng 2 hi\u1EC3n th\u1ECB combinator -->\n @if (idxLv2 === 1) {\n <div class=\"c-select\">\n <sd-select\n size=\"sm\"\n [(model)]=\"conditionLv1.combinator\"\n [items]=\"[\n { value: '&&', display: 'AND' },\n { value: '||', display: 'OR' }\n ]\"\n valueField=\"value\"\n displayField=\"display\"\n required\n hideInlineError=\"\"></sd-select>\n </div>\n }\n </div>\n @if (conditionLv2.type === 'condition') {\n <sd-autocomplete size=\"sm\" [form]=\"form\" style=\"width: 200px;\" [(model)]=\"conditionLv2.field\" [items]=\"attributes\" valueField=\"value\" displayField=\"display\" required hideInlineError></sd-autocomplete>\n @let fieldAttribute = attribute[conditionLv2.field!];\n <sd-select size=\"sm\" [form]=\"form\" style=\"width: 100px;\" [(model)]=\"conditionLv2.operator\" [items]=\"attributeOperators[fieldAttribute?.type || 'string']\" valueField=\"value\" displayField=\"display\" [disabled]=\"!fieldAttribute?.type\" required hideInlineError></sd-select>\n @if(!!fieldAttribute?.type && conditionLv2.operator && conditionLv2.operator !== 'NULL' && conditionLv2.operator !== 'NOT_NULL') {\n @if(fieldAttribute.type === 'string') {\n <sd-input size=\"sm\" [form]=\"form\" style=\"flex:1\" [(model)]=\"conditionLv2.value\" required hideInlineError></sd-input>\n } @else if(fieldAttribute.type === 'number') {\n <sd-input-number size=\"sm\" [form]=\"form\" style=\"flex:1\" [(model)]=\"conditionLv2.value\" required hideInlineError></sd-input-number>\n } @else if(fieldAttribute.type === 'boolean') {\n <sd-select size=\"sm\" [form]=\"form\" style=\"flex:1\" [(model)]=\"conditionLv2.value\" [items]=\"[{value: true, display: fieldAttribute.displayOnTrue}, {value: false, display: fieldAttribute.displayOnFalse}]\" valueField=\"value\" displayField=\"display\" required hideInlineError></sd-select>\n }@else if(fieldAttribute.type === 'values') {\n <sd-autocomplete size=\"sm\" [form]=\"form\" style=\"flex:1\" [(model)]=\"conditionLv2.value\" [items]=\"fieldAttribute.values\" valueField=\"value\" displayField=\"display\" required hideInlineError></sd-autocomplete>\n }\n }\n <sd-button\n class=\"ml-auto\"\n prefixIcon=\"delete\"\n [tooltip]=\"'core.component.form-builder.delete' | translate\"\n (click)=\"remove(conditionLv1.conditions, idxLv1)\"></sd-button>\n \n }\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\n <sd-button sdFooterRight prefixIcon=\"check\" [title]=\"'core.component.form-builder.confirm' | translate\" (click)=\"onAccept()\" color=\"primary\" type=\"fill\"></sd-button>\n</sd-modal>\n}\n", styles: [".c-builder{padding:8px;width:100%;border:1px solid #e6e6e6;border-radius:4px}.c-builder .c-condition{width:80px}.c-builder .c-condition .c-where{width:80px;border:1px solid #e6e6e6;background-color:#f2f2f2;color:#757575;border-radius:4px;padding:4px;text-align:center}.c-builder .c-condition .c-select{margin-left:10px;width:70px}\n"] }]
|
|
6878
6955
|
}], ctorParameters: () => [{ type: i0.ChangeDetectorRef }, { type: ExpressionFeelPipe }, { type: FormGenericService }], propDecorators: { modal: [{
|
|
6879
6956
|
type: ViewChild,
|
|
6880
6957
|
args: [SdModal]
|