@sdcorejs/angular 20.1.6 → 20.2.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 +10 -0
- package/assets/scss/core/menu.scss +18 -0
- package/assets/scss/core/utilities/_misc.scss +15 -0
- package/assets/scss/sd-core.scss +1 -0
- package/components/anchor/index.d.ts +3 -1
- package/components/api-contract-builder/index.d.ts +694 -0
- package/components/badge/index.d.ts +2 -0
- package/components/ckeditor-styles/index.d.ts +1 -1
- package/components/form-generic/index.d.ts +44 -33
- package/components/import-excel/index.d.ts +1 -0
- package/components/index.d.ts +7 -1
- package/components/mini-editor/index.d.ts +1 -1
- package/components/modal/index.d.ts +3 -2
- package/components/modal-resizable/index.d.ts +92 -0
- package/components/org-chart/index.d.ts +6 -0
- package/components/preview/index.d.ts +1 -0
- package/components/query-bar/index.d.ts +55 -17
- package/components/query-builder/index.d.ts +150 -98
- package/components/section/index.d.ts +5 -0
- package/components/tab-router/index.d.ts +19 -19
- package/components/table/index.d.ts +251 -109
- package/components/tree/index.d.ts +48 -30
- package/components/upload-file/index.d.ts +6 -3
- package/configurations/index.d.ts +1 -2
- package/fesm2022/sdcorejs-angular-components-anchor.mjs +37 -13
- package/fesm2022/sdcorejs-angular-components-anchor.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-api-contract-builder.mjs +3417 -0
- package/fesm2022/sdcorejs-angular-components-api-contract-builder.mjs.map +1 -0
- package/fesm2022/sdcorejs-angular-components-autoid-inspector.mjs +2 -2
- package/fesm2022/sdcorejs-angular-components-autoid-inspector.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-badge.mjs +27 -2
- package/fesm2022/sdcorejs-angular-components-badge.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-button.mjs +2 -2
- package/fesm2022/sdcorejs-angular-components-button.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-ckeditor-styles.mjs +1 -1
- package/fesm2022/sdcorejs-angular-components-ckeditor-styles.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-code-editor.mjs +3 -3
- package/fesm2022/sdcorejs-angular-components-code-editor.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-data-state.mjs +3 -3
- package/fesm2022/sdcorejs-angular-components-data-state.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 +324 -192
- package/fesm2022/sdcorejs-angular-components-form-generic.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-history.mjs +4 -4
- package/fesm2022/sdcorejs-angular-components-history.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-import-excel.mjs +13 -5
- package/fesm2022/sdcorejs-angular-components-import-excel.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-inform.mjs +3 -3
- package/fesm2022/sdcorejs-angular-components-inform.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-modal-resizable.mjs +164 -8
- package/fesm2022/sdcorejs-angular-components-modal-resizable.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-modal.mjs +30 -18
- package/fesm2022/sdcorejs-angular-components-modal.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-operator.mjs +2 -2
- package/fesm2022/sdcorejs-angular-components-operator.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-org-chart.mjs +60 -13
- package/fesm2022/sdcorejs-angular-components-org-chart.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-preview.mjs +27 -17
- package/fesm2022/sdcorejs-angular-components-preview.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-query-bar.mjs +192 -30
- package/fesm2022/sdcorejs-angular-components-query-bar.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-query-builder.mjs +187 -127
- package/fesm2022/sdcorejs-angular-components-query-builder.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-section.mjs +33 -2
- package/fesm2022/sdcorejs-angular-components-section.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-side-drawer.mjs +83 -24
- package/fesm2022/sdcorejs-angular-components-side-drawer.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-splitter.mjs +57 -21
- 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-router.mjs +68 -20
- package/fesm2022/sdcorejs-angular-components-tab-router.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-tab.mjs +3 -2
- package/fesm2022/sdcorejs-angular-components-tab.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-table.mjs +556 -268
- package/fesm2022/sdcorejs-angular-components-table.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-tree.mjs +80 -17
- package/fesm2022/sdcorejs-angular-components-tree.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-upload-file.mjs +48 -13
- package/fesm2022/sdcorejs-angular-components-upload-file.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components.mjs +7 -1
- package/fesm2022/sdcorejs-angular-components.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-configurations.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-directives.mjs +45 -8
- package/fesm2022/sdcorejs-angular-directives.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-autocomplete.mjs +40 -29
- package/fesm2022/sdcorejs-angular-forms-autocomplete.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-checkbox.mjs +5 -5
- package/fesm2022/sdcorejs-angular-forms-checkbox.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-chip-calendar.mjs +67 -50
- package/fesm2022/sdcorejs-angular-forms-chip-calendar.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-chip.mjs +57 -43
- package/fesm2022/sdcorejs-angular-forms-chip.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-date-range.mjs +232 -61
- package/fesm2022/sdcorejs-angular-forms-date-range.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-date.mjs +96 -24
- package/fesm2022/sdcorejs-angular-forms-date.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-datetime.mjs +131 -27
- package/fesm2022/sdcorejs-angular-forms-datetime.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-entity-picker.mjs +40 -16
- package/fesm2022/sdcorejs-angular-forms-entity-picker.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-input-color.mjs +16 -5
- package/fesm2022/sdcorejs-angular-forms-input-color.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-input-number.mjs +24 -9
- package/fesm2022/sdcorejs-angular-forms-input-number.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-input.mjs +58 -10
- package/fesm2022/sdcorejs-angular-forms-input.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-models.mjs +227 -6
- package/fesm2022/sdcorejs-angular-forms-models.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-radio.mjs +4 -4
- package/fesm2022/sdcorejs-angular-forms-radio.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-select.mjs +136 -38
- package/fesm2022/sdcorejs-angular-forms-select.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-switch.mjs +18 -3
- package/fesm2022/sdcorejs-angular-forms-switch.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-textarea.mjs +51 -62
- package/fesm2022/sdcorejs-angular-forms-textarea.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-time-range.mjs +57 -12
- package/fesm2022/sdcorejs-angular-forms-time-range.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-time.mjs +16 -5
- package/fesm2022/sdcorejs-angular-forms-time.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-tree-select.mjs +100 -9
- package/fesm2022/sdcorejs-angular-forms-tree-select.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms.mjs +91 -46
- package/fesm2022/sdcorejs-angular-forms.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-handlers.mjs +18 -3
- package/fesm2022/sdcorejs-angular-handlers.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-i18n.mjs +895 -68
- package/fesm2022/sdcorejs-angular-i18n.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-interceptors.mjs +87 -29
- package/fesm2022/sdcorejs-angular-interceptors.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-modules-auth.mjs +25 -15
- package/fesm2022/sdcorejs-angular-modules-auth.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-modules-icon.mjs +2 -2
- package/fesm2022/sdcorejs-angular-modules-icon.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-modules-keycloak.mjs +32 -3
- package/fesm2022/sdcorejs-angular-modules-keycloak.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-modules-layout.mjs +342 -111
- package/fesm2022/sdcorejs-angular-modules-layout.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-modules-permission.mjs +177 -40
- package/fesm2022/sdcorejs-angular-modules-permission.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-pipes.mjs +62 -28
- package/fesm2022/sdcorejs-angular-pipes.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-services-api.mjs +123 -13
- package/fesm2022/sdcorejs-angular-services-api.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-services-cache.mjs +53 -3
- package/fesm2022/sdcorejs-angular-services-cache.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-services-confirm.mjs +25 -73
- package/fesm2022/sdcorejs-angular-services-confirm.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-services-excel.mjs +12 -10
- package/fesm2022/sdcorejs-angular-services-excel.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-services-loading.mjs +75 -84
- package/fesm2022/sdcorejs-angular-services-loading.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-services-notify.mjs +46 -5
- package/fesm2022/sdcorejs-angular-services-notify.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-services-storage.mjs +4 -0
- package/fesm2022/sdcorejs-angular-services-storage.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-services.mjs +0 -1
- package/fesm2022/sdcorejs-angular-services.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-utilities-extensions.mjs +225 -80
- package/fesm2022/sdcorejs-angular-utilities-extensions.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-utilities-models.mjs +1 -63
- package/fesm2022/sdcorejs-angular-utilities-models.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular.mjs +17 -10
- package/fesm2022/sdcorejs-angular.mjs.map +1 -1
- package/forms/autocomplete/index.d.ts +5 -2
- package/forms/chip-calendar/index.d.ts +10 -0
- package/forms/date/index.d.ts +13 -2
- package/forms/date-range/index.d.ts +14 -2
- package/forms/datetime/index.d.ts +13 -2
- package/forms/entity-picker/index.d.ts +9 -3
- package/forms/index.d.ts +22 -13
- package/forms/input/index.d.ts +3 -0
- package/forms/input-color/index.d.ts +4 -0
- package/forms/input-number/index.d.ts +3 -0
- package/forms/models/index.d.ts +119 -4
- package/forms/select/index.d.ts +27 -4
- package/forms/switch/index.d.ts +7 -1
- package/forms/textarea/index.d.ts +5 -3
- package/forms/time-range/index.d.ts +22 -1
- package/forms/tree-select/index.d.ts +36 -5
- package/i18n/index.d.ts +175 -16
- package/index.d.ts +11 -2
- package/interceptors/index.d.ts +13 -3
- package/modules/auth/index.d.ts +1 -1
- package/modules/keycloak/index.d.ts +1 -0
- package/modules/layout/index.d.ts +94 -28
- package/modules/permission/index.d.ts +66 -4
- package/package.json +36 -25
- package/pipes/index.d.ts +27 -5
- package/services/api/index.d.ts +21 -1
- package/services/cache/index.d.ts +11 -1
- package/services/index.d.ts +0 -1
- package/services/notify/index.d.ts +1 -1
- package/utilities/extensions/index.d.ts +83 -61
- package/utilities/models/index.d.ts +2 -76
- package/components/base/index.d.ts +0 -11
- package/components/document-builder/index.d.ts +0 -252
- package/fesm2022/sdcorejs-angular-components-base.mjs +0 -23
- package/fesm2022/sdcorejs-angular-components-base.mjs.map +0 -1
- package/fesm2022/sdcorejs-angular-components-document-builder.mjs +0 -5038
- package/fesm2022/sdcorejs-angular-components-document-builder.mjs.map +0 -1
- package/fesm2022/sdcorejs-angular-models.mjs +0 -6
- package/fesm2022/sdcorejs-angular-models.mjs.map +0 -1
- package/fesm2022/sdcorejs-angular-services-docx.mjs +0 -291
- package/fesm2022/sdcorejs-angular-services-docx.mjs.map +0 -1
- package/fesm2022/sdcorejs-angular-services-license.mjs +0 -97
- package/fesm2022/sdcorejs-angular-services-license.mjs.map +0 -1
- package/models/index.d.ts +0 -2
- package/services/docx/index.d.ts +0 -27
- package/services/license/index.d.ts +0 -12
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
2
|
import { InjectionToken, inject, Injectable, Pipe, SecurityContext, ChangeDetectorRef, input, Input, ChangeDetectionStrategy, Component, ViewChild, output, ViewChildren, QueryList, viewChild, effect, untracked, viewChildren, computed, signal, ElementRef } from '@angular/core';
|
|
3
|
-
import { Utilities, StringUtilities
|
|
4
|
-
import { DateUtilities } from '@sdcorejs/angular/utilities';
|
|
3
|
+
import { Utilities, DateUtilities, StringUtilities, NumberUtilities, ArrayUtilities } from '@sdcorejs/utils/fns';
|
|
5
4
|
import * as i3 from '@angular/cdk/drag-drop';
|
|
6
5
|
import { CdkDropList, moveItemInArray, transferArrayItem, DragDropModule } from '@angular/cdk/drag-drop';
|
|
7
6
|
import * as i1 from '@angular/common';
|
|
@@ -12,24 +11,25 @@ import { MatTooltipModule } from '@angular/material/tooltip';
|
|
|
12
11
|
import { SdButton } from '@sdcorejs/angular/components/button';
|
|
13
12
|
import { SdModal } from '@sdcorejs/angular/components/modal';
|
|
14
13
|
import { SdInput as SdInput$1 } from '@sdcorejs/angular/forms/input';
|
|
15
|
-
import {
|
|
14
|
+
import { SdCodeEditor } from '@sdcorejs/angular/components/code-editor';
|
|
16
15
|
import { SdNotifyService, SdConfirmService } from '@sdcorejs/angular/services';
|
|
17
16
|
import { Subscription, Subject, filter, debounceTime, startWith, combineLatest } from 'rxjs';
|
|
18
17
|
import { SdSection } from '@sdcorejs/angular/components/section';
|
|
19
18
|
import { startWith as startWith$1, debounceTime as debounceTime$1 } from 'rxjs/operators';
|
|
20
|
-
import { DateUtilities as DateUtilities$1, StringUtilities, NumberUtilities, ArrayUtilities } from '@sdcorejs/angular/utilities/extensions';
|
|
21
19
|
import { EMPTY_STR } from '@sdcorejs/utils/constants';
|
|
22
20
|
import { SdFormatNumberPipe } from '@sdcorejs/angular/pipes';
|
|
23
21
|
import { DomSanitizer } from '@angular/platform-browser';
|
|
24
22
|
import { SdInput, SdChip, SdChipCalendar, SdSelect, SdAutocomplete, SdRadio, SdInputNumber as SdInputNumber$1 } from '@sdcorejs/angular/forms';
|
|
23
|
+
import { SdTextarea } from '@sdcorejs/angular/forms/textarea';
|
|
25
24
|
import { SdInputNumber } from '@sdcorejs/angular/forms/input-number';
|
|
26
25
|
import { SdDate } from '@sdcorejs/angular/forms/date';
|
|
27
26
|
import { SdDatetime } from '@sdcorejs/angular/forms/datetime';
|
|
28
27
|
import { Router } from '@angular/router';
|
|
28
|
+
import { SdCheckbox } from '@sdcorejs/angular/forms/checkbox';
|
|
29
29
|
import { SdUploadFile } from '@sdcorejs/angular/components/upload-file';
|
|
30
30
|
import { SdSideDrawer } from '@sdcorejs/angular/components/side-drawer';
|
|
31
|
-
import { SdTable, SdTableCellDefDirective } from '@sdcorejs/angular/components/table';
|
|
32
|
-
import {
|
|
31
|
+
import { SdTable, SdTableCellDefDirective, SdTableCommandHeaderDefDirective } from '@sdcorejs/angular/components/table';
|
|
32
|
+
import { I18nService, SdTranslatePipe } from '@sdcorejs/angular/i18n';
|
|
33
33
|
import { SdQueryBuilder } from '@sdcorejs/angular/components/query-builder';
|
|
34
34
|
import { SdSelect as SdSelect$1 } from '@sdcorejs/angular/forms/select';
|
|
35
35
|
import { SdAutocomplete as SdAutocomplete$1 } from '@sdcorejs/angular/forms/autocomplete';
|
|
@@ -38,25 +38,28 @@ import { SdLabel } from '@sdcorejs/angular/forms/label';
|
|
|
38
38
|
import * as i1$1 from '@angular/material/menu';
|
|
39
39
|
import { MatMenuModule } from '@angular/material/menu';
|
|
40
40
|
import { SdSuffixDefDirective } from '@sdcorejs/angular/forms/directives';
|
|
41
|
-
import { SdIcon } from '@sdcorejs/angular/modules/icon';
|
|
42
41
|
|
|
43
42
|
const SD_FORM_GENERIC_CONFIGURATION = new InjectionToken('sd.form-generic.configuration');
|
|
44
43
|
|
|
45
|
-
|
|
44
|
+
// why: nhóm helper dưới đây trước kia đặt tên PascalCase (`GenerateId`, `FormatComponent`, …) nên đọc
|
|
45
|
+
// hệt như class — `new GenerateId()` nhìn vẫn hợp lệ với người dùng lib, IDE cũng gợi ý chúng lẫn vào
|
|
46
|
+
// danh sách class. Đây là hàm thuần, nên chuyển sang camelCase kèm tiền tố `sd` để vừa lộ rõ là hàm,
|
|
47
|
+
// vừa nằm gọn trong namespace `sd` của package (tránh đụng tên với helper cùng tên của app consumer).
|
|
48
|
+
const sdGenerateId = () => {
|
|
46
49
|
return Utilities.randomId('id');
|
|
47
50
|
};
|
|
48
|
-
const
|
|
51
|
+
const sdGenerateKey = () => {
|
|
49
52
|
return Utilities.randomId('key');
|
|
50
53
|
};
|
|
51
|
-
const
|
|
54
|
+
const sdFormatComponent = (component) => {
|
|
52
55
|
if (component) {
|
|
53
56
|
if (!component.id) {
|
|
54
|
-
component.id =
|
|
57
|
+
component.id = sdGenerateId();
|
|
55
58
|
}
|
|
56
59
|
// Html và Group không có key và validate
|
|
57
60
|
if (component.type !== 'group') {
|
|
58
61
|
if (!component.key) {
|
|
59
|
-
component.key =
|
|
62
|
+
component.key = sdGenerateKey();
|
|
60
63
|
}
|
|
61
64
|
if (!component.validate) {
|
|
62
65
|
component.validate = {};
|
|
@@ -119,7 +122,7 @@ const SdFormatComponent = (component) => {
|
|
|
119
122
|
}
|
|
120
123
|
}
|
|
121
124
|
};
|
|
122
|
-
const
|
|
125
|
+
const SD_TABLE_COLUMN_TYPES = [
|
|
123
126
|
{
|
|
124
127
|
value: 'string',
|
|
125
128
|
display: 'Chuỗi',
|
|
@@ -153,7 +156,7 @@ const TableColumnTypes = [
|
|
|
153
156
|
display: 'Ảnh',
|
|
154
157
|
},
|
|
155
158
|
];
|
|
156
|
-
const
|
|
159
|
+
const SD_FORM_BUILDER_COMPONENTS = [
|
|
157
160
|
// ── Basic ───────────────────────────────────────────────────
|
|
158
161
|
{ type: 'textfield', symbol: 'text_fields', group: 'basic', name: 'Text field' },
|
|
159
162
|
{ type: 'textarea', symbol: 'notes', group: 'basic', name: 'Text area' },
|
|
@@ -174,13 +177,13 @@ const FormBuilderComponents = [
|
|
|
174
177
|
{ type: 'html', symbol: 'code', group: 'layout', name: 'HTML' },
|
|
175
178
|
];
|
|
176
179
|
/** Lookup: component type → Material icon + label, used by attribute panel header. */
|
|
177
|
-
const
|
|
178
|
-
const
|
|
180
|
+
const SD_COMPONENT_ICONS = SD_FORM_BUILDER_COMPONENTS.reduce((acc, c) => ({ ...acc, [c.type]: { symbol: c.symbol, label: c.name } }), {});
|
|
181
|
+
const sdGetComponentAttributes = (components) => {
|
|
179
182
|
const attributes = [];
|
|
180
183
|
if (components.length) {
|
|
181
184
|
for (const component of components) {
|
|
182
185
|
if (component.type === 'group') {
|
|
183
|
-
attributes.push(...
|
|
186
|
+
attributes.push(...sdGetComponentAttributes(component.components));
|
|
184
187
|
}
|
|
185
188
|
else if (component.type !== 'html') {
|
|
186
189
|
attributes.push({
|
|
@@ -192,7 +195,7 @@ const GetComponentAttributes = (components) => {
|
|
|
192
195
|
}
|
|
193
196
|
return attributes;
|
|
194
197
|
};
|
|
195
|
-
const
|
|
198
|
+
const sdGetVariableAttributes = (variables) => {
|
|
196
199
|
const attributes = [];
|
|
197
200
|
if (variables.length) {
|
|
198
201
|
for (const variable of variables) {
|
|
@@ -205,7 +208,7 @@ const GetVariableAttributes = (variables) => {
|
|
|
205
208
|
return attributes;
|
|
206
209
|
};
|
|
207
210
|
|
|
208
|
-
const
|
|
211
|
+
const SdFormGenericOperators = [
|
|
209
212
|
{
|
|
210
213
|
value: 'EQUAL',
|
|
211
214
|
symbol: '=',
|
|
@@ -247,13 +250,13 @@ const Operators = [
|
|
|
247
250
|
display: 'Không rỗng',
|
|
248
251
|
},
|
|
249
252
|
];
|
|
250
|
-
const
|
|
253
|
+
const SD_DAY_INFO_TYPES = [
|
|
251
254
|
{ value: 'RELATED', display: 'Ngày liên quan' },
|
|
252
255
|
{ value: 'NOW', display: 'Ngày hiện tại' },
|
|
253
256
|
{ value: 'DATETIME', display: 'Ngày cụ thể' },
|
|
254
257
|
{ value: 'ATTRIBUTE', display: 'Trường dữ liệu' },
|
|
255
258
|
];
|
|
256
|
-
const
|
|
259
|
+
const SD_DAY_INFO_PREVIOUSES = [
|
|
257
260
|
{ value: 'LASTDAY', display: 'Ngày trước', format: (n) => `${n || 1}LastDay` },
|
|
258
261
|
{ value: 'NEXTDAY', display: 'Ngày tới', format: (n) => `${n || 1}NextDay` },
|
|
259
262
|
// { value: 'LASTWEEK', display: 'Tuần trước', format: (n: number) => `${n || 1}LastWeek` },
|
|
@@ -261,7 +264,7 @@ const DayInfoPreviouses = [
|
|
|
261
264
|
// { value: 'LASTMONTH', display: 'Tháng trước', format: (n: number) => `${n || 1}LastMonth` },
|
|
262
265
|
// { value: 'NEXTMONTH', display: 'Tháng tới', format: (n: number) => `${n || 1}NextMonth` },
|
|
263
266
|
];
|
|
264
|
-
const
|
|
267
|
+
const sdGetDatetimeValue = (value) => {
|
|
265
268
|
if (!value || typeof value !== 'string') {
|
|
266
269
|
return undefined;
|
|
267
270
|
}
|
|
@@ -282,7 +285,7 @@ const GetDatetimeValue = (value) => {
|
|
|
282
285
|
}
|
|
283
286
|
return undefined;
|
|
284
287
|
};
|
|
285
|
-
const
|
|
288
|
+
const SD_ATTRIBUTE_OPERATORS = {
|
|
286
289
|
string: [
|
|
287
290
|
{
|
|
288
291
|
value: 'EQUAL',
|
|
@@ -305,8 +308,8 @@ const AttributeOperators = {
|
|
|
305
308
|
display: 'Không rỗng',
|
|
306
309
|
},
|
|
307
310
|
],
|
|
308
|
-
number:
|
|
309
|
-
datetime:
|
|
311
|
+
number: SdFormGenericOperators,
|
|
312
|
+
datetime: SdFormGenericOperators,
|
|
310
313
|
boolean: [
|
|
311
314
|
{
|
|
312
315
|
value: 'EQUAL',
|
|
@@ -343,12 +346,12 @@ const AttributeOperators = {
|
|
|
343
346
|
],
|
|
344
347
|
};
|
|
345
348
|
// Từ components map thành các attributes
|
|
346
|
-
const
|
|
349
|
+
const sdGetAttributes = (components) => {
|
|
347
350
|
const attributes = [];
|
|
348
351
|
if (components.length) {
|
|
349
352
|
for (const component of components) {
|
|
350
353
|
if (component.type === 'group') {
|
|
351
|
-
attributes.push(...
|
|
354
|
+
attributes.push(...sdGetAttributes(component.components));
|
|
352
355
|
}
|
|
353
356
|
else if (component.type === 'textfield' || component.type === 'textarea') {
|
|
354
357
|
attributes.push({
|
|
@@ -404,7 +407,7 @@ const GetAttributes = (components) => {
|
|
|
404
407
|
}
|
|
405
408
|
return attributes;
|
|
406
409
|
};
|
|
407
|
-
const
|
|
410
|
+
const sdTemplateToCondition = (template, entity) => {
|
|
408
411
|
if (!template) {
|
|
409
412
|
return undefined;
|
|
410
413
|
}
|
|
@@ -465,7 +468,7 @@ const EvaluateExpressionCondition = (condition, entity) => {
|
|
|
465
468
|
if (operator === 'NOT_NULL') {
|
|
466
469
|
return !!actualValue;
|
|
467
470
|
}
|
|
468
|
-
const expectedValue = typeof condition.value === 'string' ?
|
|
471
|
+
const expectedValue = typeof condition.value === 'string' ? sdGetDatetimeValue(condition.value) || condition.value : condition.value;
|
|
469
472
|
if (expectedValue === undefined || expectedValue === null || expectedValue === '') {
|
|
470
473
|
return undefined;
|
|
471
474
|
}
|
|
@@ -490,12 +493,12 @@ const EvaluateExpressionCondition = (condition, entity) => {
|
|
|
490
493
|
}
|
|
491
494
|
return undefined;
|
|
492
495
|
};
|
|
493
|
-
const
|
|
496
|
+
const sdEvaluateExpression = (condition, entity) => {
|
|
494
497
|
if (condition.type === 'combinator') {
|
|
495
498
|
if (!condition.conditions?.length) {
|
|
496
499
|
return undefined;
|
|
497
500
|
}
|
|
498
|
-
const results = condition.conditions.map(child =>
|
|
501
|
+
const results = condition.conditions.map(child => sdEvaluateExpression(child, entity));
|
|
499
502
|
if (results.some(result => typeof result !== 'boolean')) {
|
|
500
503
|
return undefined;
|
|
501
504
|
}
|
|
@@ -506,12 +509,12 @@ const EvaluateExpression = (condition, entity) => {
|
|
|
506
509
|
}
|
|
507
510
|
return EvaluateExpressionCondition(condition, entity);
|
|
508
511
|
};
|
|
509
|
-
const
|
|
512
|
+
const sdExpressionToJavascriptExpression = (condition) => {
|
|
510
513
|
const queries = [];
|
|
511
514
|
if (condition.type === 'combinator') {
|
|
512
515
|
if (condition.conditions?.length) {
|
|
513
516
|
for (const child of condition.conditions) {
|
|
514
|
-
queries.push(`(${
|
|
517
|
+
queries.push(`(${sdExpressionToJavascriptExpression(child)})`);
|
|
515
518
|
}
|
|
516
519
|
}
|
|
517
520
|
// Nếu nhiều hơn 1 điều kiện, bọc điều kiện bằng cặp ngoặc ()
|
|
@@ -545,7 +548,7 @@ const ConvertToJavascriptExpression = (condition) => {
|
|
|
545
548
|
return undefined;
|
|
546
549
|
}
|
|
547
550
|
// Xử lý đối với trường hợp string là 'datetime' thì value sẽ có thể là 'now', '1LastDay' ....
|
|
548
|
-
const datetimeValue =
|
|
551
|
+
const datetimeValue = sdGetDatetimeValue(value);
|
|
549
552
|
if (operator === 'EQUAL') {
|
|
550
553
|
// Nếu là chuỗi thì thêm dấu nháy, nếu không thì bỏ qua
|
|
551
554
|
if (typeof value === 'string') {
|
|
@@ -591,6 +594,17 @@ const ConvertToJavascriptExpression = (condition) => {
|
|
|
591
594
|
return undefined;
|
|
592
595
|
};
|
|
593
596
|
|
|
597
|
+
const ValidationAlerts = [
|
|
598
|
+
{
|
|
599
|
+
value: 'error',
|
|
600
|
+
display: 'Lỗi',
|
|
601
|
+
},
|
|
602
|
+
{
|
|
603
|
+
value: 'warning',
|
|
604
|
+
display: 'Cảnh báo',
|
|
605
|
+
},
|
|
606
|
+
];
|
|
607
|
+
|
|
594
608
|
class FormGenericService {
|
|
595
609
|
#selections;
|
|
596
610
|
#definitionHtmls;
|
|
@@ -802,10 +816,10 @@ class ComponentViewedPipe {
|
|
|
802
816
|
}
|
|
803
817
|
if (component.type === 'datetime') {
|
|
804
818
|
if (component.subtype === 'datetime') {
|
|
805
|
-
return DateUtilities
|
|
819
|
+
return DateUtilities.toFormat(val, 'dd/MM/yyyy HH:mm') || EMPTY_STR;
|
|
806
820
|
}
|
|
807
821
|
else {
|
|
808
|
-
return DateUtilities
|
|
822
|
+
return DateUtilities.toFormat(val, 'dd/MM/yyyy') || EMPTY_STR;
|
|
809
823
|
}
|
|
810
824
|
}
|
|
811
825
|
if (component.type === 'select' || component.type === 'radio' || component.type === 'checklist') {
|
|
@@ -874,7 +888,7 @@ class WhenExpressionPipe {
|
|
|
874
888
|
}
|
|
875
889
|
const hiddenWhenExpression = component.properties?.hiddenWhenExpression;
|
|
876
890
|
if (hiddenWhenExpression) {
|
|
877
|
-
const result =
|
|
891
|
+
const result = sdEvaluateExpression(hiddenWhenExpression, variables);
|
|
878
892
|
if (typeof result === 'boolean') {
|
|
879
893
|
return result;
|
|
880
894
|
}
|
|
@@ -882,7 +896,7 @@ class WhenExpressionPipe {
|
|
|
882
896
|
}
|
|
883
897
|
const visibleWhenExpression = component.properties?.visibleWhenExpression;
|
|
884
898
|
if (visibleWhenExpression) {
|
|
885
|
-
const result =
|
|
899
|
+
const result = sdEvaluateExpression(visibleWhenExpression, variables);
|
|
886
900
|
if (typeof result === 'boolean') {
|
|
887
901
|
return !result;
|
|
888
902
|
}
|
|
@@ -893,7 +907,7 @@ class WhenExpressionPipe {
|
|
|
893
907
|
#disabled = (component, variables) => {
|
|
894
908
|
const disabledWhenExpression = component.properties?.disabledWhenExpression;
|
|
895
909
|
if (disabledWhenExpression) {
|
|
896
|
-
const result =
|
|
910
|
+
const result = sdEvaluateExpression(disabledWhenExpression, variables);
|
|
897
911
|
if (typeof result === 'boolean') {
|
|
898
912
|
return result;
|
|
899
913
|
}
|
|
@@ -904,7 +918,7 @@ class WhenExpressionPipe {
|
|
|
904
918
|
#required = (component, variables) => {
|
|
905
919
|
const requiredWhenExpression = component.properties?.requiredWhenExpression;
|
|
906
920
|
if (requiredWhenExpression) {
|
|
907
|
-
const result =
|
|
921
|
+
const result = sdEvaluateExpression(requiredWhenExpression, variables);
|
|
908
922
|
if (typeof result === 'boolean') {
|
|
909
923
|
return result;
|
|
910
924
|
}
|
|
@@ -1057,7 +1071,7 @@ class ExpressionViewPipe {
|
|
|
1057
1071
|
results.push(`<div ${containerStyle}>${attributes.find(e => e.value === field)?.display || field}</div>`);
|
|
1058
1072
|
// Wording operator
|
|
1059
1073
|
if (operator) {
|
|
1060
|
-
results.push(`<div>${
|
|
1074
|
+
results.push(`<div>${SdFormGenericOperators.find(e => e.value === operator)?.display || operator}</div>`);
|
|
1061
1075
|
}
|
|
1062
1076
|
// Wording value
|
|
1063
1077
|
if (operator && value !== undefined) {
|
|
@@ -1701,7 +1715,7 @@ class DatetimeComponent {
|
|
|
1701
1715
|
}
|
|
1702
1716
|
};
|
|
1703
1717
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: DatetimeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1704
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: DatetimeComponent, isStandalone: true, selector: "lib-datetime", inputs: { setVariables: { classPropertyName: "setVariables", publicName: "setVariables", isSignal: true, isRequired: true, transformFunction: null }, form: { classPropertyName: "form", publicName: "form", isSignal: true, isRequired: false, transformFunction: null }, _entity: { classPropertyName: "_entity", publicName: "entity", isSignal: false, isRequired: true, transformFunction: null }, _component: { classPropertyName: "_component", publicName: "component", isSignal: false, isRequired: true, transformFunction: null }, _disabled: { classPropertyName: "_disabled", publicName: "disabled", isSignal: false, isRequired: false, transformFunction: null }, _required: { classPropertyName: "_required", publicName: "required", isSignal: false, isRequired: false, transformFunction: null }, _viewed: { classPropertyName: "_viewed", publicName: "viewed", isSignal: false, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if (component && entity) {\n @if (subtype === 'date') {\n <sd-date\n [form]=\"form()\"\n [label]=\"component.label || component.key\"\n [helperText]=\"component.helperText\"\n [name]=\"component.key\"\n [autoId]=\"component.key\"\n [(model)]=\"entity[component.key!]\"\n [required]=\"required\"\n [min]=\"component.validate?.min\"\n [max]=\"component.validate?.max\"\n [disabled]=\"disabled\"\n [viewed]=\"viewed || component.properties?.viewed\"></sd-date>\n } @else {\n <sd-datetime\n [form]=\"form()\"\n [label]=\"component.label || component.key\"\n [helperText]=\"component.helperText\"\n [name]=\"component.key\"\n [autoId]=\"component.key\"\n [(model)]=\"entity[component.key!]\"\n [required]=\"required\"\n [min]=\"component.validate?.min\"\n [max]=\"component.validate?.max\"\n [disabled]=\"disabled\"\n [viewed]=\"viewed || component.properties?.viewed\"></sd-datetime>\n }\n}\n", styles: [":host{display:contents;width:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { 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"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1718
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: DatetimeComponent, isStandalone: true, selector: "lib-datetime", inputs: { setVariables: { classPropertyName: "setVariables", publicName: "setVariables", isSignal: true, isRequired: true, transformFunction: null }, form: { classPropertyName: "form", publicName: "form", isSignal: true, isRequired: false, transformFunction: null }, _entity: { classPropertyName: "_entity", publicName: "entity", isSignal: false, isRequired: true, transformFunction: null }, _component: { classPropertyName: "_component", publicName: "component", isSignal: false, isRequired: true, transformFunction: null }, _disabled: { classPropertyName: "_disabled", publicName: "disabled", isSignal: false, isRequired: false, transformFunction: null }, _required: { classPropertyName: "_required", publicName: "required", isSignal: false, isRequired: false, transformFunction: null }, _viewed: { classPropertyName: "_viewed", publicName: "viewed", isSignal: false, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if (component && entity) {\n @if (subtype === 'date') {\n <sd-date\n [form]=\"form()\"\n [label]=\"component.label || component.key\"\n [helperText]=\"component.helperText\"\n [name]=\"component.key\"\n [autoId]=\"component.key\"\n [(model)]=\"entity[component.key!]\"\n [required]=\"required\"\n [min]=\"component.validate?.min\"\n [max]=\"component.validate?.max\"\n [disabled]=\"disabled\"\n [viewed]=\"viewed || component.properties?.viewed\"></sd-date>\n } @else {\n <sd-datetime\n [form]=\"form()\"\n [label]=\"component.label || component.key\"\n [helperText]=\"component.helperText\"\n [name]=\"component.key\"\n [autoId]=\"component.key\"\n [(model)]=\"entity[component.key!]\"\n [required]=\"required\"\n [min]=\"component.validate?.min\"\n [max]=\"component.validate?.max\"\n [disabled]=\"disabled\"\n [viewed]=\"viewed || component.properties?.viewed\"></sd-datetime>\n }\n}\n", styles: [":host{display:contents;width:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { 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", "transform", "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", "transform", "model"], outputs: ["modelChange", "sdChange", "sdFocus"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1705
1719
|
}
|
|
1706
1720
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: DatetimeComponent, decorators: [{
|
|
1707
1721
|
type: Component,
|
|
@@ -1805,7 +1819,7 @@ class SelectComponent {
|
|
|
1805
1819
|
// Ví dụ: {"a": "1", "b": "true", "c": "${key}"} => {"a": "1", "b": true, "c": "Dữ liệu tương ứng với key trong data"}
|
|
1806
1820
|
for (const key of Object.keys({ ...result })) {
|
|
1807
1821
|
// Xử ly
|
|
1808
|
-
result[key] = StringUtilities
|
|
1822
|
+
result[key] = StringUtilities.templateToDisplay(result[key], data);
|
|
1809
1823
|
if (result[key] === 'true') {
|
|
1810
1824
|
result[key] = true;
|
|
1811
1825
|
}
|
|
@@ -1873,7 +1887,7 @@ class SelectComponent {
|
|
|
1873
1887
|
// Đi qua từng key của component sẽ gán giá trị
|
|
1874
1888
|
for (const key of Object.keys(setVariables)) {
|
|
1875
1889
|
try {
|
|
1876
|
-
const value = StringUtilities
|
|
1890
|
+
const value = StringUtilities.parseExpression(setVariables[key], detail);
|
|
1877
1891
|
// Thực hiện gán giá trị
|
|
1878
1892
|
this.entity[key] = value;
|
|
1879
1893
|
// Thông báo setVariables để component (nếu đang hiển thị) biết và thực hiện re-render hay markForCheck()
|
|
@@ -1900,7 +1914,7 @@ class SelectComponent {
|
|
|
1900
1914
|
}
|
|
1901
1915
|
};
|
|
1902
1916
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SelectComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1903
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SelectComponent, isStandalone: true, selector: "lib-select", inputs: { setVariables: { classPropertyName: "setVariables", publicName: "setVariables", isSignal: true, isRequired: true, transformFunction: null }, _form: { classPropertyName: "_form", publicName: "form", isSignal: false, isRequired: true, transformFunction: null }, _entity: { classPropertyName: "_entity", publicName: "entity", isSignal: false, isRequired: true, transformFunction: null }, _component: { classPropertyName: "_component", publicName: "component", isSignal: false, isRequired: true, transformFunction: null }, _disabled: { classPropertyName: "_disabled", publicName: "disabled", isSignal: false, isRequired: false, transformFunction: null }, _required: { classPropertyName: "_required", publicName: "required", isSignal: false, isRequired: false, transformFunction: null }, _viewed: { classPropertyName: "_viewed", publicName: "viewed", isSignal: false, isRequired: false, transformFunction: null }, _validator: { classPropertyName: "_validator", publicName: "validator", isSignal: false, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if (entity && component) {\n <sd-select\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 [validator]=\"validator\"\n [disabled]=\"disabled\"\n [items]=\"items\"\n valueField=\"value\"\n displayField=\"display\"\n disabledField=\"disabled\"\n (sdChange)=\"onChanges()\"\n [multiple]=\"component.properties?.multiple\"\n [viewed]=\"viewed || component.properties?.viewed\"\n [hyperlink]=\"component.properties?.hyperlink | hyperlink: entity\"></sd-select>\n}\n", styles: [":host{display:contents;width:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { 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: "pipe", type: HyperlinkPipe, name: "hyperlink" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1917
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SelectComponent, isStandalone: true, selector: "lib-select", inputs: { setVariables: { classPropertyName: "setVariables", publicName: "setVariables", isSignal: true, isRequired: true, transformFunction: null }, _form: { classPropertyName: "_form", publicName: "form", isSignal: false, isRequired: true, transformFunction: null }, _entity: { classPropertyName: "_entity", publicName: "entity", isSignal: false, isRequired: true, transformFunction: null }, _component: { classPropertyName: "_component", publicName: "component", isSignal: false, isRequired: true, transformFunction: null }, _disabled: { classPropertyName: "_disabled", publicName: "disabled", isSignal: false, isRequired: false, transformFunction: null }, _required: { classPropertyName: "_required", publicName: "required", isSignal: false, isRequired: false, transformFunction: null }, _viewed: { classPropertyName: "_viewed", publicName: "viewed", isSignal: false, isRequired: false, transformFunction: null }, _validator: { classPropertyName: "_validator", publicName: "validator", isSignal: false, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if (entity && component) {\n <sd-select\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 [validator]=\"validator\"\n [disabled]=\"disabled\"\n [items]=\"items\"\n valueField=\"value\"\n displayField=\"display\"\n disabledField=\"disabled\"\n (sdChange)=\"onChanges()\"\n [multiple]=\"component.properties?.multiple\"\n [viewed]=\"viewed || component.properties?.viewed\"\n [hyperlink]=\"component.properties?.hyperlink | hyperlink: entity\"></sd-select>\n}\n", styles: [":host{display:contents;width:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { 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", "showSelectAll", "clearable", "validator", "inlineError", "appearance", "floatLabel", "items", "model"], outputs: ["modelChange", "sdChange", "sdSelection"] }, { kind: "pipe", type: HyperlinkPipe, name: "hyperlink" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1904
1918
|
}
|
|
1905
1919
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SelectComponent, decorators: [{
|
|
1906
1920
|
type: Component,
|
|
@@ -2064,6 +2078,86 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
2064
2078
|
args: ['viewed']
|
|
2065
2079
|
}] } });
|
|
2066
2080
|
|
|
2081
|
+
/**
|
|
2082
|
+
* Ô checkbox của form-render.
|
|
2083
|
+
*
|
|
2084
|
+
* why: form-builder từ trước đã tạo được field `type: 'checkbox'` nhưng form-render KHÔNG có nhánh
|
|
2085
|
+
* nào cho nó — field dựng trong builder chạy ra form trống, không hiện gì. Component này lấp chỗ đó
|
|
2086
|
+
* theo đúng khuôn của các control còn lại (textfield/radio…): nhận cùng bộ input, tự áp defaultValue,
|
|
2087
|
+
* và nghe `setVariables` để giá trị đặt từ biểu thức cũng phản ánh lên UI.
|
|
2088
|
+
*/
|
|
2089
|
+
class CheckboxComponent {
|
|
2090
|
+
ref = inject(ChangeDetectorRef);
|
|
2091
|
+
setVariables = input.required(...(ngDevMode ? [{ debugName: "setVariables" }] : []));
|
|
2092
|
+
form = input(new FormGroup({}), ...(ngDevMode ? [{ debugName: "form" }] : []));
|
|
2093
|
+
entity = {};
|
|
2094
|
+
set _entity(val) {
|
|
2095
|
+
if (this.entity !== val) {
|
|
2096
|
+
this.entity = val;
|
|
2097
|
+
this.#applyDefaultValue();
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
component;
|
|
2101
|
+
set _component(val) {
|
|
2102
|
+
this.component = val;
|
|
2103
|
+
this.#applyDefaultValue();
|
|
2104
|
+
}
|
|
2105
|
+
disabled = false;
|
|
2106
|
+
set _disabled(val) {
|
|
2107
|
+
this.disabled = val === '' || !!val;
|
|
2108
|
+
}
|
|
2109
|
+
viewed = false;
|
|
2110
|
+
set _viewed(val) {
|
|
2111
|
+
this.viewed = val === '' || !!val;
|
|
2112
|
+
}
|
|
2113
|
+
#subscription = new Subscription();
|
|
2114
|
+
ngOnInit() {
|
|
2115
|
+
this.#applyDefaultValue();
|
|
2116
|
+
this.#subscription.add(this.setVariables()
|
|
2117
|
+
.pipe(filter(variable => variable.key === this.component?.key))
|
|
2118
|
+
.subscribe(variable => {
|
|
2119
|
+
this.entity[variable.key] = variable.value;
|
|
2120
|
+
this.ref.markForCheck();
|
|
2121
|
+
}));
|
|
2122
|
+
}
|
|
2123
|
+
ngOnDestroy() {
|
|
2124
|
+
this.#subscription.unsubscribe();
|
|
2125
|
+
}
|
|
2126
|
+
/**
|
|
2127
|
+
* Chỉ gán khi ô còn `undefined` — người dùng bỏ tick (false) là một lựa chọn có chủ đích, ghi đè
|
|
2128
|
+
* bằng defaultValue sẽ tự bật lại checkbox sau mỗi lần re-render.
|
|
2129
|
+
*/
|
|
2130
|
+
#applyDefaultValue = () => {
|
|
2131
|
+
if (!this.component?.key || !this.entity)
|
|
2132
|
+
return;
|
|
2133
|
+
const isViewed = this.viewed || this.component.properties?.viewed;
|
|
2134
|
+
const isUndefined = this.entity[this.component.key] === undefined;
|
|
2135
|
+
const hasDefaultValue = this.component.defaultValue !== undefined && this.component.defaultValue !== null;
|
|
2136
|
+
if (!isViewed && !this.disabled && isUndefined && hasDefaultValue) {
|
|
2137
|
+
this.entity[this.component.key] = this.component.defaultValue;
|
|
2138
|
+
this.ref.markForCheck();
|
|
2139
|
+
}
|
|
2140
|
+
};
|
|
2141
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: CheckboxComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2142
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: CheckboxComponent, isStandalone: true, selector: "lib-checkbox", inputs: { setVariables: { classPropertyName: "setVariables", publicName: "setVariables", isSignal: true, isRequired: true, transformFunction: null }, form: { classPropertyName: "form", publicName: "form", isSignal: true, isRequired: false, transformFunction: null }, _entity: { classPropertyName: "_entity", publicName: "entity", isSignal: false, isRequired: true, transformFunction: null }, _component: { classPropertyName: "_component", publicName: "component", isSignal: false, isRequired: true, transformFunction: null }, _disabled: { classPropertyName: "_disabled", publicName: "disabled", isSignal: false, isRequired: false, transformFunction: null }, _viewed: { classPropertyName: "_viewed", publicName: "viewed", isSignal: false, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if (component && entity) {\n <sd-checkbox\n [form]=\"form()\"\n [label]=\"component.label || component.key\"\n [name]=\"component.key\"\n [autoId]=\"component.key\"\n [(model)]=\"entity[component.key!]\"\n [disabled]=\"disabled\"\n [viewed]=\"viewed || component.properties?.viewed\"></sd-checkbox>\n}\n", styles: [":host{display:contents;width:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: SdCheckbox, selector: "sd-checkbox", inputs: ["autoId", "name", "form", "label", "color", "disabled", "viewed", "inlineError", "model"], outputs: ["modelChange", "sdChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2143
|
+
}
|
|
2144
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: CheckboxComponent, decorators: [{
|
|
2145
|
+
type: Component,
|
|
2146
|
+
args: [{ selector: 'lib-checkbox', changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, SdCheckbox], template: "@if (component && entity) {\n <sd-checkbox\n [form]=\"form()\"\n [label]=\"component.label || component.key\"\n [name]=\"component.key\"\n [autoId]=\"component.key\"\n [(model)]=\"entity[component.key!]\"\n [disabled]=\"disabled\"\n [viewed]=\"viewed || component.properties?.viewed\"></sd-checkbox>\n}\n", styles: [":host{display:contents;width:100%}\n"] }]
|
|
2147
|
+
}], propDecorators: { setVariables: [{ type: i0.Input, args: [{ isSignal: true, alias: "setVariables", required: true }] }], form: [{ type: i0.Input, args: [{ isSignal: true, alias: "form", required: false }] }], _entity: [{
|
|
2148
|
+
type: Input,
|
|
2149
|
+
args: [{ alias: 'entity', required: true }]
|
|
2150
|
+
}], _component: [{
|
|
2151
|
+
type: Input,
|
|
2152
|
+
args: [{ alias: 'component', required: true }]
|
|
2153
|
+
}], _disabled: [{
|
|
2154
|
+
type: Input,
|
|
2155
|
+
args: ['disabled']
|
|
2156
|
+
}], _viewed: [{
|
|
2157
|
+
type: Input,
|
|
2158
|
+
args: ['viewed']
|
|
2159
|
+
}] } });
|
|
2160
|
+
|
|
2067
2161
|
class UploadComponent {
|
|
2068
2162
|
ref = inject(ChangeDetectorRef);
|
|
2069
2163
|
setVariables = input.required(...(ngDevMode ? [{ debugName: "setVariables" }] : []));
|
|
@@ -2155,6 +2249,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
2155
2249
|
class TableComponent {
|
|
2156
2250
|
ref = inject(ChangeDetectorRef);
|
|
2157
2251
|
formGenericService = inject(FormGenericService);
|
|
2252
|
+
#i18n = inject(I18nService);
|
|
2158
2253
|
uploadFiles;
|
|
2159
2254
|
table;
|
|
2160
2255
|
sideDrawer;
|
|
@@ -2189,6 +2284,11 @@ class TableComponent {
|
|
|
2189
2284
|
#subscription = new Subscription();
|
|
2190
2285
|
tableOption;
|
|
2191
2286
|
row = {};
|
|
2287
|
+
// why: phải nói rõ `SdSearch<SdFormGenericSelectionItem>` chứ không để `SdSearch` trần. Default
|
|
2288
|
+
// generic của `SdSearch` đã siết `any → unknown`, mà `SdSearch<T>` covariant theo `T`
|
|
2289
|
+
// (`(args) => Promise<T[]>`), nên `SdSearch<unknown>` không còn gán được cho `items` của cột
|
|
2290
|
+
// lazy-values (`SdSearch<Record<string, any>>`). Nguồn dữ liệu ở đây luôn là selection item của
|
|
2291
|
+
// form-generic, nên khai báo đúng kiểu là cách sửa chuẩn — không phải cast.
|
|
2192
2292
|
columnValues = {};
|
|
2193
2293
|
formRenderColumn = {};
|
|
2194
2294
|
// Lấy những fileColumns để định nghĩa cellDef tương ứng
|
|
@@ -2429,6 +2529,17 @@ class TableComponent {
|
|
|
2429
2529
|
ngOnDestroy() {
|
|
2430
2530
|
this.#subscription.unsubscribe();
|
|
2431
2531
|
}
|
|
2532
|
+
/**
|
|
2533
|
+
* Tiêu đề của side-drawer phải nói đúng tác vụ đang làm — trước đây nó cứng là "Chi tiết" cho cả
|
|
2534
|
+
* thêm mới lẫn sửa, còn dòng "Tạo mới/cập nhật" thì rơi vào thân drawer vì gắn nhầm slot.
|
|
2535
|
+
*/
|
|
2536
|
+
get detailTitle() {
|
|
2537
|
+
if (this.viewed || this.component?.properties?.viewed)
|
|
2538
|
+
return this.#i18n.t('core.component.form-builder.detail');
|
|
2539
|
+
return this.#selectedIndex === -1
|
|
2540
|
+
? this.component?.properties?.titleButtonCreate || this.#i18n.t('core.component.form-builder.add-row')
|
|
2541
|
+
: this.#i18n.t('core.component.form-builder.update-row');
|
|
2542
|
+
}
|
|
2432
2543
|
onDetail = (row) => {
|
|
2433
2544
|
if (row) {
|
|
2434
2545
|
this.#selectedIndex = this.model.indexOf(row);
|
|
@@ -2488,9 +2599,9 @@ class TableComponent {
|
|
|
2488
2599
|
}
|
|
2489
2600
|
};
|
|
2490
2601
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: TableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2491
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: TableComponent, isStandalone: true, selector: "lib-table", inputs: { setVariables: { classPropertyName: "setVariables", publicName: "setVariables", isSignal: true, isRequired: true, transformFunction: null }, form: { classPropertyName: "form", publicName: "form", isSignal: true, isRequired: false, transformFunction: null }, _component: { classPropertyName: "_component", publicName: "component", isSignal: false, isRequired: true, transformFunction: null }, _required: { classPropertyName: "_required", publicName: "required", isSignal: false, isRequired: false, transformFunction: null }, _viewed: { classPropertyName: "_viewed", publicName: "viewed", isSignal: false, isRequired: false, transformFunction: null }, _model: { classPropertyName: "_model", publicName: "model", isSignal: false, isRequired: false, transformFunction: null } }, 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", "mask", "hideInlineError", "blurOnEnter", "clearable", "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", "clearable", "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", "beforeClose", "drawerClass", "autoId"], outputs: ["sdClosed", "sdCloseError"] }, { 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:
|
|
2602
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: TableComponent, isStandalone: true, selector: "lib-table", inputs: { setVariables: { classPropertyName: "setVariables", publicName: "setVariables", isSignal: true, isRequired: true, transformFunction: null }, form: { classPropertyName: "form", publicName: "form", isSignal: true, isRequired: false, transformFunction: null }, _component: { classPropertyName: "_component", publicName: "component", isSignal: false, isRequired: true, transformFunction: null }, _required: { classPropertyName: "_required", publicName: "required", isSignal: false, isRequired: false, transformFunction: null }, _viewed: { classPropertyName: "_viewed", publicName: "viewed", isSignal: false, isRequired: false, transformFunction: null }, _model: { classPropertyName: "_model", publicName: "model", isSignal: false, isRequired: false, transformFunction: null } }, 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 <!-- why: n\u00FAt th\u00EAm d\u00F2ng n\u1EB1m \u1EDF \u00F4 header c\u1EE7a c\u1ED9t command \u2014 \u00F4 \u0111\u00F3 v\u1ED1n \u0111\u1EC3 tr\u1ED1ng, v\u00E0 \u0111\u1EB7t \u1EDF \u0111\u00E2y th\u00EC\n h\u00E0nh \u0111\u1ED9ng \"th\u00EAm\" \u0111\u1EE9ng ngay tr\u00EAn c\u1EE5m s\u1EEDa/xo\u00E1 c\u1EE7a t\u1EEBng d\u00F2ng thay v\u00EC chi\u1EBFm th\u00EAm m\u1ED9t d\u1EA3i\n ri\u00EAng d\u01B0\u1EDBi b\u1EA3ng. -->\n <!-- why: KH\u00D4NG b\u1ECDc ng-template trong @if \u2014 contentChild ch\u1EC9 qu\u00E9t n\u1ED9i dung chi\u1EBFu v\u00E0o t\u0129nh, c\u00F2n\n @if d\u1EF1ng m\u1ED9t embedded view n\u00EAn template s\u1EBD kh\u00F4ng bao gi\u1EDD \u0111\u01B0\u1EE3c nh\u00ECn th\u1EA5y v\u00E0 n\u00FAt bi\u1EBFn m\u1EA5t.\n Template lu\u00F4n t\u1ED3n t\u1EA1i; \u0111i\u1EC1u ki\u1EC7n n\u1EB1m B\u00CAN TRONG. -->\n <ng-template sdTableCommandHeaderDef>\n @if(!viewed && !component.properties?.viewed) {\n <sd-button\n prefixIcon=\"add\"\n type=\"text\"\n color=\"primary\"\n (click)=\"onDetail()\"\n [title]=\"''\"\n [tooltip]=\"component.properties?.titleButtonCreate || ('core.component.form-builder.add-new' | sdTranslate)\"></sd-button>\n }\n </ng-template>\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 <!-- why: `sdTitle` / `sdBody` KH\u00D4NG ph\u1EA3i slot c\u1EE7a sd-side-drawer (n\u00F3 ch\u1EC9 c\u00F3 sdHeaderLeft /\n sdHeaderRight / sdFooterLeft / sdFooterRight + ng-content m\u1EB7c \u0111\u1ECBnh), n\u00EAn hai kh\u1ED1i \u0111\u00F3\n tr\u01B0\u1EDBc \u0111\u00E2y r\u01A1i th\u1EB3ng v\u00E0o th\u00E2n drawer \u2014 \"T\u1EA1o m\u1EDBi/c\u1EADp nh\u1EADt\" hi\u1EC7n trong body c\u00F2n ti\u00EAu \u0111\u1EC1 k\u1EB9t\n \u1EDF \"Chi ti\u1EBFt\". D\u00F9ng [title] \u0111\u1ED9ng theo t\u00E1c v\u1EE5 v\u00E0 \u0111\u1EC3 n\u1ED9i dung \u1EDF content m\u1EB7c \u0111\u1ECBnh. -->\n <sd-side-drawer [title]=\"detailTitle\" width=\"500px\">\n <div class=\"c-table-create-update\">\n @if(!!component.columns?.length) {\n <div class=\"c-drawer-fields\">\n @for(column of component.columns;track column.key) {\n @let validate = formRenderColumn[column.key].validate;\n <div>\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 <!-- why: `.sd-side-drawer-footer` \u0111\u00E3 c\u00F3 padding 16px \u2014 m\u1ECDi consumer sdFooterRight kh\u00E1c trong\n lib \u0111\u1EC1u d\u1EF1a v\u00E0o \u0111\u00F3. `pr-16` \u1EDF \u0111\u00E2y c\u1ED9ng th\u00EAm 16px n\u1EEFa, \u0111\u1EA9y n\u00FAt L\u01B0u v\u00E0o trong; `text-right`\n th\u00EC v\u00F4 t\u00E1c d\u1EE5ng v\u00EC slot \u0111\u00E3 l\u00E0 flex justify-end. -->\n <div sdFooterRight>\n <sd-button [title]=\"'core.component.form-builder.save' | sdTranslate\" 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{padding:16px}.c-drawer-fields{display:flex;flex-direction:column;gap:12px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: SdInput, selector: "sd-input", inputs: ["autoId", "name", "appearance", "floatLabel", "size", "form", "label", "helperText", "placeholder", "type", "mask", "hideInlineError", "blurOnEnter", "clearable", "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", "clearable", "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: "directive", type: SdTableCommandHeaderDefDirective, selector: "[sdTableCommandHeaderDef]" }, { kind: "component", type: SdSideDrawer, selector: "sd-side-drawer", inputs: ["title", "width", "hideClose", "disableBackdropClose", "beforeClose", "drawerClass", "autoId"], outputs: ["sdClosed", "sdCloseError"] }, { 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", "transform", "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", "transform", "model"], outputs: ["modelChange", "sdChange", "sdFocus"] }, { kind: "pipe", type:
|
|
2492
2603
|
// Pipe cho phần viewed
|
|
2493
|
-
|
|
2604
|
+
SdTranslatePipe, name: "sdTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2494
2605
|
}
|
|
2495
2606
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: TableComponent, decorators: [{
|
|
2496
2607
|
type: Component,
|
|
@@ -2502,6 +2613,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
2502
2613
|
SdUploadFile,
|
|
2503
2614
|
SdTable,
|
|
2504
2615
|
SdTableCellDefDirective,
|
|
2616
|
+
SdTableCommandHeaderDefDirective,
|
|
2505
2617
|
SdSideDrawer,
|
|
2506
2618
|
SdButton,
|
|
2507
2619
|
SdUploadFile,
|
|
@@ -2509,8 +2621,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
2509
2621
|
SdDate,
|
|
2510
2622
|
SdDatetime,
|
|
2511
2623
|
// Pipe cho phần viewed
|
|
2512
|
-
|
|
2513
|
-
], 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
|
|
2624
|
+
SdTranslatePipe,
|
|
2625
|
+
], template: "@if(component && tableOption) {\n <div style=\"width: 100%\">\n <sd-table [option]=\"tableOption\">\n <!-- why: n\u00FAt th\u00EAm d\u00F2ng n\u1EB1m \u1EDF \u00F4 header c\u1EE7a c\u1ED9t command \u2014 \u00F4 \u0111\u00F3 v\u1ED1n \u0111\u1EC3 tr\u1ED1ng, v\u00E0 \u0111\u1EB7t \u1EDF \u0111\u00E2y th\u00EC\n h\u00E0nh \u0111\u1ED9ng \"th\u00EAm\" \u0111\u1EE9ng ngay tr\u00EAn c\u1EE5m s\u1EEDa/xo\u00E1 c\u1EE7a t\u1EEBng d\u00F2ng thay v\u00EC chi\u1EBFm th\u00EAm m\u1ED9t d\u1EA3i\n ri\u00EAng d\u01B0\u1EDBi b\u1EA3ng. -->\n <!-- why: KH\u00D4NG b\u1ECDc ng-template trong @if \u2014 contentChild ch\u1EC9 qu\u00E9t n\u1ED9i dung chi\u1EBFu v\u00E0o t\u0129nh, c\u00F2n\n @if d\u1EF1ng m\u1ED9t embedded view n\u00EAn template s\u1EBD kh\u00F4ng bao gi\u1EDD \u0111\u01B0\u1EE3c nh\u00ECn th\u1EA5y v\u00E0 n\u00FAt bi\u1EBFn m\u1EA5t.\n Template lu\u00F4n t\u1ED3n t\u1EA1i; \u0111i\u1EC1u ki\u1EC7n n\u1EB1m B\u00CAN TRONG. -->\n <ng-template sdTableCommandHeaderDef>\n @if(!viewed && !component.properties?.viewed) {\n <sd-button\n prefixIcon=\"add\"\n type=\"text\"\n color=\"primary\"\n (click)=\"onDetail()\"\n [title]=\"''\"\n [tooltip]=\"component.properties?.titleButtonCreate || ('core.component.form-builder.add-new' | sdTranslate)\"></sd-button>\n }\n </ng-template>\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 <!-- why: `sdTitle` / `sdBody` KH\u00D4NG ph\u1EA3i slot c\u1EE7a sd-side-drawer (n\u00F3 ch\u1EC9 c\u00F3 sdHeaderLeft /\n sdHeaderRight / sdFooterLeft / sdFooterRight + ng-content m\u1EB7c \u0111\u1ECBnh), n\u00EAn hai kh\u1ED1i \u0111\u00F3\n tr\u01B0\u1EDBc \u0111\u00E2y r\u01A1i th\u1EB3ng v\u00E0o th\u00E2n drawer \u2014 \"T\u1EA1o m\u1EDBi/c\u1EADp nh\u1EADt\" hi\u1EC7n trong body c\u00F2n ti\u00EAu \u0111\u1EC1 k\u1EB9t\n \u1EDF \"Chi ti\u1EBFt\". D\u00F9ng [title] \u0111\u1ED9ng theo t\u00E1c v\u1EE5 v\u00E0 \u0111\u1EC3 n\u1ED9i dung \u1EDF content m\u1EB7c \u0111\u1ECBnh. -->\n <sd-side-drawer [title]=\"detailTitle\" width=\"500px\">\n <div class=\"c-table-create-update\">\n @if(!!component.columns?.length) {\n <div class=\"c-drawer-fields\">\n @for(column of component.columns;track column.key) {\n @let validate = formRenderColumn[column.key].validate;\n <div>\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 <!-- why: `.sd-side-drawer-footer` \u0111\u00E3 c\u00F3 padding 16px \u2014 m\u1ECDi consumer sdFooterRight kh\u00E1c trong\n lib \u0111\u1EC1u d\u1EF1a v\u00E0o \u0111\u00F3. `pr-16` \u1EDF \u0111\u00E2y c\u1ED9ng th\u00EAm 16px n\u1EEFa, \u0111\u1EA9y n\u00FAt L\u01B0u v\u00E0o trong; `text-right`\n th\u00EC v\u00F4 t\u00E1c d\u1EE5ng v\u00EC slot \u0111\u00E3 l\u00E0 flex justify-end. -->\n <div sdFooterRight>\n <sd-button [title]=\"'core.component.form-builder.save' | sdTranslate\" 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{padding:16px}.c-drawer-fields{display:flex;flex-direction:column;gap:12px}\n"] }]
|
|
2514
2626
|
}], ctorParameters: () => [], propDecorators: { uploadFiles: [{
|
|
2515
2627
|
type: ViewChildren,
|
|
2516
2628
|
args: [SdUploadFile]
|
|
@@ -2597,7 +2709,7 @@ class HtmlComponent {
|
|
|
2597
2709
|
// Ví dụ: {"a": "1", "b": "true", "c": "${key}"} => {"a": "1", "b": true, "c": "Dữ liệu tương ứng với key trong data"}
|
|
2598
2710
|
for (const key of Object.keys({ ...result })) {
|
|
2599
2711
|
// Xử ly
|
|
2600
|
-
result[key] = StringUtilities
|
|
2712
|
+
result[key] = StringUtilities.templateToDisplay(result[key], data);
|
|
2601
2713
|
if (result[key] === 'true') {
|
|
2602
2714
|
result[key] = true;
|
|
2603
2715
|
}
|
|
@@ -2727,7 +2839,7 @@ class LibItemComponent {
|
|
|
2727
2839
|
await this.itemTable?.upload?.();
|
|
2728
2840
|
};
|
|
2729
2841
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: LibItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2730
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: LibItemComponent, isStandalone: true, selector: "lib-item", inputs: { setVariables: { classPropertyName: "setVariables", publicName: "setVariables", isSignal: true, isRequired: true, transformFunction: null }, form: { classPropertyName: "form", publicName: "form", isSignal: true, isRequired: false, transformFunction: null }, _entity: { classPropertyName: "_entity", publicName: "entity", isSignal: false, isRequired: true, transformFunction: null }, _component: { classPropertyName: "_component", publicName: "component", isSignal: false, isRequired: true, transformFunction: null }, _disabled: { classPropertyName: "_disabled", publicName: "disabled", isSignal: false, isRequired: false, transformFunction: null }, _required: { classPropertyName: "_required", publicName: "required", isSignal: false, isRequired: false, transformFunction: null }, _viewed: { classPropertyName: "_viewed", publicName: "viewed", isSignal: false, isRequired: false, transformFunction: null }, _validator: { classPropertyName: "_validator", publicName: "validator", isSignal: false, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "itemUpload", first: true, predicate: UploadComponent, descendants: true }, { propertyName: "itemTable", first: true, predicate: TableComponent, descendants: true }], ngImport: i0, template: "@if (entity && component) {\n @if (component.type === 'textfield') {\n <div [class]=\"col\">\n <lib-textfield\n [setVariables]=\"setVariables()\"\n [form]=\"form()\"\n [entity]=\"entity\"\n [component]=\"component\"\n [validator]=\"validator\"\n [required]=\"required\"\n [disabled]=\"disabled\"\n [viewed]=\"viewed\"></lib-textfield>\n </div>\n } @else if (component.type === 'textarea') {\n <div [class]=\"col\">\n <lib-textarea\n [setVariables]=\"setVariables()\"\n [form]=\"form()\"\n [entity]=\"entity\"\n [component]=\"component\"\n [required]=\"required\"\n [disabled]=\"disabled\"\n [viewed]=\"viewed\"></lib-textarea>\n </div>\n } @else if (component.type === 'chip-string') {\n <div [class]=\"col\">\n <lib-chip-string\n [setVariables]=\"setVariables()\"\n [form]=\"form()\"\n [entity]=\"entity\"\n [component]=\"component\"\n [required]=\"required\"\n [disabled]=\"disabled\"\n [viewed]=\"viewed\"></lib-chip-string>\n </div>\n } @else if (component.type === 'chip-calendar') {\n <div [class]=\"col\">\n <lib-chip-calendar\n [setVariables]=\"setVariables()\"\n [form]=\"form()\"\n [entity]=\"entity\"\n [component]=\"component\"\n [required]=\"required\"\n [disabled]=\"disabled\"\n [viewed]=\"viewed\"></lib-chip-calendar>\n </div>\n } @else if (component.type === 'number') {\n <div [class]=\"col\">\n <lib-number\n [setVariables]=\"setVariables()\"\n [form]=\"form()\"\n [entity]=\"entity\"\n [component]=\"component\"\n [required]=\"required\"\n [disabled]=\"disabled\"\n [viewed]=\"viewed\"></lib-number>\n </div>\n }\n @if (component.type === 'select') {\n <div [class]=\"col\">\n <lib-select\n [setVariables]=\"setVariables()\"\n [form]=\"form()\"\n [component]=\"component\"\n [entity]=\"entity\"\n [required]=\"required\"\n [disabled]=\"disabled\"\n [validator]=\"validator\"\n [viewed]=\"viewed\"></lib-select>\n </div>\n } @else if (component.type === 'radio') {\n <div [class]=\"col\">\n <lib-radio\n [setVariables]=\"setVariables()\"\n [form]=\"form()\"\n [component]=\"component\"\n [entity]=\"entity\"\n [required]=\"required\"\n [disabled]=\"disabled\"\n [viewed]=\"viewed\"></lib-radio>\n </div>\n } @else if (component.type === 'datetime') {\n <div [class]=\"col\">\n <lib-datetime\n [setVariables]=\"setVariables()\"\n [form]=\"form()\"\n [entity]=\"entity\"\n [component]=\"component\"\n [required]=\"required\"\n [disabled]=\"disabled\"\n [viewed]=\"viewed\"></lib-datetime>\n </div>\n } @else if (component.type === 'upload') {\n <div [class]=\"col\">\n <lib-upload\n [setVariables]=\"setVariables()\"\n [form]=\"form()\"\n [component]=\"component\"\n [entity]=\"entity\"\n [required]=\"required\"\n [disabled]=\"disabled\"\n [validator]=\"validator\"\n [viewed]=\"viewed\">\n </lib-upload>\n </div>\n } @else if (component.type === 'table') {\n <div [class]=\"col\">\n <lib-table [setVariables]=\"setVariables()\" [component]=\"component\" [(model)]=\"entity[component.key!]\" [viewed]=\"viewed\"></lib-table>\n </div>\n } @else if (component.type === 'html') {\n <div [class]=\"col\">\n <lib-html [setVariables]=\"setVariables()\" [form]=\"form()\" [entity]=\"entity\" [component]=\"component\" [viewed]=\"viewed\"> </lib-html>\n </div>\n } @else if (component.type === 'break') {\n <!-- Break: empty 12-col placeholder \u00E9p xu\u1ED1ng d\u00F2ng. Kh\u00F4ng render g\u00EC runtime. -->\n <div class=\"col-12\" style=\"height: 0;\"></div>\n }\n}\n", styles: [":host{display:contents}\n"], dependencies: [{ kind: "component", type: TextfieldComponent, selector: "lib-textfield", inputs: ["setVariables", "form", "entity", "component", "disabled", "required", "viewed", "validator"] }, { kind: "component", type: TextareaComponent, selector: "lib-textarea", inputs: ["setVariables", "form", "entity", "component", "disabled", "required", "viewed"] }, { kind: "component", type: ChipStringComponent, selector: "lib-chip-string", inputs: ["setVariables", "form", "entity", "component", "disabled", "required", "viewed"] }, { kind: "component", type: ChipCalendarComponent, selector: "lib-chip-calendar", inputs: ["setVariables", "form", "entity", "component", "disabled", "required", "viewed"] }, { kind: "component", type: NumberComponent, selector: "lib-number", inputs: ["setVariables", "form", "entity", "component", "disabled", "required", "viewed"] }, { kind: "component", type: DatetimeComponent, selector: "lib-datetime", inputs: ["setVariables", "form", "entity", "component", "disabled", "required", "viewed"] }, { kind: "component", type: SelectComponent, selector: "lib-select", inputs: ["setVariables", "form", "entity", "component", "disabled", "required", "viewed", "validator"] }, { kind: "component", type: RadioComponent, selector: "lib-radio", inputs: ["setVariables", "form", "entity", "component", "disabled", "required", "viewed"] }, { kind: "component", type: TableComponent, selector: "lib-table", inputs: ["setVariables", "form", "component", "required", "viewed", "model"], outputs: ["modelChange"] }, { kind: "component", type: UploadComponent, selector: "lib-upload", inputs: ["setVariables", "form", "entity", "component", "disabled", "required", "viewed", "validator"] }, { kind: "component", type: HtmlComponent, selector: "lib-html", inputs: ["setVariables", "form", "entity", "hashedValues", "component", "viewed"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2842
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: LibItemComponent, isStandalone: true, selector: "lib-item", inputs: { setVariables: { classPropertyName: "setVariables", publicName: "setVariables", isSignal: true, isRequired: true, transformFunction: null }, form: { classPropertyName: "form", publicName: "form", isSignal: true, isRequired: false, transformFunction: null }, _entity: { classPropertyName: "_entity", publicName: "entity", isSignal: false, isRequired: true, transformFunction: null }, _component: { classPropertyName: "_component", publicName: "component", isSignal: false, isRequired: true, transformFunction: null }, _disabled: { classPropertyName: "_disabled", publicName: "disabled", isSignal: false, isRequired: false, transformFunction: null }, _required: { classPropertyName: "_required", publicName: "required", isSignal: false, isRequired: false, transformFunction: null }, _viewed: { classPropertyName: "_viewed", publicName: "viewed", isSignal: false, isRequired: false, transformFunction: null }, _validator: { classPropertyName: "_validator", publicName: "validator", isSignal: false, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "itemUpload", first: true, predicate: UploadComponent, descendants: true }, { propertyName: "itemTable", first: true, predicate: TableComponent, descendants: true }], ngImport: i0, template: "@if (entity && component) {\n @if (component.type === 'textfield') {\n <div [class]=\"col\">\n <lib-textfield\n [setVariables]=\"setVariables()\"\n [form]=\"form()\"\n [entity]=\"entity\"\n [component]=\"component\"\n [validator]=\"validator\"\n [required]=\"required\"\n [disabled]=\"disabled\"\n [viewed]=\"viewed\"></lib-textfield>\n </div>\n } @else if (component.type === 'textarea') {\n <div [class]=\"col\">\n <lib-textarea\n [setVariables]=\"setVariables()\"\n [form]=\"form()\"\n [entity]=\"entity\"\n [component]=\"component\"\n [required]=\"required\"\n [disabled]=\"disabled\"\n [viewed]=\"viewed\"></lib-textarea>\n </div>\n } @else if (component.type === 'chip-string') {\n <div [class]=\"col\">\n <lib-chip-string\n [setVariables]=\"setVariables()\"\n [form]=\"form()\"\n [entity]=\"entity\"\n [component]=\"component\"\n [required]=\"required\"\n [disabled]=\"disabled\"\n [viewed]=\"viewed\"></lib-chip-string>\n </div>\n } @else if (component.type === 'chip-calendar') {\n <div [class]=\"col\">\n <lib-chip-calendar\n [setVariables]=\"setVariables()\"\n [form]=\"form()\"\n [entity]=\"entity\"\n [component]=\"component\"\n [required]=\"required\"\n [disabled]=\"disabled\"\n [viewed]=\"viewed\"></lib-chip-calendar>\n </div>\n } @else if (component.type === 'number') {\n <div [class]=\"col\">\n <lib-number\n [setVariables]=\"setVariables()\"\n [form]=\"form()\"\n [entity]=\"entity\"\n [component]=\"component\"\n [required]=\"required\"\n [disabled]=\"disabled\"\n [viewed]=\"viewed\"></lib-number>\n </div>\n }\n @if (component.type === 'select') {\n <div [class]=\"col\">\n <lib-select\n [setVariables]=\"setVariables()\"\n [form]=\"form()\"\n [component]=\"component\"\n [entity]=\"entity\"\n [required]=\"required\"\n [disabled]=\"disabled\"\n [validator]=\"validator\"\n [viewed]=\"viewed\"></lib-select>\n </div>\n } @else if (component.type === 'radio') {\n <div [class]=\"col\">\n <lib-radio\n [setVariables]=\"setVariables()\"\n [form]=\"form()\"\n [component]=\"component\"\n [entity]=\"entity\"\n [required]=\"required\"\n [disabled]=\"disabled\"\n [viewed]=\"viewed\"></lib-radio>\n </div>\n } @else if (component.type === 'checkbox') {\n <!-- why: form-builder t\u1EA1o \u0111\u01B0\u1EE3c field checkbox t\u1EEB l\u00E2u nh\u01B0ng \u1EDF \u0111\u00E2y KH\u00D4NG c\u00F3 nh\u00E1nh n\u00E0o cho n\u00F3, n\u00EAn\n field \u1EA5y render ra kho\u1EA3ng tr\u1ED1ng \u2014 kh\u00F4ng control, kh\u00F4ng nh\u00E3n, kh\u00F4ng d\u1EEF li\u1EC7u. -->\n <div [class]=\"col\">\n <!-- why: KH\u00D4NG truy\u1EC1n [required] \u2014 sd-checkbox kh\u00F4ng c\u00F3 input \u0111\u00F3, n\u00EAn binding ch\u1EC9 l\u00E0 ch\u1EEF ch\u1EBFt. -->\n <lib-checkbox\n [setVariables]=\"setVariables()\"\n [form]=\"form()\"\n [component]=\"component\"\n [entity]=\"entity\"\n [disabled]=\"disabled\"\n [viewed]=\"viewed\"></lib-checkbox>\n </div>\n } @else if (component.type === 'datetime') {\n <div [class]=\"col\">\n <lib-datetime\n [setVariables]=\"setVariables()\"\n [form]=\"form()\"\n [entity]=\"entity\"\n [component]=\"component\"\n [required]=\"required\"\n [disabled]=\"disabled\"\n [viewed]=\"viewed\"></lib-datetime>\n </div>\n } @else if (component.type === 'upload') {\n <div [class]=\"col\">\n <lib-upload\n [setVariables]=\"setVariables()\"\n [form]=\"form()\"\n [component]=\"component\"\n [entity]=\"entity\"\n [required]=\"required\"\n [disabled]=\"disabled\"\n [validator]=\"validator\"\n [viewed]=\"viewed\">\n </lib-upload>\n </div>\n } @else if (component.type === 'table') {\n <div [class]=\"col\">\n <lib-table [setVariables]=\"setVariables()\" [component]=\"component\" [(model)]=\"entity[component.key!]\" [viewed]=\"viewed\"></lib-table>\n </div>\n } @else if (component.type === 'html') {\n <div [class]=\"col\">\n <lib-html [setVariables]=\"setVariables()\" [form]=\"form()\" [entity]=\"entity\" [component]=\"component\" [viewed]=\"viewed\"> </lib-html>\n </div>\n } @else if (component.type === 'break') {\n <!-- Break: empty 12-col placeholder \u00E9p xu\u1ED1ng d\u00F2ng. Kh\u00F4ng render g\u00EC runtime. -->\n <div class=\"col-12\" style=\"height: 0;\"></div>\n }\n}\n", styles: [":host{display:contents}\n"], dependencies: [{ kind: "component", type: TextfieldComponent, selector: "lib-textfield", inputs: ["setVariables", "form", "entity", "component", "disabled", "required", "viewed", "validator"] }, { kind: "component", type: TextareaComponent, selector: "lib-textarea", inputs: ["setVariables", "form", "entity", "component", "disabled", "required", "viewed"] }, { kind: "component", type: ChipStringComponent, selector: "lib-chip-string", inputs: ["setVariables", "form", "entity", "component", "disabled", "required", "viewed"] }, { kind: "component", type: ChipCalendarComponent, selector: "lib-chip-calendar", inputs: ["setVariables", "form", "entity", "component", "disabled", "required", "viewed"] }, { kind: "component", type: NumberComponent, selector: "lib-number", inputs: ["setVariables", "form", "entity", "component", "disabled", "required", "viewed"] }, { kind: "component", type: DatetimeComponent, selector: "lib-datetime", inputs: ["setVariables", "form", "entity", "component", "disabled", "required", "viewed"] }, { kind: "component", type: SelectComponent, selector: "lib-select", inputs: ["setVariables", "form", "entity", "component", "disabled", "required", "viewed", "validator"] }, { kind: "component", type: RadioComponent, selector: "lib-radio", inputs: ["setVariables", "form", "entity", "component", "disabled", "required", "viewed"] }, { kind: "component", type: CheckboxComponent, selector: "lib-checkbox", inputs: ["setVariables", "form", "entity", "component", "disabled", "viewed"] }, { kind: "component", type: TableComponent, selector: "lib-table", inputs: ["setVariables", "form", "component", "required", "viewed", "model"], outputs: ["modelChange"] }, { kind: "component", type: UploadComponent, selector: "lib-upload", inputs: ["setVariables", "form", "entity", "component", "disabled", "required", "viewed", "validator"] }, { kind: "component", type: HtmlComponent, selector: "lib-html", inputs: ["setVariables", "form", "entity", "hashedValues", "component", "viewed"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2731
2843
|
}
|
|
2732
2844
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: LibItemComponent, decorators: [{
|
|
2733
2845
|
type: Component,
|
|
@@ -2740,10 +2852,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
2740
2852
|
DatetimeComponent,
|
|
2741
2853
|
SelectComponent,
|
|
2742
2854
|
RadioComponent,
|
|
2855
|
+
CheckboxComponent,
|
|
2743
2856
|
TableComponent,
|
|
2744
2857
|
UploadComponent,
|
|
2745
2858
|
HtmlComponent,
|
|
2746
|
-
], template: "@if (entity && component) {\n @if (component.type === 'textfield') {\n <div [class]=\"col\">\n <lib-textfield\n [setVariables]=\"setVariables()\"\n [form]=\"form()\"\n [entity]=\"entity\"\n [component]=\"component\"\n [validator]=\"validator\"\n [required]=\"required\"\n [disabled]=\"disabled\"\n [viewed]=\"viewed\"></lib-textfield>\n </div>\n } @else if (component.type === 'textarea') {\n <div [class]=\"col\">\n <lib-textarea\n [setVariables]=\"setVariables()\"\n [form]=\"form()\"\n [entity]=\"entity\"\n [component]=\"component\"\n [required]=\"required\"\n [disabled]=\"disabled\"\n [viewed]=\"viewed\"></lib-textarea>\n </div>\n } @else if (component.type === 'chip-string') {\n <div [class]=\"col\">\n <lib-chip-string\n [setVariables]=\"setVariables()\"\n [form]=\"form()\"\n [entity]=\"entity\"\n [component]=\"component\"\n [required]=\"required\"\n [disabled]=\"disabled\"\n [viewed]=\"viewed\"></lib-chip-string>\n </div>\n } @else if (component.type === 'chip-calendar') {\n <div [class]=\"col\">\n <lib-chip-calendar\n [setVariables]=\"setVariables()\"\n [form]=\"form()\"\n [entity]=\"entity\"\n [component]=\"component\"\n [required]=\"required\"\n [disabled]=\"disabled\"\n [viewed]=\"viewed\"></lib-chip-calendar>\n </div>\n } @else if (component.type === 'number') {\n <div [class]=\"col\">\n <lib-number\n [setVariables]=\"setVariables()\"\n [form]=\"form()\"\n [entity]=\"entity\"\n [component]=\"component\"\n [required]=\"required\"\n [disabled]=\"disabled\"\n [viewed]=\"viewed\"></lib-number>\n </div>\n }\n @if (component.type === 'select') {\n <div [class]=\"col\">\n <lib-select\n [setVariables]=\"setVariables()\"\n [form]=\"form()\"\n [component]=\"component\"\n [entity]=\"entity\"\n [required]=\"required\"\n [disabled]=\"disabled\"\n [validator]=\"validator\"\n [viewed]=\"viewed\"></lib-select>\n </div>\n } @else if (component.type === 'radio') {\n <div [class]=\"col\">\n <lib-radio\n [setVariables]=\"setVariables()\"\n [form]=\"form()\"\n [component]=\"component\"\n [entity]=\"entity\"\n [required]=\"required\"\n [disabled]=\"disabled\"\n [viewed]=\"viewed\"></lib-radio>\n </div>\n } @else if (component.type === 'datetime') {\n <div [class]=\"col\">\n <lib-datetime\n [setVariables]=\"setVariables()\"\n [form]=\"form()\"\n [entity]=\"entity\"\n [component]=\"component\"\n [required]=\"required\"\n [disabled]=\"disabled\"\n [viewed]=\"viewed\"></lib-datetime>\n </div>\n } @else if (component.type === 'upload') {\n <div [class]=\"col\">\n <lib-upload\n [setVariables]=\"setVariables()\"\n [form]=\"form()\"\n [component]=\"component\"\n [entity]=\"entity\"\n [required]=\"required\"\n [disabled]=\"disabled\"\n [validator]=\"validator\"\n [viewed]=\"viewed\">\n </lib-upload>\n </div>\n } @else if (component.type === 'table') {\n <div [class]=\"col\">\n <lib-table [setVariables]=\"setVariables()\" [component]=\"component\" [(model)]=\"entity[component.key!]\" [viewed]=\"viewed\"></lib-table>\n </div>\n } @else if (component.type === 'html') {\n <div [class]=\"col\">\n <lib-html [setVariables]=\"setVariables()\" [form]=\"form()\" [entity]=\"entity\" [component]=\"component\" [viewed]=\"viewed\"> </lib-html>\n </div>\n } @else if (component.type === 'break') {\n <!-- Break: empty 12-col placeholder \u00E9p xu\u1ED1ng d\u00F2ng. Kh\u00F4ng render g\u00EC runtime. -->\n <div class=\"col-12\" style=\"height: 0;\"></div>\n }\n}\n", styles: [":host{display:contents}\n"] }]
|
|
2859
|
+
], template: "@if (entity && component) {\n @if (component.type === 'textfield') {\n <div [class]=\"col\">\n <lib-textfield\n [setVariables]=\"setVariables()\"\n [form]=\"form()\"\n [entity]=\"entity\"\n [component]=\"component\"\n [validator]=\"validator\"\n [required]=\"required\"\n [disabled]=\"disabled\"\n [viewed]=\"viewed\"></lib-textfield>\n </div>\n } @else if (component.type === 'textarea') {\n <div [class]=\"col\">\n <lib-textarea\n [setVariables]=\"setVariables()\"\n [form]=\"form()\"\n [entity]=\"entity\"\n [component]=\"component\"\n [required]=\"required\"\n [disabled]=\"disabled\"\n [viewed]=\"viewed\"></lib-textarea>\n </div>\n } @else if (component.type === 'chip-string') {\n <div [class]=\"col\">\n <lib-chip-string\n [setVariables]=\"setVariables()\"\n [form]=\"form()\"\n [entity]=\"entity\"\n [component]=\"component\"\n [required]=\"required\"\n [disabled]=\"disabled\"\n [viewed]=\"viewed\"></lib-chip-string>\n </div>\n } @else if (component.type === 'chip-calendar') {\n <div [class]=\"col\">\n <lib-chip-calendar\n [setVariables]=\"setVariables()\"\n [form]=\"form()\"\n [entity]=\"entity\"\n [component]=\"component\"\n [required]=\"required\"\n [disabled]=\"disabled\"\n [viewed]=\"viewed\"></lib-chip-calendar>\n </div>\n } @else if (component.type === 'number') {\n <div [class]=\"col\">\n <lib-number\n [setVariables]=\"setVariables()\"\n [form]=\"form()\"\n [entity]=\"entity\"\n [component]=\"component\"\n [required]=\"required\"\n [disabled]=\"disabled\"\n [viewed]=\"viewed\"></lib-number>\n </div>\n }\n @if (component.type === 'select') {\n <div [class]=\"col\">\n <lib-select\n [setVariables]=\"setVariables()\"\n [form]=\"form()\"\n [component]=\"component\"\n [entity]=\"entity\"\n [required]=\"required\"\n [disabled]=\"disabled\"\n [validator]=\"validator\"\n [viewed]=\"viewed\"></lib-select>\n </div>\n } @else if (component.type === 'radio') {\n <div [class]=\"col\">\n <lib-radio\n [setVariables]=\"setVariables()\"\n [form]=\"form()\"\n [component]=\"component\"\n [entity]=\"entity\"\n [required]=\"required\"\n [disabled]=\"disabled\"\n [viewed]=\"viewed\"></lib-radio>\n </div>\n } @else if (component.type === 'checkbox') {\n <!-- why: form-builder t\u1EA1o \u0111\u01B0\u1EE3c field checkbox t\u1EEB l\u00E2u nh\u01B0ng \u1EDF \u0111\u00E2y KH\u00D4NG c\u00F3 nh\u00E1nh n\u00E0o cho n\u00F3, n\u00EAn\n field \u1EA5y render ra kho\u1EA3ng tr\u1ED1ng \u2014 kh\u00F4ng control, kh\u00F4ng nh\u00E3n, kh\u00F4ng d\u1EEF li\u1EC7u. -->\n <div [class]=\"col\">\n <!-- why: KH\u00D4NG truy\u1EC1n [required] \u2014 sd-checkbox kh\u00F4ng c\u00F3 input \u0111\u00F3, n\u00EAn binding ch\u1EC9 l\u00E0 ch\u1EEF ch\u1EBFt. -->\n <lib-checkbox\n [setVariables]=\"setVariables()\"\n [form]=\"form()\"\n [component]=\"component\"\n [entity]=\"entity\"\n [disabled]=\"disabled\"\n [viewed]=\"viewed\"></lib-checkbox>\n </div>\n } @else if (component.type === 'datetime') {\n <div [class]=\"col\">\n <lib-datetime\n [setVariables]=\"setVariables()\"\n [form]=\"form()\"\n [entity]=\"entity\"\n [component]=\"component\"\n [required]=\"required\"\n [disabled]=\"disabled\"\n [viewed]=\"viewed\"></lib-datetime>\n </div>\n } @else if (component.type === 'upload') {\n <div [class]=\"col\">\n <lib-upload\n [setVariables]=\"setVariables()\"\n [form]=\"form()\"\n [component]=\"component\"\n [entity]=\"entity\"\n [required]=\"required\"\n [disabled]=\"disabled\"\n [validator]=\"validator\"\n [viewed]=\"viewed\">\n </lib-upload>\n </div>\n } @else if (component.type === 'table') {\n <div [class]=\"col\">\n <lib-table [setVariables]=\"setVariables()\" [component]=\"component\" [(model)]=\"entity[component.key!]\" [viewed]=\"viewed\"></lib-table>\n </div>\n } @else if (component.type === 'html') {\n <div [class]=\"col\">\n <lib-html [setVariables]=\"setVariables()\" [form]=\"form()\" [entity]=\"entity\" [component]=\"component\" [viewed]=\"viewed\"> </lib-html>\n </div>\n } @else if (component.type === 'break') {\n <!-- Break: empty 12-col placeholder \u00E9p xu\u1ED1ng d\u00F2ng. Kh\u00F4ng render g\u00EC runtime. -->\n <div class=\"col-12\" style=\"height: 0;\"></div>\n }\n}\n", styles: [":host{display:contents}\n"] }]
|
|
2747
2860
|
}], propDecorators: { itemUpload: [{
|
|
2748
2861
|
type: ViewChild,
|
|
2749
2862
|
args: [UploadComponent]
|
|
@@ -2937,7 +3050,7 @@ class SdFormRender {
|
|
|
2937
3050
|
const messages = [];
|
|
2938
3051
|
for (const validation of this.configuration?.validations?.filter(e => e.alert === alert) || []) {
|
|
2939
3052
|
if (validation.type === 'expression') {
|
|
2940
|
-
const result =
|
|
3053
|
+
const result = sdEvaluateExpression(validation.expression, this.entity);
|
|
2941
3054
|
if (result) {
|
|
2942
3055
|
messages.push(validation.message);
|
|
2943
3056
|
}
|
|
@@ -2983,7 +3096,7 @@ class SdFormRender {
|
|
|
2983
3096
|
}
|
|
2984
3097
|
#cloneAndFormatComponent(component) {
|
|
2985
3098
|
const clonedComponent = JSON.parse(JSON.stringify(component));
|
|
2986
|
-
|
|
3099
|
+
sdFormatComponent(clonedComponent);
|
|
2987
3100
|
if (clonedComponent.type === 'group') {
|
|
2988
3101
|
clonedComponent.components = clonedComponent.components.map(child => this.#cloneAndFormatComponent(child));
|
|
2989
3102
|
}
|
|
@@ -3002,11 +3115,11 @@ class SdFormRender {
|
|
|
3002
3115
|
});
|
|
3003
3116
|
}
|
|
3004
3117
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdFormRender, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3005
|
-
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 });
|
|
3118
|
+
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 <!-- why: `.sd-section-body` \u0111\u1EC3 padding 0 (m\u1ED7i n\u1ED9i dung t\u1EF1 quy\u1EBFt), n\u00EAn c\u00E1c field ch\u1EC9\n c\u00F2n px-8/py-8 c\u1EE7a ri\u00EAng ch\u00FAng v\u00E0 d\u00EDnh s\u00E1t khung section. Group t\u1EF1 ch\u1EEBa l\u1EC1. -->\n <div class=\"row mx-0 c-group-body\">\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}.c-group-body{padding:8px 8px 12px}\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 });
|
|
3006
3119
|
}
|
|
3007
3120
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdFormRender, decorators: [{
|
|
3008
3121
|
type: Component,
|
|
3009
|
-
args: [{ selector: 'sd-form-render', changeDetection: ChangeDetectionStrategy.OnPush, imports: [SdSection, LibItemComponent, WhenExpressionPipe, VariableComponent], 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"] }]
|
|
3122
|
+
args: [{ selector: 'sd-form-render', changeDetection: ChangeDetectionStrategy.OnPush, imports: [SdSection, LibItemComponent, WhenExpressionPipe, VariableComponent], 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 <!-- why: `.sd-section-body` \u0111\u1EC3 padding 0 (m\u1ED7i n\u1ED9i dung t\u1EF1 quy\u1EBFt), n\u00EAn c\u00E1c field ch\u1EC9\n c\u00F2n px-8/py-8 c\u1EE7a ri\u00EAng ch\u00FAng v\u00E0 d\u00EDnh s\u00E1t khung section. Group t\u1EF1 ch\u1EEBa l\u1EC1. -->\n <div class=\"row mx-0 c-group-body\">\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}.c-group-body{padding:8px 8px 12px}\n"] }]
|
|
3010
3123
|
}], propDecorators: { formRenderItems: [{
|
|
3011
3124
|
type: ViewChildren,
|
|
3012
3125
|
args: [LibItemComponent]
|
|
@@ -3059,7 +3172,7 @@ function formAttributesToQueryFields(attributes) {
|
|
|
3059
3172
|
key: attribute.value,
|
|
3060
3173
|
label: attribute.display,
|
|
3061
3174
|
type: attribute.type === 'datetime' ? 'datetime' : attribute.type,
|
|
3062
|
-
operators:
|
|
3175
|
+
operators: SD_ATTRIBUTE_OPERATORS[attribute.type].map(operator => operator.value),
|
|
3063
3176
|
values: attribute.type === 'values' ? attribute.values : undefined,
|
|
3064
3177
|
trueLabel: attribute.type === 'boolean' ? attribute.displayOnTrue : undefined,
|
|
3065
3178
|
falseLabel: attribute.type === 'boolean' ? attribute.displayOnFalse : undefined,
|
|
@@ -3202,7 +3315,7 @@ class AttributeExpression {
|
|
|
3202
3315
|
if (components?.length) {
|
|
3203
3316
|
for (const component of components) {
|
|
3204
3317
|
if (component.type === 'group') {
|
|
3205
|
-
attributes.push(...
|
|
3318
|
+
attributes.push(...sdGetAttributes(component.components));
|
|
3206
3319
|
}
|
|
3207
3320
|
else if (component.type === 'textfield' || component.type === 'textarea') {
|
|
3208
3321
|
attributes.push({
|
|
@@ -3282,11 +3395,11 @@ class AttributeExpression {
|
|
|
3282
3395
|
return attributes;
|
|
3283
3396
|
};
|
|
3284
3397
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: AttributeExpression, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3285
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: AttributeExpression, isStandalone: true, selector: "attribute-expression", inputs: { components: { classPropertyName: "components", publicName: "components", isSignal: true, isRequired: true, transformFunction: null }, variables: { classPropertyName: "variables", publicName: "variables", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: false, isRequired: false, transformFunction: null }, _model: { classPropertyName: "_model", publicName: "model", isSignal: false, isRequired: true, transformFunction: null } }, 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' |
|
|
3398
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: AttributeExpression, isStandalone: true, selector: "attribute-expression", inputs: { components: { classPropertyName: "components", publicName: "components", isSignal: true, isRequired: true, transformFunction: null }, variables: { classPropertyName: "variables", publicName: "variables", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: false, isRequired: false, transformFunction: null }, _model: { classPropertyName: "_model", publicName: "model", isSignal: false, isRequired: true, transformFunction: null } }, 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' | sdTranslate }}</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' | sdTranslate\" 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' | sdTranslate\"\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", "beforeClose"], outputs: ["sdClosed", "sdCloseError"] }, { 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: SdTranslatePipe, name: "sdTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3286
3399
|
}
|
|
3287
3400
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: AttributeExpression, decorators: [{
|
|
3288
3401
|
type: Component,
|
|
3289
|
-
args: [{ selector: 'attribute-expression', changeDetection: ChangeDetectionStrategy.OnPush, imports: [SdButton, SdModal, SdQueryBuilder, ExpressionQueryPipe,
|
|
3402
|
+
args: [{ selector: 'attribute-expression', changeDetection: ChangeDetectionStrategy.OnPush, imports: [SdButton, SdModal, SdQueryBuilder, ExpressionQueryPipe, SdTranslatePipe], 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' | sdTranslate }}</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' | sdTranslate\" 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' | sdTranslate\"\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"] }]
|
|
3290
3403
|
}], ctorParameters: () => [], propDecorators: { modal: [{ type: i0.ViewChild, args: [i0.forwardRef(() => SdModal), { isSignal: true }] }], components: [{ type: i0.Input, args: [{ isSignal: true, alias: "components", required: true }] }], variables: [{ type: i0.Input, args: [{ isSignal: true, alias: "variables", required: true }] }], label: [{
|
|
3291
3404
|
type: Input
|
|
3292
3405
|
}], _model: [{
|
|
@@ -3386,11 +3499,11 @@ class AttributeSwitch {
|
|
|
3386
3499
|
this.modelChange.emit(!!value);
|
|
3387
3500
|
};
|
|
3388
3501
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: AttributeSwitch, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3389
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.25", type: AttributeSwitch, isStandalone: true, selector: "attribute-switch", inputs: { form: { classPropertyName: "form", publicName: "form", isSignal: true, isRequired: false, transformFunction: null }, _label: { classPropertyName: "_label", publicName: "label", isSignal: false, isRequired: false, transformFunction: null }, _model: { classPropertyName: "_model", publicName: "model", isSignal: false, isRequired: false, transformFunction: null } }, outputs: { modelChange: "modelChange" }, 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 <sd-switch size=\"sm\" [form]=\"form\" [(model)]=\"model\" (sdChange)=\"onChange($event)\" hideInlineError></sd-switch>\n</div> -->\n<sd-select\n class=\"py-8\"\n size=\"sm\"\n [label]=\"label!\"\n [form]=\"form()\"\n [(model)]=\"model\"\n valueField=\"value\"\n displayField=\"display\"\n [items]=\"[\n { value: true, display: ('core.confirm.yes-short' |
|
|
3502
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.25", type: AttributeSwitch, isStandalone: true, selector: "attribute-switch", inputs: { form: { classPropertyName: "form", publicName: "form", isSignal: true, isRequired: false, transformFunction: null }, _label: { classPropertyName: "_label", publicName: "label", isSignal: false, isRequired: false, transformFunction: null }, _model: { classPropertyName: "_model", publicName: "model", isSignal: false, isRequired: false, transformFunction: null } }, outputs: { modelChange: "modelChange" }, 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 <sd-switch size=\"sm\" [form]=\"form\" [(model)]=\"model\" (sdChange)=\"onChange($event)\" hideInlineError></sd-switch>\n</div> -->\n<sd-select\n class=\"py-8\"\n size=\"sm\"\n [label]=\"label!\"\n [form]=\"form()\"\n [(model)]=\"model\"\n valueField=\"value\"\n displayField=\"display\"\n [items]=\"[\n { value: true, display: ('core.confirm.yes-short' | sdTranslate) },\n { value: false, display: ('core.confirm.no-short' | sdTranslate) }\n ]\"\n (sdChange)=\"onChange($event)\"\n hideInlineError></sd-select>\n", dependencies: [{ 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", "showSelectAll", "clearable", "validator", "inlineError", "appearance", "floatLabel", "items", "model"], outputs: ["modelChange", "sdChange", "sdSelection"] }, { kind: "pipe", type: SdTranslatePipe, name: "sdTranslate" }] });
|
|
3390
3503
|
}
|
|
3391
3504
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: AttributeSwitch, decorators: [{
|
|
3392
3505
|
type: Component,
|
|
3393
|
-
args: [{ selector: 'attribute-switch', imports: [SdSelect$1,
|
|
3506
|
+
args: [{ selector: 'attribute-switch', imports: [SdSelect$1, SdTranslatePipe], template: "<!-- <div class=\"d-flex align-items-center py-4\">\n @if (label) {\n <div class=\"T14R\" style=\"flex: 1\">{{ label }}</div>\n }\n <sd-switch size=\"sm\" [form]=\"form\" [(model)]=\"model\" (sdChange)=\"onChange($event)\" hideInlineError></sd-switch>\n</div> -->\n<sd-select\n class=\"py-8\"\n size=\"sm\"\n [label]=\"label!\"\n [form]=\"form()\"\n [(model)]=\"model\"\n valueField=\"value\"\n displayField=\"display\"\n [items]=\"[\n { value: true, display: ('core.confirm.yes-short' | sdTranslate) },\n { value: false, display: ('core.confirm.no-short' | sdTranslate) }\n ]\"\n (sdChange)=\"onChange($event)\"\n hideInlineError></sd-select>\n" }]
|
|
3394
3507
|
}], propDecorators: { form: [{ type: i0.Input, args: [{ isSignal: true, alias: "form", required: false }] }], _label: [{
|
|
3395
3508
|
type: Input,
|
|
3396
3509
|
args: ['label']
|
|
@@ -3419,11 +3532,11 @@ class AttributeTemplate {
|
|
|
3419
3532
|
}
|
|
3420
3533
|
};
|
|
3421
3534
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: AttributeTemplate, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3422
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: AttributeTemplate, isStandalone: true, selector: "attribute-template", inputs: { _component: ["component", "_component"] }, outputs: { sdChange: "sdChange" }, ngImport: i0, template: "@if (templates.length) {\n <!-- <div class=\"d-flex align-items-center py-4\">\n <div class=\"T14M\" style=\"flex: 1\">D\u1EEF li\u1EC7u m\u1EABu</div>\n <sd-autocomplete\n style=\"width: 200px\"\n size=\"sm\"\n [(model)]=\"component.template\"\n [items]=\"templates\"\n valueField=\"value\"\n displayField=\"display\"\n (sdChange)=\"onChange($event)\"\n hideInlineError></sd-autocomplete>\n </div> -->\n <sd-autocomplete\n class=\"py-8\"\n size=\"sm\"\n [label]=\"'core.component.form-builder.template-data' |
|
|
3535
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: AttributeTemplate, isStandalone: true, selector: "attribute-template", inputs: { _component: ["component", "_component"] }, outputs: { sdChange: "sdChange" }, ngImport: i0, template: "@if (templates.length) {\n <!-- <div class=\"d-flex align-items-center py-4\">\n <div class=\"T14M\" style=\"flex: 1\">D\u1EEF li\u1EC7u m\u1EABu</div>\n <sd-autocomplete\n style=\"width: 200px\"\n size=\"sm\"\n [(model)]=\"component.template\"\n [items]=\"templates\"\n valueField=\"value\"\n displayField=\"display\"\n (sdChange)=\"onChange($event)\"\n hideInlineError></sd-autocomplete>\n </div> -->\n <sd-autocomplete\n class=\"py-8\"\n size=\"sm\"\n [label]=\"'core.component.form-builder.template-data' | sdTranslate\"\n [(model)]=\"component.template\"\n [items]=\"templates\"\n valueField=\"value\"\n displayField=\"display\"\n (sdChange)=\"onChange($event)\"\n hideInlineError></sd-autocomplete>\n}\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: "pipe", type: SdTranslatePipe, name: "sdTranslate" }] });
|
|
3423
3536
|
}
|
|
3424
3537
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: AttributeTemplate, decorators: [{
|
|
3425
3538
|
type: Component,
|
|
3426
|
-
args: [{ selector: 'attribute-template', imports: [SdAutocomplete$1,
|
|
3539
|
+
args: [{ selector: 'attribute-template', imports: [SdAutocomplete$1, SdTranslatePipe], template: "@if (templates.length) {\n <!-- <div class=\"d-flex align-items-center py-4\">\n <div class=\"T14M\" style=\"flex: 1\">D\u1EEF li\u1EC7u m\u1EABu</div>\n <sd-autocomplete\n style=\"width: 200px\"\n size=\"sm\"\n [(model)]=\"component.template\"\n [items]=\"templates\"\n valueField=\"value\"\n displayField=\"display\"\n (sdChange)=\"onChange($event)\"\n hideInlineError></sd-autocomplete>\n </div> -->\n <sd-autocomplete\n class=\"py-8\"\n size=\"sm\"\n [label]=\"'core.component.form-builder.template-data' | sdTranslate\"\n [(model)]=\"component.template\"\n [items]=\"templates\"\n valueField=\"value\"\n displayField=\"display\"\n (sdChange)=\"onChange($event)\"\n hideInlineError></sd-autocomplete>\n}\n" }]
|
|
3427
3540
|
}], propDecorators: { _component: [{
|
|
3428
3541
|
type: Input,
|
|
3429
3542
|
args: [{ alias: 'component', required: true }]
|
|
@@ -3477,7 +3590,7 @@ class TextfieldAttribute {
|
|
|
3477
3590
|
component;
|
|
3478
3591
|
set _component(component) {
|
|
3479
3592
|
this.component = component;
|
|
3480
|
-
|
|
3593
|
+
sdFormatComponent(this.component);
|
|
3481
3594
|
}
|
|
3482
3595
|
#subscription = new Subscription();
|
|
3483
3596
|
constructor() { }
|
|
@@ -3504,13 +3617,13 @@ class TextfieldAttribute {
|
|
|
3504
3617
|
...template,
|
|
3505
3618
|
id: this.component.id, // Giữ lại id để componentEmitters định danh được component nào bị thay đổi
|
|
3506
3619
|
});
|
|
3507
|
-
|
|
3620
|
+
sdFormatComponent(this.component);
|
|
3508
3621
|
this.builderService.componentEmitters.next(this.component);
|
|
3509
3622
|
this.ref.markForCheck();
|
|
3510
3623
|
}
|
|
3511
3624
|
};
|
|
3512
3625
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: TextfieldAttribute, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3513
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: TextfieldAttribute, isStandalone: true, selector: "textfield-attribute", inputs: { components: { classPropertyName: "components", publicName: "components", isSignal: true, isRequired: true, transformFunction: null }, variables: { classPropertyName: "variables", publicName: "variables", isSignal: true, isRequired: true, transformFunction: null }, _component: { classPropertyName: "_component", publicName: "component", isSignal: false, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<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' |
|
|
3626
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: TextfieldAttribute, isStandalone: true, selector: "textfield-attribute", inputs: { components: { classPropertyName: "components", publicName: "components", isSignal: true, isRequired: true, transformFunction: null }, variables: { classPropertyName: "variables", publicName: "variables", isSignal: true, isRequired: true, transformFunction: null }, _component: { classPropertyName: "_component", publicName: "component", isSignal: false, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<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' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-template [component]=\"component\" (sdChange)=\"onChangeTemplate($event)\"></attribute-template>\n <attribute-input [label]=\"'core.component.form-builder.field-key' | sdTranslate\" [(model)]=\"component.key\" [disabled]=\"!!component.template\"></attribute-input>\n <attribute-input [form]=\"form\" [label]=\"'core.component.form-builder.label' | sdTranslate\" [(model)]=\"component.label\"></attribute-input>\n <attribute-textarea [form]=\"form\" [label]=\"'core.component.form-builder.hint' | sdTranslate\" [(model)]=\"component.helperText\"></attribute-textarea>\n <attribute-input [form]=\"form\" [label]=\"'core.component.form-builder.default-value' | sdTranslate\" [(model)]=\"component.defaultValue\"></attribute-input>\n </div>\n</details>\n\n@if (!component.properties!.hidden && !component.properties!.viewed) {\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' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-switch [label]=\"'core.component.form-builder.disabled' | sdTranslate\" [(model)]=\"component.disabled\"></attribute-switch>\n <attribute-expression\n [label]=\"'core.component.form-builder.visible-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.visibleWhenExpression\"></attribute-expression>\n <attribute-expression\n [label]=\"'core.component.form-builder.hidden-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.hiddenWhenExpression\"></attribute-expression>\n @if (!component.disabled) {\n <attribute-expression\n [label]=\"'core.component.form-builder.disabled-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.disabledWhenExpression\"></attribute-expression>\n }\n </div>\n </details>\n <details class=\"fb-section\" open>\n <summary class=\"fb-section__head\">\n <span class=\"msi\">verified</span>\n <span class=\"fb-section__title\">{{ 'core.component.form-builder.validation' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-switch [form]=\"form\" [label]=\"'core.component.form-builder.required' | sdTranslate\" [(model)]=\"component.validate!.required\"></attribute-switch>\n <attribute-input-number [label]=\"'core.component.form-builder.maxlength' | sdTranslate\" [(model)]=\"component.validate!.maxlength\"></attribute-input-number>\n @if (!component.validate!.required) {\n <attribute-expression\n [label]=\"'core.component.form-builder.required-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.requiredWhenExpression\"></attribute-expression>\n }\n <attribute-input label=\"Expression\" [(model)]=\"component.validate!.pattern\"></attribute-input>\n <attribute-input label=\"Expression message\" [(model)]=\"component.validate!.patternErrorMessage\"></attribute-input>\n </div>\n </details>\n}\n", dependencies: [{ kind: "component", type: AttributeTemplate, selector: "attribute-template", inputs: ["component"], outputs: ["sdChange"] }, { kind: "component", type: AttributeExpression, selector: "attribute-expression", inputs: ["components", "variables", "label", "model"], outputs: ["modelChange", "sdChange"] }, { kind: "component", type: AttributeInput, selector: "attribute-input", inputs: ["form", "label", "disabled", "model"], outputs: ["modelChange", "sdChange"] }, { kind: "component", type: AttributeInputNumber, selector: "attribute-input-number", inputs: ["label", "disabled", "model"], outputs: ["modelChange"] }, { kind: "component", type: AttributeSwitch, selector: "attribute-switch", inputs: ["form", "label", "model"], outputs: ["modelChange"] }, { kind: "component", type: AttributeTextarea, selector: "attribute-textarea", inputs: ["form", "label", "disabled", "model"], outputs: ["modelChange", "sdChange"] }, { kind: "pipe", type: SdTranslatePipe, name: "sdTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3514
3627
|
}
|
|
3515
3628
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: TextfieldAttribute, decorators: [{
|
|
3516
3629
|
type: Component,
|
|
@@ -3521,8 +3634,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
3521
3634
|
AttributeInputNumber,
|
|
3522
3635
|
AttributeSwitch,
|
|
3523
3636
|
AttributeTextarea,
|
|
3524
|
-
|
|
3525
|
-
], template: "<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' |
|
|
3637
|
+
SdTranslatePipe,
|
|
3638
|
+
], template: "<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' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-template [component]=\"component\" (sdChange)=\"onChangeTemplate($event)\"></attribute-template>\n <attribute-input [label]=\"'core.component.form-builder.field-key' | sdTranslate\" [(model)]=\"component.key\" [disabled]=\"!!component.template\"></attribute-input>\n <attribute-input [form]=\"form\" [label]=\"'core.component.form-builder.label' | sdTranslate\" [(model)]=\"component.label\"></attribute-input>\n <attribute-textarea [form]=\"form\" [label]=\"'core.component.form-builder.hint' | sdTranslate\" [(model)]=\"component.helperText\"></attribute-textarea>\n <attribute-input [form]=\"form\" [label]=\"'core.component.form-builder.default-value' | sdTranslate\" [(model)]=\"component.defaultValue\"></attribute-input>\n </div>\n</details>\n\n@if (!component.properties!.hidden && !component.properties!.viewed) {\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' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-switch [label]=\"'core.component.form-builder.disabled' | sdTranslate\" [(model)]=\"component.disabled\"></attribute-switch>\n <attribute-expression\n [label]=\"'core.component.form-builder.visible-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.visibleWhenExpression\"></attribute-expression>\n <attribute-expression\n [label]=\"'core.component.form-builder.hidden-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.hiddenWhenExpression\"></attribute-expression>\n @if (!component.disabled) {\n <attribute-expression\n [label]=\"'core.component.form-builder.disabled-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.disabledWhenExpression\"></attribute-expression>\n }\n </div>\n </details>\n <details class=\"fb-section\" open>\n <summary class=\"fb-section__head\">\n <span class=\"msi\">verified</span>\n <span class=\"fb-section__title\">{{ 'core.component.form-builder.validation' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-switch [form]=\"form\" [label]=\"'core.component.form-builder.required' | sdTranslate\" [(model)]=\"component.validate!.required\"></attribute-switch>\n <attribute-input-number [label]=\"'core.component.form-builder.maxlength' | sdTranslate\" [(model)]=\"component.validate!.maxlength\"></attribute-input-number>\n @if (!component.validate!.required) {\n <attribute-expression\n [label]=\"'core.component.form-builder.required-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.requiredWhenExpression\"></attribute-expression>\n }\n <attribute-input label=\"Expression\" [(model)]=\"component.validate!.pattern\"></attribute-input>\n <attribute-input label=\"Expression message\" [(model)]=\"component.validate!.patternErrorMessage\"></attribute-input>\n </div>\n </details>\n}\n" }]
|
|
3526
3639
|
}], ctorParameters: () => [], propDecorators: { components: [{ type: i0.Input, args: [{ isSignal: true, alias: "components", required: true }] }], variables: [{ type: i0.Input, args: [{ isSignal: true, alias: "variables", required: true }] }], _component: [{
|
|
3527
3640
|
type: Input,
|
|
3528
3641
|
args: [{ alias: 'component', required: true }]
|
|
@@ -3535,7 +3648,7 @@ class TextFieldControl {
|
|
|
3535
3648
|
set _component(component) {
|
|
3536
3649
|
if (this.component !== component) {
|
|
3537
3650
|
this.component = component;
|
|
3538
|
-
|
|
3651
|
+
sdFormatComponent(this.component);
|
|
3539
3652
|
}
|
|
3540
3653
|
}
|
|
3541
3654
|
#subscription = new Subscription();
|
|
@@ -3572,7 +3685,7 @@ class TextareaAttribute {
|
|
|
3572
3685
|
component;
|
|
3573
3686
|
set _component(component) {
|
|
3574
3687
|
this.component = component;
|
|
3575
|
-
|
|
3688
|
+
sdFormatComponent(this.component);
|
|
3576
3689
|
}
|
|
3577
3690
|
#subscription = new Subscription();
|
|
3578
3691
|
constructor() { }
|
|
@@ -3599,13 +3712,13 @@ class TextareaAttribute {
|
|
|
3599
3712
|
...template,
|
|
3600
3713
|
id: this.component.id, // Giữ lại id để componentEmitters định danh được component nào bị thay đổi
|
|
3601
3714
|
});
|
|
3602
|
-
|
|
3715
|
+
sdFormatComponent(this.component);
|
|
3603
3716
|
this.builderService.componentEmitters.next(this.component);
|
|
3604
3717
|
this.ref.markForCheck();
|
|
3605
3718
|
}
|
|
3606
3719
|
};
|
|
3607
3720
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: TextareaAttribute, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3608
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: TextareaAttribute, isStandalone: true, selector: "textarea-attribute", inputs: { components: { classPropertyName: "components", publicName: "components", isSignal: true, isRequired: true, transformFunction: null }, variables: { classPropertyName: "variables", publicName: "variables", isSignal: true, isRequired: true, transformFunction: null }, _component: { classPropertyName: "_component", publicName: "component", isSignal: false, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<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' |
|
|
3721
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: TextareaAttribute, isStandalone: true, selector: "textarea-attribute", inputs: { components: { classPropertyName: "components", publicName: "components", isSignal: true, isRequired: true, transformFunction: null }, variables: { classPropertyName: "variables", publicName: "variables", isSignal: true, isRequired: true, transformFunction: null }, _component: { classPropertyName: "_component", publicName: "component", isSignal: false, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<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' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-template [component]=\"component\" (sdChange)=\"onChangeTemplate($event)\"></attribute-template>\n <attribute-input [label]=\"'core.component.form-builder.field-key' | sdTranslate\" [(model)]=\"component.key\" [disabled]=\"!!component.template\"></attribute-input>\n <attribute-input [form]=\"form\" [label]=\"'core.component.form-builder.label' | sdTranslate\" [(model)]=\"component.label\"></attribute-input>\n <attribute-textarea [form]=\"form\" [label]=\"'core.component.form-builder.hint' | sdTranslate\" [(model)]=\"component.helperText\"></attribute-textarea>\n </div>\n</details>\n\n@if (!component.properties!.hidden && !component.properties!.viewed) {\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' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-switch [label]=\"'core.component.form-builder.disabled' | sdTranslate\" [(model)]=\"component.disabled\"></attribute-switch>\n <attribute-expression\n [label]=\"'core.component.form-builder.visible-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.visibleWhenExpression\"></attribute-expression>\n <attribute-expression\n [label]=\"'core.component.form-builder.hidden-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.hiddenWhenExpression\"></attribute-expression>\n @if (!component.disabled) {\n <attribute-expression\n [label]=\"'core.component.form-builder.disabled-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.disabledWhenExpression\"></attribute-expression>\n }\n </div>\n </details>\n <details class=\"fb-section\" open>\n <summary class=\"fb-section__head\">\n <span class=\"msi\">verified</span>\n <span class=\"fb-section__title\">{{ 'core.component.form-builder.validation' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-switch [form]=\"form\" [label]=\"'core.component.form-builder.required' | sdTranslate\" [(model)]=\"component.validate!.required\"></attribute-switch>\n <attribute-input-number [label]=\"'core.component.form-builder.maxlength' | sdTranslate\" [(model)]=\"component.validate!.maxlength\"></attribute-input-number>\n @if (!component.validate!.required) {\n <attribute-expression\n [label]=\"'core.component.form-builder.required-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.requiredWhenExpression\"></attribute-expression>\n }\n </div>\n </details>\n}\n", dependencies: [{ kind: "component", type: AttributeTemplate, selector: "attribute-template", inputs: ["component"], outputs: ["sdChange"] }, { kind: "component", type: AttributeInput, selector: "attribute-input", inputs: ["form", "label", "disabled", "model"], outputs: ["modelChange", "sdChange"] }, { kind: "component", type: AttributeInputNumber, selector: "attribute-input-number", inputs: ["label", "disabled", "model"], outputs: ["modelChange"] }, { kind: "component", type: AttributeSwitch, selector: "attribute-switch", inputs: ["form", "label", "model"], outputs: ["modelChange"] }, { kind: "component", type: AttributeExpression, selector: "attribute-expression", inputs: ["components", "variables", "label", "model"], outputs: ["modelChange", "sdChange"] }, { kind: "component", type: AttributeTextarea, selector: "attribute-textarea", inputs: ["form", "label", "disabled", "model"], outputs: ["modelChange", "sdChange"] }, { kind: "pipe", type: SdTranslatePipe, name: "sdTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3609
3722
|
}
|
|
3610
3723
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: TextareaAttribute, decorators: [{
|
|
3611
3724
|
type: Component,
|
|
@@ -3616,8 +3729,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
3616
3729
|
AttributeSwitch,
|
|
3617
3730
|
AttributeExpression,
|
|
3618
3731
|
AttributeTextarea,
|
|
3619
|
-
|
|
3620
|
-
], template: "<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' |
|
|
3732
|
+
SdTranslatePipe,
|
|
3733
|
+
], template: "<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' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-template [component]=\"component\" (sdChange)=\"onChangeTemplate($event)\"></attribute-template>\n <attribute-input [label]=\"'core.component.form-builder.field-key' | sdTranslate\" [(model)]=\"component.key\" [disabled]=\"!!component.template\"></attribute-input>\n <attribute-input [form]=\"form\" [label]=\"'core.component.form-builder.label' | sdTranslate\" [(model)]=\"component.label\"></attribute-input>\n <attribute-textarea [form]=\"form\" [label]=\"'core.component.form-builder.hint' | sdTranslate\" [(model)]=\"component.helperText\"></attribute-textarea>\n </div>\n</details>\n\n@if (!component.properties!.hidden && !component.properties!.viewed) {\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' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-switch [label]=\"'core.component.form-builder.disabled' | sdTranslate\" [(model)]=\"component.disabled\"></attribute-switch>\n <attribute-expression\n [label]=\"'core.component.form-builder.visible-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.visibleWhenExpression\"></attribute-expression>\n <attribute-expression\n [label]=\"'core.component.form-builder.hidden-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.hiddenWhenExpression\"></attribute-expression>\n @if (!component.disabled) {\n <attribute-expression\n [label]=\"'core.component.form-builder.disabled-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.disabledWhenExpression\"></attribute-expression>\n }\n </div>\n </details>\n <details class=\"fb-section\" open>\n <summary class=\"fb-section__head\">\n <span class=\"msi\">verified</span>\n <span class=\"fb-section__title\">{{ 'core.component.form-builder.validation' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-switch [form]=\"form\" [label]=\"'core.component.form-builder.required' | sdTranslate\" [(model)]=\"component.validate!.required\"></attribute-switch>\n <attribute-input-number [label]=\"'core.component.form-builder.maxlength' | sdTranslate\" [(model)]=\"component.validate!.maxlength\"></attribute-input-number>\n @if (!component.validate!.required) {\n <attribute-expression\n [label]=\"'core.component.form-builder.required-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.requiredWhenExpression\"></attribute-expression>\n }\n </div>\n </details>\n}\n" }]
|
|
3621
3734
|
}], ctorParameters: () => [], propDecorators: { components: [{ type: i0.Input, args: [{ isSignal: true, alias: "components", required: true }] }], variables: [{ type: i0.Input, args: [{ isSignal: true, alias: "variables", required: true }] }], _component: [{
|
|
3622
3735
|
type: Input,
|
|
3623
3736
|
args: [{ alias: 'component', required: true }]
|
|
@@ -3630,7 +3743,7 @@ class TextareaControl {
|
|
|
3630
3743
|
set _component(component) {
|
|
3631
3744
|
if (this.component !== component) {
|
|
3632
3745
|
this.component = component;
|
|
3633
|
-
|
|
3746
|
+
sdFormatComponent(this.component);
|
|
3634
3747
|
}
|
|
3635
3748
|
}
|
|
3636
3749
|
#subscription = new Subscription();
|
|
@@ -3665,7 +3778,7 @@ class ChipStringControl {
|
|
|
3665
3778
|
set _component(component) {
|
|
3666
3779
|
if (this.component !== component) {
|
|
3667
3780
|
this.component = component;
|
|
3668
|
-
|
|
3781
|
+
sdFormatComponent(this.component);
|
|
3669
3782
|
}
|
|
3670
3783
|
}
|
|
3671
3784
|
#subscription = new Subscription();
|
|
@@ -3703,7 +3816,7 @@ class ChipStringAttribute {
|
|
|
3703
3816
|
component;
|
|
3704
3817
|
set _component(component) {
|
|
3705
3818
|
this.component = component;
|
|
3706
|
-
|
|
3819
|
+
sdFormatComponent(this.component);
|
|
3707
3820
|
}
|
|
3708
3821
|
#changes = new Subject();
|
|
3709
3822
|
#subscription = new Subscription();
|
|
@@ -3734,7 +3847,7 @@ class ChipStringAttribute {
|
|
|
3734
3847
|
...template,
|
|
3735
3848
|
id: this.component.id, // Giữ lại id để componentEmitters định danh được component nào bị thay đổi
|
|
3736
3849
|
});
|
|
3737
|
-
|
|
3850
|
+
sdFormatComponent(this.component);
|
|
3738
3851
|
this.builderService.componentEmitters.next(this.component);
|
|
3739
3852
|
this.ref.markForCheck();
|
|
3740
3853
|
}
|
|
@@ -3743,11 +3856,11 @@ class ChipStringAttribute {
|
|
|
3743
3856
|
this.#changes.next();
|
|
3744
3857
|
};
|
|
3745
3858
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: ChipStringAttribute, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3746
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: ChipStringAttribute, isStandalone: true, selector: "chip-string-attribute", inputs: { components: { classPropertyName: "components", publicName: "components", isSignal: true, isRequired: true, transformFunction: null }, variables: { classPropertyName: "variables", publicName: "variables", isSignal: true, isRequired: true, transformFunction: null }, _component: { classPropertyName: "_component", publicName: "component", isSignal: false, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<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' |
|
|
3859
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: ChipStringAttribute, isStandalone: true, selector: "chip-string-attribute", inputs: { components: { classPropertyName: "components", publicName: "components", isSignal: true, isRequired: true, transformFunction: null }, variables: { classPropertyName: "variables", publicName: "variables", isSignal: true, isRequired: true, transformFunction: null }, _component: { classPropertyName: "_component", publicName: "component", isSignal: false, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<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' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-template [component]=\"component\" (sdChange)=\"onChangeTemplate($event)\"></attribute-template>\n <attribute-input [label]=\"'core.component.form-builder.field-key' | sdTranslate\" [(model)]=\"component.key\" [disabled]=\"!!component.template\"></attribute-input>\n <attribute-input [form]=\"form\" [label]=\"'core.component.form-builder.label' | sdTranslate\" [(model)]=\"component.label\"></attribute-input>\n @if (component.properties!.viewed) {\n <attribute-input label=\"Hyperlink\" [(model)]=\"component.properties!.hyperlink\"></attribute-input>\n }\n </div>\n</details>\n\n@if (!component.properties!.hidden && !component.properties!.viewed) {\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' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-switch [label]=\"'core.component.form-builder.disabled' | sdTranslate\" [(model)]=\"component.disabled\"></attribute-switch>\n <attribute-expression\n [label]=\"'core.component.form-builder.visible-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.visibleWhenExpression\"></attribute-expression>\n <attribute-expression\n [label]=\"'core.component.form-builder.hidden-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.hiddenWhenExpression\"></attribute-expression>\n @if (!component.disabled) {\n <attribute-expression\n [label]=\"'core.component.form-builder.disabled-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.disabledWhenExpression\"></attribute-expression>\n }\n </div>\n </details>\n @if (!component.properties!.viewed) {\n <details class=\"fb-section\" open>\n <summary class=\"fb-section__head\">\n <span class=\"msi\">verified</span>\n <span class=\"fb-section__title\">{{ 'core.component.form-builder.validation' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-switch [form]=\"form\" [label]=\"'core.component.form-builder.required' | sdTranslate\" [(model)]=\"component.validate!.required\"></attribute-switch>\n @if (!component.validate!.required) {\n <attribute-expression\n [label]=\"'core.component.form-builder.required-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.requiredWhenExpression\"></attribute-expression>\n }\n </div>\n </details>\n }\n}\n", dependencies: [{ kind: "component", type: AttributeTemplate, selector: "attribute-template", inputs: ["component"], outputs: ["sdChange"] }, { kind: "component", type: AttributeInput, selector: "attribute-input", inputs: ["form", "label", "disabled", "model"], outputs: ["modelChange", "sdChange"] }, { kind: "component", type: AttributeSwitch, selector: "attribute-switch", inputs: ["form", "label", "model"], outputs: ["modelChange"] }, { kind: "component", type: AttributeExpression, selector: "attribute-expression", inputs: ["components", "variables", "label", "model"], outputs: ["modelChange", "sdChange"] }, { kind: "pipe", type: SdTranslatePipe, name: "sdTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3747
3860
|
}
|
|
3748
3861
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: ChipStringAttribute, decorators: [{
|
|
3749
3862
|
type: Component,
|
|
3750
|
-
args: [{ selector: 'chip-string-attribute', changeDetection: ChangeDetectionStrategy.OnPush, imports: [AttributeTemplate, AttributeInput, AttributeSwitch, AttributeExpression,
|
|
3863
|
+
args: [{ selector: 'chip-string-attribute', changeDetection: ChangeDetectionStrategy.OnPush, imports: [AttributeTemplate, AttributeInput, AttributeSwitch, AttributeExpression, SdTranslatePipe], template: "<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' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-template [component]=\"component\" (sdChange)=\"onChangeTemplate($event)\"></attribute-template>\n <attribute-input [label]=\"'core.component.form-builder.field-key' | sdTranslate\" [(model)]=\"component.key\" [disabled]=\"!!component.template\"></attribute-input>\n <attribute-input [form]=\"form\" [label]=\"'core.component.form-builder.label' | sdTranslate\" [(model)]=\"component.label\"></attribute-input>\n @if (component.properties!.viewed) {\n <attribute-input label=\"Hyperlink\" [(model)]=\"component.properties!.hyperlink\"></attribute-input>\n }\n </div>\n</details>\n\n@if (!component.properties!.hidden && !component.properties!.viewed) {\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' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-switch [label]=\"'core.component.form-builder.disabled' | sdTranslate\" [(model)]=\"component.disabled\"></attribute-switch>\n <attribute-expression\n [label]=\"'core.component.form-builder.visible-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.visibleWhenExpression\"></attribute-expression>\n <attribute-expression\n [label]=\"'core.component.form-builder.hidden-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.hiddenWhenExpression\"></attribute-expression>\n @if (!component.disabled) {\n <attribute-expression\n [label]=\"'core.component.form-builder.disabled-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.disabledWhenExpression\"></attribute-expression>\n }\n </div>\n </details>\n @if (!component.properties!.viewed) {\n <details class=\"fb-section\" open>\n <summary class=\"fb-section__head\">\n <span class=\"msi\">verified</span>\n <span class=\"fb-section__title\">{{ 'core.component.form-builder.validation' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-switch [form]=\"form\" [label]=\"'core.component.form-builder.required' | sdTranslate\" [(model)]=\"component.validate!.required\"></attribute-switch>\n @if (!component.validate!.required) {\n <attribute-expression\n [label]=\"'core.component.form-builder.required-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.requiredWhenExpression\"></attribute-expression>\n }\n </div>\n </details>\n }\n}\n" }]
|
|
3751
3864
|
}], ctorParameters: () => [], propDecorators: { components: [{ type: i0.Input, args: [{ isSignal: true, alias: "components", required: true }] }], variables: [{ type: i0.Input, args: [{ isSignal: true, alias: "variables", required: true }] }], _component: [{
|
|
3752
3865
|
type: Input,
|
|
3753
3866
|
args: [{ alias: 'component', required: true }]
|
|
@@ -3760,7 +3873,7 @@ class ChipCalendarControl {
|
|
|
3760
3873
|
set _component(component) {
|
|
3761
3874
|
if (this.component !== component) {
|
|
3762
3875
|
this.component = component;
|
|
3763
|
-
|
|
3876
|
+
sdFormatComponent(this.component);
|
|
3764
3877
|
}
|
|
3765
3878
|
}
|
|
3766
3879
|
#subscription = new Subscription();
|
|
@@ -3798,7 +3911,7 @@ class ChipCalendarAttribute {
|
|
|
3798
3911
|
component;
|
|
3799
3912
|
set _component(component) {
|
|
3800
3913
|
this.component = component;
|
|
3801
|
-
|
|
3914
|
+
sdFormatComponent(this.component);
|
|
3802
3915
|
}
|
|
3803
3916
|
#changes = new Subject();
|
|
3804
3917
|
#subscription = new Subscription();
|
|
@@ -3829,7 +3942,7 @@ class ChipCalendarAttribute {
|
|
|
3829
3942
|
...template,
|
|
3830
3943
|
id: this.component.id, // Giữ lại id để componentEmitters định danh được component nào bị thay đổi
|
|
3831
3944
|
});
|
|
3832
|
-
|
|
3945
|
+
sdFormatComponent(this.component);
|
|
3833
3946
|
this.builderService.componentEmitters.next(this.component);
|
|
3834
3947
|
this.ref.markForCheck();
|
|
3835
3948
|
}
|
|
@@ -3838,11 +3951,11 @@ class ChipCalendarAttribute {
|
|
|
3838
3951
|
this.#changes.next();
|
|
3839
3952
|
};
|
|
3840
3953
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: ChipCalendarAttribute, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3841
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: ChipCalendarAttribute, isStandalone: true, selector: "chip-calendar-attribute", inputs: { components: { classPropertyName: "components", publicName: "components", isSignal: true, isRequired: true, transformFunction: null }, variables: { classPropertyName: "variables", publicName: "variables", isSignal: true, isRequired: true, transformFunction: null }, _component: { classPropertyName: "_component", publicName: "component", isSignal: false, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<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' |
|
|
3954
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: ChipCalendarAttribute, isStandalone: true, selector: "chip-calendar-attribute", inputs: { components: { classPropertyName: "components", publicName: "components", isSignal: true, isRequired: true, transformFunction: null }, variables: { classPropertyName: "variables", publicName: "variables", isSignal: true, isRequired: true, transformFunction: null }, _component: { classPropertyName: "_component", publicName: "component", isSignal: false, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<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' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-template [component]=\"component\" (sdChange)=\"onChangeTemplate($event)\"></attribute-template>\n <attribute-input [label]=\"'core.component.form-builder.field-key' | sdTranslate\" [(model)]=\"component.key\" [disabled]=\"!!component.template\"></attribute-input>\n <attribute-input [form]=\"form\" [label]=\"'core.component.form-builder.label' | sdTranslate\" [(model)]=\"component.label\"></attribute-input>\n @if (component.properties!.viewed) {\n <attribute-input label=\"Hyperlink\" [(model)]=\"component.properties!.hyperlink\"></attribute-input>\n }\n </div>\n</details>\n\n@if (!component.properties!.hidden && !component.properties!.viewed) {\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' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-switch [label]=\"'core.component.form-builder.disabled' | sdTranslate\" [(model)]=\"component.disabled\"></attribute-switch>\n <attribute-expression\n [label]=\"'core.component.form-builder.visible-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.visibleWhenExpression\"></attribute-expression>\n <attribute-expression\n [label]=\"'core.component.form-builder.hidden-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.hiddenWhenExpression\"></attribute-expression>\n @if (!component.disabled) {\n <attribute-expression\n [label]=\"'core.component.form-builder.disabled-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.disabledWhenExpression\"></attribute-expression>\n }\n </div>\n </details>\n @if (!component.properties!.viewed) {\n <details class=\"fb-section\" open>\n <summary class=\"fb-section__head\">\n <span class=\"msi\">verified</span>\n <span class=\"fb-section__title\">{{ 'core.component.form-builder.validation' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-switch [form]=\"form\" [label]=\"'core.component.form-builder.required' | sdTranslate\" [(model)]=\"component.validate!.required\"></attribute-switch>\n @if (!component.validate!.required) {\n <attribute-expression\n [label]=\"'core.component.form-builder.required-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.requiredWhenExpression\"></attribute-expression>\n }\n </div>\n </details>\n }\n}\n", dependencies: [{ kind: "component", type: AttributeTemplate, selector: "attribute-template", inputs: ["component"], outputs: ["sdChange"] }, { kind: "component", type: AttributeInput, selector: "attribute-input", inputs: ["form", "label", "disabled", "model"], outputs: ["modelChange", "sdChange"] }, { kind: "component", type: AttributeSwitch, selector: "attribute-switch", inputs: ["form", "label", "model"], outputs: ["modelChange"] }, { kind: "component", type: AttributeExpression, selector: "attribute-expression", inputs: ["components", "variables", "label", "model"], outputs: ["modelChange", "sdChange"] }, { kind: "pipe", type: SdTranslatePipe, name: "sdTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3842
3955
|
}
|
|
3843
3956
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: ChipCalendarAttribute, decorators: [{
|
|
3844
3957
|
type: Component,
|
|
3845
|
-
args: [{ selector: 'chip-calendar-attribute', changeDetection: ChangeDetectionStrategy.OnPush, imports: [AttributeTemplate, AttributeInput, AttributeSwitch, AttributeExpression,
|
|
3958
|
+
args: [{ selector: 'chip-calendar-attribute', changeDetection: ChangeDetectionStrategy.OnPush, imports: [AttributeTemplate, AttributeInput, AttributeSwitch, AttributeExpression, SdTranslatePipe], template: "<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' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-template [component]=\"component\" (sdChange)=\"onChangeTemplate($event)\"></attribute-template>\n <attribute-input [label]=\"'core.component.form-builder.field-key' | sdTranslate\" [(model)]=\"component.key\" [disabled]=\"!!component.template\"></attribute-input>\n <attribute-input [form]=\"form\" [label]=\"'core.component.form-builder.label' | sdTranslate\" [(model)]=\"component.label\"></attribute-input>\n @if (component.properties!.viewed) {\n <attribute-input label=\"Hyperlink\" [(model)]=\"component.properties!.hyperlink\"></attribute-input>\n }\n </div>\n</details>\n\n@if (!component.properties!.hidden && !component.properties!.viewed) {\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' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-switch [label]=\"'core.component.form-builder.disabled' | sdTranslate\" [(model)]=\"component.disabled\"></attribute-switch>\n <attribute-expression\n [label]=\"'core.component.form-builder.visible-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.visibleWhenExpression\"></attribute-expression>\n <attribute-expression\n [label]=\"'core.component.form-builder.hidden-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.hiddenWhenExpression\"></attribute-expression>\n @if (!component.disabled) {\n <attribute-expression\n [label]=\"'core.component.form-builder.disabled-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.disabledWhenExpression\"></attribute-expression>\n }\n </div>\n </details>\n @if (!component.properties!.viewed) {\n <details class=\"fb-section\" open>\n <summary class=\"fb-section__head\">\n <span class=\"msi\">verified</span>\n <span class=\"fb-section__title\">{{ 'core.component.form-builder.validation' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-switch [form]=\"form\" [label]=\"'core.component.form-builder.required' | sdTranslate\" [(model)]=\"component.validate!.required\"></attribute-switch>\n @if (!component.validate!.required) {\n <attribute-expression\n [label]=\"'core.component.form-builder.required-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.requiredWhenExpression\"></attribute-expression>\n }\n </div>\n </details>\n }\n}\n" }]
|
|
3846
3959
|
}], ctorParameters: () => [], propDecorators: { components: [{ type: i0.Input, args: [{ isSignal: true, alias: "components", required: true }] }], variables: [{ type: i0.Input, args: [{ isSignal: true, alias: "variables", required: true }] }], _component: [{
|
|
3847
3960
|
type: Input,
|
|
3848
3961
|
args: [{ alias: 'component', required: true }]
|
|
@@ -3859,7 +3972,7 @@ class NumberAttribute {
|
|
|
3859
3972
|
set _component(component) {
|
|
3860
3973
|
if (this.component !== component) {
|
|
3861
3974
|
this.component = component;
|
|
3862
|
-
|
|
3975
|
+
sdFormatComponent(this.component);
|
|
3863
3976
|
}
|
|
3864
3977
|
}
|
|
3865
3978
|
#subscription = new Subscription();
|
|
@@ -3887,14 +4000,14 @@ class NumberAttribute {
|
|
|
3887
4000
|
...template,
|
|
3888
4001
|
id: this.component.id, // Giữ lại id để componentEmitters định danh được component nào bị thay đổi
|
|
3889
4002
|
});
|
|
3890
|
-
|
|
4003
|
+
sdFormatComponent(this.component);
|
|
3891
4004
|
this.isTemplate = true;
|
|
3892
4005
|
this.builderService.componentEmitters.next(this.component);
|
|
3893
4006
|
this.ref.markForCheck();
|
|
3894
4007
|
}
|
|
3895
4008
|
};
|
|
3896
4009
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: NumberAttribute, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3897
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: NumberAttribute, isStandalone: true, selector: "number-attribute", inputs: { components: { classPropertyName: "components", publicName: "components", isSignal: true, isRequired: true, transformFunction: null }, variables: { classPropertyName: "variables", publicName: "variables", isSignal: true, isRequired: true, transformFunction: null }, _component: { classPropertyName: "_component", publicName: "component", isSignal: false, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<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' |
|
|
4010
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: NumberAttribute, isStandalone: true, selector: "number-attribute", inputs: { components: { classPropertyName: "components", publicName: "components", isSignal: true, isRequired: true, transformFunction: null }, variables: { classPropertyName: "variables", publicName: "variables", isSignal: true, isRequired: true, transformFunction: null }, _component: { classPropertyName: "_component", publicName: "component", isSignal: false, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<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' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-template [component]=\"component\" (sdChange)=\"onChangeTemplate($event)\"></attribute-template>\n <attribute-input [label]=\"'core.component.form-builder.field-key' | sdTranslate\" [(model)]=\"component.key\" [disabled]=\"!!component.template\"></attribute-input>\n <attribute-input [form]=\"form\" [label]=\"'core.component.form-builder.label' | sdTranslate\" [(model)]=\"component.label\"></attribute-input>\n <attribute-textarea [form]=\"form\" [label]=\"'core.component.form-builder.hint' | sdTranslate\" [(model)]=\"component.helperText\"></attribute-textarea>\n </div>\n</details>\n@if (!component.properties!.hidden && !component.properties!.viewed) {\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' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-switch [label]=\"'core.component.form-builder.disabled' | sdTranslate\" [(model)]=\"component.disabled\"></attribute-switch>\n <attribute-expression\n [label]=\"'core.component.form-builder.visible-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.visibleWhenExpression\"></attribute-expression>\n <attribute-expression\n [label]=\"'core.component.form-builder.hidden-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.hiddenWhenExpression\"></attribute-expression>\n @if (!component.disabled) {\n <attribute-expression\n [label]=\"'core.component.form-builder.disabled-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.disabledWhenExpression\"></attribute-expression>\n }\n </div>\n </details>\n <details class=\"fb-section\" open>\n <summary class=\"fb-section__head\">\n <span class=\"msi\">verified</span>\n <span class=\"fb-section__title\">{{ 'core.component.form-builder.validation' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-switch [form]=\"form\" [label]=\"'core.component.form-builder.required' | sdTranslate\" [(model)]=\"component.validate!.required\"></attribute-switch>\n <attribute-input-number [label]=\"'core.component.form-builder.min' | sdTranslate\" [(model)]=\"component.validate!.min\"></attribute-input-number>\n <attribute-input-number [label]=\"'core.component.form-builder.max' | sdTranslate\" [(model)]=\"component.validate!.max\"></attribute-input-number>\n @if (!component.validate!.required) {\n <attribute-expression\n [label]=\"'core.component.form-builder.required-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.requiredWhenExpression\"></attribute-expression>\n }\n </div>\n </details>\n}\n", dependencies: [{ kind: "component", type: AttributeTemplate, selector: "attribute-template", inputs: ["component"], outputs: ["sdChange"] }, { kind: "component", type: AttributeInput, selector: "attribute-input", inputs: ["form", "label", "disabled", "model"], outputs: ["modelChange", "sdChange"] }, { kind: "component", type: AttributeInputNumber, selector: "attribute-input-number", inputs: ["label", "disabled", "model"], outputs: ["modelChange"] }, { kind: "component", type: AttributeSwitch, selector: "attribute-switch", inputs: ["form", "label", "model"], outputs: ["modelChange"] }, { kind: "component", type: AttributeExpression, selector: "attribute-expression", inputs: ["components", "variables", "label", "model"], outputs: ["modelChange", "sdChange"] }, { kind: "component", type: AttributeTextarea, selector: "attribute-textarea", inputs: ["form", "label", "disabled", "model"], outputs: ["modelChange", "sdChange"] }, { kind: "pipe", type: SdTranslatePipe, name: "sdTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3898
4011
|
}
|
|
3899
4012
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: NumberAttribute, decorators: [{
|
|
3900
4013
|
type: Component,
|
|
@@ -3905,8 +4018,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
3905
4018
|
AttributeSwitch,
|
|
3906
4019
|
AttributeExpression,
|
|
3907
4020
|
AttributeTextarea,
|
|
3908
|
-
|
|
3909
|
-
], template: "<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' |
|
|
4021
|
+
SdTranslatePipe,
|
|
4022
|
+
], template: "<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' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-template [component]=\"component\" (sdChange)=\"onChangeTemplate($event)\"></attribute-template>\n <attribute-input [label]=\"'core.component.form-builder.field-key' | sdTranslate\" [(model)]=\"component.key\" [disabled]=\"!!component.template\"></attribute-input>\n <attribute-input [form]=\"form\" [label]=\"'core.component.form-builder.label' | sdTranslate\" [(model)]=\"component.label\"></attribute-input>\n <attribute-textarea [form]=\"form\" [label]=\"'core.component.form-builder.hint' | sdTranslate\" [(model)]=\"component.helperText\"></attribute-textarea>\n </div>\n</details>\n@if (!component.properties!.hidden && !component.properties!.viewed) {\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' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-switch [label]=\"'core.component.form-builder.disabled' | sdTranslate\" [(model)]=\"component.disabled\"></attribute-switch>\n <attribute-expression\n [label]=\"'core.component.form-builder.visible-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.visibleWhenExpression\"></attribute-expression>\n <attribute-expression\n [label]=\"'core.component.form-builder.hidden-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.hiddenWhenExpression\"></attribute-expression>\n @if (!component.disabled) {\n <attribute-expression\n [label]=\"'core.component.form-builder.disabled-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.disabledWhenExpression\"></attribute-expression>\n }\n </div>\n </details>\n <details class=\"fb-section\" open>\n <summary class=\"fb-section__head\">\n <span class=\"msi\">verified</span>\n <span class=\"fb-section__title\">{{ 'core.component.form-builder.validation' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-switch [form]=\"form\" [label]=\"'core.component.form-builder.required' | sdTranslate\" [(model)]=\"component.validate!.required\"></attribute-switch>\n <attribute-input-number [label]=\"'core.component.form-builder.min' | sdTranslate\" [(model)]=\"component.validate!.min\"></attribute-input-number>\n <attribute-input-number [label]=\"'core.component.form-builder.max' | sdTranslate\" [(model)]=\"component.validate!.max\"></attribute-input-number>\n @if (!component.validate!.required) {\n <attribute-expression\n [label]=\"'core.component.form-builder.required-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.requiredWhenExpression\"></attribute-expression>\n }\n </div>\n </details>\n}\n" }]
|
|
3910
4023
|
}], ctorParameters: () => [], propDecorators: { components: [{ type: i0.Input, args: [{ isSignal: true, alias: "components", required: true }] }], variables: [{ type: i0.Input, args: [{ isSignal: true, alias: "variables", required: true }] }], _component: [{
|
|
3911
4024
|
type: Input,
|
|
3912
4025
|
args: [{ alias: 'component', required: true }]
|
|
@@ -3919,7 +4032,7 @@ class NumberControl {
|
|
|
3919
4032
|
set _component(component) {
|
|
3920
4033
|
if (this.component !== component) {
|
|
3921
4034
|
this.component = component;
|
|
3922
|
-
|
|
4035
|
+
sdFormatComponent(this.component);
|
|
3923
4036
|
}
|
|
3924
4037
|
}
|
|
3925
4038
|
number = NumberUtilities.toISO(new Date().getTime());
|
|
@@ -4019,7 +4132,7 @@ class AttributeSelect {
|
|
|
4019
4132
|
this.sdChange.emit(value);
|
|
4020
4133
|
};
|
|
4021
4134
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: AttributeSelect, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4022
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.25", type: AttributeSelect, isStandalone: true, selector: "attribute-select", inputs: { form: { classPropertyName: "form", publicName: "form", isSignal: true, isRequired: false, transformFunction: null }, _label: { classPropertyName: "_label", publicName: "label", isSignal: false, isRequired: false, transformFunction: null }, _disabled: { classPropertyName: "_disabled", publicName: "disabled", isSignal: false, isRequired: false, transformFunction: null }, _multiple: { classPropertyName: "_multiple", publicName: "multiple", isSignal: false, isRequired: false, transformFunction: null }, _items: { classPropertyName: "_items", publicName: "items", isSignal: false, isRequired: true, transformFunction: null }, _model: { classPropertyName: "_model", publicName: "model", isSignal: false, isRequired: false, transformFunction: null } }, outputs: { modelChange: "modelChange", sdChange: "sdChange" }, 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 <sd-select\n style=\"width: 200px\"\n size=\"sm\"\n [form]=\"form\"\n [(model)]=\"model\"\n [items]=\"items\"\n valueField=\"value\"\n displayField=\"display\"\n [disabled]=\"disabled\"\n (sdChange)=\"onChange($event)\"\n hideInlineError></sd-select>\n</div> -->\n<sd-select\n class=\"py-8\"\n size=\"sm\"\n [label]=\"label!\"\n [form]=\"form()\"\n [(model)]=\"model\"\n [items]=\"items\"\n valueField=\"value\"\n displayField=\"display\"\n [disabled]=\"disabled\"\n (sdChange)=\"onChange($event)\"\n hideInlineError></sd-select>", dependencies: [{ 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"] }] });
|
|
4135
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.25", type: AttributeSelect, isStandalone: true, selector: "attribute-select", inputs: { form: { classPropertyName: "form", publicName: "form", isSignal: true, isRequired: false, transformFunction: null }, _label: { classPropertyName: "_label", publicName: "label", isSignal: false, isRequired: false, transformFunction: null }, _disabled: { classPropertyName: "_disabled", publicName: "disabled", isSignal: false, isRequired: false, transformFunction: null }, _multiple: { classPropertyName: "_multiple", publicName: "multiple", isSignal: false, isRequired: false, transformFunction: null }, _items: { classPropertyName: "_items", publicName: "items", isSignal: false, isRequired: true, transformFunction: null }, _model: { classPropertyName: "_model", publicName: "model", isSignal: false, isRequired: false, transformFunction: null } }, outputs: { modelChange: "modelChange", sdChange: "sdChange" }, 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 <sd-select\n style=\"width: 200px\"\n size=\"sm\"\n [form]=\"form\"\n [(model)]=\"model\"\n [items]=\"items\"\n valueField=\"value\"\n displayField=\"display\"\n [disabled]=\"disabled\"\n (sdChange)=\"onChange($event)\"\n hideInlineError></sd-select>\n</div> -->\n<sd-select\n class=\"py-8\"\n size=\"sm\"\n [label]=\"label!\"\n [form]=\"form()\"\n [(model)]=\"model\"\n [items]=\"items\"\n valueField=\"value\"\n displayField=\"display\"\n [disabled]=\"disabled\"\n (sdChange)=\"onChange($event)\"\n hideInlineError></sd-select>", dependencies: [{ 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", "showSelectAll", "clearable", "validator", "inlineError", "appearance", "floatLabel", "items", "model"], outputs: ["modelChange", "sdChange", "sdSelection"] }] });
|
|
4023
4136
|
}
|
|
4024
4137
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: AttributeSelect, decorators: [{
|
|
4025
4138
|
type: Component,
|
|
@@ -4050,7 +4163,7 @@ class DatetimeAttribute {
|
|
|
4050
4163
|
component;
|
|
4051
4164
|
set _component(component) {
|
|
4052
4165
|
this.component = component;
|
|
4053
|
-
|
|
4166
|
+
sdFormatComponent(this.component);
|
|
4054
4167
|
}
|
|
4055
4168
|
#subscription = new Subscription();
|
|
4056
4169
|
constructor() { }
|
|
@@ -4077,17 +4190,17 @@ class DatetimeAttribute {
|
|
|
4077
4190
|
...template,
|
|
4078
4191
|
id: this.component.id, // Giữ lại id để componentEmitters định danh được component nào bị thay đổi
|
|
4079
4192
|
});
|
|
4080
|
-
|
|
4193
|
+
sdFormatComponent(this.component);
|
|
4081
4194
|
this.builderService.componentEmitters.next(this.component);
|
|
4082
4195
|
this.ref.markForCheck();
|
|
4083
4196
|
}
|
|
4084
4197
|
};
|
|
4085
4198
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: DatetimeAttribute, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4086
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: DatetimeAttribute, isStandalone: true, selector: "datetime-attribute", inputs: { components: { classPropertyName: "components", publicName: "components", isSignal: true, isRequired: true, transformFunction: null }, variables: { classPropertyName: "variables", publicName: "variables", isSignal: true, isRequired: true, transformFunction: null }, _component: { classPropertyName: "_component", publicName: "component", isSignal: false, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<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' |
|
|
4199
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: DatetimeAttribute, isStandalone: true, selector: "datetime-attribute", inputs: { components: { classPropertyName: "components", publicName: "components", isSignal: true, isRequired: true, transformFunction: null }, variables: { classPropertyName: "variables", publicName: "variables", isSignal: true, isRequired: true, transformFunction: null }, _component: { classPropertyName: "_component", publicName: "component", isSignal: false, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<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' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-template [component]=\"component\" (sdChange)=\"onChangeTemplate($event)\"></attribute-template>\n <attribute-input [form]=\"form\" [label]=\"'core.component.form-builder.field-key' | sdTranslate\" [(model)]=\"component.key\" [disabled]=\"!!component.template\"></attribute-input>\n <attribute-input [label]=\"'core.component.form-builder.label' | sdTranslate\" [(model)]=\"component.label\"></attribute-input>\n <attribute-select\n [label]=\"'core.component.form-builder.format' | sdTranslate\"\n [(model)]=\"component.subtype\"\n [items]=\"[\n { value: 'date', display: 'dd/MM/yyyy' },\n { value: 'datetime', display: 'dd/MM/yyyy HH:mm' }\n ]\"></attribute-select>\n <attribute-textarea [form]=\"form\" [label]=\"'core.component.form-builder.hint' | sdTranslate\" [(model)]=\"component.helperText\"></attribute-textarea>\n </div>\n</details>\n@if (!component.properties!.hidden && !component.properties!.viewed) {\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' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-switch [form]=\"form\" [label]=\"'core.component.form-builder.disabled' | sdTranslate\" [(model)]=\"component.disabled\"></attribute-switch>\n <attribute-expression\n [label]=\"'core.component.form-builder.visible-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.visibleWhenExpression\"></attribute-expression>\n <attribute-expression\n [label]=\"'core.component.form-builder.hidden-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.hiddenWhenExpression\"></attribute-expression>\n @if (!component.disabled) {\n <attribute-expression\n [label]=\"'core.component.form-builder.disabled-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.disabledWhenExpression\"></attribute-expression>\n }\n </div>\n </details>\n <details class=\"fb-section\" open>\n <summary class=\"fb-section__head\">\n <span class=\"msi\">verified</span>\n <span class=\"fb-section__title\">{{ 'core.component.form-builder.validation' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-switch [form]=\"form\" [label]=\"'core.component.form-builder.required' | sdTranslate\" [(model)]=\"component.validate!.required\"></attribute-switch>\n @if (!component.validate!.required) {\n <attribute-expression\n [label]=\"'core.component.form-builder.required-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.requiredWhenExpression\"></attribute-expression>\n }\n </div>\n </details>\n}\n", dependencies: [{ kind: "component", type: AttributeTemplate, selector: "attribute-template", inputs: ["component"], outputs: ["sdChange"] }, { kind: "component", type: AttributeInput, selector: "attribute-input", inputs: ["form", "label", "disabled", "model"], outputs: ["modelChange", "sdChange"] }, { kind: "component", type: AttributeSwitch, selector: "attribute-switch", inputs: ["form", "label", "model"], outputs: ["modelChange"] }, { kind: "component", type: AttributeSelect, selector: "attribute-select", inputs: ["form", "label", "disabled", "multiple", "items", "model"], outputs: ["modelChange", "sdChange"] }, { kind: "component", type: AttributeExpression, selector: "attribute-expression", inputs: ["components", "variables", "label", "model"], outputs: ["modelChange", "sdChange"] }, { kind: "component", type: AttributeTextarea, selector: "attribute-textarea", inputs: ["form", "label", "disabled", "model"], outputs: ["modelChange", "sdChange"] }, { kind: "pipe", type: SdTranslatePipe, name: "sdTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4087
4200
|
}
|
|
4088
4201
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: DatetimeAttribute, decorators: [{
|
|
4089
4202
|
type: Component,
|
|
4090
|
-
args: [{ selector: 'datetime-attribute', changeDetection: ChangeDetectionStrategy.OnPush, imports: [AttributeTemplate, AttributeInput, AttributeSwitch, AttributeSelect, AttributeExpression, AttributeTextarea,
|
|
4203
|
+
args: [{ selector: 'datetime-attribute', changeDetection: ChangeDetectionStrategy.OnPush, imports: [AttributeTemplate, AttributeInput, AttributeSwitch, AttributeSelect, AttributeExpression, AttributeTextarea, SdTranslatePipe], template: "<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' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-template [component]=\"component\" (sdChange)=\"onChangeTemplate($event)\"></attribute-template>\n <attribute-input [form]=\"form\" [label]=\"'core.component.form-builder.field-key' | sdTranslate\" [(model)]=\"component.key\" [disabled]=\"!!component.template\"></attribute-input>\n <attribute-input [label]=\"'core.component.form-builder.label' | sdTranslate\" [(model)]=\"component.label\"></attribute-input>\n <attribute-select\n [label]=\"'core.component.form-builder.format' | sdTranslate\"\n [(model)]=\"component.subtype\"\n [items]=\"[\n { value: 'date', display: 'dd/MM/yyyy' },\n { value: 'datetime', display: 'dd/MM/yyyy HH:mm' }\n ]\"></attribute-select>\n <attribute-textarea [form]=\"form\" [label]=\"'core.component.form-builder.hint' | sdTranslate\" [(model)]=\"component.helperText\"></attribute-textarea>\n </div>\n</details>\n@if (!component.properties!.hidden && !component.properties!.viewed) {\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' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-switch [form]=\"form\" [label]=\"'core.component.form-builder.disabled' | sdTranslate\" [(model)]=\"component.disabled\"></attribute-switch>\n <attribute-expression\n [label]=\"'core.component.form-builder.visible-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.visibleWhenExpression\"></attribute-expression>\n <attribute-expression\n [label]=\"'core.component.form-builder.hidden-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.hiddenWhenExpression\"></attribute-expression>\n @if (!component.disabled) {\n <attribute-expression\n [label]=\"'core.component.form-builder.disabled-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.disabledWhenExpression\"></attribute-expression>\n }\n </div>\n </details>\n <details class=\"fb-section\" open>\n <summary class=\"fb-section__head\">\n <span class=\"msi\">verified</span>\n <span class=\"fb-section__title\">{{ 'core.component.form-builder.validation' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-switch [form]=\"form\" [label]=\"'core.component.form-builder.required' | sdTranslate\" [(model)]=\"component.validate!.required\"></attribute-switch>\n @if (!component.validate!.required) {\n <attribute-expression\n [label]=\"'core.component.form-builder.required-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.requiredWhenExpression\"></attribute-expression>\n }\n </div>\n </details>\n}\n" }]
|
|
4091
4204
|
}], ctorParameters: () => [], propDecorators: { components: [{ type: i0.Input, args: [{ isSignal: true, alias: "components", required: true }] }], variables: [{ type: i0.Input, args: [{ isSignal: true, alias: "variables", required: true }] }], _component: [{
|
|
4092
4205
|
type: Input,
|
|
4093
4206
|
args: [{ alias: 'component', required: true }]
|
|
@@ -4131,7 +4244,7 @@ let BuildQueries$1 = class BuildQueries {
|
|
|
4131
4244
|
}
|
|
4132
4245
|
edit = async () => {
|
|
4133
4246
|
this.leftProperties = this.selection?.queries?.items || [];
|
|
4134
|
-
this.rightProperties = [...
|
|
4247
|
+
this.rightProperties = [...sdGetComponentAttributes(this.components()), ...sdGetVariableAttributes(this.variables())].map(e => ({
|
|
4135
4248
|
value: '${' + e.value + '}',
|
|
4136
4249
|
display: e.display,
|
|
4137
4250
|
}));
|
|
@@ -4155,11 +4268,11 @@ let BuildQueries$1 = class BuildQueries {
|
|
|
4155
4268
|
this.ref.markForCheck();
|
|
4156
4269
|
};
|
|
4157
4270
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: BuildQueries, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4158
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: BuildQueries, isStandalone: true, selector: "build-queries", inputs: { components: { classPropertyName: "components", publicName: "components", isSignal: true, isRequired: true, transformFunction: null }, variables: { classPropertyName: "variables", publicName: "variables", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: false, isRequired: false, transformFunction: null }, selections: { classPropertyName: "selections", publicName: "selections", isSignal: true, isRequired: true, transformFunction: null }, _valuesKey: { classPropertyName: "_valuesKey", publicName: "valuesKey", isSignal: false, isRequired: true, transformFunction: null }, _model: { classPropertyName: "_model", publicName: "model", isSignal: false, isRequired: true, transformFunction: null } }, 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' |
|
|
4271
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: BuildQueries, isStandalone: true, selector: "build-queries", inputs: { components: { classPropertyName: "components", publicName: "components", isSignal: true, isRequired: true, transformFunction: null }, variables: { classPropertyName: "variables", publicName: "variables", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: false, isRequired: false, transformFunction: null }, selections: { classPropertyName: "selections", publicName: "selections", isSignal: true, isRequired: true, transformFunction: null }, _valuesKey: { classPropertyName: "_valuesKey", publicName: "valuesKey", isSignal: false, isRequired: true, transformFunction: null }, _model: { classPropertyName: "_model", publicName: "model", isSignal: false, isRequired: true, transformFunction: null } }, 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' | sdTranslate\" 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' | sdTranslate\"\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' | sdTranslate\" (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", "beforeClose"], outputs: ["sdClosed", "sdCloseError"] }, { kind: "pipe", type: SdTranslatePipe, name: "sdTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4159
4272
|
};
|
|
4160
4273
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: BuildQueries$1, decorators: [{
|
|
4161
4274
|
type: Component,
|
|
4162
|
-
args: [{ selector: 'build-queries', changeDetection: ChangeDetectionStrategy.OnPush, imports: [SdAutocomplete$1, SdButton, SdModal,
|
|
4275
|
+
args: [{ selector: 'build-queries', changeDetection: ChangeDetectionStrategy.OnPush, imports: [SdAutocomplete$1, SdButton, SdModal, SdTranslatePipe], 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' | sdTranslate\" 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' | sdTranslate\"\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' | sdTranslate\" (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"] }]
|
|
4163
4276
|
}], ctorParameters: () => [], propDecorators: { modal: [{
|
|
4164
4277
|
type: ViewChild,
|
|
4165
4278
|
args: [SdModal]
|
|
@@ -4216,7 +4329,7 @@ class BuildVariables {
|
|
|
4216
4329
|
this.#subscription.unsubscribe();
|
|
4217
4330
|
}
|
|
4218
4331
|
edit = async () => {
|
|
4219
|
-
this.leftProperties = [...
|
|
4332
|
+
this.leftProperties = [...sdGetComponentAttributes(this.components()), ...sdGetVariableAttributes(this.variables())];
|
|
4220
4333
|
this.rightProperties =
|
|
4221
4334
|
this.selection?.variables?.items?.map(e => ({
|
|
4222
4335
|
value: '${' + e.value + '}',
|
|
@@ -4255,11 +4368,11 @@ class BuildVariables {
|
|
|
4255
4368
|
this.ref.markForCheck();
|
|
4256
4369
|
};
|
|
4257
4370
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: BuildVariables, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4258
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: BuildVariables, isStandalone: true, selector: "build-variables", inputs: { components: { classPropertyName: "components", publicName: "components", isSignal: true, isRequired: true, transformFunction: null }, variables: { classPropertyName: "variables", publicName: "variables", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: false, isRequired: false, transformFunction: null }, selections: { classPropertyName: "selections", publicName: "selections", isSignal: true, isRequired: true, transformFunction: null }, _valuesKey: { classPropertyName: "_valuesKey", publicName: "valuesKey", isSignal: false, isRequired: true, transformFunction: null }, _model: { classPropertyName: "_model", publicName: "model", isSignal: false, isRequired: true, transformFunction: null } }, 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' |
|
|
4371
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: BuildVariables, isStandalone: true, selector: "build-variables", inputs: { components: { classPropertyName: "components", publicName: "components", isSignal: true, isRequired: true, transformFunction: null }, variables: { classPropertyName: "variables", publicName: "variables", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: false, isRequired: false, transformFunction: null }, selections: { classPropertyName: "selections", publicName: "selections", isSignal: true, isRequired: true, transformFunction: null }, _valuesKey: { classPropertyName: "_valuesKey", publicName: "valuesKey", isSignal: false, isRequired: true, transformFunction: null }, _model: { classPropertyName: "_model", publicName: "model", isSignal: false, isRequired: true, transformFunction: null } }, 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' | sdTranslate\" 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' | sdTranslate\" 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' | sdTranslate\"\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' | sdTranslate\"\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' | sdTranslate\" (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", "beforeClose"], outputs: ["sdClosed", "sdCloseError"] }, { kind: "pipe", type: SdTranslatePipe, name: "sdTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4259
4372
|
}
|
|
4260
4373
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: BuildVariables, decorators: [{
|
|
4261
4374
|
type: Component,
|
|
4262
|
-
args: [{ selector: 'build-variables', changeDetection: ChangeDetectionStrategy.OnPush, imports: [SdAutocomplete$1, SdButton, SdModal,
|
|
4375
|
+
args: [{ selector: 'build-variables', changeDetection: ChangeDetectionStrategy.OnPush, imports: [SdAutocomplete$1, SdButton, SdModal, SdTranslatePipe], 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' | sdTranslate\" 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' | sdTranslate\" 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' | sdTranslate\"\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' | sdTranslate\"\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' | sdTranslate\" (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"] }]
|
|
4263
4376
|
}], ctorParameters: () => [], propDecorators: { modal: [{
|
|
4264
4377
|
type: ViewChild,
|
|
4265
4378
|
args: [SdModal]
|
|
@@ -4337,11 +4450,11 @@ class AttributeSelection {
|
|
|
4337
4450
|
this.ref.markForCheck();
|
|
4338
4451
|
};
|
|
4339
4452
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: AttributeSelection, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4340
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: AttributeSelection, isStandalone: true, selector: "attribute-selection", inputs: { components: { classPropertyName: "components", publicName: "components", isSignal: true, isRequired: true, transformFunction: null }, variables: { classPropertyName: "variables", publicName: "variables", isSignal: true, isRequired: true, transformFunction: null }, component: { classPropertyName: "component", publicName: "component", isSignal: false, isRequired: true, transformFunction: null }, _valuesKey: { classPropertyName: "_valuesKey", publicName: "valuesKey", isSignal: false, isRequired: true, transformFunction: null }, _values: { classPropertyName: "_values", publicName: "values", isSignal: false, isRequired: true, transformFunction: null } }, outputs: { valuesKeyChange: "valuesKeyChange", valuesChange: "valuesChange", sdChange: "sdChange" }, ngImport: i0, template: "<sd-section class=\"mt-8\" [title]=\"'core.component.form-builder.data' |
|
|
4453
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: AttributeSelection, isStandalone: true, selector: "attribute-selection", inputs: { components: { classPropertyName: "components", publicName: "components", isSignal: true, isRequired: true, transformFunction: null }, variables: { classPropertyName: "variables", publicName: "variables", isSignal: true, isRequired: true, transformFunction: null }, component: { classPropertyName: "component", publicName: "component", isSignal: false, isRequired: true, transformFunction: null }, _valuesKey: { classPropertyName: "_valuesKey", publicName: "valuesKey", isSignal: false, isRequired: true, transformFunction: null }, _values: { classPropertyName: "_values", publicName: "values", isSignal: false, isRequired: true, transformFunction: null } }, outputs: { valuesKeyChange: "valuesKeyChange", valuesChange: "valuesChange", sdChange: "sdChange" }, ngImport: i0, template: "<sd-section class=\"mt-8\" [title]=\"'core.component.form-builder.data' | sdTranslate\" 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' | sdTranslate }}</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' | sdTranslate\"\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' | sdTranslate\"\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' | sdTranslate }}</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' | sdTranslate\" (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", "mask", "hideInlineError", "blurOnEnter", "clearable", "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: SdTranslatePipe, name: "sdTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4341
4454
|
}
|
|
4342
4455
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: AttributeSelection, decorators: [{
|
|
4343
4456
|
type: Component,
|
|
4344
|
-
args: [{ selector: 'attribute-selection', changeDetection: ChangeDetectionStrategy.OnPush, imports: [SdInput$1, SdAutocomplete$1, SdButton, SdSection, BuildQueries$1, BuildVariables,
|
|
4457
|
+
args: [{ selector: 'attribute-selection', changeDetection: ChangeDetectionStrategy.OnPush, imports: [SdInput$1, SdAutocomplete$1, SdButton, SdSection, BuildQueries$1, BuildVariables, SdTranslatePipe], template: "<sd-section class=\"mt-8\" [title]=\"'core.component.form-builder.data' | sdTranslate\" 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' | sdTranslate }}</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' | sdTranslate\"\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' | sdTranslate\"\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' | sdTranslate }}</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' | sdTranslate\" (click)=\"addItem()\" color=\"primary\"></sd-button>\n </div>\n }\n</sd-section>\n" }]
|
|
4345
4458
|
}], ctorParameters: () => [], propDecorators: { components: [{ type: i0.Input, args: [{ isSignal: true, alias: "components", required: true }] }], variables: [{ type: i0.Input, args: [{ isSignal: true, alias: "variables", required: true }] }], component: [{
|
|
4346
4459
|
type: Input,
|
|
4347
4460
|
args: [{ required: true }]
|
|
@@ -4362,7 +4475,7 @@ class SelectAttribute {
|
|
|
4362
4475
|
component;
|
|
4363
4476
|
set _component(component) {
|
|
4364
4477
|
this.component = component;
|
|
4365
|
-
|
|
4478
|
+
sdFormatComponent(this.component);
|
|
4366
4479
|
}
|
|
4367
4480
|
#changes = new Subject();
|
|
4368
4481
|
#subscription = new Subscription();
|
|
@@ -4396,17 +4509,17 @@ class SelectAttribute {
|
|
|
4396
4509
|
...template,
|
|
4397
4510
|
id: this.component.id, // Giữ lại id để componentEmitters định danh được component nào bị thay đổi
|
|
4398
4511
|
});
|
|
4399
|
-
|
|
4512
|
+
sdFormatComponent(this.component);
|
|
4400
4513
|
this.builderService.componentEmitters.next(this.component);
|
|
4401
4514
|
this.ref.markForCheck();
|
|
4402
4515
|
}
|
|
4403
4516
|
};
|
|
4404
4517
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SelectAttribute, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4405
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SelectAttribute, isStandalone: true, selector: "select-attribute", inputs: { components: { classPropertyName: "components", publicName: "components", isSignal: true, isRequired: true, transformFunction: null }, variables: { classPropertyName: "variables", publicName: "variables", isSignal: true, isRequired: true, transformFunction: null }, _component: { classPropertyName: "_component", publicName: "component", isSignal: false, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<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' |
|
|
4518
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SelectAttribute, isStandalone: true, selector: "select-attribute", inputs: { components: { classPropertyName: "components", publicName: "components", isSignal: true, isRequired: true, transformFunction: null }, variables: { classPropertyName: "variables", publicName: "variables", isSignal: true, isRequired: true, transformFunction: null }, _component: { classPropertyName: "_component", publicName: "component", isSignal: false, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<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' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-template [component]=\"component\" (sdChange)=\"onChangeTemplate($event)\"></attribute-template>\n <attribute-input [label]=\"'core.component.form-builder.field-key' | sdTranslate\" [(model)]=\"component.key\" [disabled]=\"!!component.template\"></attribute-input>\n <attribute-input [form]=\"form\" [label]=\"'core.component.form-builder.label' | sdTranslate\" [(model)]=\"component.label\"></attribute-input>\n <attribute-switch [label]=\"'core.component.form-builder.multiple' | sdTranslate\" [(model)]=\"component.properties!.multiple\"></attribute-switch>\n @if (component.properties!.viewed) {\n <attribute-input label=\"Hyperlink\" [(model)]=\"component.properties!.hyperlink\"></attribute-input>\n }\n </div>\n</details>\n\n@if (!component.properties!.hidden && !component.properties!.viewed) {\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' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-switch [label]=\"'core.component.form-builder.disabled' | sdTranslate\" [(model)]=\"component.disabled\"></attribute-switch>\n <attribute-expression\n [label]=\"'core.component.form-builder.visible-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.visibleWhenExpression\"></attribute-expression>\n <attribute-expression\n [label]=\"'core.component.form-builder.hidden-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.hiddenWhenExpression\"></attribute-expression>\n @if (!component.disabled) {\n <attribute-expression\n [label]=\"'core.component.form-builder.disabled-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.disabledWhenExpression\"></attribute-expression>\n }\n </div>\n </details>\n <details class=\"fb-section\" open>\n <summary class=\"fb-section__head\">\n <span class=\"msi\">verified</span>\n <span class=\"fb-section__title\">{{ 'core.component.form-builder.validation' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-switch [form]=\"form\" [label]=\"'core.component.form-builder.required' | sdTranslate\" [(model)]=\"component.validate!.required\"></attribute-switch>\n @if (!component.validate!.required) {\n <attribute-expression\n [label]=\"'core.component.form-builder.required-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.requiredWhenExpression\"></attribute-expression>\n }\n </div>\n </details>\n <attribute-selection\n [components]=\"components()\"\n [component]=\"component\"\n [variables]=\"variables()\"\n [(valuesKey)]=\"component.valuesKey\"\n [(values)]=\"component.values\"\n (valuesKeyChange)=\"onChanges()\"\n (valuesChange)=\"onChanges()\"></attribute-selection>\n}\n", dependencies: [{ kind: "component", type: AttributeTemplate, selector: "attribute-template", inputs: ["component"], outputs: ["sdChange"] }, { 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: AttributeSwitch, selector: "attribute-switch", inputs: ["form", "label", "model"], outputs: ["modelChange"] }, { kind: "component", type: AttributeExpression, selector: "attribute-expression", inputs: ["components", "variables", "label", "model"], outputs: ["modelChange", "sdChange"] }, { kind: "pipe", type: SdTranslatePipe, name: "sdTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4406
4519
|
}
|
|
4407
4520
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SelectAttribute, decorators: [{
|
|
4408
4521
|
type: Component,
|
|
4409
|
-
args: [{ selector: 'select-attribute', changeDetection: ChangeDetectionStrategy.OnPush, imports: [AttributeTemplate, AttributeSelection, AttributeInput, AttributeSwitch, AttributeExpression,
|
|
4522
|
+
args: [{ selector: 'select-attribute', changeDetection: ChangeDetectionStrategy.OnPush, imports: [AttributeTemplate, AttributeSelection, AttributeInput, AttributeSwitch, AttributeExpression, SdTranslatePipe], template: "<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' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-template [component]=\"component\" (sdChange)=\"onChangeTemplate($event)\"></attribute-template>\n <attribute-input [label]=\"'core.component.form-builder.field-key' | sdTranslate\" [(model)]=\"component.key\" [disabled]=\"!!component.template\"></attribute-input>\n <attribute-input [form]=\"form\" [label]=\"'core.component.form-builder.label' | sdTranslate\" [(model)]=\"component.label\"></attribute-input>\n <attribute-switch [label]=\"'core.component.form-builder.multiple' | sdTranslate\" [(model)]=\"component.properties!.multiple\"></attribute-switch>\n @if (component.properties!.viewed) {\n <attribute-input label=\"Hyperlink\" [(model)]=\"component.properties!.hyperlink\"></attribute-input>\n }\n </div>\n</details>\n\n@if (!component.properties!.hidden && !component.properties!.viewed) {\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' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-switch [label]=\"'core.component.form-builder.disabled' | sdTranslate\" [(model)]=\"component.disabled\"></attribute-switch>\n <attribute-expression\n [label]=\"'core.component.form-builder.visible-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.visibleWhenExpression\"></attribute-expression>\n <attribute-expression\n [label]=\"'core.component.form-builder.hidden-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.hiddenWhenExpression\"></attribute-expression>\n @if (!component.disabled) {\n <attribute-expression\n [label]=\"'core.component.form-builder.disabled-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.disabledWhenExpression\"></attribute-expression>\n }\n </div>\n </details>\n <details class=\"fb-section\" open>\n <summary class=\"fb-section__head\">\n <span class=\"msi\">verified</span>\n <span class=\"fb-section__title\">{{ 'core.component.form-builder.validation' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-switch [form]=\"form\" [label]=\"'core.component.form-builder.required' | sdTranslate\" [(model)]=\"component.validate!.required\"></attribute-switch>\n @if (!component.validate!.required) {\n <attribute-expression\n [label]=\"'core.component.form-builder.required-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.requiredWhenExpression\"></attribute-expression>\n }\n </div>\n </details>\n <attribute-selection\n [components]=\"components()\"\n [component]=\"component\"\n [variables]=\"variables()\"\n [(valuesKey)]=\"component.valuesKey\"\n [(values)]=\"component.values\"\n (valuesKeyChange)=\"onChanges()\"\n (valuesChange)=\"onChanges()\"></attribute-selection>\n}\n" }]
|
|
4410
4523
|
}], ctorParameters: () => [], propDecorators: { components: [{ type: i0.Input, args: [{ isSignal: true, alias: "components", required: true }] }], variables: [{ type: i0.Input, args: [{ isSignal: true, alias: "variables", required: true }] }], _component: [{
|
|
4411
4524
|
type: Input,
|
|
4412
4525
|
args: [{ alias: 'component', required: true }]
|
|
@@ -4419,7 +4532,7 @@ class SelectControl {
|
|
|
4419
4532
|
set _component(component) {
|
|
4420
4533
|
if (this.component !== component) {
|
|
4421
4534
|
this.component = component;
|
|
4422
|
-
|
|
4535
|
+
sdFormatComponent(this.component);
|
|
4423
4536
|
}
|
|
4424
4537
|
}
|
|
4425
4538
|
#subscription = new Subscription();
|
|
@@ -4454,7 +4567,7 @@ class RadioControl {
|
|
|
4454
4567
|
set _component(component) {
|
|
4455
4568
|
if (this.component !== component) {
|
|
4456
4569
|
this.component = component;
|
|
4457
|
-
|
|
4570
|
+
sdFormatComponent(this.component);
|
|
4458
4571
|
}
|
|
4459
4572
|
}
|
|
4460
4573
|
#subscription = new Subscription();
|
|
@@ -4492,7 +4605,7 @@ class RadioAttribute {
|
|
|
4492
4605
|
component;
|
|
4493
4606
|
set _component(component) {
|
|
4494
4607
|
this.component = component;
|
|
4495
|
-
|
|
4608
|
+
sdFormatComponent(this.component);
|
|
4496
4609
|
}
|
|
4497
4610
|
#changes = new Subject();
|
|
4498
4611
|
#subscription = new Subscription();
|
|
@@ -4523,7 +4636,7 @@ class RadioAttribute {
|
|
|
4523
4636
|
...template,
|
|
4524
4637
|
id: this.component.id, // Giữ lại id để componentEmitters định danh được component nào bị thay đổi
|
|
4525
4638
|
});
|
|
4526
|
-
|
|
4639
|
+
sdFormatComponent(this.component);
|
|
4527
4640
|
this.builderService.componentEmitters.next(this.component);
|
|
4528
4641
|
this.ref.markForCheck();
|
|
4529
4642
|
}
|
|
@@ -4532,11 +4645,11 @@ class RadioAttribute {
|
|
|
4532
4645
|
this.#changes.next();
|
|
4533
4646
|
};
|
|
4534
4647
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: RadioAttribute, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4535
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: RadioAttribute, isStandalone: true, selector: "radio-attribute", inputs: { components: { classPropertyName: "components", publicName: "components", isSignal: true, isRequired: true, transformFunction: null }, variables: { classPropertyName: "variables", publicName: "variables", isSignal: true, isRequired: true, transformFunction: null }, _component: { classPropertyName: "_component", publicName: "component", isSignal: false, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<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' |
|
|
4648
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: RadioAttribute, isStandalone: true, selector: "radio-attribute", inputs: { components: { classPropertyName: "components", publicName: "components", isSignal: true, isRequired: true, transformFunction: null }, variables: { classPropertyName: "variables", publicName: "variables", isSignal: true, isRequired: true, transformFunction: null }, _component: { classPropertyName: "_component", publicName: "component", isSignal: false, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<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' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-template [component]=\"component\" (sdChange)=\"onChangeTemplate($event)\"></attribute-template>\n <attribute-input [label]=\"'core.component.form-builder.field-key' | sdTranslate\" [(model)]=\"component.key\" [disabled]=\"!!component.template\"></attribute-input>\n <attribute-input [form]=\"form\" [label]=\"'core.component.form-builder.label' | sdTranslate\" [(model)]=\"component.label\"></attribute-input>\n @if (component.properties!.viewed) {\n <attribute-input label=\"Hyperlink\" [(model)]=\"component.properties!.hyperlink\"></attribute-input>\n }\n </div>\n</details>\n\n@if (!component.properties!.hidden && !component.properties!.viewed) {\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' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-switch [label]=\"'core.component.form-builder.disabled' | sdTranslate\" [(model)]=\"component.disabled\"></attribute-switch>\n <attribute-expression\n [label]=\"'core.component.form-builder.visible-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.visibleWhenExpression\"></attribute-expression>\n <attribute-expression\n [label]=\"'core.component.form-builder.hidden-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.hiddenWhenExpression\"></attribute-expression>\n @if (!component.disabled) {\n <attribute-expression\n [label]=\"'core.component.form-builder.disabled-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.disabledWhenExpression\"></attribute-expression>\n }\n <attribute-select\n [form]=\"form\"\n [label]=\"'core.component.form-builder.layout' | sdTranslate\"\n [(model)]=\"component.properties!.direction\"\n [items]=\"[\n { value: 'row', display: 'Ngang' },\n { value: 'column', display: ('core.component.form-builder.vertical' | sdTranslate) }\n ]\"></attribute-select>\n </div>\n </details>\n @if (!component.properties!.viewed) {\n <details class=\"fb-section\" open>\n <summary class=\"fb-section__head\">\n <span class=\"msi\">verified</span>\n <span class=\"fb-section__title\">{{ 'core.component.form-builder.validation' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-switch [form]=\"form\" [label]=\"'core.component.form-builder.required' | sdTranslate\" [(model)]=\"component.validate!.required\"></attribute-switch>\n @if (!component.validate!.required) {\n <attribute-expression\n [label]=\"'core.component.form-builder.required-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.requiredWhenExpression\"></attribute-expression>\n }\n </div>\n </details>\n }\n <attribute-selection\n [components]=\"components()\"\n [variables]=\"variables()\"\n [component]=\"component\"\n [(valuesKey)]=\"component.valuesKey\"\n [(values)]=\"component.values\"\n (valuesKeyChange)=\"onChanges()\"\n (valuesChange)=\"onChanges()\"></attribute-selection>\n}\n", dependencies: [{ kind: "component", type: AttributeTemplate, selector: "attribute-template", inputs: ["component"], outputs: ["sdChange"] }, { 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: AttributeSwitch, selector: "attribute-switch", inputs: ["form", "label", "model"], outputs: ["modelChange"] }, { kind: "component", type: AttributeSelect, selector: "attribute-select", inputs: ["form", "label", "disabled", "multiple", "items", "model"], outputs: ["modelChange", "sdChange"] }, { kind: "component", type: AttributeExpression, selector: "attribute-expression", inputs: ["components", "variables", "label", "model"], outputs: ["modelChange", "sdChange"] }, { kind: "pipe", type: SdTranslatePipe, name: "sdTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4536
4649
|
}
|
|
4537
4650
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: RadioAttribute, decorators: [{
|
|
4538
4651
|
type: Component,
|
|
4539
|
-
args: [{ selector: 'radio-attribute', changeDetection: ChangeDetectionStrategy.OnPush, imports: [AttributeTemplate, AttributeSelection, AttributeInput, AttributeSwitch, AttributeSelect, AttributeExpression,
|
|
4652
|
+
args: [{ selector: 'radio-attribute', changeDetection: ChangeDetectionStrategy.OnPush, imports: [AttributeTemplate, AttributeSelection, AttributeInput, AttributeSwitch, AttributeSelect, AttributeExpression, SdTranslatePipe], template: "<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' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-template [component]=\"component\" (sdChange)=\"onChangeTemplate($event)\"></attribute-template>\n <attribute-input [label]=\"'core.component.form-builder.field-key' | sdTranslate\" [(model)]=\"component.key\" [disabled]=\"!!component.template\"></attribute-input>\n <attribute-input [form]=\"form\" [label]=\"'core.component.form-builder.label' | sdTranslate\" [(model)]=\"component.label\"></attribute-input>\n @if (component.properties!.viewed) {\n <attribute-input label=\"Hyperlink\" [(model)]=\"component.properties!.hyperlink\"></attribute-input>\n }\n </div>\n</details>\n\n@if (!component.properties!.hidden && !component.properties!.viewed) {\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' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-switch [label]=\"'core.component.form-builder.disabled' | sdTranslate\" [(model)]=\"component.disabled\"></attribute-switch>\n <attribute-expression\n [label]=\"'core.component.form-builder.visible-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.visibleWhenExpression\"></attribute-expression>\n <attribute-expression\n [label]=\"'core.component.form-builder.hidden-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.hiddenWhenExpression\"></attribute-expression>\n @if (!component.disabled) {\n <attribute-expression\n [label]=\"'core.component.form-builder.disabled-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.disabledWhenExpression\"></attribute-expression>\n }\n <attribute-select\n [form]=\"form\"\n [label]=\"'core.component.form-builder.layout' | sdTranslate\"\n [(model)]=\"component.properties!.direction\"\n [items]=\"[\n { value: 'row', display: 'Ngang' },\n { value: 'column', display: ('core.component.form-builder.vertical' | sdTranslate) }\n ]\"></attribute-select>\n </div>\n </details>\n @if (!component.properties!.viewed) {\n <details class=\"fb-section\" open>\n <summary class=\"fb-section__head\">\n <span class=\"msi\">verified</span>\n <span class=\"fb-section__title\">{{ 'core.component.form-builder.validation' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-switch [form]=\"form\" [label]=\"'core.component.form-builder.required' | sdTranslate\" [(model)]=\"component.validate!.required\"></attribute-switch>\n @if (!component.validate!.required) {\n <attribute-expression\n [label]=\"'core.component.form-builder.required-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.requiredWhenExpression\"></attribute-expression>\n }\n </div>\n </details>\n }\n <attribute-selection\n [components]=\"components()\"\n [variables]=\"variables()\"\n [component]=\"component\"\n [(valuesKey)]=\"component.valuesKey\"\n [(values)]=\"component.values\"\n (valuesKeyChange)=\"onChanges()\"\n (valuesChange)=\"onChanges()\"></attribute-selection>\n}\n" }]
|
|
4540
4653
|
}], ctorParameters: () => [], propDecorators: { components: [{ type: i0.Input, args: [{ isSignal: true, alias: "components", required: true }] }], variables: [{ type: i0.Input, args: [{ isSignal: true, alias: "variables", required: true }] }], _component: [{
|
|
4541
4654
|
type: Input,
|
|
4542
4655
|
args: [{ alias: 'component', required: true }]
|
|
@@ -4548,11 +4661,11 @@ class CheckboxControl {
|
|
|
4548
4661
|
this.component = component;
|
|
4549
4662
|
}
|
|
4550
4663
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: CheckboxControl, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4551
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: CheckboxControl, isStandalone: true, selector: "checkbox-control", inputs: { _component: ["component", "_component"] }, ngImport: i0, template: "@if (component.properties?.viewed) {\n <div class=\"d-block T12R text-secondary\">\n {{ component.label }}\n </div>\n <div class=\"T14M\">{{ 'core.component.form-builder.yes-no' |
|
|
4664
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: CheckboxControl, isStandalone: true, selector: "checkbox-control", inputs: { _component: ["component", "_component"] }, ngImport: i0, template: "@if (component.properties?.viewed) {\n <div class=\"d-block T12R text-secondary\">\n {{ component.label }}\n </div>\n <div class=\"T14M\">{{ 'core.component.form-builder.yes-no' | sdTranslate }}</div>\n} @else {\n <div class=\"c-checkbox\">\n <div class=\"d-flex align-items-center\">\n <div class=\"c-box mr-4\"></div>\n <div class=\"T14R\">{{ component.label }}</div>\n </div>\n </div>\n}\n", styles: ["@charset \"UTF-8\";.input{width:100%;background:#fff;height:30px;border:1px solid var(--md-sys-color-outline-variant);position:relative}.c-checkbox .c-box{border:2px solid #828282;width:16px;height:16px;border-radius:2px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "pipe", type: SdTranslatePipe, name: "sdTranslate" }] });
|
|
4552
4665
|
}
|
|
4553
4666
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: CheckboxControl, decorators: [{
|
|
4554
4667
|
type: Component,
|
|
4555
|
-
args: [{ selector: 'checkbox-control', imports: [CommonModule,
|
|
4668
|
+
args: [{ selector: 'checkbox-control', imports: [CommonModule, SdTranslatePipe], template: "@if (component.properties?.viewed) {\n <div class=\"d-block T12R text-secondary\">\n {{ component.label }}\n </div>\n <div class=\"T14M\">{{ 'core.component.form-builder.yes-no' | sdTranslate }}</div>\n} @else {\n <div class=\"c-checkbox\">\n <div class=\"d-flex align-items-center\">\n <div class=\"c-box mr-4\"></div>\n <div class=\"T14R\">{{ component.label }}</div>\n </div>\n </div>\n}\n", styles: ["@charset \"UTF-8\";.input{width:100%;background:#fff;height:30px;border:1px solid var(--md-sys-color-outline-variant);position:relative}.c-checkbox .c-box{border:2px solid #828282;width:16px;height:16px;border-radius:2px}\n"] }]
|
|
4556
4669
|
}], propDecorators: { _component: [{
|
|
4557
4670
|
type: Input,
|
|
4558
4671
|
args: [{ alias: 'component', required: true }]
|
|
@@ -4567,7 +4680,7 @@ class CheckboxAttribute {
|
|
|
4567
4680
|
component;
|
|
4568
4681
|
set _component(component) {
|
|
4569
4682
|
this.component = component;
|
|
4570
|
-
|
|
4683
|
+
sdFormatComponent(this.component);
|
|
4571
4684
|
}
|
|
4572
4685
|
#subscription = new Subscription();
|
|
4573
4686
|
constructor() { }
|
|
@@ -4594,17 +4707,17 @@ class CheckboxAttribute {
|
|
|
4594
4707
|
...template,
|
|
4595
4708
|
id: this.component.id, // Giữ lại id để componentEmitters định danh được component nào bị thay đổi
|
|
4596
4709
|
});
|
|
4597
|
-
|
|
4710
|
+
sdFormatComponent(this.component);
|
|
4598
4711
|
this.builderService.componentEmitters.next(this.component);
|
|
4599
4712
|
this.ref.markForCheck();
|
|
4600
4713
|
}
|
|
4601
4714
|
};
|
|
4602
4715
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: CheckboxAttribute, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4603
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: CheckboxAttribute, isStandalone: true, selector: "checkbox-attribute", inputs: { components: { classPropertyName: "components", publicName: "components", isSignal: true, isRequired: true, transformFunction: null }, variables: { classPropertyName: "variables", publicName: "variables", isSignal: true, isRequired: true, transformFunction: null }, _component: { classPropertyName: "_component", publicName: "component", isSignal: false, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<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' |
|
|
4716
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: CheckboxAttribute, isStandalone: true, selector: "checkbox-attribute", inputs: { components: { classPropertyName: "components", publicName: "components", isSignal: true, isRequired: true, transformFunction: null }, variables: { classPropertyName: "variables", publicName: "variables", isSignal: true, isRequired: true, transformFunction: null }, _component: { classPropertyName: "_component", publicName: "component", isSignal: false, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<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' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-template [component]=\"component\" (sdChange)=\"onChangeTemplate($event)\"></attribute-template>\n <attribute-input [form]=\"form\" [label]=\"'core.component.form-builder.field-key' | sdTranslate\" [(model)]=\"component.key\" [disabled]=\"!!component.template\"></attribute-input>\n <attribute-input [label]=\"'core.component.form-builder.label' | sdTranslate\" [(model)]=\"component.label\"></attribute-input>\n </div>\n</details>\n@if (!component.properties!.hidden && !component.properties!.viewed) {\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' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-switch [label]=\"'core.component.form-builder.disabled' | sdTranslate\" [(model)]=\"component.disabled\"></attribute-switch>\n <attribute-expression\n [label]=\"'core.component.form-builder.visible-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.visibleWhenExpression\"></attribute-expression>\n @if (!component.disabled) {\n <attribute-expression\n [label]=\"'core.component.form-builder.disabled-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.disabledWhenExpression\"></attribute-expression>\n }\n </div>\n </details>\n}\n", dependencies: [{ kind: "component", type: AttributeTemplate, selector: "attribute-template", inputs: ["component"], outputs: ["sdChange"] }, { kind: "component", type: AttributeInput, selector: "attribute-input", inputs: ["form", "label", "disabled", "model"], outputs: ["modelChange", "sdChange"] }, { kind: "component", type: AttributeSwitch, selector: "attribute-switch", inputs: ["form", "label", "model"], outputs: ["modelChange"] }, { kind: "component", type: AttributeExpression, selector: "attribute-expression", inputs: ["components", "variables", "label", "model"], outputs: ["modelChange", "sdChange"] }, { kind: "pipe", type: SdTranslatePipe, name: "sdTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4604
4717
|
}
|
|
4605
4718
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: CheckboxAttribute, decorators: [{
|
|
4606
4719
|
type: Component,
|
|
4607
|
-
args: [{ selector: 'checkbox-attribute', changeDetection: ChangeDetectionStrategy.OnPush, imports: [AttributeTemplate, AttributeInput, AttributeSwitch, AttributeExpression,
|
|
4720
|
+
args: [{ selector: 'checkbox-attribute', changeDetection: ChangeDetectionStrategy.OnPush, imports: [AttributeTemplate, AttributeInput, AttributeSwitch, AttributeExpression, SdTranslatePipe], template: "<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' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-template [component]=\"component\" (sdChange)=\"onChangeTemplate($event)\"></attribute-template>\n <attribute-input [form]=\"form\" [label]=\"'core.component.form-builder.field-key' | sdTranslate\" [(model)]=\"component.key\" [disabled]=\"!!component.template\"></attribute-input>\n <attribute-input [label]=\"'core.component.form-builder.label' | sdTranslate\" [(model)]=\"component.label\"></attribute-input>\n </div>\n</details>\n@if (!component.properties!.hidden && !component.properties!.viewed) {\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' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-switch [label]=\"'core.component.form-builder.disabled' | sdTranslate\" [(model)]=\"component.disabled\"></attribute-switch>\n <attribute-expression\n [label]=\"'core.component.form-builder.visible-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.visibleWhenExpression\"></attribute-expression>\n @if (!component.disabled) {\n <attribute-expression\n [label]=\"'core.component.form-builder.disabled-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.disabledWhenExpression\"></attribute-expression>\n }\n </div>\n </details>\n}\n" }]
|
|
4608
4721
|
}], ctorParameters: () => [], propDecorators: { components: [{ type: i0.Input, args: [{ isSignal: true, alias: "components", required: true }] }], variables: [{ type: i0.Input, args: [{ isSignal: true, alias: "variables", required: true }] }], _component: [{
|
|
4609
4722
|
type: Input,
|
|
4610
4723
|
args: [{ alias: 'component', required: true }]
|
|
@@ -4639,7 +4752,7 @@ class BuildQueries {
|
|
|
4639
4752
|
ngOnDestroy() { }
|
|
4640
4753
|
edit = () => {
|
|
4641
4754
|
this.rightProperties =
|
|
4642
|
-
[...
|
|
4755
|
+
[...sdGetComponentAttributes(this.components()), ...sdGetVariableAttributes(this.variables())].map(e => ({
|
|
4643
4756
|
value: '${' + e.value + '}',
|
|
4644
4757
|
display: e.display,
|
|
4645
4758
|
})) || [];
|
|
@@ -4663,11 +4776,11 @@ class BuildQueries {
|
|
|
4663
4776
|
this.ref.markForCheck();
|
|
4664
4777
|
};
|
|
4665
4778
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: BuildQueries, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4666
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: BuildQueries, isStandalone: true, selector: "build-queries", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: false, isRequired: false, transformFunction: null }, _queries: { classPropertyName: "_queries", publicName: "queries", isSignal: false, isRequired: true, transformFunction: null }, components: { classPropertyName: "components", publicName: "components", isSignal: true, isRequired: true, transformFunction: null }, variables: { classPropertyName: "variables", publicName: "variables", isSignal: true, isRequired: true, transformFunction: null }, _model: { classPropertyName: "_model", publicName: "model", isSignal: false, isRequired: true, transformFunction: null } }, 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' |
|
|
4779
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: BuildQueries, isStandalone: true, selector: "build-queries", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: false, isRequired: false, transformFunction: null }, _queries: { classPropertyName: "_queries", publicName: "queries", isSignal: false, isRequired: true, transformFunction: null }, components: { classPropertyName: "components", publicName: "components", isSignal: true, isRequired: true, transformFunction: null }, variables: { classPropertyName: "variables", publicName: "variables", isSignal: true, isRequired: true, transformFunction: null }, _model: { classPropertyName: "_model", publicName: "model", isSignal: false, isRequired: true, transformFunction: null } }, 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' | sdTranslate\" 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' | sdTranslate\"\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' | sdTranslate\" (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", "beforeClose"], outputs: ["sdClosed", "sdCloseError"] }, { kind: "pipe", type: SdTranslatePipe, name: "sdTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4667
4780
|
}
|
|
4668
4781
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: BuildQueries, decorators: [{
|
|
4669
4782
|
type: Component,
|
|
4670
|
-
args: [{ selector: 'build-queries', changeDetection: ChangeDetectionStrategy.OnPush, imports: [SdAutocomplete$1, SdButton, SdModal,
|
|
4783
|
+
args: [{ selector: 'build-queries', changeDetection: ChangeDetectionStrategy.OnPush, imports: [SdAutocomplete$1, SdButton, SdModal, SdTranslatePipe], 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' | sdTranslate\" 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' | sdTranslate\"\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' | sdTranslate\" (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"] }]
|
|
4671
4784
|
}], ctorParameters: () => [], propDecorators: { modal: [{
|
|
4672
4785
|
type: ViewChild,
|
|
4673
4786
|
args: [SdModal]
|
|
@@ -4691,7 +4804,7 @@ class HtmlAttribute {
|
|
|
4691
4804
|
set _component(component) {
|
|
4692
4805
|
if (this.component !== component) {
|
|
4693
4806
|
this.component = component;
|
|
4694
|
-
|
|
4807
|
+
sdFormatComponent(this.component);
|
|
4695
4808
|
}
|
|
4696
4809
|
}
|
|
4697
4810
|
form = new FormGroup({});
|
|
@@ -4707,7 +4820,7 @@ class HtmlAttribute {
|
|
|
4707
4820
|
this.ref.markForCheck();
|
|
4708
4821
|
});
|
|
4709
4822
|
this.rightProperties =
|
|
4710
|
-
|
|
4823
|
+
sdGetComponentAttributes(this.components()).map(e => ({
|
|
4711
4824
|
value: '${' + e.value + '}',
|
|
4712
4825
|
display: e.display,
|
|
4713
4826
|
})) || [];
|
|
@@ -4751,11 +4864,11 @@ class HtmlAttribute {
|
|
|
4751
4864
|
}
|
|
4752
4865
|
};
|
|
4753
4866
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: HtmlAttribute, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4754
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: HtmlAttribute, isStandalone: true, selector: "html-attribute", inputs: { components: { classPropertyName: "components", publicName: "components", isSignal: true, isRequired: true, transformFunction: null }, variables: { classPropertyName: "variables", publicName: "variables", isSignal: true, isRequired: true, transformFunction: null }, _component: { classPropertyName: "_component", publicName: "component", isSignal: false, isRequired: true, transformFunction: null } }, outputs: { attributeChanges: "attributeChanges" }, ngImport: i0, template: "@if (!component.properties!.hidden) {\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' |
|
|
4867
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: HtmlAttribute, isStandalone: true, selector: "html-attribute", inputs: { components: { classPropertyName: "components", publicName: "components", isSignal: true, isRequired: true, transformFunction: null }, variables: { classPropertyName: "variables", publicName: "variables", isSignal: true, isRequired: true, transformFunction: null }, _component: { classPropertyName: "_component", publicName: "component", isSignal: false, isRequired: true, transformFunction: null } }, outputs: { attributeChanges: "attributeChanges" }, ngImport: i0, template: "@if (!component.properties!.hidden) {\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' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-select\n [label]=\"'core.component.form-builder.template-data' | sdTranslate\"\n [(model)]=\"component.template\"\n [items]=\"definitionHtmls\"\n (sdChange)=\"onChangeDefinition($event)\"></attribute-select>\n <attribute-input [form]=\"form\" [label]=\"'core.component.form-builder.field-key' | sdTranslate\" [(model)]=\"component.key\"></attribute-input>\n <attribute-textarea [form]=\"form\" [label]=\"'core.component.form-builder.content' | sdTranslate\" [(model)]=\"component.content\"></attribute-textarea>\n @if (component.properties?.queries?.length) {\n <build-queries\n [label]=\"'core.component.form-builder.query-condition' | sdTranslate\"\n [queries]=\"component.properties?.queries\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.query\"></build-queries>\n }\n </div>\n </details>\n @if (component.properties?.variables?.length) {\n <details class=\"fb-section\" open>\n <summary class=\"fb-section__head\">\n <span class=\"msi\">format_list_bulleted</span>\n <span class=\"fb-section__title\">{{ 'core.component.form-builder.parameter' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n @for (variable of component.properties?.variables; track variable.key) {\n <attribute-input [form]=\"form\" [label]=\"variable.label\" [(model)]=\"variable.value\"></attribute-input>\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' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-expression\n [label]=\"'core.component.form-builder.visible-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.visibleWhenExpression\"></attribute-expression>\n <attribute-expression\n [label]=\"'core.component.form-builder.hidden-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.hiddenWhenExpression\"></attribute-expression>\n </div>\n </details>\n}\n", dependencies: [{ 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: AttributeExpression, selector: "attribute-expression", inputs: ["components", "variables", "label", "model"], outputs: ["modelChange", "sdChange"] }, { kind: "component", type: AttributeTextarea, selector: "attribute-textarea", inputs: ["form", "label", "disabled", "model"], outputs: ["modelChange", "sdChange"] }, { kind: "component", type: BuildQueries, selector: "build-queries", inputs: ["label", "queries", "components", "variables", "model"], outputs: ["modelChange"] }, { kind: "pipe", type: SdTranslatePipe, name: "sdTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4755
4868
|
}
|
|
4756
4869
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: HtmlAttribute, decorators: [{
|
|
4757
4870
|
type: Component,
|
|
4758
|
-
args: [{ selector: 'html-attribute', changeDetection: ChangeDetectionStrategy.OnPush, imports: [AttributeInput, AttributeSelect, AttributeExpression, AttributeTextarea, BuildQueries,
|
|
4871
|
+
args: [{ selector: 'html-attribute', changeDetection: ChangeDetectionStrategy.OnPush, imports: [AttributeInput, AttributeSelect, AttributeExpression, AttributeTextarea, BuildQueries, SdTranslatePipe], template: "@if (!component.properties!.hidden) {\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' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-select\n [label]=\"'core.component.form-builder.template-data' | sdTranslate\"\n [(model)]=\"component.template\"\n [items]=\"definitionHtmls\"\n (sdChange)=\"onChangeDefinition($event)\"></attribute-select>\n <attribute-input [form]=\"form\" [label]=\"'core.component.form-builder.field-key' | sdTranslate\" [(model)]=\"component.key\"></attribute-input>\n <attribute-textarea [form]=\"form\" [label]=\"'core.component.form-builder.content' | sdTranslate\" [(model)]=\"component.content\"></attribute-textarea>\n @if (component.properties?.queries?.length) {\n <build-queries\n [label]=\"'core.component.form-builder.query-condition' | sdTranslate\"\n [queries]=\"component.properties?.queries\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.query\"></build-queries>\n }\n </div>\n </details>\n @if (component.properties?.variables?.length) {\n <details class=\"fb-section\" open>\n <summary class=\"fb-section__head\">\n <span class=\"msi\">format_list_bulleted</span>\n <span class=\"fb-section__title\">{{ 'core.component.form-builder.parameter' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n @for (variable of component.properties?.variables; track variable.key) {\n <attribute-input [form]=\"form\" [label]=\"variable.label\" [(model)]=\"variable.value\"></attribute-input>\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' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-expression\n [label]=\"'core.component.form-builder.visible-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.visibleWhenExpression\"></attribute-expression>\n <attribute-expression\n [label]=\"'core.component.form-builder.hidden-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.hiddenWhenExpression\"></attribute-expression>\n </div>\n </details>\n}\n" }]
|
|
4759
4872
|
}], ctorParameters: () => [], propDecorators: { components: [{ type: i0.Input, args: [{ isSignal: true, alias: "components", required: true }] }], variables: [{ type: i0.Input, args: [{ isSignal: true, alias: "variables", required: true }] }], _component: [{
|
|
4760
4873
|
type: Input,
|
|
4761
4874
|
args: [{ alias: 'component', required: true }]
|
|
@@ -4768,7 +4881,7 @@ class HtmlControl {
|
|
|
4768
4881
|
component;
|
|
4769
4882
|
set _component(component) {
|
|
4770
4883
|
this.component = component;
|
|
4771
|
-
|
|
4884
|
+
sdFormatComponent(this.component);
|
|
4772
4885
|
this.content = this.component.content;
|
|
4773
4886
|
}
|
|
4774
4887
|
content;
|
|
@@ -4865,11 +4978,11 @@ class AttributeParameter {
|
|
|
4865
4978
|
this.ref.markForCheck();
|
|
4866
4979
|
};
|
|
4867
4980
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: AttributeParameter, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4868
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: AttributeParameter, isStandalone: true, selector: "attribute-parameter", inputs: { components: { classPropertyName: "components", publicName: "components", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: false, isRequired: false, transformFunction: null }, _model: { classPropertyName: "_model", publicName: "model", isSignal: false, isRequired: true, transformFunction: null } }, 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' |
|
|
4981
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: AttributeParameter, isStandalone: true, selector: "attribute-parameter", inputs: { components: { classPropertyName: "components", publicName: "components", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: false, isRequired: false, transformFunction: null }, _model: { classPropertyName: "_model", publicName: "model", isSignal: false, isRequired: true, transformFunction: null } }, 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' | sdTranslate\" 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' | sdTranslate\" 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' | sdTranslate\" [form]=\"form\" style=\"flex: 1\" [(model)]=\"item.key\" required hideInlineError></sd-input>\n <sd-input [label]=\"'core.component.form-builder.parameter-value' | sdTranslate\" [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' | sdTranslate\"\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' | sdTranslate\" (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", "beforeClose"], outputs: ["sdClosed", "sdCloseError"] }, { kind: "component", type: SdInput, selector: "sd-input", inputs: ["autoId", "name", "appearance", "floatLabel", "size", "form", "label", "helperText", "placeholder", "type", "mask", "hideInlineError", "blurOnEnter", "clearable", "required", "readonly", "disabled", "viewed", "minlength", "maxlength", "pattern", "patternErrorMessage", "validator", "inlineError", "hyperlink", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "cleared", "sdFocusForceBlur"] }, { kind: "pipe", type: SdTranslatePipe, name: "sdTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4869
4982
|
}
|
|
4870
4983
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: AttributeParameter, decorators: [{
|
|
4871
4984
|
type: Component,
|
|
4872
|
-
args: [{ selector: 'attribute-parameter', changeDetection: ChangeDetectionStrategy.OnPush, imports: [SdButton, SdModal, SdInput,
|
|
4985
|
+
args: [{ selector: 'attribute-parameter', changeDetection: ChangeDetectionStrategy.OnPush, imports: [SdButton, SdModal, SdInput, SdTranslatePipe], 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' | sdTranslate\" 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' | sdTranslate\" 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' | sdTranslate\" [form]=\"form\" style=\"flex: 1\" [(model)]=\"item.key\" required hideInlineError></sd-input>\n <sd-input [label]=\"'core.component.form-builder.parameter-value' | sdTranslate\" [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' | sdTranslate\"\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' | sdTranslate\" (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"] }]
|
|
4873
4986
|
}], ctorParameters: () => [], propDecorators: { modal: [{
|
|
4874
4987
|
type: ViewChild,
|
|
4875
4988
|
args: [SdModal]
|
|
@@ -4890,7 +5003,7 @@ class UploadAttribute {
|
|
|
4890
5003
|
extension;
|
|
4891
5004
|
set _component(component) {
|
|
4892
5005
|
this.component = component;
|
|
4893
|
-
|
|
5006
|
+
sdFormatComponent(this.component);
|
|
4894
5007
|
this.extension = this.component.properties.extensions?.join(',') || '';
|
|
4895
5008
|
}
|
|
4896
5009
|
#subscription = new Subscription();
|
|
@@ -4934,7 +5047,7 @@ class UploadAttribute {
|
|
|
4934
5047
|
...template,
|
|
4935
5048
|
id: this.component.id, // Giữ lại id để componentEmitters định danh được component nào bị thay đổi
|
|
4936
5049
|
});
|
|
4937
|
-
|
|
5050
|
+
sdFormatComponent(this.component);
|
|
4938
5051
|
this.component.properties.type = this.component.properties.type || 'file';
|
|
4939
5052
|
this.builderService.componentEmitters.next(this.component);
|
|
4940
5053
|
this.ref.markForCheck();
|
|
@@ -4949,7 +5062,7 @@ class UploadAttribute {
|
|
|
4949
5062
|
?.filter(val => !!val) || [];
|
|
4950
5063
|
};
|
|
4951
5064
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: UploadAttribute, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4952
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: UploadAttribute, isStandalone: true, selector: "upload-attribute", inputs: { components: { classPropertyName: "components", publicName: "components", isSignal: true, isRequired: true, transformFunction: null }, variables: { classPropertyName: "variables", publicName: "variables", isSignal: true, isRequired: true, transformFunction: null }, _component: { classPropertyName: "_component", publicName: "component", isSignal: false, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<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' |
|
|
5065
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: UploadAttribute, isStandalone: true, selector: "upload-attribute", inputs: { components: { classPropertyName: "components", publicName: "components", isSignal: true, isRequired: true, transformFunction: null }, variables: { classPropertyName: "variables", publicName: "variables", isSignal: true, isRequired: true, transformFunction: null }, _component: { classPropertyName: "_component", publicName: "component", isSignal: false, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<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' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-template [component]=\"component\" (sdChange)=\"onChangeTemplate($event)\"></attribute-template>\n <attribute-input [label]=\"'core.component.form-builder.field-key' | sdTranslate\" [(model)]=\"component.key\" [disabled]=\"!!component.template\"></attribute-input>\n <attribute-input [form]=\"form\" [label]=\"'core.component.form-builder.label' | sdTranslate\" [(model)]=\"component.label\"></attribute-input>\n <attribute-select\n [label]=\"'core.component.form-builder.type' | sdTranslate\"\n [(model)]=\"component.properties!.type\"\n [items]=\"[\n { value: 'file', display: ('core.component.form-builder.file' | sdTranslate) },\n { value: 'image', display: ('core.component.form-builder.image' | sdTranslate) }\n ]\"></attribute-select>\n </div>\n</details>\n\n@if (!component.properties!.hidden && !component.properties!.viewed) {\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' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-switch [label]=\"'core.component.form-builder.disabled' | sdTranslate\" [(model)]=\"component.disabled\"></attribute-switch>\n <attribute-expression\n [label]=\"'core.component.form-builder.visible-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.visibleWhenExpression\"></attribute-expression>\n <attribute-expression\n [label]=\"'core.component.form-builder.hidden-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.hiddenWhenExpression\"></attribute-expression>\n @if (!component.disabled) {\n <attribute-expression\n [label]=\"'core.component.form-builder.disabled-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.disabledWhenExpression\"></attribute-expression>\n }\n </div>\n </details>\n <details class=\"fb-section\" open>\n <summary class=\"fb-section__head\">\n <span class=\"msi\">verified</span>\n <span class=\"fb-section__title\">{{ 'core.component.form-builder.validation' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-switch [label]=\"'core.component.form-builder.required' | sdTranslate\" [(model)]=\"component.validate!.required\"></attribute-switch>\n @if (!component.validate!.required) {\n <attribute-expression\n [label]=\"'core.component.form-builder.required-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.requiredWhenExpression\"></attribute-expression>\n }\n <attribute-input-number [label]=\"'core.component.form-builder.max-files' | sdTranslate\" [(model)]=\"component.properties!.max\"></attribute-input-number>\n <attribute-input-number [label]=\"'core.component.form-builder.size-mb' | sdTranslate\" [(model)]=\"component.properties!.maxSize\"></attribute-input-number>\n @if (component.properties!.type === 'file') {\n <attribute-input [label]=\"'core.component.form-builder.format-example' | sdTranslate\" [model]=\"extension\" (modelChange)=\"onChangeExtension($event)\"></attribute-input>\n }\n @if (component.properties!.type === 'image') {\n <attribute-select [label]=\"'core.component.form-builder.source-mobile' | sdTranslate\" [(model)]=\"component.properties!.source\" [items]=\"sources\"></attribute-select>\n }\n </div>\n </details>\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' | sdTranslate }}</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' | sdTranslate\" [components]=\"components()\" [(model)]=\"component.properties!.args\"></attribute-parameter>\n </div>\n </details>\n}\n", dependencies: [{ kind: "component", type: AttributeTemplate, selector: "attribute-template", inputs: ["component"], outputs: ["sdChange"] }, { kind: "component", type: AttributeInput, selector: "attribute-input", inputs: ["form", "label", "disabled", "model"], outputs: ["modelChange", "sdChange"] }, { kind: "component", type: AttributeInputNumber, selector: "attribute-input-number", inputs: ["label", "disabled", "model"], outputs: ["modelChange"] }, { kind: "component", type: AttributeSwitch, selector: "attribute-switch", inputs: ["form", "label", "model"], outputs: ["modelChange"] }, { kind: "component", type: AttributeSelect, selector: "attribute-select", inputs: ["form", "label", "disabled", "multiple", "items", "model"], outputs: ["modelChange", "sdChange"] }, { kind: "component", type: AttributeExpression, selector: "attribute-expression", inputs: ["components", "variables", "label", "model"], outputs: ["modelChange", "sdChange"] }, { kind: "component", type: AttributeParameter, selector: "attribute-parameter", inputs: ["components", "label", "model"], outputs: ["modelChange"] }, { kind: "pipe", type: SdTranslatePipe, name: "sdTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4953
5066
|
}
|
|
4954
5067
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: UploadAttribute, decorators: [{
|
|
4955
5068
|
type: Component,
|
|
@@ -4961,8 +5074,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
4961
5074
|
AttributeSelect,
|
|
4962
5075
|
AttributeExpression,
|
|
4963
5076
|
AttributeParameter,
|
|
4964
|
-
|
|
4965
|
-
], template: "<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' |
|
|
5077
|
+
SdTranslatePipe,
|
|
5078
|
+
], template: "<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' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-template [component]=\"component\" (sdChange)=\"onChangeTemplate($event)\"></attribute-template>\n <attribute-input [label]=\"'core.component.form-builder.field-key' | sdTranslate\" [(model)]=\"component.key\" [disabled]=\"!!component.template\"></attribute-input>\n <attribute-input [form]=\"form\" [label]=\"'core.component.form-builder.label' | sdTranslate\" [(model)]=\"component.label\"></attribute-input>\n <attribute-select\n [label]=\"'core.component.form-builder.type' | sdTranslate\"\n [(model)]=\"component.properties!.type\"\n [items]=\"[\n { value: 'file', display: ('core.component.form-builder.file' | sdTranslate) },\n { value: 'image', display: ('core.component.form-builder.image' | sdTranslate) }\n ]\"></attribute-select>\n </div>\n</details>\n\n@if (!component.properties!.hidden && !component.properties!.viewed) {\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' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-switch [label]=\"'core.component.form-builder.disabled' | sdTranslate\" [(model)]=\"component.disabled\"></attribute-switch>\n <attribute-expression\n [label]=\"'core.component.form-builder.visible-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.visibleWhenExpression\"></attribute-expression>\n <attribute-expression\n [label]=\"'core.component.form-builder.hidden-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.hiddenWhenExpression\"></attribute-expression>\n @if (!component.disabled) {\n <attribute-expression\n [label]=\"'core.component.form-builder.disabled-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.disabledWhenExpression\"></attribute-expression>\n }\n </div>\n </details>\n <details class=\"fb-section\" open>\n <summary class=\"fb-section__head\">\n <span class=\"msi\">verified</span>\n <span class=\"fb-section__title\">{{ 'core.component.form-builder.validation' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-switch [label]=\"'core.component.form-builder.required' | sdTranslate\" [(model)]=\"component.validate!.required\"></attribute-switch>\n @if (!component.validate!.required) {\n <attribute-expression\n [label]=\"'core.component.form-builder.required-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.requiredWhenExpression\"></attribute-expression>\n }\n <attribute-input-number [label]=\"'core.component.form-builder.max-files' | sdTranslate\" [(model)]=\"component.properties!.max\"></attribute-input-number>\n <attribute-input-number [label]=\"'core.component.form-builder.size-mb' | sdTranslate\" [(model)]=\"component.properties!.maxSize\"></attribute-input-number>\n @if (component.properties!.type === 'file') {\n <attribute-input [label]=\"'core.component.form-builder.format-example' | sdTranslate\" [model]=\"extension\" (modelChange)=\"onChangeExtension($event)\"></attribute-input>\n }\n @if (component.properties!.type === 'image') {\n <attribute-select [label]=\"'core.component.form-builder.source-mobile' | sdTranslate\" [(model)]=\"component.properties!.source\" [items]=\"sources\"></attribute-select>\n }\n </div>\n </details>\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' | sdTranslate }}</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' | sdTranslate\" [components]=\"components()\" [(model)]=\"component.properties!.args\"></attribute-parameter>\n </div>\n </details>\n}\n" }]
|
|
4966
5079
|
}], ctorParameters: () => [], propDecorators: { components: [{ type: i0.Input, args: [{ isSignal: true, alias: "components", required: true }] }], variables: [{ type: i0.Input, args: [{ isSignal: true, alias: "variables", required: true }] }], _component: [{
|
|
4967
5080
|
type: Input,
|
|
4968
5081
|
args: [{ alias: 'component', required: true }]
|
|
@@ -4975,7 +5088,7 @@ class UploadControl {
|
|
|
4975
5088
|
set _component(component) {
|
|
4976
5089
|
if (this.component !== component) {
|
|
4977
5090
|
this.component = component;
|
|
4978
|
-
|
|
5091
|
+
sdFormatComponent(this.component);
|
|
4979
5092
|
}
|
|
4980
5093
|
}
|
|
4981
5094
|
#subscription = new Subscription();
|
|
@@ -5021,7 +5134,7 @@ class AttributeTable {
|
|
|
5021
5134
|
formGenericConfiguration = inject(SD_FORM_GENERIC_CONFIGURATION, { optional: true });
|
|
5022
5135
|
tables = this.formGenericConfiguration?.form?.tables || [];
|
|
5023
5136
|
// Columns
|
|
5024
|
-
types =
|
|
5137
|
+
types = SD_TABLE_COLUMN_TYPES;
|
|
5025
5138
|
columns = [];
|
|
5026
5139
|
set _columns(columns) {
|
|
5027
5140
|
if (this.columns !== columns) {
|
|
@@ -5096,7 +5209,7 @@ class AttributeTable {
|
|
|
5096
5209
|
this.modal?.close();
|
|
5097
5210
|
};
|
|
5098
5211
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: AttributeTable, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
5099
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: AttributeTable, isStandalone: true, selector: "attribute-table", inputs: { components: { classPropertyName: "components", publicName: "components", isSignal: true, isRequired: true, transformFunction: null }, variables: { classPropertyName: "variables", publicName: "variables", isSignal: true, isRequired: true, transformFunction: null }, _columnsKey: { classPropertyName: "_columnsKey", publicName: "columnsKey", isSignal: false, isRequired: true, transformFunction: null }, _columns: { classPropertyName: "_columns", publicName: "columns", isSignal: false, isRequired: true, transformFunction: null } }, 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' |
|
|
5212
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: AttributeTable, isStandalone: true, selector: "attribute-table", inputs: { components: { classPropertyName: "components", publicName: "components", isSignal: true, isRequired: true, transformFunction: null }, variables: { classPropertyName: "variables", publicName: "variables", isSignal: true, isRequired: true, transformFunction: null }, _columnsKey: { classPropertyName: "_columnsKey", publicName: "columnsKey", isSignal: false, isRequired: true, transformFunction: null }, _columns: { classPropertyName: "_columns", publicName: "columns", isSignal: false, isRequired: true, transformFunction: null } }, 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' | sdTranslate }}</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' | sdTranslate\"\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' | sdTranslate\" (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' | sdTranslate\" (click)=\"removeColumn(idx)\">\n <span class=\"msi\">delete</span>\n </button>\n </div>\n }\n <!-- why: <a> KH\u00D4NG c\u00F3 href kh\u00F4ng ph\u1EA3i l\u00E0 control \u2014 n\u00F3 kh\u00F4ng nh\u1EADn focus b\u00E0n ph\u00EDm v\u00E0 Enter\n kh\u00F4ng k\u00EDch ho\u1EA1t \u0111\u01B0\u1EE3c, t\u1EE9c ng\u01B0\u1EDDi d\u00F9ng b\u00E0n ph\u00EDm kh\u00F4ng th\u00EAm \u0111\u01B0\u1EE3c c\u1ED9t. \u0110\u00E2y l\u00E0 m\u1ED9t H\u00C0NH\n \u0110\u1ED8NG ch\u1EE9 kh\u00F4ng ph\u1EA3i \u0111i\u1EC1u h\u01B0\u1EDBng \u2192 <button type=\"button\"> m\u1EDBi \u0111\u00FAng ng\u1EEF ngh\u0129a. -->\n <button type=\"button\" class=\"fb-add-link\" (click)=\"addColumn()\">\n <span class=\"msi\">add</span>\n {{ 'core.component.form-builder.add-column' | sdTranslate }}\n </button>\n </div>\n }\n </div>\n</details>\n\n<sd-modal [title]=\"'core.component.form-builder.column-config' | sdTranslate\" 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' | sdTranslate }}</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' | sdTranslate\" [(model)]=\"column.key\"></attribute-input>\n <attribute-input [form]=\"form\" [label]=\"'core.component.form-builder.column-name' | sdTranslate\" [(model)]=\"column.label\"></attribute-input>\n <attribute-input [form]=\"form\" [label]=\"'core.component.form-builder.width' | sdTranslate\" [(model)]=\"column.width\"></attribute-input>\n <attribute-switch [form]=\"form\" [label]=\"'core.component.form-builder.required' | sdTranslate\" [(model)]=\"column.validate!.required\"></attribute-switch>\n <attribute-select [form]=\"form\" [label]=\"'core.component.form-builder.data-type' | sdTranslate\" [(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' | sdTranslate }}</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' | sdTranslate\" [(model)]=\"column.displayOnTrue\"></attribute-input>\n <attribute-input [form]=\"form\" [label]=\"'core.component.form-builder.show-when-false' | sdTranslate\" [(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' | sdTranslate }}</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' | sdTranslate\" [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' | sdTranslate\" (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", "beforeClose"], outputs: ["sdClosed", "sdCloseError"] }, { 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: SdTranslatePipe, name: "sdTranslate" }] });
|
|
5100
5213
|
}
|
|
5101
5214
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: AttributeTable, decorators: [{
|
|
5102
5215
|
type: Component,
|
|
@@ -5111,8 +5224,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
5111
5224
|
AttributeSelect,
|
|
5112
5225
|
AttributeSwitch,
|
|
5113
5226
|
AttributeParameter,
|
|
5114
|
-
|
|
5115
|
-
], 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' |
|
|
5227
|
+
SdTranslatePipe,
|
|
5228
|
+
], 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' | sdTranslate }}</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' | sdTranslate\"\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' | sdTranslate\" (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' | sdTranslate\" (click)=\"removeColumn(idx)\">\n <span class=\"msi\">delete</span>\n </button>\n </div>\n }\n <!-- why: <a> KH\u00D4NG c\u00F3 href kh\u00F4ng ph\u1EA3i l\u00E0 control \u2014 n\u00F3 kh\u00F4ng nh\u1EADn focus b\u00E0n ph\u00EDm v\u00E0 Enter\n kh\u00F4ng k\u00EDch ho\u1EA1t \u0111\u01B0\u1EE3c, t\u1EE9c ng\u01B0\u1EDDi d\u00F9ng b\u00E0n ph\u00EDm kh\u00F4ng th\u00EAm \u0111\u01B0\u1EE3c c\u1ED9t. \u0110\u00E2y l\u00E0 m\u1ED9t H\u00C0NH\n \u0110\u1ED8NG ch\u1EE9 kh\u00F4ng ph\u1EA3i \u0111i\u1EC1u h\u01B0\u1EDBng \u2192 <button type=\"button\"> m\u1EDBi \u0111\u00FAng ng\u1EEF ngh\u0129a. -->\n <button type=\"button\" class=\"fb-add-link\" (click)=\"addColumn()\">\n <span class=\"msi\">add</span>\n {{ 'core.component.form-builder.add-column' | sdTranslate }}\n </button>\n </div>\n }\n </div>\n</details>\n\n<sd-modal [title]=\"'core.component.form-builder.column-config' | sdTranslate\" 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' | sdTranslate }}</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' | sdTranslate\" [(model)]=\"column.key\"></attribute-input>\n <attribute-input [form]=\"form\" [label]=\"'core.component.form-builder.column-name' | sdTranslate\" [(model)]=\"column.label\"></attribute-input>\n <attribute-input [form]=\"form\" [label]=\"'core.component.form-builder.width' | sdTranslate\" [(model)]=\"column.width\"></attribute-input>\n <attribute-switch [form]=\"form\" [label]=\"'core.component.form-builder.required' | sdTranslate\" [(model)]=\"column.validate!.required\"></attribute-switch>\n <attribute-select [form]=\"form\" [label]=\"'core.component.form-builder.data-type' | sdTranslate\" [(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' | sdTranslate }}</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' | sdTranslate\" [(model)]=\"column.displayOnTrue\"></attribute-input>\n <attribute-input [form]=\"form\" [label]=\"'core.component.form-builder.show-when-false' | sdTranslate\" [(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' | sdTranslate }}</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' | sdTranslate\" [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' | sdTranslate\" (click)=\"onConfirm()\" color=\"primary\"></sd-button>\n </ng-container>\n</sd-modal>\n" }]
|
|
5116
5229
|
}], propDecorators: { modal: [{
|
|
5117
5230
|
type: ViewChild,
|
|
5118
5231
|
args: [SdModal]
|
|
@@ -5133,7 +5246,7 @@ class TableAttribute {
|
|
|
5133
5246
|
component;
|
|
5134
5247
|
set _component(component) {
|
|
5135
5248
|
this.component = component;
|
|
5136
|
-
|
|
5249
|
+
sdFormatComponent(this.component);
|
|
5137
5250
|
}
|
|
5138
5251
|
#changes = new Subject();
|
|
5139
5252
|
#subscription = new Subscription();
|
|
@@ -5164,7 +5277,7 @@ class TableAttribute {
|
|
|
5164
5277
|
...template,
|
|
5165
5278
|
id: this.component.id, // Giữ lại id để componentEmitters định danh được component nào bị thay đổi
|
|
5166
5279
|
});
|
|
5167
|
-
|
|
5280
|
+
sdFormatComponent(this.component);
|
|
5168
5281
|
this.builderService.componentEmitters.next(this.component);
|
|
5169
5282
|
this.ref.markForCheck();
|
|
5170
5283
|
}
|
|
@@ -5173,11 +5286,11 @@ class TableAttribute {
|
|
|
5173
5286
|
this.#changes.next();
|
|
5174
5287
|
};
|
|
5175
5288
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: TableAttribute, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
5176
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: TableAttribute, isStandalone: true, selector: "table-attribute", inputs: { components: { classPropertyName: "components", publicName: "components", isSignal: true, isRequired: true, transformFunction: null }, variables: { classPropertyName: "variables", publicName: "variables", isSignal: true, isRequired: true, transformFunction: null }, _component: { classPropertyName: "_component", publicName: "component", isSignal: false, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<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' |
|
|
5289
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: TableAttribute, isStandalone: true, selector: "table-attribute", inputs: { components: { classPropertyName: "components", publicName: "components", isSignal: true, isRequired: true, transformFunction: null }, variables: { classPropertyName: "variables", publicName: "variables", isSignal: true, isRequired: true, transformFunction: null }, _component: { classPropertyName: "_component", publicName: "component", isSignal: false, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<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' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-template [component]=\"component\" (sdChange)=\"onChangeTemplate($event)\"></attribute-template>\n <attribute-input [label]=\"'core.component.form-builder.field-key' | sdTranslate\" [(model)]=\"component.key\" [disabled]=\"!!component.template\"></attribute-input>\n <attribute-input [form]=\"form\" [label]=\"'core.component.form-builder.label' | sdTranslate\" [(model)]=\"component.label\"></attribute-input>\n </div>\n</details>\n\n@if (!component.properties!.hidden && !component.properties!.viewed) {\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' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-switch [label]=\"'core.component.form-builder.disabled' | sdTranslate\" [(model)]=\"component.disabled\"></attribute-switch>\n <attribute-expression\n [label]=\"'core.component.form-builder.visible-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.visibleWhenExpression\"></attribute-expression>\n <attribute-expression\n [label]=\"'core.component.form-builder.hidden-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.hiddenWhenExpression\"></attribute-expression>\n @if (!component.disabled) {\n <attribute-expression\n [label]=\"'core.component.form-builder.disabled-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.disabledWhenExpression\"></attribute-expression>\n }\n </div>\n </details>\n <details class=\"fb-section\" open>\n <summary class=\"fb-section__head\">\n <span class=\"msi\">verified</span>\n <span class=\"fb-section__title\">{{ 'core.component.form-builder.validation' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-switch [form]=\"form\" [label]=\"'core.component.form-builder.required' | sdTranslate\" [(model)]=\"component.validate!.required\"></attribute-switch>\n @if (!component.validate!.required) {\n <attribute-expression\n [label]=\"'core.component.form-builder.required-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.requiredWhenExpression\"></attribute-expression>\n }\n </div>\n </details>\n <attribute-table\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(columnsKey)]=\"component.columnsKey\"\n [(columns)]=\"component.columns\"\n (columnsKeyChange)=\"onChanges()\"\n (columnsChange)=\"onChanges()\"></attribute-table>\n}\n", dependencies: [{ kind: "component", type: AttributeTemplate, selector: "attribute-template", inputs: ["component"], outputs: ["sdChange"] }, { kind: "component", type: AttributeInput, selector: "attribute-input", inputs: ["form", "label", "disabled", "model"], outputs: ["modelChange", "sdChange"] }, { kind: "component", type: AttributeSwitch, selector: "attribute-switch", inputs: ["form", "label", "model"], outputs: ["modelChange"] }, { kind: "component", type: AttributeTable, selector: "attribute-table", inputs: ["components", "variables", "columnsKey", "columns"], outputs: ["columnsKeyChange", "columnsChange"] }, { kind: "component", type: AttributeExpression, selector: "attribute-expression", inputs: ["components", "variables", "label", "model"], outputs: ["modelChange", "sdChange"] }, { kind: "pipe", type: SdTranslatePipe, name: "sdTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
5177
5290
|
}
|
|
5178
5291
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: TableAttribute, decorators: [{
|
|
5179
5292
|
type: Component,
|
|
5180
|
-
args: [{ selector: 'table-attribute', changeDetection: ChangeDetectionStrategy.OnPush, imports: [AttributeTemplate, AttributeInput, AttributeSwitch, AttributeTable, AttributeExpression,
|
|
5293
|
+
args: [{ selector: 'table-attribute', changeDetection: ChangeDetectionStrategy.OnPush, imports: [AttributeTemplate, AttributeInput, AttributeSwitch, AttributeTable, AttributeExpression, SdTranslatePipe], template: "<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' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-template [component]=\"component\" (sdChange)=\"onChangeTemplate($event)\"></attribute-template>\n <attribute-input [label]=\"'core.component.form-builder.field-key' | sdTranslate\" [(model)]=\"component.key\" [disabled]=\"!!component.template\"></attribute-input>\n <attribute-input [form]=\"form\" [label]=\"'core.component.form-builder.label' | sdTranslate\" [(model)]=\"component.label\"></attribute-input>\n </div>\n</details>\n\n@if (!component.properties!.hidden && !component.properties!.viewed) {\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' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-switch [label]=\"'core.component.form-builder.disabled' | sdTranslate\" [(model)]=\"component.disabled\"></attribute-switch>\n <attribute-expression\n [label]=\"'core.component.form-builder.visible-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.visibleWhenExpression\"></attribute-expression>\n <attribute-expression\n [label]=\"'core.component.form-builder.hidden-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.hiddenWhenExpression\"></attribute-expression>\n @if (!component.disabled) {\n <attribute-expression\n [label]=\"'core.component.form-builder.disabled-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.disabledWhenExpression\"></attribute-expression>\n }\n </div>\n </details>\n <details class=\"fb-section\" open>\n <summary class=\"fb-section__head\">\n <span class=\"msi\">verified</span>\n <span class=\"fb-section__title\">{{ 'core.component.form-builder.validation' | sdTranslate }}</span>\n <span class=\"msi chev\">chevron_right</span>\n </summary>\n <div class=\"fb-section__body\">\n <attribute-switch [form]=\"form\" [label]=\"'core.component.form-builder.required' | sdTranslate\" [(model)]=\"component.validate!.required\"></attribute-switch>\n @if (!component.validate!.required) {\n <attribute-expression\n [label]=\"'core.component.form-builder.required-when' | sdTranslate\"\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(model)]=\"component.properties!.requiredWhenExpression\"></attribute-expression>\n }\n </div>\n </details>\n <attribute-table\n [components]=\"components()\"\n [variables]=\"variables()\"\n [(columnsKey)]=\"component.columnsKey\"\n [(columns)]=\"component.columns\"\n (columnsKeyChange)=\"onChanges()\"\n (columnsChange)=\"onChanges()\"></attribute-table>\n}\n" }]
|
|
5181
5294
|
}], ctorParameters: () => [], propDecorators: { components: [{ type: i0.Input, args: [{ isSignal: true, alias: "components", required: true }] }], variables: [{ type: i0.Input, args: [{ isSignal: true, alias: "variables", required: true }] }], _component: [{
|
|
5182
5295
|
type: Input,
|
|
5183
5296
|
args: [{ alias: 'component', required: true }]
|
|
@@ -5190,7 +5303,7 @@ class TableControl {
|
|
|
5190
5303
|
set _component(component) {
|
|
5191
5304
|
if (this.component !== component) {
|
|
5192
5305
|
this.component = component;
|
|
5193
|
-
|
|
5306
|
+
sdFormatComponent(this.component);
|
|
5194
5307
|
}
|
|
5195
5308
|
}
|
|
5196
5309
|
#subscription = new Subscription();
|
|
@@ -5259,7 +5372,7 @@ class GroupAttribute {
|
|
|
5259
5372
|
// ── constants ────────────────────────────────────────────────────────
|
|
5260
5373
|
iconPresets = ICON_PRESETS;
|
|
5261
5374
|
colorPresets = COLOR_PRESETS;
|
|
5262
|
-
componentIcons =
|
|
5375
|
+
componentIcons = SD_COMPONENT_ICONS;
|
|
5263
5376
|
form = new FormGroup({});
|
|
5264
5377
|
// ── signal input ─────────────────────────────────────────────────────
|
|
5265
5378
|
/** Required group reference. Mutated in place khi user pick icon/color/expression. */
|
|
@@ -5300,36 +5413,25 @@ class GroupAttribute {
|
|
|
5300
5413
|
return 'label' in c ? c.label : c.key || c.id;
|
|
5301
5414
|
};
|
|
5302
5415
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: GroupAttribute, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
5303
|
-
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' |
|
|
5416
|
+
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' | sdTranslate }}</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' | sdTranslate\"></attribute-input>\n\n <div class=\"fb-field\">\n <div class=\"fb-field__label\">{{ 'core.component.form-builder.group-icon' | sdTranslate }}</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' | sdTranslate }}</div>\n <div class=\"fb-color-swatches\">\n <!-- why: \u00F4 m\u00E0u l\u00E0 control ch\u1ECDn m\u00E0u TH\u1EACT nh\u01B0ng l\u1EA1i l\u00E0 <span (click)> \u2014 kh\u00F4ng nh\u1EADn focus,\n Enter/Space kh\u00F4ng ch\u1ECDn \u0111\u01B0\u1EE3c, v\u00E0 tr\u1EA1ng th\u00E1i \"\u0111ang ch\u1ECDn\" ch\u1EC9 t\u1ED3n t\u1EA1i \u1EDF CSS. \u0110\u1ED5i sang\n <button type=\"button\"> (t\u1EF1 focusable + Enter/Space) v\u00E0 khai aria-pressed \u0111\u1EC3 screen\n reader bi\u1EBFt \u00F4 n\u00E0o \u0111ang \u0111\u01B0\u1EE3c ch\u1ECDn. -->\n @for (color of colorPresets; track color) {\n <button\n type=\"button\"\n class=\"fb-color-swatch\"\n [class]=\"color\"\n [class.is-active]=\"_group.properties.color === color\"\n [attr.aria-pressed]=\"_group.properties.color === color\"\n (click)=\"pickColor(color)\"\n [attr.aria-label]=\"color\"></button>\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' | sdTranslate }}</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' | sdTranslate }}</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' | sdTranslate }}</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' | sdTranslate\">\n </attribute-expression>\n <attribute-expression\n [components]=\"[]\"\n [variables]=\"[]\"\n [(model)]=\"_group.properties.hiddenWhenExpression\"\n [label]=\"'core.component.form-builder.hidden-when' | sdTranslate\">\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: SdTranslatePipe, name: "sdTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
5304
5417
|
}
|
|
5305
5418
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: GroupAttribute, decorators: [{
|
|
5306
5419
|
type: Component,
|
|
5307
|
-
args: [{ selector: 'group-attribute', changeDetection: ChangeDetectionStrategy.OnPush, imports: [AttributeInput, AttributeExpression,
|
|
5420
|
+
args: [{ selector: 'group-attribute', changeDetection: ChangeDetectionStrategy.OnPush, imports: [AttributeInput, AttributeExpression, SdTranslatePipe], 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' | sdTranslate }}</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' | sdTranslate\"></attribute-input>\n\n <div class=\"fb-field\">\n <div class=\"fb-field__label\">{{ 'core.component.form-builder.group-icon' | sdTranslate }}</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' | sdTranslate }}</div>\n <div class=\"fb-color-swatches\">\n <!-- why: \u00F4 m\u00E0u l\u00E0 control ch\u1ECDn m\u00E0u TH\u1EACT nh\u01B0ng l\u1EA1i l\u00E0 <span (click)> \u2014 kh\u00F4ng nh\u1EADn focus,\n Enter/Space kh\u00F4ng ch\u1ECDn \u0111\u01B0\u1EE3c, v\u00E0 tr\u1EA1ng th\u00E1i \"\u0111ang ch\u1ECDn\" ch\u1EC9 t\u1ED3n t\u1EA1i \u1EDF CSS. \u0110\u1ED5i sang\n <button type=\"button\"> (t\u1EF1 focusable + Enter/Space) v\u00E0 khai aria-pressed \u0111\u1EC3 screen\n reader bi\u1EBFt \u00F4 n\u00E0o \u0111ang \u0111\u01B0\u1EE3c ch\u1ECDn. -->\n @for (color of colorPresets; track color) {\n <button\n type=\"button\"\n class=\"fb-color-swatch\"\n [class]=\"color\"\n [class.is-active]=\"_group.properties.color === color\"\n [attr.aria-pressed]=\"_group.properties.color === color\"\n (click)=\"pickColor(color)\"\n [attr.aria-label]=\"color\"></button>\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' | sdTranslate }}</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' | sdTranslate }}</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' | sdTranslate }}</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' | sdTranslate\">\n </attribute-expression>\n <attribute-expression\n [components]=\"[]\"\n [variables]=\"[]\"\n [(model)]=\"_group.properties.hiddenWhenExpression\"\n [label]=\"'core.component.form-builder.hidden-when' | sdTranslate\">\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"] }]
|
|
5308
5421
|
}], ctorParameters: () => [], propDecorators: { group: [{ type: i0.Input, args: [{ isSignal: true, alias: "group", required: true }] }] } });
|
|
5309
5422
|
|
|
5310
|
-
const ValidationAlerts = [
|
|
5311
|
-
{
|
|
5312
|
-
value: 'error',
|
|
5313
|
-
display: 'Lỗi',
|
|
5314
|
-
},
|
|
5315
|
-
{
|
|
5316
|
-
value: 'warning',
|
|
5317
|
-
display: 'Cảnh báo',
|
|
5318
|
-
},
|
|
5319
|
-
];
|
|
5320
|
-
|
|
5321
5423
|
// Component xây dựng Expression dựa vào SdFormGenericExpression
|
|
5322
5424
|
class ExpressionBuilderComponent {
|
|
5323
5425
|
ref = inject(ChangeDetectorRef);
|
|
5324
5426
|
modal;
|
|
5325
5427
|
form = new FormGroup({});
|
|
5326
|
-
attributeOperators =
|
|
5428
|
+
attributeOperators = SD_ATTRIBUTE_OPERATORS;
|
|
5327
5429
|
attributes = [];
|
|
5328
5430
|
numberAttributes = [];
|
|
5329
5431
|
dateAttributes = [];
|
|
5330
5432
|
attribute = {};
|
|
5331
|
-
types =
|
|
5332
|
-
previouses =
|
|
5433
|
+
types = SD_DAY_INFO_TYPES;
|
|
5434
|
+
previouses = SD_DAY_INFO_PREVIOUSES;
|
|
5333
5435
|
label;
|
|
5334
5436
|
set _attributes(attributes) {
|
|
5335
5437
|
this.attributes = attributes || [];
|
|
@@ -5383,6 +5485,15 @@ class ExpressionBuilderComponent {
|
|
|
5383
5485
|
this.modal?.open?.();
|
|
5384
5486
|
this.ref.markForCheck();
|
|
5385
5487
|
};
|
|
5488
|
+
// why: pill `.fb-expr` là role="button" + tabindex="0" nên Enter/Space PHẢI mở modal đúng như
|
|
5489
|
+
// click. Lọc theo target để phím bấm trên nút "functions" lồng bên trong không mở modal hai lần.
|
|
5490
|
+
onExprKeydown = (event) => {
|
|
5491
|
+
if (event.target !== event.currentTarget)
|
|
5492
|
+
return;
|
|
5493
|
+
// why: chặn Space cuộn trang.
|
|
5494
|
+
event.preventDefault();
|
|
5495
|
+
void this.onEdit();
|
|
5496
|
+
};
|
|
5386
5497
|
addCondition = (conditions) => {
|
|
5387
5498
|
conditions.push({
|
|
5388
5499
|
key: Utilities.randomId(),
|
|
@@ -5430,14 +5541,15 @@ class ExpressionBuilderComponent {
|
|
|
5430
5541
|
condition.value = 'now';
|
|
5431
5542
|
}
|
|
5432
5543
|
else if (condition.dayInfo?.type === 'RELATED' && condition.dayInfo.relatedValue) {
|
|
5433
|
-
condition.value =
|
|
5544
|
+
condition.value =
|
|
5545
|
+
SD_DAY_INFO_PREVIOUSES.find(e => e.value === condition.dayInfo.related)?.format(condition.dayInfo.relatedValue) ?? null;
|
|
5434
5546
|
}
|
|
5435
5547
|
else if (condition.value?.type === 'DATETIME' && condition.value) {
|
|
5436
|
-
condition.value = DateUtilities
|
|
5548
|
+
condition.value = DateUtilities.toFormat(condition.value, 'yyyy/MM/dd HH:mm:00');
|
|
5437
5549
|
}
|
|
5438
5550
|
};
|
|
5439
5551
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: ExpressionBuilderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
5440
|
-
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", "mask", "hideInlineError", "blurOnEnter", "clearable", "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", "clearable", "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", "beforeClose"], outputs: ["sdClosed", "sdCloseError"] }, { 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 });
|
|
5552
|
+
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 <!-- why: pill n\u00E0y \u0111\u00E3 khai role=\"button\" + tabindex=\"0\" nh\u01B0ng CH\u1EC8 c\u00F3 (click) \u2014 tab t\u1EDBi \u0111\u01B0\u1EE3c m\u00E0\n Enter/Space kh\u00F4ng m\u1EDF \u0111\u01B0\u1EE3c modal, t\u1EE9c l\u00E0 m\u1ED9t n\u00FAt gi\u1EA3. B\u1ED5 sung handler b\u00E0n ph\u00EDm ph\u1EA3n chi\u1EBFu\n \u0111\u00FAng click; l\u1ECDc theo target \u0111\u1EC3 Enter tr\u00EAn n\u00FAt \"functions\" b\u00EAn trong kh\u00F4ng m\u1EDF modal hai l\u1EA7n. -->\n <div class=\"fb-expr\" (click)=\"onEdit()\" (keydown.enter)=\"onExprKeydown($event)\" (keydown.space)=\"onExprKeydown($event)\" 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' | sdTranslate }}</span>\n }\n <button\n type=\"button\"\n class=\"fb-icon-btn\"\n (click)=\"onEdit(); $event.stopPropagation()\"\n [attr.aria-label]=\"'core.component.form-builder.set-expression' | sdTranslate\">\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' | sdTranslate\" 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' | sdTranslate\"\n color=\"primary\"\n (click)=\"addCombinator(expression.conditions)\"></sd-button>\n <sd-button\n prefixIcon=\"add\"\n [title]=\"'core.component.form-builder.condition' | sdTranslate\"\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' | sdTranslate\"\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\" type=\"text\" color=\"error\" [tooltip]=\"'core.component.form-builder.delete' | sdTranslate\" (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\" type=\"text\" color=\"error\" [tooltip]=\"'core.component.form-builder.delete' | sdTranslate\" (click)=\"remove(expression.conditions, idxLv1)\"></sd-button>\n <sd-button\n prefixIcon=\"add\"\n [title]=\"'core.component.form-builder.condition' | sdTranslate\"\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' | sdTranslate\"\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 <!-- why: ch\u1ED7 n\u00E0y t\u1EEBng truy\u1EC1n `idxLv1` \u2014 index c\u1EE7a nh\u00F3m \u1EDF C\u1EA4P 1 \u2014 v\u00E0o m\u1EA3ng\n \u0111i\u1EC1u ki\u1EC7n c\u1EA5p 2, n\u00EAn b\u1EA5m xo\u00E1 m\u1ED9t d\u00F2ng con s\u1EBD xo\u00E1 nh\u1EA7m d\u00F2ng kh\u00E1c (ho\u1EB7c\n kh\u00F4ng xo\u00E1 g\u00EC khi nh\u00F3m n\u1EB1m \u1EDF v\u1ECB tr\u00ED v\u01B0\u1EE3t qu\u00E1 s\u1ED1 con). Ph\u1EA3i l\u00E0 `idxLv2`. -->\n <sd-button\n class=\"ml-auto\"\n prefixIcon=\"delete\"\n type=\"text\"\n color=\"error\"\n [tooltip]=\"'core.component.form-builder.delete' | sdTranslate\"\n (click)=\"remove(conditionLv1.conditions, idxLv2)\"></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' | sdTranslate\" (click)=\"onAccept()\" color=\"primary\" type=\"fill\"></sd-button>\n </ng-container>\n </sd-modal>\n}\n", styles: [":host{display:block}.fb-field{display:flex;flex-direction:column;gap:4px}.fb-field__label{font-size:11px;font-weight:500;color:var(--md-sys-color-on-surface-variant, #5f6368)}.fb-expr{display:flex;align-items:center;gap:6px;min-height:32px;padding:0 4px 0 0;border-radius:6px;background:var(--md-sys-color-surface-container-lowest, #fff);border:1px solid var(--md-sys-color-outline-variant, #e0e0e0);font-size:12px;color:var(--md-sys-color-on-surface-variant, #5f6368);cursor:pointer;transition:border-color .12s ease}.fb-expr:hover,.fb-expr:focus-visible{border-color:var(--md-sys-color-primary, #2a66f4);outline:none}.fb-expr__badge{align-self:stretch;display:inline-flex;align-items:center;justify-content:center;padding:0 6px;background:var(--md-sys-color-primary-container, #eaf1ff);color:var(--md-sys-color-primary, #2a66f4);font-family:Roboto Mono,monospace;font-size:10px;font-weight:500;border-radius:5px 0 0 5px}.fb-expr__code{flex:1;min-width:0;font-family:Roboto Mono,monospace;font-size:11px;color:var(--md-sys-color-on-surface, #1f1f1f);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-expr__code .var{color:var(--md-sys-color-primary, #2a66f4);font-weight:500}.fb-expr__code .op{color:var(--md-sys-color-error, #b3261e)}.fb-expr__code .lit{color:var(--md-sys-color-success, #1e8e3e)}.fb-expr .empty{flex:1;padding-left:6px;color:var(--md-sys-color-on-surface-variant, #5f6368);font-style:italic}.fb-expr .fb-icon-btn{flex-shrink:0;width:24px;height:24px;display:inline-flex;align-items:center;justify-content:center;border:none;border-radius:4px;background:transparent;color:var(--md-sys-color-on-surface-variant, #5f6368);cursor:pointer}.fb-expr .fb-icon-btn:hover{background:#0000000f;color:var(--md-sys-color-on-surface, #1f1f1f)}.fb-expr .fb-icon-btn .msi{font-family:Material Symbols Outlined,Material Icons;font-size:18px;line-height:1}::ng-deep .sd-form-builder-dialog .c-builder{padding:8px;width:100%;border:1px solid var(--sd-border, #e6e6e6);border-radius:8px}::ng-deep .sd-form-builder-dialog .c-builder .c-condition{width:80px;flex-shrink:0}::ng-deep .sd-form-builder-dialog .c-builder .c-condition .c-where{width:80px;border:1px solid var(--sd-border, #e6e6e6);background-color:var(--sd-surface-variant, #f2f2f2);color:var(--sd-text-secondary, #757575);border-radius:4px;padding:4px;text-align:center;font-size:12px}::ng-deep .sd-form-builder-dialog .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", "mask", "hideInlineError", "blurOnEnter", "clearable", "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", "clearable", "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", "showSelectAll", "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", "beforeClose"], outputs: ["sdClosed", "sdCloseError"] }, { 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", "transform", "model"], outputs: ["modelChange", "sdChange", "sdFocus"] }, { kind: "pipe", type: ExpressionQueryPipe, name: "expressionQuery" }, { kind: "pipe", type: SdTranslatePipe, name: "sdTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
5441
5553
|
}
|
|
5442
5554
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: ExpressionBuilderComponent, decorators: [{
|
|
5443
5555
|
type: Component,
|
|
@@ -5452,8 +5564,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
5452
5564
|
ExpressionQueryPipe,
|
|
5453
5565
|
SdDatetime,
|
|
5454
5566
|
SdSuffixDefDirective,
|
|
5455
|
-
|
|
5456
|
-
], 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"] }]
|
|
5567
|
+
SdTranslatePipe,
|
|
5568
|
+
], 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 <!-- why: pill n\u00E0y \u0111\u00E3 khai role=\"button\" + tabindex=\"0\" nh\u01B0ng CH\u1EC8 c\u00F3 (click) \u2014 tab t\u1EDBi \u0111\u01B0\u1EE3c m\u00E0\n Enter/Space kh\u00F4ng m\u1EDF \u0111\u01B0\u1EE3c modal, t\u1EE9c l\u00E0 m\u1ED9t n\u00FAt gi\u1EA3. B\u1ED5 sung handler b\u00E0n ph\u00EDm ph\u1EA3n chi\u1EBFu\n \u0111\u00FAng click; l\u1ECDc theo target \u0111\u1EC3 Enter tr\u00EAn n\u00FAt \"functions\" b\u00EAn trong kh\u00F4ng m\u1EDF modal hai l\u1EA7n. -->\n <div class=\"fb-expr\" (click)=\"onEdit()\" (keydown.enter)=\"onExprKeydown($event)\" (keydown.space)=\"onExprKeydown($event)\" 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' | sdTranslate }}</span>\n }\n <button\n type=\"button\"\n class=\"fb-icon-btn\"\n (click)=\"onEdit(); $event.stopPropagation()\"\n [attr.aria-label]=\"'core.component.form-builder.set-expression' | sdTranslate\">\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' | sdTranslate\" 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' | sdTranslate\"\n color=\"primary\"\n (click)=\"addCombinator(expression.conditions)\"></sd-button>\n <sd-button\n prefixIcon=\"add\"\n [title]=\"'core.component.form-builder.condition' | sdTranslate\"\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' | sdTranslate\"\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\" type=\"text\" color=\"error\" [tooltip]=\"'core.component.form-builder.delete' | sdTranslate\" (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\" type=\"text\" color=\"error\" [tooltip]=\"'core.component.form-builder.delete' | sdTranslate\" (click)=\"remove(expression.conditions, idxLv1)\"></sd-button>\n <sd-button\n prefixIcon=\"add\"\n [title]=\"'core.component.form-builder.condition' | sdTranslate\"\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' | sdTranslate\"\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 <!-- why: ch\u1ED7 n\u00E0y t\u1EEBng truy\u1EC1n `idxLv1` \u2014 index c\u1EE7a nh\u00F3m \u1EDF C\u1EA4P 1 \u2014 v\u00E0o m\u1EA3ng\n \u0111i\u1EC1u ki\u1EC7n c\u1EA5p 2, n\u00EAn b\u1EA5m xo\u00E1 m\u1ED9t d\u00F2ng con s\u1EBD xo\u00E1 nh\u1EA7m d\u00F2ng kh\u00E1c (ho\u1EB7c\n kh\u00F4ng xo\u00E1 g\u00EC khi nh\u00F3m n\u1EB1m \u1EDF v\u1ECB tr\u00ED v\u01B0\u1EE3t qu\u00E1 s\u1ED1 con). Ph\u1EA3i l\u00E0 `idxLv2`. -->\n <sd-button\n class=\"ml-auto\"\n prefixIcon=\"delete\"\n type=\"text\"\n color=\"error\"\n [tooltip]=\"'core.component.form-builder.delete' | sdTranslate\"\n (click)=\"remove(conditionLv1.conditions, idxLv2)\"></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' | sdTranslate\" (click)=\"onAccept()\" color=\"primary\" type=\"fill\"></sd-button>\n </ng-container>\n </sd-modal>\n}\n", styles: [":host{display:block}.fb-field{display:flex;flex-direction:column;gap:4px}.fb-field__label{font-size:11px;font-weight:500;color:var(--md-sys-color-on-surface-variant, #5f6368)}.fb-expr{display:flex;align-items:center;gap:6px;min-height:32px;padding:0 4px 0 0;border-radius:6px;background:var(--md-sys-color-surface-container-lowest, #fff);border:1px solid var(--md-sys-color-outline-variant, #e0e0e0);font-size:12px;color:var(--md-sys-color-on-surface-variant, #5f6368);cursor:pointer;transition:border-color .12s ease}.fb-expr:hover,.fb-expr:focus-visible{border-color:var(--md-sys-color-primary, #2a66f4);outline:none}.fb-expr__badge{align-self:stretch;display:inline-flex;align-items:center;justify-content:center;padding:0 6px;background:var(--md-sys-color-primary-container, #eaf1ff);color:var(--md-sys-color-primary, #2a66f4);font-family:Roboto Mono,monospace;font-size:10px;font-weight:500;border-radius:5px 0 0 5px}.fb-expr__code{flex:1;min-width:0;font-family:Roboto Mono,monospace;font-size:11px;color:var(--md-sys-color-on-surface, #1f1f1f);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-expr__code .var{color:var(--md-sys-color-primary, #2a66f4);font-weight:500}.fb-expr__code .op{color:var(--md-sys-color-error, #b3261e)}.fb-expr__code .lit{color:var(--md-sys-color-success, #1e8e3e)}.fb-expr .empty{flex:1;padding-left:6px;color:var(--md-sys-color-on-surface-variant, #5f6368);font-style:italic}.fb-expr .fb-icon-btn{flex-shrink:0;width:24px;height:24px;display:inline-flex;align-items:center;justify-content:center;border:none;border-radius:4px;background:transparent;color:var(--md-sys-color-on-surface-variant, #5f6368);cursor:pointer}.fb-expr .fb-icon-btn:hover{background:#0000000f;color:var(--md-sys-color-on-surface, #1f1f1f)}.fb-expr .fb-icon-btn .msi{font-family:Material Symbols Outlined,Material Icons;font-size:18px;line-height:1}::ng-deep .sd-form-builder-dialog .c-builder{padding:8px;width:100%;border:1px solid var(--sd-border, #e6e6e6);border-radius:8px}::ng-deep .sd-form-builder-dialog .c-builder .c-condition{width:80px;flex-shrink:0}::ng-deep .sd-form-builder-dialog .c-builder .c-condition .c-where{width:80px;border:1px solid var(--sd-border, #e6e6e6);background-color:var(--sd-surface-variant, #f2f2f2);color:var(--sd-text-secondary, #757575);border-radius:4px;padding:4px;text-align:center;font-size:12px}::ng-deep .sd-form-builder-dialog .c-builder .c-condition .c-select{margin-left:10px;width:70px}\n"] }]
|
|
5457
5569
|
}], ctorParameters: () => [], propDecorators: { modal: [{
|
|
5458
5570
|
type: ViewChild,
|
|
5459
5571
|
args: [SdModal]
|
|
@@ -5497,7 +5609,7 @@ class ConfigureValidationComponent {
|
|
|
5497
5609
|
else {
|
|
5498
5610
|
this.validations = [];
|
|
5499
5611
|
}
|
|
5500
|
-
this.attributes =
|
|
5612
|
+
this.attributes = sdGetAttributes(formGeneric.components);
|
|
5501
5613
|
if (formGeneric) {
|
|
5502
5614
|
this.modal?.open();
|
|
5503
5615
|
this.ref.markForCheck();
|
|
@@ -5535,11 +5647,11 @@ class ConfigureValidationComponent {
|
|
|
5535
5647
|
this.modal?.close();
|
|
5536
5648
|
};
|
|
5537
5649
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: ConfigureValidationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
5538
|
-
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$1.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$1.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i1$1.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", "beforeClose"], outputs: ["sdClosed", "sdCloseError"] }, { 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", "mask", "hideInlineError", "blurOnEnter", "clearable", "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 });
|
|
5650
|
+
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' | sdTranslate\"\n view=\"dialog\"\n width=\"800px\"\n modalClass=\"sd-form-builder-dialog\"\n #popupConfigureVariables>\n <!-- why: tr\u01B0\u1EDBc \u0111\u00E2y m\u1ED7i lu\u1EADt l\u00E0 m\u1ED9t kh\u1ED1i mat-elevation-z2 v\u1EDBi inline style, n\u00FAt xo\u00E1 l\u00E0 <sd-icon>\n (kh\u00F4ng ph\u1EA3i n\u00FAt th\u1EADt \u2014 kh\u00F4ng focus/Enter \u0111\u01B0\u1EE3c), v\u00E0 kh\u00F4ng c\u00F3 tr\u1EA1ng th\u00E1i r\u1ED7ng n\u00EAn dialog m\u1EDF ra\n tr\u1ED1ng tr\u01A1n kh\u00F4ng n\u00F3i g\u00EC. -->\n <div class=\"fb-validations\">\n @if (validations.length) {\n @for (validation of validations; track $index; let idx = $index) {\n <div class=\"fb-validations__card\">\n <div class=\"fb-validations__head\">\n <sd-select\n class=\"fb-validations__type\"\n size=\"sm\"\n [label]=\"'core.component.form-builder.message-type' | sdTranslate\"\n [(model)]=\"validation.alert\"\n [items]=\"alerts\"\n valueField=\"value\"\n displayField=\"display\"\n required></sd-select>\n\n @if (validation.type === 'expression') {\n <sd-input\n class=\"fb-validations__message\"\n size=\"sm\"\n [form]=\"form\"\n [label]=\"'core.component.form-builder.message-display' | sdTranslate\"\n [(model)]=\"validation.message\"\n required></sd-input>\n } @else if (validation.type === 'function') {\n <sd-select\n class=\"fb-validations__message\"\n size=\"sm\"\n [form]=\"form\"\n [label]=\"'core.component.form-builder.available-methods' | sdTranslate\"\n [(model)]=\"validation.code\"\n [items]=\"functions\"\n valueField=\"value\"\n displayField=\"display\"\n required></sd-select>\n }\n\n <sd-button\n class=\"fb-validations__remove\"\n prefixIcon=\"delete\"\n type=\"text\"\n color=\"error\"\n [tooltip]=\"'core.component.form-builder.delete' | sdTranslate\"\n (click)=\"removaValidation(idx)\"></sd-button>\n </div>\n\n @if (validation.type === 'expression') {\n <expression-builder\n [label]=\"'core.component.form-builder.condition-expression' | sdTranslate\"\n [(model)]=\"validation.expression\"\n [attributes]=\"attributes\"></expression-builder>\n }\n </div>\n }\n } @else {\n <p class=\"fb-validations__empty\">{{ 'core.component.form-builder.no-validation' | sdTranslate }}</p>\n }\n\n <sd-button\n class=\"fb-validations__add\"\n [title]=\"'core.component.form-builder.add-validation' | sdTranslate\"\n [matMenuTriggerFor]=\"menu\"\n prefixIcon=\"add\"\n color=\"primary\"\n type=\"text\"></sd-button>\n </div>\n\n <mat-menu #menu=\"matMenu\" yPosition=\"above\" panelClass=\"fb-validation-menu\">\n <button (click)=\"addValidation('expression')\" mat-menu-item type=\"button\">\n <span>{{ 'core.component.form-builder.expression' | sdTranslate }}</span>\n </button>\n <button (click)=\"addValidation('function')\" mat-menu-item type=\"button\">\n <span>{{ 'core.component.form-builder.available-methods' | sdTranslate }}</span>\n </button>\n </mat-menu>\n\n <ng-container sdFooterRight>\n <sd-button type=\"fill\" [title]=\"'core.component.form-builder.save' | sdTranslate\" (click)=\"updateValidations()\" prefixIcon=\"save\" color=\"primary\"></sd-button>\n </ng-container>\n</sd-modal>\n", styles: ["::ng-deep .sd-form-builder-dialog .fb-validations{display:flex;flex-direction:column;gap:12px}::ng-deep .sd-form-builder-dialog .fb-validations__card{display:flex;flex-direction:column;gap:8px;padding:12px;border:1px solid var(--sd-border, #e6e6e6);border-radius:8px;background:var(--sd-surface, #fff)}::ng-deep .sd-form-builder-dialog .fb-validations__head{display:grid;grid-template-columns:minmax(140px,220px) minmax(0,1fr) auto;align-items:center;gap:8px}::ng-deep .sd-form-builder-dialog .fb-validations__empty{margin:0;padding:16px;border:1px dashed var(--sd-border, #e6e6e6);border-radius:8px;color:var(--sd-text-secondary, #6b7280);font-size:13px;text-align:center}::ng-deep .sd-form-builder-dialog .fb-validations__add{align-self:flex-start}\n"], dependencies: [{ kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i1$1.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$1.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i1$1.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", "beforeClose"], outputs: ["sdClosed", "sdCloseError"] }, { 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", "mask", "hideInlineError", "blurOnEnter", "clearable", "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", "showSelectAll", "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: SdTranslatePipe, name: "sdTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
5539
5651
|
}
|
|
5540
5652
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: ConfigureValidationComponent, decorators: [{
|
|
5541
5653
|
type: Component,
|
|
5542
|
-
args: [{ selector: 'configure-validation', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
|
|
5654
|
+
args: [{ selector: 'configure-validation', changeDetection: ChangeDetectionStrategy.OnPush, imports: [MatMenuModule, SdModal, SdButton, SdInput, SdSelect, ExpressionBuilderComponent, SdTranslatePipe], template: "<sd-modal\n [title]=\"'core.component.form-builder.config-validation-error-warning' | sdTranslate\"\n view=\"dialog\"\n width=\"800px\"\n modalClass=\"sd-form-builder-dialog\"\n #popupConfigureVariables>\n <!-- why: tr\u01B0\u1EDBc \u0111\u00E2y m\u1ED7i lu\u1EADt l\u00E0 m\u1ED9t kh\u1ED1i mat-elevation-z2 v\u1EDBi inline style, n\u00FAt xo\u00E1 l\u00E0 <sd-icon>\n (kh\u00F4ng ph\u1EA3i n\u00FAt th\u1EADt \u2014 kh\u00F4ng focus/Enter \u0111\u01B0\u1EE3c), v\u00E0 kh\u00F4ng c\u00F3 tr\u1EA1ng th\u00E1i r\u1ED7ng n\u00EAn dialog m\u1EDF ra\n tr\u1ED1ng tr\u01A1n kh\u00F4ng n\u00F3i g\u00EC. -->\n <div class=\"fb-validations\">\n @if (validations.length) {\n @for (validation of validations; track $index; let idx = $index) {\n <div class=\"fb-validations__card\">\n <div class=\"fb-validations__head\">\n <sd-select\n class=\"fb-validations__type\"\n size=\"sm\"\n [label]=\"'core.component.form-builder.message-type' | sdTranslate\"\n [(model)]=\"validation.alert\"\n [items]=\"alerts\"\n valueField=\"value\"\n displayField=\"display\"\n required></sd-select>\n\n @if (validation.type === 'expression') {\n <sd-input\n class=\"fb-validations__message\"\n size=\"sm\"\n [form]=\"form\"\n [label]=\"'core.component.form-builder.message-display' | sdTranslate\"\n [(model)]=\"validation.message\"\n required></sd-input>\n } @else if (validation.type === 'function') {\n <sd-select\n class=\"fb-validations__message\"\n size=\"sm\"\n [form]=\"form\"\n [label]=\"'core.component.form-builder.available-methods' | sdTranslate\"\n [(model)]=\"validation.code\"\n [items]=\"functions\"\n valueField=\"value\"\n displayField=\"display\"\n required></sd-select>\n }\n\n <sd-button\n class=\"fb-validations__remove\"\n prefixIcon=\"delete\"\n type=\"text\"\n color=\"error\"\n [tooltip]=\"'core.component.form-builder.delete' | sdTranslate\"\n (click)=\"removaValidation(idx)\"></sd-button>\n </div>\n\n @if (validation.type === 'expression') {\n <expression-builder\n [label]=\"'core.component.form-builder.condition-expression' | sdTranslate\"\n [(model)]=\"validation.expression\"\n [attributes]=\"attributes\"></expression-builder>\n }\n </div>\n }\n } @else {\n <p class=\"fb-validations__empty\">{{ 'core.component.form-builder.no-validation' | sdTranslate }}</p>\n }\n\n <sd-button\n class=\"fb-validations__add\"\n [title]=\"'core.component.form-builder.add-validation' | sdTranslate\"\n [matMenuTriggerFor]=\"menu\"\n prefixIcon=\"add\"\n color=\"primary\"\n type=\"text\"></sd-button>\n </div>\n\n <mat-menu #menu=\"matMenu\" yPosition=\"above\" panelClass=\"fb-validation-menu\">\n <button (click)=\"addValidation('expression')\" mat-menu-item type=\"button\">\n <span>{{ 'core.component.form-builder.expression' | sdTranslate }}</span>\n </button>\n <button (click)=\"addValidation('function')\" mat-menu-item type=\"button\">\n <span>{{ 'core.component.form-builder.available-methods' | sdTranslate }}</span>\n </button>\n </mat-menu>\n\n <ng-container sdFooterRight>\n <sd-button type=\"fill\" [title]=\"'core.component.form-builder.save' | sdTranslate\" (click)=\"updateValidations()\" prefixIcon=\"save\" color=\"primary\"></sd-button>\n </ng-container>\n</sd-modal>\n", styles: ["::ng-deep .sd-form-builder-dialog .fb-validations{display:flex;flex-direction:column;gap:12px}::ng-deep .sd-form-builder-dialog .fb-validations__card{display:flex;flex-direction:column;gap:8px;padding:12px;border:1px solid var(--sd-border, #e6e6e6);border-radius:8px;background:var(--sd-surface, #fff)}::ng-deep .sd-form-builder-dialog .fb-validations__head{display:grid;grid-template-columns:minmax(140px,220px) minmax(0,1fr) auto;align-items:center;gap:8px}::ng-deep .sd-form-builder-dialog .fb-validations__empty{margin:0;padding:16px;border:1px dashed var(--sd-border, #e6e6e6);border-radius:8px;color:var(--sd-text-secondary, #6b7280);font-size:13px;text-align:center}::ng-deep .sd-form-builder-dialog .fb-validations__add{align-self:flex-start}\n"] }]
|
|
5543
5655
|
}], ctorParameters: () => [], propDecorators: { modal: [{
|
|
5544
5656
|
type: ViewChild,
|
|
5545
5657
|
args: [SdModal]
|
|
@@ -5644,8 +5756,8 @@ class SdFormBuilder {
|
|
|
5644
5756
|
/** Input chính: schema form. Mỗi lần ref thay đổi → effect sẽ clone về local arrays. */
|
|
5645
5757
|
formGeneric = input(undefined, ...(ngDevMode ? [{ debugName: "formGeneric" }] : []));
|
|
5646
5758
|
// ── component registry (immutable) ─────────────────────────────────────
|
|
5647
|
-
formBuilderComponents =
|
|
5648
|
-
componentIcons =
|
|
5759
|
+
formBuilderComponents = SD_FORM_BUILDER_COMPONENTS;
|
|
5760
|
+
componentIcons = SD_COMPONENT_ICONS;
|
|
5649
5761
|
// ── palette state (signal + computed group buckets) ────────────────────
|
|
5650
5762
|
paletteSearch = signal('', ...(ngDevMode ? [{ debugName: "paletteSearch" }] : []));
|
|
5651
5763
|
paletteGroups = computed(() => {
|
|
@@ -5855,7 +5967,7 @@ class SdFormBuilder {
|
|
|
5855
5967
|
// why: không cho thêm group khi đang Detail trong 1 group (tránh group lồng group).
|
|
5856
5968
|
if (item.type === 'group' && this.editingGroupId())
|
|
5857
5969
|
return;
|
|
5858
|
-
const id =
|
|
5970
|
+
const id = sdGenerateId();
|
|
5859
5971
|
const columns = item.type === 'break' ? '12' : layoutColumns;
|
|
5860
5972
|
let newComponent;
|
|
5861
5973
|
if (item.type === 'group') {
|
|
@@ -5877,7 +5989,7 @@ class SdFormBuilder {
|
|
|
5877
5989
|
// (auto-gen cho stability) nhưng không hiển thị trong attribute panel.
|
|
5878
5990
|
newComponent = {
|
|
5879
5991
|
id,
|
|
5880
|
-
key:
|
|
5992
|
+
key: sdGenerateKey(),
|
|
5881
5993
|
type: 'break',
|
|
5882
5994
|
label: 'Break',
|
|
5883
5995
|
layout: { columns: '12' },
|
|
@@ -5889,7 +6001,7 @@ class SdFormBuilder {
|
|
|
5889
6001
|
else {
|
|
5890
6002
|
newComponent = {
|
|
5891
6003
|
id,
|
|
5892
|
-
key:
|
|
6004
|
+
key: sdGenerateKey(),
|
|
5893
6005
|
type: item.type,
|
|
5894
6006
|
label: item.type,
|
|
5895
6007
|
layout: { columns },
|
|
@@ -5967,7 +6079,7 @@ class SdFormBuilder {
|
|
|
5967
6079
|
const p = item.properties || {};
|
|
5968
6080
|
return !!(p.visibleWhenExpression || p.hiddenWhenExpression || p.disabledWhenExpression || p.requiredWhenExpression);
|
|
5969
6081
|
};
|
|
5970
|
-
/** Map
|
|
6082
|
+
/** Map Color preset → 2 CSS var names cho header-bg và header-fg của group card. */
|
|
5971
6083
|
groupColorVars = (color) => {
|
|
5972
6084
|
switch (color) {
|
|
5973
6085
|
case 'secondary':
|
|
@@ -5989,6 +6101,15 @@ class SdFormBuilder {
|
|
|
5989
6101
|
};
|
|
5990
6102
|
// ── Group drill-in (Detail) navigation ──────────────────────────────────
|
|
5991
6103
|
/** Mở canvas thiết kế riêng cho 1 group (chỉ children của nó). Snapshot để Cancel revert. */
|
|
6104
|
+
// why: body group nay là role="button" + tabindex="0" nên Enter/Space phải mở màn hình Detail
|
|
6105
|
+
// đúng như click. stopPropagation để item cha (cũng là role="button") không tự chọn lại.
|
|
6106
|
+
onEnterGroupEditKeydown = (group, event) => {
|
|
6107
|
+
if (event.target !== event.currentTarget)
|
|
6108
|
+
return;
|
|
6109
|
+
event.preventDefault();
|
|
6110
|
+
event.stopPropagation();
|
|
6111
|
+
this.enterGroupEdit(group);
|
|
6112
|
+
};
|
|
5992
6113
|
enterGroupEdit = (group) => {
|
|
5993
6114
|
this.#groupSnapshot = JSON.stringify(group.components ?? []);
|
|
5994
6115
|
this.editingGroupId.set(group.id);
|
|
@@ -6024,8 +6145,8 @@ class SdFormBuilder {
|
|
|
6024
6145
|
const lastItemId = row.items[row.items.length - 1]?.id;
|
|
6025
6146
|
const insertAfter = lastItemId ? scope.findIndex(c => c.id === lastItemId) : scope.length - 1;
|
|
6026
6147
|
const newBreak = {
|
|
6027
|
-
id:
|
|
6028
|
-
key:
|
|
6148
|
+
id: sdGenerateId(),
|
|
6149
|
+
key: sdGenerateKey(),
|
|
6029
6150
|
type: 'break',
|
|
6030
6151
|
label: 'Break',
|
|
6031
6152
|
layout: { columns: '12' },
|
|
@@ -6054,6 +6175,15 @@ class SdFormBuilder {
|
|
|
6054
6175
|
this.selectedComponent.set(item);
|
|
6055
6176
|
this.#ref.markForCheck();
|
|
6056
6177
|
};
|
|
6178
|
+
// why: mục palette nay là role="button" + tabindex="0" nên Enter/Space phải thêm component đúng
|
|
6179
|
+
// như click — trước đây chỉ kéo-thả hoặc click chuột mới dựng được form.
|
|
6180
|
+
onPaletteItemKeydown = (item, event) => {
|
|
6181
|
+
if (event.target !== event.currentTarget)
|
|
6182
|
+
return;
|
|
6183
|
+
// why: chặn Space cuộn trang.
|
|
6184
|
+
event.preventDefault();
|
|
6185
|
+
this.addComponent(item);
|
|
6186
|
+
};
|
|
6057
6187
|
onClickedOutside = (e) => {
|
|
6058
6188
|
const classList = e.target.classList;
|
|
6059
6189
|
if (!classList.length || classList.contains('components') || classList.contains('cdk-drop-list')) {
|
|
@@ -6302,7 +6432,9 @@ class SdFormBuilder {
|
|
|
6302
6432
|
// Copy form hiện tại
|
|
6303
6433
|
jsonString;
|
|
6304
6434
|
viewJSON = () => {
|
|
6305
|
-
|
|
6435
|
+
// why: JSON một dòng thì không đọc nổi và cũng không sửa được bằng tay; schema này lồng nhiều
|
|
6436
|
+
// tầng nên in thụt lề mới dùng được với editor.
|
|
6437
|
+
this.jsonString = JSON.stringify({ components: this.components }, null, 2);
|
|
6306
6438
|
this.popupViewJSON()?.open();
|
|
6307
6439
|
this.#ref.markForCheck();
|
|
6308
6440
|
};
|
|
@@ -6491,17 +6623,17 @@ class SdFormBuilder {
|
|
|
6491
6623
|
return clonedComponent;
|
|
6492
6624
|
};
|
|
6493
6625
|
#regenerateComponentIdentity = (component) => {
|
|
6494
|
-
component.id =
|
|
6626
|
+
component.id = sdGenerateId();
|
|
6495
6627
|
if (component.type === 'group') {
|
|
6496
6628
|
component.components = component.components.map(child => this.#cloneComponentWithNewIdentity(child));
|
|
6497
6629
|
return;
|
|
6498
6630
|
}
|
|
6499
|
-
component.key =
|
|
6631
|
+
component.key = sdGenerateKey();
|
|
6500
6632
|
};
|
|
6501
6633
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdFormBuilder, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
6502
|
-
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 }, { propertyName: "canvasDropZone", first: true, predicate: ["canvasDropZone"], descendants: true, isSignal: true }, { propertyName: "rowDropLists", predicate: ["rowDropList"], descendants: true, read: CdkDropList, 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\n class=\"fb-palette__list\"\n cdkDropList\n [cdkDropListData]=\"group.items\"\n [cdkDropListConnectedTo]=\"connectedRowDropLists()\"\n [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 *cdkDragPlaceholder class=\"fb-palette-native-placeholder\" aria-hidden=\"true\"></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 #canvasDropZone 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 && !isPaletteEmptyDropTarget()) {\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 [cdkDropListEnterPredicate]=\"canEnterCanvasDropList\"\n (cdkDropListDropped)=\"drop($event)\"\n (click)=\"clickFormContentEmpty()\"\n aria-hidden=\"true\">\n @if (isPaletteEmptyDropTarget()) {\n <div class=\"fb-row-insert-placeholder fb-palette-drop-placeholder\" [style.--fb-placeholder-columns]=\"12\" aria-hidden=\"true\">\n <ng-container *ngTemplateOutlet=\"dropPlaceholderTemplate; context: { $implicit: draggedPaletteItem() }\"></ng-container>\n </div>\n }\n @for (row of dragDropRows; track row.id) {\n @if (shouldShowRowInsertionPlaceholder(row, 'before')) {\n <div class=\"fb-row-insert-placeholder fb-palette-drop-placeholder\" [style.--fb-placeholder-columns]=\"12\" aria-hidden=\"true\">\n <ng-container\n *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 #rowDropList=\"cdkDropList\"\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 [cdkDropListConnectedTo]=\"connectedRowDropLists()\"\n [cdkDropListEnterPredicate]=\"canEnterRowDropList\"\n (cdkDropListEntered)=\"onRowItemsDropEntered(row, $event)\"\n (cdkDropListSorted)=\"onRowItemsDropSorted(row, $event)\"\n (cdkDropListExited)=\"onRowItemsDropExited(row)\"\n (cdkDropListDropped)=\"drop($event)\">\n @for (item of row.items; track item.id; let i = $index) {\n @if (shouldShowInlinePalettePlaceholder(row, i)) {\n <div\n class=\"fb-drop-placeholder fb-palette-drop-placeholder\"\n [style.--fb-placeholder-columns]=\"palettePlaceholderColumnsFor(row)\"\n aria-hidden=\"true\">\n <ng-container\n *ngTemplateOutlet=\"dropPlaceholderTemplate; context: { $implicit: draggedPaletteItem() }\"></ng-container>\n </div>\n }\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 *cdkDragPlaceholder class=\"fb-drop-placeholder\" [style.--fb-placeholder-columns]=\"12\" 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 @if (shouldShowInlinePalettePlaceholder(row, row.items.length)) {\n <div\n class=\"fb-drop-placeholder fb-palette-drop-placeholder\"\n [style.--fb-placeholder-columns]=\"palettePlaceholderColumnsFor(row)\"\n aria-hidden=\"true\">\n <ng-container *ngTemplateOutlet=\"dropPlaceholderTemplate; context: { $implicit: draggedPaletteItem() }\"></ng-container>\n </div>\n }\n </div>\n </div>\n @if (shouldShowRowInsertionPlaceholder(row, 'after')) {\n <div class=\"fb-row-insert-placeholder fb-palette-drop-placeholder\" [style.--fb-placeholder-columns]=\"12\" aria-hidden=\"true\">\n <ng-container\n *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;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-palette-native-placeholder{display:block!important;visibility:hidden!important;opacity:0!important;width:1px!important;min-width:1px!important;height:1px!important;min-height:1px!important;flex:0 0 1px!important;margin:0!important;padding:0!important;border:0!important;overflow:hidden!important;pointer-events: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-canvas__body>.cdk-drop-list{min-height:100%;box-sizing:border-box}.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.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:
|
|
6634
|
+
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 }, { propertyName: "canvasDropZone", first: true, predicate: ["canvasDropZone"], descendants: true, isSignal: true }, { propertyName: "rowDropLists", predicate: ["rowDropList"], descendants: true, read: CdkDropList, 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' | sdTranslate }}</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' | sdTranslate\"\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\n class=\"fb-palette__list\"\n cdkDropList\n [cdkDropListData]=\"group.items\"\n [cdkDropListConnectedTo]=\"connectedRowDropLists()\"\n [cdkDropListEnterPredicate]=\"noReturnPredicate\">\n <!-- why: b\u1ECF aria-hidden=\"true\" \u2014 m\u1EE5c palette l\u00E0 control TH\u1EACT (click \u0111\u1EC3 th\u00EAm component v\u00E0o\n form) v\u00E0 c\u00F3 t\u00EAn hi\u1EC3n th\u1ECB. aria-hidden xo\u00E1 n\u00F3 kh\u1ECFi accessibility tree trong khi v\u1EABn\n nh\u1EADn chu\u1ED9t. Nay role=\"button\" + tabindex=\"0\" + Enter/Space ch\u1EA1y \u0111\u00FAng nh\u01B0 click, t\u1EE9c\n ng\u01B0\u1EDDi d\u00F9ng b\u00E0n ph\u00EDm d\u1EF1ng \u0111\u01B0\u1EE3c form m\u00E0 kh\u00F4ng c\u1EA7n k\u00E9o-th\u1EA3. -->\n @for (formBuilderComponent of group.items; track formBuilderComponent.type) {\n <div\n cdkDrag\n [cdkDragData]=\"formBuilderComponent\"\n class=\"fb-palette-item\"\n role=\"button\"\n tabindex=\"0\"\n [attr.aria-label]=\"formBuilderComponent.name\"\n [matTooltip]=\"formBuilderComponent.name\"\n matTooltipPosition=\"right\"\n (click)=\"addComponent(formBuilderComponent)\"\n (keydown.enter)=\"onPaletteItemKeydown(formBuilderComponent, $event)\"\n (keydown.space)=\"onPaletteItemKeydown(formBuilderComponent, $event)\"\n (cdkDragStarted)=\"onPaletteDragStarted(formBuilderComponent)\"\n (cdkDragMoved)=\"onAnyDragMoved($event)\"\n (cdkDragEnded)=\"onAnyDragEnded()\">\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-palette-native-placeholder\" aria-hidden=\"true\"></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' | sdTranslate\"\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' | sdTranslate) }}</span>\n <span class=\"fb-groupedit__hint\">{{ 'core.component.form-builder.edit-group' | sdTranslate }}</span>\n </div>\n <div class=\"fb-canvas__actions\">\n <sd-button type=\"text\" [title]=\"'core.component.form-builder.cancel' | sdTranslate\" (click)=\"cancelGroupEdit()\"></sd-button>\n <sd-button\n type=\"fill\"\n color=\"primary\"\n prefixIcon=\"check\"\n [title]=\"'core.component.form-builder.confirm' | sdTranslate\"\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.canvas' | sdTranslate }}</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' | sdTranslate\" (click)=\"viewJSON()\">\n <span class=\"msi\">data_object</span>\n </button>\n <!-- why: s\u1ED1 bi\u1EBFn / s\u1ED1 x\u00E1c th\u1EF1c tr\u01B0\u1EDBc \u0111\u00E2y ch\u1EC9 bi\u1EBFt \u0111\u01B0\u1EE3c sau khi m\u1EDF dialog. Badge cho th\u1EA5y\n form \u0111\u00E3 c\u1EA5u h\u00ECnh g\u00EC ngay tr\u00EAn toolbar. -->\n <button\n type=\"button\"\n class=\"fb-icon-btn fb-icon-btn--badged\"\n [matTooltip]=\"'core.component.form-builder.config-variable' | sdTranslate\"\n (click)=\"configureVariables()\">\n <span class=\"msi\">data_array</span>\n @if (variables.length) {\n <span class=\"fb-icon-btn__badge\" [attr.aria-label]=\"variables.length\">{{ variables.length }}</span>\n }\n </button>\n <button\n type=\"button\"\n class=\"fb-icon-btn fb-icon-btn--badged\"\n [matTooltip]=\"'core.component.form-builder.config-validation-warning-error' | sdTranslate\"\n (click)=\"openConfigureValidation()\">\n <span class=\"msi\">rule</span>\n @if (validations.length) {\n <span class=\"fb-icon-btn__badge\" [attr.aria-label]=\"validations.length\">{{ validations.length }}</span>\n }\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' | sdTranslate }}</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' | sdTranslate }}</span>\n </button>\n </div>\n </div>\n </header>\n }\n\n <div #canvasDropZone 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' | sdTranslate }}\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' | sdTranslate\" (click)=\"setMode(false)\"></sd-button>\n <sd-button\n type=\"fill\"\n color=\"primary\"\n [title]=\"'core.component.form-builder.submit' | sdTranslate\"\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 && !isPaletteEmptyDropTarget()) {\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' | sdTranslate }}</div>\n <div class=\"fb-empty__hint\">\n {{ 'core.component.form-builder.empty-hint' | sdTranslate }}\n <span class=\"fb-empty__kbd\"><span class=\"msi\">keyboard</span>\u2318 K</span>\n </div>\n </div>\n }\n\n <!-- why: aria-hidden=\"true\" \u1EDF \u0111\u00E2y \u1EA9n TO\u00C0N B\u1ED8 canvas \u2014 m\u1ECDi row, m\u1ECDi component \u0111\u00E3 \u0111\u1EB7t v\u00E0 m\u1ECDi\n <input> preview b\u00EAn trong \u0111\u1EC1u bi\u1EBFn m\u1EA5t kh\u1ECFi accessibility tree. B\u1ECF h\u1EB3n. \"Click v\u00F9ng\n tr\u1ED1ng \u0111\u1EC3 b\u1ECF ch\u1ECDn\" ch\u1EC9 l\u00E0 h\u00E0nh vi chu\u1ED9t n\u00EAn b\u1ED5 sung Escape l\u00E0m t\u01B0\u01A1ng \u0111\u01B0\u01A1ng b\u00E0n ph\u00EDm;\n role=\"group\" m\u00F4 t\u1EA3 \u0111\u00FAng b\u1EA3n ch\u1EA5t (v\u00F9ng ch\u1EE9a control) v\u00E0 gi\u1EEF canvas kh\u00F4ng nh\u1EADn tabindex. -->\n <div\n cdkDropList\n cdkDropListOrientation=\"vertical\"\n [cdkDropListData]=\"dragDropRows\"\n [cdkDropListEnterPredicate]=\"canEnterCanvasDropList\"\n (cdkDropListDropped)=\"drop($event)\"\n (click)=\"clickFormContentEmpty()\"\n (keydown.escape)=\"clickFormContentEmpty()\"\n role=\"group\"\n [attr.aria-label]=\"'core.component.form-builder.canvas' | sdTranslate\">\n @if (isPaletteEmptyDropTarget()) {\n <div class=\"fb-row-insert-placeholder fb-palette-drop-placeholder\" [style.--fb-placeholder-columns]=\"12\" aria-hidden=\"true\">\n <ng-container *ngTemplateOutlet=\"dropPlaceholderTemplate; context: { $implicit: draggedPaletteItem() }\"></ng-container>\n </div>\n }\n @for (row of dragDropRows; track row.id) {\n @if (shouldShowRowInsertionPlaceholder(row, 'before')) {\n <div class=\"fb-row-insert-placeholder fb-palette-drop-placeholder\" [style.--fb-placeholder-columns]=\"12\" aria-hidden=\"true\">\n <ng-container\n *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 <!-- why: b\u1ECF aria-hidden=\"true\" \u2014 row l\u00E0 V\u00D9NG CH\u1EE8A c\u00E1c component \u0111\u00E3 \u0111\u1EB7t (g\u1ED3m c\u1EA3 input\n th\u1EADt). aria-hidden \u1EA9n h\u1EBFt n\u1ED9i dung con kh\u1ECFi accessibility tree trong khi ch\u00FAng\n v\u1EABn tab t\u1EDBi \u0111\u01B0\u1EE3c. Row t\u1EF1 n\u00F3 kh\u00F4ng ph\u1EA3i control (kh\u00F4ng c\u00F3 click) n\u00EAn kh\u00F4ng c\u1EA7n\n role/tabindex. -->\n <div class=\"fb-row__drag\" [matTooltip]=\"'core.component.form-builder.drag-vertically' | sdTranslate\" 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' | sdTranslate\"\n matTooltipPosition=\"right\"\n (click)=\"insertBreakAfter(row); $event.stopPropagation()\">\n <span class=\"msi\">horizontal_rule</span>\n </button>\n\n <div\n #rowDropList=\"cdkDropList\"\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 [cdkDropListConnectedTo]=\"connectedRowDropLists()\"\n [cdkDropListEnterPredicate]=\"canEnterRowDropList\"\n (cdkDropListEntered)=\"onRowItemsDropEntered(row, $event)\"\n (cdkDropListSorted)=\"onRowItemsDropSorted(row, $event)\"\n (cdkDropListExited)=\"onRowItemsDropExited(row)\"\n (cdkDropListDropped)=\"drop($event)\">\n @for (item of row.items; track item.id; let i = $index) {\n @if (shouldShowInlinePalettePlaceholder(row, i)) {\n <div\n class=\"fb-drop-placeholder fb-palette-drop-placeholder\"\n [style.--fb-placeholder-columns]=\"palettePlaceholderColumnsFor(row)\"\n aria-hidden=\"true\">\n <ng-container\n *ngTemplateOutlet=\"dropPlaceholderTemplate; context: { $implicit: draggedPaletteItem() }\"></ng-container>\n </div>\n }\n <!-- Group component renders as outlined card with colored header bar -->\n <!-- why: card KH\u00D4NG mang role=\"button\". N\u00F3 b\u1ECDc c\u1EA3 .fb-actions (n\u00FAt th\u1EADt) v\u00E0, v\u1EDBi\n nh\u00E1nh component, preview control c\u00F3 <input> th\u1EADt \u2014 role=\"button\" \u1EDF \u0111\u00E2y khi\u1EBFn AT\n g\u1ED9p to\u00E0n b\u1ED9 card th\u00E0nh M\u1ED8T n\u00FAt v\u00E0 m\u1ECDi control b\u00EAn trong bi\u1EBFn m\u1EA5t kh\u1ECFi\n accessibility tree (\u0111\u00FAng l\u1ED7i m\u00E0 aria-hidden c\u0169 t\u1EEBng g\u00E2y ra). Affordance b\u00E0n\n ph\u00EDm/AT l\u00E0 <button> th\u1EADt \u1EDF \u0111\u1EA7u .fb-actions (aria-pressed + aria-label), toolbar\n hi\u1EC7n c\u1EA3 khi :focus-within. (click) tr\u00EAn card ch\u1EC9 l\u00E0 ti\u1EC7n \u00EDch chu\u1ED9t, n\u00EAn hai rule\n a11y d\u01B0\u1EDBi \u0111\u00E2y b\u1ECB t\u1EAFt C\u00D3 CH\u1EE6 \u00DD \u2014 th\u00EAm tabindex/keydown v\u00E0o card s\u1EBD d\u1EF1ng l\u1EA1i \u0111\u00FAng\n c\u00E1i b\u1EABy v\u1EEBa g\u1EE1. -->\n @if (item.type === 'group') {\n @let g = $any(item);\n @let cv = groupColorVars(g.properties?.color);\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\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 <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' | sdTranslate }}\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: \u0111\u00E2y l\u00E0 affordance \"ch\u1ECDn item \u0111\u1EC3 s\u1EEDa\" cho b\u00E0n ph\u00EDm / screen reader. N\u00F3 KH\u00D4NG n\u1EB1m\n tr\u00EAn th\u1EBB card b\u1ECDc ngo\u00E0i: card b\u1ECDc c\u1EA3 .fb-actions (n\u00FAt th\u1EADt) v\u00E0 preview control (input\n th\u1EADt), n\u00EAn role=\"button\" \u1EDF \u0111\u00F3 khi\u1EBFn AT g\u1ED9p to\u00E0n b\u1ED9 th\u00E0nh m\u1ED9t n\u00FAt v\u00E0 c\u00E1c control b\u00EAn\n trong bi\u1EBFn m\u1EA5t kh\u1ECFi accessibility tree. Card gi\u1EEF (click) l\u00E0m ti\u1EC7n \u00EDch chu\u1ED9t. -->\n <button\n type=\"button\"\n class=\"fb-icon-btn\"\n [attr.aria-pressed]=\"item.id === selectedComponent()?.id\"\n [attr.aria-label]=\"item.properties?.label || item.type\"\n [matTooltip]=\"'core.component.form-builder.select-item' | sdTranslate\"\n (click)=\"selectComponent(item); $event.stopPropagation()\">\n <span class=\"msi\">ads_click</span>\n </button>\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' | sdTranslate\"\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' | sdTranslate\"\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' | sdTranslate)\n : ('core.component.form-builder.hide' | sdTranslate)\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' | sdTranslate\"\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 <!-- why: warning n\u00E0y ch\u1EC9 l\u1ED9 ra SAU khi b\u1ECF aria-hidden \u1EDF div cha (rule b\u1ECF qua\n m\u1ECDi h\u1EADu du\u1EC7 c\u1EE7a m\u1ED9t node aria-hidden). Body group l\u00E0 control m\u1EDF m\u00E0n h\u00ECnh\n Detail n\u00EAn nh\u1EADn role=\"button\" + tabindex=\"0\" + Enter/Space; preview con\n b\u00EAn trong \u0111\u00E3 `pointer-events: none` n\u00EAn kh\u00F4ng tranh ch\u1EA5p target. -->\n <div\n class=\"fb-group__body fb-group__body--static\"\n role=\"button\"\n tabindex=\"0\"\n [attr.aria-label]=\"'core.component.form-builder.detail' | sdTranslate\"\n (click)=\"enterGroupEdit(g); $event.stopPropagation()\"\n (keydown.enter)=\"onEnterGroupEditKeydown(g, $event)\"\n (keydown.space)=\"onEnterGroupEditKeydown(g, $event)\">\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' | sdTranslate }}\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 <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\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 <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\" [style.--fb-placeholder-columns]=\"12\" 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' | sdTranslate }}</span>\n </div>\n <div class=\"fb-actions\">\n <!-- why: \u0111\u00E2y l\u00E0 affordance \"ch\u1ECDn item \u0111\u1EC3 s\u1EEDa\" cho b\u00E0n ph\u00EDm / screen reader. N\u00F3 KH\u00D4NG n\u1EB1m\n tr\u00EAn th\u1EBB card b\u1ECDc ngo\u00E0i: card b\u1ECDc c\u1EA3 .fb-actions (n\u00FAt th\u1EADt) v\u00E0 preview control (input\n th\u1EADt), n\u00EAn role=\"button\" \u1EDF \u0111\u00F3 khi\u1EBFn AT g\u1ED9p to\u00E0n b\u1ED9 th\u00E0nh m\u1ED9t n\u00FAt v\u00E0 c\u00E1c control b\u00EAn\n trong bi\u1EBFn m\u1EA5t kh\u1ECFi accessibility tree. Card gi\u1EEF (click) l\u00E0m ti\u1EC7n \u00EDch chu\u1ED9t. -->\n <button\n type=\"button\"\n class=\"fb-icon-btn\"\n [attr.aria-pressed]=\"item.id === selectedComponent()?.id\"\n [attr.aria-label]=\"item.properties?.label || item.type\"\n [matTooltip]=\"'core.component.form-builder.select-item' | sdTranslate\"\n (click)=\"selectComponent(item); $event.stopPropagation()\">\n <span class=\"msi\">ads_click</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' | sdTranslate)\n : ('core.component.form-builder.hide' | sdTranslate)\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' | sdTranslate\"\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 <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\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 <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' | sdTranslate }}\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' | sdTranslate }}\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' | sdTranslate }}\n </span>\n }\n\n <!-- Floating action toolbar pill -->\n <div class=\"fb-actions\">\n <!-- why: \u0111\u00E2y l\u00E0 affordance \"ch\u1ECDn item \u0111\u1EC3 s\u1EEDa\" cho b\u00E0n ph\u00EDm / screen reader. N\u00F3 KH\u00D4NG n\u1EB1m\n tr\u00EAn th\u1EBB card b\u1ECDc ngo\u00E0i: card b\u1ECDc c\u1EA3 .fb-actions (n\u00FAt th\u1EADt) v\u00E0 preview control (input\n th\u1EADt), n\u00EAn role=\"button\" \u1EDF \u0111\u00F3 khi\u1EBFn AT g\u1ED9p to\u00E0n b\u1ED9 th\u00E0nh m\u1ED9t n\u00FAt v\u00E0 c\u00E1c control b\u00EAn\n trong bi\u1EBFn m\u1EA5t kh\u1ECFi accessibility tree. Card gi\u1EEF (click) l\u00E0m ti\u1EC7n \u00EDch chu\u1ED9t. -->\n <button\n type=\"button\"\n class=\"fb-icon-btn\"\n [attr.aria-pressed]=\"item.id === selectedComponent()?.id\"\n [attr.aria-label]=\"item.properties?.label || item.type\"\n [matTooltip]=\"'core.component.form-builder.select-item' | sdTranslate\"\n (click)=\"selectComponent(item); $event.stopPropagation()\">\n <span class=\"msi\">ads_click</span>\n </button>\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' | sdTranslate)\n : ('core.component.form-builder.set-readonly' | sdTranslate)\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' | sdTranslate\"\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' | sdTranslate)\n : ('core.component.form-builder.hide' | sdTranslate)\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' | sdTranslate\"\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' | sdTranslate\"\n aria-label=\"resize\"></div>\n </div>\n </div>\n }\n }\n @if (shouldShowInlinePalettePlaceholder(row, row.items.length)) {\n <div\n class=\"fb-drop-placeholder fb-palette-drop-placeholder\"\n [style.--fb-placeholder-columns]=\"palettePlaceholderColumnsFor(row)\"\n aria-hidden=\"true\">\n <ng-container *ngTemplateOutlet=\"dropPlaceholderTemplate; context: { $implicit: draggedPaletteItem() }\"></ng-container>\n </div>\n }\n </div>\n </div>\n @if (shouldShowRowInsertionPlaceholder(row, 'after')) {\n <div class=\"fb-row-insert-placeholder fb-palette-drop-placeholder\" [style.--fb-placeholder-columns]=\"12\" aria-hidden=\"true\">\n <ng-container\n *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' | sdTranslate }}</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' | sdTranslate) }}</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 <!-- why: aria-hidden=\"true\" \u1EDF \u0111\u00E2y l\u00E0 \u1ED5 ch\u1EE9a l\u1EDBn nh\u1EA5t \u2014 panel n\u00E0y render TO\u00C0N B\u1ED8 form thu\u1ED9c\n t\u00EDnh c\u1EE7a component \u0111ang ch\u1ECDn (h\u00E0ng ch\u1EE5c <input>/<select> th\u1EADt). aria-hidden khi\u1EBFn ch\u00FAng\n v\u1EABn tab t\u1EDBi \u0111\u01B0\u1EE3c nh\u01B0ng screen reader kh\u00F4ng \u0111\u1ECDc \u0111\u01B0\u1EE3c nh\u00E3n n\u00E0o c\u1EA3. B\u1ECF h\u1EB3n. -->\n <div class=\"fb-attrs__body\">\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' | sdTranslate }}</div>\n <div class=\"hint\">{{ 'core.component.form-builder.no-selection-hint' | sdTranslate }}</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 <!-- why: schema l\u00E0 JSON nhi\u1EC1u t\u1EA7ng \u2014 textarea tr\u1EA7n kh\u00F4ng cho bi\u1EBFt d\u1EA5u ngo\u1EB7c n\u00E0o kh\u1EDBp d\u1EA5u n\u00E0o,\n c\u0169ng kh\u00F4ng b\u00E1o c\u00FA ph\u00E1p h\u1ECFng cho t\u1EDBi l\u00FAc b\u1EA5m L\u01B0u. -->\n <sd-code-editor language=\"json\" [(model)]=\"jsonString\" maxHeight=\"60vh\"></sd-code-editor>\n <ng-container sdFooterRight>\n <sd-button\n type=\"fill\"\n [title]=\"'core.component.form-builder.save' | sdTranslate\"\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' | sdTranslate\"\n view=\"dialog\"\n width=\"800px\"\n modalClass=\"sd-form-builder-dialog\"\n #popupConfigureVariables>\n <!-- why: tr\u01B0\u1EDBc \u0111\u00E2y c\u00E1c h\u00E0ng n\u1EB1m tr\u1EA7n trong modal (modal body padding 0) n\u00EAn d\u00EDnh s\u00E1t m\u00E9p, kh\u00F4ng\n c\u00F3 tr\u1EA1ng th\u00E1i r\u1ED7ng, v\u00E0 n\u00FAt th\u00EAm l\u00E0 <a href=\"javascript:;\"> \u2014 kh\u00F4ng ph\u1EA3i n\u00FAt th\u1EADt, b\u00E0n ph\u00EDm v\u00E0\n screen reader \u0111\u1EC1u \u0111\u1ECDc sai. -->\n <div class=\"fb-variables\">\n @if (clonedVariables.length) {\n <!-- why: m\u1ED7i bi\u1EBFn ch\u1EC9 c\u00F3 hai tr\u01B0\u1EDDng c\u00F9ng ki\u1EC3u, l\u1EB7p l\u1EA1i theo h\u00E0ng \u2014 \u0111\u00F3 l\u00E0 d\u1EEF li\u1EC7u d\u1EA1ng b\u1EA3ng.\n \u0110\u1EB7t nh\u00E3n c\u1ED9t M\u1ED8T l\u1EA7n \u1EDF header thay v\u00EC l\u1EB7p nh\u00E3n n\u1ED5i tr\u00EAn t\u1EEBng \u00F4 c\u1EE7a t\u1EEBng h\u00E0ng. -->\n <table class=\"fb-variables__table\">\n <thead>\n <tr>\n <th scope=\"col\">{{ 'core.component.form-builder.field-key' | sdTranslate }}</th>\n <th scope=\"col\">{{ 'core.component.form-builder.display-label' | sdTranslate }}</th>\n <!-- why: c\u1ED9t thao t\u00E1c kh\u00F4ng c\u1EA7n nh\u00E3n nh\u00ECn th\u1EA5y, nh\u01B0ng v\u1EABn ph\u1EA3i c\u00F3 t\u00EAn cho screen reader \u2014\n lib kh\u00F4ng c\u00F3 ti\u1EC7n \u00EDch .sr-only n\u00EAn \u0111\u1EB7t t\u00EAn b\u1EB1ng aria-label ngay tr\u00EAn \u00F4 header. -->\n <th scope=\"col\" class=\"fb-variables__actions-col\" [attr.aria-label]=\"'core.component.form-builder.delete' | sdTranslate\"></th>\n </tr>\n </thead>\n <tbody>\n @for (variable of clonedVariables; track variable.id) {\n <tr>\n <td>\n <sd-input size=\"sm\" [form]=\"form\" [(model)]=\"variable.key\" hideInlineError required></sd-input>\n </td>\n <td>\n <sd-input size=\"sm\" [form]=\"form\" [(model)]=\"variable.label\" hideInlineError required></sd-input>\n </td>\n <td class=\"fb-variables__actions-col\">\n <sd-button\n prefixIcon=\"delete\"\n type=\"text\"\n color=\"error\"\n [tooltip]=\"'core.component.form-builder.delete' | sdTranslate\"\n (click)=\"removeVariables(variable.id)\"></sd-button>\n </td>\n </tr>\n }\n </tbody>\n </table>\n } @else {\n <p class=\"fb-variables__empty\">{{ 'core.component.form-builder.no-variable' | sdTranslate }}</p>\n }\n\n <sd-button\n class=\"fb-variables__add\"\n type=\"text\"\n color=\"primary\"\n prefixIcon=\"add\"\n [title]=\"'core.component.form-builder.add-variable' | sdTranslate\"\n (click)=\"addVariables()\"></sd-button>\n </div>\n <ng-container sdFooterRight>\n <sd-button\n type=\"fill\"\n [title]=\"'core.component.form-builder.save' | sdTranslate\"\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--badged{position:relative;overflow:visible}:host ::ng-deep .fb-icon-btn__badge{position:absolute;top:-2px;right:-2px;min-width:14px;height:14px;padding:0 3px;border-radius:7px;background:var(--md-sys-color-error, #b3261e);color:var(--md-sys-color-on-error, #fff);font-size:9px;font-weight:600;line-height:14px;text-align:center;pointer-events:none}: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;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-palette-native-placeholder{display:block!important;visibility:hidden!important;opacity:0!important;width:1px!important;min-width:1px!important;height:1px!important;min-height:1px!important;flex:0 0 1px!important;margin:0!important;padding:0!important;border:0!important;overflow:hidden!important;pointer-events: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)}::ng-deep .sd-form-builder-dialog .fb-variables{display:flex;flex-direction:column;gap:8px}::ng-deep .sd-form-builder-dialog .fb-variables__table{width:100%;border-collapse:separate;border-spacing:0;border:1px solid var(--sd-border, #e6e6e6);border-radius:8px;overflow:hidden}::ng-deep .sd-form-builder-dialog .fb-variables__table th{padding:8px 12px;background:var(--md-sys-color-surface-container-low, #f7f8fa);border-bottom:1px solid var(--sd-border, #e6e6e6);color:var(--sd-text-secondary, #6b7280);font-size:12px;font-weight:500;text-align:left;white-space:nowrap}::ng-deep .sd-form-builder-dialog .fb-variables__table td{padding:8px 12px;border-bottom:1px solid var(--sd-border, #e6e6e6);vertical-align:middle}::ng-deep .sd-form-builder-dialog .fb-variables__table tbody tr:last-child td{border-bottom:none}::ng-deep .sd-form-builder-dialog .fb-variables__actions-col{width:1px;white-space:nowrap;text-align:right}::ng-deep .sd-form-builder-dialog .fb-variables__empty{margin:0;padding:16px;border:1px dashed var(--sd-border, #e6e6e6);border-radius:8px;color:var(--sd-text-secondary, #6b7280);font-size:13px;text-align:center}::ng-deep .sd-form-builder-dialog .fb-variables__add{align-self:flex-start}::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-canvas__body>.cdk-drop-list{min-height:100%;box-sizing:border-box}.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;padding:0;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:focus-visible{outline:2px solid var(--sd-primary, #005cbb);outline-offset:2px}.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;border:0;background:transparent;font-family:inherit}.fb-add-link .msi{font-size:14px}.fb-add-link:hover{text-decoration:underline}.fb-add-link:focus-visible{outline:2px solid var(--sd-primary, #005cbb);outline-offset:2px}.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.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:
|
|
6503
6635
|
// Controls
|
|
6504
|
-
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", "beforeClose"], outputs: ["sdClosed", "sdCloseError"] }, { kind: "component", type: SdInput$1, selector: "sd-input", inputs: ["autoId", "name", "appearance", "floatLabel", "size", "form", "label", "helperText", "placeholder", "type", "mask", "hideInlineError", "blurOnEnter", "clearable", "required", "readonly", "disabled", "viewed", "minlength", "maxlength", "pattern", "patternErrorMessage", "validator", "inlineError", "hyperlink", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "cleared", "sdFocusForceBlur"] }, { kind: "component", type:
|
|
6636
|
+
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", "beforeClose"], outputs: ["sdClosed", "sdCloseError"] }, { kind: "component", type: SdInput$1, selector: "sd-input", inputs: ["autoId", "name", "appearance", "floatLabel", "size", "form", "label", "helperText", "placeholder", "type", "mask", "hideInlineError", "blurOnEnter", "clearable", "required", "readonly", "disabled", "viewed", "minlength", "maxlength", "pattern", "patternErrorMessage", "validator", "inlineError", "hyperlink", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "cleared", "sdFocusForceBlur"] }, { kind: "component", type: SdCodeEditor, selector: "sd-code-editor", inputs: ["model", "language", "maxHeight", "viewed"], outputs: ["modelChange"] }, { 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: SdTranslatePipe, name: "sdTranslate" }] });
|
|
6505
6637
|
}
|
|
6506
6638
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdFormBuilder, decorators: [{
|
|
6507
6639
|
type: Component,
|
|
@@ -6537,12 +6669,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
6537
6669
|
GroupAttribute,
|
|
6538
6670
|
SdModal,
|
|
6539
6671
|
SdInput$1,
|
|
6540
|
-
|
|
6672
|
+
SdCodeEditor,
|
|
6541
6673
|
SdButton,
|
|
6542
6674
|
SdFormRender,
|
|
6543
6675
|
ConfigureValidationComponent,
|
|
6544
|
-
|
|
6545
|
-
], 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\n class=\"fb-palette__list\"\n cdkDropList\n [cdkDropListData]=\"group.items\"\n [cdkDropListConnectedTo]=\"connectedRowDropLists()\"\n [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 *cdkDragPlaceholder class=\"fb-palette-native-placeholder\" aria-hidden=\"true\"></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 #canvasDropZone 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 && !isPaletteEmptyDropTarget()) {\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 [cdkDropListEnterPredicate]=\"canEnterCanvasDropList\"\n (cdkDropListDropped)=\"drop($event)\"\n (click)=\"clickFormContentEmpty()\"\n aria-hidden=\"true\">\n @if (isPaletteEmptyDropTarget()) {\n <div class=\"fb-row-insert-placeholder fb-palette-drop-placeholder\" [style.--fb-placeholder-columns]=\"12\" aria-hidden=\"true\">\n <ng-container *ngTemplateOutlet=\"dropPlaceholderTemplate; context: { $implicit: draggedPaletteItem() }\"></ng-container>\n </div>\n }\n @for (row of dragDropRows; track row.id) {\n @if (shouldShowRowInsertionPlaceholder(row, 'before')) {\n <div class=\"fb-row-insert-placeholder fb-palette-drop-placeholder\" [style.--fb-placeholder-columns]=\"12\" aria-hidden=\"true\">\n <ng-container\n *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 #rowDropList=\"cdkDropList\"\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 [cdkDropListConnectedTo]=\"connectedRowDropLists()\"\n [cdkDropListEnterPredicate]=\"canEnterRowDropList\"\n (cdkDropListEntered)=\"onRowItemsDropEntered(row, $event)\"\n (cdkDropListSorted)=\"onRowItemsDropSorted(row, $event)\"\n (cdkDropListExited)=\"onRowItemsDropExited(row)\"\n (cdkDropListDropped)=\"drop($event)\">\n @for (item of row.items; track item.id; let i = $index) {\n @if (shouldShowInlinePalettePlaceholder(row, i)) {\n <div\n class=\"fb-drop-placeholder fb-palette-drop-placeholder\"\n [style.--fb-placeholder-columns]=\"palettePlaceholderColumnsFor(row)\"\n aria-hidden=\"true\">\n <ng-container\n *ngTemplateOutlet=\"dropPlaceholderTemplate; context: { $implicit: draggedPaletteItem() }\"></ng-container>\n </div>\n }\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 *cdkDragPlaceholder class=\"fb-drop-placeholder\" [style.--fb-placeholder-columns]=\"12\" 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 @if (shouldShowInlinePalettePlaceholder(row, row.items.length)) {\n <div\n class=\"fb-drop-placeholder fb-palette-drop-placeholder\"\n [style.--fb-placeholder-columns]=\"palettePlaceholderColumnsFor(row)\"\n aria-hidden=\"true\">\n <ng-container *ngTemplateOutlet=\"dropPlaceholderTemplate; context: { $implicit: draggedPaletteItem() }\"></ng-container>\n </div>\n }\n </div>\n </div>\n @if (shouldShowRowInsertionPlaceholder(row, 'after')) {\n <div class=\"fb-row-insert-placeholder fb-palette-drop-placeholder\" [style.--fb-placeholder-columns]=\"12\" aria-hidden=\"true\">\n <ng-container\n *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;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-palette-native-placeholder{display:block!important;visibility:hidden!important;opacity:0!important;width:1px!important;min-width:1px!important;height:1px!important;min-height:1px!important;flex:0 0 1px!important;margin:0!important;padding:0!important;border:0!important;overflow:hidden!important;pointer-events: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-canvas__body>.cdk-drop-list{min-height:100%;box-sizing:border-box}.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"] }]
|
|
6676
|
+
SdTranslatePipe,
|
|
6677
|
+
], 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' | sdTranslate }}</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' | sdTranslate\"\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\n class=\"fb-palette__list\"\n cdkDropList\n [cdkDropListData]=\"group.items\"\n [cdkDropListConnectedTo]=\"connectedRowDropLists()\"\n [cdkDropListEnterPredicate]=\"noReturnPredicate\">\n <!-- why: b\u1ECF aria-hidden=\"true\" \u2014 m\u1EE5c palette l\u00E0 control TH\u1EACT (click \u0111\u1EC3 th\u00EAm component v\u00E0o\n form) v\u00E0 c\u00F3 t\u00EAn hi\u1EC3n th\u1ECB. aria-hidden xo\u00E1 n\u00F3 kh\u1ECFi accessibility tree trong khi v\u1EABn\n nh\u1EADn chu\u1ED9t. Nay role=\"button\" + tabindex=\"0\" + Enter/Space ch\u1EA1y \u0111\u00FAng nh\u01B0 click, t\u1EE9c\n ng\u01B0\u1EDDi d\u00F9ng b\u00E0n ph\u00EDm d\u1EF1ng \u0111\u01B0\u1EE3c form m\u00E0 kh\u00F4ng c\u1EA7n k\u00E9o-th\u1EA3. -->\n @for (formBuilderComponent of group.items; track formBuilderComponent.type) {\n <div\n cdkDrag\n [cdkDragData]=\"formBuilderComponent\"\n class=\"fb-palette-item\"\n role=\"button\"\n tabindex=\"0\"\n [attr.aria-label]=\"formBuilderComponent.name\"\n [matTooltip]=\"formBuilderComponent.name\"\n matTooltipPosition=\"right\"\n (click)=\"addComponent(formBuilderComponent)\"\n (keydown.enter)=\"onPaletteItemKeydown(formBuilderComponent, $event)\"\n (keydown.space)=\"onPaletteItemKeydown(formBuilderComponent, $event)\"\n (cdkDragStarted)=\"onPaletteDragStarted(formBuilderComponent)\"\n (cdkDragMoved)=\"onAnyDragMoved($event)\"\n (cdkDragEnded)=\"onAnyDragEnded()\">\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-palette-native-placeholder\" aria-hidden=\"true\"></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' | sdTranslate\"\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' | sdTranslate) }}</span>\n <span class=\"fb-groupedit__hint\">{{ 'core.component.form-builder.edit-group' | sdTranslate }}</span>\n </div>\n <div class=\"fb-canvas__actions\">\n <sd-button type=\"text\" [title]=\"'core.component.form-builder.cancel' | sdTranslate\" (click)=\"cancelGroupEdit()\"></sd-button>\n <sd-button\n type=\"fill\"\n color=\"primary\"\n prefixIcon=\"check\"\n [title]=\"'core.component.form-builder.confirm' | sdTranslate\"\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.canvas' | sdTranslate }}</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' | sdTranslate\" (click)=\"viewJSON()\">\n <span class=\"msi\">data_object</span>\n </button>\n <!-- why: s\u1ED1 bi\u1EBFn / s\u1ED1 x\u00E1c th\u1EF1c tr\u01B0\u1EDBc \u0111\u00E2y ch\u1EC9 bi\u1EBFt \u0111\u01B0\u1EE3c sau khi m\u1EDF dialog. Badge cho th\u1EA5y\n form \u0111\u00E3 c\u1EA5u h\u00ECnh g\u00EC ngay tr\u00EAn toolbar. -->\n <button\n type=\"button\"\n class=\"fb-icon-btn fb-icon-btn--badged\"\n [matTooltip]=\"'core.component.form-builder.config-variable' | sdTranslate\"\n (click)=\"configureVariables()\">\n <span class=\"msi\">data_array</span>\n @if (variables.length) {\n <span class=\"fb-icon-btn__badge\" [attr.aria-label]=\"variables.length\">{{ variables.length }}</span>\n }\n </button>\n <button\n type=\"button\"\n class=\"fb-icon-btn fb-icon-btn--badged\"\n [matTooltip]=\"'core.component.form-builder.config-validation-warning-error' | sdTranslate\"\n (click)=\"openConfigureValidation()\">\n <span class=\"msi\">rule</span>\n @if (validations.length) {\n <span class=\"fb-icon-btn__badge\" [attr.aria-label]=\"validations.length\">{{ validations.length }}</span>\n }\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' | sdTranslate }}</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' | sdTranslate }}</span>\n </button>\n </div>\n </div>\n </header>\n }\n\n <div #canvasDropZone 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' | sdTranslate }}\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' | sdTranslate\" (click)=\"setMode(false)\"></sd-button>\n <sd-button\n type=\"fill\"\n color=\"primary\"\n [title]=\"'core.component.form-builder.submit' | sdTranslate\"\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 && !isPaletteEmptyDropTarget()) {\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' | sdTranslate }}</div>\n <div class=\"fb-empty__hint\">\n {{ 'core.component.form-builder.empty-hint' | sdTranslate }}\n <span class=\"fb-empty__kbd\"><span class=\"msi\">keyboard</span>\u2318 K</span>\n </div>\n </div>\n }\n\n <!-- why: aria-hidden=\"true\" \u1EDF \u0111\u00E2y \u1EA9n TO\u00C0N B\u1ED8 canvas \u2014 m\u1ECDi row, m\u1ECDi component \u0111\u00E3 \u0111\u1EB7t v\u00E0 m\u1ECDi\n <input> preview b\u00EAn trong \u0111\u1EC1u bi\u1EBFn m\u1EA5t kh\u1ECFi accessibility tree. B\u1ECF h\u1EB3n. \"Click v\u00F9ng\n tr\u1ED1ng \u0111\u1EC3 b\u1ECF ch\u1ECDn\" ch\u1EC9 l\u00E0 h\u00E0nh vi chu\u1ED9t n\u00EAn b\u1ED5 sung Escape l\u00E0m t\u01B0\u01A1ng \u0111\u01B0\u01A1ng b\u00E0n ph\u00EDm;\n role=\"group\" m\u00F4 t\u1EA3 \u0111\u00FAng b\u1EA3n ch\u1EA5t (v\u00F9ng ch\u1EE9a control) v\u00E0 gi\u1EEF canvas kh\u00F4ng nh\u1EADn tabindex. -->\n <div\n cdkDropList\n cdkDropListOrientation=\"vertical\"\n [cdkDropListData]=\"dragDropRows\"\n [cdkDropListEnterPredicate]=\"canEnterCanvasDropList\"\n (cdkDropListDropped)=\"drop($event)\"\n (click)=\"clickFormContentEmpty()\"\n (keydown.escape)=\"clickFormContentEmpty()\"\n role=\"group\"\n [attr.aria-label]=\"'core.component.form-builder.canvas' | sdTranslate\">\n @if (isPaletteEmptyDropTarget()) {\n <div class=\"fb-row-insert-placeholder fb-palette-drop-placeholder\" [style.--fb-placeholder-columns]=\"12\" aria-hidden=\"true\">\n <ng-container *ngTemplateOutlet=\"dropPlaceholderTemplate; context: { $implicit: draggedPaletteItem() }\"></ng-container>\n </div>\n }\n @for (row of dragDropRows; track row.id) {\n @if (shouldShowRowInsertionPlaceholder(row, 'before')) {\n <div class=\"fb-row-insert-placeholder fb-palette-drop-placeholder\" [style.--fb-placeholder-columns]=\"12\" aria-hidden=\"true\">\n <ng-container\n *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 <!-- why: b\u1ECF aria-hidden=\"true\" \u2014 row l\u00E0 V\u00D9NG CH\u1EE8A c\u00E1c component \u0111\u00E3 \u0111\u1EB7t (g\u1ED3m c\u1EA3 input\n th\u1EADt). aria-hidden \u1EA9n h\u1EBFt n\u1ED9i dung con kh\u1ECFi accessibility tree trong khi ch\u00FAng\n v\u1EABn tab t\u1EDBi \u0111\u01B0\u1EE3c. Row t\u1EF1 n\u00F3 kh\u00F4ng ph\u1EA3i control (kh\u00F4ng c\u00F3 click) n\u00EAn kh\u00F4ng c\u1EA7n\n role/tabindex. -->\n <div class=\"fb-row__drag\" [matTooltip]=\"'core.component.form-builder.drag-vertically' | sdTranslate\" 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' | sdTranslate\"\n matTooltipPosition=\"right\"\n (click)=\"insertBreakAfter(row); $event.stopPropagation()\">\n <span class=\"msi\">horizontal_rule</span>\n </button>\n\n <div\n #rowDropList=\"cdkDropList\"\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 [cdkDropListConnectedTo]=\"connectedRowDropLists()\"\n [cdkDropListEnterPredicate]=\"canEnterRowDropList\"\n (cdkDropListEntered)=\"onRowItemsDropEntered(row, $event)\"\n (cdkDropListSorted)=\"onRowItemsDropSorted(row, $event)\"\n (cdkDropListExited)=\"onRowItemsDropExited(row)\"\n (cdkDropListDropped)=\"drop($event)\">\n @for (item of row.items; track item.id; let i = $index) {\n @if (shouldShowInlinePalettePlaceholder(row, i)) {\n <div\n class=\"fb-drop-placeholder fb-palette-drop-placeholder\"\n [style.--fb-placeholder-columns]=\"palettePlaceholderColumnsFor(row)\"\n aria-hidden=\"true\">\n <ng-container\n *ngTemplateOutlet=\"dropPlaceholderTemplate; context: { $implicit: draggedPaletteItem() }\"></ng-container>\n </div>\n }\n <!-- Group component renders as outlined card with colored header bar -->\n <!-- why: card KH\u00D4NG mang role=\"button\". N\u00F3 b\u1ECDc c\u1EA3 .fb-actions (n\u00FAt th\u1EADt) v\u00E0, v\u1EDBi\n nh\u00E1nh component, preview control c\u00F3 <input> th\u1EADt \u2014 role=\"button\" \u1EDF \u0111\u00E2y khi\u1EBFn AT\n g\u1ED9p to\u00E0n b\u1ED9 card th\u00E0nh M\u1ED8T n\u00FAt v\u00E0 m\u1ECDi control b\u00EAn trong bi\u1EBFn m\u1EA5t kh\u1ECFi\n accessibility tree (\u0111\u00FAng l\u1ED7i m\u00E0 aria-hidden c\u0169 t\u1EEBng g\u00E2y ra). Affordance b\u00E0n\n ph\u00EDm/AT l\u00E0 <button> th\u1EADt \u1EDF \u0111\u1EA7u .fb-actions (aria-pressed + aria-label), toolbar\n hi\u1EC7n c\u1EA3 khi :focus-within. (click) tr\u00EAn card ch\u1EC9 l\u00E0 ti\u1EC7n \u00EDch chu\u1ED9t, n\u00EAn hai rule\n a11y d\u01B0\u1EDBi \u0111\u00E2y b\u1ECB t\u1EAFt C\u00D3 CH\u1EE6 \u00DD \u2014 th\u00EAm tabindex/keydown v\u00E0o card s\u1EBD d\u1EF1ng l\u1EA1i \u0111\u00FAng\n c\u00E1i b\u1EABy v\u1EEBa g\u1EE1. -->\n @if (item.type === 'group') {\n @let g = $any(item);\n @let cv = groupColorVars(g.properties?.color);\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\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 <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' | sdTranslate }}\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: \u0111\u00E2y l\u00E0 affordance \"ch\u1ECDn item \u0111\u1EC3 s\u1EEDa\" cho b\u00E0n ph\u00EDm / screen reader. N\u00F3 KH\u00D4NG n\u1EB1m\n tr\u00EAn th\u1EBB card b\u1ECDc ngo\u00E0i: card b\u1ECDc c\u1EA3 .fb-actions (n\u00FAt th\u1EADt) v\u00E0 preview control (input\n th\u1EADt), n\u00EAn role=\"button\" \u1EDF \u0111\u00F3 khi\u1EBFn AT g\u1ED9p to\u00E0n b\u1ED9 th\u00E0nh m\u1ED9t n\u00FAt v\u00E0 c\u00E1c control b\u00EAn\n trong bi\u1EBFn m\u1EA5t kh\u1ECFi accessibility tree. Card gi\u1EEF (click) l\u00E0m ti\u1EC7n \u00EDch chu\u1ED9t. -->\n <button\n type=\"button\"\n class=\"fb-icon-btn\"\n [attr.aria-pressed]=\"item.id === selectedComponent()?.id\"\n [attr.aria-label]=\"item.properties?.label || item.type\"\n [matTooltip]=\"'core.component.form-builder.select-item' | sdTranslate\"\n (click)=\"selectComponent(item); $event.stopPropagation()\">\n <span class=\"msi\">ads_click</span>\n </button>\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' | sdTranslate\"\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' | sdTranslate\"\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' | sdTranslate)\n : ('core.component.form-builder.hide' | sdTranslate)\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' | sdTranslate\"\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 <!-- why: warning n\u00E0y ch\u1EC9 l\u1ED9 ra SAU khi b\u1ECF aria-hidden \u1EDF div cha (rule b\u1ECF qua\n m\u1ECDi h\u1EADu du\u1EC7 c\u1EE7a m\u1ED9t node aria-hidden). Body group l\u00E0 control m\u1EDF m\u00E0n h\u00ECnh\n Detail n\u00EAn nh\u1EADn role=\"button\" + tabindex=\"0\" + Enter/Space; preview con\n b\u00EAn trong \u0111\u00E3 `pointer-events: none` n\u00EAn kh\u00F4ng tranh ch\u1EA5p target. -->\n <div\n class=\"fb-group__body fb-group__body--static\"\n role=\"button\"\n tabindex=\"0\"\n [attr.aria-label]=\"'core.component.form-builder.detail' | sdTranslate\"\n (click)=\"enterGroupEdit(g); $event.stopPropagation()\"\n (keydown.enter)=\"onEnterGroupEditKeydown(g, $event)\"\n (keydown.space)=\"onEnterGroupEditKeydown(g, $event)\">\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' | sdTranslate }}\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 <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\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 <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\" [style.--fb-placeholder-columns]=\"12\" 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' | sdTranslate }}</span>\n </div>\n <div class=\"fb-actions\">\n <!-- why: \u0111\u00E2y l\u00E0 affordance \"ch\u1ECDn item \u0111\u1EC3 s\u1EEDa\" cho b\u00E0n ph\u00EDm / screen reader. N\u00F3 KH\u00D4NG n\u1EB1m\n tr\u00EAn th\u1EBB card b\u1ECDc ngo\u00E0i: card b\u1ECDc c\u1EA3 .fb-actions (n\u00FAt th\u1EADt) v\u00E0 preview control (input\n th\u1EADt), n\u00EAn role=\"button\" \u1EDF \u0111\u00F3 khi\u1EBFn AT g\u1ED9p to\u00E0n b\u1ED9 th\u00E0nh m\u1ED9t n\u00FAt v\u00E0 c\u00E1c control b\u00EAn\n trong bi\u1EBFn m\u1EA5t kh\u1ECFi accessibility tree. Card gi\u1EEF (click) l\u00E0m ti\u1EC7n \u00EDch chu\u1ED9t. -->\n <button\n type=\"button\"\n class=\"fb-icon-btn\"\n [attr.aria-pressed]=\"item.id === selectedComponent()?.id\"\n [attr.aria-label]=\"item.properties?.label || item.type\"\n [matTooltip]=\"'core.component.form-builder.select-item' | sdTranslate\"\n (click)=\"selectComponent(item); $event.stopPropagation()\">\n <span class=\"msi\">ads_click</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' | sdTranslate)\n : ('core.component.form-builder.hide' | sdTranslate)\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' | sdTranslate\"\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 <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\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 <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' | sdTranslate }}\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' | sdTranslate }}\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' | sdTranslate }}\n </span>\n }\n\n <!-- Floating action toolbar pill -->\n <div class=\"fb-actions\">\n <!-- why: \u0111\u00E2y l\u00E0 affordance \"ch\u1ECDn item \u0111\u1EC3 s\u1EEDa\" cho b\u00E0n ph\u00EDm / screen reader. N\u00F3 KH\u00D4NG n\u1EB1m\n tr\u00EAn th\u1EBB card b\u1ECDc ngo\u00E0i: card b\u1ECDc c\u1EA3 .fb-actions (n\u00FAt th\u1EADt) v\u00E0 preview control (input\n th\u1EADt), n\u00EAn role=\"button\" \u1EDF \u0111\u00F3 khi\u1EBFn AT g\u1ED9p to\u00E0n b\u1ED9 th\u00E0nh m\u1ED9t n\u00FAt v\u00E0 c\u00E1c control b\u00EAn\n trong bi\u1EBFn m\u1EA5t kh\u1ECFi accessibility tree. Card gi\u1EEF (click) l\u00E0m ti\u1EC7n \u00EDch chu\u1ED9t. -->\n <button\n type=\"button\"\n class=\"fb-icon-btn\"\n [attr.aria-pressed]=\"item.id === selectedComponent()?.id\"\n [attr.aria-label]=\"item.properties?.label || item.type\"\n [matTooltip]=\"'core.component.form-builder.select-item' | sdTranslate\"\n (click)=\"selectComponent(item); $event.stopPropagation()\">\n <span class=\"msi\">ads_click</span>\n </button>\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' | sdTranslate)\n : ('core.component.form-builder.set-readonly' | sdTranslate)\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' | sdTranslate\"\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' | sdTranslate)\n : ('core.component.form-builder.hide' | sdTranslate)\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' | sdTranslate\"\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' | sdTranslate\"\n aria-label=\"resize\"></div>\n </div>\n </div>\n }\n }\n @if (shouldShowInlinePalettePlaceholder(row, row.items.length)) {\n <div\n class=\"fb-drop-placeholder fb-palette-drop-placeholder\"\n [style.--fb-placeholder-columns]=\"palettePlaceholderColumnsFor(row)\"\n aria-hidden=\"true\">\n <ng-container *ngTemplateOutlet=\"dropPlaceholderTemplate; context: { $implicit: draggedPaletteItem() }\"></ng-container>\n </div>\n }\n </div>\n </div>\n @if (shouldShowRowInsertionPlaceholder(row, 'after')) {\n <div class=\"fb-row-insert-placeholder fb-palette-drop-placeholder\" [style.--fb-placeholder-columns]=\"12\" aria-hidden=\"true\">\n <ng-container\n *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' | sdTranslate }}</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' | sdTranslate) }}</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 <!-- why: aria-hidden=\"true\" \u1EDF \u0111\u00E2y l\u00E0 \u1ED5 ch\u1EE9a l\u1EDBn nh\u1EA5t \u2014 panel n\u00E0y render TO\u00C0N B\u1ED8 form thu\u1ED9c\n t\u00EDnh c\u1EE7a component \u0111ang ch\u1ECDn (h\u00E0ng ch\u1EE5c <input>/<select> th\u1EADt). aria-hidden khi\u1EBFn ch\u00FAng\n v\u1EABn tab t\u1EDBi \u0111\u01B0\u1EE3c nh\u01B0ng screen reader kh\u00F4ng \u0111\u1ECDc \u0111\u01B0\u1EE3c nh\u00E3n n\u00E0o c\u1EA3. B\u1ECF h\u1EB3n. -->\n <div class=\"fb-attrs__body\">\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' | sdTranslate }}</div>\n <div class=\"hint\">{{ 'core.component.form-builder.no-selection-hint' | sdTranslate }}</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 <!-- why: schema l\u00E0 JSON nhi\u1EC1u t\u1EA7ng \u2014 textarea tr\u1EA7n kh\u00F4ng cho bi\u1EBFt d\u1EA5u ngo\u1EB7c n\u00E0o kh\u1EDBp d\u1EA5u n\u00E0o,\n c\u0169ng kh\u00F4ng b\u00E1o c\u00FA ph\u00E1p h\u1ECFng cho t\u1EDBi l\u00FAc b\u1EA5m L\u01B0u. -->\n <sd-code-editor language=\"json\" [(model)]=\"jsonString\" maxHeight=\"60vh\"></sd-code-editor>\n <ng-container sdFooterRight>\n <sd-button\n type=\"fill\"\n [title]=\"'core.component.form-builder.save' | sdTranslate\"\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' | sdTranslate\"\n view=\"dialog\"\n width=\"800px\"\n modalClass=\"sd-form-builder-dialog\"\n #popupConfigureVariables>\n <!-- why: tr\u01B0\u1EDBc \u0111\u00E2y c\u00E1c h\u00E0ng n\u1EB1m tr\u1EA7n trong modal (modal body padding 0) n\u00EAn d\u00EDnh s\u00E1t m\u00E9p, kh\u00F4ng\n c\u00F3 tr\u1EA1ng th\u00E1i r\u1ED7ng, v\u00E0 n\u00FAt th\u00EAm l\u00E0 <a href=\"javascript:;\"> \u2014 kh\u00F4ng ph\u1EA3i n\u00FAt th\u1EADt, b\u00E0n ph\u00EDm v\u00E0\n screen reader \u0111\u1EC1u \u0111\u1ECDc sai. -->\n <div class=\"fb-variables\">\n @if (clonedVariables.length) {\n <!-- why: m\u1ED7i bi\u1EBFn ch\u1EC9 c\u00F3 hai tr\u01B0\u1EDDng c\u00F9ng ki\u1EC3u, l\u1EB7p l\u1EA1i theo h\u00E0ng \u2014 \u0111\u00F3 l\u00E0 d\u1EEF li\u1EC7u d\u1EA1ng b\u1EA3ng.\n \u0110\u1EB7t nh\u00E3n c\u1ED9t M\u1ED8T l\u1EA7n \u1EDF header thay v\u00EC l\u1EB7p nh\u00E3n n\u1ED5i tr\u00EAn t\u1EEBng \u00F4 c\u1EE7a t\u1EEBng h\u00E0ng. -->\n <table class=\"fb-variables__table\">\n <thead>\n <tr>\n <th scope=\"col\">{{ 'core.component.form-builder.field-key' | sdTranslate }}</th>\n <th scope=\"col\">{{ 'core.component.form-builder.display-label' | sdTranslate }}</th>\n <!-- why: c\u1ED9t thao t\u00E1c kh\u00F4ng c\u1EA7n nh\u00E3n nh\u00ECn th\u1EA5y, nh\u01B0ng v\u1EABn ph\u1EA3i c\u00F3 t\u00EAn cho screen reader \u2014\n lib kh\u00F4ng c\u00F3 ti\u1EC7n \u00EDch .sr-only n\u00EAn \u0111\u1EB7t t\u00EAn b\u1EB1ng aria-label ngay tr\u00EAn \u00F4 header. -->\n <th scope=\"col\" class=\"fb-variables__actions-col\" [attr.aria-label]=\"'core.component.form-builder.delete' | sdTranslate\"></th>\n </tr>\n </thead>\n <tbody>\n @for (variable of clonedVariables; track variable.id) {\n <tr>\n <td>\n <sd-input size=\"sm\" [form]=\"form\" [(model)]=\"variable.key\" hideInlineError required></sd-input>\n </td>\n <td>\n <sd-input size=\"sm\" [form]=\"form\" [(model)]=\"variable.label\" hideInlineError required></sd-input>\n </td>\n <td class=\"fb-variables__actions-col\">\n <sd-button\n prefixIcon=\"delete\"\n type=\"text\"\n color=\"error\"\n [tooltip]=\"'core.component.form-builder.delete' | sdTranslate\"\n (click)=\"removeVariables(variable.id)\"></sd-button>\n </td>\n </tr>\n }\n </tbody>\n </table>\n } @else {\n <p class=\"fb-variables__empty\">{{ 'core.component.form-builder.no-variable' | sdTranslate }}</p>\n }\n\n <sd-button\n class=\"fb-variables__add\"\n type=\"text\"\n color=\"primary\"\n prefixIcon=\"add\"\n [title]=\"'core.component.form-builder.add-variable' | sdTranslate\"\n (click)=\"addVariables()\"></sd-button>\n </div>\n <ng-container sdFooterRight>\n <sd-button\n type=\"fill\"\n [title]=\"'core.component.form-builder.save' | sdTranslate\"\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--badged{position:relative;overflow:visible}:host ::ng-deep .fb-icon-btn__badge{position:absolute;top:-2px;right:-2px;min-width:14px;height:14px;padding:0 3px;border-radius:7px;background:var(--md-sys-color-error, #b3261e);color:var(--md-sys-color-on-error, #fff);font-size:9px;font-weight:600;line-height:14px;text-align:center;pointer-events:none}: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;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-palette-native-placeholder{display:block!important;visibility:hidden!important;opacity:0!important;width:1px!important;min-width:1px!important;height:1px!important;min-height:1px!important;flex:0 0 1px!important;margin:0!important;padding:0!important;border:0!important;overflow:hidden!important;pointer-events: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)}::ng-deep .sd-form-builder-dialog .fb-variables{display:flex;flex-direction:column;gap:8px}::ng-deep .sd-form-builder-dialog .fb-variables__table{width:100%;border-collapse:separate;border-spacing:0;border:1px solid var(--sd-border, #e6e6e6);border-radius:8px;overflow:hidden}::ng-deep .sd-form-builder-dialog .fb-variables__table th{padding:8px 12px;background:var(--md-sys-color-surface-container-low, #f7f8fa);border-bottom:1px solid var(--sd-border, #e6e6e6);color:var(--sd-text-secondary, #6b7280);font-size:12px;font-weight:500;text-align:left;white-space:nowrap}::ng-deep .sd-form-builder-dialog .fb-variables__table td{padding:8px 12px;border-bottom:1px solid var(--sd-border, #e6e6e6);vertical-align:middle}::ng-deep .sd-form-builder-dialog .fb-variables__table tbody tr:last-child td{border-bottom:none}::ng-deep .sd-form-builder-dialog .fb-variables__actions-col{width:1px;white-space:nowrap;text-align:right}::ng-deep .sd-form-builder-dialog .fb-variables__empty{margin:0;padding:16px;border:1px dashed var(--sd-border, #e6e6e6);border-radius:8px;color:var(--sd-text-secondary, #6b7280);font-size:13px;text-align:center}::ng-deep .sd-form-builder-dialog .fb-variables__add{align-self:flex-start}::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-canvas__body>.cdk-drop-list{min-height:100%;box-sizing:border-box}.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;padding:0;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:focus-visible{outline:2px solid var(--sd-primary, #005cbb);outline-offset:2px}.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;border:0;background:transparent;font-family:inherit}.fb-add-link .msi{font-size:14px}.fb-add-link:hover{text-decoration:underline}.fb-add-link:focus-visible{outline:2px solid var(--sd-primary, #005cbb);outline-offset:2px}.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"] }]
|
|
6546
6678
|
}], 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 }] }], canvasDropZone: [{ type: i0.ViewChild, args: ['canvasDropZone', { isSignal: true }] }], rowDropLists: [{ type: i0.ViewChildren, args: ['rowDropList', { ...{ read: CdkDropList }, isSignal: true }] }], formGeneric: [{ type: i0.Input, args: [{ isSignal: true, alias: "formGeneric", required: false }] }] } });
|
|
6547
6679
|
|
|
6548
6680
|
// Template là các mẫu do Portal định nghĩa sẵn (key, label ....) để người dùng chọn nhanh
|
|
@@ -6553,7 +6685,7 @@ class SdFeelExpression {
|
|
|
6553
6685
|
formGenericService = inject(FormGenericService);
|
|
6554
6686
|
modal;
|
|
6555
6687
|
form = new FormGroup({});
|
|
6556
|
-
attributeOperators =
|
|
6688
|
+
attributeOperators = SD_ATTRIBUTE_OPERATORS;
|
|
6557
6689
|
attributes = [];
|
|
6558
6690
|
attribute = {};
|
|
6559
6691
|
components = input.required(...(ngDevMode ? [{ debugName: "components" }] : []));
|
|
@@ -6638,7 +6770,7 @@ class SdFeelExpression {
|
|
|
6638
6770
|
if (components.length) {
|
|
6639
6771
|
for (const component of components) {
|
|
6640
6772
|
if (component.type === 'group') {
|
|
6641
|
-
attributes.push(...
|
|
6773
|
+
attributes.push(...sdGetAttributes(component.components));
|
|
6642
6774
|
}
|
|
6643
6775
|
else if (component.type === 'textfield' || component.type === 'textarea') {
|
|
6644
6776
|
attributes.push({
|
|
@@ -6703,11 +6835,11 @@ class SdFeelExpression {
|
|
|
6703
6835
|
return attributes;
|
|
6704
6836
|
};
|
|
6705
6837
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdFeelExpression, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
6706
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdFeelExpression, isStandalone: true, selector: "sd-feel-expression", inputs: { components: { classPropertyName: "components", publicName: "components", isSignal: true, isRequired: true, transformFunction: null }, _expression: { classPropertyName: "_expression", publicName: "expression", isSignal: false, isRequired: true, transformFunction: null }, _model: { classPropertyName: "_model", publicName: "model", isSignal: false, isRequired: true, transformFunction: null } }, 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", "mask", "hideInlineError", "blurOnEnter", "clearable", "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", "clearable", "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", "beforeClose"], outputs: ["sdClosed", "sdCloseError"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
6838
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: SdFeelExpression, isStandalone: true, selector: "sd-feel-expression", inputs: { components: { classPropertyName: "components", publicName: "components", isSignal: true, isRequired: true, transformFunction: null }, _expression: { classPropertyName: "_expression", publicName: "expression", isSignal: false, isRequired: true, transformFunction: null }, _model: { classPropertyName: "_model", publicName: "model", isSignal: false, isRequired: true, transformFunction: null } }, 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' | sdTranslate\" 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' | sdTranslate\" color=\"primary\" (click)=\"addCombinator(expression.conditions)\"></sd-button>\n <sd-button prefixIcon=\"add\" [title]=\"'core.component.form-builder.condition' | sdTranslate\" 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' | sdTranslate\"\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' | sdTranslate\"\n (click)=\"remove(expression.conditions, idxLv1)\"></sd-button>\n <sd-button prefixIcon=\"add\" [title]=\"'core.component.form-builder.condition' | sdTranslate\" 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' | sdTranslate\"\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' | sdTranslate\" (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", "mask", "hideInlineError", "blurOnEnter", "clearable", "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", "clearable", "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", "showSelectAll", "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", "beforeClose"], outputs: ["sdClosed", "sdCloseError"] }, { kind: "pipe", type: SdTranslatePipe, name: "sdTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
6707
6839
|
}
|
|
6708
6840
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdFeelExpression, decorators: [{
|
|
6709
6841
|
type: Component,
|
|
6710
|
-
args: [{ selector: 'sd-feel-expression', changeDetection: ChangeDetectionStrategy.OnPush, imports: [SdInput$1, SdInputNumber, SdSelect$1, SdAutocomplete$1, SdButton, SdSelect$1, SdModal,
|
|
6842
|
+
args: [{ selector: 'sd-feel-expression', changeDetection: ChangeDetectionStrategy.OnPush, imports: [SdInput$1, SdInputNumber, SdSelect$1, SdAutocomplete$1, SdButton, SdSelect$1, SdModal, SdTranslatePipe], template: "<a class=\"T12R\" href=\"javascript:;\" (click)=\"edit()\">{{model || '--'}}</a>\n@if (expression) {\n<sd-modal [title]=\"'core.component.form-builder.set-expression' | sdTranslate\" 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' | sdTranslate\" color=\"primary\" (click)=\"addCombinator(expression.conditions)\"></sd-button>\n <sd-button prefixIcon=\"add\" [title]=\"'core.component.form-builder.condition' | sdTranslate\" 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' | sdTranslate\"\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' | sdTranslate\"\n (click)=\"remove(expression.conditions, idxLv1)\"></sd-button>\n <sd-button prefixIcon=\"add\" [title]=\"'core.component.form-builder.condition' | sdTranslate\" 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' | sdTranslate\"\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' | sdTranslate\" (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"] }]
|
|
6711
6843
|
}], ctorParameters: () => [], propDecorators: { modal: [{
|
|
6712
6844
|
type: ViewChild,
|
|
6713
6845
|
args: [SdModal]
|
|
@@ -6723,5 +6855,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
|
|
|
6723
6855
|
* Generated bundle index. Do not edit.
|
|
6724
6856
|
*/
|
|
6725
6857
|
|
|
6726
|
-
export {
|
|
6858
|
+
export { SD_ATTRIBUTE_OPERATORS, SD_COMPONENT_ICONS, SD_DAY_INFO_PREVIOUSES, SD_DAY_INFO_TYPES, SD_FORM_BUILDER_COMPONENTS, SD_FORM_GENERIC_CONFIGURATION, SD_TABLE_COLUMN_TYPES, SdFeelExpression, SdFormBuilder, SdFormGenericOperators, SdFormRender, SdFormRenderService, ValidationAlerts, sdEvaluateExpression, sdExpressionToJavascriptExpression, sdFormatComponent, sdGenerateId, sdGenerateKey, sdGetAttributes, sdGetComponentAttributes, sdGetDatetimeValue, sdGetVariableAttributes, sdTemplateToCondition };
|
|
6727
6859
|
//# sourceMappingURL=sdcorejs-angular-components-form-generic.mjs.map
|