@sdcorejs/angular 20.0.3 → 20.0.4

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.
Files changed (37) hide show
  1. package/components/button/index.d.ts +1 -2
  2. package/components/form-generic/index.d.ts +3 -4
  3. package/components/query-builder/index.d.ts +247 -23
  4. package/components/section/index.d.ts +1 -2
  5. package/components/side-drawer/index.d.ts +1 -2
  6. package/components/stepper/index.d.ts +1 -2
  7. package/components/tab/index.d.ts +1 -2
  8. package/components/table/index.d.ts +1 -2
  9. package/components/upload-file/index.d.ts +2 -0
  10. package/fesm2022/sdcorejs-angular-components-button.mjs +2 -4
  11. package/fesm2022/sdcorejs-angular-components-button.mjs.map +1 -1
  12. package/fesm2022/sdcorejs-angular-components-form-generic.mjs +6 -10
  13. package/fesm2022/sdcorejs-angular-components-form-generic.mjs.map +1 -1
  14. package/fesm2022/sdcorejs-angular-components-query-builder.mjs +669 -57
  15. package/fesm2022/sdcorejs-angular-components-query-builder.mjs.map +1 -1
  16. package/fesm2022/sdcorejs-angular-components-section.mjs +2 -4
  17. package/fesm2022/sdcorejs-angular-components-section.mjs.map +1 -1
  18. package/fesm2022/sdcorejs-angular-components-side-drawer.mjs +2 -4
  19. package/fesm2022/sdcorejs-angular-components-side-drawer.mjs.map +1 -1
  20. package/fesm2022/sdcorejs-angular-components-stepper.mjs +2 -4
  21. package/fesm2022/sdcorejs-angular-components-stepper.mjs.map +1 -1
  22. package/fesm2022/sdcorejs-angular-components-tab.mjs +2 -4
  23. package/fesm2022/sdcorejs-angular-components-tab.mjs.map +1 -1
  24. package/fesm2022/sdcorejs-angular-components-table.mjs +2 -4
  25. package/fesm2022/sdcorejs-angular-components-table.mjs.map +1 -1
  26. package/fesm2022/sdcorejs-angular-components-upload-file.mjs +15 -4
  27. package/fesm2022/sdcorejs-angular-components-upload-file.mjs.map +1 -1
  28. package/fesm2022/sdcorejs-angular-forms-autocomplete.mjs +7 -2
  29. package/fesm2022/sdcorejs-angular-forms-autocomplete.mjs.map +1 -1
  30. package/fesm2022/sdcorejs-angular-forms-input-number.mjs +9 -3
  31. package/fesm2022/sdcorejs-angular-forms-input-number.mjs.map +1 -1
  32. package/fesm2022/sdcorejs-angular-forms-input.mjs +2 -2
  33. package/fesm2022/sdcorejs-angular-forms-input.mjs.map +1 -1
  34. package/fesm2022/sdcorejs-angular-services-notify.mjs +26 -8
  35. package/fesm2022/sdcorejs-angular-services-notify.mjs.map +1 -1
  36. package/package.json +1 -1
  37. package/services/notify/index.d.ts +17 -0
@@ -11,7 +11,6 @@ import * as i2 from '@angular/material/icon';
11
11
  import { MatIconModule } from '@angular/material/icon';
12
12
  import * as i2$1 from '@angular/material/tooltip';
13
13
  import { MatTooltipModule } from '@angular/material/tooltip';
14
- import { SdBaseSecureComponent } from '@sdcorejs/angular/components/base';
15
14
  import { SdButton } from '@sdcorejs/angular/components/button';
16
15
  import { SdModal } from '@sdcorejs/angular/components/modal';
17
16
  import { SdInput as SdInput$1 } from '@sdcorejs/angular/forms/input';
@@ -2938,7 +2937,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImpo
2938
2937
 
2939
2938
  /* eslint-disable @typescript-eslint/no-explicit-any */
2940
2939
  /* eslint-disable @angular-eslint/no-input-rename */
2941
- class SdFormRender extends SdBaseSecureComponent {
2940
+ class SdFormRender {
2942
2941
  ref;
2943
2942
  formGenericConfiguration;
2944
2943
  formRenderItems = new QueryList();
@@ -2979,7 +2978,6 @@ class SdFormRender extends SdBaseSecureComponent {
2979
2978
  // Các component item của form-render sẽ lắng nghe thay đổi
2980
2979
  setVariables = new Subject();
2981
2980
  constructor(ref, formGenericConfiguration) {
2982
- super();
2983
2981
  this.ref = ref;
2984
2982
  this.formGenericConfiguration = formGenericConfiguration;
2985
2983
  }
@@ -3065,7 +3063,7 @@ class SdFormRender extends SdBaseSecureComponent {
3065
3063
  return messages;
3066
3064
  };
3067
3065
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.24", ngImport: i0, type: SdFormRender, deps: [{ token: i0.ChangeDetectorRef }, { token: SD_FORM_GENERIC_CONFIGURATION, optional: true }], target: i0.ɵɵFactoryTarget.Component });
3068
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.24", 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 }], usesInheritance: 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", "collapsable", "hideHeader", "noPaddingBody"], 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 });
3066
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.24", 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", "collapsable", "hideHeader", "noPaddingBody"], 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 });
3069
3067
  }
