@sd-angular/core 19.0.0-beta.99 → 19.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 (412) hide show
  1. package/README.md +9 -11
  2. package/assets/images/coming-soon.svg +23 -0
  3. package/assets/images/data-empty.svg +21 -0
  4. package/assets/images/expired.svg +22 -0
  5. package/assets/images/file-error.svg +28 -0
  6. package/assets/images/filter-empty.svg +20 -0
  7. package/assets/images/filter-required.svg +21 -0
  8. package/assets/images/forbidden.svg +15 -0
  9. package/assets/images/image-error.svg +27 -0
  10. package/assets/images/maintenance.svg +23 -0
  11. package/assets/images/not-found.svg +20 -0
  12. package/assets/images/offline.svg +20 -0
  13. package/assets/images/submitted.svg +18 -0
  14. package/assets/images/success.svg +18 -0
  15. package/assets/images/unauthorized.svg +22 -0
  16. package/assets/images/unknown-error.svg +18 -0
  17. package/assets/scss/README.md +433 -353
  18. package/assets/scss/core/_inline-edit.scss +105 -0
  19. package/assets/scss/core/form.scss +58 -1
  20. package/assets/scss/core/image.scss +35 -0
  21. package/assets/scss/core/utilities/_base.scss +6 -6
  22. package/assets/scss/core/utilities/_grid.scss +4 -3
  23. package/assets/scss/core/utilities/_index.scss +1 -1
  24. package/assets/scss/core/utilities/_sizing.scss +2 -2
  25. package/assets/scss/sd-core.scss +1 -2
  26. package/components/autoid-inspector/src/autoid-inspector.component.d.ts +18 -0
  27. package/components/autoid-inspector/src/models/autoid-element.model.d.ts +45 -1
  28. package/components/autoid-inspector/src/models/autoid-export-format.model.d.ts +19 -0
  29. package/components/autoid-inspector/src/services/autoid-export.service.d.ts +10 -2
  30. package/components/autoid-inspector/src/services/autoid-scanner.service.d.ts +26 -3
  31. package/components/badge/src/badge.component.d.ts +20 -3
  32. package/components/button/src/button.component.d.ts +3 -3
  33. package/components/ckeditor-styles/index.d.ts +1 -0
  34. package/components/ckeditor-styles/src/ckeditor-styles.component.d.ts +25 -0
  35. package/components/form-generic/src/components/form-builder/form-builder.component.d.ts +13 -15
  36. package/components/form-generic/src/components/form-render/form-render.component.d.ts +1 -2
  37. package/components/form-generic/src/components/sd-feel-expression/sd-feel-expression.component.d.ts +1 -2
  38. package/components/form-generic/src/models/form-generic-component.model.d.ts +5 -6
  39. package/components/form-generic/src/models/form-generic-expression.model.d.ts +1 -1
  40. package/components/history/models/history.model.d.ts +2 -2
  41. package/components/index.d.ts +2 -0
  42. package/components/inform/index.d.ts +2 -0
  43. package/components/inform/src/inform-action.directive.d.ts +9 -0
  44. package/components/inform/src/inform.component.d.ts +59 -0
  45. package/components/modal/src/modal.component.d.ts +2 -1
  46. package/components/modal-resizable/index.d.ts +1 -0
  47. package/components/modal-resizable/src/modal-resizable.component.d.ts +29 -0
  48. package/components/operator/index.d.ts +1 -0
  49. package/components/operator/src/operator.component.d.ts +34 -0
  50. package/components/query-bar/index.d.ts +2 -0
  51. package/components/query-bar/src/components/actions-bar/actions-bar.component.d.ts +28 -0
  52. package/components/query-bar/src/components/build-chip/build-chip.component.d.ts +67 -0
  53. package/components/query-bar/src/components/chip-popover/chip-popover.component.d.ts +73 -0
  54. package/components/query-bar/src/components/field-picker/field-picker.component.d.ts +37 -0
  55. package/components/query-bar/src/components/inline-chip/inline-chip.component.d.ts +76 -0
  56. package/components/query-bar/src/components/inline-value-chip/inline-value-chip.component.d.ts +67 -0
  57. package/components/query-bar/src/components/popover-chip/popover-chip.component.d.ts +35 -0
  58. package/components/query-bar/src/components/saved-filters-menu/saved-filters-menu.component.d.ts +39 -0
  59. package/components/query-bar/src/query-bar.component.d.ts +137 -0
  60. package/components/query-bar/src/query-bar.model.d.ts +136 -0
  61. package/components/query-builder/index.d.ts +2 -0
  62. package/components/query-builder/src/query-builder.component.d.ts +98 -22
  63. package/components/query-builder/src/query-builder.model.d.ts +118 -0
  64. package/components/query-builder/src/query-builder.serializer.d.ts +32 -0
  65. package/components/section/src/section.component.d.ts +3 -3
  66. package/components/side-drawer/src/side-drawer.component.d.ts +1 -2
  67. package/components/stepper/index.d.ts +2 -0
  68. package/components/stepper/src/step.component.d.ts +17 -0
  69. package/components/stepper/src/stepper.component.d.ts +32 -0
  70. package/components/tab/index.d.ts +2 -0
  71. package/components/tab/src/tab-group.component.d.ts +36 -0
  72. package/components/tab/src/tab.component.d.ts +13 -0
  73. package/components/tab-router/src/decorators/tab.decorator.d.ts +2 -2
  74. package/components/tab-router/src/models/tab-router.model.d.ts +2 -2
  75. package/components/table/index.d.ts +1 -0
  76. package/components/table/src/components/column-title/column-title.component.d.ts +1 -1
  77. package/components/table/src/components/config/config.component.d.ts +3 -1
  78. package/components/table/src/components/desktop-cell/desktop-cell.component.d.ts +3 -3
  79. package/components/table/src/components/filter/column-filter/column-filter.component.d.ts +3 -7
  80. package/components/table/src/components/selector-action/selector-action.component.d.ts +3 -1
  81. package/components/table/src/configurations/table.configuration.d.ts +3 -3
  82. package/components/table/src/directives/index.d.ts +1 -0
  83. package/components/table/src/directives/sd-table-cell-def.directive.d.ts +12 -4
  84. package/components/table/src/directives/sd-table-column-filter-def.directive.d.ts +12 -4
  85. package/components/table/src/directives/sd-table-expand-def.directive.d.ts +10 -4
  86. package/components/table/src/directives/sd-table-filter-def.directive.d.ts +3 -3
  87. package/components/table/src/directives/sd-table-footer-def.directive.d.ts +12 -4
  88. package/components/table/src/directives/sd-table-group-def.directive.d.ts +44 -0
  89. package/components/table/src/directives/sd-table-title-def.directive.d.ts +10 -4
  90. package/components/table/src/models/index.d.ts +1 -0
  91. package/components/table/src/models/table-column.model.d.ts +13 -13
  92. package/components/table/src/models/table-command.model.d.ts +3 -2
  93. package/components/table/src/models/table-item.model.d.ts +19 -4
  94. package/components/table/src/models/table-option-export.model.d.ts +3 -3
  95. package/components/table/src/models/table-option-group.model.d.ts +19 -2
  96. package/components/table/src/models/table-option-selector.model.d.ts +17 -1
  97. package/components/table/src/models/table-option-tree.model.d.ts +96 -8
  98. package/components/table/src/models/table-option.model.d.ts +11 -2
  99. package/components/table/src/pipes/sd-group.pipe.d.ts +15 -1
  100. package/components/table/src/services/table-filter/table-filter.model.d.ts +9 -9
  101. package/components/table/src/services/table-filter/table-filter.service.d.ts +2 -2
  102. package/components/table/src/services/tree/tree.util.d.ts +43 -2
  103. package/components/table/src/table.component.d.ts +37 -13
  104. package/components/upload-file/src/upload-file.component.d.ts +2 -0
  105. package/components/view/src/view.component.d.ts +35 -1
  106. package/directives/src/sd-desktop.directive.d.ts +2 -4
  107. package/directives/src/sd-mobile.directive.d.ts +2 -4
  108. package/fesm2022/sd-angular-core-components-anchor.mjs +75 -49
  109. package/fesm2022/sd-angular-core-components-anchor.mjs.map +1 -1
  110. package/fesm2022/sd-angular-core-components-autoid-inspector.mjs +254 -29
  111. package/fesm2022/sd-angular-core-components-autoid-inspector.mjs.map +1 -1
  112. package/fesm2022/sd-angular-core-components-avatar.mjs +2 -2
  113. package/fesm2022/sd-angular-core-components-avatar.mjs.map +1 -1
  114. package/fesm2022/sd-angular-core-components-badge.mjs +18 -3
  115. package/fesm2022/sd-angular-core-components-badge.mjs.map +1 -1
  116. package/fesm2022/sd-angular-core-components-button.mjs +3 -5
  117. package/fesm2022/sd-angular-core-components-button.mjs.map +1 -1
  118. package/fesm2022/sd-angular-core-components-ckeditor-styles.mjs +38 -0
  119. package/fesm2022/sd-angular-core-components-ckeditor-styles.mjs.map +1 -0
  120. package/fesm2022/sd-angular-core-components-document-builder.mjs +10 -8
  121. package/fesm2022/sd-angular-core-components-document-builder.mjs.map +1 -1
  122. package/fesm2022/sd-angular-core-components-editor.mjs +3 -2
  123. package/fesm2022/sd-angular-core-components-editor.mjs.map +1 -1
  124. package/fesm2022/sd-angular-core-components-form-generic.mjs +153 -243
  125. package/fesm2022/sd-angular-core-components-form-generic.mjs.map +1 -1
  126. package/fesm2022/sd-angular-core-components-history.mjs +3 -2
  127. package/fesm2022/sd-angular-core-components-history.mjs.map +1 -1
  128. package/fesm2022/sd-angular-core-components-inform.mjs +190 -0
  129. package/fesm2022/sd-angular-core-components-inform.mjs.map +1 -0
  130. package/fesm2022/sd-angular-core-components-mini-editor.mjs +4 -3
  131. package/fesm2022/sd-angular-core-components-mini-editor.mjs.map +1 -1
  132. package/fesm2022/sd-angular-core-components-modal-resizable.mjs +158 -0
  133. package/fesm2022/sd-angular-core-components-modal-resizable.mjs.map +1 -0
  134. package/fesm2022/sd-angular-core-components-modal.mjs +2 -2
  135. package/fesm2022/sd-angular-core-components-modal.mjs.map +1 -1
  136. package/fesm2022/sd-angular-core-components-operator.mjs +77 -0
  137. package/fesm2022/sd-angular-core-components-operator.mjs.map +1 -0
  138. package/fesm2022/sd-angular-core-components-preview.mjs +4 -4
  139. package/fesm2022/sd-angular-core-components-preview.mjs.map +1 -1
  140. package/fesm2022/sd-angular-core-components-query-bar.mjs +1384 -0
  141. package/fesm2022/sd-angular-core-components-query-bar.mjs.map +1 -0
  142. package/fesm2022/sd-angular-core-components-query-builder.mjs +669 -57
  143. package/fesm2022/sd-angular-core-components-query-builder.mjs.map +1 -1
  144. package/fesm2022/sd-angular-core-components-quick-action.mjs +2 -2
  145. package/fesm2022/sd-angular-core-components-quick-action.mjs.map +1 -1
  146. package/fesm2022/sd-angular-core-components-section.mjs +2 -4
  147. package/fesm2022/sd-angular-core-components-section.mjs.map +1 -1
  148. package/fesm2022/sd-angular-core-components-side-drawer.mjs +3 -5
  149. package/fesm2022/sd-angular-core-components-side-drawer.mjs.map +1 -1
  150. package/fesm2022/sd-angular-core-components-splitter.mjs +5 -2
  151. package/fesm2022/sd-angular-core-components-splitter.mjs.map +1 -1
  152. package/fesm2022/sd-angular-core-components-stepper.mjs +138 -0
  153. package/fesm2022/sd-angular-core-components-stepper.mjs.map +1 -0
  154. package/fesm2022/sd-angular-core-components-tab-router.mjs +3 -3
  155. package/fesm2022/sd-angular-core-components-tab-router.mjs.map +1 -1
  156. package/fesm2022/sd-angular-core-components-tab.mjs +143 -0
  157. package/fesm2022/sd-angular-core-components-tab.mjs.map +1 -0
  158. package/fesm2022/sd-angular-core-components-table.mjs +577 -225
  159. package/fesm2022/sd-angular-core-components-table.mjs.map +1 -1
  160. package/fesm2022/sd-angular-core-components-upload-file.mjs +36 -25
  161. package/fesm2022/sd-angular-core-components-upload-file.mjs.map +1 -1
  162. package/fesm2022/sd-angular-core-components-view.mjs +44 -9
  163. package/fesm2022/sd-angular-core-components-view.mjs.map +1 -1
  164. package/fesm2022/sd-angular-core-components.mjs +2 -0
  165. package/fesm2022/sd-angular-core-components.mjs.map +1 -1
  166. package/fesm2022/sd-angular-core-directives.mjs +19 -21
  167. package/fesm2022/sd-angular-core-directives.mjs.map +1 -1
  168. package/fesm2022/sd-angular-core-forms-autocomplete.mjs +45 -16
  169. package/fesm2022/sd-angular-core-forms-autocomplete.mjs.map +1 -1
  170. package/fesm2022/sd-angular-core-forms-checkbox.mjs +97 -87
  171. package/fesm2022/sd-angular-core-forms-checkbox.mjs.map +1 -1
  172. package/fesm2022/sd-angular-core-forms-chip-calendar.mjs +92 -61
  173. package/fesm2022/sd-angular-core-forms-chip-calendar.mjs.map +1 -1
  174. package/fesm2022/sd-angular-core-forms-chip.mjs +89 -52
  175. package/fesm2022/sd-angular-core-forms-chip.mjs.map +1 -1
  176. package/fesm2022/sd-angular-core-forms-date-range.mjs +63 -10
  177. package/fesm2022/sd-angular-core-forms-date-range.mjs.map +1 -1
  178. package/fesm2022/sd-angular-core-forms-date.mjs +39 -15
  179. package/fesm2022/sd-angular-core-forms-date.mjs.map +1 -1
  180. package/fesm2022/sd-angular-core-forms-datetime.mjs +33 -14
  181. package/fesm2022/sd-angular-core-forms-datetime.mjs.map +1 -1
  182. package/fesm2022/sd-angular-core-forms-directives.mjs +18 -24
  183. package/fesm2022/sd-angular-core-forms-directives.mjs.map +1 -1
  184. package/fesm2022/sd-angular-core-forms-inline-text.mjs +167 -0
  185. package/fesm2022/sd-angular-core-forms-inline-text.mjs.map +1 -0
  186. package/fesm2022/sd-angular-core-forms-input-color.mjs +119 -0
  187. package/fesm2022/sd-angular-core-forms-input-color.mjs.map +1 -0
  188. package/fesm2022/sd-angular-core-forms-input-number.mjs +73 -15
  189. package/fesm2022/sd-angular-core-forms-input-number.mjs.map +1 -1
  190. package/fesm2022/sd-angular-core-forms-input.mjs +86 -29
  191. package/fesm2022/sd-angular-core-forms-input.mjs.map +1 -1
  192. package/fesm2022/sd-angular-core-forms-models.mjs +44 -2
  193. package/fesm2022/sd-angular-core-forms-models.mjs.map +1 -1
  194. package/fesm2022/sd-angular-core-forms-radio.mjs +130 -175
  195. package/fesm2022/sd-angular-core-forms-radio.mjs.map +1 -1
  196. package/fesm2022/sd-angular-core-forms-select.mjs +66 -23
  197. package/fesm2022/sd-angular-core-forms-select.mjs.map +1 -1
  198. package/fesm2022/sd-angular-core-forms-switch.mjs +95 -98
  199. package/fesm2022/sd-angular-core-forms-switch.mjs.map +1 -1
  200. package/fesm2022/sd-angular-core-forms-textarea.mjs +33 -12
  201. package/fesm2022/sd-angular-core-forms-textarea.mjs.map +1 -1
  202. package/fesm2022/sd-angular-core-i18n.mjs +65 -0
  203. package/fesm2022/sd-angular-core-i18n.mjs.map +1 -1
  204. package/fesm2022/sd-angular-core-modules-auth.mjs +4 -4
  205. package/fesm2022/sd-angular-core-modules-auth.mjs.map +1 -1
  206. package/fesm2022/sd-angular-core-modules-authom.mjs +1 -1
  207. package/fesm2022/sd-angular-core-modules-authom.mjs.map +1 -1
  208. package/fesm2022/sd-angular-core-modules-layout.mjs +23 -23
  209. package/fesm2022/sd-angular-core-modules-layout.mjs.map +1 -1
  210. package/fesm2022/sd-angular-core-modules-permission.mjs +3 -3
  211. package/fesm2022/sd-angular-core-modules-permission.mjs.map +1 -1
  212. package/fesm2022/sd-angular-core-pipes.mjs +2 -2
  213. package/fesm2022/sd-angular-core-pipes.mjs.map +1 -1
  214. package/fesm2022/sd-angular-core-services-api.mjs +3 -3
  215. package/fesm2022/sd-angular-core-services-api.mjs.map +1 -1
  216. package/fesm2022/sd-angular-core-services-cache.mjs +3 -2
  217. package/fesm2022/sd-angular-core-services-cache.mjs.map +1 -1
  218. package/fesm2022/sd-angular-core-services-confirm.mjs +1 -1
  219. package/fesm2022/sd-angular-core-services-confirm.mjs.map +1 -1
  220. package/fesm2022/sd-angular-core-services-excel.mjs +6 -5
  221. package/fesm2022/sd-angular-core-services-excel.mjs.map +1 -1
  222. package/fesm2022/sd-angular-core-services-notify.mjs +43 -12
  223. package/fesm2022/sd-angular-core-services-notify.mjs.map +1 -1
  224. package/fesm2022/sd-angular-core-services-storage.mjs +2 -2
  225. package/fesm2022/sd-angular-core-services-storage.mjs.map +1 -1
  226. package/fesm2022/sd-angular-core-utilities-models.mjs.map +1 -1
  227. package/forms/autocomplete/src/autocomplete.component.d.ts +24 -10
  228. package/forms/checkbox/src/checkbox.component.d.ts +21 -20
  229. package/forms/chip/src/chip.component.d.ts +25 -20
  230. package/forms/chip-calendar/src/chip-calendar.component.d.ts +27 -22
  231. package/forms/date/src/date.component.d.ts +23 -9
  232. package/forms/date-range/src/date-range.component.d.ts +28 -5
  233. package/forms/datetime/src/datetime.component.d.ts +21 -8
  234. package/forms/directives/src/item-def.directive.d.ts +10 -5
  235. package/forms/directives/src/label-def.directive.d.ts +3 -2
  236. package/forms/directives/src/suffix-def.directive.d.ts +3 -2
  237. package/forms/directives/src/view-def.directive.d.ts +9 -9
  238. package/forms/inline-text/index.d.ts +1 -0
  239. package/forms/inline-text/src/inline-text.component.d.ts +76 -0
  240. package/forms/input/src/input.component.d.ts +31 -13
  241. package/forms/input-color/index.d.ts +1 -0
  242. package/forms/input-color/src/input-color.component.d.ts +34 -0
  243. package/forms/input-number/src/input-number.component.d.ts +25 -9
  244. package/forms/models/index.d.ts +1 -0
  245. package/forms/models/src/sd-custom-validator.model.d.ts +13 -1
  246. package/forms/models/src/sd-viewed.d.ts +38 -0
  247. package/forms/radio/src/radio.component.d.ts +37 -38
  248. package/forms/select/src/select.component.d.ts +36 -14
  249. package/forms/switch/src/switch.component.d.ts +22 -23
  250. package/forms/textarea/src/textarea.component.d.ts +18 -8
  251. package/i18n/src/en.d.ts +11 -0
  252. package/modules/auth/configurations/auth.configuration.d.ts +4 -4
  253. package/modules/layout/configurations/layout.configuration.d.ts +3 -3
  254. package/modules/permission/src/configurations/permission.configuration.d.ts +3 -3
  255. package/package.json +77 -41
  256. package/sd-angular-core-19.0.4.tgz +0 -0
  257. package/services/confirm/src/lib/components/dialog-confirm/dialog-confirm.component.d.ts +3 -3
  258. package/services/confirm/src/lib/confirm.service.d.ts +9 -9
  259. package/services/notify/src/components/toast/toast.component.d.ts +16 -1
  260. package/services/notify/src/notify.model.d.ts +17 -0
  261. package/utilities/models/src/icon.model.d.ts +1 -1
  262. package/assets/scss/bootstrap-4.6.2/_alert.scss +0 -52
  263. package/assets/scss/bootstrap-4.6.2/_badge.scss +0 -54
  264. package/assets/scss/bootstrap-4.6.2/_breadcrumb.scss +0 -42
  265. package/assets/scss/bootstrap-4.6.2/_button-group.scss +0 -163
  266. package/assets/scss/bootstrap-4.6.2/_buttons.scss +0 -142
  267. package/assets/scss/bootstrap-4.6.2/_card.scss +0 -286
  268. package/assets/scss/bootstrap-4.6.2/_carousel.scss +0 -200
  269. package/assets/scss/bootstrap-4.6.2/_close.scss +0 -40
  270. package/assets/scss/bootstrap-4.6.2/_code.scss +0 -48
  271. package/assets/scss/bootstrap-4.6.2/_custom-forms.scss +0 -526
  272. package/assets/scss/bootstrap-4.6.2/_dropdown.scss +0 -192
  273. package/assets/scss/bootstrap-4.6.2/_forms.scss +0 -347
  274. package/assets/scss/bootstrap-4.6.2/_functions.scss +0 -190
  275. package/assets/scss/bootstrap-4.6.2/_grid.scss +0 -73
  276. package/assets/scss/bootstrap-4.6.2/_images.scss +0 -42
  277. package/assets/scss/bootstrap-4.6.2/_input-group.scss +0 -211
  278. package/assets/scss/bootstrap-4.6.2/_jumbotron.scss +0 -17
  279. package/assets/scss/bootstrap-4.6.2/_list-group.scss +0 -154
  280. package/assets/scss/bootstrap-4.6.2/_media.scss +0 -8
  281. package/assets/scss/bootstrap-4.6.2/_mixins.scss +0 -47
  282. package/assets/scss/bootstrap-4.6.2/_modal.scss +0 -240
  283. package/assets/scss/bootstrap-4.6.2/_nav.scss +0 -125
  284. package/assets/scss/bootstrap-4.6.2/_navbar.scss +0 -332
  285. package/assets/scss/bootstrap-4.6.2/_pagination.scss +0 -74
  286. package/assets/scss/bootstrap-4.6.2/_popover.scss +0 -170
  287. package/assets/scss/bootstrap-4.6.2/_print.scss +0 -132
  288. package/assets/scss/bootstrap-4.6.2/_progress.scss +0 -47
  289. package/assets/scss/bootstrap-4.6.2/_reboot.scss +0 -484
  290. package/assets/scss/bootstrap-4.6.2/_root.scss +0 -19
  291. package/assets/scss/bootstrap-4.6.2/_spinners.scss +0 -65
  292. package/assets/scss/bootstrap-4.6.2/_tables.scss +0 -185
  293. package/assets/scss/bootstrap-4.6.2/_toasts.scss +0 -46
  294. package/assets/scss/bootstrap-4.6.2/_tooltip.scss +0 -115
  295. package/assets/scss/bootstrap-4.6.2/_transitions.scss +0 -26
  296. package/assets/scss/bootstrap-4.6.2/_type.scss +0 -125
  297. package/assets/scss/bootstrap-4.6.2/_utilities.scss +0 -18
  298. package/assets/scss/bootstrap-4.6.2/_variables.scss +0 -1150
  299. package/assets/scss/bootstrap-4.6.2/bootstrap-grid.scss +0 -30
  300. package/assets/scss/bootstrap-4.6.2/bootstrap-reboot.scss +0 -12
  301. package/assets/scss/bootstrap-4.6.2/bootstrap.scss +0 -44
  302. package/assets/scss/bootstrap-4.6.2/mixins/_alert.scss +0 -13
  303. package/assets/scss/bootstrap-4.6.2/mixins/_background-variant.scss +0 -23
  304. package/assets/scss/bootstrap-4.6.2/mixins/_badge.scss +0 -17
  305. package/assets/scss/bootstrap-4.6.2/mixins/_border-radius.scss +0 -76
  306. package/assets/scss/bootstrap-4.6.2/mixins/_box-shadow.scss +0 -20
  307. package/assets/scss/bootstrap-4.6.2/mixins/_breakpoints.scss +0 -123
  308. package/assets/scss/bootstrap-4.6.2/mixins/_buttons.scss +0 -110
  309. package/assets/scss/bootstrap-4.6.2/mixins/_caret.scss +0 -62
  310. package/assets/scss/bootstrap-4.6.2/mixins/_clearfix.scss +0 -7
  311. package/assets/scss/bootstrap-4.6.2/mixins/_deprecate.scss +0 -10
  312. package/assets/scss/bootstrap-4.6.2/mixins/_float.scss +0 -14
  313. package/assets/scss/bootstrap-4.6.2/mixins/_forms.scss +0 -195
  314. package/assets/scss/bootstrap-4.6.2/mixins/_gradients.scss +0 -45
  315. package/assets/scss/bootstrap-4.6.2/mixins/_grid-framework.scss +0 -80
  316. package/assets/scss/bootstrap-4.6.2/mixins/_grid.scss +0 -69
  317. package/assets/scss/bootstrap-4.6.2/mixins/_hover.scss +0 -37
  318. package/assets/scss/bootstrap-4.6.2/mixins/_image.scss +0 -36
  319. package/assets/scss/bootstrap-4.6.2/mixins/_list-group.scss +0 -21
  320. package/assets/scss/bootstrap-4.6.2/mixins/_lists.scss +0 -7
  321. package/assets/scss/bootstrap-4.6.2/mixins/_nav-divider.scss +0 -11
  322. package/assets/scss/bootstrap-4.6.2/mixins/_pagination.scss +0 -22
  323. package/assets/scss/bootstrap-4.6.2/mixins/_reset-text.scss +0 -17
  324. package/assets/scss/bootstrap-4.6.2/mixins/_resize.scss +0 -6
  325. package/assets/scss/bootstrap-4.6.2/mixins/_screen-reader.scss +0 -34
  326. package/assets/scss/bootstrap-4.6.2/mixins/_size.scss +0 -7
  327. package/assets/scss/bootstrap-4.6.2/mixins/_table-row.scss +0 -39
  328. package/assets/scss/bootstrap-4.6.2/mixins/_text-emphasis.scss +0 -17
  329. package/assets/scss/bootstrap-4.6.2/mixins/_text-hide.scss +0 -11
  330. package/assets/scss/bootstrap-4.6.2/mixins/_text-truncate.scss +0 -8
  331. package/assets/scss/bootstrap-4.6.2/mixins/_transition.scss +0 -26
  332. package/assets/scss/bootstrap-4.6.2/mixins/_visibility.scss +0 -8
  333. package/assets/scss/bootstrap-4.6.2/utilities/_align.scss +0 -8
  334. package/assets/scss/bootstrap-4.6.2/utilities/_background.scss +0 -19
  335. package/assets/scss/bootstrap-4.6.2/utilities/_borders.scss +0 -75
  336. package/assets/scss/bootstrap-4.6.2/utilities/_clearfix.scss +0 -3
  337. package/assets/scss/bootstrap-4.6.2/utilities/_display.scss +0 -26
  338. package/assets/scss/bootstrap-4.6.2/utilities/_embed.scss +0 -39
  339. package/assets/scss/bootstrap-4.6.2/utilities/_flex.scss +0 -51
  340. package/assets/scss/bootstrap-4.6.2/utilities/_float.scss +0 -11
  341. package/assets/scss/bootstrap-4.6.2/utilities/_interactions.scss +0 -5
  342. package/assets/scss/bootstrap-4.6.2/utilities/_overflow.scss +0 -5
  343. package/assets/scss/bootstrap-4.6.2/utilities/_position.scss +0 -32
  344. package/assets/scss/bootstrap-4.6.2/utilities/_screenreaders.scss +0 -11
  345. package/assets/scss/bootstrap-4.6.2/utilities/_shadows.scss +0 -6
  346. package/assets/scss/bootstrap-4.6.2/utilities/_sizing.scss +0 -20
  347. package/assets/scss/bootstrap-4.6.2/utilities/_spacing.scss +0 -73
  348. package/assets/scss/bootstrap-4.6.2/utilities/_stretched-link.scss +0 -19
  349. package/assets/scss/bootstrap-4.6.2/utilities/_text.scss +0 -72
  350. package/assets/scss/bootstrap-4.6.2/utilities/_visibility.scss +0 -13
  351. package/assets/scss/bootstrap-4.6.2/vendor/_rfs.scss +0 -228
  352. package/assets/scss/core/bootstrap.scss +0 -13
  353. package/components/table/index.d.ts.map +0 -1
  354. package/components/table/sd-angular-core-components-table.d.ts.map +0 -1
  355. package/components/table/src/components/column-title/column-title.component.d.ts.map +0 -1
  356. package/components/table/src/components/command/desktop-command.component.d.ts.map +0 -1
  357. package/components/table/src/components/command/pipes/command.pipe.d.ts.map +0 -1
  358. package/components/table/src/components/command/pipes/filter.pipe.d.ts.map +0 -1
  359. package/components/table/src/components/config/config.component.d.ts.map +0 -1
  360. package/components/table/src/components/desktop-cell/desktop-cell.component.d.ts.map +0 -1
  361. package/components/table/src/components/desktop-cell/view/view.component.d.ts.map +0 -1
  362. package/components/table/src/components/filter/column-filter/column-filter.component.d.ts.map +0 -1
  363. package/components/table/src/components/filter/external-filter/external-filter.component.d.ts.map +0 -1
  364. package/components/table/src/components/filter/index.d.ts.map +0 -1
  365. package/components/table/src/components/filter/mobile-filter/mobile-filter.component.d.ts.map +0 -1
  366. package/components/table/src/components/index.d.ts.map +0 -1
  367. package/components/table/src/components/popup-export/popup-export.component.d.ts.map +0 -1
  368. package/components/table/src/components/selector-action/action-filter.pipe.d.ts.map +0 -1
  369. package/components/table/src/components/selector-action/selector-action.component.d.ts.map +0 -1
  370. package/components/table/src/configurations/index.d.ts.map +0 -1
  371. package/components/table/src/configurations/table.configuration.d.ts.map +0 -1
  372. package/components/table/src/directives/index.d.ts.map +0 -1
  373. package/components/table/src/directives/sd-column-resize.directive.d.ts.map +0 -1
  374. package/components/table/src/directives/sd-table-cell-def.directive.d.ts.map +0 -1
  375. package/components/table/src/directives/sd-table-column-filter-def.directive.d.ts.map +0 -1
  376. package/components/table/src/directives/sd-table-expand-def.directive.d.ts.map +0 -1
  377. package/components/table/src/directives/sd-table-filter-def.directive.d.ts.map +0 -1
  378. package/components/table/src/directives/sd-table-footer-def.directive.d.ts.map +0 -1
  379. package/components/table/src/directives/sd-table-title-def.directive.d.ts.map +0 -1
  380. package/components/table/src/directives/sticky-shadow.directive.d.ts.map +0 -1
  381. package/components/table/src/models/index.d.ts.map +0 -1
  382. package/components/table/src/models/table-column.model.d.ts.map +0 -1
  383. package/components/table/src/models/table-command.model.d.ts.map +0 -1
  384. package/components/table/src/models/table-item.model.d.ts.map +0 -1
  385. package/components/table/src/models/table-option-config.model.d.ts.map +0 -1
  386. package/components/table/src/models/table-option-expand.model.d.ts.map +0 -1
  387. package/components/table/src/models/table-option-export.model.d.ts.map +0 -1
  388. package/components/table/src/models/table-option-group.model.d.ts.map +0 -1
  389. package/components/table/src/models/table-option-paginate.model.d.ts.map +0 -1
  390. package/components/table/src/models/table-option-reload.model.d.ts.map +0 -1
  391. package/components/table/src/models/table-option-selector.model.d.ts.map +0 -1
  392. package/components/table/src/models/table-option-sort.model.d.ts.map +0 -1
  393. package/components/table/src/models/table-option-style.model.d.ts.map +0 -1
  394. package/components/table/src/models/table-option-tree.model.d.ts.map +0 -1
  395. package/components/table/src/models/table-option.model.d.ts.map +0 -1
  396. package/components/table/src/pipes/filter-values.pipe.d.ts.map +0 -1
  397. package/components/table/src/pipes/index.d.ts.map +0 -1
  398. package/components/table/src/pipes/sd-group.pipe.d.ts.map +0 -1
  399. package/components/table/src/pipes/sd-tree.pipe.d.ts.map +0 -1
  400. package/components/table/src/pipes/selection-disabled.pipe.d.ts.map +0 -1
  401. package/components/table/src/pipes/selection-visible-select-all.pipe.d.ts.map +0 -1
  402. package/components/table/src/pipes/selection-visible.pipe.d.ts.map +0 -1
  403. package/components/table/src/services/column-width.util.d.ts.map +0 -1
  404. package/components/table/src/services/config.service.d.ts.map +0 -1
  405. package/components/table/src/services/index.d.ts.map +0 -1
  406. package/components/table/src/services/table-export/table-export.service.d.ts.map +0 -1
  407. package/components/table/src/services/table-filter/table-filter.model.d.ts.map +0 -1
  408. package/components/table/src/services/table-filter/table-filter.service.d.ts.map +0 -1
  409. package/components/table/src/services/table-format/table-format.service.d.ts.map +0 -1
  410. package/components/table/src/services/tree/tree.util.d.ts.map +0 -1
  411. package/components/table/src/table.component.d.ts.map +0 -1
  412. package/sd-angular-core-19.0.0-beta.99.tgz +0 -0
@@ -1,7 +1,7 @@
1
1
  import * as i0 from '@angular/core';
2
2
  import { InjectionToken, Inject, Optional, Injectable, Pipe, SecurityContext, Input, ChangeDetectionStrategy, Component, ViewChild, EventEmitter, Output, ViewChildren, QueryList, inject, input, ChangeDetectorRef, effect, untracked, viewChild, signal, computed } from '@angular/core';
3
- import { SdUtilities, DateUtilities as DateUtilities$1, StringUtilities, ArrayUtilities, NumberUtilities } from '@sd-angular/core/utilities/extensions';
4
- import { DateUtilities, SdUtilities as SdUtilities$1, StringUtilities as StringUtilities$1 } from '@sd-angular/core/utilities';
3
+ import { Utilities, StringUtilities as StringUtilities$1 } from '@sdcorejs/utils/fns';
4
+ import { DateUtilities } from '@sd-angular/core/utilities';
5
5
  import * as i3 from '@angular/cdk/drag-drop';
6
6
  import { moveItemInArray, transferArrayItem, DragDropModule } from '@angular/cdk/drag-drop';
7
7
  import * as i1$2 from '@angular/common';
@@ -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 '@sd-angular/core/components/base';
15
14
  import { SdButton } from '@sd-angular/core/components/button';
16
15
  import { SdModal } from '@sd-angular/core/components/modal';
17
16
  import { SdInput as SdInput$1 } from '@sd-angular/core/forms/input';
@@ -20,7 +19,8 @@ import { SdNotifyService, SdConfirmService } from '@sd-angular/core/services';
20
19
  import { Subscription, Subject, filter, debounceTime, startWith, combineLatest } from 'rxjs';
21
20
  import { SdSection } from '@sd-angular/core/components/section';
22
21
  import { startWith as startWith$1, debounceTime as debounceTime$1 } from 'rxjs/operators';
23
- import { SD_EMPTY_STR } from '@sd-angular/core/utilities/models';
22
+ import { DateUtilities as DateUtilities$1, StringUtilities, ArrayUtilities, NumberUtilities } from '@sd-angular/core/utilities/extensions';
23
+ import { EMPTY_STR } from '@sdcorejs/utils/constants';
24
24
  import * as i1 from '@sd-angular/core/pipes';
25
25
  import * as i1$1 from '@angular/platform-browser';
26
26
  import { SdInput, SdChip, SdChipCalendar, SdSelect, SdAutocomplete, SdRadio, SdInputNumber as SdInputNumber$1 } from '@sd-angular/core/forms';
@@ -44,10 +44,10 @@ import { MatMenuModule } from '@angular/material/menu';
44
44
  const SD_FORM_GENERIC_CONFIGURATION = new InjectionToken('sd.form-generic.configuration');
45
45
 
46
46
  const GenerateId = () => {
47
- return SdUtilities.randomId('id');
47
+ return Utilities.randomId('id');
48
48
  };
49
49
  const GenerateKey = () => {
50
- return SdUtilities.randomId('key');
50
+ return Utilities.randomId('key');
51
51
  };
52
52
  const SdFormatComponent = (component) => {
53
53
  if (component) {
@@ -807,26 +807,26 @@ class ComponentViewedPipe {
807
807
  }
808
808
  const val = entity?.[component.key];
809
809
  if (val === undefined || val === null) {
810
- return SD_EMPTY_STR;
810
+ return EMPTY_STR;
811
811
  }
812
812
  if (component.type === 'textfield' || component.type === 'textarea') {
813
- return val || SD_EMPTY_STR;
813
+ return val || EMPTY_STR;
814
814
  }
815
815
  if (component.type === 'number') {
816
- return this.formatNumberPipe.transform(value)?.toString() ?? SD_EMPTY_STR;
816
+ return this.formatNumberPipe.transform(value)?.toString() ?? EMPTY_STR;
817
817
  }
818
818
  if (component.type === 'datetime') {
819
819
  if (component.subtype === 'datetime') {
820
- return DateUtilities$1.toFormat(val, 'dd/MM/yyyy HH:mm') || SD_EMPTY_STR;
820
+ return DateUtilities$1.toFormat(val, 'dd/MM/yyyy HH:mm') || EMPTY_STR;
821
821
  }
822
822
  else {
823
- return DateUtilities$1.toFormat(val, 'dd/MM/yyyy') || SD_EMPTY_STR;
823
+ return DateUtilities$1.toFormat(val, 'dd/MM/yyyy') || EMPTY_STR;
824
824
  }
825
825
  }
826
826
  if (component.type === 'select' || component.type === 'radio' || component.type === 'checklist') {
827
827
  const values = (Array.isArray(val) ? val : [val]).filter(value => value !== undefined && value !== null && value !== '');
828
828
  if (!values.length) {
829
- return SD_EMPTY_STR;
829
+ return EMPTY_STR;
830
830
  }
831
831
  const selection = await this.formGenericService.selection.getDefinition(component?.valuesKey);
832
832
  // Nếu có cấu hình view tương ứng với selection thì sử dụng viewed
@@ -834,7 +834,7 @@ class ComponentViewedPipe {
834
834
  return ((await selection.viewed(values, {
835
835
  entity,
836
836
  component,
837
- })) || SD_EMPTY_STR);
837
+ })) || EMPTY_STR);
838
838
  }
839
839
  // Ngoài ra xử lý lấy thông tin items
840
840
  const items = await this.formGenericService.selection.items(component?.valuesKey, {
@@ -842,14 +842,14 @@ class ComponentViewedPipe {
842
842
  component,
843
843
  });
844
844
  if (!items) {
845
- return SD_EMPTY_STR;
845
+ return EMPTY_STR;
846
846
  }
847
847
  // Nếu items là Array thì kiểm tra value và hiển thị display
848
848
  if (Array.isArray(items)) {
849
849
  return (items
850
850
  .filter(e => values.includes(e.value))
851
851
  .map(e => e.display)
852
- .join(', ') || SD_EMPTY_STR);
852
+ .join(', ') || EMPTY_STR);
853
853
  }
854
854
  // items là Function (lazyValues) thì thực hiện gọi API
855
855
  return await items({
@@ -859,7 +859,7 @@ class ComponentViewedPipe {
859
859
  return (results
860
860
  .filter(e => values.includes(e.value))
861
861
  .map(e => e.display)
862
- .join(', ') || SD_EMPTY_STR);
862
+ .join(', ') || EMPTY_STR);
863
863
  });
864
864
  }
865
865
  return '';
@@ -1327,7 +1327,7 @@ class TextfieldComponent {
1327
1327
  }
1328
1328
  };
1329
1329
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: TextfieldComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
1330
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: TextfieldComponent, isStandalone: true, selector: "lib-textfield", inputs: { setVariables: "setVariables", form: "form", _entity: ["entity", "_entity"], _component: ["component", "_component"], _disabled: ["disabled", "_disabled"], _required: ["required", "_required"], _viewed: ["viewed", "_viewed"], _validator: ["validator", "_validator"] }, ngImport: i0, template: "@if (component && entity) {\r\n <sd-input\r\n [form]=\"form\"\r\n [label]=\"component.label || component.key\"\r\n [helperText]=\"component.helperText\"\r\n [name]=\"component.key\"\r\n [autoId]=\"component.key\"\r\n [(model)]=\"entity[component.key!]\"\r\n [required]=\"required\"\r\n [validator]=\"validator\"\r\n [maxlength]=\"component.validate?.maxlength\"\r\n [pattern]=\"component.validate?.pattern\"\r\n [patternErrorMessage]=\"component.validate?.patternErrorMessage\"\r\n [disabled]=\"disabled\"\r\n [viewed]=\"viewed || component.properties?.viewed\"></sd-input>\r\n}\r\n", styles: [":host{display:contents;width:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: SdInput, selector: "sd-input", inputs: ["autoId", "name", "appearance", "floatLabel", "size", "form", "label", "helperText", "placeholder", "type", "hideInlineError", "blurOnEnter", "required", "readonly", "disabled", "viewed", "minlength", "maxlength", "pattern", "patternErrorMessage", "validator", "inlineError", "hyperlink", "tooltip", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "sdFocusForceBlur"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1330
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: TextfieldComponent, isStandalone: true, selector: "lib-textfield", inputs: { setVariables: "setVariables", form: "form", _entity: ["entity", "_entity"], _component: ["component", "_component"], _disabled: ["disabled", "_disabled"], _required: ["required", "_required"], _viewed: ["viewed", "_viewed"], _validator: ["validator", "_validator"] }, ngImport: i0, template: "@if (component && entity) {\r\n <sd-input\r\n [form]=\"form\"\r\n [label]=\"component.label || component.key\"\r\n [helperText]=\"component.helperText\"\r\n [name]=\"component.key\"\r\n [autoId]=\"component.key\"\r\n [(model)]=\"entity[component.key!]\"\r\n [required]=\"required\"\r\n [validator]=\"validator\"\r\n [maxlength]=\"component.validate?.maxlength\"\r\n [pattern]=\"component.validate?.pattern\"\r\n [patternErrorMessage]=\"component.validate?.patternErrorMessage\"\r\n [disabled]=\"disabled\"\r\n [viewed]=\"viewed || component.properties?.viewed\"></sd-input>\r\n}\r\n", styles: [":host{display:contents;width:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: SdInput, selector: "sd-input", inputs: ["autoId", "name", "appearance", "floatLabel", "size", "form", "label", "helperText", "placeholder", "type", "hideInlineError", "blurOnEnter", "required", "readonly", "disabled", "viewed", "minlength", "maxlength", "pattern", "patternErrorMessage", "validator", "inlineError", "hyperlink", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "cleared", "sdFocusForceBlur"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1331
1331
  }
1332
1332
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: TextfieldComponent, decorators: [{
1333
1333
  type: Component,
@@ -1492,7 +1492,7 @@ class ChipStringComponent {
1492
1492
  this.#subscription.unsubscribe();
1493
1493
  }
1494
1494
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: ChipStringComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
1495
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: ChipStringComponent, isStandalone: true, selector: "lib-chip-string", inputs: { setVariables: "setVariables", form: "form", _entity: ["entity", "_entity"], _component: ["component", "_component"], _disabled: ["disabled", "_disabled"], _required: ["required", "_required"], _viewed: ["viewed", "_viewed"] }, ngImport: i0, template: "@if (component && entity) {\r\n @if (viewed || component.properties?.viewed) {\r\n <div class=\"text-black400\">{{ component.label || component.key }}</div>\r\n <div class=\"T14M\">{{ entity[component.key!] | componentViewed: entity : component | async }}</div>\r\n } @else {\r\n <sd-chip\r\n [form]=\"form\"\r\n [label]=\"component.label || component.key\"\r\n [name]=\"component.key\"\r\n [autoId]=\"component.key\"\r\n [(model)]=\"entity[component.key!]\"\r\n [required]=\"required\"\r\n [disabled]=\"disabled\"></sd-chip>\r\n }\r\n}\r\n", styles: [":host{display:contents;width:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "pipe", type: i1$2.AsyncPipe, name: "async" }, { kind: "component", type: SdChip, selector: "sd-chip", inputs: ["autoId", "name", "appearance", "floatLabel", "size", "form", "label", "placeholder", "removable", "hideInlineError", "model", "required", "min", "max", "addable", "disabled", "viewed", "hyperlink"], outputs: ["modelChange", "sdChange"] }, { kind: "pipe", type:
1495
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: ChipStringComponent, isStandalone: true, selector: "lib-chip-string", inputs: { setVariables: "setVariables", form: "form", _entity: ["entity", "_entity"], _component: ["component", "_component"], _disabled: ["disabled", "_disabled"], _required: ["required", "_required"], _viewed: ["viewed", "_viewed"] }, ngImport: i0, template: "@if (component && entity) {\r\n @if (viewed || component.properties?.viewed) {\r\n <div class=\"text-black400\">{{ component.label || component.key }}</div>\r\n <div class=\"T14M\">{{ entity[component.key!] | componentViewed: entity : component | async }}</div>\r\n } @else {\r\n <sd-chip\r\n [form]=\"form\"\r\n [label]=\"component.label || component.key\"\r\n [name]=\"component.key\"\r\n [autoId]=\"component.key\"\r\n [(model)]=\"entity[component.key!]\"\r\n [required]=\"required\"\r\n [disabled]=\"disabled\"></sd-chip>\r\n }\r\n}\r\n", styles: [":host{display:contents;width:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "pipe", type: i1$2.AsyncPipe, name: "async" }, { kind: "component", type: SdChip, selector: "sd-chip", inputs: ["autoId", "name", "appearance", "floatLabel", "size", "form", "label", "placeholder", "removable", "hideInlineError", "required", "min", "max", "addable", "disabled", "viewed", "hyperlink", "model"], outputs: ["modelChange", "sdChange"] }, { kind: "pipe", type:
1496
1496
  // Pipe cho phần viewed
1497
1497
  ComponentViewedPipe, name: "componentViewed" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1498
1498
  }
@@ -1575,7 +1575,7 @@ class ChipCalendarComponent {
1575
1575
  this.#subscription.unsubscribe();
1576
1576
  }
1577
1577
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: ChipCalendarComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
1578
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: ChipCalendarComponent, isStandalone: true, selector: "lib-chip-calendar", inputs: { setVariables: "setVariables", form: "form", _entity: ["entity", "_entity"], _component: ["component", "_component"], _disabled: ["disabled", "_disabled"], _required: ["required", "_required"], _viewed: ["viewed", "_viewed"] }, ngImport: i0, template: "@if (component && entity) {\r\n @if (viewed || component.properties?.viewed) {\r\n <div class=\"text-black400\">{{ component.label || component.key }}</div>\r\n <div class=\"T14M\">{{ entity[component.key!] | componentViewed: entity : component | async }}</div>\r\n } @else {\r\n <sd-chip-calendar\r\n [form]=\"form\"\r\n [label]=\"component.label || component.key\"\r\n [name]=\"component.key\"\r\n [autoId]=\"component.key\"\r\n [(model)]=\"entity[component.key!]\"\r\n [required]=\"required\"\r\n [disabled]=\"disabled\"></sd-chip-calendar>\r\n }\r\n}\r\n", styles: [":host{display:contents;width:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "pipe", type: i1$2.AsyncPipe, name: "async" }, { kind: "component", type: SdChipCalendar, selector: "sd-chip-calendar", inputs: ["autoId", "name", "appearance", "floatLabel", "size", "form", "label", "placeholder", "removable", "hideInlineError", "model", "required", "min", "max", "disabled", "viewed", "hyperlink"], outputs: ["modelChange", "sdChange"] }, { kind: "pipe", type:
1578
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: ChipCalendarComponent, isStandalone: true, selector: "lib-chip-calendar", inputs: { setVariables: "setVariables", form: "form", _entity: ["entity", "_entity"], _component: ["component", "_component"], _disabled: ["disabled", "_disabled"], _required: ["required", "_required"], _viewed: ["viewed", "_viewed"] }, ngImport: i0, template: "@if (component && entity) {\r\n @if (viewed || component.properties?.viewed) {\r\n <div class=\"text-black400\">{{ component.label || component.key }}</div>\r\n <div class=\"T14M\">{{ entity[component.key!] | componentViewed: entity : component | async }}</div>\r\n } @else {\r\n <sd-chip-calendar\r\n [form]=\"form\"\r\n [label]=\"component.label || component.key\"\r\n [name]=\"component.key\"\r\n [autoId]=\"component.key\"\r\n [(model)]=\"entity[component.key!]\"\r\n [required]=\"required\"\r\n [disabled]=\"disabled\"></sd-chip-calendar>\r\n }\r\n}\r\n", styles: [":host{display:contents;width:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "pipe", type: i1$2.AsyncPipe, name: "async" }, { kind: "component", type: SdChipCalendar, selector: "sd-chip-calendar", inputs: ["autoId", "name", "appearance", "floatLabel", "size", "form", "label", "placeholder", "removable", "hideInlineError", "required", "min", "max", "disabled", "viewed", "hyperlink", "model"], outputs: ["modelChange", "sdChange"] }, { kind: "pipe", type:
1579
1579
  // Pipe cho phần viewed
1580
1580
  ComponentViewedPipe, name: "componentViewed" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1581
1581
  }
@@ -1658,7 +1658,7 @@ class NumberComponent {
1658
1658
  this.#subscription.unsubscribe();
1659
1659
  }
1660
1660
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: NumberComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
1661
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: NumberComponent, isStandalone: true, selector: "lib-number", inputs: { setVariables: "setVariables", form: "form", _entity: ["entity", "_entity"], _component: ["component", "_component"], _disabled: ["disabled", "_disabled"], _required: ["required", "_required"], _viewed: ["viewed", "_viewed"] }, ngImport: i0, template: "@if (component && entity) {\r\n @if (viewed || component.properties?.viewed) {\r\n <div class=\"text-secondary\">{{ component.label || component.key }}</div>\r\n <div class=\"T16M\">{{ entity[component.key!] | componentViewed: entity : component | async }}</div>\r\n } @else {\r\n <sd-input-number\r\n [form]=\"form\"\r\n [label]=\"component.label || component.key\"\r\n [helperText]=\"component.helperText\"\r\n [name]=\"component.key\"\r\n [autoId]=\"component.key\"\r\n [(model)]=\"entity[component.key!]\"\r\n [required]=\"required\"\r\n [disabled]=\"disabled\"\r\n [max]=\"component.validate?.max\"\r\n [min]=\"component.validate?.min\"\r\n [viewed]=\"viewed || component.properties?.viewed\"></sd-input-number>\r\n }\r\n}\r\n", styles: [":host{display:contents;width:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "pipe", type: i1$2.AsyncPipe, name: "async" }, { 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", "sdFocusForceBlur"] }, { kind: "pipe", type:
1661
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: NumberComponent, isStandalone: true, selector: "lib-number", inputs: { setVariables: "setVariables", form: "form", _entity: ["entity", "_entity"], _component: ["component", "_component"], _disabled: ["disabled", "_disabled"], _required: ["required", "_required"], _viewed: ["viewed", "_viewed"] }, ngImport: i0, template: "@if (component && entity) {\r\n @if (viewed || component.properties?.viewed) {\r\n <div class=\"text-secondary\">{{ component.label || component.key }}</div>\r\n <div class=\"T16M\">{{ entity[component.key!] | componentViewed: entity : component | async }}</div>\r\n } @else {\r\n <sd-input-number\r\n [form]=\"form\"\r\n [label]=\"component.label || component.key\"\r\n [helperText]=\"component.helperText\"\r\n [name]=\"component.key\"\r\n [autoId]=\"component.key\"\r\n [(model)]=\"entity[component.key!]\"\r\n [required]=\"required\"\r\n [disabled]=\"disabled\"\r\n [max]=\"component.validate?.max\"\r\n [min]=\"component.validate?.min\"\r\n [viewed]=\"viewed || component.properties?.viewed\"></sd-input-number>\r\n }\r\n}\r\n", styles: [":host{display:contents;width:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "pipe", type: i1$2.AsyncPipe, name: "async" }, { 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: "pipe", type:
1662
1662
  // Pipe cho phần viewed
1663
1663
  ComponentViewedPipe, name: "componentViewed" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1664
1664
  }
@@ -1751,12 +1751,12 @@ class DatetimeComponent {
1751
1751
  }
1752
1752
  else {
1753
1753
  const [path, queryString] = url?.split('?');
1754
- const queryParams = SdUtilities$1.parseQueryParams(queryString);
1754
+ const queryParams = Utilities.parseQueryParams(queryString);
1755
1755
  this.router.navigate([path], { queryParams });
1756
1756
  }
1757
1757
  };
1758
1758
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: DatetimeComponent, deps: [{ token: i1$3.Router }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
1759
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: DatetimeComponent, isStandalone: true, selector: "lib-datetime", inputs: { setVariables: "setVariables", form: "form", _entity: ["entity", "_entity"], _component: ["component", "_component"], _disabled: ["disabled", "_disabled"], _required: ["required", "_required"], _viewed: ["viewed", "_viewed"] }, ngImport: i0, template: "@if (component && entity) {\r\n @if (subtype === 'date') {\r\n <sd-date\r\n [form]=\"form\"\r\n [label]=\"component.label || component.key\"\r\n [helperText]=\"component.helperText\"\r\n [name]=\"component.key\"\r\n [autoId]=\"component.key\"\r\n [(model)]=\"entity[component.key!]\"\r\n [required]=\"required\"\r\n [min]=\"component.validate?.min\"\r\n [max]=\"component.validate?.max\"\r\n [disabled]=\"disabled\"\r\n [viewed]=\"viewed || component.properties?.viewed\"></sd-date>\r\n } @else {\r\n <sd-datetime\r\n [form]=\"form\"\r\n [label]=\"component.label || component.key\"\r\n [helperText]=\"component.helperText\"\r\n [name]=\"component.key\"\r\n [autoId]=\"component.key\"\r\n [(model)]=\"entity[component.key!]\"\r\n [required]=\"required\"\r\n [min]=\"component.validate?.min\"\r\n [max]=\"component.validate?.max\"\r\n [disabled]=\"disabled\"\r\n [viewed]=\"viewed || component.properties?.viewed\"></sd-datetime>\r\n }\r\n}\r\n", styles: [":host{display:contents;width:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: SdDate, selector: "sd-date", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "hideInlineError", "required", "disabled", "viewed", "inlineError", "hyperlink", "appearance", "floatLabel", "min", "minDate", "max", "maxDate", "model"], outputs: ["modelChange", "sdChange", "sdFocus"] }, { kind: "component", type: SdDatetime, selector: "sd-datetime", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "hideInlineError", "required", "disabled", "viewed", "showSeconds", "inlineError", "hyperlink", "appearance", "floatLabel", "min", "minDate", "max", "maxDate", "model"], outputs: ["modelChange", "sdChange", "sdFocus"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1759
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: DatetimeComponent, isStandalone: true, selector: "lib-datetime", inputs: { setVariables: "setVariables", form: "form", _entity: ["entity", "_entity"], _component: ["component", "_component"], _disabled: ["disabled", "_disabled"], _required: ["required", "_required"], _viewed: ["viewed", "_viewed"] }, ngImport: i0, template: "@if (component && entity) {\r\n @if (subtype === 'date') {\r\n <sd-date\r\n [form]=\"form\"\r\n [label]=\"component.label || component.key\"\r\n [helperText]=\"component.helperText\"\r\n [name]=\"component.key\"\r\n [autoId]=\"component.key\"\r\n [(model)]=\"entity[component.key!]\"\r\n [required]=\"required\"\r\n [min]=\"component.validate?.min\"\r\n [max]=\"component.validate?.max\"\r\n [disabled]=\"disabled\"\r\n [viewed]=\"viewed || component.properties?.viewed\"></sd-date>\r\n } @else {\r\n <sd-datetime\r\n [form]=\"form\"\r\n [label]=\"component.label || component.key\"\r\n [helperText]=\"component.helperText\"\r\n [name]=\"component.key\"\r\n [autoId]=\"component.key\"\r\n [(model)]=\"entity[component.key!]\"\r\n [required]=\"required\"\r\n [min]=\"component.validate?.min\"\r\n [max]=\"component.validate?.max\"\r\n [disabled]=\"disabled\"\r\n [viewed]=\"viewed || component.properties?.viewed\"></sd-datetime>\r\n }\r\n}\r\n", styles: [":host{display:contents;width:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: SdDate, selector: "sd-date", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "hideInlineError", "required", "disabled", "viewed", "clearable", "inlineError", "hyperlink", "appearance", "floatLabel", "min", "minDate", "max", "maxDate", "model"], outputs: ["modelChange", "sdChange", "sdFocus"] }, { kind: "component", type: SdDatetime, selector: "sd-datetime", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "hideInlineError", "required", "disabled", "viewed", "showSeconds", "clearable", "inlineError", "hyperlink", "appearance", "floatLabel", "min", "minDate", "max", "maxDate", "model"], outputs: ["modelChange", "sdChange", "sdFocus"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1760
1760
  }
1761
1761
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: DatetimeComponent, decorators: [{
1762
1762
  type: Component,
@@ -1892,7 +1892,7 @@ class SelectComponent {
1892
1892
  }
1893
1893
  ngAfterViewInit() {
1894
1894
  this.#subscription.add(this.form.valueChanges.pipe(debounceTime(500), startWith(this.form.value)).subscribe(values => {
1895
- const hashedValues = SdUtilities$1.hash(values);
1895
+ const hashedValues = Utilities.hash(values);
1896
1896
  if (this.hashedValues !== hashedValues) {
1897
1897
  this.hashedValues = hashedValues;
1898
1898
  this.#inputChanges.next();
@@ -1903,7 +1903,7 @@ class SelectComponent {
1903
1903
  if (!this.viewed && this.component && !this.component?.properties?.viewed) {
1904
1904
  const values = { ...this.entity, ...this.form.value };
1905
1905
  const query = this.#generateQuery(this.component?.properties?.query, values);
1906
- const hashedQuery = SdUtilities$1.hash(query);
1906
+ const hashedQuery = Utilities.hash(query);
1907
1907
  // Nếu query có thay đổi thì thực hiện gán lại items
1908
1908
  if (hashedQuery !== this.#hashedQuery) {
1909
1909
  this.#hashedQuery = hashedQuery;
@@ -1956,12 +1956,12 @@ class SelectComponent {
1956
1956
  }
1957
1957
  else {
1958
1958
  const [path, queryString] = url?.split('?');
1959
- const queryParams = SdUtilities$1.parseQueryParams(queryString);
1959
+ const queryParams = Utilities.parseQueryParams(queryString);
1960
1960
  this.router.navigate([path], { queryParams });
1961
1961
  }
1962
1962
  };
1963
1963
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SelectComponent, deps: [{ token: i1$3.Router }, { token: i0.ChangeDetectorRef }, { token: FormGenericService }], target: i0.ɵɵFactoryTarget.Component });
1964
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: SelectComponent, isStandalone: true, selector: "lib-select", inputs: { setVariables: "setVariables", _form: ["form", "_form"], _entity: ["entity", "_entity"], _component: ["component", "_component"], _disabled: ["disabled", "_disabled"], _required: ["required", "_required"], _viewed: ["viewed", "_viewed"], _validator: ["validator", "_validator"] }, ngImport: i0, template: "@if (entity && component) {\r\n <sd-select\r\n [form]=\"form\"\r\n [label]=\"component.label || component.key\"\r\n [name]=\"component.key\"\r\n [autoId]=\"component.key\"\r\n [(model)]=\"entity[component.key]\"\r\n [required]=\"required\"\r\n [validator]=\"validator\"\r\n [disabled]=\"disabled\"\r\n [items]=\"items\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n disabledField=\"disabled\"\r\n (sdChange)=\"onChanges()\"\r\n [multiple]=\"component.properties?.multiple\"\r\n [viewed]=\"viewed || component.properties?.viewed\"\r\n [hyperlink]=\"component.properties?.hyperlink | hyperlink: entity\"></sd-select>\r\n}\r\n", styles: [":host{display:contents;width:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: SdSelect, selector: "sd-select", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "valueField", "displayField", "disabledField", "cacheChecksum", "limit", "hyperlink", "minWidthPanel", "hideInlineError", "required", "disabled", "viewed", "multiple", "validator", "inlineError", "appearance", "floatLabel", "items", "model"], outputs: ["modelChange", "sdChange", "sdSelection"] }, { kind: "pipe", type: HyperlinkPipe, name: "hyperlink" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1964
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: SelectComponent, isStandalone: true, selector: "lib-select", inputs: { setVariables: "setVariables", _form: ["form", "_form"], _entity: ["entity", "_entity"], _component: ["component", "_component"], _disabled: ["disabled", "_disabled"], _required: ["required", "_required"], _viewed: ["viewed", "_viewed"], _validator: ["validator", "_validator"] }, ngImport: i0, template: "@if (entity && component) {\r\n <sd-select\r\n [form]=\"form\"\r\n [label]=\"component.label || component.key\"\r\n [name]=\"component.key\"\r\n [autoId]=\"component.key\"\r\n [(model)]=\"entity[component.key]\"\r\n [required]=\"required\"\r\n [validator]=\"validator\"\r\n [disabled]=\"disabled\"\r\n [items]=\"items\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n disabledField=\"disabled\"\r\n (sdChange)=\"onChanges()\"\r\n [multiple]=\"component.properties?.multiple\"\r\n [viewed]=\"viewed || component.properties?.viewed\"\r\n [hyperlink]=\"component.properties?.hyperlink | hyperlink: entity\"></sd-select>\r\n}\r\n", styles: [":host{display:contents;width:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: SdSelect, selector: "sd-select", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "valueField", "displayField", "disabledField", "cacheChecksum", "limit", "hyperlink", "minWidthPanel", "hideInlineError", "required", "disabled", "viewed", "multiple", "clearable", "validator", "inlineError", "appearance", "floatLabel", "items", "model"], outputs: ["modelChange", "sdChange", "sdSelection"] }, { kind: "pipe", type: HyperlinkPipe, name: "hyperlink" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1965
1965
  }
1966
1966
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SelectComponent, decorators: [{
1967
1967
  type: Component,
@@ -2090,12 +2090,12 @@ class RadioComponent {
2090
2090
  }
2091
2091
  else {
2092
2092
  const [path, queryString] = url?.split('?');
2093
- const queryParams = SdUtilities$1.parseQueryParams(queryString);
2093
+ const queryParams = Utilities.parseQueryParams(queryString);
2094
2094
  this.router.navigate([path], { queryParams });
2095
2095
  }
2096
2096
  };
2097
2097
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: RadioComponent, deps: [{ token: i1$3.Router }, { token: i0.ChangeDetectorRef }, { token: FormGenericService }], target: i0.ɵɵFactoryTarget.Component });
2098
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: RadioComponent, isStandalone: true, selector: "lib-radio", inputs: { setVariables: "setVariables", _form: ["form", "_form"], _entity: ["entity", "_entity"], _component: ["component", "_component"], _disabled: ["disabled", "_disabled"], _required: ["required", "_required"], _viewed: ["viewed", "_viewed"] }, ngImport: i0, template: "@if (entity && component) {\r\n <sd-radio\r\n [form]=\"form\"\r\n [label]=\"component.label || component.key\"\r\n [name]=\"component.key\"\r\n [autoId]=\"component.key\"\r\n [(model)]=\"entity[component.key!]\"\r\n [required]=\"required\"\r\n [disabled]=\"disabled\"\r\n [items]=\"items\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n [display]=\"component.properties?.direction\"\r\n [viewed]=\"viewed || component.properties?.viewed\"\r\n [hyperlink]=\"component.properties?.hyperlink | hyperlink: entity\"></sd-radio>\r\n}\r\n", styles: [":host{display:contents;width:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: SdRadio, selector: "sd-radio", inputs: ["autoId", "name", "form", "label", "placeholder", "display", "model", "items", "valueField", "displayField", "required", "inlineError", "disabled", "viewed", "hyperlink"], outputs: ["modelChange", "sdChange", "sdSelection"] }, { kind: "pipe", type: HyperlinkPipe, name: "hyperlink" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2098
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: RadioComponent, isStandalone: true, selector: "lib-radio", inputs: { setVariables: "setVariables", _form: ["form", "_form"], _entity: ["entity", "_entity"], _component: ["component", "_component"], _disabled: ["disabled", "_disabled"], _required: ["required", "_required"], _viewed: ["viewed", "_viewed"] }, ngImport: i0, template: "@if (entity && component) {\r\n <sd-radio\r\n [form]=\"form\"\r\n [label]=\"component.label || component.key\"\r\n [name]=\"component.key\"\r\n [autoId]=\"component.key\"\r\n [(model)]=\"entity[component.key!]\"\r\n [required]=\"required\"\r\n [disabled]=\"disabled\"\r\n [items]=\"items\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n [display]=\"component.properties?.direction\"\r\n [viewed]=\"viewed || component.properties?.viewed\"\r\n [hyperlink]=\"component.properties?.hyperlink | hyperlink: entity\"></sd-radio>\r\n}\r\n", styles: [":host{display:contents;width:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: SdRadio, selector: "sd-radio", inputs: ["autoId", "name", "form", "label", "placeholder", "display", "items", "valueField", "displayField", "required", "inlineError", "disabled", "viewed", "color", "hyperlink", "model"], outputs: ["modelChange", "sdChange", "sdSelection"] }, { kind: "pipe", type: HyperlinkPipe, name: "hyperlink" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2099
2099
  }
2100
2100
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: RadioComponent, decorators: [{
2101
2101
  type: Component,
@@ -2569,7 +2569,7 @@ class TableComponent {
2569
2569
  }
2570
2570
  };
2571
2571
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: TableComponent, deps: [{ token: i0.ChangeDetectorRef }, { token: FormGenericService }], target: i0.ɵɵFactoryTarget.Component });
2572
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: TableComponent, isStandalone: true, selector: "lib-table", inputs: { setVariables: "setVariables", form: "form", _component: ["component", "_component"], _required: ["required", "_required"], _viewed: ["viewed", "_viewed"], _model: ["model", "_model"] }, outputs: { modelChange: "modelChange" }, viewQueries: [{ propertyName: "table", first: true, predicate: SdTable, descendants: true }, { propertyName: "sideDrawer", first: true, predicate: SdSideDrawer, descendants: true }, { propertyName: "uploadFiles", predicate: SdUploadFile, descendants: true }], ngImport: i0, template: "@if(component && tableOption) {\r\n <div style=\"width: 100%\">\r\n <sd-table [option]=\"tableOption\">\r\n @for(key of imageKeys; track key) {\r\n <ng-template [sdTableCellDef]=\"key\" let-item=\"item\">\r\n <sd-upload-file\r\n type=\"image\"\r\n [model]=\"item[key]\"\r\n [args]=\"getArgs(key)\"\r\n (modelChange)=\"onFileUploadChange(item, key, $event)\"\r\n align=\"left\"\r\n disabled\r\n previewWidth=\"50px\"\r\n previewHeight=\"50px\"></sd-upload-file>\r\n </ng-template>\r\n }\r\n @for(key of fileKeys; track key) {\r\n <ng-template [sdTableCellDef]=\"key\" let-item=\"item\">\r\n <sd-upload-file\r\n [model]=\"item[key]\"\r\n [args]=\"getArgs(key)\"\r\n (modelChange)=\"onFileUploadChange(item, key, $event)\"\r\n align=\"left\"\r\n disabled\r\n previewWidth=\"50px\"\r\n previewHeight=\"50px\"></sd-upload-file>\r\n </ng-template>\r\n }\r\n </sd-table>\r\n @if(!viewed && !component.properties?.viewed) {\r\n <sd-button\r\n prefixIcon=\"add\"\r\n color=\"primary\"\r\n (click)=\"onDetail()\"\r\n [title]=\"component.properties?.titleButtonCreate || ('core.component.form-builder.add-new' | translate)\"></sd-button>\r\n }\r\n @if(!viewed && !component.properties?.viewed) {\r\n <sd-side-drawer [title]=\"'core.component.form-builder.detail' | translate\" width=\"500px\">\r\n <div sdTitle>\r\n <span>{{ 'core.component.form-builder.create-update' | translate }}</span>\r\n </div>\r\n <div sdBody class=\"c-table-create-update\">\r\n @if(!!component.columns?.length) {\r\n <div class=\"row mx-0\">\r\n @for(column of component.columns;track column.key) {\r\n @let validate = formRenderColumn[column.key].validate;\r\n <div class=\"col-12 px-0 mt-16\">\r\n @if(column.type === 'string') {\r\n <sd-input\r\n [form]=\"form\"\r\n [label]=\"column.label\"\r\n [(model)]=\"row[column.key]\"\r\n [required]=\"validate?.required\"\r\n [pattern]=\"column.validate?.pattern\"\r\n [patternErrorMessage]=\"column.validate?.patternErrorMessage\"></sd-input>\r\n }@else if(column.type === 'number') {\r\n <sd-input-number\r\n [form]=\"form\"\r\n [label]=\"column.label\"\r\n [(model)]=\"row[column.key]\"\r\n [required]=\"validate?.required\"\r\n [min]=\"column.validate?.min\"></sd-input-number>\r\n }@else if(column.type === 'radio') {\r\n <sd-radio\r\n [form]=\"form\"\r\n [label]=\"column.label\"\r\n [name]=\"column.key\"\r\n [items]=\"column.values\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n [required]=\"validate?.required\"></sd-radio>\r\n }@else if(column.type === 'values') {\r\n <sd-autocomplete\r\n [form]=\"form\"\r\n [label]=\"column.label\"\r\n [(model)]=\"row[column.key]\"\r\n [items]=\"columnValues[column.key]\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n [required]=\"validate?.required\"></sd-autocomplete>\r\n }@else if(column.type === 'date') {\r\n <sd-date\r\n [form]=\"form\"\r\n [label]=\"column.label\"\r\n [(model)]=\"row[column.key]\"\r\n [required]=\"validate?.required\"></sd-date>\r\n }@else if(column.type === 'datetime') {\r\n <sd-datetime\r\n [form]=\"form\"\r\n [label]=\"column.label\"\r\n [(model)]=\"row[column.key]\"\r\n [required]=\"validate?.required\"></sd-datetime>\r\n }@else if(column.type === 'image') {\r\n <sd-upload-file\r\n [label]=\"column.label\"\r\n type=\"image\"\r\n [(model)]=\"row[column.key]\"\r\n [args]=\"column.args\"\r\n previewWidth=\"50px\"\r\n previewHeight=\"50px\"\r\n align=\"left\"></sd-upload-file>\r\n }@else if (column.type === 'file') {\r\n <sd-upload-file\r\n [label]=\"column.label\"\r\n type=\"file\"\r\n [(model)]=\"row[column.key]\"\r\n [args]=\"column.args\"\r\n previewWidth=\"50px\"\r\n previewHeight=\"50px\"\r\n align=\"left\"></sd-upload-file>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n \r\n </div>\r\n <div sdFooter class=\"text-right pr-16\">\r\n <sd-button [title]=\"'core.component.form-builder.save' | translate\" prefixIcon=\"save\" color=\"primary\" type=\"fill\" (click)=\"onAccept()\"></sd-button>\r\n </div>\r\n </sd-side-drawer>\r\n }\r\n </div>\r\n}\r\n\r\n", styles: [":host{display:contents}.c-table-create-update{background:#fff;margin:-16px;padding:16px;height:calc(100vh - 97px)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: SdInput, selector: "sd-input", inputs: ["autoId", "name", "appearance", "floatLabel", "size", "form", "label", "helperText", "placeholder", "type", "hideInlineError", "blurOnEnter", "required", "readonly", "disabled", "viewed", "minlength", "maxlength", "pattern", "patternErrorMessage", "validator", "inlineError", "hyperlink", "tooltip", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "sdFocusForceBlur"] }, { kind: "component", type: SdInputNumber$1, selector: "sd-input-number", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "hideInlineError", "blurOnEnter", "required", "readonly", "disabled", "viewed", "type", "precision", "format", "min", "max", "validator", "inlineError", "hyperlink", "appearance", "floatLabel", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "sdFocusForceBlur"] }, { kind: "component", type: SdAutocomplete, selector: "sd-autocomplete", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "valueField", "displayField", "disabledField", "limit", "cacheChecksum", "hyperlink", "items", "hideInlineError", "addable", "required", "disabled", "viewed", "validator", "inlineError", "appearance", "model"], outputs: ["modelChange", "sdChange", "sdSelection", "sdAdd"] }, { kind: "component", type: SdUploadFile, selector: "sd-upload-file", inputs: ["autoId", "args", "label", "key", "description", "previewWidth", "previewHeight", "align", "upload", "details", "download", "imageValidator", "maxSize", "maxWidth", "maxHeight", "scaleToPixel", "form", "name", "required", "type", "helperText", "max", "maxOfImage", "extensions", "disabled", "model"], outputs: ["loaded", "filesChanged", "modelChange"] }, { kind: "component", type: SdTable, selector: "sd-table", inputs: ["autoId", "option"] }, { kind: "directive", type: SdTableCellDefDirective, selector: "[sdTableCellDef]", inputs: ["sdTableCellDef"] }, { kind: "component", type: SdSideDrawer, selector: "sd-side-drawer", inputs: ["title", "width", "hideClose", "disableBackdropClose", "drawerClass", "autoId"], outputs: ["sdClosed"] }, { kind: "component", type: SdButton, selector: "sd-button", inputs: ["autoId", "type", "color", "size", "fontSet", "title", "width", "tooltip", "prefixIcon", "suffixIcon", "disabled", "loading", "block", "htmlType"], outputs: ["click"] }, { kind: "component", type: SdRadio, selector: "sd-radio", inputs: ["autoId", "name", "form", "label", "placeholder", "display", "model", "items", "valueField", "displayField", "required", "inlineError", "disabled", "viewed", "hyperlink"], outputs: ["modelChange", "sdChange", "sdSelection"] }, { kind: "component", type: SdDate, selector: "sd-date", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "hideInlineError", "required", "disabled", "viewed", "inlineError", "hyperlink", "appearance", "floatLabel", "min", "minDate", "max", "maxDate", "model"], outputs: ["modelChange", "sdChange", "sdFocus"] }, { kind: "component", type: SdDatetime, selector: "sd-datetime", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "hideInlineError", "required", "disabled", "viewed", "showSeconds", "inlineError", "hyperlink", "appearance", "floatLabel", "min", "minDate", "max", "maxDate", "model"], outputs: ["modelChange", "sdChange", "sdFocus"] }, { kind: "pipe", type:
2572
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: TableComponent, isStandalone: true, selector: "lib-table", inputs: { setVariables: "setVariables", form: "form", _component: ["component", "_component"], _required: ["required", "_required"], _viewed: ["viewed", "_viewed"], _model: ["model", "_model"] }, outputs: { modelChange: "modelChange" }, viewQueries: [{ propertyName: "table", first: true, predicate: SdTable, descendants: true }, { propertyName: "sideDrawer", first: true, predicate: SdSideDrawer, descendants: true }, { propertyName: "uploadFiles", predicate: SdUploadFile, descendants: true }], ngImport: i0, template: "@if(component && tableOption) {\r\n <div style=\"width: 100%\">\r\n <sd-table [option]=\"tableOption\">\r\n @for(key of imageKeys; track key) {\r\n <ng-template [sdTableCellDef]=\"key\" let-item=\"item\">\r\n <sd-upload-file\r\n type=\"image\"\r\n [model]=\"item[key]\"\r\n [args]=\"getArgs(key)\"\r\n (modelChange)=\"onFileUploadChange(item, key, $event)\"\r\n align=\"left\"\r\n disabled\r\n previewWidth=\"50px\"\r\n previewHeight=\"50px\"></sd-upload-file>\r\n </ng-template>\r\n }\r\n @for(key of fileKeys; track key) {\r\n <ng-template [sdTableCellDef]=\"key\" let-item=\"item\">\r\n <sd-upload-file\r\n [model]=\"item[key]\"\r\n [args]=\"getArgs(key)\"\r\n (modelChange)=\"onFileUploadChange(item, key, $event)\"\r\n align=\"left\"\r\n disabled\r\n previewWidth=\"50px\"\r\n previewHeight=\"50px\"></sd-upload-file>\r\n </ng-template>\r\n }\r\n </sd-table>\r\n @if(!viewed && !component.properties?.viewed) {\r\n <sd-button\r\n prefixIcon=\"add\"\r\n color=\"primary\"\r\n (click)=\"onDetail()\"\r\n [title]=\"component.properties?.titleButtonCreate || ('core.component.form-builder.add-new' | translate)\"></sd-button>\r\n }\r\n @if(!viewed && !component.properties?.viewed) {\r\n <sd-side-drawer [title]=\"'core.component.form-builder.detail' | translate\" width=\"500px\">\r\n <div sdTitle>\r\n <span>{{ 'core.component.form-builder.create-update' | translate }}</span>\r\n </div>\r\n <div sdBody class=\"c-table-create-update\">\r\n @if(!!component.columns?.length) {\r\n <div class=\"row mx-0\">\r\n @for(column of component.columns;track column.key) {\r\n @let validate = formRenderColumn[column.key].validate;\r\n <div class=\"col-12 px-0 mt-16\">\r\n @if(column.type === 'string') {\r\n <sd-input\r\n [form]=\"form\"\r\n [label]=\"column.label\"\r\n [(model)]=\"row[column.key]\"\r\n [required]=\"validate?.required\"\r\n [pattern]=\"column.validate?.pattern\"\r\n [patternErrorMessage]=\"column.validate?.patternErrorMessage\"></sd-input>\r\n }@else if(column.type === 'number') {\r\n <sd-input-number\r\n [form]=\"form\"\r\n [label]=\"column.label\"\r\n [(model)]=\"row[column.key]\"\r\n [required]=\"validate?.required\"\r\n [min]=\"column.validate?.min\"></sd-input-number>\r\n }@else if(column.type === 'radio') {\r\n <sd-radio\r\n [form]=\"form\"\r\n [label]=\"column.label\"\r\n [name]=\"column.key\"\r\n [items]=\"column.values\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n [required]=\"validate?.required\"></sd-radio>\r\n }@else if(column.type === 'values') {\r\n <sd-autocomplete\r\n [form]=\"form\"\r\n [label]=\"column.label\"\r\n [(model)]=\"row[column.key]\"\r\n [items]=\"columnValues[column.key]\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n [required]=\"validate?.required\"></sd-autocomplete>\r\n }@else if(column.type === 'date') {\r\n <sd-date\r\n [form]=\"form\"\r\n [label]=\"column.label\"\r\n [(model)]=\"row[column.key]\"\r\n [required]=\"validate?.required\"></sd-date>\r\n }@else if(column.type === 'datetime') {\r\n <sd-datetime\r\n [form]=\"form\"\r\n [label]=\"column.label\"\r\n [(model)]=\"row[column.key]\"\r\n [required]=\"validate?.required\"></sd-datetime>\r\n }@else if(column.type === 'image') {\r\n <sd-upload-file\r\n [label]=\"column.label\"\r\n type=\"image\"\r\n [(model)]=\"row[column.key]\"\r\n [args]=\"column.args\"\r\n previewWidth=\"50px\"\r\n previewHeight=\"50px\"\r\n align=\"left\"></sd-upload-file>\r\n }@else if (column.type === 'file') {\r\n <sd-upload-file\r\n [label]=\"column.label\"\r\n type=\"file\"\r\n [(model)]=\"row[column.key]\"\r\n [args]=\"column.args\"\r\n previewWidth=\"50px\"\r\n previewHeight=\"50px\"\r\n align=\"left\"></sd-upload-file>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n \r\n </div>\r\n <div sdFooter class=\"text-right pr-16\">\r\n <sd-button [title]=\"'core.component.form-builder.save' | translate\" prefixIcon=\"save\" color=\"primary\" type=\"fill\" (click)=\"onAccept()\"></sd-button>\r\n </div>\r\n </sd-side-drawer>\r\n }\r\n </div>\r\n}\r\n\r\n", styles: [":host{display:contents}.c-table-create-update{background:#fff;margin:-16px;padding:16px;height:calc(100vh - 97px)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: SdInput, selector: "sd-input", inputs: ["autoId", "name", "appearance", "floatLabel", "size", "form", "label", "helperText", "placeholder", "type", "hideInlineError", "blurOnEnter", "required", "readonly", "disabled", "viewed", "minlength", "maxlength", "pattern", "patternErrorMessage", "validator", "inlineError", "hyperlink", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "cleared", "sdFocusForceBlur"] }, { kind: "component", type: SdInputNumber$1, selector: "sd-input-number", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "hideInlineError", "blurOnEnter", "required", "readonly", "disabled", "viewed", "type", "precision", "format", "min", "max", "validator", "inlineError", "hyperlink", "appearance", "floatLabel", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "cleared", "sdFocusForceBlur"] }, { kind: "component", type: SdAutocomplete, selector: "sd-autocomplete", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "valueField", "displayField", "disabledField", "limit", "cacheChecksum", "hyperlink", "items", "hideInlineError", "addable", "required", "disabled", "viewed", "clearable", "validator", "inlineError", "appearance", "model"], outputs: ["modelChange", "sdChange", "sdSelection", "sdAdd"] }, { kind: "component", type: SdUploadFile, selector: "sd-upload-file", inputs: ["autoId", "args", "label", "key", "description", "previewWidth", "previewHeight", "align", "upload", "details", "download", "imageValidator", "maxSize", "maxWidth", "maxHeight", "scaleToPixel", "form", "name", "required", "type", "helperText", "max", "maxOfImage", "extensions", "disabled", "model"], outputs: ["loaded", "filesChanged", "modelChange"] }, { kind: "component", type: SdTable, selector: "sd-table", inputs: ["autoId", "option"] }, { kind: "directive", type: SdTableCellDefDirective, selector: "[sdTableCellDef]", inputs: ["sdTableCellDef"] }, { kind: "component", type: SdSideDrawer, selector: "sd-side-drawer", inputs: ["title", "width", "hideClose", "disableBackdropClose", "drawerClass", "autoId"], outputs: ["sdClosed"] }, { kind: "component", type: SdButton, selector: "sd-button", inputs: ["autoId", "type", "color", "size", "fontSet", "title", "width", "tooltip", "prefixIcon", "suffixIcon", "disabled", "loading", "block", "htmlType"], outputs: ["click"] }, { kind: "component", type: SdRadio, selector: "sd-radio", inputs: ["autoId", "name", "form", "label", "placeholder", "display", "items", "valueField", "displayField", "required", "inlineError", "disabled", "viewed", "color", "hyperlink", "model"], outputs: ["modelChange", "sdChange", "sdSelection"] }, { kind: "component", type: SdDate, selector: "sd-date", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "hideInlineError", "required", "disabled", "viewed", "clearable", "inlineError", "hyperlink", "appearance", "floatLabel", "min", "minDate", "max", "maxDate", "model"], outputs: ["modelChange", "sdChange", "sdFocus"] }, { kind: "component", type: SdDatetime, selector: "sd-datetime", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "hideInlineError", "required", "disabled", "viewed", "showSeconds", "clearable", "inlineError", "hyperlink", "appearance", "floatLabel", "min", "minDate", "max", "maxDate", "model"], outputs: ["modelChange", "sdChange", "sdFocus"] }, { kind: "pipe", type:
2573
2573
  // Pipe cho phần viewed
2574
2574
  TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2575
2575
  }
@@ -2729,7 +2729,7 @@ class HtmlComponent {
2729
2729
  else {
2730
2730
  // Nếu có queries thì lắng nghe thay đổi của form
2731
2731
  this.#subscription.add(this.form.valueChanges.pipe(debounceTime(500), startWith(this.form.value)).subscribe(values => {
2732
- const hashedValues = SdUtilities$1.hash(values);
2732
+ const hashedValues = Utilities.hash(values);
2733
2733
  if (this.hashedValues !== hashedValues) {
2734
2734
  this.hashedValues = hashedValues;
2735
2735
  this.#inputChanges.next();
@@ -2738,7 +2738,7 @@ class HtmlComponent {
2738
2738
  this.#subscription.add(this.#inputChanges.pipe(startWith('')).subscribe(async () => {
2739
2739
  const values = { ...this.entity, ...this.form.value };
2740
2740
  const query = this.#generateQuery(this.component?.properties?.query, values);
2741
- const hashedQuery = SdUtilities$1.hash(query);
2741
+ const hashedQuery = Utilities.hash(query);
2742
2742
  // Nếu query có thay đổi thì thực hiện gán lại items
2743
2743
  if (hashedQuery !== this.#hashedQuery) {
2744
2744
  this.#hashedQuery = hashedQuery;
@@ -2937,7 +2937,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImpo
2937
2937
 
2938
2938
  /* eslint-disable @typescript-eslint/no-explicit-any */
2939
2939
  /* eslint-disable @angular-eslint/no-input-rename */
2940
- class SdFormRender extends SdBaseSecureComponent {
2940
+ class SdFormRender {
2941
2941
  ref;
2942
2942
  formGenericConfiguration;
2943
2943
  formRenderItems = new QueryList();
@@ -2978,7 +2978,6 @@ class SdFormRender extends SdBaseSecureComponent {
2978
2978
  // Các component item của form-render sẽ lắng nghe thay đổi
2979
2979
  setVariables = new Subject();
2980
2980
  constructor(ref, formGenericConfiguration) {
2981
- super();
2982
2981
  this.ref = ref;
2983
2982
  this.formGenericConfiguration = formGenericConfiguration;
2984
2983
  }
@@ -3014,7 +3013,7 @@ class SdFormRender extends SdBaseSecureComponent {
3014
3013
  }
3015
3014
  }));
3016
3015
  this.#subscription.add(this.form.valueChanges.pipe(debounceTime$1(500), startWith$1(this.form.value)).subscribe(values => {
3017
- const hashedValues = SdUtilities$1.hash(values);
3016
+ const hashedValues = Utilities.hash(values);
3018
3017
  if (this.hashedValues !== hashedValues) {
3019
3018
  this.hashedValues = hashedValues;
3020
3019
  // Ở trạng thái view thì không có FormControl nên phải binding entity mới có dữ liệu cho formValue
@@ -3064,7 +3063,7 @@ class SdFormRender extends SdBaseSecureComponent {
3064
3063
  return messages;
3065
3064
  };
3066
3065
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdFormRender, deps: [{ token: i0.ChangeDetectorRef }, { token: SD_FORM_GENERIC_CONFIGURATION, optional: true }], target: i0.ɵɵFactoryTarget.Component });
3067
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", 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) {\r\n <div class=\"row mx-0\">\r\n @for (component of configuration.components; track component.id) {\r\n @let expression = hashedValues | whenExpression: component : formValue;\r\n @if (!expression.hidden) {\r\n @if (component.type === 'group') {\r\n @if (!!component.components.length) {\r\n <div class=\"col-12 mt-16\">\r\n <sd-section [title]=\"component.label\" [icon]=\"component.properties.icon\">\r\n <div class=\"row mx-0\">\r\n @for (childComponent of component.components; track childComponent.id) {\r\n @let childExpression = hashedValues | whenExpression: childComponent : formValue;\r\n @if (!childExpression.hidden) {\r\n <lib-item\r\n [setVariables]=\"setVariables\"\r\n [form]=\"form\"\r\n [entity]=\"entity\"\r\n [component]=\"childComponent\"\r\n [required]=\"childExpression.required\"\r\n [disabled]=\"childExpression.disabled\"\r\n [viewed]=\"viewed\"></lib-item>\r\n }\r\n }\r\n </div>\r\n </sd-section>\r\n </div>\r\n }\r\n } @else {\r\n <lib-item\r\n [setVariables]=\"setVariables\"\r\n [form]=\"form\"\r\n [entity]=\"entity\"\r\n [component]=\"component\"\r\n [required]=\"expression.required\"\r\n [disabled]=\"expression.disabled\"\r\n [viewed]=\"viewed\"></lib-item>\r\n }\r\n }\r\n }\r\n <variable [variables]=\"configuration.variables\" [setVariables]=\"setVariables\" [form]=\"form\" [entity]=\"entity\" ></variable>\r\n </div>\r\n}\r\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: "pipe", type: WhenExpressionPipe, name: "whenExpression" }, { kind: "component", type: VariableComponent, selector: "variable", inputs: ["variables", "setVariables", "form", "entity"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3066
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", 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) {\r\n <div class=\"row mx-0\">\r\n @for (component of configuration.components; track component.id) {\r\n @let expression = hashedValues | whenExpression: component : formValue;\r\n @if (!expression.hidden) {\r\n @if (component.type === 'group') {\r\n @if (!!component.components.length) {\r\n <div class=\"col-12 mt-16\">\r\n <sd-section [title]=\"component.label\" [icon]=\"component.properties.icon\">\r\n <div class=\"row mx-0\">\r\n @for (childComponent of component.components; track childComponent.id) {\r\n @let childExpression = hashedValues | whenExpression: childComponent : formValue;\r\n @if (!childExpression.hidden) {\r\n <lib-item\r\n [setVariables]=\"setVariables\"\r\n [form]=\"form\"\r\n [entity]=\"entity\"\r\n [component]=\"childComponent\"\r\n [required]=\"childExpression.required\"\r\n [disabled]=\"childExpression.disabled\"\r\n [viewed]=\"viewed\"></lib-item>\r\n }\r\n }\r\n </div>\r\n </sd-section>\r\n </div>\r\n }\r\n } @else {\r\n <lib-item\r\n [setVariables]=\"setVariables\"\r\n [form]=\"form\"\r\n [entity]=\"entity\"\r\n [component]=\"component\"\r\n [required]=\"expression.required\"\r\n [disabled]=\"expression.disabled\"\r\n [viewed]=\"viewed\"></lib-item>\r\n }\r\n }\r\n }\r\n <variable [variables]=\"configuration.variables\" [setVariables]=\"setVariables\" [form]=\"form\" [entity]=\"entity\" ></variable>\r\n </div>\r\n}\r\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: "pipe", type: WhenExpressionPipe, name: "whenExpression" }, { kind: "component", type: VariableComponent, selector: "variable", inputs: ["variables", "setVariables", "form", "entity"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3068
3067
  }
3069
3068
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdFormRender, decorators: [{
3070
3069
  type: Component,
@@ -3128,13 +3127,13 @@ class ExpressionBuilderComponent {
3128
3127
  this.attribute = ArrayUtilities.toObject('value', this.attributes);
3129
3128
  }
3130
3129
  expression = {
3131
- key: SdUtilities.randomId(),
3130
+ key: Utilities.randomId(),
3132
3131
  type: 'combinator',
3133
3132
  combinator: '&&',
3134
3133
  conditions: [],
3135
3134
  };
3136
3135
  model = {
3137
- key: SdUtilities.randomId(),
3136
+ key: Utilities.randomId(),
3138
3137
  type: 'combinator',
3139
3138
  combinator: '&&',
3140
3139
  conditions: [],
@@ -3144,7 +3143,7 @@ class ExpressionBuilderComponent {
3144
3143
  this.model = model;
3145
3144
  }
3146
3145
  this.model = this.model || {
3147
- key: SdUtilities.randomId(),
3146
+ key: Utilities.randomId(),
3148
3147
  type: 'combinator',
3149
3148
  combinator: '&&',
3150
3149
  conditions: [],
@@ -3167,7 +3166,7 @@ class ExpressionBuilderComponent {
3167
3166
  };
3168
3167
  addCondition = (conditions) => {
3169
3168
  conditions.push({
3170
- key: SdUtilities.randomId(),
3169
+ key: Utilities.randomId(),
3171
3170
  type: 'condition',
3172
3171
  field: undefined,
3173
3172
  operator: 'EQUAL',
@@ -3178,7 +3177,7 @@ class ExpressionBuilderComponent {
3178
3177
  };
3179
3178
  addCombinator = (conditions) => {
3180
3179
  conditions.push({
3181
- key: SdUtilities.randomId(),
3180
+ key: Utilities.randomId(),
3182
3181
  type: 'combinator',
3183
3182
  combinator: '&&',
3184
3183
  conditions: [],
@@ -3219,7 +3218,7 @@ class ExpressionBuilderComponent {
3219
3218
  }
3220
3219
  };
3221
3220
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: ExpressionBuilderComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
3222
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: ExpressionBuilderComponent, isStandalone: true, selector: "expression-builder", inputs: { label: "label", _attributes: ["attributes", "_attributes"], _model: ["model", "_model"] }, outputs: { modelChange: "modelChange", sdChange: "sdChange", edit: "edit" }, viewQueries: [{ propertyName: "modal", first: true, predicate: SdModal, descendants: true }], ngImport: i0, template: "<!--\r\n FEEL-style expression trigger pill (M3 redesign).\r\n B\u1EA5m v\u00E0o pill m\u1EDF modal builder hi\u1EC7n t\u1EA1i (onEdit).\r\n-->\r\n<div class=\"fb-field\">\r\n @if (label) {\r\n <div class=\"fb-field__label\">{{ label }}</div>\r\n }\r\n @let exprText = model | expressionQuery;\r\n <div class=\"fb-expr\" (click)=\"onEdit()\" role=\"button\" tabindex=\"0\">\r\n <span class=\"fb-expr__badge\">FEEL</span>\r\n @if (exprText) {\r\n <span class=\"fb-expr__code mono\">{{ exprText }}</span>\r\n } @else {\r\n <span class=\"empty\">{{ 'core.component.form-builder.set-expression' | translate }}</span>\r\n }\r\n <button type=\"button\" class=\"fb-icon-btn\" (click)=\"onEdit(); $event.stopPropagation()\" aria-label=\"edit expression\">\r\n <span class=\"msi\">functions</span>\r\n </button>\r\n </div>\r\n</div>\r\n@if (expression) {\r\n <sd-modal [title]=\"'core.component.form-builder.set-expression' | translate\">\r\n <div class=\"c-builder\">\r\n <div class=\"d-flex justify-content-end\">\r\n <sd-button\r\n prefixIcon=\"calculate\"\r\n class=\"mr-4\"\r\n [title]=\"'core.component.form-builder.condition-group' | translate\"\r\n color=\"primary\"\r\n (click)=\"addCombinator(expression.conditions)\"></sd-button>\r\n <sd-button\r\n prefixIcon=\"add\"\r\n [title]=\"'core.component.form-builder.condition' | translate\"\r\n color=\"primary\"\r\n (click)=\"addCondition(expression.conditions)\"\r\n type=\"fill\"></sd-button>\r\n </div>\r\n <!-- C\u1EA5p 1 -->\r\n @for (conditionLv1 of expression.conditions; track conditionLv1.key; let idxLv1 = $index) {\r\n <div class=\"d-flex align-items-end py-8\" style=\"gap: 8px\">\r\n <div class=\"c-condition\">\r\n <!-- D\u00F2ng 1 ch\u1EC9 hi\u1EC7n WHERE -->\r\n @if (idxLv1 === 0) {\r\n <div class=\"c-where\">WHERE</div>\r\n }\r\n <!-- D\u00F2ng 2 hi\u1EC3n th\u1ECB combinator -->\r\n @if (idxLv1 === 1) {\r\n <div class=\"c-select\">\r\n <sd-select\r\n size=\"sm\"\r\n [(model)]=\"expression.combinator\"\r\n [items]=\"[\r\n { value: '&&', display: 'AND' },\r\n { value: '||', display: 'OR' }\r\n ]\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n required\r\n hideInlineError=\"\"></sd-select>\r\n </div>\r\n }\r\n </div>\r\n @if (conditionLv1.type === 'condition') {\r\n <sd-autocomplete\r\n size=\"sm\"\r\n [form]=\"form\"\r\n style=\"width: 200px\"\r\n [(model)]=\"conditionLv1.field\"\r\n [items]=\"attributes\"\r\n (sdChange)=\"onChangeAttribute(conditionLv1)\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n required\r\n hideInlineError></sd-autocomplete>\r\n @let fieldAttribute = attribute[conditionLv1.field!];\r\n <sd-select\r\n size=\"sm\"\r\n [form]=\"form\"\r\n style=\"width: 100px\"\r\n [(model)]=\"conditionLv1.operator\"\r\n [items]=\"attributeOperators[fieldAttribute?.type || 'string']\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n [disabled]=\"!fieldAttribute?.type\"\r\n required\r\n hideInlineError></sd-select>\r\n @if (\r\n !!fieldAttribute?.type && conditionLv1.operator && conditionLv1.operator !== 'NULL' && conditionLv1.operator !== 'NOT_NULL'\r\n ) {\r\n @if (fieldAttribute.type === 'string') {\r\n <sd-input size=\"sm\" [form]=\"form\" style=\"flex: 1\" [(model)]=\"conditionLv1.value\" required hideInlineError></sd-input>\r\n } @else if (fieldAttribute.type === 'number') {\r\n <sd-input-number size=\"sm\" [form]=\"form\" style=\"flex: 1\" [(model)]=\"conditionLv1.value\" hideInlineError></sd-input-number>\r\n <sd-autocomplete\r\n size=\"sm\"\r\n [form]=\"form\"\r\n style=\"flex: 1\"\r\n [(model)]=\"conditionLv1.value\"\r\n [items]=\"numberAttributes\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n hideInlineError></sd-autocomplete>\r\n } @else if (fieldAttribute.type === 'datetime' && conditionLv1.dayInfo) {\r\n <sd-select\r\n size=\"sm\"\r\n [placeholder]=\"'core.component.form-builder.time' | translate\"\r\n [form]=\"form\"\r\n style=\"width: 150px\"\r\n [items]=\"types\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n [(model)]=\"conditionLv1.dayInfo.type\"\r\n (sdChange)=\"onChangeDateInfo(conditionLv1)\"\r\n required\r\n hideInlineError>\r\n </sd-select>\r\n @if (conditionLv1.dayInfo.type === 'RELATED') {\r\n <sd-input-number\r\n size=\"sm\"\r\n [form]=\"form\"\r\n style=\"width: 80px\"\r\n [(model)]=\"conditionLv1.dayInfo.relatedValue\"\r\n (sdChange)=\"onChangeDateInfo(conditionLv1)\"\r\n [min]=\"1\"\r\n required\r\n hideInlineError>\r\n </sd-input-number>\r\n <sd-select\r\n size=\"sm\"\r\n [form]=\"form\"\r\n style=\"flex: 1\"\r\n [items]=\"previouses\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n [(model)]=\"conditionLv1.dayInfo.related\"\r\n (sdChange)=\"onChangeDateInfo(conditionLv1)\"\r\n required\r\n hideInlineError>\r\n </sd-select>\r\n } @else if (conditionLv1.dayInfo.type === 'DATETIME') {\r\n <sd-datetime\r\n size=\"sm\"\r\n [form]=\"form\"\r\n style=\"flex: 1\"\r\n [(model)]=\"conditionLv1.value\"\r\n (sdChange)=\"onChangeDateInfo(conditionLv1)\"\r\n required\r\n hideInlineError>\r\n </sd-datetime>\r\n } @else if (conditionLv1.dayInfo.type === 'ATTRIBUTE') {\r\n <sd-autocomplete\r\n size=\"sm\"\r\n [form]=\"form\"\r\n style=\"flex: 1\"\r\n [(model)]=\"conditionLv1.value\"\r\n [items]=\"dateAttributes\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n hideInlineError></sd-autocomplete>\r\n }\r\n } @else if (fieldAttribute.type === 'boolean') {\r\n <sd-select\r\n size=\"sm\"\r\n [form]=\"form\"\r\n style=\"flex: 1\"\r\n [(model)]=\"conditionLv1.value\"\r\n [items]=\"[\r\n { value: true, display: fieldAttribute.displayOnTrue },\r\n { value: false, display: fieldAttribute.displayOnFalse }\r\n ]\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n required\r\n hideInlineError></sd-select>\r\n } @else if (fieldAttribute.type === 'values') {\r\n <sd-autocomplete\r\n size=\"sm\"\r\n [form]=\"form\"\r\n style=\"flex: 1\"\r\n [(model)]=\"conditionLv1.value\"\r\n [items]=\"fieldAttribute.values\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n required\r\n hideInlineError></sd-autocomplete>\r\n }\r\n }\r\n <sd-button class=\"ml-auto\" prefixIcon=\"delete\" [tooltip]=\"'core.component.form-builder.delete' | translate\" (click)=\"remove(expression.conditions, idxLv1)\"></sd-button>\r\n } @else if (conditionLv1.type === 'combinator') {\r\n <div class=\"c-builder\">\r\n <div class=\"d-flex justify-content-end\">\r\n <sd-button class=\"mr-4\" prefixIcon=\"delete\" [tooltip]=\"'core.component.form-builder.delete' | translate\" (click)=\"remove(expression.conditions, idxLv1)\"></sd-button>\r\n <sd-button\r\n prefixIcon=\"add\"\r\n [title]=\"'core.component.form-builder.condition' | translate\"\r\n size=\"sm\"\r\n color=\"primary\"\r\n (click)=\"addCondition(conditionLv1.conditions)\"\r\n type=\"fill\"></sd-button>\r\n </div>\r\n <!-- C\u1EA5p 2 -->\r\n @for (conditionLv2 of conditionLv1.conditions; track conditionLv2.key; let idxLv2 = $index) {\r\n <div class=\"d-flex align-items-end py-8\" style=\"gap: 8px\">\r\n <div class=\"c-condition\">\r\n <!-- D\u00F2ng 1 ch\u1EC9 hi\u1EC7n WHERE -->\r\n @if (idxLv2 === 0) {\r\n <div class=\"c-where\">WHERE</div>\r\n }\r\n <!-- D\u00F2ng 2 hi\u1EC3n th\u1ECB combinator -->\r\n @if (idxLv2 === 1) {\r\n <div class=\"c-select\">\r\n <sd-select\r\n size=\"sm\"\r\n [(model)]=\"conditionLv1.combinator\"\r\n [items]=\"[\r\n { value: '&&', display: 'AND' },\r\n { value: '||', display: 'OR' }\r\n ]\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n required\r\n hideInlineError=\"\"></sd-select>\r\n </div>\r\n }\r\n </div>\r\n @if (conditionLv2.type === 'condition') {\r\n <sd-autocomplete\r\n size=\"sm\"\r\n [form]=\"form\"\r\n style=\"width: 200px\"\r\n [(model)]=\"conditionLv2.field\"\r\n [items]=\"attributes\"\r\n (sdChange)=\"onChangeAttribute(conditionLv2)\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n required\r\n hideInlineError></sd-autocomplete>\r\n @let fieldAttribute = attribute[conditionLv2.field!];\r\n <sd-select\r\n size=\"sm\"\r\n [form]=\"form\"\r\n style=\"width: 100px\"\r\n [(model)]=\"conditionLv2.operator\"\r\n [items]=\"attributeOperators[fieldAttribute?.type || 'string']\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n [disabled]=\"!fieldAttribute?.type\"\r\n required\r\n hideInlineError></sd-select>\r\n @if (\r\n !!fieldAttribute?.type &&\r\n conditionLv2.operator &&\r\n conditionLv2.operator !== 'NULL' &&\r\n conditionLv2.operator !== 'NOT_NULL'\r\n ) {\r\n @if (fieldAttribute.type === 'string') {\r\n <sd-input\r\n size=\"sm\"\r\n [form]=\"form\"\r\n style=\"flex: 1\"\r\n [(model)]=\"conditionLv2.value\"\r\n required\r\n hideInlineError></sd-input>\r\n } @else if (fieldAttribute.type === 'number') {\r\n <sd-input-number\r\n size=\"sm\"\r\n [form]=\"form\"\r\n style=\"flex: 1\"\r\n [(model)]=\"conditionLv2.value\"\r\n hideInlineError></sd-input-number>\r\n <sd-autocomplete\r\n size=\"sm\"\r\n [form]=\"form\"\r\n style=\"flex: 1\"\r\n [(model)]=\"conditionLv2.value\"\r\n [items]=\"numberAttributes\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n hideInlineError></sd-autocomplete>\r\n } @else if (fieldAttribute.type === 'datetime' && conditionLv2.dayInfo) {\r\n <sd-select\r\n size=\"sm\"\r\n [form]=\"form\"\r\n [placeholder]=\"'core.component.form-builder.time' | translate\"\r\n style=\"width: 150px\"\r\n [items]=\"types\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n [(model)]=\"conditionLv2.dayInfo.type\"\r\n (sdChange)=\"onChangeDateInfo(conditionLv2)\"\r\n required\r\n hideInlineError>\r\n </sd-select>\r\n @if (conditionLv2.dayInfo.type === 'RELATED') {\r\n <sd-input-number\r\n size=\"sm\"\r\n [form]=\"form\"\r\n style=\"width: 80px\"\r\n [(model)]=\"conditionLv2.dayInfo.relatedValue\"\r\n (sdChange)=\"onChangeDateInfo(conditionLv2)\"\r\n [min]=\"1\"\r\n required\r\n hideInlineError>\r\n </sd-input-number>\r\n <sd-select\r\n size=\"sm\"\r\n [form]=\"form\"\r\n style=\"flex: 1\"\r\n [items]=\"previouses\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n [(model)]=\"conditionLv2.dayInfo.related\"\r\n (sdChange)=\"onChangeDateInfo(conditionLv2)\"\r\n required\r\n hideInlineError>\r\n </sd-select>\r\n } @else if (conditionLv2.dayInfo.type === 'DATETIME') {\r\n <sd-datetime\r\n size=\"sm\"\r\n [form]=\"form\"\r\n style=\"flex: 1\"\r\n [(model)]=\"conditionLv2.value\"\r\n (sdChange)=\"onChangeDateInfo(conditionLv2)\"\r\n required\r\n hideInlineError>\r\n </sd-datetime>\r\n } @else if (conditionLv2.dayInfo.type === 'ATTRIBUTE') {\r\n <sd-autocomplete\r\n size=\"sm\"\r\n [form]=\"form\"\r\n style=\"flex: 1\"\r\n [(model)]=\"conditionLv2.value\"\r\n [items]=\"dateAttributes\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n hideInlineError></sd-autocomplete>\r\n }\r\n } @else if (fieldAttribute.type === 'boolean') {\r\n <sd-select\r\n size=\"sm\"\r\n [form]=\"form\"\r\n style=\"flex: 1\"\r\n [(model)]=\"conditionLv2.value\"\r\n [items]=\"[\r\n { value: true, display: fieldAttribute.displayOnTrue },\r\n { value: false, display: fieldAttribute.displayOnFalse }\r\n ]\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n required\r\n hideInlineError></sd-select>\r\n } @else if (fieldAttribute.type === 'values') {\r\n <sd-autocomplete\r\n size=\"sm\"\r\n [form]=\"form\"\r\n style=\"flex: 1\"\r\n [(model)]=\"conditionLv2.value\"\r\n [items]=\"fieldAttribute.values\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n required\r\n hideInlineError></sd-autocomplete>\r\n }\r\n }\r\n <sd-button\r\n class=\"ml-auto\"\r\n prefixIcon=\"delete\"\r\n [tooltip]=\"'core.component.form-builder.delete' | translate\"\r\n (click)=\"remove(conditionLv1.conditions, idxLv1)\"></sd-button>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n <ng-container sdFooter>\r\n <sd-button prefixIcon=\"save\" [title]=\"'core.component.form-builder.confirm' | translate\" (click)=\"onAccept()\" color=\"primary\" type=\"fill\"></sd-button>\r\n </ng-container>\r\n </sd-modal>\r\n}\r\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", "tooltip", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "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", "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", "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", "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: ExpressionQueryPipe, name: "expressionQuery" }, { kind: "component", type: SdDatetime, selector: "sd-datetime", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "hideInlineError", "required", "disabled", "viewed", "showSeconds", "inlineError", "hyperlink", "appearance", "floatLabel", "min", "minDate", "max", "maxDate", "model"], outputs: ["modelChange", "sdChange", "sdFocus"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3221
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: ExpressionBuilderComponent, isStandalone: true, selector: "expression-builder", inputs: { label: "label", _attributes: ["attributes", "_attributes"], _model: ["model", "_model"] }, outputs: { modelChange: "modelChange", sdChange: "sdChange", edit: "edit" }, viewQueries: [{ propertyName: "modal", first: true, predicate: SdModal, descendants: true }], ngImport: i0, template: "<!--\r\n FEEL-style expression trigger pill (M3 redesign).\r\n B\u1EA5m v\u00E0o pill m\u1EDF modal builder hi\u1EC7n t\u1EA1i (onEdit).\r\n-->\r\n<div class=\"fb-field\">\r\n @if (label) {\r\n <div class=\"fb-field__label\">{{ label }}</div>\r\n }\r\n @let exprText = model | expressionQuery;\r\n <div class=\"fb-expr\" (click)=\"onEdit()\" role=\"button\" tabindex=\"0\">\r\n <span class=\"fb-expr__badge\">FEEL</span>\r\n @if (exprText) {\r\n <span class=\"fb-expr__code mono\">{{ exprText }}</span>\r\n } @else {\r\n <span class=\"empty\">{{ 'core.component.form-builder.set-expression' | translate }}</span>\r\n }\r\n <button type=\"button\" class=\"fb-icon-btn\" (click)=\"onEdit(); $event.stopPropagation()\" aria-label=\"edit expression\">\r\n <span class=\"msi\">functions</span>\r\n </button>\r\n </div>\r\n</div>\r\n@if (expression) {\r\n <sd-modal [title]=\"'core.component.form-builder.set-expression' | translate\">\r\n <div class=\"c-builder\">\r\n <div class=\"d-flex justify-content-end\">\r\n <sd-button\r\n prefixIcon=\"calculate\"\r\n class=\"mr-4\"\r\n [title]=\"'core.component.form-builder.condition-group' | translate\"\r\n color=\"primary\"\r\n (click)=\"addCombinator(expression.conditions)\"></sd-button>\r\n <sd-button\r\n prefixIcon=\"add\"\r\n [title]=\"'core.component.form-builder.condition' | translate\"\r\n color=\"primary\"\r\n (click)=\"addCondition(expression.conditions)\"\r\n type=\"fill\"></sd-button>\r\n </div>\r\n <!-- C\u1EA5p 1 -->\r\n @for (conditionLv1 of expression.conditions; track conditionLv1.key; let idxLv1 = $index) {\r\n <div class=\"d-flex align-items-end py-8\" style=\"gap: 8px\">\r\n <div class=\"c-condition\">\r\n <!-- D\u00F2ng 1 ch\u1EC9 hi\u1EC7n WHERE -->\r\n @if (idxLv1 === 0) {\r\n <div class=\"c-where\">WHERE</div>\r\n }\r\n <!-- D\u00F2ng 2 hi\u1EC3n th\u1ECB combinator -->\r\n @if (idxLv1 === 1) {\r\n <div class=\"c-select\">\r\n <sd-select\r\n size=\"sm\"\r\n [(model)]=\"expression.combinator\"\r\n [items]=\"[\r\n { value: '&&', display: 'AND' },\r\n { value: '||', display: 'OR' }\r\n ]\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n required\r\n hideInlineError=\"\"></sd-select>\r\n </div>\r\n }\r\n </div>\r\n @if (conditionLv1.type === 'condition') {\r\n <sd-autocomplete\r\n size=\"sm\"\r\n [form]=\"form\"\r\n style=\"width: 200px\"\r\n [(model)]=\"conditionLv1.field\"\r\n [items]=\"attributes\"\r\n (sdChange)=\"onChangeAttribute(conditionLv1)\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n required\r\n hideInlineError></sd-autocomplete>\r\n @let fieldAttribute = attribute[conditionLv1.field!];\r\n <sd-select\r\n size=\"sm\"\r\n [form]=\"form\"\r\n style=\"width: 100px\"\r\n [(model)]=\"conditionLv1.operator\"\r\n [items]=\"attributeOperators[fieldAttribute?.type || 'string']\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n [disabled]=\"!fieldAttribute?.type\"\r\n required\r\n hideInlineError></sd-select>\r\n @if (\r\n !!fieldAttribute?.type && conditionLv1.operator && conditionLv1.operator !== 'NULL' && conditionLv1.operator !== 'NOT_NULL'\r\n ) {\r\n @if (fieldAttribute.type === 'string') {\r\n <sd-input size=\"sm\" [form]=\"form\" style=\"flex: 1\" [(model)]=\"conditionLv1.value\" required hideInlineError></sd-input>\r\n } @else if (fieldAttribute.type === 'number') {\r\n <sd-input-number size=\"sm\" [form]=\"form\" style=\"flex: 1\" [(model)]=\"conditionLv1.value\" hideInlineError></sd-input-number>\r\n <sd-autocomplete\r\n size=\"sm\"\r\n [form]=\"form\"\r\n style=\"flex: 1\"\r\n [(model)]=\"conditionLv1.value\"\r\n [items]=\"numberAttributes\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n hideInlineError></sd-autocomplete>\r\n } @else if (fieldAttribute.type === 'datetime' && conditionLv1.dayInfo) {\r\n <sd-select\r\n size=\"sm\"\r\n [placeholder]=\"'core.component.form-builder.time' | translate\"\r\n [form]=\"form\"\r\n style=\"width: 150px\"\r\n [items]=\"types\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n [(model)]=\"conditionLv1.dayInfo.type\"\r\n (sdChange)=\"onChangeDateInfo(conditionLv1)\"\r\n required\r\n hideInlineError>\r\n </sd-select>\r\n @if (conditionLv1.dayInfo.type === 'RELATED') {\r\n <sd-input-number\r\n size=\"sm\"\r\n [form]=\"form\"\r\n style=\"width: 80px\"\r\n [(model)]=\"conditionLv1.dayInfo.relatedValue\"\r\n (sdChange)=\"onChangeDateInfo(conditionLv1)\"\r\n [min]=\"1\"\r\n required\r\n hideInlineError>\r\n </sd-input-number>\r\n <sd-select\r\n size=\"sm\"\r\n [form]=\"form\"\r\n style=\"flex: 1\"\r\n [items]=\"previouses\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n [(model)]=\"conditionLv1.dayInfo.related\"\r\n (sdChange)=\"onChangeDateInfo(conditionLv1)\"\r\n required\r\n hideInlineError>\r\n </sd-select>\r\n } @else if (conditionLv1.dayInfo.type === 'DATETIME') {\r\n <sd-datetime\r\n size=\"sm\"\r\n [form]=\"form\"\r\n style=\"flex: 1\"\r\n [(model)]=\"conditionLv1.value\"\r\n (sdChange)=\"onChangeDateInfo(conditionLv1)\"\r\n required\r\n hideInlineError>\r\n </sd-datetime>\r\n } @else if (conditionLv1.dayInfo.type === 'ATTRIBUTE') {\r\n <sd-autocomplete\r\n size=\"sm\"\r\n [form]=\"form\"\r\n style=\"flex: 1\"\r\n [(model)]=\"conditionLv1.value\"\r\n [items]=\"dateAttributes\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n hideInlineError></sd-autocomplete>\r\n }\r\n } @else if (fieldAttribute.type === 'boolean') {\r\n <sd-select\r\n size=\"sm\"\r\n [form]=\"form\"\r\n style=\"flex: 1\"\r\n [(model)]=\"conditionLv1.value\"\r\n [items]=\"[\r\n { value: true, display: fieldAttribute.displayOnTrue },\r\n { value: false, display: fieldAttribute.displayOnFalse }\r\n ]\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n required\r\n hideInlineError></sd-select>\r\n } @else if (fieldAttribute.type === 'values') {\r\n <sd-autocomplete\r\n size=\"sm\"\r\n [form]=\"form\"\r\n style=\"flex: 1\"\r\n [(model)]=\"conditionLv1.value\"\r\n [items]=\"fieldAttribute.values\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n required\r\n hideInlineError></sd-autocomplete>\r\n }\r\n }\r\n <sd-button class=\"ml-auto\" prefixIcon=\"delete\" [tooltip]=\"'core.component.form-builder.delete' | translate\" (click)=\"remove(expression.conditions, idxLv1)\"></sd-button>\r\n } @else if (conditionLv1.type === 'combinator') {\r\n <div class=\"c-builder\">\r\n <div class=\"d-flex justify-content-end\">\r\n <sd-button class=\"mr-4\" prefixIcon=\"delete\" [tooltip]=\"'core.component.form-builder.delete' | translate\" (click)=\"remove(expression.conditions, idxLv1)\"></sd-button>\r\n <sd-button\r\n prefixIcon=\"add\"\r\n [title]=\"'core.component.form-builder.condition' | translate\"\r\n size=\"sm\"\r\n color=\"primary\"\r\n (click)=\"addCondition(conditionLv1.conditions)\"\r\n type=\"fill\"></sd-button>\r\n </div>\r\n <!-- C\u1EA5p 2 -->\r\n @for (conditionLv2 of conditionLv1.conditions; track conditionLv2.key; let idxLv2 = $index) {\r\n <div class=\"d-flex align-items-end py-8\" style=\"gap: 8px\">\r\n <div class=\"c-condition\">\r\n <!-- D\u00F2ng 1 ch\u1EC9 hi\u1EC7n WHERE -->\r\n @if (idxLv2 === 0) {\r\n <div class=\"c-where\">WHERE</div>\r\n }\r\n <!-- D\u00F2ng 2 hi\u1EC3n th\u1ECB combinator -->\r\n @if (idxLv2 === 1) {\r\n <div class=\"c-select\">\r\n <sd-select\r\n size=\"sm\"\r\n [(model)]=\"conditionLv1.combinator\"\r\n [items]=\"[\r\n { value: '&&', display: 'AND' },\r\n { value: '||', display: 'OR' }\r\n ]\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n required\r\n hideInlineError=\"\"></sd-select>\r\n </div>\r\n }\r\n </div>\r\n @if (conditionLv2.type === 'condition') {\r\n <sd-autocomplete\r\n size=\"sm\"\r\n [form]=\"form\"\r\n style=\"width: 200px\"\r\n [(model)]=\"conditionLv2.field\"\r\n [items]=\"attributes\"\r\n (sdChange)=\"onChangeAttribute(conditionLv2)\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n required\r\n hideInlineError></sd-autocomplete>\r\n @let fieldAttribute = attribute[conditionLv2.field!];\r\n <sd-select\r\n size=\"sm\"\r\n [form]=\"form\"\r\n style=\"width: 100px\"\r\n [(model)]=\"conditionLv2.operator\"\r\n [items]=\"attributeOperators[fieldAttribute?.type || 'string']\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n [disabled]=\"!fieldAttribute?.type\"\r\n required\r\n hideInlineError></sd-select>\r\n @if (\r\n !!fieldAttribute?.type &&\r\n conditionLv2.operator &&\r\n conditionLv2.operator !== 'NULL' &&\r\n conditionLv2.operator !== 'NOT_NULL'\r\n ) {\r\n @if (fieldAttribute.type === 'string') {\r\n <sd-input\r\n size=\"sm\"\r\n [form]=\"form\"\r\n style=\"flex: 1\"\r\n [(model)]=\"conditionLv2.value\"\r\n required\r\n hideInlineError></sd-input>\r\n } @else if (fieldAttribute.type === 'number') {\r\n <sd-input-number\r\n size=\"sm\"\r\n [form]=\"form\"\r\n style=\"flex: 1\"\r\n [(model)]=\"conditionLv2.value\"\r\n hideInlineError></sd-input-number>\r\n <sd-autocomplete\r\n size=\"sm\"\r\n [form]=\"form\"\r\n style=\"flex: 1\"\r\n [(model)]=\"conditionLv2.value\"\r\n [items]=\"numberAttributes\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n hideInlineError></sd-autocomplete>\r\n } @else if (fieldAttribute.type === 'datetime' && conditionLv2.dayInfo) {\r\n <sd-select\r\n size=\"sm\"\r\n [form]=\"form\"\r\n [placeholder]=\"'core.component.form-builder.time' | translate\"\r\n style=\"width: 150px\"\r\n [items]=\"types\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n [(model)]=\"conditionLv2.dayInfo.type\"\r\n (sdChange)=\"onChangeDateInfo(conditionLv2)\"\r\n required\r\n hideInlineError>\r\n </sd-select>\r\n @if (conditionLv2.dayInfo.type === 'RELATED') {\r\n <sd-input-number\r\n size=\"sm\"\r\n [form]=\"form\"\r\n style=\"width: 80px\"\r\n [(model)]=\"conditionLv2.dayInfo.relatedValue\"\r\n (sdChange)=\"onChangeDateInfo(conditionLv2)\"\r\n [min]=\"1\"\r\n required\r\n hideInlineError>\r\n </sd-input-number>\r\n <sd-select\r\n size=\"sm\"\r\n [form]=\"form\"\r\n style=\"flex: 1\"\r\n [items]=\"previouses\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n [(model)]=\"conditionLv2.dayInfo.related\"\r\n (sdChange)=\"onChangeDateInfo(conditionLv2)\"\r\n required\r\n hideInlineError>\r\n </sd-select>\r\n } @else if (conditionLv2.dayInfo.type === 'DATETIME') {\r\n <sd-datetime\r\n size=\"sm\"\r\n [form]=\"form\"\r\n style=\"flex: 1\"\r\n [(model)]=\"conditionLv2.value\"\r\n (sdChange)=\"onChangeDateInfo(conditionLv2)\"\r\n required\r\n hideInlineError>\r\n </sd-datetime>\r\n } @else if (conditionLv2.dayInfo.type === 'ATTRIBUTE') {\r\n <sd-autocomplete\r\n size=\"sm\"\r\n [form]=\"form\"\r\n style=\"flex: 1\"\r\n [(model)]=\"conditionLv2.value\"\r\n [items]=\"dateAttributes\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n hideInlineError></sd-autocomplete>\r\n }\r\n } @else if (fieldAttribute.type === 'boolean') {\r\n <sd-select\r\n size=\"sm\"\r\n [form]=\"form\"\r\n style=\"flex: 1\"\r\n [(model)]=\"conditionLv2.value\"\r\n [items]=\"[\r\n { value: true, display: fieldAttribute.displayOnTrue },\r\n { value: false, display: fieldAttribute.displayOnFalse }\r\n ]\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n required\r\n hideInlineError></sd-select>\r\n } @else if (fieldAttribute.type === 'values') {\r\n <sd-autocomplete\r\n size=\"sm\"\r\n [form]=\"form\"\r\n style=\"flex: 1\"\r\n [(model)]=\"conditionLv2.value\"\r\n [items]=\"fieldAttribute.values\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n required\r\n hideInlineError></sd-autocomplete>\r\n }\r\n }\r\n <sd-button\r\n class=\"ml-auto\"\r\n prefixIcon=\"delete\"\r\n [tooltip]=\"'core.component.form-builder.delete' | translate\"\r\n (click)=\"remove(conditionLv1.conditions, idxLv1)\"></sd-button>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n <ng-container sdFooter>\r\n <sd-button prefixIcon=\"save\" [title]=\"'core.component.form-builder.confirm' | translate\" (click)=\"onAccept()\" color=\"primary\" type=\"fill\"></sd-button>\r\n </ng-container>\r\n </sd-modal>\r\n}\r\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: ExpressionQueryPipe, name: "expressionQuery" }, { kind: "component", type: SdDatetime, selector: "sd-datetime", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "hideInlineError", "required", "disabled", "viewed", "showSeconds", "clearable", "inlineError", "hyperlink", "appearance", "floatLabel", "min", "minDate", "max", "maxDate", "model"], outputs: ["modelChange", "sdChange", "sdFocus"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3223
3222
  }
3224
3223
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: ExpressionBuilderComponent, decorators: [{
3225
3224
  type: Component,
@@ -3410,7 +3409,7 @@ class AttributeInputNumber {
3410
3409
  this.modelChange.emit(value);
3411
3410
  };
3412
3411
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: AttributeInputNumber, deps: [], target: i0.ɵɵFactoryTarget.Component });
3413
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.21", type: AttributeInputNumber, isStandalone: true, selector: "attribute-input-number", inputs: { _label: ["label", "_label"], _disabled: ["disabled", "_disabled"], _model: ["model", "_model"] }, outputs: { modelChange: "modelChange" }, ngImport: i0, template: "<!-- <div class=\"d-flex align-items-center py-4\">\r\n @if (label) {\r\n <div class=\"T14R\" style=\"flex: 1\">{{ label }}</div>\r\n }\r\n <sd-input-number\r\n style=\"width: 200px\"\r\n size=\"sm\"\r\n [(model)]=\"model\"\r\n [disabled]=\"disabled\"\r\n (sdChange)=\"onChange($event)\"\r\n hideInlineError></sd-input-number>\r\n</div> -->\r\n<sd-input-number\r\n class=\"py-8\"\r\n size=\"sm\"\r\n [label]=\"label!\"\r\n [(model)]=\"model\"\r\n [disabled]=\"disabled\"\r\n (sdChange)=\"onChange($event)\"\r\n hideInlineError></sd-input-number>\r\n", dependencies: [{ 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", "sdFocusForceBlur"] }] });
3412
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.21", type: AttributeInputNumber, isStandalone: true, selector: "attribute-input-number", inputs: { _label: ["label", "_label"], _disabled: ["disabled", "_disabled"], _model: ["model", "_model"] }, outputs: { modelChange: "modelChange" }, ngImport: i0, template: "<!-- <div class=\"d-flex align-items-center py-4\">\r\n @if (label) {\r\n <div class=\"T14R\" style=\"flex: 1\">{{ label }}</div>\r\n }\r\n <sd-input-number\r\n style=\"width: 200px\"\r\n size=\"sm\"\r\n [(model)]=\"model\"\r\n [disabled]=\"disabled\"\r\n (sdChange)=\"onChange($event)\"\r\n hideInlineError></sd-input-number>\r\n</div> -->\r\n<sd-input-number\r\n class=\"py-8\"\r\n size=\"sm\"\r\n [label]=\"label!\"\r\n [(model)]=\"model\"\r\n [disabled]=\"disabled\"\r\n (sdChange)=\"onChange($event)\"\r\n hideInlineError></sd-input-number>\r\n", dependencies: [{ 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"] }] });
3414
3413
  }
3415
3414
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: AttributeInputNumber, decorators: [{
3416
3415
  type: Component,
@@ -3452,7 +3451,7 @@ class AttributeInput {
3452
3451
  this.sdChange.emit(value);
3453
3452
  };
3454
3453
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: AttributeInput, deps: [], target: i0.ɵɵFactoryTarget.Component });
3455
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.21", type: AttributeInput, isStandalone: true, selector: "attribute-input", inputs: { form: "form", _label: ["label", "_label"], _disabled: ["disabled", "_disabled"], _model: ["model", "_model"] }, outputs: { modelChange: "modelChange", sdChange: "sdChange" }, ngImport: i0, template: "<!-- <div class=\"d-flex align-items-center py-4\">\r\n @if (label) {\r\n <div class=\"T14R\" style=\"flex: 1\">{{ label }}</div>\r\n }\r\n <sd-input style=\"width: 200px\" size=\"sm\" [form]=\"form\" [(model)]=\"model\" [disabled]=\"disabled\" (sdChange)=\"onChange($event)\" hideInlineError></sd-input>\r\n</div> -->\r\n<sd-input class=\"py-8\" size=\"sm\" [label]=\"label!\" [form]=\"form\" [(model)]=\"model\" [disabled]=\"disabled\" (sdChange)=\"onChange($event)\" hideInlineError></sd-input>\r\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", "tooltip", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "sdFocusForceBlur"] }] });
3454
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.21", type: AttributeInput, isStandalone: true, selector: "attribute-input", inputs: { form: "form", _label: ["label", "_label"], _disabled: ["disabled", "_disabled"], _model: ["model", "_model"] }, outputs: { modelChange: "modelChange", sdChange: "sdChange" }, ngImport: i0, template: "<!-- <div class=\"d-flex align-items-center py-4\">\r\n @if (label) {\r\n <div class=\"T14R\" style=\"flex: 1\">{{ label }}</div>\r\n }\r\n <sd-input style=\"width: 200px\" size=\"sm\" [form]=\"form\" [(model)]=\"model\" [disabled]=\"disabled\" (sdChange)=\"onChange($event)\" hideInlineError></sd-input>\r\n</div> -->\r\n<sd-input class=\"py-8\" size=\"sm\" [label]=\"label!\" [form]=\"form\" [(model)]=\"model\" [disabled]=\"disabled\" (sdChange)=\"onChange($event)\" hideInlineError></sd-input>\r\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"] }] });
3456
3455
  }
3457
3456
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: AttributeInput, decorators: [{
3458
3457
  type: Component,
@@ -3493,7 +3492,7 @@ class AttributeSwitch {
3493
3492
  this.modelChange.emit(!!value);
3494
3493
  };
3495
3494
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: AttributeSwitch, deps: [], target: i0.ɵɵFactoryTarget.Component });
3496
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.21", type: AttributeSwitch, isStandalone: true, selector: "attribute-switch", inputs: { form: "form", _label: ["label", "_label"], _model: ["model", "_model"] }, outputs: { modelChange: "modelChange" }, ngImport: i0, template: "<!-- <div class=\"d-flex align-items-center py-4\">\r\n @if (label) {\r\n <div class=\"T14R\" style=\"flex: 1\">{{ label }}</div>\r\n }\r\n <sd-switch size=\"sm\" [form]=\"form\" [(model)]=\"model\" (sdChange)=\"onChange($event)\" hideInlineError></sd-switch>\r\n</div> -->\r\n<sd-select\r\n class=\"py-8\"\r\n size=\"sm\"\r\n [label]=\"label!\"\r\n [form]=\"form\"\r\n [(model)]=\"model\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n [items]=\"[\r\n { value: true, display: ('core.confirm.yes-short' | translate) },\r\n { value: false, display: ('core.confirm.no-short' | translate) }\r\n ]\"\r\n (sdChange)=\"onChange($event)\"\r\n hideInlineError></sd-select>\r\n", dependencies: [{ kind: "component", type: SdSelect$1, selector: "sd-select", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "valueField", "displayField", "disabledField", "cacheChecksum", "limit", "hyperlink", "minWidthPanel", "hideInlineError", "required", "disabled", "viewed", "multiple", "validator", "inlineError", "appearance", "floatLabel", "items", "model"], outputs: ["modelChange", "sdChange", "sdSelection"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
3495
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.21", type: AttributeSwitch, isStandalone: true, selector: "attribute-switch", inputs: { form: "form", _label: ["label", "_label"], _model: ["model", "_model"] }, outputs: { modelChange: "modelChange" }, ngImport: i0, template: "<!-- <div class=\"d-flex align-items-center py-4\">\r\n @if (label) {\r\n <div class=\"T14R\" style=\"flex: 1\">{{ label }}</div>\r\n }\r\n <sd-switch size=\"sm\" [form]=\"form\" [(model)]=\"model\" (sdChange)=\"onChange($event)\" hideInlineError></sd-switch>\r\n</div> -->\r\n<sd-select\r\n class=\"py-8\"\r\n size=\"sm\"\r\n [label]=\"label!\"\r\n [form]=\"form\"\r\n [(model)]=\"model\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n [items]=\"[\r\n { value: true, display: ('core.confirm.yes-short' | translate) },\r\n { value: false, display: ('core.confirm.no-short' | translate) }\r\n ]\"\r\n (sdChange)=\"onChange($event)\"\r\n hideInlineError></sd-select>\r\n", dependencies: [{ kind: "component", type: SdSelect$1, selector: "sd-select", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "valueField", "displayField", "disabledField", "cacheChecksum", "limit", "hyperlink", "minWidthPanel", "hideInlineError", "required", "disabled", "viewed", "multiple", "clearable", "validator", "inlineError", "appearance", "floatLabel", "items", "model"], outputs: ["modelChange", "sdChange", "sdSelection"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
3497
3496
  }
3498
3497
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: AttributeSwitch, decorators: [{
3499
3498
  type: Component,
@@ -3533,7 +3532,7 @@ class AttributeTemplate {
3533
3532
  }
3534
3533
  };
3535
3534
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: AttributeTemplate, deps: [{ token: SD_FORM_GENERIC_CONFIGURATION, optional: true }], target: i0.ɵɵFactoryTarget.Component });
3536
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: AttributeTemplate, isStandalone: true, selector: "attribute-template", inputs: { _component: ["component", "_component"] }, outputs: { sdChange: "sdChange" }, ngImport: i0, template: "@if (templates.length) {\r\n <!-- <div class=\"d-flex align-items-center py-4\">\r\n <div class=\"T14M\" style=\"flex: 1\">D\u1EEF li\u1EC7u m\u1EABu</div>\r\n <sd-autocomplete\r\n style=\"width: 200px\"\r\n size=\"sm\"\r\n [(model)]=\"component.template\"\r\n [items]=\"templates\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n (sdChange)=\"onChange($event)\"\r\n hideInlineError></sd-autocomplete>\r\n </div> -->\r\n <sd-autocomplete\r\n class=\"py-8\"\r\n size=\"sm\"\r\n [label]=\"'core.component.form-builder.template-data' | translate\"\r\n [(model)]=\"component.template\"\r\n [items]=\"templates\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n (sdChange)=\"onChange($event)\"\r\n hideInlineError></sd-autocomplete>\r\n}\r\n", dependencies: [{ kind: "component", type: SdAutocomplete$1, selector: "sd-autocomplete", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "valueField", "displayField", "disabledField", "limit", "cacheChecksum", "hyperlink", "items", "hideInlineError", "addable", "required", "disabled", "viewed", "validator", "inlineError", "appearance", "model"], outputs: ["modelChange", "sdChange", "sdSelection", "sdAdd"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
3535
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: AttributeTemplate, isStandalone: true, selector: "attribute-template", inputs: { _component: ["component", "_component"] }, outputs: { sdChange: "sdChange" }, ngImport: i0, template: "@if (templates.length) {\r\n <!-- <div class=\"d-flex align-items-center py-4\">\r\n <div class=\"T14M\" style=\"flex: 1\">D\u1EEF li\u1EC7u m\u1EABu</div>\r\n <sd-autocomplete\r\n style=\"width: 200px\"\r\n size=\"sm\"\r\n [(model)]=\"component.template\"\r\n [items]=\"templates\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n (sdChange)=\"onChange($event)\"\r\n hideInlineError></sd-autocomplete>\r\n </div> -->\r\n <sd-autocomplete\r\n class=\"py-8\"\r\n size=\"sm\"\r\n [label]=\"'core.component.form-builder.template-data' | translate\"\r\n [(model)]=\"component.template\"\r\n [items]=\"templates\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n (sdChange)=\"onChange($event)\"\r\n hideInlineError></sd-autocomplete>\r\n}\r\n", dependencies: [{ kind: "component", type: SdAutocomplete$1, selector: "sd-autocomplete", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "valueField", "displayField", "disabledField", "limit", "cacheChecksum", "hyperlink", "items", "hideInlineError", "addable", "required", "disabled", "viewed", "clearable", "validator", "inlineError", "appearance", "model"], outputs: ["modelChange", "sdChange", "sdSelection", "sdAdd"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
3537
3536
  }
3538
3537
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: AttributeTemplate, decorators: [{
3539
3538
  type: Component,
@@ -4222,7 +4221,7 @@ class AttributeSelect {
4222
4221
  this.sdChange.emit(value);
4223
4222
  };
4224
4223
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: AttributeSelect, deps: [], target: i0.ɵɵFactoryTarget.Component });
4225
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.21", type: AttributeSelect, isStandalone: true, selector: "attribute-select", inputs: { form: "form", _label: ["label", "_label"], _disabled: ["disabled", "_disabled"], _multiple: ["multiple", "_multiple"], _items: ["items", "_items"], _model: ["model", "_model"] }, outputs: { modelChange: "modelChange", sdChange: "sdChange" }, ngImport: i0, template: "<!-- <div class=\"d-flex align-items-center py-4\">\r\n @if (label) {\r\n <div class=\"T14R\" style=\"flex: 1\">{{ label }}</div>\r\n }\r\n <sd-select\r\n style=\"width: 200px\"\r\n size=\"sm\"\r\n [form]=\"form\"\r\n [(model)]=\"model\"\r\n [items]=\"items\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n [disabled]=\"disabled\"\r\n (sdChange)=\"onChange($event)\"\r\n hideInlineError></sd-select>\r\n</div> -->\r\n<sd-select\r\n class=\"py-8\"\r\n size=\"sm\"\r\n [label]=\"label!\"\r\n [form]=\"form\"\r\n [(model)]=\"model\"\r\n [items]=\"items\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n [disabled]=\"disabled\"\r\n (sdChange)=\"onChange($event)\"\r\n hideInlineError></sd-select>", dependencies: [{ kind: "component", type: SdSelect$1, selector: "sd-select", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "valueField", "displayField", "disabledField", "cacheChecksum", "limit", "hyperlink", "minWidthPanel", "hideInlineError", "required", "disabled", "viewed", "multiple", "validator", "inlineError", "appearance", "floatLabel", "items", "model"], outputs: ["modelChange", "sdChange", "sdSelection"] }] });
4224
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.21", type: AttributeSelect, isStandalone: true, selector: "attribute-select", inputs: { form: "form", _label: ["label", "_label"], _disabled: ["disabled", "_disabled"], _multiple: ["multiple", "_multiple"], _items: ["items", "_items"], _model: ["model", "_model"] }, outputs: { modelChange: "modelChange", sdChange: "sdChange" }, ngImport: i0, template: "<!-- <div class=\"d-flex align-items-center py-4\">\r\n @if (label) {\r\n <div class=\"T14R\" style=\"flex: 1\">{{ label }}</div>\r\n }\r\n <sd-select\r\n style=\"width: 200px\"\r\n size=\"sm\"\r\n [form]=\"form\"\r\n [(model)]=\"model\"\r\n [items]=\"items\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n [disabled]=\"disabled\"\r\n (sdChange)=\"onChange($event)\"\r\n hideInlineError></sd-select>\r\n</div> -->\r\n<sd-select\r\n class=\"py-8\"\r\n size=\"sm\"\r\n [label]=\"label!\"\r\n [form]=\"form\"\r\n [(model)]=\"model\"\r\n [items]=\"items\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n [disabled]=\"disabled\"\r\n (sdChange)=\"onChange($event)\"\r\n hideInlineError></sd-select>", dependencies: [{ kind: "component", type: SdSelect$1, selector: "sd-select", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "valueField", "displayField", "disabledField", "cacheChecksum", "limit", "hyperlink", "minWidthPanel", "hideInlineError", "required", "disabled", "viewed", "multiple", "clearable", "validator", "inlineError", "appearance", "floatLabel", "items", "model"], outputs: ["modelChange", "sdChange", "sdSelection"] }] });
4226
4225
  }
4227
4226
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: AttributeSelect, decorators: [{
4228
4227
  type: Component,
@@ -4378,7 +4377,7 @@ let BuildQueries$1 = class BuildQueries {
4378
4377
  this.ref.markForCheck();
4379
4378
  };
4380
4379
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: BuildQueries, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
4381
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: BuildQueries, isStandalone: true, selector: "build-queries", inputs: { components: "components", variables: "variables", label: "label", selections: "selections", _valuesKey: ["valuesKey", "_valuesKey"], _model: ["model", "_model"] }, outputs: { modelChange: "modelChange" }, viewQueries: [{ propertyName: "modal", first: true, predicate: SdModal, descendants: true }], ngImport: i0, template: "@if (!!selection?.queries?.items?.length) {\r\n <div class=\"d-flex align-items-center py-4\">\r\n @if (label) {\r\n <div class=\"T14R\" style=\"flex: 1\">{{ label }}</div>\r\n }\r\n <div style=\"width: 200px\" class=\"d-flex align-items-center\">\r\n <a class=\"T12R\" href=\"javascript:;\" (click)=\"edit()\">{{ queryString }}</a>\r\n </div>\r\n </div>\r\n <sd-modal [title]=\"'core.component.form-builder.set-query-value' | translate\" width=\"400px\">\r\n <div class=\"c-builder\">\r\n @for (property of leftProperties; track property.value) {\r\n <div class=\"d-flex align-items-center py-8\" style=\"gap: 8px\">\r\n <div style=\"width: 150px\">\r\n <div class=\"text-secondary\">{{ property.value }}</div>\r\n <div class=\"T16M\">{{ property.display }}</div>\r\n </div>\r\n <sd-autocomplete\r\n [label]=\"'core.component.form-builder.value-source-field' | translate\"\r\n [form]=\"form\"\r\n style=\"flex: 1\"\r\n [(model)]=\"model![property.value]\"\r\n [items]=\"rightProperties\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n hideInlineError></sd-autocomplete>\r\n </div>\r\n }\r\n </div>\r\n <ng-container sdFooter>\r\n <sd-button prefixIcon=\"save\" [title]=\"'core.component.form-builder.confirm' | translate\" (click)=\"onAccept()\" color=\"primary\" type=\"fill\"></sd-button>\r\n </ng-container>\r\n </sd-modal>\r\n}\r\n", styles: [".c-builder{padding:8px;width:100%;border:1px solid #e6e6e6;border-radius:4px}.c-builder .c-condition{width:80px}.c-builder .c-condition .c-where{width:80px;border:1px solid #e6e6e6;background-color:#f2f2f2;color:#757575;border-radius:4px;padding:4px;text-align:center}.c-builder .c-condition .c-select{margin-left:10px;width:70px}\n"], dependencies: [{ kind: "component", type: SdAutocomplete$1, selector: "sd-autocomplete", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "valueField", "displayField", "disabledField", "limit", "cacheChecksum", "hyperlink", "items", "hideInlineError", "addable", "required", "disabled", "viewed", "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 });
4380
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: BuildQueries, isStandalone: true, selector: "build-queries", inputs: { components: "components", variables: "variables", label: "label", selections: "selections", _valuesKey: ["valuesKey", "_valuesKey"], _model: ["model", "_model"] }, outputs: { modelChange: "modelChange" }, viewQueries: [{ propertyName: "modal", first: true, predicate: SdModal, descendants: true }], ngImport: i0, template: "@if (!!selection?.queries?.items?.length) {\r\n <div class=\"d-flex align-items-center py-4\">\r\n @if (label) {\r\n <div class=\"T14R\" style=\"flex: 1\">{{ label }}</div>\r\n }\r\n <div style=\"width: 200px\" class=\"d-flex align-items-center\">\r\n <a class=\"T12R\" href=\"javascript:;\" (click)=\"edit()\">{{ queryString }}</a>\r\n </div>\r\n </div>\r\n <sd-modal [title]=\"'core.component.form-builder.set-query-value' | translate\" width=\"400px\">\r\n <div class=\"c-builder\">\r\n @for (property of leftProperties; track property.value) {\r\n <div class=\"d-flex align-items-center py-8\" style=\"gap: 8px\">\r\n <div style=\"width: 150px\">\r\n <div class=\"text-secondary\">{{ property.value }}</div>\r\n <div class=\"T16M\">{{ property.display }}</div>\r\n </div>\r\n <sd-autocomplete\r\n [label]=\"'core.component.form-builder.value-source-field' | translate\"\r\n [form]=\"form\"\r\n style=\"flex: 1\"\r\n [(model)]=\"model![property.value]\"\r\n [items]=\"rightProperties\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n hideInlineError></sd-autocomplete>\r\n </div>\r\n }\r\n </div>\r\n <ng-container sdFooter>\r\n <sd-button prefixIcon=\"save\" [title]=\"'core.component.form-builder.confirm' | translate\" (click)=\"onAccept()\" color=\"primary\" type=\"fill\"></sd-button>\r\n </ng-container>\r\n </sd-modal>\r\n}\r\n", styles: [".c-builder{padding:8px;width:100%;border:1px solid #e6e6e6;border-radius:4px}.c-builder .c-condition{width:80px}.c-builder .c-condition .c-where{width:80px;border:1px solid #e6e6e6;background-color:#f2f2f2;color:#757575;border-radius:4px;padding:4px;text-align:center}.c-builder .c-condition .c-select{margin-left:10px;width:70px}\n"], dependencies: [{ kind: "component", type: SdAutocomplete$1, selector: "sd-autocomplete", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "valueField", "displayField", "disabledField", "limit", "cacheChecksum", "hyperlink", "items", "hideInlineError", "addable", "required", "disabled", "viewed", "clearable", "validator", "inlineError", "appearance", "model"], outputs: ["modelChange", "sdChange", "sdSelection", "sdAdd"] }, { kind: "component", type: SdButton, selector: "sd-button", inputs: ["autoId", "type", "color", "size", "fontSet", "title", "width", "tooltip", "prefixIcon", "suffixIcon", "disabled", "loading", "block", "htmlType"], outputs: ["click"] }, { kind: "component", type: SdModal, selector: "sd-modal", inputs: ["autoId", "title", "color", "width", "height", "view", "modalClass", "lazyLoadContent", "hideClose", "disableBackdropClose"], outputs: ["sdClosed"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4382
4381
  };
4383
4382
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: BuildQueries$1, decorators: [{
4384
4383
  type: Component,
@@ -4433,7 +4432,7 @@ class BuildVariables {
4433
4432
  // Parse JSON -> STRING để hiển thị trên UI
4434
4433
  this.queryString = JSON.stringify(this.#model);
4435
4434
  this.items = Object.keys(this.#model).map(key => ({
4436
- id: SdUtilities.randomId(),
4435
+ id: Utilities.randomId(),
4437
4436
  key,
4438
4437
  value: this.#model?.[key],
4439
4438
  }));
@@ -4467,7 +4466,7 @@ class BuildVariables {
4467
4466
  };
4468
4467
  addField = () => {
4469
4468
  this.items.push({
4470
- id: SdUtilities.randomId(),
4469
+ id: Utilities.randomId(),
4471
4470
  key: '',
4472
4471
  value: '',
4473
4472
  });
@@ -4493,7 +4492,7 @@ class BuildVariables {
4493
4492
  this.ref.markForCheck();
4494
4493
  };
4495
4494
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: BuildVariables, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
4496
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: BuildVariables, isStandalone: true, selector: "build-variables", inputs: { components: "components", variables: "variables", label: "label", selections: "selections", _valuesKey: ["valuesKey", "_valuesKey"], _model: ["model", "_model"] }, outputs: { modelChange: "modelChange" }, viewQueries: [{ propertyName: "modal", first: true, predicate: SdModal, descendants: true }], ngImport: i0, template: "@if (!!selection?.variables?.items?.length) {\r\n <div class=\"d-flex align-items-center py-4\">\r\n @if (label) {\r\n <div class=\"T14R\" style=\"flex: 1\">{{ label }}</div>\r\n }\r\n <div style=\"width: 200px\" class=\"d-flex align-items-center\">\r\n <a class=\"T12R\" href=\"javascript:;\" (click)=\"edit()\">{{ queryString }}</a>\r\n </div>\r\n </div>\r\n <sd-modal [title]=\"'core.component.form-builder.set-on-change-assign' | translate\" width=\"500px\">\r\n <div class=\"c-builder\">\r\n <div class=\"d-flex justify-content-end\">\r\n <sd-button prefixIcon=\"add\" [title]=\"'core.component.form-builder.add-field' | translate\" color=\"primary\" (click)=\"addField()\"></sd-button>\r\n </div>\r\n <!-- C\u1EA5p 1 -->\r\n @for (item of items; track item.id; let idx = $index) {\r\n <div class=\"d-flex align-items-center py-8\" style=\"gap: 8px\">\r\n <sd-autocomplete\r\n [label]=\"'core.component.form-builder.target-field' | translate\"\r\n [form]=\"form\"\r\n style=\"flex: 1\"\r\n [(model)]=\"item.key\"\r\n [items]=\"leftProperties\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n required\r\n hideInlineError></sd-autocomplete>\r\n <sd-autocomplete\r\n [label]=\"'core.component.form-builder.target-value' | translate\"\r\n [form]=\"form\"\r\n style=\"flex: 1\"\r\n [(model)]=\"item.value\"\r\n [items]=\"rightProperties\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n required\r\n hideInlineError></sd-autocomplete>\r\n </div>\r\n }\r\n </div>\r\n <ng-container sdFooter>\r\n <sd-button prefixIcon=\"save\" [title]=\"'core.component.form-builder.confirm' | translate\" (click)=\"onAccept()\" color=\"primary\" type=\"fill\"></sd-button>\r\n </ng-container>\r\n </sd-modal>\r\n}\r\n", styles: [".c-builder{padding:8px;width:100%;border:1px solid #e6e6e6;border-radius:4px}.c-builder .c-condition{width:80px}.c-builder .c-condition .c-where{width:80px;border:1px solid #e6e6e6;background-color:#f2f2f2;color:#757575;border-radius:4px;padding:4px;text-align:center}.c-builder .c-condition .c-select{margin-left:10px;width:70px}\n"], dependencies: [{ kind: "component", type: SdAutocomplete$1, selector: "sd-autocomplete", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "valueField", "displayField", "disabledField", "limit", "cacheChecksum", "hyperlink", "items", "hideInlineError", "addable", "required", "disabled", "viewed", "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 });
4495
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: BuildVariables, isStandalone: true, selector: "build-variables", inputs: { components: "components", variables: "variables", label: "label", selections: "selections", _valuesKey: ["valuesKey", "_valuesKey"], _model: ["model", "_model"] }, outputs: { modelChange: "modelChange" }, viewQueries: [{ propertyName: "modal", first: true, predicate: SdModal, descendants: true }], ngImport: i0, template: "@if (!!selection?.variables?.items?.length) {\r\n <div class=\"d-flex align-items-center py-4\">\r\n @if (label) {\r\n <div class=\"T14R\" style=\"flex: 1\">{{ label }}</div>\r\n }\r\n <div style=\"width: 200px\" class=\"d-flex align-items-center\">\r\n <a class=\"T12R\" href=\"javascript:;\" (click)=\"edit()\">{{ queryString }}</a>\r\n </div>\r\n </div>\r\n <sd-modal [title]=\"'core.component.form-builder.set-on-change-assign' | translate\" width=\"500px\">\r\n <div class=\"c-builder\">\r\n <div class=\"d-flex justify-content-end\">\r\n <sd-button prefixIcon=\"add\" [title]=\"'core.component.form-builder.add-field' | translate\" color=\"primary\" (click)=\"addField()\"></sd-button>\r\n </div>\r\n <!-- C\u1EA5p 1 -->\r\n @for (item of items; track item.id; let idx = $index) {\r\n <div class=\"d-flex align-items-center py-8\" style=\"gap: 8px\">\r\n <sd-autocomplete\r\n [label]=\"'core.component.form-builder.target-field' | translate\"\r\n [form]=\"form\"\r\n style=\"flex: 1\"\r\n [(model)]=\"item.key\"\r\n [items]=\"leftProperties\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n required\r\n hideInlineError></sd-autocomplete>\r\n <sd-autocomplete\r\n [label]=\"'core.component.form-builder.target-value' | translate\"\r\n [form]=\"form\"\r\n style=\"flex: 1\"\r\n [(model)]=\"item.value\"\r\n [items]=\"rightProperties\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n required\r\n hideInlineError></sd-autocomplete>\r\n </div>\r\n }\r\n </div>\r\n <ng-container sdFooter>\r\n <sd-button prefixIcon=\"save\" [title]=\"'core.component.form-builder.confirm' | translate\" (click)=\"onAccept()\" color=\"primary\" type=\"fill\"></sd-button>\r\n </ng-container>\r\n </sd-modal>\r\n}\r\n", styles: [".c-builder{padding:8px;width:100%;border:1px solid #e6e6e6;border-radius:4px}.c-builder .c-condition{width:80px}.c-builder .c-condition .c-where{width:80px;border:1px solid #e6e6e6;background-color:#f2f2f2;color:#757575;border-radius:4px;padding:4px;text-align:center}.c-builder .c-condition .c-select{margin-left:10px;width:70px}\n"], dependencies: [{ kind: "component", type: SdAutocomplete$1, selector: "sd-autocomplete", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "valueField", "displayField", "disabledField", "limit", "cacheChecksum", "hyperlink", "items", "hideInlineError", "addable", "required", "disabled", "viewed", "clearable", "validator", "inlineError", "appearance", "model"], outputs: ["modelChange", "sdChange", "sdSelection", "sdAdd"] }, { kind: "component", type: SdButton, selector: "sd-button", inputs: ["autoId", "type", "color", "size", "fontSet", "title", "width", "tooltip", "prefixIcon", "suffixIcon", "disabled", "loading", "block", "htmlType"], outputs: ["click"] }, { kind: "component", type: SdModal, selector: "sd-modal", inputs: ["autoId", "title", "color", "width", "height", "view", "modalClass", "lazyLoadContent", "hideClose", "disableBackdropClose"], outputs: ["sdClosed"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4497
4496
  }
4498
4497
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: BuildVariables, decorators: [{
4499
4498
  type: Component,
@@ -4591,7 +4590,7 @@ class AttributeSelection {
4591
4590
  this.ref.markForCheck();
4592
4591
  };
4593
4592
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: AttributeSelection, deps: [{ token: i0.ChangeDetectorRef }, { token: FormGenericService }], target: i0.ɵɵFactoryTarget.Component });
4594
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: AttributeSelection, isStandalone: true, selector: "attribute-selection", inputs: { components: "components", variables: "variables", component: "component", _valuesKey: ["valuesKey", "_valuesKey"], _values: ["values", "_values"] }, outputs: { valuesKeyChange: "valuesKeyChange", valuesChange: "valuesChange", sdChange: "sdChange" }, ngImport: i0, template: "<sd-section class=\"mt-8\" [title]=\"'core.component.form-builder.data' | translate\" icon=\"format_list_bulleted\">\r\n @if (selections.length) {\r\n <div class=\"d-flex align-items-center py-4\">\r\n <div class=\"T14M\" style=\"flex: 1\">{{ 'core.component.form-builder.data-api' | translate }}</div>\r\n <sd-autocomplete\r\n style=\"width: 200px\"\r\n size=\"sm\"\r\n [(model)]=\"valuesKey\"\r\n [items]=\"selections\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n (sdChange)=\"onChangeValuesKey($event)\"\r\n hideInlineError></sd-autocomplete>\r\n </div>\r\n <!-- N\u1EBFu l\u00E0 select t\u1EEB API th\u00EC cho ph\u00E9p truy\u1EC1n th\u00EAm Query -->\r\n\r\n @if (component.type === 'select' && !!valuesKey) {\r\n <build-queries\r\n [label]=\"'core.component.form-builder.query-condition' | translate\"\r\n [selections]=\"selections\"\r\n [valuesKey]=\"valuesKey\"\r\n [components]=\"components\"\r\n [variables]=\"variables\"\r\n [(model)]=\"component.properties!.query\"></build-queries>\r\n <build-variables\r\n [label]=\"'core.component.form-builder.assign-value' | translate\"\r\n [selections]=\"selections\"\r\n [valuesKey]=\"valuesKey\"\r\n [components]=\"components\"\r\n [variables]=\"variables\"\r\n [(model)]=\"component.properties!.setVariables\"></build-variables>\r\n <!-- <div class=\"d-flex align-items-center py-4\">\r\n <div class=\"T14M\" style=\"flex: 1\">\u0110i\u1EC1u ki\u1EC7n truy v\u1EA5n</div>\r\n <sd-input style=\"width: 200px\" size=\"sm\" [(model)]=\"component.properties!.query\" hideInlineError></sd-input>\r\n </div> -->\r\n }\r\n }\r\n @if (!valuesKey) {\r\n <div class=\"d-flex flex-column\" style=\"gap: 4px\">\r\n <div class=\"T14M\">{{ 'core.component.form-builder.data-static' | translate }}</div>\r\n @for (item of values; track item.value; let idx = $index) {\r\n <div class=\"d-flex align-items-end py-4\" style=\"gap: 4px\">\r\n <sd-input style=\"width: 200px\" size=\"sm\" [(model)]=\"item.value\" (sdChange)=\"onChangeValues()\" hideInlineError></sd-input>\r\n <sd-input style=\"width: 200px\" size=\"sm\" [(model)]=\"item.label\" (sdChange)=\"onChangeValues()\" hideInlineError></sd-input>\r\n <sd-button prefixIcon=\"delete\" (click)=\"removeItem(idx)\" color=\"error\"></sd-button>\r\n </div>\r\n }\r\n <sd-button prefixIcon=\"add\" [title]=\"'core.component.form-builder.add-data' | translate\" (click)=\"addItem()\" color=\"primary\"></sd-button>\r\n </div>\r\n }\r\n</sd-section>\r\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", "tooltip", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "sdFocusForceBlur"] }, { kind: "component", type: SdAutocomplete$1, selector: "sd-autocomplete", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "valueField", "displayField", "disabledField", "limit", "cacheChecksum", "hyperlink", "items", "hideInlineError", "addable", "required", "disabled", "viewed", "validator", "inlineError", "appearance", "model"], outputs: ["modelChange", "sdChange", "sdSelection", "sdAdd"] }, { kind: "component", type: SdButton, selector: "sd-button", inputs: ["autoId", "type", "color", "size", "fontSet", "title", "width", "tooltip", "prefixIcon", "suffixIcon", "disabled", "loading", "block", "htmlType"], outputs: ["click"] }, { kind: "component", type: SdSection, selector: "sd-section", inputs: ["title", "subTitle", "icon", "iconColor", "collapsed", "collapsable", "hideHeader", "noPaddingBody"], outputs: ["collapsedChange"] }, { kind: "component", type: BuildQueries$1, selector: "build-queries", inputs: ["components", "variables", "label", "selections", "valuesKey", "model"], outputs: ["modelChange"] }, { kind: "component", type: BuildVariables, selector: "build-variables", inputs: ["components", "variables", "label", "selections", "valuesKey", "model"], outputs: ["modelChange"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4593
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: AttributeSelection, isStandalone: true, selector: "attribute-selection", inputs: { components: "components", variables: "variables", component: "component", _valuesKey: ["valuesKey", "_valuesKey"], _values: ["values", "_values"] }, outputs: { valuesKeyChange: "valuesKeyChange", valuesChange: "valuesChange", sdChange: "sdChange" }, ngImport: i0, template: "<sd-section class=\"mt-8\" [title]=\"'core.component.form-builder.data' | translate\" icon=\"format_list_bulleted\">\r\n @if (selections.length) {\r\n <div class=\"d-flex align-items-center py-4\">\r\n <div class=\"T14M\" style=\"flex: 1\">{{ 'core.component.form-builder.data-api' | translate }}</div>\r\n <sd-autocomplete\r\n style=\"width: 200px\"\r\n size=\"sm\"\r\n [(model)]=\"valuesKey\"\r\n [items]=\"selections\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n (sdChange)=\"onChangeValuesKey($event)\"\r\n hideInlineError></sd-autocomplete>\r\n </div>\r\n <!-- N\u1EBFu l\u00E0 select t\u1EEB API th\u00EC cho ph\u00E9p truy\u1EC1n th\u00EAm Query -->\r\n\r\n @if (component.type === 'select' && !!valuesKey) {\r\n <build-queries\r\n [label]=\"'core.component.form-builder.query-condition' | translate\"\r\n [selections]=\"selections\"\r\n [valuesKey]=\"valuesKey\"\r\n [components]=\"components\"\r\n [variables]=\"variables\"\r\n [(model)]=\"component.properties!.query\"></build-queries>\r\n <build-variables\r\n [label]=\"'core.component.form-builder.assign-value' | translate\"\r\n [selections]=\"selections\"\r\n [valuesKey]=\"valuesKey\"\r\n [components]=\"components\"\r\n [variables]=\"variables\"\r\n [(model)]=\"component.properties!.setVariables\"></build-variables>\r\n <!-- <div class=\"d-flex align-items-center py-4\">\r\n <div class=\"T14M\" style=\"flex: 1\">\u0110i\u1EC1u ki\u1EC7n truy v\u1EA5n</div>\r\n <sd-input style=\"width: 200px\" size=\"sm\" [(model)]=\"component.properties!.query\" hideInlineError></sd-input>\r\n </div> -->\r\n }\r\n }\r\n @if (!valuesKey) {\r\n <div class=\"d-flex flex-column\" style=\"gap: 4px\">\r\n <div class=\"T14M\">{{ 'core.component.form-builder.data-static' | translate }}</div>\r\n @for (item of values; track item.value; let idx = $index) {\r\n <div class=\"d-flex align-items-end py-4\" style=\"gap: 4px\">\r\n <sd-input style=\"width: 200px\" size=\"sm\" [(model)]=\"item.value\" (sdChange)=\"onChangeValues()\" hideInlineError></sd-input>\r\n <sd-input style=\"width: 200px\" size=\"sm\" [(model)]=\"item.label\" (sdChange)=\"onChangeValues()\" hideInlineError></sd-input>\r\n <sd-button prefixIcon=\"delete\" (click)=\"removeItem(idx)\" color=\"error\"></sd-button>\r\n </div>\r\n }\r\n <sd-button prefixIcon=\"add\" [title]=\"'core.component.form-builder.add-data' | translate\" (click)=\"addItem()\" color=\"primary\"></sd-button>\r\n </div>\r\n }\r\n</sd-section>\r\n", dependencies: [{ kind: "component", type: SdInput$1, selector: "sd-input", inputs: ["autoId", "name", "appearance", "floatLabel", "size", "form", "label", "helperText", "placeholder", "type", "hideInlineError", "blurOnEnter", "required", "readonly", "disabled", "viewed", "minlength", "maxlength", "pattern", "patternErrorMessage", "validator", "inlineError", "hyperlink", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "cleared", "sdFocusForceBlur"] }, { kind: "component", type: SdAutocomplete$1, selector: "sd-autocomplete", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "valueField", "displayField", "disabledField", "limit", "cacheChecksum", "hyperlink", "items", "hideInlineError", "addable", "required", "disabled", "viewed", "clearable", "validator", "inlineError", "appearance", "model"], outputs: ["modelChange", "sdChange", "sdSelection", "sdAdd"] }, { kind: "component", type: SdButton, selector: "sd-button", inputs: ["autoId", "type", "color", "size", "fontSet", "title", "width", "tooltip", "prefixIcon", "suffixIcon", "disabled", "loading", "block", "htmlType"], outputs: ["click"] }, { kind: "component", type: SdSection, selector: "sd-section", inputs: ["title", "subTitle", "icon", "iconColor", "collapsed", "collapsable", "hideHeader", "noPaddingBody"], outputs: ["collapsedChange"] }, { kind: "component", type: BuildQueries$1, selector: "build-queries", inputs: ["components", "variables", "label", "selections", "valuesKey", "model"], outputs: ["modelChange"] }, { kind: "component", type: BuildVariables, selector: "build-variables", inputs: ["components", "variables", "label", "selections", "valuesKey", "model"], outputs: ["modelChange"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4595
4594
  }
4596
4595
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: AttributeSelection, decorators: [{
4597
4596
  type: Component,
@@ -4972,7 +4971,7 @@ class BuildQueries {
4972
4971
  this.ref.markForCheck();
4973
4972
  };
4974
4973
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: BuildQueries, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
4975
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: BuildQueries, isStandalone: true, selector: "build-queries", inputs: { label: "label", _queries: ["queries", "_queries"], components: "components", variables: "variables", _model: ["model", "_model"] }, outputs: { modelChange: "modelChange" }, viewQueries: [{ propertyName: "modal", first: true, predicate: SdModal, descendants: true }], ngImport: i0, template: "@if (!!leftProperties?.length) {\r\n <div class=\"d-flex align-items-center py-4\">\r\n @if (label) {\r\n <div class=\"T14R\" style=\"flex: 1\">{{ label }}</div>\r\n }\r\n <div style=\"width: 200px\" class=\"d-flex align-items-center\">\r\n <a class=\"T12R\" href=\"javascript:;\" (click)=\"edit()\">{{ queryString }}</a>\r\n </div>\r\n </div>\r\n <sd-modal [title]=\"'core.component.form-builder.set-query-value' | translate\" width=\"400px\">\r\n <div class=\"c-builder\">\r\n @for (property of leftProperties; track property.value) {\r\n <div class=\"d-flex align-items-center py-8\" style=\"gap: 8px\">\r\n <div class=\"T14M\" style=\"width: 150px\">{{ property.display }}</div>\r\n <sd-autocomplete\r\n [label]=\"'core.component.form-builder.value-source-field' | translate\"\r\n [form]=\"form\"\r\n style=\"flex: 1\"\r\n [(model)]=\"model![property.value]\"\r\n [items]=\"rightProperties\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n required\r\n hideInlineError></sd-autocomplete>\r\n </div>\r\n }\r\n </div>\r\n <ng-container sdFooter>\r\n <sd-button prefixIcon=\"save\" [title]=\"'core.component.form-builder.confirm' | translate\" (click)=\"onAccept()\" color=\"primary\" type=\"fill\"></sd-button>\r\n </ng-container>\r\n </sd-modal>\r\n}\r\n", styles: [".c-builder{padding:8px;width:100%;border:1px solid #e6e6e6;border-radius:4px}.c-builder .c-condition{width:80px}.c-builder .c-condition .c-where{width:80px;border:1px solid #e6e6e6;background-color:#f2f2f2;color:#757575;border-radius:4px;padding:4px;text-align:center}.c-builder .c-condition .c-select{margin-left:10px;width:70px}\n"], dependencies: [{ kind: "component", type: SdAutocomplete$1, selector: "sd-autocomplete", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "valueField", "displayField", "disabledField", "limit", "cacheChecksum", "hyperlink", "items", "hideInlineError", "addable", "required", "disabled", "viewed", "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 });
4974
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: BuildQueries, isStandalone: true, selector: "build-queries", inputs: { label: "label", _queries: ["queries", "_queries"], components: "components", variables: "variables", _model: ["model", "_model"] }, outputs: { modelChange: "modelChange" }, viewQueries: [{ propertyName: "modal", first: true, predicate: SdModal, descendants: true }], ngImport: i0, template: "@if (!!leftProperties?.length) {\r\n <div class=\"d-flex align-items-center py-4\">\r\n @if (label) {\r\n <div class=\"T14R\" style=\"flex: 1\">{{ label }}</div>\r\n }\r\n <div style=\"width: 200px\" class=\"d-flex align-items-center\">\r\n <a class=\"T12R\" href=\"javascript:;\" (click)=\"edit()\">{{ queryString }}</a>\r\n </div>\r\n </div>\r\n <sd-modal [title]=\"'core.component.form-builder.set-query-value' | translate\" width=\"400px\">\r\n <div class=\"c-builder\">\r\n @for (property of leftProperties; track property.value) {\r\n <div class=\"d-flex align-items-center py-8\" style=\"gap: 8px\">\r\n <div class=\"T14M\" style=\"width: 150px\">{{ property.display }}</div>\r\n <sd-autocomplete\r\n [label]=\"'core.component.form-builder.value-source-field' | translate\"\r\n [form]=\"form\"\r\n style=\"flex: 1\"\r\n [(model)]=\"model![property.value]\"\r\n [items]=\"rightProperties\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n required\r\n hideInlineError></sd-autocomplete>\r\n </div>\r\n }\r\n </div>\r\n <ng-container sdFooter>\r\n <sd-button prefixIcon=\"save\" [title]=\"'core.component.form-builder.confirm' | translate\" (click)=\"onAccept()\" color=\"primary\" type=\"fill\"></sd-button>\r\n </ng-container>\r\n </sd-modal>\r\n}\r\n", styles: [".c-builder{padding:8px;width:100%;border:1px solid #e6e6e6;border-radius:4px}.c-builder .c-condition{width:80px}.c-builder .c-condition .c-where{width:80px;border:1px solid #e6e6e6;background-color:#f2f2f2;color:#757575;border-radius:4px;padding:4px;text-align:center}.c-builder .c-condition .c-select{margin-left:10px;width:70px}\n"], dependencies: [{ kind: "component", type: SdAutocomplete$1, selector: "sd-autocomplete", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "valueField", "displayField", "disabledField", "limit", "cacheChecksum", "hyperlink", "items", "hideInlineError", "addable", "required", "disabled", "viewed", "clearable", "validator", "inlineError", "appearance", "model"], outputs: ["modelChange", "sdChange", "sdSelection", "sdAdd"] }, { kind: "component", type: SdButton, selector: "sd-button", inputs: ["autoId", "type", "color", "size", "fontSet", "title", "width", "tooltip", "prefixIcon", "suffixIcon", "disabled", "loading", "block", "htmlType"], outputs: ["click"] }, { kind: "component", type: SdModal, selector: "sd-modal", inputs: ["autoId", "title", "color", "width", "height", "view", "modalClass", "lazyLoadContent", "hideClose", "disableBackdropClose"], outputs: ["sdClosed"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4976
4975
  }
4977
4976
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: BuildQueries, decorators: [{
4978
4977
  type: Component,
@@ -5120,7 +5119,7 @@ class HtmlControl {
5120
5119
  else {
5121
5120
  this.content = this.component.content;
5122
5121
  }
5123
- this.hashed = SdUtilities.hash({
5122
+ this.hashed = Utilities.hash({
5124
5123
  content: this.content,
5125
5124
  variables: this.component.properties?.variables?.reduce((current, next) => ({
5126
5125
  ...current,
@@ -5160,7 +5159,7 @@ class AttributeParameter {
5160
5159
  // Parse JSON -> STRING để hiển thị trên UI
5161
5160
  this.queryString = JSON.stringify(this.#model);
5162
5161
  this.items = Object.keys(this.#model).map(key => ({
5163
- id: SdUtilities.randomId(),
5162
+ id: Utilities.randomId(),
5164
5163
  key,
5165
5164
  value: this.#model?.[key],
5166
5165
  }));
@@ -5179,7 +5178,7 @@ class AttributeParameter {
5179
5178
  };
5180
5179
  addField = () => {
5181
5180
  this.items.push({
5182
- id: SdUtilities.randomId(),
5181
+ id: Utilities.randomId(),
5183
5182
  key: '',
5184
5183
  value: '',
5185
5184
  });
@@ -5205,7 +5204,7 @@ class AttributeParameter {
5205
5204
  this.ref.markForCheck();
5206
5205
  };
5207
5206
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: AttributeParameter, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
5208
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: AttributeParameter, isStandalone: true, selector: "attribute-parameter", inputs: { components: "components", label: "label", _model: ["model", "_model"] }, outputs: { modelChange: "modelChange" }, viewQueries: [{ propertyName: "modal", first: true, predicate: SdModal, descendants: true }], ngImport: i0, template: "<div class=\"d-flex align-items-center py-4\">\r\n @if (label) {\r\n <div class=\"T14R\" style=\"flex: 1\">{{ label }}</div>\r\n }\r\n <div style=\"width: 200px\" class=\"d-flex align-items-center\">\r\n <a class=\"T12R\" href=\"javascript:;\" (click)=\"edit()\">{{ queryString }}</a>\r\n </div>\r\n</div>\r\n<sd-modal [title]=\"'core.component.form-builder.assign-parameter-value' | translate\" width=\"500px\">\r\n <div class=\"c-builder\">\r\n <div class=\"d-flex justify-content-end\">\r\n <sd-button prefixIcon=\"add\" [title]=\"'core.component.form-builder.add-field' | translate\" color=\"primary\" (click)=\"addField()\"></sd-button>\r\n </div>\r\n <!-- C\u1EA5p 1 -->\r\n @for (item of items; track item.id; let idx = $index) {\r\n <div class=\"d-flex align-items-center py-8\" style=\"gap: 8px\">\r\n <sd-input [label]=\"'core.component.form-builder.parameter-name' | translate\" [form]=\"form\" style=\"flex: 1\" [(model)]=\"item.key\" required hideInlineError></sd-input>\r\n <sd-input [label]=\"'core.component.form-builder.parameter-value' | translate\" [form]=\"form\" style=\"flex: 1\" [(model)]=\"item.value\" required hideInlineError></sd-input>\r\n <div style=\"width: 32px; min-width: 32px\">\r\n <sd-button\r\n prefixIcon=\"delete\"\r\n [tooltip]=\"'core.component.form-builder.delete' | translate\"\r\n (click)=\"remove(idx)\"\r\n size=\"sm\"\r\n class=\"p-0\"\r\n ></sd-button>\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n <ng-container sdFooter>\r\n <sd-button prefixIcon=\"save\" [title]=\"'core.component.form-builder.confirm' | translate\" (click)=\"onAccept()\" color=\"primary\" type=\"fill\"></sd-button>\r\n </ng-container>\r\n</sd-modal>\r\n", styles: [".c-builder{padding:8px;width:100%;border:1px solid #e6e6e6;border-radius:4px}.c-builder .c-condition{width:80px}.c-builder .c-condition .c-where{width:80px;border:1px solid #e6e6e6;background-color:#f2f2f2;color:#757575;border-radius:4px;padding:4px;text-align:center}.c-builder .c-condition .c-select{margin-left:10px;width:70px}\n"], dependencies: [{ kind: "component", type: SdButton, selector: "sd-button", inputs: ["autoId", "type", "color", "size", "fontSet", "title", "width", "tooltip", "prefixIcon", "suffixIcon", "disabled", "loading", "block", "htmlType"], outputs: ["click"] }, { kind: "component", type: SdModal, selector: "sd-modal", inputs: ["autoId", "title", "color", "width", "height", "view", "modalClass", "lazyLoadContent", "hideClose", "disableBackdropClose"], outputs: ["sdClosed"] }, { kind: "component", type: SdInput, selector: "sd-input", inputs: ["autoId", "name", "appearance", "floatLabel", "size", "form", "label", "helperText", "placeholder", "type", "hideInlineError", "blurOnEnter", "required", "readonly", "disabled", "viewed", "minlength", "maxlength", "pattern", "patternErrorMessage", "validator", "inlineError", "hyperlink", "tooltip", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "sdFocusForceBlur"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5207
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: AttributeParameter, isStandalone: true, selector: "attribute-parameter", inputs: { components: "components", label: "label", _model: ["model", "_model"] }, outputs: { modelChange: "modelChange" }, viewQueries: [{ propertyName: "modal", first: true, predicate: SdModal, descendants: true }], ngImport: i0, template: "<div class=\"d-flex align-items-center py-4\">\r\n @if (label) {\r\n <div class=\"T14R\" style=\"flex: 1\">{{ label }}</div>\r\n }\r\n <div style=\"width: 200px\" class=\"d-flex align-items-center\">\r\n <a class=\"T12R\" href=\"javascript:;\" (click)=\"edit()\">{{ queryString }}</a>\r\n </div>\r\n</div>\r\n<sd-modal [title]=\"'core.component.form-builder.assign-parameter-value' | translate\" width=\"500px\">\r\n <div class=\"c-builder\">\r\n <div class=\"d-flex justify-content-end\">\r\n <sd-button prefixIcon=\"add\" [title]=\"'core.component.form-builder.add-field' | translate\" color=\"primary\" (click)=\"addField()\"></sd-button>\r\n </div>\r\n <!-- C\u1EA5p 1 -->\r\n @for (item of items; track item.id; let idx = $index) {\r\n <div class=\"d-flex align-items-center py-8\" style=\"gap: 8px\">\r\n <sd-input [label]=\"'core.component.form-builder.parameter-name' | translate\" [form]=\"form\" style=\"flex: 1\" [(model)]=\"item.key\" required hideInlineError></sd-input>\r\n <sd-input [label]=\"'core.component.form-builder.parameter-value' | translate\" [form]=\"form\" style=\"flex: 1\" [(model)]=\"item.value\" required hideInlineError></sd-input>\r\n <div style=\"width: 32px; min-width: 32px\">\r\n <sd-button\r\n prefixIcon=\"delete\"\r\n [tooltip]=\"'core.component.form-builder.delete' | translate\"\r\n (click)=\"remove(idx)\"\r\n size=\"sm\"\r\n class=\"p-0\"\r\n ></sd-button>\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n <ng-container sdFooter>\r\n <sd-button prefixIcon=\"save\" [title]=\"'core.component.form-builder.confirm' | translate\" (click)=\"onAccept()\" color=\"primary\" type=\"fill\"></sd-button>\r\n </ng-container>\r\n</sd-modal>\r\n", styles: [".c-builder{padding:8px;width:100%;border:1px solid #e6e6e6;border-radius:4px}.c-builder .c-condition{width:80px}.c-builder .c-condition .c-where{width:80px;border:1px solid #e6e6e6;background-color:#f2f2f2;color:#757575;border-radius:4px;padding:4px;text-align:center}.c-builder .c-condition .c-select{margin-left:10px;width:70px}\n"], dependencies: [{ kind: "component", type: SdButton, selector: "sd-button", inputs: ["autoId", "type", "color", "size", "fontSet", "title", "width", "tooltip", "prefixIcon", "suffixIcon", "disabled", "loading", "block", "htmlType"], outputs: ["click"] }, { kind: "component", type: SdModal, selector: "sd-modal", inputs: ["autoId", "title", "color", "width", "height", "view", "modalClass", "lazyLoadContent", "hideClose", "disableBackdropClose"], outputs: ["sdClosed"] }, { kind: "component", type: SdInput, selector: "sd-input", inputs: ["autoId", "name", "appearance", "floatLabel", "size", "form", "label", "helperText", "placeholder", "type", "hideInlineError", "blurOnEnter", "required", "readonly", "disabled", "viewed", "minlength", "maxlength", "pattern", "patternErrorMessage", "validator", "inlineError", "hyperlink", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "cleared", "sdFocusForceBlur"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5209
5208
  }
5210
5209
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: AttributeParameter, decorators: [{
5211
5210
  type: Component,
@@ -5395,8 +5394,8 @@ class AttributeTable {
5395
5394
  addColumn = () => {
5396
5395
  this.selectedIdx = undefined;
5397
5396
  this.column = {
5398
- key: SdUtilities.randomId('key'),
5399
- label: SdUtilities.randomId('label'),
5397
+ key: Utilities.randomId('key'),
5398
+ label: Utilities.randomId('label'),
5400
5399
  type: 'string',
5401
5400
  width: '100px',
5402
5401
  validate: {},
@@ -5447,7 +5446,7 @@ class AttributeTable {
5447
5446
  this.modal?.close();
5448
5447
  };
5449
5448
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: AttributeTable, deps: [{ token: SD_FORM_GENERIC_CONFIGURATION, optional: true }], target: i0.ɵɵFactoryTarget.Component });
5450
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: AttributeTable, isStandalone: true, selector: "attribute-table", inputs: { components: "components", variables: "variables", _columnsKey: ["columnsKey", "_columnsKey"], _columns: ["columns", "_columns"] }, outputs: { columnsKeyChange: "columnsKeyChange", columnsChange: "columnsChange" }, viewQueries: [{ propertyName: "modal", first: true, predicate: SdModal, descendants: true }], ngImport: i0, template: "<details class=\"fb-section\" open>\r\n <summary class=\"fb-section__head\">\r\n <span class=\"msi\">view_column</span>\r\n <span class=\"fb-section__title\">{{ 'core.component.form-builder.column-list' | translate }}</span>\r\n <span class=\"msi chev\">chevron_right</span>\r\n </summary>\r\n <div class=\"fb-section__body\">\r\n @if (tables.length) {\r\n <sd-autocomplete\r\n class=\"py-8\"\r\n [label]=\"'core.component.form-builder.column-data' | translate\"\r\n [(model)]=\"columnsKey\"\r\n [items]=\"tables\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n (sdChange)=\"onChangeColumnsKey($event)\"\r\n hideInlineError></sd-autocomplete>\r\n }\r\n @if (!columnsKey) {\r\n <div class=\"fb-columns-list\">\r\n @for (item of columns; track item.key; let idx = $index) {\r\n <div class=\"fb-column-row\" [class.is-active]=\"idx === selectedIdx\">\r\n <span class=\"fb-column-row__type-icon\"><span class=\"msi\">{{ columnTypeSymbol(item.type) }}</span></span>\r\n <div class=\"fb-column-row__meta\">\r\n <div class=\"fb-column-row__label\">\r\n {{ item.label }}\r\n @if (item.validate?.required) {\r\n <span class=\"fb-column-row__req\">*</span>\r\n }\r\n </div>\r\n <div class=\"fb-column-row__sub mono\">{{ item.key }} \u00B7 {{ item.type }}</div>\r\n </div>\r\n <button type=\"button\" class=\"fb-icon-btn\" [attr.aria-label]=\"'core.component.form-builder.column-config' | translate\" (click)=\"editColumn(idx)\">\r\n <span class=\"msi\">edit</span>\r\n </button>\r\n <button type=\"button\" class=\"fb-icon-btn danger\" [attr.aria-label]=\"'core.component.form-builder.delete' | translate\" (click)=\"removeColumn(idx)\">\r\n <span class=\"msi\">delete</span>\r\n </button>\r\n </div>\r\n }\r\n <a class=\"fb-add-link\" (click)=\"addColumn()\">\r\n <span class=\"msi\">add</span>\r\n {{ 'core.component.form-builder.add-column' | translate }}\r\n </a>\r\n </div>\r\n }\r\n </div>\r\n</details>\r\n\r\n<sd-modal [title]=\"'core.component.form-builder.column-config' | translate\" width=\"600px\">\r\n @if (column) {\r\n <details class=\"fb-section\" open>\r\n <summary class=\"fb-section__head\">\r\n <span class=\"msi\">info</span>\r\n <span class=\"fb-section__title\">{{ 'core.component.form-builder.general' | translate }}</span>\r\n <span class=\"msi chev\">chevron_right</span>\r\n </summary>\r\n <div class=\"fb-section__body\">\r\n <attribute-input [form]=\"form\" [label]=\"'core.component.form-builder.field-key' | translate\" [(model)]=\"column.key\"></attribute-input>\r\n <attribute-input [form]=\"form\" [label]=\"'core.component.form-builder.column-name' | translate\" [(model)]=\"column.label\"></attribute-input>\r\n <attribute-input [form]=\"form\" [label]=\"'core.component.form-builder.width' | translate\" [(model)]=\"column.width\"></attribute-input>\r\n <attribute-switch [form]=\"form\" [label]=\"'core.component.form-builder.required' | translate\" [(model)]=\"column.validate!.required\"></attribute-switch>\r\n <attribute-select [form]=\"form\" [label]=\"'core.component.form-builder.data-type' | translate\" [(model)]=\"column.type\" [items]=\"types\"></attribute-select>\r\n </div>\r\n </details>\r\n @if (column.type === 'boolean') {\r\n <details class=\"fb-section\" open>\r\n <summary class=\"fb-section__head\">\r\n <span class=\"msi\">visibility</span>\r\n <span class=\"fb-section__title\">{{ 'core.component.form-builder.display' | translate }}</span>\r\n <span class=\"msi chev\">chevron_right</span>\r\n </summary>\r\n <div class=\"fb-section__body\">\r\n <attribute-input [form]=\"form\" [label]=\"'core.component.form-builder.show-when-true' | translate\" [(model)]=\"column.displayOnTrue\"></attribute-input>\r\n <attribute-input [form]=\"form\" [label]=\"'core.component.form-builder.show-when-false' | translate\" [(model)]=\"column.displayOnFalse\"></attribute-input>\r\n </div>\r\n </details>\r\n } @else if (column.type === 'values') {\r\n <attribute-selection\r\n [components]=\"components\"\r\n [variables]=\"variables\"\r\n [component]=\"column\"\r\n [(valuesKey)]=\"column.valuesKey\"\r\n [(values)]=\"column.values\"></attribute-selection>\r\n } @else if (column.type === 'file' || column.type === 'image') {\r\n <details class=\"fb-section\" open>\r\n <summary class=\"fb-section__head\">\r\n <span class=\"msi\">settings</span>\r\n <span class=\"fb-section__title\">{{ 'core.component.form-builder.parameter' | translate }}</span>\r\n <span class=\"msi chev\">chevron_right</span>\r\n </summary>\r\n <div class=\"fb-section__body\">\r\n <attribute-parameter [label]=\"'core.component.form-builder.assign-parameter' | translate\" [components]=\"components\" [(model)]=\"column!.args\"></attribute-parameter>\r\n </div>\r\n </details>\r\n }\r\n }\r\n <ng-container sdFooter>\r\n <sd-button prefixIcon=\"save\" [title]=\"'core.component.form-builder.save' | translate\" (click)=\"onConfirm()\" color=\"primary\"></sd-button>\r\n </ng-container>\r\n</sd-modal>\r\n", dependencies: [{ kind: "ngmodule", type: MatIconModule }, { kind: "ngmodule", type: MatDividerModule }, { kind: "component", type: SdAutocomplete$1, selector: "sd-autocomplete", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "valueField", "displayField", "disabledField", "limit", "cacheChecksum", "hyperlink", "items", "hideInlineError", "addable", "required", "disabled", "viewed", "validator", "inlineError", "appearance", "model"], outputs: ["modelChange", "sdChange", "sdSelection", "sdAdd"] }, { kind: "component", type: SdButton, selector: "sd-button", inputs: ["autoId", "type", "color", "size", "fontSet", "title", "width", "tooltip", "prefixIcon", "suffixIcon", "disabled", "loading", "block", "htmlType"], outputs: ["click"] }, { kind: "component", type: SdModal, selector: "sd-modal", inputs: ["autoId", "title", "color", "width", "height", "view", "modalClass", "lazyLoadContent", "hideClose", "disableBackdropClose"], outputs: ["sdClosed"] }, { kind: "component", type: AttributeSelection, selector: "attribute-selection", inputs: ["components", "variables", "component", "valuesKey", "values"], outputs: ["valuesKeyChange", "valuesChange", "sdChange"] }, { kind: "component", type: AttributeInput, selector: "attribute-input", inputs: ["form", "label", "disabled", "model"], outputs: ["modelChange", "sdChange"] }, { kind: "component", type: AttributeSelect, selector: "attribute-select", inputs: ["form", "label", "disabled", "multiple", "items", "model"], outputs: ["modelChange", "sdChange"] }, { kind: "component", type: AttributeSwitch, selector: "attribute-switch", inputs: ["form", "label", "model"], outputs: ["modelChange"] }, { kind: "component", type: AttributeParameter, selector: "attribute-parameter", inputs: ["components", "label", "model"], outputs: ["modelChange"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
5449
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: AttributeTable, isStandalone: true, selector: "attribute-table", inputs: { components: "components", variables: "variables", _columnsKey: ["columnsKey", "_columnsKey"], _columns: ["columns", "_columns"] }, outputs: { columnsKeyChange: "columnsKeyChange", columnsChange: "columnsChange" }, viewQueries: [{ propertyName: "modal", first: true, predicate: SdModal, descendants: true }], ngImport: i0, template: "<details class=\"fb-section\" open>\r\n <summary class=\"fb-section__head\">\r\n <span class=\"msi\">view_column</span>\r\n <span class=\"fb-section__title\">{{ 'core.component.form-builder.column-list' | translate }}</span>\r\n <span class=\"msi chev\">chevron_right</span>\r\n </summary>\r\n <div class=\"fb-section__body\">\r\n @if (tables.length) {\r\n <sd-autocomplete\r\n class=\"py-8\"\r\n [label]=\"'core.component.form-builder.column-data' | translate\"\r\n [(model)]=\"columnsKey\"\r\n [items]=\"tables\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n (sdChange)=\"onChangeColumnsKey($event)\"\r\n hideInlineError></sd-autocomplete>\r\n }\r\n @if (!columnsKey) {\r\n <div class=\"fb-columns-list\">\r\n @for (item of columns; track item.key; let idx = $index) {\r\n <div class=\"fb-column-row\" [class.is-active]=\"idx === selectedIdx\">\r\n <span class=\"fb-column-row__type-icon\"><span class=\"msi\">{{ columnTypeSymbol(item.type) }}</span></span>\r\n <div class=\"fb-column-row__meta\">\r\n <div class=\"fb-column-row__label\">\r\n {{ item.label }}\r\n @if (item.validate?.required) {\r\n <span class=\"fb-column-row__req\">*</span>\r\n }\r\n </div>\r\n <div class=\"fb-column-row__sub mono\">{{ item.key }} \u00B7 {{ item.type }}</div>\r\n </div>\r\n <button type=\"button\" class=\"fb-icon-btn\" [attr.aria-label]=\"'core.component.form-builder.column-config' | translate\" (click)=\"editColumn(idx)\">\r\n <span class=\"msi\">edit</span>\r\n </button>\r\n <button type=\"button\" class=\"fb-icon-btn danger\" [attr.aria-label]=\"'core.component.form-builder.delete' | translate\" (click)=\"removeColumn(idx)\">\r\n <span class=\"msi\">delete</span>\r\n </button>\r\n </div>\r\n }\r\n <a class=\"fb-add-link\" (click)=\"addColumn()\">\r\n <span class=\"msi\">add</span>\r\n {{ 'core.component.form-builder.add-column' | translate }}\r\n </a>\r\n </div>\r\n }\r\n </div>\r\n</details>\r\n\r\n<sd-modal [title]=\"'core.component.form-builder.column-config' | translate\" width=\"600px\">\r\n @if (column) {\r\n <details class=\"fb-section\" open>\r\n <summary class=\"fb-section__head\">\r\n <span class=\"msi\">info</span>\r\n <span class=\"fb-section__title\">{{ 'core.component.form-builder.general' | translate }}</span>\r\n <span class=\"msi chev\">chevron_right</span>\r\n </summary>\r\n <div class=\"fb-section__body\">\r\n <attribute-input [form]=\"form\" [label]=\"'core.component.form-builder.field-key' | translate\" [(model)]=\"column.key\"></attribute-input>\r\n <attribute-input [form]=\"form\" [label]=\"'core.component.form-builder.column-name' | translate\" [(model)]=\"column.label\"></attribute-input>\r\n <attribute-input [form]=\"form\" [label]=\"'core.component.form-builder.width' | translate\" [(model)]=\"column.width\"></attribute-input>\r\n <attribute-switch [form]=\"form\" [label]=\"'core.component.form-builder.required' | translate\" [(model)]=\"column.validate!.required\"></attribute-switch>\r\n <attribute-select [form]=\"form\" [label]=\"'core.component.form-builder.data-type' | translate\" [(model)]=\"column.type\" [items]=\"types\"></attribute-select>\r\n </div>\r\n </details>\r\n @if (column.type === 'boolean') {\r\n <details class=\"fb-section\" open>\r\n <summary class=\"fb-section__head\">\r\n <span class=\"msi\">visibility</span>\r\n <span class=\"fb-section__title\">{{ 'core.component.form-builder.display' | translate }}</span>\r\n <span class=\"msi chev\">chevron_right</span>\r\n </summary>\r\n <div class=\"fb-section__body\">\r\n <attribute-input [form]=\"form\" [label]=\"'core.component.form-builder.show-when-true' | translate\" [(model)]=\"column.displayOnTrue\"></attribute-input>\r\n <attribute-input [form]=\"form\" [label]=\"'core.component.form-builder.show-when-false' | translate\" [(model)]=\"column.displayOnFalse\"></attribute-input>\r\n </div>\r\n </details>\r\n } @else if (column.type === 'values') {\r\n <attribute-selection\r\n [components]=\"components\"\r\n [variables]=\"variables\"\r\n [component]=\"column\"\r\n [(valuesKey)]=\"column.valuesKey\"\r\n [(values)]=\"column.values\"></attribute-selection>\r\n } @else if (column.type === 'file' || column.type === 'image') {\r\n <details class=\"fb-section\" open>\r\n <summary class=\"fb-section__head\">\r\n <span class=\"msi\">settings</span>\r\n <span class=\"fb-section__title\">{{ 'core.component.form-builder.parameter' | translate }}</span>\r\n <span class=\"msi chev\">chevron_right</span>\r\n </summary>\r\n <div class=\"fb-section__body\">\r\n <attribute-parameter [label]=\"'core.component.form-builder.assign-parameter' | translate\" [components]=\"components\" [(model)]=\"column!.args\"></attribute-parameter>\r\n </div>\r\n </details>\r\n }\r\n }\r\n <ng-container sdFooter>\r\n <sd-button prefixIcon=\"save\" [title]=\"'core.component.form-builder.save' | translate\" (click)=\"onConfirm()\" color=\"primary\"></sd-button>\r\n </ng-container>\r\n</sd-modal>\r\n", dependencies: [{ kind: "ngmodule", type: MatIconModule }, { kind: "ngmodule", type: MatDividerModule }, { kind: "component", type: SdAutocomplete$1, selector: "sd-autocomplete", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "valueField", "displayField", "disabledField", "limit", "cacheChecksum", "hyperlink", "items", "hideInlineError", "addable", "required", "disabled", "viewed", "clearable", "validator", "inlineError", "appearance", "model"], outputs: ["modelChange", "sdChange", "sdSelection", "sdAdd"] }, { kind: "component", type: SdButton, selector: "sd-button", inputs: ["autoId", "type", "color", "size", "fontSet", "title", "width", "tooltip", "prefixIcon", "suffixIcon", "disabled", "loading", "block", "htmlType"], outputs: ["click"] }, { kind: "component", type: SdModal, selector: "sd-modal", inputs: ["autoId", "title", "color", "width", "height", "view", "modalClass", "lazyLoadContent", "hideClose", "disableBackdropClose"], outputs: ["sdClosed"] }, { kind: "component", type: AttributeSelection, selector: "attribute-selection", inputs: ["components", "variables", "component", "valuesKey", "values"], outputs: ["valuesKeyChange", "valuesChange", "sdChange"] }, { kind: "component", type: AttributeInput, selector: "attribute-input", inputs: ["form", "label", "disabled", "model"], outputs: ["modelChange", "sdChange"] }, { kind: "component", type: AttributeSelect, selector: "attribute-select", inputs: ["form", "label", "disabled", "multiple", "items", "model"], outputs: ["modelChange", "sdChange"] }, { kind: "component", type: AttributeSwitch, selector: "attribute-switch", inputs: ["form", "label", "model"], outputs: ["modelChange"] }, { kind: "component", type: AttributeParameter, selector: "attribute-parameter", inputs: ["components", "label", "model"], outputs: ["modelChange"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
5451
5450
  }
5452
5451
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: AttributeTable, decorators: [{
5453
5452
  type: Component,
@@ -5741,7 +5740,7 @@ class ConfigureValidationComponent {
5741
5740
  alert: 'error',
5742
5741
  type,
5743
5742
  expression: {
5744
- key: SdUtilities.randomId(),
5743
+ key: Utilities.randomId(),
5745
5744
  type: 'combinator',
5746
5745
  combinator: '&&',
5747
5746
  conditions: [],
@@ -5767,7 +5766,7 @@ class ConfigureValidationComponent {
5767
5766
  this.modal?.close();
5768
5767
  };
5769
5768
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: ConfigureValidationComponent, deps: [{ token: i0.ChangeDetectorRef }, { token: SD_FORM_GENERIC_CONFIGURATION, optional: true }], target: i0.ɵɵFactoryTarget.Component });
5770
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: ConfigureValidationComponent, isStandalone: true, selector: "configure-validation", outputs: { accept: "accept" }, viewQueries: [{ propertyName: "modal", first: true, predicate: SdModal, descendants: true }], ngImport: i0, template: "<sd-modal [title]=\"'core.component.form-builder.config-validation-error-warning' | translate\" view=\"dialog\" width=\"800px\" #popupConfigureVariables>\r\n @for (validation of validations; track $index; let idx = $index) {\r\n <div class=\"mat-elevation-z2 position-relative p-8 mb-16\" style=\"border-radius: 8px;\">\r\n <div class=\"row mx-0\">\r\n <sd-select\r\n class=\"col-3\"\r\n [label]=\"'core.component.form-builder.message-type' | translate\"\r\n [(model)]=\"validation.alert\"\r\n [items]=\"alerts\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n required></sd-select>\r\n @if (validation.type === 'expression') {\r\n <sd-input class=\"col-9\" [form]=\"form\" [label]=\"'core.component.form-builder.message-display' | translate\" [(model)]=\"validation.message\" required></sd-input>\r\n <expression-builder class=\"col-12\" [label]=\"'core.component.form-builder.condition-expression' | translate\" [(model)]=\"validation.expression\" [attributes]=\"attributes\"></expression-builder>\r\n } @else if (validation.type === 'function') {\r\n <sd-select\r\n class=\"col-9\"\r\n [form]=\"form\"\r\n [(model)]=\"validation.code\"\r\n [items]=\"functions\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n required></sd-select>\r\n }\r\n <mat-icon class=\"c-close-icon\" (click)=\"removaValidation(idx)\">delete</mat-icon>\r\n <!-- <sd-button\r\n \r\n prefixIcon=\"delete\"\r\n (click)=\"removaValidation(idx)\"\r\n color=\"error\"\r\n type=\"link\"></sd-button> -->\r\n </div>\r\n </div>\r\n }\r\n <sd-button [title]=\"'core.component.form-builder.add-validation' | translate\" [matMenuTriggerFor]=\"menu\" prefixIcon=\"add\" color=\"primary\" type=\"link\"></sd-button>\r\n <mat-menu #menu=\"matMenu\">\r\n <button (click)=\"addValidation('expression')\" mat-menu-item type=\"button\">\r\n <!-- <mat-icon fontIcon=\"warning\"></mat-icon> -->\r\n <span>{{ 'core.component.form-builder.expression' | translate }}</span>\r\n </button>\r\n <button (click)=\"addValidation('function')\" mat-menu-item type=\"button\">\r\n <!-- <mat-icon fontIcon=\"error\"></mat-icon> -->\r\n <span>{{ 'core.component.form-builder.available-methods' | translate }}</span>\r\n </button>\r\n </mat-menu>\r\n <ng-container sdFooter>\r\n <sd-button type=\"fill\" [title]=\"'core.component.form-builder.save' | translate\" (click)=\"updateValidations()\" prefixIcon=\"save\" color=\"primary\"></sd-button>\r\n </ng-container>\r\n</sd-modal>\r\n", styles: [".text-primary{color:var(--sd-primary)!important}.bg-primary{background:var(--sd-primary)!important}.border-primary{border-color:var(--sd-primary)!important}.text-primary-light{color:var(--sd-primary-light)!important}.bg-primary-light{background:var(--sd-primary-light)!important}.border-primary-light{border-color:var(--sd-primary-light)!important}.text-primary-dark{color:var(--sd-primary-dark)!important}.bg-primary-dark{background:var(--sd-primary-dark)!important}.border-primary-dark{border-color:var(--sd-primary-dark)!important}.text-info{color:var(--sd-info)!important}.bg-info{background:var(--sd-info)!important}.border-info{border-color:var(--sd-info)!important}.text-info-light{color:var(--sd-info-light)!important}.bg-info-light{background:var(--sd-info-light)!important}.border-info-light{border-color:var(--sd-info-light)!important}.text-info-dark{color:var(--sd-info-dark)!important}.bg-info-dark{background:var(--sd-info-dark)!important}.border-info-dark{border-color:var(--sd-info-dark)!important}.text-success{color:var(--sd-success)!important}.bg-success{background:var(--sd-success)!important}.border-success{border-color:var(--sd-success)!important}.text-success-light{color:var(--sd-success-light)!important}.bg-success-light{background:var(--sd-success-light)!important}.border-success-light{border-color:var(--sd-success-light)!important}.text-success-dark{color:var(--sd-success-dark)!important}.bg-success-dark{background:var(--sd-success-dark)!important}.border-success-dark{border-color:var(--sd-success-dark)!important}.text-warning{color:var(--sd-warning)!important}.bg-warning{background:var(--sd-warning)!important}.border-warning{border-color:var(--sd-warning)!important}.text-warning-light{color:var(--sd-warning-light)!important}.bg-warning-light{background:var(--sd-warning-light)!important}.border-warning-light{border-color:var(--sd-warning-light)!important}.text-warning-dark{color:var(--sd-warning-dark)!important}.bg-warning-dark{background:var(--sd-warning-dark)!important}.border-warning-dark{border-color:var(--sd-warning-dark)!important}.text-error{color:var(--sd-error)!important}.bg-error{background:var(--sd-error)!important}.border-error{border-color:var(--sd-error)!important}.text-error-light{color:var(--sd-error-light)!important}.bg-error-light{background:var(--sd-error-light)!important}.border-error-light{border-color:var(--sd-error-light)!important}.text-error-dark{color:var(--sd-error-dark)!important}.bg-error-dark{background:var(--sd-error-dark)!important}.border-error-dark{border-color:var(--sd-error-dark)!important}.text-secondary{color:var(--sd-secondary)!important}.bg-secondary{background:var(--sd-secondary)!important}.border-secondary{border-color:var(--sd-secondary)!important}.text-secondary-light{color:var(--sd-secondary-light)!important}.bg-secondary-light{background:var(--sd-secondary-light)!important}.border-secondary-light{border-color:var(--sd-secondary-light)!important}.text-secondary-dark{color:var(--sd-secondary-dark)!important}.bg-secondary-dark{background:var(--sd-secondary-dark)!important}.border-secondary-dark{border-color:var(--sd-secondary-dark)!important}.text-light{color:var(--sd-light)!important}.bg-light{background:var(--sd-light)!important}.border-light{border-color:var(--sd-light)!important}.text-dark{color:var(--sd-dark)!important}.bg-dark{background:var(--sd-dark)!important}.border-dark{border-color:var(--sd-dark)!important}.text-black500{color:var(--sd-black500)!important}.bg-black500{background:var(--sd-black500)!important}.border-black500{border-color:var(--sd-black500)!important}.text-black400{color:var(--sd-black400)!important}.bg-black400{background:var(--sd-black400)!important}.border-black400{border-color:var(--sd-black400)!important}.text-black300{color:var(--sd-black300)!important}.bg-black300{background:var(--sd-black300)!important}.border-black300{border-color:var(--sd-black300)!important}.text-black200{color:var(--sd-black200)!important}.bg-black200{background:var(--sd-black200)!important}.border-black200{border-color:var(--sd-black200)!important}.text-black100{color:var(--sd-black100)!important}.bg-black100{background:var(--sd-black100)!important}.border-black100{border-color:var(--sd-black100)!important}.text-white{color:#fff!important}.bg-white{background:#fff!important}.border-white{border-color:#fff!important}.text-black{color:#000!important}.bg-black{background:#000!important}.border-black{border-color:#000!important}.c-close-icon{margin-left:auto;font-size:16px;width:16px;height:16px;cursor:pointer;border-radius:50%;color:var(--sd-error);top:4px;right:4px;position:absolute}.c-close-icon:hover{background-color:var(--sd-black200)}\n"], dependencies: [{ kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i1$4.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i1$4.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i1$4.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "component", type: SdModal, selector: "sd-modal", inputs: ["autoId", "title", "color", "width", "height", "view", "modalClass", "lazyLoadContent", "hideClose", "disableBackdropClose"], outputs: ["sdClosed"] }, { kind: "component", type: SdButton, selector: "sd-button", inputs: ["autoId", "type", "color", "size", "fontSet", "title", "width", "tooltip", "prefixIcon", "suffixIcon", "disabled", "loading", "block", "htmlType"], outputs: ["click"] }, { kind: "component", type: SdInput, selector: "sd-input", inputs: ["autoId", "name", "appearance", "floatLabel", "size", "form", "label", "helperText", "placeholder", "type", "hideInlineError", "blurOnEnter", "required", "readonly", "disabled", "viewed", "minlength", "maxlength", "pattern", "patternErrorMessage", "validator", "inlineError", "hyperlink", "tooltip", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "sdFocusForceBlur"] }, { kind: "component", type: SdSelect, selector: "sd-select", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "valueField", "displayField", "disabledField", "cacheChecksum", "limit", "hyperlink", "minWidthPanel", "hideInlineError", "required", "disabled", "viewed", "multiple", "validator", "inlineError", "appearance", "floatLabel", "items", "model"], outputs: ["modelChange", "sdChange", "sdSelection"] }, { kind: "component", type: ExpressionBuilderComponent, selector: "expression-builder", inputs: ["label", "attributes", "model"], outputs: ["modelChange", "sdChange", "edit"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5769
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: ConfigureValidationComponent, isStandalone: true, selector: "configure-validation", outputs: { accept: "accept" }, viewQueries: [{ propertyName: "modal", first: true, predicate: SdModal, descendants: true }], ngImport: i0, template: "<sd-modal [title]=\"'core.component.form-builder.config-validation-error-warning' | translate\" view=\"dialog\" width=\"800px\" #popupConfigureVariables>\r\n @for (validation of validations; track $index; let idx = $index) {\r\n <div class=\"mat-elevation-z2 position-relative p-8 mb-16\" style=\"border-radius: 8px;\">\r\n <div class=\"row mx-0\">\r\n <sd-select\r\n class=\"col-3\"\r\n [label]=\"'core.component.form-builder.message-type' | translate\"\r\n [(model)]=\"validation.alert\"\r\n [items]=\"alerts\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n required></sd-select>\r\n @if (validation.type === 'expression') {\r\n <sd-input class=\"col-9\" [form]=\"form\" [label]=\"'core.component.form-builder.message-display' | translate\" [(model)]=\"validation.message\" required></sd-input>\r\n <expression-builder class=\"col-12\" [label]=\"'core.component.form-builder.condition-expression' | translate\" [(model)]=\"validation.expression\" [attributes]=\"attributes\"></expression-builder>\r\n } @else if (validation.type === 'function') {\r\n <sd-select\r\n class=\"col-9\"\r\n [form]=\"form\"\r\n [(model)]=\"validation.code\"\r\n [items]=\"functions\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n required></sd-select>\r\n }\r\n <mat-icon class=\"c-close-icon\" (click)=\"removaValidation(idx)\">delete</mat-icon>\r\n <!-- <sd-button\r\n \r\n prefixIcon=\"delete\"\r\n (click)=\"removaValidation(idx)\"\r\n color=\"error\"\r\n type=\"link\"></sd-button> -->\r\n </div>\r\n </div>\r\n }\r\n <sd-button [title]=\"'core.component.form-builder.add-validation' | translate\" [matMenuTriggerFor]=\"menu\" prefixIcon=\"add\" color=\"primary\" type=\"link\"></sd-button>\r\n <mat-menu #menu=\"matMenu\">\r\n <button (click)=\"addValidation('expression')\" mat-menu-item type=\"button\">\r\n <!-- <mat-icon fontIcon=\"warning\"></mat-icon> -->\r\n <span>{{ 'core.component.form-builder.expression' | translate }}</span>\r\n </button>\r\n <button (click)=\"addValidation('function')\" mat-menu-item type=\"button\">\r\n <!-- <mat-icon fontIcon=\"error\"></mat-icon> -->\r\n <span>{{ 'core.component.form-builder.available-methods' | translate }}</span>\r\n </button>\r\n </mat-menu>\r\n <ng-container sdFooter>\r\n <sd-button type=\"fill\" [title]=\"'core.component.form-builder.save' | translate\" (click)=\"updateValidations()\" prefixIcon=\"save\" color=\"primary\"></sd-button>\r\n </ng-container>\r\n</sd-modal>\r\n", styles: [".text-primary{color:var(--sd-primary)!important}.bg-primary{background:var(--sd-primary)!important}.border-primary{border-color:var(--sd-primary)!important}.text-primary-light{color:var(--sd-primary-light)!important}.bg-primary-light{background:var(--sd-primary-light)!important}.border-primary-light{border-color:var(--sd-primary-light)!important}.text-primary-dark{color:var(--sd-primary-dark)!important}.bg-primary-dark{background:var(--sd-primary-dark)!important}.border-primary-dark{border-color:var(--sd-primary-dark)!important}.text-info{color:var(--sd-info)!important}.bg-info{background:var(--sd-info)!important}.border-info{border-color:var(--sd-info)!important}.text-info-light{color:var(--sd-info-light)!important}.bg-info-light{background:var(--sd-info-light)!important}.border-info-light{border-color:var(--sd-info-light)!important}.text-info-dark{color:var(--sd-info-dark)!important}.bg-info-dark{background:var(--sd-info-dark)!important}.border-info-dark{border-color:var(--sd-info-dark)!important}.text-success{color:var(--sd-success)!important}.bg-success{background:var(--sd-success)!important}.border-success{border-color:var(--sd-success)!important}.text-success-light{color:var(--sd-success-light)!important}.bg-success-light{background:var(--sd-success-light)!important}.border-success-light{border-color:var(--sd-success-light)!important}.text-success-dark{color:var(--sd-success-dark)!important}.bg-success-dark{background:var(--sd-success-dark)!important}.border-success-dark{border-color:var(--sd-success-dark)!important}.text-warning{color:var(--sd-warning)!important}.bg-warning{background:var(--sd-warning)!important}.border-warning{border-color:var(--sd-warning)!important}.text-warning-light{color:var(--sd-warning-light)!important}.bg-warning-light{background:var(--sd-warning-light)!important}.border-warning-light{border-color:var(--sd-warning-light)!important}.text-warning-dark{color:var(--sd-warning-dark)!important}.bg-warning-dark{background:var(--sd-warning-dark)!important}.border-warning-dark{border-color:var(--sd-warning-dark)!important}.text-error{color:var(--sd-error)!important}.bg-error{background:var(--sd-error)!important}.border-error{border-color:var(--sd-error)!important}.text-error-light{color:var(--sd-error-light)!important}.bg-error-light{background:var(--sd-error-light)!important}.border-error-light{border-color:var(--sd-error-light)!important}.text-error-dark{color:var(--sd-error-dark)!important}.bg-error-dark{background:var(--sd-error-dark)!important}.border-error-dark{border-color:var(--sd-error-dark)!important}.text-secondary{color:var(--sd-secondary)!important}.bg-secondary{background:var(--sd-secondary)!important}.border-secondary{border-color:var(--sd-secondary)!important}.text-secondary-light{color:var(--sd-secondary-light)!important}.bg-secondary-light{background:var(--sd-secondary-light)!important}.border-secondary-light{border-color:var(--sd-secondary-light)!important}.text-secondary-dark{color:var(--sd-secondary-dark)!important}.bg-secondary-dark{background:var(--sd-secondary-dark)!important}.border-secondary-dark{border-color:var(--sd-secondary-dark)!important}.text-light{color:var(--sd-light)!important}.bg-light{background:var(--sd-light)!important}.border-light{border-color:var(--sd-light)!important}.text-dark{color:var(--sd-dark)!important}.bg-dark{background:var(--sd-dark)!important}.border-dark{border-color:var(--sd-dark)!important}.text-black500{color:var(--sd-black500)!important}.bg-black500{background:var(--sd-black500)!important}.border-black500{border-color:var(--sd-black500)!important}.text-black400{color:var(--sd-black400)!important}.bg-black400{background:var(--sd-black400)!important}.border-black400{border-color:var(--sd-black400)!important}.text-black300{color:var(--sd-black300)!important}.bg-black300{background:var(--sd-black300)!important}.border-black300{border-color:var(--sd-black300)!important}.text-black200{color:var(--sd-black200)!important}.bg-black200{background:var(--sd-black200)!important}.border-black200{border-color:var(--sd-black200)!important}.text-black100{color:var(--sd-black100)!important}.bg-black100{background:var(--sd-black100)!important}.border-black100{border-color:var(--sd-black100)!important}.text-white{color:#fff!important}.bg-white{background:#fff!important}.border-white{border-color:#fff!important}.text-black{color:#000!important}.bg-black{background:#000!important}.border-black{border-color:#000!important}.c-close-icon{margin-left:auto;font-size:16px;width:16px;height:16px;cursor:pointer;border-radius:50%;color:var(--sd-error);top:4px;right:4px;position:absolute}.c-close-icon:hover{background-color:var(--sd-black200)}\n"], dependencies: [{ kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i1$4.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i1$4.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i1$4.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "component", type: SdModal, selector: "sd-modal", inputs: ["autoId", "title", "color", "width", "height", "view", "modalClass", "lazyLoadContent", "hideClose", "disableBackdropClose"], outputs: ["sdClosed"] }, { kind: "component", type: SdButton, selector: "sd-button", inputs: ["autoId", "type", "color", "size", "fontSet", "title", "width", "tooltip", "prefixIcon", "suffixIcon", "disabled", "loading", "block", "htmlType"], outputs: ["click"] }, { kind: "component", type: SdInput, selector: "sd-input", inputs: ["autoId", "name", "appearance", "floatLabel", "size", "form", "label", "helperText", "placeholder", "type", "hideInlineError", "blurOnEnter", "required", "readonly", "disabled", "viewed", "minlength", "maxlength", "pattern", "patternErrorMessage", "validator", "inlineError", "hyperlink", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "cleared", "sdFocusForceBlur"] }, { kind: "component", type: SdSelect, selector: "sd-select", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "valueField", "displayField", "disabledField", "cacheChecksum", "limit", "hyperlink", "minWidthPanel", "hideInlineError", "required", "disabled", "viewed", "multiple", "clearable", "validator", "inlineError", "appearance", "floatLabel", "items", "model"], outputs: ["modelChange", "sdChange", "sdSelection"] }, { kind: "component", type: ExpressionBuilderComponent, selector: "expression-builder", inputs: ["label", "attributes", "model"], outputs: ["modelChange", "sdChange", "edit"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5771
5770
  }
5772
5771
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: ConfigureValidationComponent, decorators: [{
5773
5772
  type: Component,
@@ -5787,7 +5786,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImpo
5787
5786
  /* eslint-disable @angular-eslint/no-input-rename */
5788
5787
  /* eslint-disable @typescript-eslint/no-explicit-any */
5789
5788
  /* eslint-disable @typescript-eslint/no-unused-vars */
5790
- class SdFormBuilder extends SdBaseSecureComponent {
5789
+ class SdFormBuilder {
5791
5790
  // ── viewChild signals (Angular 17+) ────────────────────────────────────
5792
5791
  popupViewJSON = viewChild('popupViewJSON');
5793
5792
  popupConfigureVariables = viewChild('popupConfigureVariables');
@@ -5810,7 +5809,10 @@ class SdFormBuilder extends SdBaseSecureComponent {
5810
5809
  paletteSearch = signal('');
5811
5810
  paletteGroups = computed(() => {
5812
5811
  const term = this.paletteSearch().trim().toLowerCase();
5813
- const match = (c) => !term || c.name.toLowerCase().includes(term) || c.type.toLowerCase().includes(term);
5812
+ // why: trong chế độ Detail group, ẩn item 'group' khỏi palette — không cho group lồng group.
5813
+ const inGroup = !!this.editingGroupId();
5814
+ const match = (c) => (!inGroup || c.type !== 'group') &&
5815
+ (!term || c.name.toLowerCase().includes(term) || c.type.toLowerCase().includes(term));
5814
5816
  const buckets = { basic: [], choice: [], advanced: [], layout: [] };
5815
5817
  for (const c of this.formBuilderComponents) {
5816
5818
  if (match(c))
@@ -5834,6 +5836,23 @@ class SdFormBuilder extends SdBaseSecureComponent {
5834
5836
  clonedVariables = [];
5835
5837
  selectedComponent = signal(undefined);
5836
5838
  isPreview = signal(false);
5839
+ // ── group drill-in (Detail) ─────────────────────────────────────────────
5840
+ // why: bỏ hẳn drag/drop trong group. Thay vào đó "Detail" 1 group → mở canvas
5841
+ // riêng chỉ thiết kế children của group đó (cùng tooling: palette/reorder/resize/
5842
+ // attribute), KHÔNG cho group lồng group. OK = giữ, Cancel = revert. Depth tối đa
5843
+ // 1 (group không chứa group) nên chỉ cần 1 con trỏ editingGroupId, không cần stack.
5844
+ editingGroupId = signal(undefined);
5845
+ /** Group đang được Detail (tìm ở TOP-LEVEL theo id), undefined = đang ở canvas chính. */
5846
+ editingGroup = computed(() => {
5847
+ const id = this.editingGroupId();
5848
+ if (!id)
5849
+ return undefined;
5850
+ return this.components.find(c => c.id === id && c.type === 'group');
5851
+ });
5852
+ /** Snapshot (deep clone JSON) của group.components khi vào Detail — phục vụ Cancel. */
5853
+ #groupSnapshot;
5854
+ /** Mảng components đang được canvas thao tác: children của group khi Detail, ngược lại là top-level. */
5855
+ #scope = () => this.editingGroup()?.components ?? this.components;
5837
5856
  /** Signal toàn cục: TRUE khi BẤT KỲ cdkDrag nào đang active (palette, canvas, group, resize).
5838
5857
  * Trigger class `.fb-shell--dragging` để ẩn hover/actions/resize toàn diện, không phụ thuộc :has(). */
5839
5858
  isAnyDragging = signal(false);
@@ -5873,7 +5892,6 @@ class SdFormBuilder extends SdBaseSecureComponent {
5873
5892
  #validationsChanges = new Subject();
5874
5893
  #subscription = new Subscription();
5875
5894
  constructor() {
5876
- super();
5877
5895
  // Khi input `formGeneric` ref thay đổi: clone components/variables/validations
5878
5896
  // sang local arrays và bắn subject tương ứng cho stream debounced (syncRows).
5879
5897
  // untracked() bao bọc các mutation để không tạo cycle (effect chỉ tracks formGeneric()).
@@ -5905,6 +5923,9 @@ class SdFormBuilder extends SdBaseSecureComponent {
5905
5923
  this.#subscription.unsubscribe();
5906
5924
  }
5907
5925
  addComponent = (item, index) => {
5926
+ // why: không cho thêm group khi đang Detail trong 1 group (tránh group lồng group).
5927
+ if (item.type === 'group' && this.editingGroupId())
5928
+ return;
5908
5929
  const id = GenerateId();
5909
5930
  let newComponent;
5910
5931
  if (item.type === 'group') {
@@ -5947,14 +5968,15 @@ class SdFormBuilder extends SdBaseSecureComponent {
5947
5968
  properties: {},
5948
5969
  };
5949
5970
  }
5971
+ const scope = this.#scope();
5950
5972
  if (index !== undefined) {
5951
- this.components.splice(index, 0, newComponent);
5973
+ scope.splice(index, 0, newComponent);
5952
5974
  }
5953
5975
  else {
5954
- this.components.push(newComponent);
5976
+ scope.push(newComponent);
5955
5977
  }
5956
5978
  this.#recountTabIndex();
5957
- const created = this.components?.find(component => component.id === id);
5979
+ const created = scope.find(component => component.id === id);
5958
5980
  this.selectedComponent.set(created);
5959
5981
  this.selectComponent(created);
5960
5982
  this.#ref.markForCheck();
@@ -5995,43 +6017,42 @@ class SdFormBuilder extends SdBaseSecureComponent {
5995
6017
  setMode = (preview) => {
5996
6018
  this.isPreview.set(preview);
5997
6019
  };
5998
- /** Nested drop handler when dragging a palette item or canvas item INTO a group body. */
5999
- dropInGroup = (event, group) => {
6000
- if (event.previousContainer === event.container) {
6001
- moveItemInArray(event.container.data, event.previousIndex, event.currentIndex);
6002
- return;
6003
- }
6004
- const dropped = event.previousContainer.data[event.previousIndex];
6005
- if (dropped && 'symbol' in dropped) {
6006
- // Palette → group: tạo component mới rồi push vào group.components.
6007
- const item = dropped;
6008
- if (item.type === 'group') {
6009
- this.#notifyService.warning('Group lồng group chưa được hỗ trợ.');
6010
- return;
6011
- }
6012
- const newComponent = this.#createComponentFromPalette(item);
6013
- group.components.splice(event.currentIndex, 0, newComponent);
6014
- this.selectedComponent.set(newComponent);
6015
- this.#ref.markForCheck();
6016
- }
6017
- else {
6018
- // Canvas group: chuyển instance hiện có.
6019
- const component = dropped;
6020
- if (component.type === 'group') {
6021
- this.#notifyService.warning('Group lồng group chưa được hỗ trợ.');
6022
- return;
6023
- }
6024
- transferArrayItem(event.previousContainer.data, event.container.data, event.previousIndex, event.currentIndex);
6025
- this.#syncRowsToComponents();
6020
+ // ── Group drill-in (Detail) navigation ──────────────────────────────────
6021
+ /** Mở canvas thiết kế riêng cho 1 group (chỉ children của nó). Snapshot để Cancel revert. */
6022
+ enterGroupEdit = (group) => {
6023
+ this.#groupSnapshot = JSON.stringify(group.components ?? []);
6024
+ this.editingGroupId.set(group.id);
6025
+ this.selectedComponent.set(undefined);
6026
+ this.#recountTabIndex();
6027
+ this.#ref.markForCheck();
6028
+ };
6029
+ /** OK: giữ thay đổi children, quay về canvas chính. */
6030
+ confirmGroupEdit = () => {
6031
+ this.#groupSnapshot = undefined;
6032
+ this.editingGroupId.set(undefined);
6033
+ this.selectedComponent.set(undefined);
6034
+ this.#syncComponentsToRows();
6035
+ this.#ref.markForCheck();
6036
+ };
6037
+ /** Cancel: revert children về snapshot lúc vào Detail, rồi quay về canvas chính. */
6038
+ cancelGroupEdit = () => {
6039
+ const g = this.editingGroup();
6040
+ if (g && this.#groupSnapshot !== undefined) {
6041
+ g.components = JSON.parse(this.#groupSnapshot);
6026
6042
  }
6043
+ this.#groupSnapshot = undefined;
6044
+ this.editingGroupId.set(undefined);
6045
+ this.selectedComponent.set(undefined);
6046
+ this.#syncComponentsToRows();
6047
+ this.#ref.markForCheck();
6027
6048
  };
6028
- /** Quick-add break sau row chỉ định.
6029
- * Tính component index cuối cùng của row đó trong this.components, splice break vào sau.
6030
- * Không qua palette — chỉ là utility action bám theo row trên canvas.
6049
+ /** Quick-add break sau row chỉ định (theo scope hiện tại — top-level hoặc group).
6050
+ * Tính component index cuối cùng của row đó trong scope, splice break vào sau.
6031
6051
  */
6032
6052
  insertBreakAfter = (row) => {
6053
+ const scope = this.#scope();
6033
6054
  const lastItemId = row.items[row.items.length - 1]?.id;
6034
- const insertAfter = lastItemId ? this.components.findIndex(c => c.id === lastItemId) : this.components.length - 1;
6055
+ const insertAfter = lastItemId ? scope.findIndex(c => c.id === lastItemId) : scope.length - 1;
6035
6056
  const newBreak = {
6036
6057
  id: GenerateId(),
6037
6058
  key: GenerateKey(),
@@ -6042,19 +6063,21 @@ class SdFormBuilder extends SdBaseSecureComponent {
6042
6063
  disabled: false,
6043
6064
  properties: {},
6044
6065
  };
6045
- this.components.splice(insertAfter + 1, 0, newBreak);
6066
+ scope.splice(insertAfter + 1, 0, newBreak);
6046
6067
  this.#recountTabIndex();
6047
6068
  this.#ref.markForCheck();
6048
6069
  };
6049
- /** Remove a child component from a group's nested list. */
6050
- removeFromGroup = (group, id) => {
6051
- group.components = group.components.filter(c => c.id !== id);
6070
+ /** Xoá component theo scope hiện tại (top-level hoặc children của group đang Detail). */
6071
+ removeComponent = (id) => {
6072
+ const g = this.editingGroup();
6073
+ if (g) {
6074
+ g.components = g.components.filter((t) => t.id !== id);
6075
+ }
6076
+ else {
6077
+ this.components = this.components.filter((t) => t.id !== id);
6078
+ }
6052
6079
  if (this.selectedComponent()?.id === id)
6053
6080
  this.selectedComponent.set(undefined);
6054
- this.#ref.markForCheck();
6055
- };
6056
- removeComponent = (id) => {
6057
- this.components = this.components.filter((t) => t.id !== id);
6058
6081
  this.#recountTabIndex();
6059
6082
  };
6060
6083
  selectComponent = (item) => {
@@ -6073,10 +6096,6 @@ class SdFormBuilder extends SdBaseSecureComponent {
6073
6096
  }
6074
6097
  };
6075
6098
  drop = (event) => {
6076
- if (this.#dropIntoGroupAtPointer(event)) {
6077
- this.#recountTabIndex();
6078
- return;
6079
- }
6080
6099
  if (event.previousContainer === event.container) {
6081
6100
  moveItemInArray(event.container.data, event.previousIndex, event.currentIndex);
6082
6101
  this.#syncRowsToComponents();
@@ -6084,7 +6103,7 @@ class SdFormBuilder extends SdBaseSecureComponent {
6084
6103
  if (!event.isPointerOverContainer) {
6085
6104
  const dragItemId = event.item.element.nativeElement.id;
6086
6105
  if (dragItemId) {
6087
- const dragItem = this.components.find((t) => t.id === dragItemId);
6106
+ const dragItem = this.#scope().find((t) => t.id === dragItemId);
6088
6107
  this.xuLyKeoCheo(dragItem);
6089
6108
  }
6090
6109
  }
@@ -6153,8 +6172,9 @@ class SdFormBuilder extends SdBaseSecureComponent {
6153
6172
  return false;
6154
6173
  };
6155
6174
  #recountTabIndex = () => {
6156
- // Tránh dùng map vì nó sẽ sinh ra reference mới
6157
- this.components.forEach((item, index) => {
6175
+ // Tránh dùng map vì nó sẽ sinh ra reference mới. Chạy theo scope hiện tại (top-level hoặc group).
6176
+ const scope = this.#scope();
6177
+ scope.forEach((item, index) => {
6158
6178
  if (item.layout) {
6159
6179
  item.layout.row = `${index + 1}`;
6160
6180
  item.layout.columns = item.layout?.columns || '12';
@@ -6168,15 +6188,15 @@ class SdFormBuilder extends SdBaseSecureComponent {
6168
6188
  });
6169
6189
  const sel = this.selectedComponent();
6170
6190
  if (sel && sel?.layout?.row) {
6171
- sel.layout.row = this.components.find(t => t.id === sel.id)?.layout?.row;
6191
+ sel.layout.row = scope.find(t => t.id === sel.id)?.layout?.row;
6172
6192
  }
6173
6193
  this.#syncComponentsToRows();
6174
6194
  };
6175
- // Hàm xử lý chuyển đổi components -> dragDropRows
6195
+ // Hàm xử lý chuyển đổi components (scope hiện tại) -> dragDropRows
6176
6196
  // Dựa vào columns của component để quyết định dragDropRows có bao nhiêu items trên row, đảm bảo columns tổng số items <= 12
6177
6197
  #syncComponentsToRows = () => {
6178
6198
  this.dragDropRows = [];
6179
- for (const component of this.components) {
6199
+ for (const component of this.#scope()) {
6180
6200
  // lấy dòng cuối cùng của dragDropList
6181
6201
  let lastRow = { rowIndex: this.dragDropRows.length, items: [] };
6182
6202
  if (this.dragDropRows.length) {
@@ -6197,99 +6217,18 @@ class SdFormBuilder extends SdBaseSecureComponent {
6197
6217
  }
6198
6218
  }
6199
6219
  };
6200
- // Hàm xử lý chuyển đổi dragDropRows -> components
6220
+ // Hàm xử lý chuyển đổi dragDropRows -> components (ghi về scope hiện tại)
6201
6221
  // Vì khi kéo thả sẽ thay đổi vị trí, do đó cần sắp xếp lại components
6202
6222
  #syncRowsToComponents = () => {
6203
6223
  // rải data ma trận droplist => schema.components
6204
- this.components = this.dragDropRows?.map(e => e.items)?.reduce((current, next) => [...current, ...next], []) || [];
6205
- };
6206
- #createComponentFromPalette = (item) => {
6207
- const id = GenerateId();
6208
- if (item.type === 'group') {
6209
- return {
6210
- id,
6211
- type: 'group',
6212
- label: 'Group',
6213
- layout: { columns: '12' },
6214
- components: [],
6215
- properties: {
6216
- icon: item.symbol || 'category',
6217
- color: 'primary',
6218
- },
6219
- };
6220
- }
6221
- if (item.type === 'break') {
6222
- return {
6223
- id,
6224
- key: GenerateKey(),
6225
- type: 'break',
6226
- label: 'Break',
6227
- layout: { columns: '12' },
6228
- validate: {},
6229
- disabled: false,
6230
- properties: {},
6231
- };
6232
- }
6233
- return {
6234
- id,
6235
- key: GenerateKey(),
6236
- type: item.type,
6237
- label: item.type,
6238
- layout: { columns: '12' },
6239
- validate: { required: false },
6240
- disabled: false,
6241
- properties: {},
6242
- };
6243
- };
6244
- #dropIntoGroupAtPointer = (event) => {
6245
- const dropPoint = event.dropPoint || this.lastDragPointer;
6246
- if (!dropPoint)
6247
- return false;
6248
- const bodyEl = document.elementFromPoint(dropPoint.x, dropPoint.y)?.closest('.fb-group__body');
6249
- const groupEl = bodyEl?.closest('.fb-group');
6250
- const group = groupEl?.id ? this.#findGroupById(groupEl.id) : undefined;
6251
- if (!group || event.container.data === group.components)
6252
- return false;
6253
- const dropped = event.previousContainer.data[event.previousIndex];
6254
- if (!dropped)
6255
- return false;
6256
- if ('symbol' in dropped) {
6257
- const paletteItem = dropped;
6258
- if (paletteItem.type === 'group') {
6259
- this.#notifyService.warning('Group lồng group chưa được hỗ trợ.');
6260
- return true;
6261
- }
6262
- const created = this.#createComponentFromPalette(paletteItem);
6263
- group.components.push(created);
6264
- this.selectedComponent.set(created);
6224
+ const flat = this.dragDropRows?.map(e => e.items)?.reduce((current, next) => [...current, ...next], []) || [];
6225
+ const g = this.editingGroup();
6226
+ if (g) {
6227
+ g.components = flat;
6265
6228
  }
6266
6229
  else {
6267
- if (!('type' in dropped))
6268
- return false;
6269
- const component = dropped;
6270
- if (component.type === 'group') {
6271
- this.#notifyService.warning('Group lồng group chưa được hỗ trợ.');
6272
- return true;
6273
- }
6274
- transferArrayItem(event.previousContainer.data, group.components, event.previousIndex, group.components.length);
6275
- this.#syncRowsToComponents();
6276
- this.selectedComponent.set(component);
6230
+ this.components = flat;
6277
6231
  }
6278
- this.#ref.markForCheck();
6279
- return true;
6280
- };
6281
- #findGroupById = (id, items = this.components) => {
6282
- for (const item of items) {
6283
- if (item.type === 'group') {
6284
- const group = item;
6285
- if (group.id === id)
6286
- return group;
6287
- const nested = this.#findGroupById(id, group.components || []);
6288
- if (nested)
6289
- return nested;
6290
- }
6291
- }
6292
- return undefined;
6293
6232
  };
6294
6233
  changeSizeControl = async (event, item, items, currentIndex) => {
6295
6234
  // const totalColumnInRow = items.map(k => k.layout.columns).reduce((acc, curr) => acc + parseInt(curr, 0), 0);
@@ -6324,35 +6263,6 @@ class SdFormBuilder extends SdBaseSecureComponent {
6324
6263
  dragEndChangeSizeControl = (event) => {
6325
6264
  this.#recountTabIndex();
6326
6265
  };
6327
- /** Resize a child item INSIDE a group.
6328
- * Math: lấy bounding của chính item, tính (mouse.x - item.left) / colWidth = newCols.
6329
- * Cách này đúng cho cả flex-wrap (item ở wrap row 2+) vì dùng item-relative,
6330
- * không phải sum-of-all-preceding-cols (sai khi item wrap xuống row mới).
6331
- */
6332
- changeSizeChildInGroup = (event, child, group) => {
6333
- const groupEl = document.getElementById(group.id);
6334
- const bodyEl = groupEl?.querySelector('.fb-group__body');
6335
- const childEl = document.getElementById(child.id);
6336
- if (!bodyEl || !childEl)
6337
- return;
6338
- const bodyRect = bodyEl.getBoundingClientRect();
6339
- const childRect = childEl.getBoundingClientRect();
6340
- // Width của 1 col trong group body (loại trừ padding nếu có).
6341
- const bodyStyle = getComputedStyle(bodyEl);
6342
- const padL = parseFloat(bodyStyle.paddingLeft) || 0;
6343
- const padR = parseFloat(bodyStyle.paddingRight) || 0;
6344
- const colWidth = (bodyRect.width - padL - padR) / 12;
6345
- if (colWidth <= 0)
6346
- return;
6347
- // Mới: new right edge = mouse.x; new width = mouse.x - child.left
6348
- const newWidth = event.pointerPosition.x - childRect.left;
6349
- let newCols = Math.round(newWidth / colWidth);
6350
- if (newCols < 2)
6351
- newCols = 2;
6352
- if (newCols > 12)
6353
- newCols = 12;
6354
- child.layout.columns = `${newCols}`;
6355
- };
6356
6266
  onChangeViewed = (component) => {
6357
6267
  component.properties.viewed = !component.properties.viewed;
6358
6268
  // Emit khi có sự thay đổi để control và attribute lắng nghe và render lại
@@ -6363,14 +6273,15 @@ class SdFormBuilder extends SdBaseSecureComponent {
6363
6273
  // Emit khi có sự thay đổi để control và attribute lắng nghe và render lại
6364
6274
  this.#builderService.componentEmitters.next(component);
6365
6275
  };
6366
- // Duplicate component nhưng sẽ clear id và key để tránh trùng lặp
6276
+ // Duplicate component nhưng sẽ clear id và key để tránh trùng lặp (theo scope hiện tại)
6367
6277
  onDuplicate = (component) => {
6368
6278
  const clonedComponent = JSON.parse(JSON.stringify(component));
6369
6279
  clonedComponent.id = GenerateId();
6370
6280
  clonedComponent.key = GenerateKey();
6371
- this.components.push(clonedComponent);
6281
+ const scope = this.#scope();
6282
+ scope.push(clonedComponent);
6372
6283
  this.#recountTabIndex();
6373
- const created = this.components?.find(t => t.id === clonedComponent.id);
6284
+ const created = scope.find(t => t.id === clonedComponent.id);
6374
6285
  this.selectedComponent.set(created);
6375
6286
  this.selectComponent(created);
6376
6287
  this.#ref.markForCheck();
@@ -6409,7 +6320,7 @@ class SdFormBuilder extends SdBaseSecureComponent {
6409
6320
  };
6410
6321
  addVariables = () => {
6411
6322
  this.clonedVariables.push({
6412
- id: SdUtilities$1.randomId(),
6323
+ id: Utilities.randomId(),
6413
6324
  key: '',
6414
6325
  label: '',
6415
6326
  });
@@ -6470,9 +6381,9 @@ class SdFormBuilder extends SdBaseSecureComponent {
6470
6381
  }
6471
6382
  };
6472
6383
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdFormBuilder, deps: [], target: i0.ɵɵFactoryTarget.Component });
6473
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", 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.\r\n [class.fb-shell--dragging]: JS-driven flag \u0111\u1EC3 \u1EA9n hover/actions/resize to\u00E0n di\u1EC7n\r\n trong l\u00FAc drag (CSS :has() kh\u00F4ng b\u1EAFt 100% tr\u01B0\u1EDDng h\u1EE3p v\u1EDBi CDK preview clone). -->\r\n<div class=\"fb-shell\" cdkDropListGroup [class.fb-shell--dragging]=\"isAnyDragging()\">\r\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 -->\r\n <aside class=\"fb-palette\">\r\n <div class=\"fb-panel-header\">\r\n <div class=\"fb-panel-header__title\">{{ 'core.component.form-builder.components' | translate }}</div>\r\n </div>\r\n\r\n <!-- Search pill -->\r\n <div class=\"fb-palette__search\">\r\n <span class=\"msi\" aria-hidden=\"true\">search</span>\r\n <input\r\n type=\"text\"\r\n [placeholder]=\"'core.component.form-builder.search-components' | translate\"\r\n [value]=\"paletteSearch()\"\r\n (input)=\"paletteSearch.set($any($event.target).value)\" />\r\n <span class=\"kbd\">\u2318K</span>\r\n </div>\r\n\r\n <div class=\"fb-palette__scroll\">\r\n @for (group of paletteGroups(); track group.key) {\r\n <div class=\"fb-palette__group-label\">{{ group.label }}</div>\r\n <div\r\n class=\"fb-palette__list\"\r\n cdkDropList\r\n [cdkDropListData]=\"group.items\"\r\n [cdkDropListEnterPredicate]=\"noReturnPredicate\">\r\n @for (formBuilderComponent of group.items; track formBuilderComponent.type) {\r\n <div\r\n cdkDrag\r\n class=\"fb-palette-item\"\r\n [matTooltip]=\"formBuilderComponent.name\"\r\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>\r\n <span class=\"fb-palette-item__name\">{{ formBuilderComponent.name }}</span>\r\n <span class=\"fb-palette-item__drag\"><span class=\"msi\">drag_indicator</span></span>\r\n <!-- Generic drag preview: ch\u1EC9 icon (puck), label \u1EA9n -->\r\n <div *cdkDragPreview class=\"fb-drag-preview\"></div>\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n </aside>\r\n\r\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 -->\r\n <section class=\"fb-canvas\">\r\n <header class=\"fb-canvas__header\">\r\n <div class=\"fb-canvas__title\">\r\n <span>{{ 'core.component.form-builder.layout' | translate }}</span>\r\n </div>\r\n <div class=\"fb-canvas__actions\">\r\n <!-- Toolbar cluster: view JSON / variables / validation -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-icon-btn\"\r\n [matTooltip]=\"'core.component.form-builder.view-json' | translate\"\r\n (click)=\"viewJSON()\">\r\n <span class=\"msi\">data_object</span>\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-icon-btn\"\r\n [matTooltip]=\"'core.component.form-builder.config-variable' | translate\"\r\n (click)=\"configureVariables()\">\r\n <span class=\"msi\">data_array</span>\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-icon-btn\"\r\n [matTooltip]=\"'core.component.form-builder.config-validation-warning-error' | translate\"\r\n (click)=\"openConfigureValidation()\">\r\n <span class=\"msi\">rule</span>\r\n </button>\r\n\r\n <span class=\"fb-divider\"></span>\r\n\r\n <!-- M3 segmented Design/Preview toggle -->\r\n <div class=\"fb-mode-toggle\" role=\"tablist\">\r\n <button\r\n type=\"button\"\r\n role=\"tab\"\r\n [class.is-active]=\"!isPreview()\"\r\n (click)=\"setMode(false)\">\r\n <span class=\"msi\">edit</span>\r\n <span>{{ 'core.component.form-builder.design' | translate }}</span>\r\n </button>\r\n <button\r\n type=\"button\"\r\n role=\"tab\"\r\n [class.is-active]=\"isPreview()\"\r\n (click)=\"setMode(true)\">\r\n <span class=\"msi\">visibility</span>\r\n <span>{{ 'core.component.form-builder.preview' | translate }}</span>\r\n </button>\r\n </div>\r\n </div>\r\n </header>\r\n\r\n <div\r\n class=\"fb-canvas__body\"\r\n [class.is-preview]=\"isPreview()\"\r\n id=\"frmComponent\">\r\n @if (isPreview()) {\r\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 -->\r\n <div class=\"fb-preview-card\">\r\n <div class=\"fb-preview-card__chip\">\r\n <span class=\"msi\">visibility</span>\r\n {{ 'core.component.form-builder.preview-runtime' | translate }}\r\n </div>\r\n <sd-form-render [configuration]=\"{ components: this.components, validations: this.validations }\"></sd-form-render>\r\n <div class=\"fb-preview-card__footer\">\r\n <sd-button type=\"outline\" [title]=\"'core.component.form-builder.cancel' | translate\" (click)=\"setMode(false)\"></sd-button>\r\n <sd-button type=\"fill\" color=\"primary\" [title]=\"'core.component.form-builder.submit' | translate\" (click)=\"onValidate()\"></sd-button>\r\n </div>\r\n </div>\r\n } @else {\r\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 -->\r\n @if (!dragDropRows.length) {\r\n <div class=\"fb-empty\">\r\n <div class=\"fb-empty__art\"><span class=\"msi\">dashboard_customize</span></div>\r\n <div class=\"fb-empty__title\">{{ 'core.component.form-builder.empty-title' | translate }}</div>\r\n <div class=\"fb-empty__hint\">\r\n {{ 'core.component.form-builder.empty-hint' | translate }}\r\n <span class=\"fb-empty__kbd\"><span class=\"msi\">keyboard</span>\u2318 K</span>\r\n </div>\r\n </div>\r\n }\r\n\r\n <div\r\n cdkDropList\r\n cdkDropListOrientation=\"vertical\"\r\n [cdkDropListData]=\"dragDropRows\"\r\n (cdkDropListDropped)=\"drop($event)\"\r\n (click)=\"clickFormContentEmpty()\"\r\n aria-hidden=\"true\">\r\n @for (row of dragDropRows; track row.rowIndex) {\r\n <div\r\n cdkDrag\r\n class=\"fb-row\"\r\n (mouseover)=\"onMouseover($event, row)\"\r\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\">\r\n <div class=\"fb-row__drag\" [matTooltip]=\"'core.component.form-builder.drag-vertically' | translate\" cdkDragHandle>\r\n <span class=\"msi\">drag_indicator</span>\r\n </div>\r\n <!-- Generic puck preview -->\r\n <div *cdkDragPreview class=\"fb-drag-preview\"></div>\n <!-- Quick-add break button \u2014 show on row hover, insert break after this row -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-row-break-btn\"\r\n [matTooltip]=\"'core.component.form-builder.break' | translate\"\r\n matTooltipPosition=\"right\"\r\n (click)=\"insertBreakAfter(row); $event.stopPropagation()\">\r\n <span class=\"msi\">horizontal_rule</span>\r\n </button>\r\n\r\n <div\r\n class=\"fb-row__items\"\r\n style=\"display: flex; flex: 1; gap: var(--gap-2);\"\r\n cdkDropList\r\n cdkDropListOrientation=\"horizontal\"\r\n [cdkDropListData]=\"row.items\"\r\n (cdkDropListDropped)=\"drop($event)\">\r\n\r\n @for (item of row.items; track item.id; let i = $index) {\r\n <!-- Group component renders as outlined card with colored header bar -->\r\n @if (item.type === 'group') {\r\n @let g = $any(item);\r\n @let cv = groupColorVars(g.properties?.color);\r\n <div\r\n cdkDrag\r\n [id]=\"item.id\"\r\n class=\"fb-group column-{{ item.layout?.columns || 12 }}\"\r\n [class.fb-item]=\"false\"\r\n [class.is-selected]=\"item.id === selectedComponent()?.id\"\r\n [class.is-hidden]=\"item.properties?.hidden\"\r\n [style.flex]=\"'0 0 calc(' + (+(item.layout?.columns || 12)) + '/12 * 100% - var(--gap-2))'\"\r\n [style.maxWidth]=\"'calc(' + (+(item.layout?.columns || 12)) + '/12 * 100% - var(--gap-2))'\"\r\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 -->\r\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. -->\r\n <div class=\"fb-group__header\" cdkDragHandle [style.background]=\"cv.bg\" [style.color]=\"cv.fg\">\r\n <span class=\"fb-group__icon\"><span class=\"msi\">{{ g.properties?.icon || 'category' }}</span></span>\r\n <span class=\"fb-group__label\">{{ item.label }}</span>\r\n @if (hasConditional(item)) {\r\n <span class=\"fb-group__conditional-chip\">\r\n <span class=\"msi\" style=\"font-size: 12px;\">bolt</span>\r\n {{ 'core.component.form-builder.conditional' | translate }}\r\n </span>\r\n }\r\n <span class=\"fb-group__count\">{{ g.components?.length || 0 }} fields</span>\r\n </div>\r\n <!-- Floating action toolbar -->\r\n <div class=\"fb-actions\">\r\n <button type=\"button\" class=\"fb-icon-btn\"\r\n [matTooltip]=\"'core.component.form-builder.duplicate' | translate\"\r\n (click)=\"onDuplicate(item); $event.stopPropagation()\">\r\n <span class=\"msi\">content_copy</span>\r\n </button>\r\n <button type=\"button\" class=\"fb-icon-btn\"\r\n [class.warning]=\"item.properties?.hidden\"\r\n [class.on]=\"item.properties?.hidden\"\r\n [matTooltip]=\"item.properties?.hidden ? ('core.component.form-builder.show' | translate) : ('core.component.form-builder.hide' | translate)\"\r\n (click)=\"onChangeHidden(item); $event.stopPropagation()\">\r\n <span class=\"msi\">{{ item.properties?.hidden ? 'visibility_off' : 'visibility' }}</span>\r\n </button>\r\n <button type=\"button\" class=\"fb-icon-btn danger\"\r\n [matTooltip]=\"'core.component.form-builder.delete' | translate\"\r\n (click)=\"removeComponent(item.id); $event.stopPropagation()\">\r\n <span class=\"msi\">delete</span>\r\n </button>\r\n </div>\r\n\r\n <div\n class=\"fb-group__body\"\n cdkDropList\n cdkDropListOrientation=\"horizontal\"\n [cdkDropListData]=\"g.components\"\n (cdkDropListDropped)=\"dropInGroup($event, g)\">\n @if (!g.components?.length) {\r\n <div class=\"fb-group__empty\">\r\n <span class=\"msi\" style=\"font-size: 16px; margin-right: 6px;\">south</span>\r\n {{ 'core.component.form-builder.group-drop-here' | translate }}\r\n </div>\r\n }\r\n @for (child of g.components; track child.id; let ci = $index) {\r\n <div\r\n cdkDrag\r\n [id]=\"child.id\"\r\n class=\"fb-item is-child column-{{ child.layout?.columns || 12 }}\"\r\n [class.is-selected]=\"child.id === selectedComponent()?.id\"\r\n [class.is-hidden]=\"child.properties?.hidden\"\r\n [class.is-readonly]=\"child.properties?.viewed\"\r\n (click)=\"selectComponent(child); $event.stopPropagation()\"\n (cdkDragStarted)=\"onAnyDragStarted()\"\n (cdkDragMoved)=\"onAnyDragMoved($event)\"\n (cdkDragEnded)=\"onAnyDragEnded()\">\n <!-- Generic puck preview -->\r\n <div *cdkDragPreview class=\"fb-drag-preview\"></div>\n <ng-container\r\n *ngTemplateOutlet=\"controlTemplate; context: { $implicit: child }\">\r\n </ng-container>\r\n <!-- Resize handle cho group child \u2014 chia 12-col ngay trong group body.\r\n Wrapping cdkDropList c\u00F3 enterPredicate=false \u0111\u1EC3 KH\u00D4NG nh\u1EADn drop\r\n (ch\u1EC9 d\u00F9ng l\u00E0m context cho cdkDrag), tr\u00E1nh xung \u0111\u1ED9t v\u1EDBi group__body drop list. -->\r\n <div cdkDropList cdkDropListOrientation=\"horizontal\" [cdkDropListEnterPredicate]=\"noReturnPredicate\">\r\n <div\r\n cdkDrag\r\n cdkDragLockAxis=\"x\"\r\n class=\"fb-resize\"\r\n (cdkDragStarted)=\"onAnyDragStarted()\"\r\n (cdkDragMoved)=\"changeSizeChildInGroup($event, child, g)\"\r\n (cdkDragEnded)=\"dragEndChangeSizeControl($event); onAnyDragEnded()\"\r\n [matTooltip]=\"'core.component.form-builder.flex' | translate\"\r\n aria-label=\"resize\"></div>\r\n </div>\r\n <!-- Inline status chips for nested children -->\r\n @if (child.properties?.hidden) {\r\n <span class=\"fb-status-chip hidden\">\r\n <span class=\"msi\">visibility_off</span>\r\n {{ 'core.component.form-builder.hidden' | translate }}\r\n </span>\r\n } @else if (child.properties?.viewed) {\r\n <span class=\"fb-status-chip readonly\">\r\n <span class=\"msi\">edit_off</span>\r\n {{ 'core.component.form-builder.read-only' | translate }}\r\n </span>\r\n } @else if (hasConditional(child)) {\r\n <span class=\"fb-status-chip conditional\">\r\n <span class=\"msi\">bolt</span>\r\n {{ 'core.component.form-builder.conditional' | translate }}\r\n </span>\r\n }\r\n <div class=\"fb-actions\">\r\n <button type=\"button\" class=\"fb-icon-btn\"\r\n [class.warning]=\"child.properties?.viewed\"\r\n [class.on]=\"child.properties?.viewed\"\r\n [matTooltip]=\"child.properties?.viewed ? ('core.component.form-builder.unset-readonly' | translate) : ('core.component.form-builder.set-readonly' | translate)\"\r\n (click)=\"onChangeViewed(child); $event.stopPropagation()\">\r\n <span class=\"msi\">edit_off</span>\r\n </button>\r\n <button type=\"button\" class=\"fb-icon-btn\"\r\n [matTooltip]=\"'core.component.form-builder.duplicate' | translate\"\r\n (click)=\"onDuplicate(child); $event.stopPropagation()\">\r\n <span class=\"msi\">content_copy</span>\r\n </button>\r\n <button type=\"button\" class=\"fb-icon-btn\"\r\n [class.warning]=\"child.properties?.hidden\"\r\n [class.on]=\"child.properties?.hidden\"\r\n [matTooltip]=\"child.properties?.hidden ? ('core.component.form-builder.show' | translate) : ('core.component.form-builder.hide' | translate)\"\r\n (click)=\"onChangeHidden(child); $event.stopPropagation()\">\r\n <span class=\"msi\">{{ child.properties?.hidden ? 'visibility_off' : 'visibility' }}</span>\r\n </button>\r\n <button type=\"button\" class=\"fb-icon-btn danger\"\r\n [matTooltip]=\"'core.component.form-builder.delete' | translate\"\r\n (click)=\"removeFromGroup(g, child.id); $event.stopPropagation()\">\r\n <span class=\"msi\">delete</span>\r\n </button>\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n } @else if (item.type === 'break') {\r\n <!-- Break: row separator c\u1ED1 \u0111\u1ECBnh. Render 12-col line, kh\u00F4ng c\u00F3 resize/edit attribute. -->\r\n <div\r\n cdkDrag\r\n [id]=\"item.id\"\r\n class=\"fb-item is-break column-12\"\r\n [class.is-selected]=\"item.id === selectedComponent()?.id\"\r\n [class.is-hidden]=\"item.properties?.hidden\"\r\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 -->\r\n <div *cdkDragPreview class=\"fb-drag-preview\"></div>\n <div class=\"fb-break-line\">\r\n <span class=\"msi\">horizontal_rule</span>\r\n <span class=\"fb-break-label\">{{ 'core.component.form-builder.break' | translate }}</span>\r\n </div>\r\n <div class=\"fb-actions\">\r\n <button type=\"button\" class=\"fb-icon-btn\"\r\n [class.warning]=\"item.properties?.hidden\"\r\n [class.on]=\"item.properties?.hidden\"\r\n [matTooltip]=\"item.properties?.hidden ? ('core.component.form-builder.show' | translate) : ('core.component.form-builder.hide' | translate)\"\r\n (click)=\"onChangeHidden(item); $event.stopPropagation()\">\r\n <span class=\"msi\">{{ item.properties?.hidden ? 'visibility_off' : 'visibility' }}</span>\r\n </button>\r\n <button type=\"button\" class=\"fb-icon-btn danger\"\r\n [matTooltip]=\"'core.component.form-builder.delete' | translate\"\r\n (click)=\"removeComponent(item.id); $event.stopPropagation()\">\r\n <span class=\"msi\">delete</span>\r\n </button>\r\n </div>\r\n </div>\r\n } @else {\r\n <!-- Regular form component -->\r\n <div\r\n cdkDrag\r\n [id]=\"item.id\"\r\n class=\"fb-item column-{{ item.layout?.columns || 12 }}\"\r\n [class.is-selected]=\"item.id === selectedComponent()?.id\"\r\n [class.is-hidden]=\"item.properties?.hidden\"\r\n [class.is-readonly]=\"item.properties?.viewed\"\r\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 -->\r\n <div *cdkDragPreview class=\"fb-drag-preview\"></div>\n\r\n <!-- Status chip (mutually exclusive priority: hidden > readonly > conditional) -->\r\n @if (item.properties?.hidden) {\r\n <span class=\"fb-status-chip hidden\">\r\n <span class=\"msi\">visibility_off</span>\r\n {{ 'core.component.form-builder.hidden' | translate }}\r\n </span>\r\n } @else if (item.properties?.viewed) {\r\n <span class=\"fb-status-chip readonly\">\r\n <span class=\"msi\">edit_off</span>\r\n {{ 'core.component.form-builder.read-only' | translate }}\r\n </span>\r\n } @else if (hasConditional(item)) {\r\n <span class=\"fb-status-chip conditional\">\r\n <span class=\"msi\">bolt</span>\r\n {{ 'core.component.form-builder.conditional' | translate }}\r\n </span>\r\n }\r\n\r\n <!-- Floating action toolbar pill -->\r\n <div class=\"fb-actions\">\r\n <button type=\"button\" class=\"fb-icon-btn\"\r\n [class.warning]=\"item.properties?.viewed\"\r\n [class.on]=\"item.properties?.viewed\"\r\n [matTooltip]=\"item.properties?.viewed ? ('core.component.form-builder.unset-readonly' | translate) : ('core.component.form-builder.set-readonly' | translate)\"\r\n (click)=\"onChangeViewed(item); $event.stopPropagation()\">\r\n <span class=\"msi\">edit_off</span>\r\n </button>\r\n <button type=\"button\" class=\"fb-icon-btn\"\r\n [matTooltip]=\"'core.component.form-builder.duplicate' | translate\"\r\n (click)=\"onDuplicate(item); $event.stopPropagation()\">\r\n <span class=\"msi\">content_copy</span>\r\n </button>\r\n <button type=\"button\" class=\"fb-icon-btn\"\r\n [class.warning]=\"item.properties?.hidden\"\r\n [class.on]=\"item.properties?.hidden\"\r\n [matTooltip]=\"item.properties?.hidden ? ('core.component.form-builder.show' | translate) : ('core.component.form-builder.hide' | translate)\"\r\n (click)=\"onChangeHidden(item); $event.stopPropagation()\">\r\n <span class=\"msi\">{{ item.properties?.hidden ? 'visibility_off' : 'visibility' }}</span>\r\n </button>\r\n <button type=\"button\" class=\"fb-icon-btn danger\"\r\n [matTooltip]=\"'core.component.form-builder.delete' | translate\"\r\n (click)=\"removeComponent(item.id); $event.stopPropagation()\">\r\n <span class=\"msi\">delete</span>\r\n </button>\r\n </div>\r\n\r\n <ng-container\r\n *ngTemplateOutlet=\"controlTemplate; context: { $implicit: item }\">\r\n </ng-container>\r\n\r\n <!-- Resize handle (pill) \u2014 cdkDragLockAxis=\"x\" \u0111\u1EC3 ch\u1EC9 k\u00E9o ngang.\r\n Wrapping cdkDropList c\u00F3 enterPredicate=false, kh\u00F4ng nh\u1EADn drop. -->\r\n <div cdkDropList cdkDropListOrientation=\"horizontal\" [cdkDropListEnterPredicate]=\"noReturnPredicate\">\r\n <div\r\n cdkDrag\r\n cdkDragLockAxis=\"x\"\r\n class=\"fb-resize\"\r\n (cdkDragStarted)=\"onAnyDragStarted()\"\r\n (cdkDragMoved)=\"changeSizeControl($event, item, row.items, i)\"\r\n (cdkDragEnded)=\"dragEndChangeSizeControl($event); onAnyDragEnded()\"\r\n [matTooltip]=\"'core.component.form-builder.flex' | translate\"\r\n aria-label=\"resize\"></div>\r\n </div>\r\n </div>\r\n }\r\n }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n </section>\r\n\r\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 -->\r\n <aside class=\"fb-attrs\">\r\n <div class=\"fb-panel-header\">\r\n <div class=\"fb-panel-header__title\">{{ 'core.component.form-builder.attribute' | translate }}</div>\r\n </div>\r\n\r\n @let sel = selectedComponent();\r\n @if (sel) {\r\n <!-- Selection header: type icon + name + key/type -->\r\n <div class=\"fb-attrs__selection\">\r\n <span class=\"fb-attrs__sel-icon\"><span class=\"msi\">{{ symbolFor(sel) }}</span></span>\r\n <div class=\"fb-attrs__sel-meta\">\r\n <div class=\"fb-attrs__sel-name\">{{ $any(sel).label || ('core.component.form-builder.untitled' | translate) }}</div>\r\n <div class=\"fb-attrs__sel-key\">\r\n @if ($any(sel).key) {\r\n {{ $any(sel).key }} \u00B7 {{ typeLabelFor(sel) }}\r\n } @else {\r\n {{ typeLabelFor(sel) }}\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"fb-attrs__body\" aria-hidden=\"true\">\r\n @if (sel.type === 'textfield') {\r\n <textfield-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></textfield-attribute>\r\n } @else if (sel.type === 'textarea') {\r\n <textarea-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></textarea-attribute>\r\n } @else if (sel.type === 'chip-string') {\r\n <chip-string-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></chip-string-attribute>\r\n } @else if (sel.type === 'chip-calendar') {\r\n <chip-calendar-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></chip-calendar-attribute>\r\n } @else if (sel.type === 'number') {\r\n <number-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></number-attribute>\r\n } @else if (sel.type === 'datetime') {\r\n <datetime-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></datetime-attribute>\r\n } @else if (sel.type === 'select') {\r\n <select-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></select-attribute>\r\n } @else if (sel.type === 'radio') {\r\n <radio-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></radio-attribute>\r\n } @else if (sel.type === 'checkbox') {\r\n <checkbox-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></checkbox-attribute>\r\n } @else if (sel.type === 'html') {\r\n <html-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></html-attribute>\r\n } @else if (sel.type === 'upload') {\r\n <upload-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></upload-attribute>\r\n } @else if (sel.type === 'table') {\r\n <table-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></table-attribute>\r\n } @else if (sel.type === 'group') {\r\n <group-attribute [group]=\"$any(sel)\"></group-attribute>\r\n }\r\n </div>\r\n } @else {\r\n <div class=\"fb-attrs__placeholder\">\r\n <span class=\"msi\">ads_click</span>\r\n <div class=\"title\">{{ 'core.component.form-builder.no-selection-title' | translate }}</div>\r\n <div class=\"hint\">{{ 'core.component.form-builder.no-selection-hint' | translate }}</div>\r\n </div>\r\n }\r\n </aside>\r\n</div>\r\n\r\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 -->\r\n<ng-template #controlTemplate let-item>\r\n @if (item.type === 'textfield') {\r\n <textfield-control [component]=\"item\"></textfield-control>\r\n } @else if (item.type === 'textarea') {\r\n <textarea-control [component]=\"item\"></textarea-control>\r\n } @else if (item.type === 'chip-string') {\r\n <chip-string-control [component]=\"item\"></chip-string-control>\r\n } @else if (item.type === 'chip-calendar') {\r\n <chip-calendar-control [component]=\"item\"></chip-calendar-control>\r\n } @else if (item.type === 'number') {\r\n <number-control [component]=\"item\"></number-control>\r\n } @else if (item.type === 'datetime') {\r\n <datetime-control [component]=\"item\"></datetime-control>\r\n } @else if (item.type === 'select') {\r\n <select-control [component]=\"item\"></select-control>\r\n } @else if (item.type === 'radio') {\r\n <radio-control [component]=\"item\"></radio-control>\r\n } @else if (item.type === 'checkbox') {\r\n <checkbox-control [component]=\"item\"></checkbox-control>\r\n } @else if (item.type === 'html') {\r\n <html-control [component]=\"item\"></html-control>\r\n } @else if (item.type === 'upload') {\r\n <upload-control [component]=\"item\"></upload-control>\r\n } @else if (item.type === 'table') {\r\n <table-control [component]=\"item\"></table-control>\r\n }\r\n</ng-template>\r\n\r\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 -->\r\n<sd-modal title=\"JSON\" view=\"dialog\" width=\"80%\" #popupViewJSON>\r\n <sd-textarea [(model)]=\"jsonString\"></sd-textarea>\r\n <ng-container sdFooter>\r\n <sd-button type=\"fill\" [title]=\"'core.component.form-builder.save' | translate\" (click)=\"updateJSON()\" prefixIcon=\"save\" color=\"primary\"></sd-button>\r\n </ng-container>\r\n</sd-modal>\r\n<sd-modal [title]=\"'core.component.form-builder.config-variable' | translate\" view=\"dialog\" width=\"800px\" #popupConfigureVariables>\r\n @for (variable of clonedVariables; track variable.id) {\r\n <div class=\"d-flex align-items-center\" style=\"gap: 8px\">\r\n <sd-input [form]=\"form\" [label]=\"'core.component.form-builder.field-key' | translate\" [(model)]=\"variable.key\" required></sd-input>\r\n <sd-input [form]=\"form\" [label]=\"'core.component.form-builder.display-label' | translate\" [(model)]=\"variable.label\" required></sd-input>\r\n <sd-button prefixIcon=\"delete\" (click)=\"removeVariables(variable.id)\" color=\"error\"></sd-button>\r\n </div>\r\n }\r\n <a href=\"javascript:;\" (click)=\"addVariables()\">{{ 'core.component.form-builder.add-variable' | translate }}</a>\r\n <ng-container sdFooter>\r\n <sd-button type=\"fill\" [title]=\"'core.component.form-builder.save' | translate\" (click)=\"updateVariables()\" prefixIcon=\"save\" color=\"primary\"></sd-button>\r\n </ng-container>\r\n</sd-modal>\r\n<configure-validation (accept)=\"onUpdateValidations($event)\"></configure-validation>\r\n", styles: [":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}\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"], 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: "19.2.21", 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.\r\n [class.fb-shell--dragging]: JS-driven flag \u0111\u1EC3 \u1EA9n hover/actions/resize to\u00E0n di\u1EC7n\r\n trong l\u00FAc drag (CSS :has() kh\u00F4ng b\u1EAFt 100% tr\u01B0\u1EDDng h\u1EE3p v\u1EDBi CDK preview clone). -->\r\n<div class=\"fb-shell\" cdkDropListGroup [class.fb-shell--dragging]=\"isAnyDragging()\">\r\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 -->\r\n <aside class=\"fb-palette\">\r\n <div class=\"fb-panel-header\">\r\n <div class=\"fb-panel-header__title\">{{ 'core.component.form-builder.components' | translate }}</div>\r\n </div>\r\n\r\n <!-- Search pill -->\r\n <div class=\"fb-palette__search\">\r\n <span class=\"msi\" aria-hidden=\"true\">search</span>\r\n <input\r\n type=\"text\"\r\n [placeholder]=\"'core.component.form-builder.search-components' | translate\"\r\n [value]=\"paletteSearch()\"\r\n (input)=\"paletteSearch.set($any($event.target).value)\" />\r\n <span class=\"kbd\">\u2318K</span>\r\n </div>\r\n\r\n <div class=\"fb-palette__scroll\">\r\n @for (group of paletteGroups(); track group.key) {\r\n <div class=\"fb-palette__group-label\">{{ group.label }}</div>\r\n <div\r\n class=\"fb-palette__list\"\r\n cdkDropList\r\n [cdkDropListData]=\"group.items\"\r\n [cdkDropListEnterPredicate]=\"noReturnPredicate\">\r\n @for (formBuilderComponent of group.items; track formBuilderComponent.type) {\r\n <div\r\n cdkDrag\r\n class=\"fb-palette-item\"\r\n [matTooltip]=\"formBuilderComponent.name\"\r\n matTooltipPosition=\"right\"\r\n (click)=\"addComponent(formBuilderComponent)\"\r\n (cdkDragStarted)=\"onAnyDragStarted()\"\r\n (cdkDragMoved)=\"onAnyDragMoved($event)\"\r\n (cdkDragEnded)=\"onAnyDragEnded()\"\r\n aria-hidden=\"true\">\r\n <span class=\"fb-palette-item__icon\"><span class=\"msi\">{{ formBuilderComponent.symbol }}</span></span>\r\n <span class=\"fb-palette-item__name\">{{ formBuilderComponent.name }}</span>\r\n <span class=\"fb-palette-item__drag\"><span class=\"msi\">drag_indicator</span></span>\r\n <!-- Generic drag preview: ch\u1EC9 icon (puck), label \u1EA9n -->\r\n <div *cdkDragPreview class=\"fb-drag-preview\"></div>\r\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n </aside>\r\n\r\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 -->\r\n <section class=\"fb-canvas\">\r\n @let _editing = editingGroup();\r\n @if (_editing) {\r\n <!-- \u2500\u2500 Drill-in header: \u0111ang thi\u1EBFt k\u1EBF children c\u1EE7a 1 group \u2500\u2500 -->\r\n <header class=\"fb-canvas__header fb-canvas__header--group\">\r\n <div class=\"fb-canvas__title fb-groupedit__crumb\">\r\n <button type=\"button\" class=\"fb-icon-btn\" [matTooltip]=\"'core.component.form-builder.back-to-design' | translate\" (click)=\"cancelGroupEdit()\">\r\n <span class=\"msi\">chevron_left</span>\r\n </button>\r\n <span class=\"fb-groupedit__icon\"><span class=\"msi\">{{ _editing.properties?.icon || 'category' }}</span></span>\r\n <span>{{ _editing.label || ('core.component.form-builder.edit-group' | translate) }}</span>\r\n <span class=\"fb-groupedit__hint\">{{ 'core.component.form-builder.edit-group' | translate }}</span>\r\n </div>\r\n <div class=\"fb-canvas__actions\">\r\n <sd-button type=\"outline\" [title]=\"'core.component.form-builder.cancel' | translate\" (click)=\"cancelGroupEdit()\"></sd-button>\r\n <sd-button type=\"fill\" color=\"primary\" prefixIcon=\"check\" [title]=\"'core.component.form-builder.confirm' | translate\" (click)=\"confirmGroupEdit()\"></sd-button>\r\n </div>\r\n </header>\r\n } @else {\r\n <header class=\"fb-canvas__header\">\r\n <div class=\"fb-canvas__title\">\r\n <span>{{ 'core.component.form-builder.layout' | translate }}</span>\r\n </div>\r\n <div class=\"fb-canvas__actions\">\r\n <!-- Toolbar cluster: view JSON / variables / validation -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-icon-btn\"\r\n [matTooltip]=\"'core.component.form-builder.view-json' | translate\"\r\n (click)=\"viewJSON()\">\r\n <span class=\"msi\">data_object</span>\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-icon-btn\"\r\n [matTooltip]=\"'core.component.form-builder.config-variable' | translate\"\r\n (click)=\"configureVariables()\">\r\n <span class=\"msi\">data_array</span>\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-icon-btn\"\r\n [matTooltip]=\"'core.component.form-builder.config-validation-warning-error' | translate\"\r\n (click)=\"openConfigureValidation()\">\r\n <span class=\"msi\">rule</span>\r\n </button>\r\n\r\n <span class=\"fb-divider\"></span>\r\n\r\n <!-- M3 segmented Design/Preview toggle -->\r\n <div class=\"fb-mode-toggle\" role=\"tablist\">\r\n <button\r\n type=\"button\"\r\n role=\"tab\"\r\n [class.is-active]=\"!isPreview()\"\r\n (click)=\"setMode(false)\">\r\n <span class=\"msi\">edit</span>\r\n <span>{{ 'core.component.form-builder.design' | translate }}</span>\r\n </button>\r\n <button\r\n type=\"button\"\r\n role=\"tab\"\r\n [class.is-active]=\"isPreview()\"\r\n (click)=\"setMode(true)\">\r\n <span class=\"msi\">visibility</span>\r\n <span>{{ 'core.component.form-builder.preview' | translate }}</span>\r\n </button>\r\n </div>\r\n </div>\r\n </header>\r\n }\r\n\r\n <div\r\n class=\"fb-canvas__body\"\r\n [class.is-preview]=\"isPreview()\"\r\n id=\"frmComponent\">\r\n @if (isPreview()) {\r\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 -->\r\n <div class=\"fb-preview-card\">\r\n <div class=\"fb-preview-card__chip\">\r\n <span class=\"msi\">visibility</span>\r\n {{ 'core.component.form-builder.preview-runtime' | translate }}\r\n </div>\r\n <sd-form-render [configuration]=\"{ components: this.components, validations: this.validations }\"></sd-form-render>\r\n <div class=\"fb-preview-card__footer\">\r\n <sd-button type=\"outline\" [title]=\"'core.component.form-builder.cancel' | translate\" (click)=\"setMode(false)\"></sd-button>\r\n <sd-button type=\"fill\" color=\"primary\" [title]=\"'core.component.form-builder.submit' | translate\" (click)=\"onValidate()\"></sd-button>\r\n </div>\r\n </div>\r\n } @else {\r\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 -->\r\n @if (!dragDropRows.length) {\r\n <div class=\"fb-empty\">\r\n <div class=\"fb-empty__art\"><span class=\"msi\">dashboard_customize</span></div>\r\n <div class=\"fb-empty__title\">{{ 'core.component.form-builder.empty-title' | translate }}</div>\r\n <div class=\"fb-empty__hint\">\r\n {{ 'core.component.form-builder.empty-hint' | translate }}\r\n <span class=\"fb-empty__kbd\"><span class=\"msi\">keyboard</span>\u2318 K</span>\r\n </div>\r\n </div>\r\n }\r\n\r\n <div\r\n cdkDropList\r\n cdkDropListOrientation=\"vertical\"\r\n [cdkDropListData]=\"dragDropRows\"\r\n (cdkDropListDropped)=\"drop($event)\"\r\n (click)=\"clickFormContentEmpty()\"\r\n aria-hidden=\"true\">\r\n @for (row of dragDropRows; track row.rowIndex) {\r\n <div\r\n cdkDrag\r\n class=\"fb-row\"\r\n (mouseover)=\"onMouseover($event, row)\"\r\n (focus)=\"onFocus($event)\"\r\n (cdkDragStarted)=\"onAnyDragStarted()\"\r\n (cdkDragMoved)=\"onAnyDragMoved($event)\"\r\n (cdkDragEnded)=\"onAnyDragEnded()\"\r\n [class.is-drag-target]=\"targetItem === row\"\r\n [id]=\"'row_' + row.rowIndex\"\r\n aria-hidden=\"true\">\r\n <div class=\"fb-row__drag\" [matTooltip]=\"'core.component.form-builder.drag-vertically' | translate\" cdkDragHandle>\r\n <span class=\"msi\">drag_indicator</span>\r\n </div>\r\n <!-- Generic puck preview -->\r\n <div *cdkDragPreview class=\"fb-drag-preview\"></div>\r\n <!-- Quick-add break button \u2014 show on row hover, insert break after this row -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-row-break-btn\"\r\n [matTooltip]=\"'core.component.form-builder.break' | translate\"\r\n matTooltipPosition=\"right\"\r\n (click)=\"insertBreakAfter(row); $event.stopPropagation()\">\r\n <span class=\"msi\">horizontal_rule</span>\r\n </button>\r\n\r\n <div\r\n class=\"fb-row__items\"\r\n style=\"display: flex; flex: 1; gap: var(--gap-2);\"\r\n cdkDropList\r\n cdkDropListOrientation=\"horizontal\"\r\n [cdkDropListData]=\"row.items\"\r\n (cdkDropListDropped)=\"drop($event)\">\r\n\r\n @for (item of row.items; track item.id; let i = $index) {\r\n <!-- Group component renders as outlined card with colored header bar -->\r\n @if (item.type === 'group') {\r\n @let g = $any(item);\r\n @let cv = groupColorVars(g.properties?.color);\r\n <div\r\n cdkDrag\r\n [id]=\"item.id\"\r\n class=\"fb-group column-{{ item.layout?.columns || 12 }}\"\r\n [class.fb-item]=\"false\"\r\n [class.is-selected]=\"item.id === selectedComponent()?.id\"\r\n [class.is-hidden]=\"item.properties?.hidden\"\r\n [style.flex]=\"'0 0 calc(' + (+(item.layout?.columns || 12)) + '/12 * 100% - var(--gap-2))'\"\r\n [style.maxWidth]=\"'calc(' + (+(item.layout?.columns || 12)) + '/12 * 100% - var(--gap-2))'\"\r\n (click)=\"selectComponent(item); $event.stopPropagation()\"\r\n (cdkDragStarted)=\"dragStartComponentItem($event); onAnyDragStarted()\"\r\n (cdkDragMoved)=\"onAnyDragMoved($event)\"\r\n (cdkDragEnded)=\"dragEndComponentItem($event); onAnyDragEnded()\"\r\n aria-hidden=\"true\">\r\n <!-- Generic puck preview -->\r\n <div *cdkDragPreview class=\"fb-drag-preview\"></div>\r\n <!-- cdkDragHandle: ch\u1EC9 cho ph\u00E9p k\u00E9o group qua header \u2014 body free cho click v\u00E0o child. -->\r\n <div class=\"fb-group__header\" cdkDragHandle [style.background]=\"cv.bg\" [style.color]=\"cv.fg\">\r\n <span class=\"fb-group__icon\"><span class=\"msi\">{{ g.properties?.icon || 'category' }}</span></span>\r\n <span class=\"fb-group__label\">{{ item.label }}</span>\r\n @if (hasConditional(item)) {\r\n <span class=\"fb-group__conditional-chip\">\r\n <span class=\"msi\" style=\"font-size: 12px;\">bolt</span>\r\n {{ 'core.component.form-builder.conditional' | translate }}\r\n </span>\r\n }\r\n <span class=\"fb-group__count\">{{ g.components?.length || 0 }} fields</span>\r\n </div>\r\n <!-- Floating action toolbar -->\r\n <div class=\"fb-actions\">\r\n <!-- why: Detail = m\u1EDF canvas thi\u1EBFt k\u1EBF ri\u00EAng cho group (thay cho drag/drop trong group). -->\r\n <button type=\"button\" class=\"fb-icon-btn\"\r\n [matTooltip]=\"'core.component.form-builder.edit-group' | translate\"\r\n (click)=\"enterGroupEdit(g); $event.stopPropagation()\">\r\n <span class=\"msi\">open_in_full</span>\r\n </button>\r\n <button type=\"button\" class=\"fb-icon-btn\"\r\n [matTooltip]=\"'core.component.form-builder.duplicate' | translate\"\r\n (click)=\"onDuplicate(item); $event.stopPropagation()\">\r\n <span class=\"msi\">content_copy</span>\r\n </button>\r\n <button type=\"button\" class=\"fb-icon-btn\"\r\n [class.warning]=\"item.properties?.hidden\"\r\n [class.on]=\"item.properties?.hidden\"\r\n [matTooltip]=\"item.properties?.hidden ? ('core.component.form-builder.show' | translate) : ('core.component.form-builder.hide' | translate)\"\r\n (click)=\"onChangeHidden(item); $event.stopPropagation()\">\r\n <span class=\"msi\">{{ item.properties?.hidden ? 'visibility_off' : 'visibility' }}</span>\r\n </button>\r\n <button type=\"button\" class=\"fb-icon-btn danger\"\r\n [matTooltip]=\"'core.component.form-builder.delete' | translate\"\r\n (click)=\"removeComponent(item.id); $event.stopPropagation()\">\r\n <span class=\"msi\">delete</span>\r\n </button>\r\n </div>\r\n\r\n <!-- Static preview: children render read-only (kh\u00F4ng drag/resize/select).\r\n S\u1EEDa n\u1ED9i dung group qua n\u00FAt Detail (enterGroupEdit). -->\r\n <div class=\"fb-group__body fb-group__body--static\" (click)=\"enterGroupEdit(g); $event.stopPropagation()\">\r\n @if (!g.components?.length) {\r\n <div class=\"fb-group__empty\">\r\n <span class=\"msi\" style=\"font-size: 16px; margin-right: 6px;\">open_in_full</span>\r\n {{ 'core.component.form-builder.group-empty-hint' | translate }}\r\n </div>\r\n }\r\n @for (child of g.components; track child.id) {\r\n <div\r\n class=\"fb-item is-child fb-item--static column-{{ child.layout?.columns || 12 }}\"\r\n [class.is-hidden]=\"child.properties?.hidden\">\r\n <ng-container\r\n *ngTemplateOutlet=\"controlTemplate; context: { $implicit: child }\">\r\n </ng-container>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n } @else if (item.type === 'break') {\r\n <!-- Break: row separator c\u1ED1 \u0111\u1ECBnh. Render 12-col line, kh\u00F4ng c\u00F3 resize/edit attribute. -->\r\n <div\r\n cdkDrag\r\n [id]=\"item.id\"\r\n class=\"fb-item is-break column-12\"\r\n [class.is-selected]=\"item.id === selectedComponent()?.id\"\r\n [class.is-hidden]=\"item.properties?.hidden\"\r\n (click)=\"selectComponent(item); $event.stopPropagation()\"\r\n (cdkDragStarted)=\"dragStartComponentItem($event); onAnyDragStarted()\"\r\n (cdkDragMoved)=\"onAnyDragMoved($event)\"\r\n (cdkDragEnded)=\"dragEndComponentItem($event); onAnyDragEnded()\"\r\n aria-hidden=\"true\">\r\n <!-- Generic puck preview -->\r\n <div *cdkDragPreview class=\"fb-drag-preview\"></div>\r\n <div class=\"fb-break-line\">\r\n <span class=\"msi\">horizontal_rule</span>\r\n <span class=\"fb-break-label\">{{ 'core.component.form-builder.break' | translate }}</span>\r\n </div>\r\n <div class=\"fb-actions\">\r\n <button type=\"button\" class=\"fb-icon-btn\"\r\n [class.warning]=\"item.properties?.hidden\"\r\n [class.on]=\"item.properties?.hidden\"\r\n [matTooltip]=\"item.properties?.hidden ? ('core.component.form-builder.show' | translate) : ('core.component.form-builder.hide' | translate)\"\r\n (click)=\"onChangeHidden(item); $event.stopPropagation()\">\r\n <span class=\"msi\">{{ item.properties?.hidden ? 'visibility_off' : 'visibility' }}</span>\r\n </button>\r\n <button type=\"button\" class=\"fb-icon-btn danger\"\r\n [matTooltip]=\"'core.component.form-builder.delete' | translate\"\r\n (click)=\"removeComponent(item.id); $event.stopPropagation()\">\r\n <span class=\"msi\">delete</span>\r\n </button>\r\n </div>\r\n </div>\r\n } @else {\r\n <!-- Regular form component -->\r\n <div\r\n cdkDrag\r\n [id]=\"item.id\"\r\n class=\"fb-item column-{{ item.layout?.columns || 12 }}\"\r\n [class.is-selected]=\"item.id === selectedComponent()?.id\"\r\n [class.is-hidden]=\"item.properties?.hidden\"\r\n [class.is-readonly]=\"item.properties?.viewed\"\r\n (click)=\"selectComponent(item); $event.stopPropagation()\"\r\n (cdkDragStarted)=\"dragStartComponentItem($event); onAnyDragStarted()\"\r\n (cdkDragMoved)=\"onAnyDragMoved($event)\"\r\n (cdkDragEnded)=\"dragEndComponentItem($event); onAnyDragEnded()\"\r\n aria-hidden=\"true\">\r\n <!-- Generic puck preview -->\r\n <div *cdkDragPreview class=\"fb-drag-preview\"></div>\r\n\r\n <!-- Status chip (mutually exclusive priority: hidden > readonly > conditional) -->\r\n @if (item.properties?.hidden) {\r\n <span class=\"fb-status-chip hidden\">\r\n <span class=\"msi\">visibility_off</span>\r\n {{ 'core.component.form-builder.hidden' | translate }}\r\n </span>\r\n } @else if (item.properties?.viewed) {\r\n <span class=\"fb-status-chip readonly\">\r\n <span class=\"msi\">edit_off</span>\r\n {{ 'core.component.form-builder.read-only' | translate }}\r\n </span>\r\n } @else if (hasConditional(item)) {\r\n <span class=\"fb-status-chip conditional\">\r\n <span class=\"msi\">bolt</span>\r\n {{ 'core.component.form-builder.conditional' | translate }}\r\n </span>\r\n }\r\n\r\n <!-- Floating action toolbar pill -->\r\n <div class=\"fb-actions\">\r\n <button type=\"button\" class=\"fb-icon-btn\"\r\n [class.warning]=\"item.properties?.viewed\"\r\n [class.on]=\"item.properties?.viewed\"\r\n [matTooltip]=\"item.properties?.viewed ? ('core.component.form-builder.unset-readonly' | translate) : ('core.component.form-builder.set-readonly' | translate)\"\r\n (click)=\"onChangeViewed(item); $event.stopPropagation()\">\r\n <span class=\"msi\">edit_off</span>\r\n </button>\r\n <button type=\"button\" class=\"fb-icon-btn\"\r\n [matTooltip]=\"'core.component.form-builder.duplicate' | translate\"\r\n (click)=\"onDuplicate(item); $event.stopPropagation()\">\r\n <span class=\"msi\">content_copy</span>\r\n </button>\r\n <button type=\"button\" class=\"fb-icon-btn\"\r\n [class.warning]=\"item.properties?.hidden\"\r\n [class.on]=\"item.properties?.hidden\"\r\n [matTooltip]=\"item.properties?.hidden ? ('core.component.form-builder.show' | translate) : ('core.component.form-builder.hide' | translate)\"\r\n (click)=\"onChangeHidden(item); $event.stopPropagation()\">\r\n <span class=\"msi\">{{ item.properties?.hidden ? 'visibility_off' : 'visibility' }}</span>\r\n </button>\r\n <button type=\"button\" class=\"fb-icon-btn danger\"\r\n [matTooltip]=\"'core.component.form-builder.delete' | translate\"\r\n (click)=\"removeComponent(item.id); $event.stopPropagation()\">\r\n <span class=\"msi\">delete</span>\r\n </button>\r\n </div>\r\n\r\n <ng-container\r\n *ngTemplateOutlet=\"controlTemplate; context: { $implicit: item }\">\r\n </ng-container>\r\n\r\n <!-- Resize handle (pill) \u2014 cdkDragLockAxis=\"x\" \u0111\u1EC3 ch\u1EC9 k\u00E9o ngang.\r\n Wrapping cdkDropList c\u00F3 enterPredicate=false, kh\u00F4ng nh\u1EADn drop. -->\r\n <div cdkDropList cdkDropListOrientation=\"horizontal\" [cdkDropListEnterPredicate]=\"noReturnPredicate\">\r\n <div\r\n cdkDrag\r\n cdkDragLockAxis=\"x\"\r\n class=\"fb-resize\"\r\n (cdkDragStarted)=\"onAnyDragStarted()\"\r\n (cdkDragMoved)=\"changeSizeControl($event, item, row.items, i)\"\r\n (cdkDragEnded)=\"dragEndChangeSizeControl($event); onAnyDragEnded()\"\r\n [matTooltip]=\"'core.component.form-builder.flex' | translate\"\r\n aria-label=\"resize\"></div>\r\n </div>\r\n </div>\r\n }\r\n }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n </section>\r\n\r\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 -->\r\n <aside class=\"fb-attrs\">\r\n <div class=\"fb-panel-header\">\r\n <div class=\"fb-panel-header__title\">{{ 'core.component.form-builder.attribute' | translate }}</div>\r\n </div>\r\n\r\n @let sel = selectedComponent();\r\n @if (sel) {\r\n <!-- Selection header: type icon + name + key/type -->\r\n <div class=\"fb-attrs__selection\">\r\n <span class=\"fb-attrs__sel-icon\"><span class=\"msi\">{{ symbolFor(sel) }}</span></span>\r\n <div class=\"fb-attrs__sel-meta\">\r\n <div class=\"fb-attrs__sel-name\">{{ $any(sel).label || ('core.component.form-builder.untitled' | translate) }}</div>\r\n <div class=\"fb-attrs__sel-key\">\r\n @if ($any(sel).key) {\r\n {{ $any(sel).key }} \u00B7 {{ typeLabelFor(sel) }}\r\n } @else {\r\n {{ typeLabelFor(sel) }}\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"fb-attrs__body\" aria-hidden=\"true\">\r\n @if (sel.type === 'textfield') {\r\n <textfield-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></textfield-attribute>\r\n } @else if (sel.type === 'textarea') {\r\n <textarea-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></textarea-attribute>\r\n } @else if (sel.type === 'chip-string') {\r\n <chip-string-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></chip-string-attribute>\r\n } @else if (sel.type === 'chip-calendar') {\r\n <chip-calendar-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></chip-calendar-attribute>\r\n } @else if (sel.type === 'number') {\r\n <number-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></number-attribute>\r\n } @else if (sel.type === 'datetime') {\r\n <datetime-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></datetime-attribute>\r\n } @else if (sel.type === 'select') {\r\n <select-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></select-attribute>\r\n } @else if (sel.type === 'radio') {\r\n <radio-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></radio-attribute>\r\n } @else if (sel.type === 'checkbox') {\r\n <checkbox-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></checkbox-attribute>\r\n } @else if (sel.type === 'html') {\r\n <html-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></html-attribute>\r\n } @else if (sel.type === 'upload') {\r\n <upload-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></upload-attribute>\r\n } @else if (sel.type === 'table') {\r\n <table-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></table-attribute>\r\n } @else if (sel.type === 'group') {\r\n <group-attribute [group]=\"$any(sel)\"></group-attribute>\r\n }\r\n </div>\r\n } @else {\r\n <div class=\"fb-attrs__placeholder\">\r\n <span class=\"msi\">ads_click</span>\r\n <div class=\"title\">{{ 'core.component.form-builder.no-selection-title' | translate }}</div>\r\n <div class=\"hint\">{{ 'core.component.form-builder.no-selection-hint' | translate }}</div>\r\n </div>\r\n }\r\n </aside>\r\n</div>\r\n\r\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 -->\r\n<ng-template #controlTemplate let-item>\r\n @if (item.type === 'textfield') {\r\n <textfield-control [component]=\"item\"></textfield-control>\r\n } @else if (item.type === 'textarea') {\r\n <textarea-control [component]=\"item\"></textarea-control>\r\n } @else if (item.type === 'chip-string') {\r\n <chip-string-control [component]=\"item\"></chip-string-control>\r\n } @else if (item.type === 'chip-calendar') {\r\n <chip-calendar-control [component]=\"item\"></chip-calendar-control>\r\n } @else if (item.type === 'number') {\r\n <number-control [component]=\"item\"></number-control>\r\n } @else if (item.type === 'datetime') {\r\n <datetime-control [component]=\"item\"></datetime-control>\r\n } @else if (item.type === 'select') {\r\n <select-control [component]=\"item\"></select-control>\r\n } @else if (item.type === 'radio') {\r\n <radio-control [component]=\"item\"></radio-control>\r\n } @else if (item.type === 'checkbox') {\r\n <checkbox-control [component]=\"item\"></checkbox-control>\r\n } @else if (item.type === 'html') {\r\n <html-control [component]=\"item\"></html-control>\r\n } @else if (item.type === 'upload') {\r\n <upload-control [component]=\"item\"></upload-control>\r\n } @else if (item.type === 'table') {\r\n <table-control [component]=\"item\"></table-control>\r\n }\r\n</ng-template>\r\n\r\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 -->\r\n<sd-modal title=\"JSON\" view=\"dialog\" width=\"80%\" #popupViewJSON>\r\n <sd-textarea [(model)]=\"jsonString\"></sd-textarea>\r\n <ng-container sdFooter>\r\n <sd-button type=\"fill\" [title]=\"'core.component.form-builder.save' | translate\" (click)=\"updateJSON()\" prefixIcon=\"save\" color=\"primary\"></sd-button>\r\n </ng-container>\r\n</sd-modal>\r\n<sd-modal [title]=\"'core.component.form-builder.config-variable' | translate\" view=\"dialog\" width=\"800px\" #popupConfigureVariables>\r\n @for (variable of clonedVariables; track variable.id) {\r\n <div class=\"d-flex align-items-center\" style=\"gap: 8px\">\r\n <sd-input [form]=\"form\" [label]=\"'core.component.form-builder.field-key' | translate\" [(model)]=\"variable.key\" required></sd-input>\r\n <sd-input [form]=\"form\" [label]=\"'core.component.form-builder.display-label' | translate\" [(model)]=\"variable.label\" required></sd-input>\r\n <sd-button prefixIcon=\"delete\" (click)=\"removeVariables(variable.id)\" color=\"error\"></sd-button>\r\n </div>\r\n }\r\n <a href=\"javascript:;\" (click)=\"addVariables()\">{{ 'core.component.form-builder.add-variable' | translate }}</a>\r\n <ng-container sdFooter>\r\n <sd-button type=\"fill\" [title]=\"'core.component.form-builder.save' | translate\" (click)=\"updateVariables()\" prefixIcon=\"save\" color=\"primary\"></sd-button>\r\n </ng-container>\r\n</sd-modal>\r\n<configure-validation (accept)=\"onUpdateValidations($event)\"></configure-validation>\r\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"], 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:
6474
6385
  // Controls
6475
- 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", "tooltip", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "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" }] });
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" }] });
6476
6387
  }
6477
6388
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdFormBuilder, decorators: [{
6478
6389
  type: Component,
@@ -6513,14 +6424,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImpo
6513
6424
  SdButton,
6514
6425
  SdFormRender,
6515
6426
  ConfigureValidationComponent, TranslatePipe
6516
- ], template: "<!-- cdkDropListGroup: auto-connect t\u1EA5t c\u1EA3 cdkDropList trong palette, canvas rows, group bodies.\r\n [class.fb-shell--dragging]: JS-driven flag \u0111\u1EC3 \u1EA9n hover/actions/resize to\u00E0n di\u1EC7n\r\n trong l\u00FAc drag (CSS :has() kh\u00F4ng b\u1EAFt 100% tr\u01B0\u1EDDng h\u1EE3p v\u1EDBi CDK preview clone). -->\r\n<div class=\"fb-shell\" cdkDropListGroup [class.fb-shell--dragging]=\"isAnyDragging()\">\r\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 -->\r\n <aside class=\"fb-palette\">\r\n <div class=\"fb-panel-header\">\r\n <div class=\"fb-panel-header__title\">{{ 'core.component.form-builder.components' | translate }}</div>\r\n </div>\r\n\r\n <!-- Search pill -->\r\n <div class=\"fb-palette__search\">\r\n <span class=\"msi\" aria-hidden=\"true\">search</span>\r\n <input\r\n type=\"text\"\r\n [placeholder]=\"'core.component.form-builder.search-components' | translate\"\r\n [value]=\"paletteSearch()\"\r\n (input)=\"paletteSearch.set($any($event.target).value)\" />\r\n <span class=\"kbd\">\u2318K</span>\r\n </div>\r\n\r\n <div class=\"fb-palette__scroll\">\r\n @for (group of paletteGroups(); track group.key) {\r\n <div class=\"fb-palette__group-label\">{{ group.label }}</div>\r\n <div\r\n class=\"fb-palette__list\"\r\n cdkDropList\r\n [cdkDropListData]=\"group.items\"\r\n [cdkDropListEnterPredicate]=\"noReturnPredicate\">\r\n @for (formBuilderComponent of group.items; track formBuilderComponent.type) {\r\n <div\r\n cdkDrag\r\n class=\"fb-palette-item\"\r\n [matTooltip]=\"formBuilderComponent.name\"\r\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>\r\n <span class=\"fb-palette-item__name\">{{ formBuilderComponent.name }}</span>\r\n <span class=\"fb-palette-item__drag\"><span class=\"msi\">drag_indicator</span></span>\r\n <!-- Generic drag preview: ch\u1EC9 icon (puck), label \u1EA9n -->\r\n <div *cdkDragPreview class=\"fb-drag-preview\"></div>\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n </aside>\r\n\r\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 -->\r\n <section class=\"fb-canvas\">\r\n <header class=\"fb-canvas__header\">\r\n <div class=\"fb-canvas__title\">\r\n <span>{{ 'core.component.form-builder.layout' | translate }}</span>\r\n </div>\r\n <div class=\"fb-canvas__actions\">\r\n <!-- Toolbar cluster: view JSON / variables / validation -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-icon-btn\"\r\n [matTooltip]=\"'core.component.form-builder.view-json' | translate\"\r\n (click)=\"viewJSON()\">\r\n <span class=\"msi\">data_object</span>\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-icon-btn\"\r\n [matTooltip]=\"'core.component.form-builder.config-variable' | translate\"\r\n (click)=\"configureVariables()\">\r\n <span class=\"msi\">data_array</span>\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-icon-btn\"\r\n [matTooltip]=\"'core.component.form-builder.config-validation-warning-error' | translate\"\r\n (click)=\"openConfigureValidation()\">\r\n <span class=\"msi\">rule</span>\r\n </button>\r\n\r\n <span class=\"fb-divider\"></span>\r\n\r\n <!-- M3 segmented Design/Preview toggle -->\r\n <div class=\"fb-mode-toggle\" role=\"tablist\">\r\n <button\r\n type=\"button\"\r\n role=\"tab\"\r\n [class.is-active]=\"!isPreview()\"\r\n (click)=\"setMode(false)\">\r\n <span class=\"msi\">edit</span>\r\n <span>{{ 'core.component.form-builder.design' | translate }}</span>\r\n </button>\r\n <button\r\n type=\"button\"\r\n role=\"tab\"\r\n [class.is-active]=\"isPreview()\"\r\n (click)=\"setMode(true)\">\r\n <span class=\"msi\">visibility</span>\r\n <span>{{ 'core.component.form-builder.preview' | translate }}</span>\r\n </button>\r\n </div>\r\n </div>\r\n </header>\r\n\r\n <div\r\n class=\"fb-canvas__body\"\r\n [class.is-preview]=\"isPreview()\"\r\n id=\"frmComponent\">\r\n @if (isPreview()) {\r\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 -->\r\n <div class=\"fb-preview-card\">\r\n <div class=\"fb-preview-card__chip\">\r\n <span class=\"msi\">visibility</span>\r\n {{ 'core.component.form-builder.preview-runtime' | translate }}\r\n </div>\r\n <sd-form-render [configuration]=\"{ components: this.components, validations: this.validations }\"></sd-form-render>\r\n <div class=\"fb-preview-card__footer\">\r\n <sd-button type=\"outline\" [title]=\"'core.component.form-builder.cancel' | translate\" (click)=\"setMode(false)\"></sd-button>\r\n <sd-button type=\"fill\" color=\"primary\" [title]=\"'core.component.form-builder.submit' | translate\" (click)=\"onValidate()\"></sd-button>\r\n </div>\r\n </div>\r\n } @else {\r\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 -->\r\n @if (!dragDropRows.length) {\r\n <div class=\"fb-empty\">\r\n <div class=\"fb-empty__art\"><span class=\"msi\">dashboard_customize</span></div>\r\n <div class=\"fb-empty__title\">{{ 'core.component.form-builder.empty-title' | translate }}</div>\r\n <div class=\"fb-empty__hint\">\r\n {{ 'core.component.form-builder.empty-hint' | translate }}\r\n <span class=\"fb-empty__kbd\"><span class=\"msi\">keyboard</span>\u2318 K</span>\r\n </div>\r\n </div>\r\n }\r\n\r\n <div\r\n cdkDropList\r\n cdkDropListOrientation=\"vertical\"\r\n [cdkDropListData]=\"dragDropRows\"\r\n (cdkDropListDropped)=\"drop($event)\"\r\n (click)=\"clickFormContentEmpty()\"\r\n aria-hidden=\"true\">\r\n @for (row of dragDropRows; track row.rowIndex) {\r\n <div\r\n cdkDrag\r\n class=\"fb-row\"\r\n (mouseover)=\"onMouseover($event, row)\"\r\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\">\r\n <div class=\"fb-row__drag\" [matTooltip]=\"'core.component.form-builder.drag-vertically' | translate\" cdkDragHandle>\r\n <span class=\"msi\">drag_indicator</span>\r\n </div>\r\n <!-- Generic puck preview -->\r\n <div *cdkDragPreview class=\"fb-drag-preview\"></div>\n <!-- Quick-add break button \u2014 show on row hover, insert break after this row -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-row-break-btn\"\r\n [matTooltip]=\"'core.component.form-builder.break' | translate\"\r\n matTooltipPosition=\"right\"\r\n (click)=\"insertBreakAfter(row); $event.stopPropagation()\">\r\n <span class=\"msi\">horizontal_rule</span>\r\n </button>\r\n\r\n <div\r\n class=\"fb-row__items\"\r\n style=\"display: flex; flex: 1; gap: var(--gap-2);\"\r\n cdkDropList\r\n cdkDropListOrientation=\"horizontal\"\r\n [cdkDropListData]=\"row.items\"\r\n (cdkDropListDropped)=\"drop($event)\">\r\n\r\n @for (item of row.items; track item.id; let i = $index) {\r\n <!-- Group component renders as outlined card with colored header bar -->\r\n @if (item.type === 'group') {\r\n @let g = $any(item);\r\n @let cv = groupColorVars(g.properties?.color);\r\n <div\r\n cdkDrag\r\n [id]=\"item.id\"\r\n class=\"fb-group column-{{ item.layout?.columns || 12 }}\"\r\n [class.fb-item]=\"false\"\r\n [class.is-selected]=\"item.id === selectedComponent()?.id\"\r\n [class.is-hidden]=\"item.properties?.hidden\"\r\n [style.flex]=\"'0 0 calc(' + (+(item.layout?.columns || 12)) + '/12 * 100% - var(--gap-2))'\"\r\n [style.maxWidth]=\"'calc(' + (+(item.layout?.columns || 12)) + '/12 * 100% - var(--gap-2))'\"\r\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 -->\r\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. -->\r\n <div class=\"fb-group__header\" cdkDragHandle [style.background]=\"cv.bg\" [style.color]=\"cv.fg\">\r\n <span class=\"fb-group__icon\"><span class=\"msi\">{{ g.properties?.icon || 'category' }}</span></span>\r\n <span class=\"fb-group__label\">{{ item.label }}</span>\r\n @if (hasConditional(item)) {\r\n <span class=\"fb-group__conditional-chip\">\r\n <span class=\"msi\" style=\"font-size: 12px;\">bolt</span>\r\n {{ 'core.component.form-builder.conditional' | translate }}\r\n </span>\r\n }\r\n <span class=\"fb-group__count\">{{ g.components?.length || 0 }} fields</span>\r\n </div>\r\n <!-- Floating action toolbar -->\r\n <div class=\"fb-actions\">\r\n <button type=\"button\" class=\"fb-icon-btn\"\r\n [matTooltip]=\"'core.component.form-builder.duplicate' | translate\"\r\n (click)=\"onDuplicate(item); $event.stopPropagation()\">\r\n <span class=\"msi\">content_copy</span>\r\n </button>\r\n <button type=\"button\" class=\"fb-icon-btn\"\r\n [class.warning]=\"item.properties?.hidden\"\r\n [class.on]=\"item.properties?.hidden\"\r\n [matTooltip]=\"item.properties?.hidden ? ('core.component.form-builder.show' | translate) : ('core.component.form-builder.hide' | translate)\"\r\n (click)=\"onChangeHidden(item); $event.stopPropagation()\">\r\n <span class=\"msi\">{{ item.properties?.hidden ? 'visibility_off' : 'visibility' }}</span>\r\n </button>\r\n <button type=\"button\" class=\"fb-icon-btn danger\"\r\n [matTooltip]=\"'core.component.form-builder.delete' | translate\"\r\n (click)=\"removeComponent(item.id); $event.stopPropagation()\">\r\n <span class=\"msi\">delete</span>\r\n </button>\r\n </div>\r\n\r\n <div\n class=\"fb-group__body\"\n cdkDropList\n cdkDropListOrientation=\"horizontal\"\n [cdkDropListData]=\"g.components\"\n (cdkDropListDropped)=\"dropInGroup($event, g)\">\n @if (!g.components?.length) {\r\n <div class=\"fb-group__empty\">\r\n <span class=\"msi\" style=\"font-size: 16px; margin-right: 6px;\">south</span>\r\n {{ 'core.component.form-builder.group-drop-here' | translate }}\r\n </div>\r\n }\r\n @for (child of g.components; track child.id; let ci = $index) {\r\n <div\r\n cdkDrag\r\n [id]=\"child.id\"\r\n class=\"fb-item is-child column-{{ child.layout?.columns || 12 }}\"\r\n [class.is-selected]=\"child.id === selectedComponent()?.id\"\r\n [class.is-hidden]=\"child.properties?.hidden\"\r\n [class.is-readonly]=\"child.properties?.viewed\"\r\n (click)=\"selectComponent(child); $event.stopPropagation()\"\n (cdkDragStarted)=\"onAnyDragStarted()\"\n (cdkDragMoved)=\"onAnyDragMoved($event)\"\n (cdkDragEnded)=\"onAnyDragEnded()\">\n <!-- Generic puck preview -->\r\n <div *cdkDragPreview class=\"fb-drag-preview\"></div>\n <ng-container\r\n *ngTemplateOutlet=\"controlTemplate; context: { $implicit: child }\">\r\n </ng-container>\r\n <!-- Resize handle cho group child \u2014 chia 12-col ngay trong group body.\r\n Wrapping cdkDropList c\u00F3 enterPredicate=false \u0111\u1EC3 KH\u00D4NG nh\u1EADn drop\r\n (ch\u1EC9 d\u00F9ng l\u00E0m context cho cdkDrag), tr\u00E1nh xung \u0111\u1ED9t v\u1EDBi group__body drop list. -->\r\n <div cdkDropList cdkDropListOrientation=\"horizontal\" [cdkDropListEnterPredicate]=\"noReturnPredicate\">\r\n <div\r\n cdkDrag\r\n cdkDragLockAxis=\"x\"\r\n class=\"fb-resize\"\r\n (cdkDragStarted)=\"onAnyDragStarted()\"\r\n (cdkDragMoved)=\"changeSizeChildInGroup($event, child, g)\"\r\n (cdkDragEnded)=\"dragEndChangeSizeControl($event); onAnyDragEnded()\"\r\n [matTooltip]=\"'core.component.form-builder.flex' | translate\"\r\n aria-label=\"resize\"></div>\r\n </div>\r\n <!-- Inline status chips for nested children -->\r\n @if (child.properties?.hidden) {\r\n <span class=\"fb-status-chip hidden\">\r\n <span class=\"msi\">visibility_off</span>\r\n {{ 'core.component.form-builder.hidden' | translate }}\r\n </span>\r\n } @else if (child.properties?.viewed) {\r\n <span class=\"fb-status-chip readonly\">\r\n <span class=\"msi\">edit_off</span>\r\n {{ 'core.component.form-builder.read-only' | translate }}\r\n </span>\r\n } @else if (hasConditional(child)) {\r\n <span class=\"fb-status-chip conditional\">\r\n <span class=\"msi\">bolt</span>\r\n {{ 'core.component.form-builder.conditional' | translate }}\r\n </span>\r\n }\r\n <div class=\"fb-actions\">\r\n <button type=\"button\" class=\"fb-icon-btn\"\r\n [class.warning]=\"child.properties?.viewed\"\r\n [class.on]=\"child.properties?.viewed\"\r\n [matTooltip]=\"child.properties?.viewed ? ('core.component.form-builder.unset-readonly' | translate) : ('core.component.form-builder.set-readonly' | translate)\"\r\n (click)=\"onChangeViewed(child); $event.stopPropagation()\">\r\n <span class=\"msi\">edit_off</span>\r\n </button>\r\n <button type=\"button\" class=\"fb-icon-btn\"\r\n [matTooltip]=\"'core.component.form-builder.duplicate' | translate\"\r\n (click)=\"onDuplicate(child); $event.stopPropagation()\">\r\n <span class=\"msi\">content_copy</span>\r\n </button>\r\n <button type=\"button\" class=\"fb-icon-btn\"\r\n [class.warning]=\"child.properties?.hidden\"\r\n [class.on]=\"child.properties?.hidden\"\r\n [matTooltip]=\"child.properties?.hidden ? ('core.component.form-builder.show' | translate) : ('core.component.form-builder.hide' | translate)\"\r\n (click)=\"onChangeHidden(child); $event.stopPropagation()\">\r\n <span class=\"msi\">{{ child.properties?.hidden ? 'visibility_off' : 'visibility' }}</span>\r\n </button>\r\n <button type=\"button\" class=\"fb-icon-btn danger\"\r\n [matTooltip]=\"'core.component.form-builder.delete' | translate\"\r\n (click)=\"removeFromGroup(g, child.id); $event.stopPropagation()\">\r\n <span class=\"msi\">delete</span>\r\n </button>\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n } @else if (item.type === 'break') {\r\n <!-- Break: row separator c\u1ED1 \u0111\u1ECBnh. Render 12-col line, kh\u00F4ng c\u00F3 resize/edit attribute. -->\r\n <div\r\n cdkDrag\r\n [id]=\"item.id\"\r\n class=\"fb-item is-break column-12\"\r\n [class.is-selected]=\"item.id === selectedComponent()?.id\"\r\n [class.is-hidden]=\"item.properties?.hidden\"\r\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 -->\r\n <div *cdkDragPreview class=\"fb-drag-preview\"></div>\n <div class=\"fb-break-line\">\r\n <span class=\"msi\">horizontal_rule</span>\r\n <span class=\"fb-break-label\">{{ 'core.component.form-builder.break' | translate }}</span>\r\n </div>\r\n <div class=\"fb-actions\">\r\n <button type=\"button\" class=\"fb-icon-btn\"\r\n [class.warning]=\"item.properties?.hidden\"\r\n [class.on]=\"item.properties?.hidden\"\r\n [matTooltip]=\"item.properties?.hidden ? ('core.component.form-builder.show' | translate) : ('core.component.form-builder.hide' | translate)\"\r\n (click)=\"onChangeHidden(item); $event.stopPropagation()\">\r\n <span class=\"msi\">{{ item.properties?.hidden ? 'visibility_off' : 'visibility' }}</span>\r\n </button>\r\n <button type=\"button\" class=\"fb-icon-btn danger\"\r\n [matTooltip]=\"'core.component.form-builder.delete' | translate\"\r\n (click)=\"removeComponent(item.id); $event.stopPropagation()\">\r\n <span class=\"msi\">delete</span>\r\n </button>\r\n </div>\r\n </div>\r\n } @else {\r\n <!-- Regular form component -->\r\n <div\r\n cdkDrag\r\n [id]=\"item.id\"\r\n class=\"fb-item column-{{ item.layout?.columns || 12 }}\"\r\n [class.is-selected]=\"item.id === selectedComponent()?.id\"\r\n [class.is-hidden]=\"item.properties?.hidden\"\r\n [class.is-readonly]=\"item.properties?.viewed\"\r\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 -->\r\n <div *cdkDragPreview class=\"fb-drag-preview\"></div>\n\r\n <!-- Status chip (mutually exclusive priority: hidden > readonly > conditional) -->\r\n @if (item.properties?.hidden) {\r\n <span class=\"fb-status-chip hidden\">\r\n <span class=\"msi\">visibility_off</span>\r\n {{ 'core.component.form-builder.hidden' | translate }}\r\n </span>\r\n } @else if (item.properties?.viewed) {\r\n <span class=\"fb-status-chip readonly\">\r\n <span class=\"msi\">edit_off</span>\r\n {{ 'core.component.form-builder.read-only' | translate }}\r\n </span>\r\n } @else if (hasConditional(item)) {\r\n <span class=\"fb-status-chip conditional\">\r\n <span class=\"msi\">bolt</span>\r\n {{ 'core.component.form-builder.conditional' | translate }}\r\n </span>\r\n }\r\n\r\n <!-- Floating action toolbar pill -->\r\n <div class=\"fb-actions\">\r\n <button type=\"button\" class=\"fb-icon-btn\"\r\n [class.warning]=\"item.properties?.viewed\"\r\n [class.on]=\"item.properties?.viewed\"\r\n [matTooltip]=\"item.properties?.viewed ? ('core.component.form-builder.unset-readonly' | translate) : ('core.component.form-builder.set-readonly' | translate)\"\r\n (click)=\"onChangeViewed(item); $event.stopPropagation()\">\r\n <span class=\"msi\">edit_off</span>\r\n </button>\r\n <button type=\"button\" class=\"fb-icon-btn\"\r\n [matTooltip]=\"'core.component.form-builder.duplicate' | translate\"\r\n (click)=\"onDuplicate(item); $event.stopPropagation()\">\r\n <span class=\"msi\">content_copy</span>\r\n </button>\r\n <button type=\"button\" class=\"fb-icon-btn\"\r\n [class.warning]=\"item.properties?.hidden\"\r\n [class.on]=\"item.properties?.hidden\"\r\n [matTooltip]=\"item.properties?.hidden ? ('core.component.form-builder.show' | translate) : ('core.component.form-builder.hide' | translate)\"\r\n (click)=\"onChangeHidden(item); $event.stopPropagation()\">\r\n <span class=\"msi\">{{ item.properties?.hidden ? 'visibility_off' : 'visibility' }}</span>\r\n </button>\r\n <button type=\"button\" class=\"fb-icon-btn danger\"\r\n [matTooltip]=\"'core.component.form-builder.delete' | translate\"\r\n (click)=\"removeComponent(item.id); $event.stopPropagation()\">\r\n <span class=\"msi\">delete</span>\r\n </button>\r\n </div>\r\n\r\n <ng-container\r\n *ngTemplateOutlet=\"controlTemplate; context: { $implicit: item }\">\r\n </ng-container>\r\n\r\n <!-- Resize handle (pill) \u2014 cdkDragLockAxis=\"x\" \u0111\u1EC3 ch\u1EC9 k\u00E9o ngang.\r\n Wrapping cdkDropList c\u00F3 enterPredicate=false, kh\u00F4ng nh\u1EADn drop. -->\r\n <div cdkDropList cdkDropListOrientation=\"horizontal\" [cdkDropListEnterPredicate]=\"noReturnPredicate\">\r\n <div\r\n cdkDrag\r\n cdkDragLockAxis=\"x\"\r\n class=\"fb-resize\"\r\n (cdkDragStarted)=\"onAnyDragStarted()\"\r\n (cdkDragMoved)=\"changeSizeControl($event, item, row.items, i)\"\r\n (cdkDragEnded)=\"dragEndChangeSizeControl($event); onAnyDragEnded()\"\r\n [matTooltip]=\"'core.component.form-builder.flex' | translate\"\r\n aria-label=\"resize\"></div>\r\n </div>\r\n </div>\r\n }\r\n }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n </section>\r\n\r\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 -->\r\n <aside class=\"fb-attrs\">\r\n <div class=\"fb-panel-header\">\r\n <div class=\"fb-panel-header__title\">{{ 'core.component.form-builder.attribute' | translate }}</div>\r\n </div>\r\n\r\n @let sel = selectedComponent();\r\n @if (sel) {\r\n <!-- Selection header: type icon + name + key/type -->\r\n <div class=\"fb-attrs__selection\">\r\n <span class=\"fb-attrs__sel-icon\"><span class=\"msi\">{{ symbolFor(sel) }}</span></span>\r\n <div class=\"fb-attrs__sel-meta\">\r\n <div class=\"fb-attrs__sel-name\">{{ $any(sel).label || ('core.component.form-builder.untitled' | translate) }}</div>\r\n <div class=\"fb-attrs__sel-key\">\r\n @if ($any(sel).key) {\r\n {{ $any(sel).key }} \u00B7 {{ typeLabelFor(sel) }}\r\n } @else {\r\n {{ typeLabelFor(sel) }}\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"fb-attrs__body\" aria-hidden=\"true\">\r\n @if (sel.type === 'textfield') {\r\n <textfield-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></textfield-attribute>\r\n } @else if (sel.type === 'textarea') {\r\n <textarea-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></textarea-attribute>\r\n } @else if (sel.type === 'chip-string') {\r\n <chip-string-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></chip-string-attribute>\r\n } @else if (sel.type === 'chip-calendar') {\r\n <chip-calendar-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></chip-calendar-attribute>\r\n } @else if (sel.type === 'number') {\r\n <number-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></number-attribute>\r\n } @else if (sel.type === 'datetime') {\r\n <datetime-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></datetime-attribute>\r\n } @else if (sel.type === 'select') {\r\n <select-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></select-attribute>\r\n } @else if (sel.type === 'radio') {\r\n <radio-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></radio-attribute>\r\n } @else if (sel.type === 'checkbox') {\r\n <checkbox-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></checkbox-attribute>\r\n } @else if (sel.type === 'html') {\r\n <html-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></html-attribute>\r\n } @else if (sel.type === 'upload') {\r\n <upload-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></upload-attribute>\r\n } @else if (sel.type === 'table') {\r\n <table-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></table-attribute>\r\n } @else if (sel.type === 'group') {\r\n <group-attribute [group]=\"$any(sel)\"></group-attribute>\r\n }\r\n </div>\r\n } @else {\r\n <div class=\"fb-attrs__placeholder\">\r\n <span class=\"msi\">ads_click</span>\r\n <div class=\"title\">{{ 'core.component.form-builder.no-selection-title' | translate }}</div>\r\n <div class=\"hint\">{{ 'core.component.form-builder.no-selection-hint' | translate }}</div>\r\n </div>\r\n }\r\n </aside>\r\n</div>\r\n\r\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 -->\r\n<ng-template #controlTemplate let-item>\r\n @if (item.type === 'textfield') {\r\n <textfield-control [component]=\"item\"></textfield-control>\r\n } @else if (item.type === 'textarea') {\r\n <textarea-control [component]=\"item\"></textarea-control>\r\n } @else if (item.type === 'chip-string') {\r\n <chip-string-control [component]=\"item\"></chip-string-control>\r\n } @else if (item.type === 'chip-calendar') {\r\n <chip-calendar-control [component]=\"item\"></chip-calendar-control>\r\n } @else if (item.type === 'number') {\r\n <number-control [component]=\"item\"></number-control>\r\n } @else if (item.type === 'datetime') {\r\n <datetime-control [component]=\"item\"></datetime-control>\r\n } @else if (item.type === 'select') {\r\n <select-control [component]=\"item\"></select-control>\r\n } @else if (item.type === 'radio') {\r\n <radio-control [component]=\"item\"></radio-control>\r\n } @else if (item.type === 'checkbox') {\r\n <checkbox-control [component]=\"item\"></checkbox-control>\r\n } @else if (item.type === 'html') {\r\n <html-control [component]=\"item\"></html-control>\r\n } @else if (item.type === 'upload') {\r\n <upload-control [component]=\"item\"></upload-control>\r\n } @else if (item.type === 'table') {\r\n <table-control [component]=\"item\"></table-control>\r\n }\r\n</ng-template>\r\n\r\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 -->\r\n<sd-modal title=\"JSON\" view=\"dialog\" width=\"80%\" #popupViewJSON>\r\n <sd-textarea [(model)]=\"jsonString\"></sd-textarea>\r\n <ng-container sdFooter>\r\n <sd-button type=\"fill\" [title]=\"'core.component.form-builder.save' | translate\" (click)=\"updateJSON()\" prefixIcon=\"save\" color=\"primary\"></sd-button>\r\n </ng-container>\r\n</sd-modal>\r\n<sd-modal [title]=\"'core.component.form-builder.config-variable' | translate\" view=\"dialog\" width=\"800px\" #popupConfigureVariables>\r\n @for (variable of clonedVariables; track variable.id) {\r\n <div class=\"d-flex align-items-center\" style=\"gap: 8px\">\r\n <sd-input [form]=\"form\" [label]=\"'core.component.form-builder.field-key' | translate\" [(model)]=\"variable.key\" required></sd-input>\r\n <sd-input [form]=\"form\" [label]=\"'core.component.form-builder.display-label' | translate\" [(model)]=\"variable.label\" required></sd-input>\r\n <sd-button prefixIcon=\"delete\" (click)=\"removeVariables(variable.id)\" color=\"error\"></sd-button>\r\n </div>\r\n }\r\n <a href=\"javascript:;\" (click)=\"addVariables()\">{{ 'core.component.form-builder.add-variable' | translate }}</a>\r\n <ng-container sdFooter>\r\n <sd-button type=\"fill\" [title]=\"'core.component.form-builder.save' | translate\" (click)=\"updateVariables()\" prefixIcon=\"save\" color=\"primary\"></sd-button>\r\n </ng-container>\r\n</sd-modal>\r\n<configure-validation (accept)=\"onUpdateValidations($event)\"></configure-validation>\r\n", styles: [":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}\n"] }]
6427
+ ], template: "<!-- cdkDropListGroup: auto-connect t\u1EA5t c\u1EA3 cdkDropList trong palette, canvas rows, group bodies.\r\n [class.fb-shell--dragging]: JS-driven flag \u0111\u1EC3 \u1EA9n hover/actions/resize to\u00E0n di\u1EC7n\r\n trong l\u00FAc drag (CSS :has() kh\u00F4ng b\u1EAFt 100% tr\u01B0\u1EDDng h\u1EE3p v\u1EDBi CDK preview clone). -->\r\n<div class=\"fb-shell\" cdkDropListGroup [class.fb-shell--dragging]=\"isAnyDragging()\">\r\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 -->\r\n <aside class=\"fb-palette\">\r\n <div class=\"fb-panel-header\">\r\n <div class=\"fb-panel-header__title\">{{ 'core.component.form-builder.components' | translate }}</div>\r\n </div>\r\n\r\n <!-- Search pill -->\r\n <div class=\"fb-palette__search\">\r\n <span class=\"msi\" aria-hidden=\"true\">search</span>\r\n <input\r\n type=\"text\"\r\n [placeholder]=\"'core.component.form-builder.search-components' | translate\"\r\n [value]=\"paletteSearch()\"\r\n (input)=\"paletteSearch.set($any($event.target).value)\" />\r\n <span class=\"kbd\">\u2318K</span>\r\n </div>\r\n\r\n <div class=\"fb-palette__scroll\">\r\n @for (group of paletteGroups(); track group.key) {\r\n <div class=\"fb-palette__group-label\">{{ group.label }}</div>\r\n <div\r\n class=\"fb-palette__list\"\r\n cdkDropList\r\n [cdkDropListData]=\"group.items\"\r\n [cdkDropListEnterPredicate]=\"noReturnPredicate\">\r\n @for (formBuilderComponent of group.items; track formBuilderComponent.type) {\r\n <div\r\n cdkDrag\r\n class=\"fb-palette-item\"\r\n [matTooltip]=\"formBuilderComponent.name\"\r\n matTooltipPosition=\"right\"\r\n (click)=\"addComponent(formBuilderComponent)\"\r\n (cdkDragStarted)=\"onAnyDragStarted()\"\r\n (cdkDragMoved)=\"onAnyDragMoved($event)\"\r\n (cdkDragEnded)=\"onAnyDragEnded()\"\r\n aria-hidden=\"true\">\r\n <span class=\"fb-palette-item__icon\"><span class=\"msi\">{{ formBuilderComponent.symbol }}</span></span>\r\n <span class=\"fb-palette-item__name\">{{ formBuilderComponent.name }}</span>\r\n <span class=\"fb-palette-item__drag\"><span class=\"msi\">drag_indicator</span></span>\r\n <!-- Generic drag preview: ch\u1EC9 icon (puck), label \u1EA9n -->\r\n <div *cdkDragPreview class=\"fb-drag-preview\"></div>\r\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n </aside>\r\n\r\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 -->\r\n <section class=\"fb-canvas\">\r\n @let _editing = editingGroup();\r\n @if (_editing) {\r\n <!-- \u2500\u2500 Drill-in header: \u0111ang thi\u1EBFt k\u1EBF children c\u1EE7a 1 group \u2500\u2500 -->\r\n <header class=\"fb-canvas__header fb-canvas__header--group\">\r\n <div class=\"fb-canvas__title fb-groupedit__crumb\">\r\n <button type=\"button\" class=\"fb-icon-btn\" [matTooltip]=\"'core.component.form-builder.back-to-design' | translate\" (click)=\"cancelGroupEdit()\">\r\n <span class=\"msi\">chevron_left</span>\r\n </button>\r\n <span class=\"fb-groupedit__icon\"><span class=\"msi\">{{ _editing.properties?.icon || 'category' }}</span></span>\r\n <span>{{ _editing.label || ('core.component.form-builder.edit-group' | translate) }}</span>\r\n <span class=\"fb-groupedit__hint\">{{ 'core.component.form-builder.edit-group' | translate }}</span>\r\n </div>\r\n <div class=\"fb-canvas__actions\">\r\n <sd-button type=\"outline\" [title]=\"'core.component.form-builder.cancel' | translate\" (click)=\"cancelGroupEdit()\"></sd-button>\r\n <sd-button type=\"fill\" color=\"primary\" prefixIcon=\"check\" [title]=\"'core.component.form-builder.confirm' | translate\" (click)=\"confirmGroupEdit()\"></sd-button>\r\n </div>\r\n </header>\r\n } @else {\r\n <header class=\"fb-canvas__header\">\r\n <div class=\"fb-canvas__title\">\r\n <span>{{ 'core.component.form-builder.layout' | translate }}</span>\r\n </div>\r\n <div class=\"fb-canvas__actions\">\r\n <!-- Toolbar cluster: view JSON / variables / validation -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-icon-btn\"\r\n [matTooltip]=\"'core.component.form-builder.view-json' | translate\"\r\n (click)=\"viewJSON()\">\r\n <span class=\"msi\">data_object</span>\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-icon-btn\"\r\n [matTooltip]=\"'core.component.form-builder.config-variable' | translate\"\r\n (click)=\"configureVariables()\">\r\n <span class=\"msi\">data_array</span>\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-icon-btn\"\r\n [matTooltip]=\"'core.component.form-builder.config-validation-warning-error' | translate\"\r\n (click)=\"openConfigureValidation()\">\r\n <span class=\"msi\">rule</span>\r\n </button>\r\n\r\n <span class=\"fb-divider\"></span>\r\n\r\n <!-- M3 segmented Design/Preview toggle -->\r\n <div class=\"fb-mode-toggle\" role=\"tablist\">\r\n <button\r\n type=\"button\"\r\n role=\"tab\"\r\n [class.is-active]=\"!isPreview()\"\r\n (click)=\"setMode(false)\">\r\n <span class=\"msi\">edit</span>\r\n <span>{{ 'core.component.form-builder.design' | translate }}</span>\r\n </button>\r\n <button\r\n type=\"button\"\r\n role=\"tab\"\r\n [class.is-active]=\"isPreview()\"\r\n (click)=\"setMode(true)\">\r\n <span class=\"msi\">visibility</span>\r\n <span>{{ 'core.component.form-builder.preview' | translate }}</span>\r\n </button>\r\n </div>\r\n </div>\r\n </header>\r\n }\r\n\r\n <div\r\n class=\"fb-canvas__body\"\r\n [class.is-preview]=\"isPreview()\"\r\n id=\"frmComponent\">\r\n @if (isPreview()) {\r\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 -->\r\n <div class=\"fb-preview-card\">\r\n <div class=\"fb-preview-card__chip\">\r\n <span class=\"msi\">visibility</span>\r\n {{ 'core.component.form-builder.preview-runtime' | translate }}\r\n </div>\r\n <sd-form-render [configuration]=\"{ components: this.components, validations: this.validations }\"></sd-form-render>\r\n <div class=\"fb-preview-card__footer\">\r\n <sd-button type=\"outline\" [title]=\"'core.component.form-builder.cancel' | translate\" (click)=\"setMode(false)\"></sd-button>\r\n <sd-button type=\"fill\" color=\"primary\" [title]=\"'core.component.form-builder.submit' | translate\" (click)=\"onValidate()\"></sd-button>\r\n </div>\r\n </div>\r\n } @else {\r\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 -->\r\n @if (!dragDropRows.length) {\r\n <div class=\"fb-empty\">\r\n <div class=\"fb-empty__art\"><span class=\"msi\">dashboard_customize</span></div>\r\n <div class=\"fb-empty__title\">{{ 'core.component.form-builder.empty-title' | translate }}</div>\r\n <div class=\"fb-empty__hint\">\r\n {{ 'core.component.form-builder.empty-hint' | translate }}\r\n <span class=\"fb-empty__kbd\"><span class=\"msi\">keyboard</span>\u2318 K</span>\r\n </div>\r\n </div>\r\n }\r\n\r\n <div\r\n cdkDropList\r\n cdkDropListOrientation=\"vertical\"\r\n [cdkDropListData]=\"dragDropRows\"\r\n (cdkDropListDropped)=\"drop($event)\"\r\n (click)=\"clickFormContentEmpty()\"\r\n aria-hidden=\"true\">\r\n @for (row of dragDropRows; track row.rowIndex) {\r\n <div\r\n cdkDrag\r\n class=\"fb-row\"\r\n (mouseover)=\"onMouseover($event, row)\"\r\n (focus)=\"onFocus($event)\"\r\n (cdkDragStarted)=\"onAnyDragStarted()\"\r\n (cdkDragMoved)=\"onAnyDragMoved($event)\"\r\n (cdkDragEnded)=\"onAnyDragEnded()\"\r\n [class.is-drag-target]=\"targetItem === row\"\r\n [id]=\"'row_' + row.rowIndex\"\r\n aria-hidden=\"true\">\r\n <div class=\"fb-row__drag\" [matTooltip]=\"'core.component.form-builder.drag-vertically' | translate\" cdkDragHandle>\r\n <span class=\"msi\">drag_indicator</span>\r\n </div>\r\n <!-- Generic puck preview -->\r\n <div *cdkDragPreview class=\"fb-drag-preview\"></div>\r\n <!-- Quick-add break button \u2014 show on row hover, insert break after this row -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-row-break-btn\"\r\n [matTooltip]=\"'core.component.form-builder.break' | translate\"\r\n matTooltipPosition=\"right\"\r\n (click)=\"insertBreakAfter(row); $event.stopPropagation()\">\r\n <span class=\"msi\">horizontal_rule</span>\r\n </button>\r\n\r\n <div\r\n class=\"fb-row__items\"\r\n style=\"display: flex; flex: 1; gap: var(--gap-2);\"\r\n cdkDropList\r\n cdkDropListOrientation=\"horizontal\"\r\n [cdkDropListData]=\"row.items\"\r\n (cdkDropListDropped)=\"drop($event)\">\r\n\r\n @for (item of row.items; track item.id; let i = $index) {\r\n <!-- Group component renders as outlined card with colored header bar -->\r\n @if (item.type === 'group') {\r\n @let g = $any(item);\r\n @let cv = groupColorVars(g.properties?.color);\r\n <div\r\n cdkDrag\r\n [id]=\"item.id\"\r\n class=\"fb-group column-{{ item.layout?.columns || 12 }}\"\r\n [class.fb-item]=\"false\"\r\n [class.is-selected]=\"item.id === selectedComponent()?.id\"\r\n [class.is-hidden]=\"item.properties?.hidden\"\r\n [style.flex]=\"'0 0 calc(' + (+(item.layout?.columns || 12)) + '/12 * 100% - var(--gap-2))'\"\r\n [style.maxWidth]=\"'calc(' + (+(item.layout?.columns || 12)) + '/12 * 100% - var(--gap-2))'\"\r\n (click)=\"selectComponent(item); $event.stopPropagation()\"\r\n (cdkDragStarted)=\"dragStartComponentItem($event); onAnyDragStarted()\"\r\n (cdkDragMoved)=\"onAnyDragMoved($event)\"\r\n (cdkDragEnded)=\"dragEndComponentItem($event); onAnyDragEnded()\"\r\n aria-hidden=\"true\">\r\n <!-- Generic puck preview -->\r\n <div *cdkDragPreview class=\"fb-drag-preview\"></div>\r\n <!-- cdkDragHandle: ch\u1EC9 cho ph\u00E9p k\u00E9o group qua header \u2014 body free cho click v\u00E0o child. -->\r\n <div class=\"fb-group__header\" cdkDragHandle [style.background]=\"cv.bg\" [style.color]=\"cv.fg\">\r\n <span class=\"fb-group__icon\"><span class=\"msi\">{{ g.properties?.icon || 'category' }}</span></span>\r\n <span class=\"fb-group__label\">{{ item.label }}</span>\r\n @if (hasConditional(item)) {\r\n <span class=\"fb-group__conditional-chip\">\r\n <span class=\"msi\" style=\"font-size: 12px;\">bolt</span>\r\n {{ 'core.component.form-builder.conditional' | translate }}\r\n </span>\r\n }\r\n <span class=\"fb-group__count\">{{ g.components?.length || 0 }} fields</span>\r\n </div>\r\n <!-- Floating action toolbar -->\r\n <div class=\"fb-actions\">\r\n <!-- why: Detail = m\u1EDF canvas thi\u1EBFt k\u1EBF ri\u00EAng cho group (thay cho drag/drop trong group). -->\r\n <button type=\"button\" class=\"fb-icon-btn\"\r\n [matTooltip]=\"'core.component.form-builder.edit-group' | translate\"\r\n (click)=\"enterGroupEdit(g); $event.stopPropagation()\">\r\n <span class=\"msi\">open_in_full</span>\r\n </button>\r\n <button type=\"button\" class=\"fb-icon-btn\"\r\n [matTooltip]=\"'core.component.form-builder.duplicate' | translate\"\r\n (click)=\"onDuplicate(item); $event.stopPropagation()\">\r\n <span class=\"msi\">content_copy</span>\r\n </button>\r\n <button type=\"button\" class=\"fb-icon-btn\"\r\n [class.warning]=\"item.properties?.hidden\"\r\n [class.on]=\"item.properties?.hidden\"\r\n [matTooltip]=\"item.properties?.hidden ? ('core.component.form-builder.show' | translate) : ('core.component.form-builder.hide' | translate)\"\r\n (click)=\"onChangeHidden(item); $event.stopPropagation()\">\r\n <span class=\"msi\">{{ item.properties?.hidden ? 'visibility_off' : 'visibility' }}</span>\r\n </button>\r\n <button type=\"button\" class=\"fb-icon-btn danger\"\r\n [matTooltip]=\"'core.component.form-builder.delete' | translate\"\r\n (click)=\"removeComponent(item.id); $event.stopPropagation()\">\r\n <span class=\"msi\">delete</span>\r\n </button>\r\n </div>\r\n\r\n <!-- Static preview: children render read-only (kh\u00F4ng drag/resize/select).\r\n S\u1EEDa n\u1ED9i dung group qua n\u00FAt Detail (enterGroupEdit). -->\r\n <div class=\"fb-group__body fb-group__body--static\" (click)=\"enterGroupEdit(g); $event.stopPropagation()\">\r\n @if (!g.components?.length) {\r\n <div class=\"fb-group__empty\">\r\n <span class=\"msi\" style=\"font-size: 16px; margin-right: 6px;\">open_in_full</span>\r\n {{ 'core.component.form-builder.group-empty-hint' | translate }}\r\n </div>\r\n }\r\n @for (child of g.components; track child.id) {\r\n <div\r\n class=\"fb-item is-child fb-item--static column-{{ child.layout?.columns || 12 }}\"\r\n [class.is-hidden]=\"child.properties?.hidden\">\r\n <ng-container\r\n *ngTemplateOutlet=\"controlTemplate; context: { $implicit: child }\">\r\n </ng-container>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n } @else if (item.type === 'break') {\r\n <!-- Break: row separator c\u1ED1 \u0111\u1ECBnh. Render 12-col line, kh\u00F4ng c\u00F3 resize/edit attribute. -->\r\n <div\r\n cdkDrag\r\n [id]=\"item.id\"\r\n class=\"fb-item is-break column-12\"\r\n [class.is-selected]=\"item.id === selectedComponent()?.id\"\r\n [class.is-hidden]=\"item.properties?.hidden\"\r\n (click)=\"selectComponent(item); $event.stopPropagation()\"\r\n (cdkDragStarted)=\"dragStartComponentItem($event); onAnyDragStarted()\"\r\n (cdkDragMoved)=\"onAnyDragMoved($event)\"\r\n (cdkDragEnded)=\"dragEndComponentItem($event); onAnyDragEnded()\"\r\n aria-hidden=\"true\">\r\n <!-- Generic puck preview -->\r\n <div *cdkDragPreview class=\"fb-drag-preview\"></div>\r\n <div class=\"fb-break-line\">\r\n <span class=\"msi\">horizontal_rule</span>\r\n <span class=\"fb-break-label\">{{ 'core.component.form-builder.break' | translate }}</span>\r\n </div>\r\n <div class=\"fb-actions\">\r\n <button type=\"button\" class=\"fb-icon-btn\"\r\n [class.warning]=\"item.properties?.hidden\"\r\n [class.on]=\"item.properties?.hidden\"\r\n [matTooltip]=\"item.properties?.hidden ? ('core.component.form-builder.show' | translate) : ('core.component.form-builder.hide' | translate)\"\r\n (click)=\"onChangeHidden(item); $event.stopPropagation()\">\r\n <span class=\"msi\">{{ item.properties?.hidden ? 'visibility_off' : 'visibility' }}</span>\r\n </button>\r\n <button type=\"button\" class=\"fb-icon-btn danger\"\r\n [matTooltip]=\"'core.component.form-builder.delete' | translate\"\r\n (click)=\"removeComponent(item.id); $event.stopPropagation()\">\r\n <span class=\"msi\">delete</span>\r\n </button>\r\n </div>\r\n </div>\r\n } @else {\r\n <!-- Regular form component -->\r\n <div\r\n cdkDrag\r\n [id]=\"item.id\"\r\n class=\"fb-item column-{{ item.layout?.columns || 12 }}\"\r\n [class.is-selected]=\"item.id === selectedComponent()?.id\"\r\n [class.is-hidden]=\"item.properties?.hidden\"\r\n [class.is-readonly]=\"item.properties?.viewed\"\r\n (click)=\"selectComponent(item); $event.stopPropagation()\"\r\n (cdkDragStarted)=\"dragStartComponentItem($event); onAnyDragStarted()\"\r\n (cdkDragMoved)=\"onAnyDragMoved($event)\"\r\n (cdkDragEnded)=\"dragEndComponentItem($event); onAnyDragEnded()\"\r\n aria-hidden=\"true\">\r\n <!-- Generic puck preview -->\r\n <div *cdkDragPreview class=\"fb-drag-preview\"></div>\r\n\r\n <!-- Status chip (mutually exclusive priority: hidden > readonly > conditional) -->\r\n @if (item.properties?.hidden) {\r\n <span class=\"fb-status-chip hidden\">\r\n <span class=\"msi\">visibility_off</span>\r\n {{ 'core.component.form-builder.hidden' | translate }}\r\n </span>\r\n } @else if (item.properties?.viewed) {\r\n <span class=\"fb-status-chip readonly\">\r\n <span class=\"msi\">edit_off</span>\r\n {{ 'core.component.form-builder.read-only' | translate }}\r\n </span>\r\n } @else if (hasConditional(item)) {\r\n <span class=\"fb-status-chip conditional\">\r\n <span class=\"msi\">bolt</span>\r\n {{ 'core.component.form-builder.conditional' | translate }}\r\n </span>\r\n }\r\n\r\n <!-- Floating action toolbar pill -->\r\n <div class=\"fb-actions\">\r\n <button type=\"button\" class=\"fb-icon-btn\"\r\n [class.warning]=\"item.properties?.viewed\"\r\n [class.on]=\"item.properties?.viewed\"\r\n [matTooltip]=\"item.properties?.viewed ? ('core.component.form-builder.unset-readonly' | translate) : ('core.component.form-builder.set-readonly' | translate)\"\r\n (click)=\"onChangeViewed(item); $event.stopPropagation()\">\r\n <span class=\"msi\">edit_off</span>\r\n </button>\r\n <button type=\"button\" class=\"fb-icon-btn\"\r\n [matTooltip]=\"'core.component.form-builder.duplicate' | translate\"\r\n (click)=\"onDuplicate(item); $event.stopPropagation()\">\r\n <span class=\"msi\">content_copy</span>\r\n </button>\r\n <button type=\"button\" class=\"fb-icon-btn\"\r\n [class.warning]=\"item.properties?.hidden\"\r\n [class.on]=\"item.properties?.hidden\"\r\n [matTooltip]=\"item.properties?.hidden ? ('core.component.form-builder.show' | translate) : ('core.component.form-builder.hide' | translate)\"\r\n (click)=\"onChangeHidden(item); $event.stopPropagation()\">\r\n <span class=\"msi\">{{ item.properties?.hidden ? 'visibility_off' : 'visibility' }}</span>\r\n </button>\r\n <button type=\"button\" class=\"fb-icon-btn danger\"\r\n [matTooltip]=\"'core.component.form-builder.delete' | translate\"\r\n (click)=\"removeComponent(item.id); $event.stopPropagation()\">\r\n <span class=\"msi\">delete</span>\r\n </button>\r\n </div>\r\n\r\n <ng-container\r\n *ngTemplateOutlet=\"controlTemplate; context: { $implicit: item }\">\r\n </ng-container>\r\n\r\n <!-- Resize handle (pill) \u2014 cdkDragLockAxis=\"x\" \u0111\u1EC3 ch\u1EC9 k\u00E9o ngang.\r\n Wrapping cdkDropList c\u00F3 enterPredicate=false, kh\u00F4ng nh\u1EADn drop. -->\r\n <div cdkDropList cdkDropListOrientation=\"horizontal\" [cdkDropListEnterPredicate]=\"noReturnPredicate\">\r\n <div\r\n cdkDrag\r\n cdkDragLockAxis=\"x\"\r\n class=\"fb-resize\"\r\n (cdkDragStarted)=\"onAnyDragStarted()\"\r\n (cdkDragMoved)=\"changeSizeControl($event, item, row.items, i)\"\r\n (cdkDragEnded)=\"dragEndChangeSizeControl($event); onAnyDragEnded()\"\r\n [matTooltip]=\"'core.component.form-builder.flex' | translate\"\r\n aria-label=\"resize\"></div>\r\n </div>\r\n </div>\r\n }\r\n }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n </section>\r\n\r\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 -->\r\n <aside class=\"fb-attrs\">\r\n <div class=\"fb-panel-header\">\r\n <div class=\"fb-panel-header__title\">{{ 'core.component.form-builder.attribute' | translate }}</div>\r\n </div>\r\n\r\n @let sel = selectedComponent();\r\n @if (sel) {\r\n <!-- Selection header: type icon + name + key/type -->\r\n <div class=\"fb-attrs__selection\">\r\n <span class=\"fb-attrs__sel-icon\"><span class=\"msi\">{{ symbolFor(sel) }}</span></span>\r\n <div class=\"fb-attrs__sel-meta\">\r\n <div class=\"fb-attrs__sel-name\">{{ $any(sel).label || ('core.component.form-builder.untitled' | translate) }}</div>\r\n <div class=\"fb-attrs__sel-key\">\r\n @if ($any(sel).key) {\r\n {{ $any(sel).key }} \u00B7 {{ typeLabelFor(sel) }}\r\n } @else {\r\n {{ typeLabelFor(sel) }}\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"fb-attrs__body\" aria-hidden=\"true\">\r\n @if (sel.type === 'textfield') {\r\n <textfield-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></textfield-attribute>\r\n } @else if (sel.type === 'textarea') {\r\n <textarea-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></textarea-attribute>\r\n } @else if (sel.type === 'chip-string') {\r\n <chip-string-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></chip-string-attribute>\r\n } @else if (sel.type === 'chip-calendar') {\r\n <chip-calendar-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></chip-calendar-attribute>\r\n } @else if (sel.type === 'number') {\r\n <number-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></number-attribute>\r\n } @else if (sel.type === 'datetime') {\r\n <datetime-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></datetime-attribute>\r\n } @else if (sel.type === 'select') {\r\n <select-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></select-attribute>\r\n } @else if (sel.type === 'radio') {\r\n <radio-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></radio-attribute>\r\n } @else if (sel.type === 'checkbox') {\r\n <checkbox-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></checkbox-attribute>\r\n } @else if (sel.type === 'html') {\r\n <html-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></html-attribute>\r\n } @else if (sel.type === 'upload') {\r\n <upload-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></upload-attribute>\r\n } @else if (sel.type === 'table') {\r\n <table-attribute [components]=\"components\" [variables]=\"variables\" [component]=\"sel\"></table-attribute>\r\n } @else if (sel.type === 'group') {\r\n <group-attribute [group]=\"$any(sel)\"></group-attribute>\r\n }\r\n </div>\r\n } @else {\r\n <div class=\"fb-attrs__placeholder\">\r\n <span class=\"msi\">ads_click</span>\r\n <div class=\"title\">{{ 'core.component.form-builder.no-selection-title' | translate }}</div>\r\n <div class=\"hint\">{{ 'core.component.form-builder.no-selection-hint' | translate }}</div>\r\n </div>\r\n }\r\n </aside>\r\n</div>\r\n\r\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 -->\r\n<ng-template #controlTemplate let-item>\r\n @if (item.type === 'textfield') {\r\n <textfield-control [component]=\"item\"></textfield-control>\r\n } @else if (item.type === 'textarea') {\r\n <textarea-control [component]=\"item\"></textarea-control>\r\n } @else if (item.type === 'chip-string') {\r\n <chip-string-control [component]=\"item\"></chip-string-control>\r\n } @else if (item.type === 'chip-calendar') {\r\n <chip-calendar-control [component]=\"item\"></chip-calendar-control>\r\n } @else if (item.type === 'number') {\r\n <number-control [component]=\"item\"></number-control>\r\n } @else if (item.type === 'datetime') {\r\n <datetime-control [component]=\"item\"></datetime-control>\r\n } @else if (item.type === 'select') {\r\n <select-control [component]=\"item\"></select-control>\r\n } @else if (item.type === 'radio') {\r\n <radio-control [component]=\"item\"></radio-control>\r\n } @else if (item.type === 'checkbox') {\r\n <checkbox-control [component]=\"item\"></checkbox-control>\r\n } @else if (item.type === 'html') {\r\n <html-control [component]=\"item\"></html-control>\r\n } @else if (item.type === 'upload') {\r\n <upload-control [component]=\"item\"></upload-control>\r\n } @else if (item.type === 'table') {\r\n <table-control [component]=\"item\"></table-control>\r\n }\r\n</ng-template>\r\n\r\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 -->\r\n<sd-modal title=\"JSON\" view=\"dialog\" width=\"80%\" #popupViewJSON>\r\n <sd-textarea [(model)]=\"jsonString\"></sd-textarea>\r\n <ng-container sdFooter>\r\n <sd-button type=\"fill\" [title]=\"'core.component.form-builder.save' | translate\" (click)=\"updateJSON()\" prefixIcon=\"save\" color=\"primary\"></sd-button>\r\n </ng-container>\r\n</sd-modal>\r\n<sd-modal [title]=\"'core.component.form-builder.config-variable' | translate\" view=\"dialog\" width=\"800px\" #popupConfigureVariables>\r\n @for (variable of clonedVariables; track variable.id) {\r\n <div class=\"d-flex align-items-center\" style=\"gap: 8px\">\r\n <sd-input [form]=\"form\" [label]=\"'core.component.form-builder.field-key' | translate\" [(model)]=\"variable.key\" required></sd-input>\r\n <sd-input [form]=\"form\" [label]=\"'core.component.form-builder.display-label' | translate\" [(model)]=\"variable.label\" required></sd-input>\r\n <sd-button prefixIcon=\"delete\" (click)=\"removeVariables(variable.id)\" color=\"error\"></sd-button>\r\n </div>\r\n }\r\n <a href=\"javascript:;\" (click)=\"addVariables()\">{{ 'core.component.form-builder.add-variable' | translate }}</a>\r\n <ng-container sdFooter>\r\n <sd-button type=\"fill\" [title]=\"'core.component.form-builder.save' | translate\" (click)=\"updateVariables()\" prefixIcon=\"save\" color=\"primary\"></sd-button>\r\n </ng-container>\r\n</sd-modal>\r\n<configure-validation (accept)=\"onUpdateValidations($event)\"></configure-validation>\r\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"] }]
6517
6428
  }], ctorParameters: () => [] });
6518
6429
 
6519
6430
  /* eslint-disable @angular-eslint/no-input-rename */
6520
6431
  /* eslint-disable @typescript-eslint/no-explicit-any */
6521
6432
  // Template là các mẫu do Portal định nghĩa sẵn (key, label ....) để người dùng chọn nhanh
6522
6433
  // Khi thực hiện sao chép 1 template chúng ta sẽ CLONE để tránh ảnh hưởng template gốc
6523
- class SdFeelExpression extends SdBaseSecureComponent {
6434
+ class SdFeelExpression {
6524
6435
  ref;
6525
6436
  expressionFeelPipe;
6526
6437
  formGenericService;
@@ -6554,7 +6465,6 @@ class SdFeelExpression extends SdBaseSecureComponent {
6554
6465
  modelChange = new EventEmitter();
6555
6466
  sdChange = new EventEmitter();
6556
6467
  constructor(ref, expressionFeelPipe, formGenericService) {
6557
- super();
6558
6468
  this.ref = ref;
6559
6469
  this.expressionFeelPipe = expressionFeelPipe;
6560
6470
  this.formGenericService = formGenericService;
@@ -6681,7 +6591,7 @@ class SdFeelExpression extends SdBaseSecureComponent {
6681
6591
  return attributes;
6682
6592
  };
6683
6593
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdFeelExpression, deps: [{ token: i0.ChangeDetectorRef }, { token: ExpressionFeelPipe }, { token: FormGenericService }], target: i0.ɵɵFactoryTarget.Component });
6684
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", 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>\r\n@if (expression) {\r\n<sd-modal [title]=\"'core.component.form-builder.set-expression' | translate\">\r\n <div class=\"c-builder\">\r\n <div class=\"d-flex justify-content-end\">\r\n <sd-button prefixIcon=\"calculate\" class=\"mr-4\" [title]=\"'core.component.form-builder.condition-group' | translate\" color=\"primary\" (click)=\"addCombinator(expression.conditions)\"></sd-button>\r\n <sd-button prefixIcon=\"add\" [title]=\"'core.component.form-builder.condition' | translate\" color=\"primary\" (click)=\"addCondition(expression.conditions)\" type=\"fill\"></sd-button>\r\n </div>\r\n <!-- C\u1EA5p 1 -->\r\n @for (conditionLv1 of expression.conditions; track conditionLv1.key; let idxLv1 = $index) {\r\n <div class=\"d-flex align-items-end py-8\" style=\"gap: 8px;\">\r\n <div class=\"c-condition\">\r\n <!-- D\u00F2ng 1 ch\u1EC9 hi\u1EC7n WHERE -->\r\n @if (idxLv1 === 0) {\r\n <div class=\"c-where\">WHERE</div>\r\n }\r\n <!-- D\u00F2ng 2 hi\u1EC3n th\u1ECB combinator -->\r\n @if (idxLv1 === 1) {\r\n <div class=\"c-select\">\r\n <sd-select\r\n size=\"sm\"\r\n [(model)]=\"expression.combinator\"\r\n [items]=\"[\r\n { value: '&&', display: 'AND' },\r\n { value: '||', display: 'OR' }\r\n ]\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n required\r\n hideInlineError=\"\"></sd-select>\r\n </div>\r\n }\r\n </div>\r\n @if (conditionLv1.type === 'condition') {\r\n <sd-autocomplete size=\"sm\" [form]=\"form\" style=\"width: 200px;\" [(model)]=\"conditionLv1.field\" [items]=\"attributes\" valueField=\"value\" displayField=\"display\" required hideInlineError></sd-autocomplete>\r\n @let fieldAttribute = attribute[conditionLv1.field!];\r\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>\r\n @if(!!fieldAttribute?.type && conditionLv1.operator && conditionLv1.operator !== 'NULL' && conditionLv1.operator !== 'NOT_NULL') {\r\n @if(fieldAttribute.type === 'string') {\r\n <sd-input size=\"sm\" [form]=\"form\" style=\"flex:1\" [(model)]=\"conditionLv1.value\" required hideInlineError></sd-input>\r\n } @else if(fieldAttribute.type === 'number') {\r\n <sd-input-number size=\"sm\" [form]=\"form\" style=\"flex:1\" [(model)]=\"conditionLv1.value\" required hideInlineError></sd-input-number>\r\n } @else if(fieldAttribute.type === 'boolean') {\r\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>\r\n }@else if(fieldAttribute.type === 'values') {\r\n <sd-autocomplete size=\"sm\" [form]=\"form\" style=\"flex:1\" [(model)]=\"conditionLv1.value\" [items]=\"fieldAttribute.values\" valueField=\"value\" displayField=\"display\" required hideInlineError></sd-autocomplete>\r\n }\r\n }\r\n <sd-button\r\n class=\"ml-auto\"\r\n prefixIcon=\"delete\"\r\n [tooltip]=\"'core.component.form-builder.delete' | translate\"\r\n (click)=\"remove(expression.conditions, idxLv1)\"></sd-button>\r\n \r\n } @else if (conditionLv1.type === 'combinator') {\r\n <div class=\"c-builder\">\r\n <div class=\"d-flex justify-content-end\">\r\n <sd-button\r\n class=\"mr-4\"\r\n prefixIcon=\"delete\"\r\n [tooltip]=\"'core.component.form-builder.delete' | translate\"\r\n (click)=\"remove(expression.conditions, idxLv1)\"></sd-button>\r\n <sd-button prefixIcon=\"add\" [title]=\"'core.component.form-builder.condition' | translate\" size=\"sm\" color=\"primary\" (click)=\"addCondition(conditionLv1.conditions)\" type=\"fill\"></sd-button>\r\n </div>\r\n <!-- C\u1EA5p 2 -->\r\n @for (conditionLv2 of conditionLv1.conditions; track conditionLv2.key; let idxLv2 = $index) {\r\n <div class=\"d-flex align-items-end py-8\" style=\"gap: 8px;\">\r\n <div class=\"c-condition\">\r\n <!-- D\u00F2ng 1 ch\u1EC9 hi\u1EC7n WHERE -->\r\n @if (idxLv2 === 0) {\r\n <div class=\"c-where\">WHERE</div>\r\n }\r\n <!-- D\u00F2ng 2 hi\u1EC3n th\u1ECB combinator -->\r\n @if (idxLv2 === 1) {\r\n <div class=\"c-select\">\r\n <sd-select\r\n size=\"sm\"\r\n [(model)]=\"conditionLv1.combinator\"\r\n [items]=\"[\r\n { value: '&&', display: 'AND' },\r\n { value: '||', display: 'OR' }\r\n ]\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n required\r\n hideInlineError=\"\"></sd-select>\r\n </div>\r\n }\r\n </div>\r\n @if (conditionLv2.type === 'condition') {\r\n <sd-autocomplete size=\"sm\" [form]=\"form\" style=\"width: 200px;\" [(model)]=\"conditionLv2.field\" [items]=\"attributes\" valueField=\"value\" displayField=\"display\" required hideInlineError></sd-autocomplete>\r\n @let fieldAttribute = attribute[conditionLv2.field!];\r\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>\r\n @if(!!fieldAttribute?.type && conditionLv2.operator && conditionLv2.operator !== 'NULL' && conditionLv2.operator !== 'NOT_NULL') {\r\n @if(fieldAttribute.type === 'string') {\r\n <sd-input size=\"sm\" [form]=\"form\" style=\"flex:1\" [(model)]=\"conditionLv2.value\" required hideInlineError></sd-input>\r\n } @else if(fieldAttribute.type === 'number') {\r\n <sd-input-number size=\"sm\" [form]=\"form\" style=\"flex:1\" [(model)]=\"conditionLv2.value\" required hideInlineError></sd-input-number>\r\n } @else if(fieldAttribute.type === 'boolean') {\r\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>\r\n }@else if(fieldAttribute.type === 'values') {\r\n <sd-autocomplete size=\"sm\" [form]=\"form\" style=\"flex:1\" [(model)]=\"conditionLv2.value\" [items]=\"fieldAttribute.values\" valueField=\"value\" displayField=\"display\" required hideInlineError></sd-autocomplete>\r\n }\r\n }\r\n <sd-button\r\n class=\"ml-auto\"\r\n prefixIcon=\"delete\"\r\n [tooltip]=\"'core.component.form-builder.delete' | translate\"\r\n (click)=\"remove(conditionLv1.conditions, idxLv1)\"></sd-button>\r\n \r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n <ng-container sdFooter>\r\n <sd-button prefixIcon=\"save\" [title]=\"'core.component.form-builder.confirm' | translate\" (click)=\"onAccept()\" color=\"primary\" type=\"fill\"></sd-button>\r\n </ng-container>\r\n</sd-modal>\r\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", "tooltip", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "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", "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", "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", "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: "19.2.21", 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>\r\n@if (expression) {\r\n<sd-modal [title]=\"'core.component.form-builder.set-expression' | translate\">\r\n <div class=\"c-builder\">\r\n <div class=\"d-flex justify-content-end\">\r\n <sd-button prefixIcon=\"calculate\" class=\"mr-4\" [title]=\"'core.component.form-builder.condition-group' | translate\" color=\"primary\" (click)=\"addCombinator(expression.conditions)\"></sd-button>\r\n <sd-button prefixIcon=\"add\" [title]=\"'core.component.form-builder.condition' | translate\" color=\"primary\" (click)=\"addCondition(expression.conditions)\" type=\"fill\"></sd-button>\r\n </div>\r\n <!-- C\u1EA5p 1 -->\r\n @for (conditionLv1 of expression.conditions; track conditionLv1.key; let idxLv1 = $index) {\r\n <div class=\"d-flex align-items-end py-8\" style=\"gap: 8px;\">\r\n <div class=\"c-condition\">\r\n <!-- D\u00F2ng 1 ch\u1EC9 hi\u1EC7n WHERE -->\r\n @if (idxLv1 === 0) {\r\n <div class=\"c-where\">WHERE</div>\r\n }\r\n <!-- D\u00F2ng 2 hi\u1EC3n th\u1ECB combinator -->\r\n @if (idxLv1 === 1) {\r\n <div class=\"c-select\">\r\n <sd-select\r\n size=\"sm\"\r\n [(model)]=\"expression.combinator\"\r\n [items]=\"[\r\n { value: '&&', display: 'AND' },\r\n { value: '||', display: 'OR' }\r\n ]\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n required\r\n hideInlineError=\"\"></sd-select>\r\n </div>\r\n }\r\n </div>\r\n @if (conditionLv1.type === 'condition') {\r\n <sd-autocomplete size=\"sm\" [form]=\"form\" style=\"width: 200px;\" [(model)]=\"conditionLv1.field\" [items]=\"attributes\" valueField=\"value\" displayField=\"display\" required hideInlineError></sd-autocomplete>\r\n @let fieldAttribute = attribute[conditionLv1.field!];\r\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>\r\n @if(!!fieldAttribute?.type && conditionLv1.operator && conditionLv1.operator !== 'NULL' && conditionLv1.operator !== 'NOT_NULL') {\r\n @if(fieldAttribute.type === 'string') {\r\n <sd-input size=\"sm\" [form]=\"form\" style=\"flex:1\" [(model)]=\"conditionLv1.value\" required hideInlineError></sd-input>\r\n } @else if(fieldAttribute.type === 'number') {\r\n <sd-input-number size=\"sm\" [form]=\"form\" style=\"flex:1\" [(model)]=\"conditionLv1.value\" required hideInlineError></sd-input-number>\r\n } @else if(fieldAttribute.type === 'boolean') {\r\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>\r\n }@else if(fieldAttribute.type === 'values') {\r\n <sd-autocomplete size=\"sm\" [form]=\"form\" style=\"flex:1\" [(model)]=\"conditionLv1.value\" [items]=\"fieldAttribute.values\" valueField=\"value\" displayField=\"display\" required hideInlineError></sd-autocomplete>\r\n }\r\n }\r\n <sd-button\r\n class=\"ml-auto\"\r\n prefixIcon=\"delete\"\r\n [tooltip]=\"'core.component.form-builder.delete' | translate\"\r\n (click)=\"remove(expression.conditions, idxLv1)\"></sd-button>\r\n \r\n } @else if (conditionLv1.type === 'combinator') {\r\n <div class=\"c-builder\">\r\n <div class=\"d-flex justify-content-end\">\r\n <sd-button\r\n class=\"mr-4\"\r\n prefixIcon=\"delete\"\r\n [tooltip]=\"'core.component.form-builder.delete' | translate\"\r\n (click)=\"remove(expression.conditions, idxLv1)\"></sd-button>\r\n <sd-button prefixIcon=\"add\" [title]=\"'core.component.form-builder.condition' | translate\" size=\"sm\" color=\"primary\" (click)=\"addCondition(conditionLv1.conditions)\" type=\"fill\"></sd-button>\r\n </div>\r\n <!-- C\u1EA5p 2 -->\r\n @for (conditionLv2 of conditionLv1.conditions; track conditionLv2.key; let idxLv2 = $index) {\r\n <div class=\"d-flex align-items-end py-8\" style=\"gap: 8px;\">\r\n <div class=\"c-condition\">\r\n <!-- D\u00F2ng 1 ch\u1EC9 hi\u1EC7n WHERE -->\r\n @if (idxLv2 === 0) {\r\n <div class=\"c-where\">WHERE</div>\r\n }\r\n <!-- D\u00F2ng 2 hi\u1EC3n th\u1ECB combinator -->\r\n @if (idxLv2 === 1) {\r\n <div class=\"c-select\">\r\n <sd-select\r\n size=\"sm\"\r\n [(model)]=\"conditionLv1.combinator\"\r\n [items]=\"[\r\n { value: '&&', display: 'AND' },\r\n { value: '||', display: 'OR' }\r\n ]\"\r\n valueField=\"value\"\r\n displayField=\"display\"\r\n required\r\n hideInlineError=\"\"></sd-select>\r\n </div>\r\n }\r\n </div>\r\n @if (conditionLv2.type === 'condition') {\r\n <sd-autocomplete size=\"sm\" [form]=\"form\" style=\"width: 200px;\" [(model)]=\"conditionLv2.field\" [items]=\"attributes\" valueField=\"value\" displayField=\"display\" required hideInlineError></sd-autocomplete>\r\n @let fieldAttribute = attribute[conditionLv2.field!];\r\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>\r\n @if(!!fieldAttribute?.type && conditionLv2.operator && conditionLv2.operator !== 'NULL' && conditionLv2.operator !== 'NOT_NULL') {\r\n @if(fieldAttribute.type === 'string') {\r\n <sd-input size=\"sm\" [form]=\"form\" style=\"flex:1\" [(model)]=\"conditionLv2.value\" required hideInlineError></sd-input>\r\n } @else if(fieldAttribute.type === 'number') {\r\n <sd-input-number size=\"sm\" [form]=\"form\" style=\"flex:1\" [(model)]=\"conditionLv2.value\" required hideInlineError></sd-input-number>\r\n } @else if(fieldAttribute.type === 'boolean') {\r\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>\r\n }@else if(fieldAttribute.type === 'values') {\r\n <sd-autocomplete size=\"sm\" [form]=\"form\" style=\"flex:1\" [(model)]=\"conditionLv2.value\" [items]=\"fieldAttribute.values\" valueField=\"value\" displayField=\"display\" required hideInlineError></sd-autocomplete>\r\n }\r\n }\r\n <sd-button\r\n class=\"ml-auto\"\r\n prefixIcon=\"delete\"\r\n [tooltip]=\"'core.component.form-builder.delete' | translate\"\r\n (click)=\"remove(conditionLv1.conditions, idxLv1)\"></sd-button>\r\n \r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n <ng-container sdFooter>\r\n <sd-button prefixIcon=\"save\" [title]=\"'core.component.form-builder.confirm' | translate\" (click)=\"onAccept()\" color=\"primary\" type=\"fill\"></sd-button>\r\n </ng-container>\r\n</sd-modal>\r\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 });
6685
6595
  }
6686
6596
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdFeelExpression, decorators: [{
6687
6597
  type: Component,