3070
3068
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImport: i0, type: SdFormRender, decorators: [{
3071
3069
  type: Component,
@@ -5788,7 +5786,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImpo
5788
5786
  /* eslint-disable @angular-eslint/no-input-rename */
5789
5787
  /* eslint-disable @typescript-eslint/no-explicit-any */
5790
5788
  /* eslint-disable @typescript-eslint/no-unused-vars */
5791
- class SdFormBuilder extends SdBaseSecureComponent {
5789
+ class SdFormBuilder {
5792
5790
  // ── viewChild signals (Angular 17+) ────────────────────────────────────
5793
5791
  popupViewJSON = viewChild('popupViewJSON', ...(ngDevMode ? [{ debugName: "popupViewJSON" }] : []));
5794
5792
  popupConfigureVariables = viewChild('popupConfigureVariables', ...(ngDevMode ? [{ debugName: "popupConfigureVariables" }] : []));
@@ -5894,7 +5892,6 @@ class SdFormBuilder extends SdBaseSecureComponent {
5894
5892
  #validationsChanges = new Subject();
5895
5893
  #subscription = new Subscription();
5896
5894
  constructor() {
5897
- super();
5898
5895
  // Khi input `formGeneric` ref thay đổi: clone components/variables/validations
5899
5896
  // sang local arrays và bắn subject tương ứng cho stream debounced (syncRows).
5900
5897
  // untracked() bao bọc các mutation để không tạo cycle (effect chỉ tracks formGeneric()).
@@ -6384,7 +6381,7 @@ class SdFormBuilder extends SdBaseSecureComponent {
6384
6381
  }
6385
6382
  };
6386
6383
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.24", ngImport: i0, type: SdFormBuilder, deps: [], target: i0.ɵɵFactoryTarget.Component });
6387
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.24", 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 }], usesInheritance: 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 class=\"fb-shell\" cdkDropListGroup [class.fb-shell--dragging]=\"isAnyDragging()\">\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 [cdkDropListEnterPredicate]=\"noReturnPredicate\">\n @for (formBuilderComponent of group.items; track formBuilderComponent.type) {\n <div\n cdkDrag\n class=\"fb-palette-item\"\n [matTooltip]=\"formBuilderComponent.name\"\n matTooltipPosition=\"right\"\n (click)=\"addComponent(formBuilderComponent)\"\n (cdkDragStarted)=\"onAnyDragStarted()\"\n (cdkDragMoved)=\"onAnyDragMoved($event)\"\n (cdkDragEnded)=\"onAnyDragEnded()\"\n aria-hidden=\"true\">\n <span class=\"fb-palette-item__icon\"><span class=\"msi\">{{ formBuilderComponent.symbol }}</span></span>\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 <!-- Generic drag preview: ch\u1EC9 icon (puck), label \u1EA9n -->\n <div *cdkDragPreview class=\"fb-drag-preview\"></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 _editing = editingGroup();\n @if (_editing) {\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 type=\"button\" class=\"fb-icon-btn\" [matTooltip]=\"'core.component.form-builder.back-to-design' | translate\" (click)=\"cancelGroupEdit()\">\n <span class=\"msi\">chevron_left</span>\n </button>\n <span class=\"fb-groupedit__icon\"><span class=\"msi\">{{ _editing.properties?.icon || 'category' }}</span></span>\n <span>{{ _editing.label || ('core.component.form-builder.edit-group' | translate) }}</span>\n <span class=\"fb-groupedit__hint\">{{ 'core.component.form-builder.edit-group' | translate }}</span>\n </div>\n <div class=\"fb-canvas__actions\">\n <sd-button type=\"outline\" [title]=\"'core.component.form-builder.cancel' | translate\" (click)=\"cancelGroupEdit()\"></sd-button>\n <sd-button type=\"fill\" color=\"primary\" prefixIcon=\"check\" [title]=\"'core.component.form-builder.confirm' | translate\" (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\n type=\"button\"\n class=\"fb-icon-btn\"\n [matTooltip]=\"'core.component.form-builder.view-json' | translate\"\n (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\n type=\"button\"\n role=\"tab\"\n [class.is-active]=\"!isPreview()\"\n (click)=\"setMode(false)\">\n <span class=\"msi\">edit</span>\n <span>{{ 'core.component.form-builder.design' | translate }}</span>\n </button>\n <button\n type=\"button\"\n role=\"tab\"\n [class.is-active]=\"isPreview()\"\n (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\n class=\"fb-canvas__body\"\n [class.is-preview]=\"isPreview()\"\n id=\"frmComponent\">\n @if (isPreview()) {\n <!-- \u2500\u2500 Preview mode \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"fb-preview-card\">\n <div class=\"fb-preview-card__chip\">\n <span class=\"msi\">visibility</span>\n {{ 'core.component.form-builder.preview-runtime' | translate }}\n </div>\n <sd-form-render [configuration]=\"{ components: this.components, validations: this.validations }\"></sd-form-render>\n <div class=\"fb-preview-card__footer\">\n <sd-button type=\"outline\" [title]=\"'core.component.form-builder.cancel' | translate\" (click)=\"setMode(false)\"></sd-button>\n <sd-button type=\"fill\" color=\"primary\" [title]=\"'core.component.form-builder.submit' | translate\" (click)=\"onValidate()\"></sd-button>\n </div>\n </div>\n } @else {\n <!-- \u2500\u2500 Design mode \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (!dragDropRows.length) {\n <div class=\"fb-empty\">\n <div class=\"fb-empty__art\"><span class=\"msi\">dashboard_customize</span></div>\n <div class=\"fb-empty__title\">{{ 'core.component.form-builder.empty-title' | translate }}</div>\n <div class=\"fb-empty__hint\">\n {{ 'core.component.form-builder.empty-hint' | translate }}\n <span class=\"fb-empty__kbd\"><span class=\"msi\">keyboard</span>\u2318 K</span>\n </div>\n </div>\n }\n\n <div\n cdkDropList\n cdkDropListOrientation=\"vertical\"\n [cdkDropListData]=\"dragDropRows\"\n (cdkDropListDropped)=\"drop($event)\"\n (click)=\"clickFormContentEmpty()\"\n aria-hidden=\"true\">\n @for (row of dragDropRows; track row.rowIndex) {\n <div\n cdkDrag\n class=\"fb-row\"\n (mouseover)=\"onMouseover($event, row)\"\n (focus)=\"onFocus($event)\"\n (cdkDragStarted)=\"onAnyDragStarted()\"\n (cdkDragMoved)=\"onAnyDragMoved($event)\"\n (cdkDragEnded)=\"onAnyDragEnded()\"\n [class.is-drag-target]=\"targetItem === row\"\n [id]=\"'row_' + row.rowIndex\"\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 <!-- Generic puck preview -->\n <div *cdkDragPreview class=\"fb-drag-preview\"></div>\n <!-- Quick-add break button \u2014 show on row hover, insert break after this row -->\n <button\n type=\"button\"\n class=\"fb-row-break-btn\"\n [matTooltip]=\"'core.component.form-builder.break' | translate\"\n matTooltipPosition=\"right\"\n (click)=\"insertBreakAfter(row); $event.stopPropagation()\">\n <span class=\"msi\">horizontal_rule</span>\n </button>\n\n <div\n class=\"fb-row__items\"\n style=\"display: flex; flex: 1; gap: var(--gap-2);\"\n cdkDropList\n cdkDropListOrientation=\"horizontal\"\n [cdkDropListData]=\"row.items\"\n (cdkDropListDropped)=\"drop($event)\">\n\n @for (item of row.items; track item.id; let i = $index) {\n <!-- Group component renders as outlined card with colored header bar -->\n @if (item.type === 'group') {\n @let g = $any(item);\n @let cv = groupColorVars(g.properties?.color);\n <div\n cdkDrag\n [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); onAnyDragStarted()\"\n (cdkDragMoved)=\"onAnyDragMoved($event)\"\n (cdkDragEnded)=\"dragEndComponentItem($event); onAnyDragEnded()\"\n aria-hidden=\"true\">\n <!-- Generic puck preview -->\n <div *cdkDragPreview class=\"fb-drag-preview\"></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\"><span class=\"msi\">{{ g.properties?.icon || 'category' }}</span></span>\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 type=\"button\" 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 type=\"button\" 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 type=\"button\" class=\"fb-icon-btn\"\n [class.warning]=\"item.properties?.hidden\"\n [class.on]=\"item.properties?.hidden\"\n [matTooltip]=\"item.properties?.hidden ? ('core.component.form-builder.show' | translate) : ('core.component.form-builder.hide' | translate)\"\n (click)=\"onChangeHidden(item); $event.stopPropagation()\">\n <span class=\"msi\">{{ item.properties?.hidden ? 'visibility_off' : 'visibility' }}</span>\n </button>\n <button type=\"button\" 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\n *ngTemplateOutlet=\"controlTemplate; context: { $implicit: child }\">\n </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 [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); onAnyDragStarted()\"\n (cdkDragMoved)=\"onAnyDragMoved($event)\"\n (cdkDragEnded)=\"dragEndComponentItem($event); onAnyDragEnded()\"\n aria-hidden=\"true\">\n <!-- Generic puck preview -->\n <div *cdkDragPreview class=\"fb-drag-preview\"></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 type=\"button\" class=\"fb-icon-btn\"\n [class.warning]=\"item.properties?.hidden\"\n [class.on]=\"item.properties?.hidden\"\n [matTooltip]=\"item.properties?.hidden ? ('core.component.form-builder.show' | translate) : ('core.component.form-builder.hide' | translate)\"\n (click)=\"onChangeHidden(item); $event.stopPropagation()\">\n <span class=\"msi\">{{ item.properties?.hidden ? 'visibility_off' : 'visibility' }}</span>\n </button>\n <button type=\"button\" 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 [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 (click)=\"selectComponent(item); $event.stopPropagation()\"\n (cdkDragStarted)=\"dragStartComponentItem($event); onAnyDragStarted()\"\n (cdkDragMoved)=\"onAnyDragMoved($event)\"\n (cdkDragEnded)=\"dragEndComponentItem($event); onAnyDragEnded()\"\n aria-hidden=\"true\">\n <!-- Generic puck preview -->\n <div *cdkDragPreview class=\"fb-drag-preview\"></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 type=\"button\" class=\"fb-icon-btn\"\n [class.warning]=\"item.properties?.viewed\"\n [class.on]=\"item.properties?.viewed\"\n [matTooltip]=\"item.properties?.viewed ? ('core.component.form-builder.unset-readonly' | translate) : ('core.component.form-builder.set-readonly' | translate)\"\n (click)=\"onChangeViewed(item); $event.stopPropagation()\">\n <span class=\"msi\">edit_off</span>\n </button>\n <button type=\"button\" 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 type=\"button\" class=\"fb-icon-btn\"\n [class.warning]=\"item.properties?.hidden\"\n [class.on]=\"item.properties?.hidden\"\n [matTooltip]=\"item.properties?.hidden ? ('core.component.form-builder.show' | translate) : ('core.component.form-builder.hide' | translate)\"\n (click)=\"onChangeHidden(item); $event.stopPropagation()\">\n <span class=\"msi\">{{ item.properties?.hidden ? 'visibility_off' : 'visibility' }}</span>\n </button>\n <button type=\"button\" 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\n *ngTemplateOutlet=\"controlTemplate; context: { $implicit: item }\">\n </ng-container>\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)=\"onAnyDragStarted()\"\n (cdkDragMoved)=\"changeSizeControl($event, item, row.items, i)\"\n (cdkDragEnded)=\"dragEndChangeSizeControl($event); onAnyDragEnded()\"\n [matTooltip]=\"'core.component.form-builder.flex' | translate\"\n aria-label=\"resize\"></div>\n </div>\n </div>\n }\n }\n </div>\n </div>\n }\n </div>\n }\n </div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 RIGHT ATTRIBUTES \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <aside class=\"fb-attrs\">\n <div class=\"fb-panel-header\">\n <div class=\"fb-panel-header__title\">{{ 'core.component.form-builder.attribute' | translate }}</div>\n </div>\n\n @let sel = selectedComponent();\n @if (sel) {\n <!-- Selection header: type icon + name + key/type -->\n <div class=\"fb-attrs__selection\">\n <span class=\"fb-attrs__sel-icon\"><span class=\"msi\">{{ symbolFor(sel) }}</span></span>\n <div class=\"fb-attrs__sel-meta\">\n <div class=\"fb-attrs__sel-name\">{{ $any(sel).label || ('core.component.form-builder.untitled' | translate) }}</div>\n <div class=\"fb-attrs__sel-key\">\n @if ($any(sel).key) {\n {{ $any(sel).key }} \u00B7 {{ typeLabelFor(sel) }}\n } @else {\n {{ typeLabelFor(sel) }}\n }\n </div>\n </div>\n </div>\n\n <div class=\"fb-attrs__body\" aria-hidden=\"true\">\n @if (sel.type === 'textfield') {\n <textfield-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></textfield-attribute>\n } @else if (sel.type === 'textarea') {\n <textarea-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></textarea-attribute>\n } @else if (sel.type === 'chip-string') {\n <chip-string-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></chip-string-attribute>\n } @else if (sel.type === 'chip-calendar') {\n <chip-calendar-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></chip-calendar-attribute>\n } @else if (sel.type === 'number') {\n <number-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></number-attribute>\n } @else if (sel.type === 'datetime') {\n <datetime-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></datetime-attribute>\n } @else if (sel.type === 'select') {\n <select-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></select-attribute>\n } @else if (sel.type === 'radio') {\n <radio-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></radio-attribute>\n } @else if (sel.type === 'checkbox') {\n <checkbox-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></checkbox-attribute>\n } @else if (sel.type === 'html') {\n <html-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></html-attribute>\n } @else if (sel.type === 'upload') {\n <upload-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></upload-attribute>\n } @else if (sel.type === 'table') {\n <table-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></table-attribute>\n } @else if (sel.type === 'group') {\n <group-attribute [group]=\"$any(sel)\"></group-attribute>\n }\n </div>\n } @else {\n <div class=\"fb-attrs__placeholder\">\n <span class=\"msi\">ads_click</span>\n <div class=\"title\">{{ 'core.component.form-builder.no-selection-title' | translate }}</div>\n <div class=\"hint\">{{ 'core.component.form-builder.no-selection-hint' | translate }}</div>\n </div>\n }\n </aside>\n</div>\n\n<!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 Reusable control template \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n<ng-template #controlTemplate let-item>\n @if (item.type === 'textfield') {\n <textfield-control [component]=\"item\"></textfield-control>\n } @else if (item.type === 'textarea') {\n <textarea-control [component]=\"item\"></textarea-control>\n } @else if (item.type === 'chip-string') {\n <chip-string-control [component]=\"item\"></chip-string-control>\n } @else if (item.type === 'chip-calendar') {\n <chip-calendar-control [component]=\"item\"></chip-calendar-control>\n } @else if (item.type === 'number') {\n <number-control [component]=\"item\"></number-control>\n } @else if (item.type === 'datetime') {\n <datetime-control [component]=\"item\"></datetime-control>\n } @else if (item.type === 'select') {\n <select-control [component]=\"item\"></select-control>\n } @else if (item.type === 'radio') {\n <radio-control [component]=\"item\"></radio-control>\n } @else if (item.type === 'checkbox') {\n <checkbox-control [component]=\"item\"></checkbox-control>\n } @else if (item.type === 'html') {\n <html-control [component]=\"item\"></html-control>\n } @else if (item.type === 'upload') {\n <upload-control [component]=\"item\"></upload-control>\n } @else if (item.type === 'table') {\n <table-control [component]=\"item\"></table-control>\n }\n</ng-template>\n\n<!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 JSON / variables modals \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n<sd-modal title=\"JSON\" view=\"dialog\" width=\"80%\" #popupViewJSON>\n <sd-textarea [(model)]=\"jsonString\"></sd-textarea>\n <ng-container sdFooter>\n <sd-button type=\"fill\" [title]=\"'core.component.form-builder.save' | translate\" (click)=\"updateJSON()\" prefixIcon=\"save\" color=\"primary\"></sd-button>\n </ng-container>\n</sd-modal>\n<sd-modal [title]=\"'core.component.form-builder.config-variable' | translate\" view=\"dialog\" width=\"800px\" #popupConfigureVariables>\n @for (variable of clonedVariables; track variable.id) {\n <div class=\"d-flex align-items-center\" style=\"gap: 8px\">\n <sd-input [form]=\"form\" [label]=\"'core.component.form-builder.field-key' | translate\" [(model)]=\"variable.key\" required></sd-input>\n <sd-input [form]=\"form\" [label]=\"'core.component.form-builder.display-label' | translate\" [(model)]=\"variable.label\" required></sd-input>\n <sd-button prefixIcon=\"delete\" (click)=\"removeVariables(variable.id)\" color=\"error\"></sd-button>\n </div>\n }\n <a href=\"javascript:;\" (click)=\"addVariables()\">{{ 'core.component.form-builder.add-variable' | translate }}</a>\n <ng-container sdFooter>\n <sd-button type=\"fill\" [title]=\"'core.component.form-builder.save' | translate\" (click)=\"updateVariables()\" prefixIcon=\"save\" color=\"primary\"></sd-button>\n </ng-container>\n</sd-modal>\n<configure-validation (accept)=\"onUpdateValidations($event)\"></configure-validation>\n", styles: ["@charset \"UTF-8\";:host{--md-sys-color-primary: var(--sd-primary, #2A66F4);--md-sys-color-on-primary: #ffffff;--md-sys-color-primary-container: var(--sd-primary-light, #EAF1FF);--md-sys-color-on-primary-container: var(--sd-primary, #2A66F4);--md-sys-color-info: var(--sd-info, #2962FF);--md-sys-color-info-container: var(--sd-info-light, #E7E9FF);--md-sys-color-secondary: #625b71;--md-sys-color-secondary-container: #e8def8;--md-sys-color-on-secondary-container: #1d192b;--md-sys-color-surface: #fbfafc;--md-sys-color-surface-container-lowest: #ffffff;--md-sys-color-surface-container-low: #f6f4f8;--md-sys-color-surface-container: #f1eef3;--md-sys-color-surface-container-high: #ebe8ee;--md-sys-color-surface-container-highest: #e6e2ea;--md-sys-color-on-surface: #1c1b1f;--md-sys-color-on-surface-variant: #49454e;--md-sys-color-outline: #79747e;--md-sys-color-outline-variant: #d6d2da;--md-sys-color-error: #b3261e;--md-sys-color-error-container: #f9dedc;--md-sys-color-warning: #8a5400;--md-sys-color-warning-container: #ffddb1;--md-sys-color-success: #1f6f43;--md-sys-color-success-container: #b7f0cb;--h-row: 28px;--h-field: 32px;--h-palette: 36px;--h-header: 36px;--h-section: 28px;--h-action-btn: 24px;--gap-1: 4px;--gap-2: 8px;--gap-3: 12px;--gap-4: 16px;--gap-5: 24px;--r-xs: 4px;--r-sm: 8px;--r-md: 12px;--r-lg: 16px;--r-xl: 28px;--r-pill: 999px;--elev-1: 0 1px 2px 0 rgba(0, 0, 0, .06);--elev-2: 0 2px 6px 0 rgba(0, 0, 0, .08), 0 1px 2px 0 rgba(0, 0, 0, .04);--elev-3: 0 6px 14px 0 rgba(0, 0, 0, .1), 0 2px 4px 0 rgba(0, 0, 0, .06);display:block;width:100%;height:100%;font-family:Roboto,system-ui,sans-serif;font-size:13px;color:var(--md-sys-color-on-surface);background:var(--md-sys-color-surface)}:host ::ng-deep .msi{font-family:Material Symbols Rounded;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;font-variation-settings:\"wght\" 300,\"opsz\" 24,\"GRAD\" 0,\"FILL\" 0}:host ::ng-deep .msi.filled{font-variation-settings:\"wght\" 300,\"opsz\" 24,\"GRAD\" 0,\"FILL\" 1}: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:36px;height:36px;min-width:36px;min-height:36px;max-width:36px;max-height:36px;box-sizing:border-box;border-radius:50%;background:var(--md-sys-color-primary, #6750a4);display:inline-flex;align-items:center;justify-content:center;box-shadow:0 10px 24px #00000042,0 3px 8px #00000024;border:3px solid var(--md-sys-color-on-primary, #ffffff);cursor:grabbing;pointer-events:none;-webkit-user-select:none;user-select:none;opacity:.96;overflow:hidden;z-index:10000}:host ::ng-deep .cdk-drag-preview.fb-drag-preview:after,:host ::ng-deep .fb-drag-preview:after{content:\"\";width:8px;height:8px;border-radius:50%;background:var(--md-sys-color-on-primary, #ffffff);opacity:.9}@keyframes fb-drop-pulse{0%,to{box-shadow:0 0 0 3px color-mix(in srgb,var(--md-sys-color-info) 16%,transparent)}50%{box-shadow:0 0 0 5px color-mix(in srgb,var(--md-sys-color-info) 24%,transparent)}}:host ::ng-deep .cdk-drag-placeholder{background:transparent!important;border:none!important;border-radius:999px!important;padding:0!important;transform:none!important;transition:none!important;pointer-events:none;overflow:hidden;animation:none!important;opacity:0!important}:host ::ng-deep .cdk-drag-placeholder>*{visibility:hidden!important;pointer-events:none!important}:host ::ng-deep .fb-palette__list>.cdk-drag-placeholder{display:none!important}:host ::ng-deep .fb-row__items>.cdk-drag-placeholder,:host ::ng-deep .fb-group__body>.cdk-drag-placeholder{width:0!important;min-width:0!important;max-width:0!important;height:auto!important;min-height:0!important;align-self:stretch;flex:0 0 0!important;margin:0!important}:host ::ng-deep [cdkDropListOrientation=vertical]>.cdk-drag-placeholder{width:auto!important;height:0!important;min-height:0!important;max-height:0!important;flex:0 0 0!important;margin:0!important;align-self:stretch}: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) 10%,transparent)!important;outline:2px solid color-mix(in srgb,var(--md-sys-color-primary) 78%,transparent);outline-offset:-6px;border-radius:var(--r-sm)}:host ::ng-deep .fb-resize.cdk-drag-preview{display:none!important}:host ::ng-deep .fb-shell--dragging .fb-item:hover,:host ::ng-deep .fb-shell--dragging .fb-group:hover,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-item:hover,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-group:hover{border-color:transparent!important;background:transparent!important}:host ::ng-deep .fb-shell--dragging .fb-row:hover,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-row:hover{outline-color:transparent!important;background:transparent!important}:host ::ng-deep .fb-shell--dragging .fb-row:hover .fb-row__drag,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-row:hover .fb-row__drag{opacity:0!important}:host ::ng-deep .fb-shell--dragging .fb-row.is-drag-target,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-row.is-drag-target{outline-color:color-mix(in srgb,var(--md-sys-color-info) 44%,transparent)!important;background:color-mix(in srgb,var(--md-sys-color-info) 4%,transparent)!important}:host ::ng-deep .fb-shell--dragging .fb-row__drag,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-row__drag{opacity:0!important}:host ::ng-deep .fb-shell--dragging .fb-actions,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-actions{opacity:0!important;pointer-events:none!important;transform:translateY(-2px)!important}:host ::ng-deep .fb-shell--dragging .fb-resize,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-resize{opacity:0!important;pointer-events:none!important}:host ::ng-deep .fb-shell--dragging .fb-row-break-btn,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-row-break-btn{opacity:0!important;pointer-events:none!important}:host ::ng-deep .fb-shell--dragging .fb-palette-item:hover,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-palette-item:hover{background:transparent!important}:host ::ng-deep .fb-shell--dragging .fb-palette-item:hover .fb-palette-item__drag,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-palette-item:hover .fb-palette-item__drag{opacity:0!important}:host ::ng-deep .fb-shell--dragging .fb-group__body,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-group__body{min-height:80px!important;background:color-mix(in srgb,var(--md-sys-color-primary) 5%,transparent)!important;outline:2px dashed color-mix(in srgb,var(--md-sys-color-primary) 50%,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) 12%,transparent)!important;outline:2px solid var(--md-sys-color-primary)!important}: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}.fb-shell{width:100%;height:100%;display:flex;background:var(--md-sys-color-surface);overflow:hidden;min-height:0}.fb-panel-header{height:var(--h-header);display:flex;align-items:center;justify-content:space-between;padding:0 var(--gap-3);border-bottom:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-low);flex-shrink:0}.fb-panel-header__title{font-size:11px;font-weight:500;letter-spacing:.08em;text-transform:uppercase;color:var(--md-sys-color-on-surface-variant)}.fb-panel-header__actions{display:flex;align-items:center;gap:2px}.fb-palette{width:220px;flex-shrink:0;display:flex;flex-direction:column;background:var(--md-sys-color-surface-container-low);border-right:1px solid var(--md-sys-color-outline-variant)}.fb-palette__scroll{flex:1;overflow-y:auto;padding-bottom:var(--gap-3)}.fb-palette__search{margin:var(--gap-3);height:32px;display:flex;align-items:center;gap:var(--gap-2);padding:0 var(--gap-3);border-radius:var(--r-pill);background:var(--md-sys-color-surface-container-high);color:var(--md-sys-color-on-surface-variant);font-size:13px;flex-shrink:0}.fb-palette__search .msi{font-size:18px}.fb-palette__search input{flex:1;border:none;outline:none;background:transparent;color:var(--md-sys-color-on-surface);font-size:13px;font-family:inherit}.fb-palette__search input::placeholder{color:var(--md-sys-color-on-surface-variant)}.fb-palette__search .kbd{font-family:Roboto Mono,monospace;font-size:10px;padding:1px 6px;border-radius:var(--r-xs);background:var(--md-sys-color-surface-container-lowest);border:1px solid var(--md-sys-color-outline-variant);color:var(--md-sys-color-on-surface-variant)}.fb-palette__group-label{font-size:11px;font-weight:500;letter-spacing:.04em;color:var(--md-sys-color-on-surface-variant);padding:var(--gap-3) var(--gap-4) var(--gap-1);text-transform:uppercase}.fb-palette__list{display:flex;flex-direction:column;padding:0 var(--gap-2)}.fb-palette-item{height:var(--h-palette);display:flex;align-items:center;gap:var(--gap-3);padding:0 var(--gap-3);border-radius:var(--r-sm);cursor:grab;color:var(--md-sys-color-on-surface);position:relative;-webkit-user-select:none;user-select:none}.fb-palette-item:hover{background:color-mix(in srgb,var(--md-sys-color-primary) 8%,transparent)}.fb-palette-item .fb-palette-item__icon{width:24px;height:24px;border-radius:var(--r-xs);display:inline-flex;align-items:center;justify-content:center;color:var(--md-sys-color-primary);background:var(--md-sys-color-primary-container);flex-shrink:0}.fb-palette-item .fb-palette-item__icon .msi{font-size:16px}.fb-palette-item .fb-palette-item__name{font-size:13px;font-weight:400}.fb-palette-item .fb-palette-item__drag{margin-left:auto;color:var(--md-sys-color-outline);opacity:0;transition:opacity .12s ease}.fb-palette-item .fb-palette-item__drag .msi{font-size:16px}.fb-palette-item:hover .fb-palette-item__drag{opacity:1}.fb-canvas{flex:1 1 0;min-width:0;display:flex;flex-direction:column;background:var(--md-sys-color-surface);overflow:hidden}.fb-canvas__header{height:var(--h-header);display:flex;align-items:center;justify-content:space-between;padding:0 var(--gap-4);border-bottom:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface);gap:var(--gap-3);flex-shrink:0}.fb-canvas__title{display:flex;align-items:center;gap:var(--gap-2);font-size:11px;font-weight:500;letter-spacing:.08em;text-transform:uppercase;color:var(--md-sys-color-on-surface-variant)}.fb-canvas__title .breadcrumb-sep{color:var(--md-sys-color-outline)}.fb-canvas__actions{display:flex;align-items:center;gap:var(--gap-2)}.fb-divider{width:1px;height:18px;background:var(--md-sys-color-outline-variant)}.fb-canvas__body{flex:1 1 0;overflow-y:auto;overflow-x:hidden;padding:var(--gap-4);position:relative;background:var(--md-sys-color-surface);min-width:0}.fb-canvas__body.is-preview{background:var(--md-sys-color-surface-container-low);padding:32px}.fb-mode-toggle{display:inline-flex;height:28px;border:1px solid var(--md-sys-color-outline);border-radius:var(--r-pill);overflow:hidden;flex-shrink:0}.fb-mode-toggle button{border:0;background:transparent;padding:0 var(--gap-3);font-size:12px;font-weight:500;display:inline-flex;align-items:center;gap:6px;color:var(--md-sys-color-on-surface-variant);cursor:pointer}.fb-mode-toggle button .msi{font-size:16px}.fb-mode-toggle button.is-active{background:var(--md-sys-color-secondary-container);color:var(--md-sys-color-on-secondary-container)}.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.is-drag-target:after{content:\"\";position:absolute;left:10px;right:10px;bottom:-3px;height:4px;border-radius:999px;background:var(--md-sys-color-info);box-shadow:0 0 0 4px color-mix(in srgb,var(--md-sys-color-info) 18%,transparent);pointer-events:none;animation:fb-drop-pulse .9s ease-in-out infinite;z-index:3}.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)}.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{position:absolute;top:8px;bottom:8px;right:-5px;width:6px;border-radius:999px;background:var(--md-sys-color-primary);display:flex;align-items:center;justify-content:center;cursor:col-resize;opacity:0;transition:opacity .12s ease;z-index:3}.fb-resize:before{content:\"\";width:2px;height:18px;background:var(--md-sys-color-on-primary);border-radius:999px}.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)}.fb-group.is-hidden{opacity:.55}.fb-group__header{display:flex;align-items:center;gap:var(--gap-2);padding:8px 12px;border-top-left-radius:12px;border-top-right-radius:12px;cursor:grab}.fb-group__header:active{cursor:grabbing}.fb-group__header .fb-group__icon .msi{font-size:18px}.fb-group__header .fb-group__label{font-size:13px;font-weight:500}.fb-group__header .fb-group__count{margin-left:auto;font-size:10px;opacity:.75}.fb-group__conditional-chip{display:inline-flex;align-items:center;gap:4px;font-family:Roboto Mono,monospace;font-size:10px;padding:2px 8px;border-radius:var(--r-pill);background:color-mix(in srgb,currentColor 12%,transparent)}.fb-group__body{padding:12px;display:flex;flex-wrap:wrap;gap:var(--gap-2);min-height:60px;position:relative}.fb-group__empty{display:flex;align-items:center;justify-content:center;height:56px;border-radius:var(--r-sm);border:1.5px dashed var(--md-sys-color-outline-variant);color:var(--md-sys-color-on-surface-variant);font-size:12px;font-style:italic}.fb-empty{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--gap-3);color:var(--md-sys-color-on-surface-variant);text-align:center;padding:24px;pointer-events:none}.fb-empty__art{width:96px;height:96px;border-radius:var(--r-xl);background:var(--md-sys-color-primary-container);color:var(--md-sys-color-primary);display:inline-flex;align-items:center;justify-content:center}.fb-empty__art .msi{font-size:44px}.fb-empty__title{font-size:16px;font-weight:500;color:var(--md-sys-color-on-surface)}.fb-empty__hint{font-size:13px;max-width:320px;line-height:1.5}.fb-empty__kbd{display:inline-flex;align-items:center;gap:6px;font-family:Roboto Mono,monospace;font-size:11px;padding:4px 8px;border-radius:var(--r-xs);border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container);color:var(--md-sys-color-on-surface-variant)}.fb-empty__kbd .msi{font-size:12px}.fb-attrs{width:320px;flex-shrink:0;display:flex;flex-direction:column;background:var(--md-sys-color-surface-container-low);border-left:1px solid var(--md-sys-color-outline-variant)}.fb-attrs__selection{padding:var(--gap-3) var(--gap-4);display:flex;align-items:center;gap:var(--gap-2);border-bottom:1px solid var(--md-sys-color-outline-variant)}.fb-attrs__sel-icon{width:28px;height:28px;border-radius:var(--r-sm);background:var(--md-sys-color-primary-container);color:var(--md-sys-color-primary);display:inline-flex;align-items:center;justify-content:center;flex-shrink:0}.fb-attrs__sel-icon .msi{font-size:18px}.fb-attrs__sel-meta{display:flex;flex-direction:column;min-width:0}.fb-attrs__sel-name{font-size:13px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.fb-attrs__sel-key{font-family:Roboto Mono,monospace;font-size:11px;color:var(--md-sys-color-on-surface-variant)}.fb-attrs__body{flex:1 1 0;overflow-y:auto;overflow-x:hidden;min-width:0}.fb-attrs__placeholder{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--gap-2);padding:32px;color:var(--md-sys-color-on-surface-variant);text-align:center}.fb-attrs__placeholder .msi{font-size:32px;color:var(--md-sys-color-outline)}.fb-attrs__placeholder .title{font-size:13px;font-weight:500;color:var(--md-sys-color-on-surface)}.fb-attrs__placeholder .hint{font-size:12px;max-width:220px;line-height:1.5}.fb-preview-card{max-width:760px;margin:0 auto;background:var(--md-sys-color-surface-container-lowest);border-radius:var(--r-lg);padding:32px;box-shadow:var(--elev-1)}.fb-preview-card__chip{display:inline-flex;align-items:center;gap:6px;padding:2px 10px;height:22px;border-radius:var(--r-pill);background:var(--md-sys-color-primary-container);color:var(--md-sys-color-primary);font-size:10px;font-weight:500;letter-spacing:.08em;text-transform:uppercase;margin-bottom:var(--gap-3)}.fb-preview-card__title{font-size:20px;font-weight:500;margin:0 0 var(--gap-4);color:var(--md-sys-color-on-surface)}.fb-preview-card__footer{display:flex;justify-content:flex-end;gap:var(--gap-2);margin-top:var(--gap-4);padding-top:var(--gap-4);border-top:1px solid var(--md-sys-color-outline-variant)}.fb-icon-picker{display:grid;grid-template-columns:repeat(6,1fr);gap:4px;margin-top:4px}.fb-icon-picker .fb-icon-picker__btn{height:28px;border-radius:var(--r-xs);border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-lowest);color:var(--md-sys-color-on-surface-variant);display:inline-flex;align-items:center;justify-content:center;cursor:pointer}.fb-icon-picker .fb-icon-picker__btn .msi{font-size:16px}.fb-icon-picker .fb-icon-picker__btn.is-active{border-color:var(--md-sys-color-primary);background:var(--md-sys-color-primary-container);color:var(--md-sys-color-primary)}.fb-icon-picker .fb-icon-picker__btn:hover{background:color-mix(in srgb,var(--md-sys-color-primary) 8%,transparent)}.fb-color-swatches{display:flex;gap:var(--gap-2);margin-top:4px}.fb-color-swatches .fb-color-swatch{width:24px;height:24px;border-radius:50%;cursor:pointer;border:2px solid transparent}.fb-color-swatches .fb-color-swatch.is-active{border-color:var(--md-sys-color-on-surface)}.fb-color-swatches .fb-color-swatch.primary{background:var(--md-sys-color-primary)}.fb-color-swatches .fb-color-swatch.secondary{background:var(--md-sys-color-secondary)}.fb-color-swatches .fb-color-swatch.success{background:var(--md-sys-color-success)}.fb-color-swatches .fb-color-swatch.warning{background:var(--md-sys-color-warning)}.fb-color-swatches .fb-color-swatch.error{background:var(--md-sys-color-error)}.fb-children-list{display:flex;flex-direction:column;gap:4px}.fb-child-row{display:flex;align-items:center;gap:var(--gap-2);height:30px;padding:0 8px;border-radius:var(--r-xs);background:var(--md-sys-color-surface-container-lowest);border:1px solid var(--md-sys-color-outline-variant);font-size:12px}.fb-child-row .fb-child-row__icon{width:22px;height:22px;border-radius:var(--r-xs);background:var(--md-sys-color-primary-container);color:var(--md-sys-color-primary);display:inline-flex;align-items:center;justify-content:center;flex-shrink:0}.fb-child-row .fb-child-row__icon .msi{font-size:14px}.fb-child-row .fb-child-row__label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-child-row .fb-child-row__key{font-family:Roboto Mono,monospace;font-size:10px;color:var(--md-sys-color-on-surface-variant)}.fb-child-row:hover{background:color-mix(in srgb,var(--md-sys-color-primary) 4%,transparent)}.fb-add-link{display:inline-flex;align-items:center;gap:4px;font-size:12px;color:var(--md-sys-color-primary);cursor:pointer;padding:4px 0;text-decoration:none}.fb-add-link .msi{font-size:14px}.fb-add-link:hover{text-decoration:underline}.fb-group__body--static{display:flex;flex-wrap:wrap;gap:var(--gap-2, 8px);cursor:pointer;min-height:48px}.fb-group__body--static .fb-item--static{pointer-events:none;position:relative}.fb-group__body--static .fb-item--static .fb-resize,.fb-group__body--static .fb-item--static .fb-actions,.fb-group__body--static .fb-item--static .fb-status-chip{display:none!important}.fb-canvas__header--group .fb-groupedit__crumb{display:flex;align-items:center;gap:6px;font-weight:600}.fb-canvas__header--group .fb-groupedit__icon .msi{font-size:18px;color:var(--md-sys-color-primary)}.fb-canvas__header--group .fb-groupedit__hint{font-weight:400;font-size:12px;color:var(--md-sys-color-on-surface-variant, #6b6b6b);padding:1px 8px;border-radius:999px;background:color-mix(in srgb,var(--md-sys-color-primary) 8%,transparent)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i2$1.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: "component", type:
6384
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.24", type: SdFormBuilder, isStandalone: true, selector: "sd-form-builder", inputs: { formGeneric: { classPropertyName: "formGeneric", publicName: "formGeneric", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "popupViewJSON", first: true, predicate: ["popupViewJSON"], descendants: true, isSignal: true }, { propertyName: "popupConfigureVariables", first: true, predicate: ["popupConfigureVariables"], descendants: true, isSignal: true }, { propertyName: "configureValidation", first: true, predicate: ConfigureValidationComponent, descendants: true, isSignal: true }, { propertyName: "formRender", first: true, predicate: SdFormRender, descendants: true, isSignal: true }], ngImport: i0, template: "<!-- cdkDropListGroup: auto-connect t\u1EA5t c\u1EA3 cdkDropList trong palette, canvas rows, group bodies.\n [class.fb-shell--dragging]: JS-driven flag \u0111\u1EC3 \u1EA9n hover/actions/resize to\u00E0n di\u1EC7n\n trong l\u00FAc drag (CSS :has() kh\u00F4ng b\u1EAFt 100% tr\u01B0\u1EDDng h\u1EE3p v\u1EDBi CDK preview clone). -->\n<div class=\"fb-shell\" cdkDropListGroup [class.fb-shell--dragging]=\"isAnyDragging()\">\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 [cdkDropListEnterPredicate]=\"noReturnPredicate\">\n @for (formBuilderComponent of group.items; track formBuilderComponent.type) {\n <div\n cdkDrag\n class=\"fb-palette-item\"\n [matTooltip]=\"formBuilderComponent.name\"\n matTooltipPosition=\"right\"\n (click)=\"addComponent(formBuilderComponent)\"\n (cdkDragStarted)=\"onAnyDragStarted()\"\n (cdkDragMoved)=\"onAnyDragMoved($event)\"\n (cdkDragEnded)=\"onAnyDragEnded()\"\n aria-hidden=\"true\">\n <span class=\"fb-palette-item__icon\"><span class=\"msi\">{{ formBuilderComponent.symbol }}</span></span>\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 <!-- Generic drag preview: ch\u1EC9 icon (puck), label \u1EA9n -->\n <div *cdkDragPreview class=\"fb-drag-preview\"></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 _editing = editingGroup();\n @if (_editing) {\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 type=\"button\" class=\"fb-icon-btn\" [matTooltip]=\"'core.component.form-builder.back-to-design' | translate\" (click)=\"cancelGroupEdit()\">\n <span class=\"msi\">chevron_left</span>\n </button>\n <span class=\"fb-groupedit__icon\"><span class=\"msi\">{{ _editing.properties?.icon || 'category' }}</span></span>\n <span>{{ _editing.label || ('core.component.form-builder.edit-group' | translate) }}</span>\n <span class=\"fb-groupedit__hint\">{{ 'core.component.form-builder.edit-group' | translate }}</span>\n </div>\n <div class=\"fb-canvas__actions\">\n <sd-button type=\"outline\" [title]=\"'core.component.form-builder.cancel' | translate\" (click)=\"cancelGroupEdit()\"></sd-button>\n <sd-button type=\"fill\" color=\"primary\" prefixIcon=\"check\" [title]=\"'core.component.form-builder.confirm' | translate\" (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\n type=\"button\"\n class=\"fb-icon-btn\"\n [matTooltip]=\"'core.component.form-builder.view-json' | translate\"\n (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\n type=\"button\"\n role=\"tab\"\n [class.is-active]=\"!isPreview()\"\n (click)=\"setMode(false)\">\n <span class=\"msi\">edit</span>\n <span>{{ 'core.component.form-builder.design' | translate }}</span>\n </button>\n <button\n type=\"button\"\n role=\"tab\"\n [class.is-active]=\"isPreview()\"\n (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\n class=\"fb-canvas__body\"\n [class.is-preview]=\"isPreview()\"\n id=\"frmComponent\">\n @if (isPreview()) {\n <!-- \u2500\u2500 Preview mode \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"fb-preview-card\">\n <div class=\"fb-preview-card__chip\">\n <span class=\"msi\">visibility</span>\n {{ 'core.component.form-builder.preview-runtime' | translate }}\n </div>\n <sd-form-render [configuration]=\"{ components: this.components, validations: this.validations }\"></sd-form-render>\n <div class=\"fb-preview-card__footer\">\n <sd-button type=\"outline\" [title]=\"'core.component.form-builder.cancel' | translate\" (click)=\"setMode(false)\"></sd-button>\n <sd-button type=\"fill\" color=\"primary\" [title]=\"'core.component.form-builder.submit' | translate\" (click)=\"onValidate()\"></sd-button>\n </div>\n </div>\n } @else {\n <!-- \u2500\u2500 Design mode \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (!dragDropRows.length) {\n <div class=\"fb-empty\">\n <div class=\"fb-empty__art\"><span class=\"msi\">dashboard_customize</span></div>\n <div class=\"fb-empty__title\">{{ 'core.component.form-builder.empty-title' | translate }}</div>\n <div class=\"fb-empty__hint\">\n {{ 'core.component.form-builder.empty-hint' | translate }}\n <span class=\"fb-empty__kbd\"><span class=\"msi\">keyboard</span>\u2318 K</span>\n </div>\n </div>\n }\n\n <div\n cdkDropList\n cdkDropListOrientation=\"vertical\"\n [cdkDropListData]=\"dragDropRows\"\n (cdkDropListDropped)=\"drop($event)\"\n (click)=\"clickFormContentEmpty()\"\n aria-hidden=\"true\">\n @for (row of dragDropRows; track row.rowIndex) {\n <div\n cdkDrag\n class=\"fb-row\"\n (mouseover)=\"onMouseover($event, row)\"\n (focus)=\"onFocus($event)\"\n (cdkDragStarted)=\"onAnyDragStarted()\"\n (cdkDragMoved)=\"onAnyDragMoved($event)\"\n (cdkDragEnded)=\"onAnyDragEnded()\"\n [class.is-drag-target]=\"targetItem === row\"\n [id]=\"'row_' + row.rowIndex\"\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 <!-- Generic puck preview -->\n <div *cdkDragPreview class=\"fb-drag-preview\"></div>\n <!-- Quick-add break button \u2014 show on row hover, insert break after this row -->\n <button\n type=\"button\"\n class=\"fb-row-break-btn\"\n [matTooltip]=\"'core.component.form-builder.break' | translate\"\n matTooltipPosition=\"right\"\n (click)=\"insertBreakAfter(row); $event.stopPropagation()\">\n <span class=\"msi\">horizontal_rule</span>\n </button>\n\n <div\n class=\"fb-row__items\"\n style=\"display: flex; flex: 1; gap: var(--gap-2);\"\n cdkDropList\n cdkDropListOrientation=\"horizontal\"\n [cdkDropListData]=\"row.items\"\n (cdkDropListDropped)=\"drop($event)\">\n\n @for (item of row.items; track item.id; let i = $index) {\n <!-- Group component renders as outlined card with colored header bar -->\n @if (item.type === 'group') {\n @let g = $any(item);\n @let cv = groupColorVars(g.properties?.color);\n <div\n cdkDrag\n [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); onAnyDragStarted()\"\n (cdkDragMoved)=\"onAnyDragMoved($event)\"\n (cdkDragEnded)=\"dragEndComponentItem($event); onAnyDragEnded()\"\n aria-hidden=\"true\">\n <!-- Generic puck preview -->\n <div *cdkDragPreview class=\"fb-drag-preview\"></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\"><span class=\"msi\">{{ g.properties?.icon || 'category' }}</span></span>\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 type=\"button\" 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 type=\"button\" 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 type=\"button\" class=\"fb-icon-btn\"\n [class.warning]=\"item.properties?.hidden\"\n [class.on]=\"item.properties?.hidden\"\n [matTooltip]=\"item.properties?.hidden ? ('core.component.form-builder.show' | translate) : ('core.component.form-builder.hide' | translate)\"\n (click)=\"onChangeHidden(item); $event.stopPropagation()\">\n <span class=\"msi\">{{ item.properties?.hidden ? 'visibility_off' : 'visibility' }}</span>\n </button>\n <button type=\"button\" 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\n *ngTemplateOutlet=\"controlTemplate; context: { $implicit: child }\">\n </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 [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); onAnyDragStarted()\"\n (cdkDragMoved)=\"onAnyDragMoved($event)\"\n (cdkDragEnded)=\"dragEndComponentItem($event); onAnyDragEnded()\"\n aria-hidden=\"true\">\n <!-- Generic puck preview -->\n <div *cdkDragPreview class=\"fb-drag-preview\"></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 type=\"button\" class=\"fb-icon-btn\"\n [class.warning]=\"item.properties?.hidden\"\n [class.on]=\"item.properties?.hidden\"\n [matTooltip]=\"item.properties?.hidden ? ('core.component.form-builder.show' | translate) : ('core.component.form-builder.hide' | translate)\"\n (click)=\"onChangeHidden(item); $event.stopPropagation()\">\n <span class=\"msi\">{{ item.properties?.hidden ? 'visibility_off' : 'visibility' }}</span>\n </button>\n <button type=\"button\" 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 [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 (click)=\"selectComponent(item); $event.stopPropagation()\"\n (cdkDragStarted)=\"dragStartComponentItem($event); onAnyDragStarted()\"\n (cdkDragMoved)=\"onAnyDragMoved($event)\"\n (cdkDragEnded)=\"dragEndComponentItem($event); onAnyDragEnded()\"\n aria-hidden=\"true\">\n <!-- Generic puck preview -->\n <div *cdkDragPreview class=\"fb-drag-preview\"></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 type=\"button\" class=\"fb-icon-btn\"\n [class.warning]=\"item.properties?.viewed\"\n [class.on]=\"item.properties?.viewed\"\n [matTooltip]=\"item.properties?.viewed ? ('core.component.form-builder.unset-readonly' | translate) : ('core.component.form-builder.set-readonly' | translate)\"\n (click)=\"onChangeViewed(item); $event.stopPropagation()\">\n <span class=\"msi\">edit_off</span>\n </button>\n <button type=\"button\" 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 type=\"button\" class=\"fb-icon-btn\"\n [class.warning]=\"item.properties?.hidden\"\n [class.on]=\"item.properties?.hidden\"\n [matTooltip]=\"item.properties?.hidden ? ('core.component.form-builder.show' | translate) : ('core.component.form-builder.hide' | translate)\"\n (click)=\"onChangeHidden(item); $event.stopPropagation()\">\n <span class=\"msi\">{{ item.properties?.hidden ? 'visibility_off' : 'visibility' }}</span>\n </button>\n <button type=\"button\" 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\n *ngTemplateOutlet=\"controlTemplate; context: { $implicit: item }\">\n </ng-container>\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)=\"onAnyDragStarted()\"\n (cdkDragMoved)=\"changeSizeControl($event, item, row.items, i)\"\n (cdkDragEnded)=\"dragEndChangeSizeControl($event); onAnyDragEnded()\"\n [matTooltip]=\"'core.component.form-builder.flex' | translate\"\n aria-label=\"resize\"></div>\n </div>\n </div>\n }\n }\n </div>\n </div>\n }\n </div>\n }\n </div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 RIGHT ATTRIBUTES \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <aside class=\"fb-attrs\">\n <div class=\"fb-panel-header\">\n <div class=\"fb-panel-header__title\">{{ 'core.component.form-builder.attribute' | translate }}</div>\n </div>\n\n @let sel = selectedComponent();\n @if (sel) {\n <!-- Selection header: type icon + name + key/type -->\n <div class=\"fb-attrs__selection\">\n <span class=\"fb-attrs__sel-icon\"><span class=\"msi\">{{ symbolFor(sel) }}</span></span>\n <div class=\"fb-attrs__sel-meta\">\n <div class=\"fb-attrs__sel-name\">{{ $any(sel).label || ('core.component.form-builder.untitled' | translate) }}</div>\n <div class=\"fb-attrs__sel-key\">\n @if ($any(sel).key) {\n {{ $any(sel).key }} \u00B7 {{ typeLabelFor(sel) }}\n } @else {\n {{ typeLabelFor(sel) }}\n }\n </div>\n </div>\n </div>\n\n <div class=\"fb-attrs__body\" aria-hidden=\"true\">\n @if (sel.type === 'textfield') {\n <textfield-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></textfield-attribute>\n } @else if (sel.type === 'textarea') {\n <textarea-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></textarea-attribute>\n } @else if (sel.type === 'chip-string') {\n <chip-string-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></chip-string-attribute>\n } @else if (sel.type === 'chip-calendar') {\n <chip-calendar-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></chip-calendar-attribute>\n } @else if (sel.type === 'number') {\n <number-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></number-attribute>\n } @else if (sel.type === 'datetime') {\n <datetime-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></datetime-attribute>\n } @else if (sel.type === 'select') {\n <select-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></select-attribute>\n } @else if (sel.type === 'radio') {\n <radio-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></radio-attribute>\n } @else if (sel.type === 'checkbox') {\n <checkbox-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></checkbox-attribute>\n } @else if (sel.type === 'html') {\n <html-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></html-attribute>\n } @else if (sel.type === 'upload') {\n <upload-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></upload-attribute>\n } @else if (sel.type === 'table') {\n <table-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></table-attribute>\n } @else if (sel.type === 'group') {\n <group-attribute [group]=\"$any(sel)\"></group-attribute>\n }\n </div>\n } @else {\n <div class=\"fb-attrs__placeholder\">\n <span class=\"msi\">ads_click</span>\n <div class=\"title\">{{ 'core.component.form-builder.no-selection-title' | translate }}</div>\n <div class=\"hint\">{{ 'core.component.form-builder.no-selection-hint' | translate }}</div>\n </div>\n }\n </aside>\n</div>\n\n<!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 Reusable control template \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n<ng-template #controlTemplate let-item>\n @if (item.type === 'textfield') {\n <textfield-control [component]=\"item\"></textfield-control>\n } @else if (item.type === 'textarea') {\n <textarea-control [component]=\"item\"></textarea-control>\n } @else if (item.type === 'chip-string') {\n <chip-string-control [component]=\"item\"></chip-string-control>\n } @else if (item.type === 'chip-calendar') {\n <chip-calendar-control [component]=\"item\"></chip-calendar-control>\n } @else if (item.type === 'number') {\n <number-control [component]=\"item\"></number-control>\n } @else if (item.type === 'datetime') {\n <datetime-control [component]=\"item\"></datetime-control>\n } @else if (item.type === 'select') {\n <select-control [component]=\"item\"></select-control>\n } @else if (item.type === 'radio') {\n <radio-control [component]=\"item\"></radio-control>\n } @else if (item.type === 'checkbox') {\n <checkbox-control [component]=\"item\"></checkbox-control>\n } @else if (item.type === 'html') {\n <html-control [component]=\"item\"></html-control>\n } @else if (item.type === 'upload') {\n <upload-control [component]=\"item\"></upload-control>\n } @else if (item.type === 'table') {\n <table-control [component]=\"item\"></table-control>\n }\n</ng-template>\n\n<!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 JSON / variables modals \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n<sd-modal title=\"JSON\" view=\"dialog\" width=\"80%\" #popupViewJSON>\n <sd-textarea [(model)]=\"jsonString\"></sd-textarea>\n <ng-container sdFooter>\n <sd-button type=\"fill\" [title]=\"'core.component.form-builder.save' | translate\" (click)=\"updateJSON()\" prefixIcon=\"save\" color=\"primary\"></sd-button>\n </ng-container>\n</sd-modal>\n<sd-modal [title]=\"'core.component.form-builder.config-variable' | translate\" view=\"dialog\" width=\"800px\" #popupConfigureVariables>\n @for (variable of clonedVariables; track variable.id) {\n <div class=\"d-flex align-items-center\" style=\"gap: 8px\">\n <sd-input [form]=\"form\" [label]=\"'core.component.form-builder.field-key' | translate\" [(model)]=\"variable.key\" required></sd-input>\n <sd-input [form]=\"form\" [label]=\"'core.component.form-builder.display-label' | translate\" [(model)]=\"variable.label\" required></sd-input>\n <sd-button prefixIcon=\"delete\" (click)=\"removeVariables(variable.id)\" color=\"error\"></sd-button>\n </div>\n }\n <a href=\"javascript:;\" (click)=\"addVariables()\">{{ 'core.component.form-builder.add-variable' | translate }}</a>\n <ng-container sdFooter>\n <sd-button type=\"fill\" [title]=\"'core.component.form-builder.save' | translate\" (click)=\"updateVariables()\" prefixIcon=\"save\" color=\"primary\"></sd-button>\n </ng-container>\n</sd-modal>\n<configure-validation (accept)=\"onUpdateValidations($event)\"></configure-validation>\n", styles: ["@charset \"UTF-8\";:host{--md-sys-color-primary: var(--sd-primary, #2A66F4);--md-sys-color-on-primary: #ffffff;--md-sys-color-primary-container: var(--sd-primary-light, #EAF1FF);--md-sys-color-on-primary-container: var(--sd-primary, #2A66F4);--md-sys-color-info: var(--sd-info, #2962FF);--md-sys-color-info-container: var(--sd-info-light, #E7E9FF);--md-sys-color-secondary: #625b71;--md-sys-color-secondary-container: #e8def8;--md-sys-color-on-secondary-container: #1d192b;--md-sys-color-surface: #fbfafc;--md-sys-color-surface-container-lowest: #ffffff;--md-sys-color-surface-container-low: #f6f4f8;--md-sys-color-surface-container: #f1eef3;--md-sys-color-surface-container-high: #ebe8ee;--md-sys-color-surface-container-highest: #e6e2ea;--md-sys-color-on-surface: #1c1b1f;--md-sys-color-on-surface-variant: #49454e;--md-sys-color-outline: #79747e;--md-sys-color-outline-variant: #d6d2da;--md-sys-color-error: #b3261e;--md-sys-color-error-container: #f9dedc;--md-sys-color-warning: #8a5400;--md-sys-color-warning-container: #ffddb1;--md-sys-color-success: #1f6f43;--md-sys-color-success-container: #b7f0cb;--h-row: 28px;--h-field: 32px;--h-palette: 36px;--h-header: 36px;--h-section: 28px;--h-action-btn: 24px;--gap-1: 4px;--gap-2: 8px;--gap-3: 12px;--gap-4: 16px;--gap-5: 24px;--r-xs: 4px;--r-sm: 8px;--r-md: 12px;--r-lg: 16px;--r-xl: 28px;--r-pill: 999px;--elev-1: 0 1px 2px 0 rgba(0, 0, 0, .06);--elev-2: 0 2px 6px 0 rgba(0, 0, 0, .08), 0 1px 2px 0 rgba(0, 0, 0, .04);--elev-3: 0 6px 14px 0 rgba(0, 0, 0, .1), 0 2px 4px 0 rgba(0, 0, 0, .06);display:block;width:100%;height:100%;font-family:Roboto,system-ui,sans-serif;font-size:13px;color:var(--md-sys-color-on-surface);background:var(--md-sys-color-surface)}:host ::ng-deep .msi{font-family:Material Symbols Rounded;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;font-variation-settings:\"wght\" 300,\"opsz\" 24,\"GRAD\" 0,\"FILL\" 0}:host ::ng-deep .msi.filled{font-variation-settings:\"wght\" 300,\"opsz\" 24,\"GRAD\" 0,\"FILL\" 1}: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:36px;height:36px;min-width:36px;min-height:36px;max-width:36px;max-height:36px;box-sizing:border-box;border-radius:50%;background:var(--md-sys-color-primary, #6750a4);display:inline-flex;align-items:center;justify-content:center;box-shadow:0 10px 24px #00000042,0 3px 8px #00000024;border:3px solid var(--md-sys-color-on-primary, #ffffff);cursor:grabbing;pointer-events:none;-webkit-user-select:none;user-select:none;opacity:.96;overflow:hidden;z-index:10000}:host ::ng-deep .cdk-drag-preview.fb-drag-preview:after,:host ::ng-deep .fb-drag-preview:after{content:\"\";width:8px;height:8px;border-radius:50%;background:var(--md-sys-color-on-primary, #ffffff);opacity:.9}@keyframes fb-drop-pulse{0%,to{box-shadow:0 0 0 3px color-mix(in srgb,var(--md-sys-color-info) 16%,transparent)}50%{box-shadow:0 0 0 5px color-mix(in srgb,var(--md-sys-color-info) 24%,transparent)}}:host ::ng-deep .cdk-drag-placeholder{background:transparent!important;border:none!important;border-radius:999px!important;padding:0!important;transform:none!important;transition:none!important;pointer-events:none;overflow:hidden;animation:none!important;opacity:0!important}:host ::ng-deep .cdk-drag-placeholder>*{visibility:hidden!important;pointer-events:none!important}:host ::ng-deep .fb-palette__list>.cdk-drag-placeholder{display:none!important}:host ::ng-deep .fb-row__items>.cdk-drag-placeholder,:host ::ng-deep .fb-group__body>.cdk-drag-placeholder{width:0!important;min-width:0!important;max-width:0!important;height:auto!important;min-height:0!important;align-self:stretch;flex:0 0 0!important;margin:0!important}:host ::ng-deep [cdkDropListOrientation=vertical]>.cdk-drag-placeholder{width:auto!important;height:0!important;min-height:0!important;max-height:0!important;flex:0 0 0!important;margin:0!important;align-self:stretch}: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) 10%,transparent)!important;outline:2px solid color-mix(in srgb,var(--md-sys-color-primary) 78%,transparent);outline-offset:-6px;border-radius:var(--r-sm)}:host ::ng-deep .fb-resize.cdk-drag-preview{display:none!important}:host ::ng-deep .fb-shell--dragging .fb-item:hover,:host ::ng-deep .fb-shell--dragging .fb-group:hover,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-item:hover,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-group:hover{border-color:transparent!important;background:transparent!important}:host ::ng-deep .fb-shell--dragging .fb-row:hover,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-row:hover{outline-color:transparent!important;background:transparent!important}:host ::ng-deep .fb-shell--dragging .fb-row:hover .fb-row__drag,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-row:hover .fb-row__drag{opacity:0!important}:host ::ng-deep .fb-shell--dragging .fb-row.is-drag-target,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-row.is-drag-target{outline-color:color-mix(in srgb,var(--md-sys-color-info) 44%,transparent)!important;background:color-mix(in srgb,var(--md-sys-color-info) 4%,transparent)!important}:host ::ng-deep .fb-shell--dragging .fb-row__drag,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-row__drag{opacity:0!important}:host ::ng-deep .fb-shell--dragging .fb-actions,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-actions{opacity:0!important;pointer-events:none!important;transform:translateY(-2px)!important}:host ::ng-deep .fb-shell--dragging .fb-resize,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-resize{opacity:0!important;pointer-events:none!important}:host ::ng-deep .fb-shell--dragging .fb-row-break-btn,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-row-break-btn{opacity:0!important;pointer-events:none!important}:host ::ng-deep .fb-shell--dragging .fb-palette-item:hover,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-palette-item:hover{background:transparent!important}:host ::ng-deep .fb-shell--dragging .fb-palette-item:hover .fb-palette-item__drag,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-palette-item:hover .fb-palette-item__drag{opacity:0!important}:host ::ng-deep .fb-shell--dragging .fb-group__body,:host ::ng-deep .fb-shell:has(.cdk-drag-dragging) .fb-group__body{min-height:80px!important;background:color-mix(in srgb,var(--md-sys-color-primary) 5%,transparent)!important;outline:2px dashed color-mix(in srgb,var(--md-sys-color-primary) 50%,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) 12%,transparent)!important;outline:2px solid var(--md-sys-color-primary)!important}: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}.fb-shell{width:100%;height:100%;display:flex;background:var(--md-sys-color-surface);overflow:hidden;min-height:0}.fb-panel-header{height:var(--h-header);display:flex;align-items:center;justify-content:space-between;padding:0 var(--gap-3);border-bottom:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-low);flex-shrink:0}.fb-panel-header__title{font-size:11px;font-weight:500;letter-spacing:.08em;text-transform:uppercase;color:var(--md-sys-color-on-surface-variant)}.fb-panel-header__actions{display:flex;align-items:center;gap:2px}.fb-palette{width:220px;flex-shrink:0;display:flex;flex-direction:column;background:var(--md-sys-color-surface-container-low);border-right:1px solid var(--md-sys-color-outline-variant)}.fb-palette__scroll{flex:1;overflow-y:auto;padding-bottom:var(--gap-3)}.fb-palette__search{margin:var(--gap-3);height:32px;display:flex;align-items:center;gap:var(--gap-2);padding:0 var(--gap-3);border-radius:var(--r-pill);background:var(--md-sys-color-surface-container-high);color:var(--md-sys-color-on-surface-variant);font-size:13px;flex-shrink:0}.fb-palette__search .msi{font-size:18px}.fb-palette__search input{flex:1;border:none;outline:none;background:transparent;color:var(--md-sys-color-on-surface);font-size:13px;font-family:inherit}.fb-palette__search input::placeholder{color:var(--md-sys-color-on-surface-variant)}.fb-palette__search .kbd{font-family:Roboto Mono,monospace;font-size:10px;padding:1px 6px;border-radius:var(--r-xs);background:var(--md-sys-color-surface-container-lowest);border:1px solid var(--md-sys-color-outline-variant);color:var(--md-sys-color-on-surface-variant)}.fb-palette__group-label{font-size:11px;font-weight:500;letter-spacing:.04em;color:var(--md-sys-color-on-surface-variant);padding:var(--gap-3) var(--gap-4) var(--gap-1);text-transform:uppercase}.fb-palette__list{display:flex;flex-direction:column;padding:0 var(--gap-2)}.fb-palette-item{height:var(--h-palette);display:flex;align-items:center;gap:var(--gap-3);padding:0 var(--gap-3);border-radius:var(--r-sm);cursor:grab;color:var(--md-sys-color-on-surface);position:relative;-webkit-user-select:none;user-select:none}.fb-palette-item:hover{background:color-mix(in srgb,var(--md-sys-color-primary) 8%,transparent)}.fb-palette-item .fb-palette-item__icon{width:24px;height:24px;border-radius:var(--r-xs);display:inline-flex;align-items:center;justify-content:center;color:var(--md-sys-color-primary);background:var(--md-sys-color-primary-container);flex-shrink:0}.fb-palette-item .fb-palette-item__icon .msi{font-size:16px}.fb-palette-item .fb-palette-item__name{font-size:13px;font-weight:400}.fb-palette-item .fb-palette-item__drag{margin-left:auto;color:var(--md-sys-color-outline);opacity:0;transition:opacity .12s ease}.fb-palette-item .fb-palette-item__drag .msi{font-size:16px}.fb-palette-item:hover .fb-palette-item__drag{opacity:1}.fb-canvas{flex:1 1 0;min-width:0;display:flex;flex-direction:column;background:var(--md-sys-color-surface);overflow:hidden}.fb-canvas__header{height:var(--h-header);display:flex;align-items:center;justify-content:space-between;padding:0 var(--gap-4);border-bottom:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface);gap:var(--gap-3);flex-shrink:0}.fb-canvas__title{display:flex;align-items:center;gap:var(--gap-2);font-size:11px;font-weight:500;letter-spacing:.08em;text-transform:uppercase;color:var(--md-sys-color-on-surface-variant)}.fb-canvas__title .breadcrumb-sep{color:var(--md-sys-color-outline)}.fb-canvas__actions{display:flex;align-items:center;gap:var(--gap-2)}.fb-divider{width:1px;height:18px;background:var(--md-sys-color-outline-variant)}.fb-canvas__body{flex:1 1 0;overflow-y:auto;overflow-x:hidden;padding:var(--gap-4);position:relative;background:var(--md-sys-color-surface);min-width:0}.fb-canvas__body.is-preview{background:var(--md-sys-color-surface-container-low);padding:32px}.fb-mode-toggle{display:inline-flex;height:28px;border:1px solid var(--md-sys-color-outline);border-radius:var(--r-pill);overflow:hidden;flex-shrink:0}.fb-mode-toggle button{border:0;background:transparent;padding:0 var(--gap-3);font-size:12px;font-weight:500;display:inline-flex;align-items:center;gap:6px;color:var(--md-sys-color-on-surface-variant);cursor:pointer}.fb-mode-toggle button .msi{font-size:16px}.fb-mode-toggle button.is-active{background:var(--md-sys-color-secondary-container);color:var(--md-sys-color-on-secondary-container)}.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.is-drag-target:after{content:\"\";position:absolute;left:10px;right:10px;bottom:-3px;height:4px;border-radius:999px;background:var(--md-sys-color-info);box-shadow:0 0 0 4px color-mix(in srgb,var(--md-sys-color-info) 18%,transparent);pointer-events:none;animation:fb-drop-pulse .9s ease-in-out infinite;z-index:3}.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)}.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{position:absolute;top:8px;bottom:8px;right:-5px;width:6px;border-radius:999px;background:var(--md-sys-color-primary);display:flex;align-items:center;justify-content:center;cursor:col-resize;opacity:0;transition:opacity .12s ease;z-index:3}.fb-resize:before{content:\"\";width:2px;height:18px;background:var(--md-sys-color-on-primary);border-radius:999px}.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)}.fb-group.is-hidden{opacity:.55}.fb-group__header{display:flex;align-items:center;gap:var(--gap-2);padding:8px 12px;border-top-left-radius:12px;border-top-right-radius:12px;cursor:grab}.fb-group__header:active{cursor:grabbing}.fb-group__header .fb-group__icon .msi{font-size:18px}.fb-group__header .fb-group__label{font-size:13px;font-weight:500}.fb-group__header .fb-group__count{margin-left:auto;font-size:10px;opacity:.75}.fb-group__conditional-chip{display:inline-flex;align-items:center;gap:4px;font-family:Roboto Mono,monospace;font-size:10px;padding:2px 8px;border-radius:var(--r-pill);background:color-mix(in srgb,currentColor 12%,transparent)}.fb-group__body{padding:12px;display:flex;flex-wrap:wrap;gap:var(--gap-2);min-height:60px;position:relative}.fb-group__empty{display:flex;align-items:center;justify-content:center;height:56px;border-radius:var(--r-sm);border:1.5px dashed var(--md-sys-color-outline-variant);color:var(--md-sys-color-on-surface-variant);font-size:12px;font-style:italic}.fb-empty{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--gap-3);color:var(--md-sys-color-on-surface-variant);text-align:center;padding:24px;pointer-events:none}.fb-empty__art{width:96px;height:96px;border-radius:var(--r-xl);background:var(--md-sys-color-primary-container);color:var(--md-sys-color-primary);display:inline-flex;align-items:center;justify-content:center}.fb-empty__art .msi{font-size:44px}.fb-empty__title{font-size:16px;font-weight:500;color:var(--md-sys-color-on-surface)}.fb-empty__hint{font-size:13px;max-width:320px;line-height:1.5}.fb-empty__kbd{display:inline-flex;align-items:center;gap:6px;font-family:Roboto Mono,monospace;font-size:11px;padding:4px 8px;border-radius:var(--r-xs);border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container);color:var(--md-sys-color-on-surface-variant)}.fb-empty__kbd .msi{font-size:12px}.fb-attrs{width:320px;flex-shrink:0;display:flex;flex-direction:column;background:var(--md-sys-color-surface-container-low);border-left:1px solid var(--md-sys-color-outline-variant)}.fb-attrs__selection{padding:var(--gap-3) var(--gap-4);display:flex;align-items:center;gap:var(--gap-2);border-bottom:1px solid var(--md-sys-color-outline-variant)}.fb-attrs__sel-icon{width:28px;height:28px;border-radius:var(--r-sm);background:var(--md-sys-color-primary-container);color:var(--md-sys-color-primary);display:inline-flex;align-items:center;justify-content:center;flex-shrink:0}.fb-attrs__sel-icon .msi{font-size:18px}.fb-attrs__sel-meta{display:flex;flex-direction:column;min-width:0}.fb-attrs__sel-name{font-size:13px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.fb-attrs__sel-key{font-family:Roboto Mono,monospace;font-size:11px;color:var(--md-sys-color-on-surface-variant)}.fb-attrs__body{flex:1 1 0;overflow-y:auto;overflow-x:hidden;min-width:0}.fb-attrs__placeholder{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--gap-2);padding:32px;color:var(--md-sys-color-on-surface-variant);text-align:center}.fb-attrs__placeholder .msi{font-size:32px;color:var(--md-sys-color-outline)}.fb-attrs__placeholder .title{font-size:13px;font-weight:500;color:var(--md-sys-color-on-surface)}.fb-attrs__placeholder .hint{font-size:12px;max-width:220px;line-height:1.5}.fb-preview-card{max-width:760px;margin:0 auto;background:var(--md-sys-color-surface-container-lowest);border-radius:var(--r-lg);padding:32px;box-shadow:var(--elev-1)}.fb-preview-card__chip{display:inline-flex;align-items:center;gap:6px;padding:2px 10px;height:22px;border-radius:var(--r-pill);background:var(--md-sys-color-primary-container);color:var(--md-sys-color-primary);font-size:10px;font-weight:500;letter-spacing:.08em;text-transform:uppercase;margin-bottom:var(--gap-3)}.fb-preview-card__title{font-size:20px;font-weight:500;margin:0 0 var(--gap-4);color:var(--md-sys-color-on-surface)}.fb-preview-card__footer{display:flex;justify-content:flex-end;gap:var(--gap-2);margin-top:var(--gap-4);padding-top:var(--gap-4);border-top:1px solid var(--md-sys-color-outline-variant)}.fb-icon-picker{display:grid;grid-template-columns:repeat(6,1fr);gap:4px;margin-top:4px}.fb-icon-picker .fb-icon-picker__btn{height:28px;border-radius:var(--r-xs);border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-lowest);color:var(--md-sys-color-on-surface-variant);display:inline-flex;align-items:center;justify-content:center;cursor:pointer}.fb-icon-picker .fb-icon-picker__btn .msi{font-size:16px}.fb-icon-picker .fb-icon-picker__btn.is-active{border-color:var(--md-sys-color-primary);background:var(--md-sys-color-primary-container);color:var(--md-sys-color-primary)}.fb-icon-picker .fb-icon-picker__btn:hover{background:color-mix(in srgb,var(--md-sys-color-primary) 8%,transparent)}.fb-color-swatches{display:flex;gap:var(--gap-2);margin-top:4px}.fb-color-swatches .fb-color-swatch{width:24px;height:24px;border-radius:50%;cursor:pointer;border:2px solid transparent}.fb-color-swatches .fb-color-swatch.is-active{border-color:var(--md-sys-color-on-surface)}.fb-color-swatches .fb-color-swatch.primary{background:var(--md-sys-color-primary)}.fb-color-swatches .fb-color-swatch.secondary{background:var(--md-sys-color-secondary)}.fb-color-swatches .fb-color-swatch.success{background:var(--md-sys-color-success)}.fb-color-swatches .fb-color-swatch.warning{background:var(--md-sys-color-warning)}.fb-color-swatches .fb-color-swatch.error{background:var(--md-sys-color-error)}.fb-children-list{display:flex;flex-direction:column;gap:4px}.fb-child-row{display:flex;align-items:center;gap:var(--gap-2);height:30px;padding:0 8px;border-radius:var(--r-xs);background:var(--md-sys-color-surface-container-lowest);border:1px solid var(--md-sys-color-outline-variant);font-size:12px}.fb-child-row .fb-child-row__icon{width:22px;height:22px;border-radius:var(--r-xs);background:var(--md-sys-color-primary-container);color:var(--md-sys-color-primary);display:inline-flex;align-items:center;justify-content:center;flex-shrink:0}.fb-child-row .fb-child-row__icon .msi{font-size:14px}.fb-child-row .fb-child-row__label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-child-row .fb-child-row__key{font-family:Roboto Mono,monospace;font-size:10px;color:var(--md-sys-color-on-surface-variant)}.fb-child-row:hover{background:color-mix(in srgb,var(--md-sys-color-primary) 4%,transparent)}.fb-add-link{display:inline-flex;align-items:center;gap:4px;font-size:12px;color:var(--md-sys-color-primary);cursor:pointer;padding:4px 0;text-decoration:none}.fb-add-link .msi{font-size:14px}.fb-add-link:hover{text-decoration:underline}.fb-group__body--static{display:flex;flex-wrap:wrap;gap:var(--gap-2, 8px);cursor:pointer;min-height:48px}.fb-group__body--static .fb-item--static{pointer-events:none;position:relative}.fb-group__body--static .fb-item--static .fb-resize,.fb-group__body--static .fb-item--static .fb-actions,.fb-group__body--static .fb-item--static .fb-status-chip{display:none!important}.fb-canvas__header--group .fb-groupedit__crumb{display:flex;align-items:center;gap:6px;font-weight:600}.fb-canvas__header--group .fb-groupedit__icon .msi{font-size:18px;color:var(--md-sys-color-primary)}.fb-canvas__header--group .fb-groupedit__hint{font-weight:400;font-size:12px;color:var(--md-sys-color-on-surface-variant, #6b6b6b);padding:1px 8px;border-radius:999px;background:color-mix(in srgb,var(--md-sys-color-primary) 8%,transparent)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i2$1.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: "component", type:
6388
6385
  // Controls
6389
6386
  TextFieldControl, selector: "textfield-control", inputs: ["component"] }, { kind: "component", type: TextfieldAttribute, selector: "textfield-attribute", inputs: ["components", "variables", "component"] }, { kind: "component", type: TextareaControl, selector: "textarea-control", inputs: ["component"] }, { kind: "component", type: TextareaAttribute, selector: "textarea-attribute", inputs: ["components", "variables", "component"] }, { kind: "component", type: ChipStringControl, selector: "chip-string-control", inputs: ["component"] }, { kind: "component", type: ChipStringAttribute, selector: "chip-string-attribute", inputs: ["components", "variables", "component"] }, { kind: "component", type: ChipCalendarControl, selector: "chip-calendar-control", inputs: ["component"] }, { kind: "component", type: ChipCalendarAttribute, selector: "chip-calendar-attribute", inputs: ["components", "variables", "component"] }, { kind: "component", type: NumberControl, selector: "number-control", inputs: ["component"] }, { kind: "component", type: NumberAttribute, selector: "number-attribute", inputs: ["components", "variables", "component"] }, { kind: "component", type: SelectControl, selector: "select-control", inputs: ["component"] }, { kind: "component", type: SelectAttribute, selector: "select-attribute", inputs: ["components", "variables", "component"] }, { kind: "component", type: DatetimeControl, selector: "datetime-control", inputs: ["component"] }, { kind: "component", type: DatetimeAttribute, selector: "datetime-attribute", inputs: ["components", "variables", "component"] }, { kind: "component", type: RadioControl, selector: "radio-control", inputs: ["component"] }, { kind: "component", type: RadioAttribute, selector: "radio-attribute", inputs: ["components", "variables", "component"] }, { kind: "component", type: CheckboxControl, selector: "checkbox-control", inputs: ["component"] }, { kind: "component", type: CheckboxAttribute, selector: "checkbox-attribute", inputs: ["components", "variables", "component"] }, { kind: "component", type: HtmlControl, selector: "html-control", inputs: ["component"] }, { kind: "component", type: HtmlAttribute, selector: "html-attribute", inputs: ["components", "variables", "component"], outputs: ["attributeChanges"] }, { kind: "component", type: UploadControl, selector: "upload-control", inputs: ["component"] }, { kind: "component", type: UploadAttribute, selector: "upload-attribute", inputs: ["components", "variables", "component"] }, { kind: "component", type: TableControl, selector: "table-control", inputs: ["component"] }, { kind: "component", type: TableAttribute, selector: "table-attribute", inputs: ["components", "variables", "component"] }, { kind: "component", type: GroupAttribute, selector: "group-attribute", inputs: ["group"] }, { kind: "component", type: SdModal, selector: "sd-modal", inputs: ["autoId", "title", "color", "width", "height", "view", "modalClass", "lazyLoadContent", "hideClose", "disableBackdropClose"], outputs: ["sdClosed"] }, { kind: "component", type: SdInput$1, selector: "sd-input", inputs: ["autoId", "name", "appearance", "floatLabel", "size", "form", "label", "helperText", "placeholder", "type", "hideInlineError", "blurOnEnter", "required", "readonly", "disabled", "viewed", "minlength", "maxlength", "pattern", "patternErrorMessage", "validator", "inlineError", "hyperlink", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "cleared", "sdFocusForceBlur"] }, { kind: "component", type: SdTextarea, selector: "sd-textarea", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "rows", "hideInlineError", "required", "disabled", "viewed", "autoHeight", "maxlength", "pattern", "validator", "inlineError", "appearance", "floatLabel", "model"], outputs: ["modelChange", "sdChange"] }, { kind: "component", type: SdButton, selector: "sd-button", inputs: ["autoId", "type", "color", "size", "fontSet", "title", "width", "tooltip", "prefixIcon", "suffixIcon", "disabled", "loading", "block", "htmlType"], outputs: ["click"] }, { kind: "component", type: SdFormRender, selector: "sd-form-render", inputs: ["form", "configuration", "defaultEntity", "entity", "properties", "viewed"] }, { kind: "component", type: ConfigureValidationComponent, selector: "configure-validation", outputs: ["accept"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
6390
6387
  }
@@ -6434,7 +6431,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImpo
6434
6431
  /* eslint-disable @typescript-eslint/no-explicit-any */
6435
6432
  // Template là các mẫu do Portal định nghĩa sẵn (key, label ....) để người dùng chọn nhanh
6436
6433
  // Khi thực hiện sao chép 1 template chúng ta sẽ CLONE để tránh ảnh hưởng template gốc
6437
- class SdFeelExpression extends SdBaseSecureComponent {
6434
+ class SdFeelExpression {
6438
6435
  ref;
6439
6436
  expressionFeelPipe;
6440
6437
  formGenericService;
@@ -6468,7 +6465,6 @@ class SdFeelExpression extends SdBaseSecureComponent {
6468
6465
  modelChange = new EventEmitter();
6469
6466
  sdChange = new EventEmitter();
6470
6467
  constructor(ref, expressionFeelPipe, formGenericService) {
6471
- super();
6472
6468
  this.ref = ref;
6473
6469
  this.expressionFeelPipe = expressionFeelPipe;
6474
6470
  this.formGenericService = formGenericService;
@@ -6595,7 +6591,7 @@ class SdFeelExpression extends SdBaseSecureComponent {
6595
6591
  return attributes;
6596
6592
  };
6597
6593
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.24", ngImport: i0, type: SdFeelExpression, deps: [{ token: i0.ChangeDetectorRef }, { token: ExpressionFeelPipe }, { token: FormGenericService }], target: i0.ɵɵFactoryTarget.Component });
6598
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.24", type: SdFeelExpression, isStandalone: true, selector: "sd-feel-expression", inputs: { components: "components", _expression: ["expression", "_expression"], _model: ["model", "_model"] }, outputs: { expressionChange: "expressionChange", modelChange: "modelChange", sdChange: "sdChange" }, viewQueries: [{ propertyName: "modal", first: true, predicate: SdModal, descendants: true }], usesInheritance: true, ngImport: i0, template: "<a class=\"T12R\" href=\"javascript:;\" (click)=\"edit()\">{{model || '--'}}</a>\n@if (expression) {\n<sd-modal [title]=\"'core.component.form-builder.set-expression' | translate\">\n <div class=\"c-builder\">\n <div class=\"d-flex justify-content-end\">\n <sd-button prefixIcon=\"calculate\" class=\"mr-4\" [title]=\"'core.component.form-builder.condition-group' | translate\" color=\"primary\" (click)=\"addCombinator(expression.conditions)\"></sd-button>\n <sd-button prefixIcon=\"add\" [title]=\"'core.component.form-builder.condition' | translate\" color=\"primary\" (click)=\"addCondition(expression.conditions)\" type=\"fill\"></sd-button>\n </div>\n <!-- C\u1EA5p 1 -->\n @for (conditionLv1 of expression.conditions; track conditionLv1.key; let idxLv1 = $index) {\n <div class=\"d-flex align-items-end py-8\" style=\"gap: 8px;\">\n <div class=\"c-condition\">\n <!-- D\u00F2ng 1 ch\u1EC9 hi\u1EC7n WHERE -->\n @if (idxLv1 === 0) {\n <div class=\"c-where\">WHERE</div>\n }\n <!-- D\u00F2ng 2 hi\u1EC3n th\u1ECB combinator -->\n @if (idxLv1 === 1) {\n <div class=\"c-select\">\n <sd-select\n size=\"sm\"\n [(model)]=\"expression.combinator\"\n [items]=\"[\n { value: '&&', display: 'AND' },\n { value: '||', display: 'OR' }\n ]\"\n valueField=\"value\"\n displayField=\"display\"\n required\n hideInlineError=\"\"></sd-select>\n </div>\n }\n </div>\n @if (conditionLv1.type === 'condition') {\n <sd-autocomplete size=\"sm\" [form]=\"form\" style=\"width: 200px;\" [(model)]=\"conditionLv1.field\" [items]=\"attributes\" valueField=\"value\" displayField=\"display\" required hideInlineError></sd-autocomplete>\n @let fieldAttribute = attribute[conditionLv1.field!];\n <sd-select size=\"sm\" [form]=\"form\" style=\"width: 100px;\" [(model)]=\"conditionLv1.operator\" [items]=\"attributeOperators[fieldAttribute?.type || 'string']\" valueField=\"value\" displayField=\"display\" [disabled]=\"!fieldAttribute?.type\" required hideInlineError></sd-select>\n @if(!!fieldAttribute?.type && conditionLv1.operator && conditionLv1.operator !== 'NULL' && conditionLv1.operator !== 'NOT_NULL') {\n @if(fieldAttribute.type === 'string') {\n <sd-input size=\"sm\" [form]=\"form\" style=\"flex:1\" [(model)]=\"conditionLv1.value\" required hideInlineError></sd-input>\n } @else if(fieldAttribute.type === 'number') {\n <sd-input-number size=\"sm\" [form]=\"form\" style=\"flex:1\" [(model)]=\"conditionLv1.value\" required hideInlineError></sd-input-number>\n } @else if(fieldAttribute.type === 'boolean') {\n <sd-select size=\"sm\" [form]=\"form\" style=\"flex:1\" [(model)]=\"conditionLv1.value\" [items]=\"[{value: true, display: fieldAttribute.displayOnTrue}, {value: false, display: fieldAttribute.displayOnFalse}]\" valueField=\"value\" displayField=\"display\" required hideInlineError></sd-select>\n }@else if(fieldAttribute.type === 'values') {\n <sd-autocomplete size=\"sm\" [form]=\"form\" style=\"flex:1\" [(model)]=\"conditionLv1.value\" [items]=\"fieldAttribute.values\" valueField=\"value\" displayField=\"display\" required hideInlineError></sd-autocomplete>\n }\n }\n <sd-button\n class=\"ml-auto\"\n prefixIcon=\"delete\"\n [tooltip]=\"'core.component.form-builder.delete' | translate\"\n (click)=\"remove(expression.conditions, idxLv1)\"></sd-button>\n \n } @else if (conditionLv1.type === 'combinator') {\n <div class=\"c-builder\">\n <div class=\"d-flex justify-content-end\">\n <sd-button\n class=\"mr-4\"\n prefixIcon=\"delete\"\n [tooltip]=\"'core.component.form-builder.delete' | translate\"\n (click)=\"remove(expression.conditions, idxLv1)\"></sd-button>\n <sd-button prefixIcon=\"add\" [title]=\"'core.component.form-builder.condition' | translate\" size=\"sm\" color=\"primary\" (click)=\"addCondition(conditionLv1.conditions)\" type=\"fill\"></sd-button>\n </div>\n <!-- C\u1EA5p 2 -->\n @for (conditionLv2 of conditionLv1.conditions; track conditionLv2.key; let idxLv2 = $index) {\n <div class=\"d-flex align-items-end py-8\" style=\"gap: 8px;\">\n <div class=\"c-condition\">\n <!-- D\u00F2ng 1 ch\u1EC9 hi\u1EC7n WHERE -->\n @if (idxLv2 === 0) {\n <div class=\"c-where\">WHERE</div>\n }\n <!-- D\u00F2ng 2 hi\u1EC3n th\u1ECB combinator -->\n @if (idxLv2 === 1) {\n <div class=\"c-select\">\n <sd-select\n size=\"sm\"\n [(model)]=\"conditionLv1.combinator\"\n [items]=\"[\n { value: '&&', display: 'AND' },\n { value: '||', display: 'OR' }\n ]\"\n valueField=\"value\"\n displayField=\"display\"\n required\n hideInlineError=\"\"></sd-select>\n </div>\n }\n </div>\n @if (conditionLv2.type === 'condition') {\n <sd-autocomplete size=\"sm\" [form]=\"form\" style=\"width: 200px;\" [(model)]=\"conditionLv2.field\" [items]=\"attributes\" valueField=\"value\" displayField=\"display\" required hideInlineError></sd-autocomplete>\n @let fieldAttribute = attribute[conditionLv2.field!];\n <sd-select size=\"sm\" [form]=\"form\" style=\"width: 100px;\" [(model)]=\"conditionLv2.operator\" [items]=\"attributeOperators[fieldAttribute?.type || 'string']\" valueField=\"value\" displayField=\"display\" [disabled]=\"!fieldAttribute?.type\" required hideInlineError></sd-select>\n @if(!!fieldAttribute?.type && conditionLv2.operator && conditionLv2.operator !== 'NULL' && conditionLv2.operator !== 'NOT_NULL') {\n @if(fieldAttribute.type === 'string') {\n <sd-input size=\"sm\" [form]=\"form\" style=\"flex:1\" [(model)]=\"conditionLv2.value\" required hideInlineError></sd-input>\n } @else if(fieldAttribute.type === 'number') {\n <sd-input-number size=\"sm\" [form]=\"form\" style=\"flex:1\" [(model)]=\"conditionLv2.value\" required hideInlineError></sd-input-number>\n } @else if(fieldAttribute.type === 'boolean') {\n <sd-select size=\"sm\" [form]=\"form\" style=\"flex:1\" [(model)]=\"conditionLv2.value\" [items]=\"[{value: true, display: fieldAttribute.displayOnTrue}, {value: false, display: fieldAttribute.displayOnFalse}]\" valueField=\"value\" displayField=\"display\" required hideInlineError></sd-select>\n }@else if(fieldAttribute.type === 'values') {\n <sd-autocomplete size=\"sm\" [form]=\"form\" style=\"flex:1\" [(model)]=\"conditionLv2.value\" [items]=\"fieldAttribute.values\" valueField=\"value\" displayField=\"display\" required hideInlineError></sd-autocomplete>\n }\n }\n <sd-button\n class=\"ml-auto\"\n prefixIcon=\"delete\"\n [tooltip]=\"'core.component.form-builder.delete' | translate\"\n (click)=\"remove(conditionLv1.conditions, idxLv1)\"></sd-button>\n \n }\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\n <ng-container sdFooter>\n <sd-button prefixIcon=\"save\" [title]=\"'core.component.form-builder.confirm' | translate\" (click)=\"onAccept()\" color=\"primary\" type=\"fill\"></sd-button>\n </ng-container>\n</sd-modal>\n}", styles: [".c-builder{padding:8px;width:100%;border:1px solid #e6e6e6;border-radius:4px}.c-builder .c-condition{width:80px}.c-builder .c-condition .c-where{width:80px;border:1px solid #e6e6e6;background-color:#f2f2f2;color:#757575;border-radius:4px;padding:4px;text-align:center}.c-builder .c-condition .c-select{margin-left:10px;width:70px}\n"], dependencies: [{ kind: "component", type: SdInput$1, selector: "sd-input", inputs: ["autoId", "name", "appearance", "floatLabel", "size", "form", "label", "helperText", "placeholder", "type", "hideInlineError", "blurOnEnter", "required", "readonly", "disabled", "viewed", "minlength", "maxlength", "pattern", "patternErrorMessage", "validator", "inlineError", "hyperlink", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "cleared", "sdFocusForceBlur"] }, { kind: "component", type: SdInputNumber, selector: "sd-input-number", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "hideInlineError", "blurOnEnter", "required", "readonly", "disabled", "viewed", "type", "precision", "format", "min", "max", "validator", "inlineError", "hyperlink", "appearance", "floatLabel", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "cleared", "sdFocusForceBlur"] }, { kind: "component", type: SdSelect$1, selector: "sd-select", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "valueField", "displayField", "disabledField", "cacheChecksum", "limit", "hyperlink", "minWidthPanel", "hideInlineError", "required", "disabled", "viewed", "multiple", "clearable", "validator", "inlineError", "appearance", "floatLabel", "items", "model"], outputs: ["modelChange", "sdChange", "sdSelection"] }, { kind: "component", type: SdAutocomplete$1, selector: "sd-autocomplete", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "valueField", "displayField", "disabledField", "limit", "cacheChecksum", "hyperlink", "items", "hideInlineError", "addable", "required", "disabled", "viewed", "clearable", "validator", "inlineError", "appearance", "model"], outputs: ["modelChange", "sdChange", "sdSelection", "sdAdd"] }, { kind: "component", type: SdButton, selector: "sd-button", inputs: ["autoId", "type", "color", "size", "fontSet", "title", "width", "tooltip", "prefixIcon", "suffixIcon", "disabled", "loading", "block", "htmlType"], outputs: ["click"] }, { kind: "component", type: SdModal, selector: "sd-modal", inputs: ["autoId", "title", "color", "width", "height", "view", "modalClass", "lazyLoadContent", "hideClose", "disableBackdropClose"], outputs: ["sdClosed"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
6594
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.24", type: SdFeelExpression, isStandalone: true, selector: "sd-feel-expression", inputs: { components: "components", _expression: ["expression", "_expression"], _model: ["model", "_model"] }, outputs: { expressionChange: "expressionChange", modelChange: "modelChange", sdChange: "sdChange" }, viewQueries: [{ propertyName: "modal", first: true, predicate: SdModal, descendants: true }], ngImport: i0, template: "<a class=\"T12R\" href=\"javascript:;\" (click)=\"edit()\">{{model || '--'}}</a>\n@if (expression) {\n<sd-modal [title]=\"'core.component.form-builder.set-expression' | translate\">\n <div class=\"c-builder\">\n <div class=\"d-flex justify-content-end\">\n <sd-button prefixIcon=\"calculate\" class=\"mr-4\" [title]=\"'core.component.form-builder.condition-group' | translate\" color=\"primary\" (click)=\"addCombinator(expression.conditions)\"></sd-button>\n <sd-button prefixIcon=\"add\" [title]=\"'core.component.form-builder.condition' | translate\" color=\"primary\" (click)=\"addCondition(expression.conditions)\" type=\"fill\"></sd-button>\n </div>\n <!-- C\u1EA5p 1 -->\n @for (conditionLv1 of expression.conditions; track conditionLv1.key; let idxLv1 = $index) {\n <div class=\"d-flex align-items-end py-8\" style=\"gap: 8px;\">\n <div class=\"c-condition\">\n <!-- D\u00F2ng 1 ch\u1EC9 hi\u1EC7n WHERE -->\n @if (idxLv1 === 0) {\n <div class=\"c-where\">WHERE</div>\n }\n <!-- D\u00F2ng 2 hi\u1EC3n th\u1ECB combinator -->\n @if (idxLv1 === 1) {\n <div class=\"c-select\">\n <sd-select\n size=\"sm\"\n [(model)]=\"expression.combinator\"\n [items]=\"[\n { value: '&&', display: 'AND' },\n { value: '||', display: 'OR' }\n ]\"\n valueField=\"value\"\n displayField=\"display\"\n required\n hideInlineError=\"\"></sd-select>\n </div>\n }\n </div>\n @if (conditionLv1.type === 'condition') {\n <sd-autocomplete size=\"sm\" [form]=\"form\" style=\"width: 200px;\" [(model)]=\"conditionLv1.field\" [items]=\"attributes\" valueField=\"value\" displayField=\"display\" required hideInlineError></sd-autocomplete>\n @let fieldAttribute = attribute[conditionLv1.field!];\n <sd-select size=\"sm\" [form]=\"form\" style=\"width: 100px;\" [(model)]=\"conditionLv1.operator\" [items]=\"attributeOperators[fieldAttribute?.type || 'string']\" valueField=\"value\" displayField=\"display\" [disabled]=\"!fieldAttribute?.type\" required hideInlineError></sd-select>\n @if(!!fieldAttribute?.type && conditionLv1.operator && conditionLv1.operator !== 'NULL' && conditionLv1.operator !== 'NOT_NULL') {\n @if(fieldAttribute.type === 'string') {\n <sd-input size=\"sm\" [form]=\"form\" style=\"flex:1\" [(model)]=\"conditionLv1.value\" required hideInlineError></sd-input>\n } @else if(fieldAttribute.type === 'number') {\n <sd-input-number size=\"sm\" [form]=\"form\" style=\"flex:1\" [(model)]=\"conditionLv1.value\" required hideInlineError></sd-input-number>\n } @else if(fieldAttribute.type === 'boolean') {\n <sd-select size=\"sm\" [form]=\"form\" style=\"flex:1\" [(model)]=\"conditionLv1.value\" [items]=\"[{value: true, display: fieldAttribute.displayOnTrue}, {value: false, display: fieldAttribute.displayOnFalse}]\" valueField=\"value\" displayField=\"display\" required hideInlineError></sd-select>\n }@else if(fieldAttribute.type === 'values') {\n <sd-autocomplete size=\"sm\" [form]=\"form\" style=\"flex:1\" [(model)]=\"conditionLv1.value\" [items]=\"fieldAttribute.values\" valueField=\"value\" displayField=\"display\" required hideInlineError></sd-autocomplete>\n }\n }\n <sd-button\n class=\"ml-auto\"\n prefixIcon=\"delete\"\n [tooltip]=\"'core.component.form-builder.delete' | translate\"\n (click)=\"remove(expression.conditions, idxLv1)\"></sd-button>\n \n } @else if (conditionLv1.type === 'combinator') {\n <div class=\"c-builder\">\n <div class=\"d-flex justify-content-end\">\n <sd-button\n class=\"mr-4\"\n prefixIcon=\"delete\"\n [tooltip]=\"'core.component.form-builder.delete' | translate\"\n (click)=\"remove(expression.conditions, idxLv1)\"></sd-button>\n <sd-button prefixIcon=\"add\" [title]=\"'core.component.form-builder.condition' | translate\" size=\"sm\" color=\"primary\" (click)=\"addCondition(conditionLv1.conditions)\" type=\"fill\"></sd-button>\n </div>\n <!-- C\u1EA5p 2 -->\n @for (conditionLv2 of conditionLv1.conditions; track conditionLv2.key; let idxLv2 = $index) {\n <div class=\"d-flex align-items-end py-8\" style=\"gap: 8px;\">\n <div class=\"c-condition\">\n <!-- D\u00F2ng 1 ch\u1EC9 hi\u1EC7n WHERE -->\n @if (idxLv2 === 0) {\n <div class=\"c-where\">WHERE</div>\n }\n <!-- D\u00F2ng 2 hi\u1EC3n th\u1ECB combinator -->\n @if (idxLv2 === 1) {\n <div class=\"c-select\">\n <sd-select\n size=\"sm\"\n [(model)]=\"conditionLv1.combinator\"\n [items]=\"[\n { value: '&&', display: 'AND' },\n { value: '||', display: 'OR' }\n ]\"\n valueField=\"value\"\n displayField=\"display\"\n required\n hideInlineError=\"\"></sd-select>\n </div>\n }\n </div>\n @if (conditionLv2.type === 'condition') {\n <sd-autocomplete size=\"sm\" [form]=\"form\" style=\"width: 200px;\" [(model)]=\"conditionLv2.field\" [items]=\"attributes\" valueField=\"value\" displayField=\"display\" required hideInlineError></sd-autocomplete>\n @let fieldAttribute = attribute[conditionLv2.field!];\n <sd-select size=\"sm\" [form]=\"form\" style=\"width: 100px;\" [(model)]=\"conditionLv2.operator\" [items]=\"attributeOperators[fieldAttribute?.type || 'string']\" valueField=\"value\" displayField=\"display\" [disabled]=\"!fieldAttribute?.type\" required hideInlineError></sd-select>\n @if(!!fieldAttribute?.type && conditionLv2.operator && conditionLv2.operator !== 'NULL' && conditionLv2.operator !== 'NOT_NULL') {\n @if(fieldAttribute.type === 'string') {\n <sd-input size=\"sm\" [form]=\"form\" style=\"flex:1\" [(model)]=\"conditionLv2.value\" required hideInlineError></sd-input>\n } @else if(fieldAttribute.type === 'number') {\n <sd-input-number size=\"sm\" [form]=\"form\" style=\"flex:1\" [(model)]=\"conditionLv2.value\" required hideInlineError></sd-input-number>\n } @else if(fieldAttribute.type === 'boolean') {\n <sd-select size=\"sm\" [form]=\"form\" style=\"flex:1\" [(model)]=\"conditionLv2.value\" [items]=\"[{value: true, display: fieldAttribute.displayOnTrue}, {value: false, display: fieldAttribute.displayOnFalse}]\" valueField=\"value\" displayField=\"display\" required hideInlineError></sd-select>\n }@else if(fieldAttribute.type === 'values') {\n <sd-autocomplete size=\"sm\" [form]=\"form\" style=\"flex:1\" [(model)]=\"conditionLv2.value\" [items]=\"fieldAttribute.values\" valueField=\"value\" displayField=\"display\" required hideInlineError></sd-autocomplete>\n }\n }\n <sd-button\n class=\"ml-auto\"\n prefixIcon=\"delete\"\n [tooltip]=\"'core.component.form-builder.delete' | translate\"\n (click)=\"remove(conditionLv1.conditions, idxLv1)\"></sd-button>\n \n }\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\n <ng-container sdFooter>\n <sd-button prefixIcon=\"save\" [title]=\"'core.component.form-builder.confirm' | translate\" (click)=\"onAccept()\" color=\"primary\" type=\"fill\"></sd-button>\n </ng-container>\n</sd-modal>\n}", styles: [".c-builder{padding:8px;width:100%;border:1px solid #e6e6e6;border-radius:4px}.c-builder .c-condition{width:80px}.c-builder .c-condition .c-where{width:80px;border:1px solid #e6e6e6;background-color:#f2f2f2;color:#757575;border-radius:4px;padding:4px;text-align:center}.c-builder .c-condition .c-select{margin-left:10px;width:70px}\n"], dependencies: [{ kind: "component", type: SdInput$1, selector: "sd-input", inputs: ["autoId", "name", "appearance", "floatLabel", "size", "form", "label", "helperText", "placeholder", "type", "hideInlineError", "blurOnEnter", "required", "readonly", "disabled", "viewed", "minlength", "maxlength", "pattern", "patternErrorMessage", "validator", "inlineError", "hyperlink", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "cleared", "sdFocusForceBlur"] }, { kind: "component", type: SdInputNumber, selector: "sd-input-number", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "hideInlineError", "blurOnEnter", "required", "readonly", "disabled", "viewed", "type", "precision", "format", "min", "max", "validator", "inlineError", "hyperlink", "appearance", "floatLabel", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "cleared", "sdFocusForceBlur"] }, { kind: "component", type: SdSelect$1, selector: "sd-select", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "valueField", "displayField", "disabledField", "cacheChecksum", "limit", "hyperlink", "minWidthPanel", "hideInlineError", "required", "disabled", "viewed", "multiple", "clearable", "validator", "inlineError", "appearance", "floatLabel", "items", "model"], outputs: ["modelChange", "sdChange", "sdSelection"] }, { kind: "component", type: SdAutocomplete$1, selector: "sd-autocomplete", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "valueField", "displayField", "disabledField", "limit", "cacheChecksum", "hyperlink", "items", "hideInlineError", "addable", "required", "disabled", "viewed", "clearable", "validator", "inlineError", "appearance", "model"], outputs: ["modelChange", "sdChange", "sdSelection", "sdAdd"] }, { kind: "component", type: SdButton, selector: "sd-button", inputs: ["autoId", "type", "color", "size", "fontSet", "title", "width", "tooltip", "prefixIcon", "suffixIcon", "disabled", "loading", "block", "htmlType"], outputs: ["click"] }, { kind: "component", type: SdModal, selector: "sd-modal", inputs: ["autoId", "title", "color", "width", "height", "view", "modalClass", "lazyLoadContent", "hideClose", "disableBackdropClose"], outputs: ["sdClosed"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
6599
6595
  }
6600
6596
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.24", ngImport: i0, type: SdFeelExpression, decorators: [{
6601
6597
  type: Component,