commons-shared-web-ui 0.0.62 → 0.0.63

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 (222) hide show
  1. package/fesm2022/commons-shared-web-ui.mjs +16153 -0
  2. package/fesm2022/commons-shared-web-ui.mjs.map +1 -0
  3. package/index.d.ts +5033 -0
  4. package/package.json +34 -53
  5. package/.claude/settings.local.json +0 -10
  6. package/documentation/alert.md +0 -123
  7. package/documentation/button-dropdown.md +0 -126
  8. package/documentation/button.md +0 -184
  9. package/documentation/cards-usage-guidelines.md +0 -131
  10. package/documentation/configurable-form.md +0 -605
  11. package/documentation/confirmation-modal.md +0 -250
  12. package/documentation/filter-sidebar.md +0 -178
  13. package/documentation/filter-table-selector.md +0 -228
  14. package/documentation/form-builder.md +0 -599
  15. package/documentation/form-components.md +0 -384
  16. package/documentation/form-field-configuration.md +0 -250
  17. package/documentation/nav.md +0 -427
  18. package/documentation/pagination.md +0 -181
  19. package/documentation/side-nav-documentation.md +0 -169
  20. package/documentation/smart-form.md +0 -2819
  21. package/documentation/smart-table.md +0 -1297
  22. package/documentation/snackbar.md +0 -118
  23. package/documentation/style-externalization.md +0 -88
  24. package/documentation/summary-card.md +0 -279
  25. package/documentation/table-column-configuration.md +0 -221
  26. package/documentation/time-picker.md +0 -274
  27. package/ng-package.json +0 -30
  28. package/src/lib/modules/alert/alert.models.ts +0 -6
  29. package/src/lib/modules/alert/alert.module.ts +0 -16
  30. package/src/lib/modules/alert/components/alert/alert.component.html +0 -27
  31. package/src/lib/modules/alert/components/alert/alert.component.scss +0 -92
  32. package/src/lib/modules/alert/components/alert/alert.component.ts +0 -81
  33. package/src/lib/modules/button/button.models.ts +0 -13
  34. package/src/lib/modules/button/button.module.ts +0 -16
  35. package/src/lib/modules/button/components/button/button.component.html +0 -22
  36. package/src/lib/modules/button/components/button/button.component.scss +0 -92
  37. package/src/lib/modules/button/components/button/button.component.ts +0 -67
  38. package/src/lib/modules/button-dropdown/button-dropdown.models.ts +0 -26
  39. package/src/lib/modules/button-dropdown/button-dropdown.module.ts +0 -22
  40. package/src/lib/modules/button-dropdown/components/button-dropdown/button-dropdown.component.html +0 -41
  41. package/src/lib/modules/button-dropdown/components/button-dropdown/button-dropdown.component.scss +0 -135
  42. package/src/lib/modules/button-dropdown/components/button-dropdown/button-dropdown.component.ts +0 -160
  43. package/src/lib/modules/configurable-form/component/configurable-form.component.html +0 -294
  44. package/src/lib/modules/configurable-form/component/configurable-form.component.scss +0 -503
  45. package/src/lib/modules/configurable-form/component/configurable-form.component.ts +0 -628
  46. package/src/lib/modules/configurable-form/configurable-form.examples.ts +0 -154
  47. package/src/lib/modules/configurable-form/configurable-form.model.ts +0 -131
  48. package/src/lib/modules/configurable-form/configurable-form.module.ts +0 -19
  49. package/src/lib/modules/confirmation-modal/components/confirmation-modal/confirmation-modal.component.html +0 -77
  50. package/src/lib/modules/confirmation-modal/components/confirmation-modal/confirmation-modal.component.scss +0 -395
  51. package/src/lib/modules/confirmation-modal/components/confirmation-modal/confirmation-modal.component.ts +0 -266
  52. package/src/lib/modules/confirmation-modal/confirmation-modal.models.ts +0 -71
  53. package/src/lib/modules/confirmation-modal/confirmation-modal.module.ts +0 -20
  54. package/src/lib/modules/filter/components/filter/filter.component.html +0 -131
  55. package/src/lib/modules/filter/components/filter/filter.component.scss +0 -245
  56. package/src/lib/modules/filter/components/filter/filter.component.ts +0 -216
  57. package/src/lib/modules/filter/filter.models.ts +0 -88
  58. package/src/lib/modules/filter/filter.module.ts +0 -24
  59. package/src/lib/modules/filter-sidebar/components/filter-sidebar/filter-sidebar.component.html +0 -112
  60. package/src/lib/modules/filter-sidebar/components/filter-sidebar/filter-sidebar.component.scss +0 -186
  61. package/src/lib/modules/filter-sidebar/components/filter-sidebar/filter-sidebar.component.ts +0 -163
  62. package/src/lib/modules/filter-sidebar/filter-sidebar.models.ts +0 -95
  63. package/src/lib/modules/filter-sidebar/filter-sidebar.module.ts +0 -24
  64. package/src/lib/modules/filter-table-selector/components/filter-table-selector/filter-table-selector.component.html +0 -73
  65. package/src/lib/modules/filter-table-selector/components/filter-table-selector/filter-table-selector.component.scss +0 -321
  66. package/src/lib/modules/filter-table-selector/components/filter-table-selector/filter-table-selector.component.ts +0 -361
  67. package/src/lib/modules/filter-table-selector/filter-table-selector.models.ts +0 -91
  68. package/src/lib/modules/filter-table-selector/filter-table-selector.module.ts +0 -22
  69. package/src/lib/modules/form-builder/components/field-configurator/configurator-config-panel/configurator-config-panel.component.html +0 -63
  70. package/src/lib/modules/form-builder/components/field-configurator/configurator-config-panel/configurator-config-panel.component.scss +0 -496
  71. package/src/lib/modules/form-builder/components/field-configurator/configurator-config-panel/configurator-config-panel.component.ts +0 -445
  72. package/src/lib/modules/form-builder/components/field-configurator/configurator-tree/configurator-tree.component.html +0 -75
  73. package/src/lib/modules/form-builder/components/field-configurator/configurator-tree/configurator-tree.component.scss +0 -210
  74. package/src/lib/modules/form-builder/components/field-configurator/configurator-tree/configurator-tree.component.ts +0 -55
  75. package/src/lib/modules/form-builder/components/field-configurator/field-configurator.component.html +0 -25
  76. package/src/lib/modules/form-builder/components/field-configurator/field-configurator.component.scss +0 -82
  77. package/src/lib/modules/form-builder/components/field-configurator/field-configurator.component.ts +0 -95
  78. package/src/lib/modules/form-builder/components/field-selection/field-selection.component.html +0 -20
  79. package/src/lib/modules/form-builder/components/field-selection/field-selection.component.scss +0 -37
  80. package/src/lib/modules/form-builder/components/field-selection/field-selection.component.ts +0 -94
  81. package/src/lib/modules/form-builder/components/field-selection/group-node/group-node.component.html +0 -46
  82. package/src/lib/modules/form-builder/components/field-selection/group-node/group-node.component.scss +0 -102
  83. package/src/lib/modules/form-builder/components/field-selection/group-node/group-node.component.ts +0 -50
  84. package/src/lib/modules/form-builder/components/field-selection/selection-field-node/selection-field-node.component.html +0 -35
  85. package/src/lib/modules/form-builder/components/field-selection/selection-field-node/selection-field-node.component.scss +0 -67
  86. package/src/lib/modules/form-builder/components/field-selection/selection-field-node/selection-field-node.component.ts +0 -34
  87. package/src/lib/modules/form-builder/components/field-selection/selection-section-node/selection-section-node.component.html +0 -68
  88. package/src/lib/modules/form-builder/components/field-selection/selection-section-node/selection-section-node.component.scss +0 -113
  89. package/src/lib/modules/form-builder/components/field-selection/selection-section-node/selection-section-node.component.ts +0 -74
  90. package/src/lib/modules/form-builder/configs/field-type-schema.map.ts +0 -533
  91. package/src/lib/modules/form-builder/form-builder.module.ts +0 -36
  92. package/src/lib/modules/form-builder/index.ts +0 -9
  93. package/src/lib/modules/form-builder/models/builder.models.ts +0 -7
  94. package/src/lib/modules/form-builder/models/field-configurator.models.ts +0 -38
  95. package/src/lib/modules/form-builder/models/field-selection.models.ts +0 -51
  96. package/src/lib/modules/form-builder/services/field-configurator.service.ts +0 -258
  97. package/src/lib/modules/form-builder/services/field-selection.service.ts +0 -299
  98. package/src/lib/modules/form-builder/services/form-schema-tree.service.ts +0 -670
  99. package/src/lib/modules/form-builder/tokens/builder.tokens.ts +0 -10
  100. package/src/lib/modules/form-builder/utils/constants.ts +0 -43
  101. package/src/lib/modules/form-components/components/checkbox/checkbox.component.html +0 -29
  102. package/src/lib/modules/form-components/components/checkbox/checkbox.component.scss +0 -111
  103. package/src/lib/modules/form-components/components/checkbox/checkbox.component.ts +0 -207
  104. package/src/lib/modules/form-components/components/checkbox/checkbox.models.ts +0 -35
  105. package/src/lib/modules/form-components/components/datepicker/datepicker.component.html +0 -42
  106. package/src/lib/modules/form-components/components/datepicker/datepicker.component.scss +0 -115
  107. package/src/lib/modules/form-components/components/datepicker/datepicker.component.ts +0 -267
  108. package/src/lib/modules/form-components/components/datepicker/datepicker.models.ts +0 -45
  109. package/src/lib/modules/form-components/components/dropdown/dropdown.component.html +0 -74
  110. package/src/lib/modules/form-components/components/dropdown/dropdown.component.scss +0 -252
  111. package/src/lib/modules/form-components/components/dropdown/dropdown.component.ts +0 -377
  112. package/src/lib/modules/form-components/components/dropdown/dropdown.models.ts +0 -53
  113. package/src/lib/modules/form-components/components/input/input.component.html +0 -51
  114. package/src/lib/modules/form-components/components/input/input.component.scss +0 -128
  115. package/src/lib/modules/form-components/components/input/input.component.ts +0 -250
  116. package/src/lib/modules/form-components/components/input/input.models.ts +0 -55
  117. package/src/lib/modules/form-components/components/radio/radio.component.html +0 -22
  118. package/src/lib/modules/form-components/components/radio/radio.component.scss +0 -107
  119. package/src/lib/modules/form-components/components/radio/radio.component.ts +0 -181
  120. package/src/lib/modules/form-components/components/radio/radio.models.ts +0 -39
  121. package/src/lib/modules/form-components/components/search/search.component.html +0 -15
  122. package/src/lib/modules/form-components/components/search/search.component.scss +0 -87
  123. package/src/lib/modules/form-components/components/search/search.component.ts +0 -213
  124. package/src/lib/modules/form-components/components/search/search.models.ts +0 -40
  125. package/src/lib/modules/form-components/components/toggle/toggle.component.html +0 -15
  126. package/src/lib/modules/form-components/components/toggle/toggle.component.scss +0 -81
  127. package/src/lib/modules/form-components/components/toggle/toggle.component.ts +0 -166
  128. package/src/lib/modules/form-components/components/toggle/toggle.models.ts +0 -27
  129. package/src/lib/modules/form-components/directives/click-outside.directive.ts +0 -22
  130. package/src/lib/modules/form-components/form-components.module.ts +0 -42
  131. package/src/lib/modules/form-field-configuration/components/_ffc-controls.scss +0 -89
  132. package/src/lib/modules/form-field-configuration/components/config-field-node/config-field-node.component.html +0 -68
  133. package/src/lib/modules/form-field-configuration/components/config-field-node/config-field-node.component.scss +0 -73
  134. package/src/lib/modules/form-field-configuration/components/config-field-node/config-field-node.component.ts +0 -34
  135. package/src/lib/modules/form-field-configuration/components/config-section-node/config-section-node.component.html +0 -78
  136. package/src/lib/modules/form-field-configuration/components/config-section-node/config-section-node.component.scss +0 -83
  137. package/src/lib/modules/form-field-configuration/components/config-section-node/config-section-node.component.ts +0 -94
  138. package/src/lib/modules/form-field-configuration/components/form-field-configuration/form-field-configuration.component.html +0 -38
  139. package/src/lib/modules/form-field-configuration/components/form-field-configuration/form-field-configuration.component.scss +0 -42
  140. package/src/lib/modules/form-field-configuration/components/form-field-configuration/form-field-configuration.component.ts +0 -92
  141. package/src/lib/modules/form-field-configuration/form-field-configuration.module.ts +0 -23
  142. package/src/lib/modules/form-field-configuration/index.ts +0 -7
  143. package/src/lib/modules/form-field-configuration/models/field-configuration.models.ts +0 -62
  144. package/src/lib/modules/form-field-configuration/services/config-schema-tree.service.ts +0 -159
  145. package/src/lib/modules/form-field-configuration/services/field-configuration.service.ts +0 -228
  146. package/src/lib/modules/material/material.module.ts +0 -94
  147. package/src/lib/modules/nav/components/nav/nav.component.html +0 -34
  148. package/src/lib/modules/nav/components/nav/nav.component.scss +0 -171
  149. package/src/lib/modules/nav/components/nav/nav.component.ts +0 -82
  150. package/src/lib/modules/nav/nav.models.ts +0 -31
  151. package/src/lib/modules/nav/nav.module.ts +0 -17
  152. package/src/lib/modules/pagination/components/pagination/pagination.component.html +0 -52
  153. package/src/lib/modules/pagination/components/pagination/pagination.component.scss +0 -155
  154. package/src/lib/modules/pagination/components/pagination/pagination.component.ts +0 -109
  155. package/src/lib/modules/pagination/pagination.module.ts +0 -17
  156. package/src/lib/modules/side-nav/components/side-nav/side-nav.component.html +0 -56
  157. package/src/lib/modules/side-nav/components/side-nav/side-nav.component.scss +0 -342
  158. package/src/lib/modules/side-nav/components/side-nav/side-nav.component.ts +0 -135
  159. package/src/lib/modules/side-nav/side-nav.models.ts +0 -38
  160. package/src/lib/modules/side-nav/side-nav.module.ts +0 -16
  161. package/src/lib/modules/smart-form/components/form-field/form-field.component.html +0 -1379
  162. package/src/lib/modules/smart-form/components/form-field/form-field.component.scss +0 -2255
  163. package/src/lib/modules/smart-form/components/form-field/form-field.component.ts +0 -3174
  164. package/src/lib/modules/smart-form/components/form-section/form-section.component.html +0 -67
  165. package/src/lib/modules/smart-form/components/form-section/form-section.component.scss +0 -209
  166. package/src/lib/modules/smart-form/components/form-section/form-section.component.ts +0 -142
  167. package/src/lib/modules/smart-form/components/smart-form/smart-form.component.html +0 -253
  168. package/src/lib/modules/smart-form/components/smart-form/smart-form.component.scss +0 -689
  169. package/src/lib/modules/smart-form/components/smart-form/smart-form.component.ts +0 -1134
  170. package/src/lib/modules/smart-form/index.ts +0 -10
  171. package/src/lib/modules/smart-form/models/form-schema.model.ts +0 -803
  172. package/src/lib/modules/smart-form/models/hierarchy-config.model.ts +0 -22
  173. package/src/lib/modules/smart-form/services/expression.service.ts +0 -75
  174. package/src/lib/modules/smart-form/services/smart-form-controller.service.ts +0 -67
  175. package/src/lib/modules/smart-form/smart-form.examples.ts +0 -1324
  176. package/src/lib/modules/smart-form/smart-form.module.ts +0 -40
  177. package/src/lib/modules/smart-form/utils/translation.utils.ts +0 -82
  178. package/src/lib/modules/smart-form/utils/trusted-url.pipe.ts +0 -25
  179. package/src/lib/modules/smart-form/utils/validation.utils.ts +0 -98
  180. package/src/lib/modules/smart-table/components/smart-table/smart-table.component.html +0 -283
  181. package/src/lib/modules/smart-table/components/smart-table/smart-table.component.scss +0 -685
  182. package/src/lib/modules/smart-table/components/smart-table/smart-table.component.ts +0 -1219
  183. package/src/lib/modules/smart-table/models/table-config.model.ts +0 -247
  184. package/src/lib/modules/smart-table/smart-table.module.ts +0 -30
  185. package/src/lib/modules/smart-table/utils/safe-html.pipe.ts +0 -28
  186. package/src/lib/modules/smart-table/utils/smart-table.utils.ts +0 -18
  187. package/src/lib/modules/snackbar/components/snackbar.component.html +0 -41
  188. package/src/lib/modules/snackbar/components/snackbar.component.scss +0 -99
  189. package/src/lib/modules/snackbar/components/snackbar.component.ts +0 -18
  190. package/src/lib/modules/snackbar/models/snackbar.models.ts +0 -10
  191. package/src/lib/modules/snackbar/services/snackbar.service.ts +0 -40
  192. package/src/lib/modules/snackbar/snackbar.module.ts +0 -11
  193. package/src/lib/modules/summary-card/components/summary-card/summary-card.component.html +0 -47
  194. package/src/lib/modules/summary-card/components/summary-card/summary-card.component.scss +0 -199
  195. package/src/lib/modules/summary-card/components/summary-card/summary-card.component.ts +0 -126
  196. package/src/lib/modules/summary-card/summary-card.module.ts +0 -18
  197. package/src/lib/modules/table-column-configuration/components/_tcc-controls.scss +0 -53
  198. package/src/lib/modules/table-column-configuration/components/config-column-node/config-column-node.component.html +0 -47
  199. package/src/lib/modules/table-column-configuration/components/config-column-node/config-column-node.component.scss +0 -50
  200. package/src/lib/modules/table-column-configuration/components/config-column-node/config-column-node.component.ts +0 -33
  201. package/src/lib/modules/table-column-configuration/components/table-column-configuration/table-column-configuration.component.html +0 -30
  202. package/src/lib/modules/table-column-configuration/components/table-column-configuration/table-column-configuration.component.scss +0 -36
  203. package/src/lib/modules/table-column-configuration/components/table-column-configuration/table-column-configuration.component.ts +0 -78
  204. package/src/lib/modules/table-column-configuration/index.ts +0 -5
  205. package/src/lib/modules/table-column-configuration/models/table-column-configuration.models.ts +0 -20
  206. package/src/lib/modules/table-column-configuration/services/table-column-configuration.service.ts +0 -77
  207. package/src/lib/modules/table-column-configuration/table-column-configuration.module.ts +0 -21
  208. package/src/lib/modules/time-picker/components/time-picker/time-picker.component.html +0 -37
  209. package/src/lib/modules/time-picker/components/time-picker/time-picker.component.scss +0 -102
  210. package/src/lib/modules/time-picker/components/time-picker/time-picker.component.ts +0 -178
  211. package/src/lib/modules/time-picker/components/time-wheel-panel/time-wheel-panel.component.html +0 -78
  212. package/src/lib/modules/time-picker/components/time-wheel-panel/time-wheel-panel.component.scss +0 -226
  213. package/src/lib/modules/time-picker/components/time-wheel-panel/time-wheel-panel.component.ts +0 -595
  214. package/src/lib/modules/time-picker/models/time-picker.models.ts +0 -49
  215. package/src/lib/modules/time-picker/time-picker.module.ts +0 -23
  216. package/src/lib/shared-ui.module.ts +0 -55
  217. package/src/lib/utils/constants.ts +0 -11
  218. package/src/lib/utils/storage.utils.ts +0 -37
  219. package/src/lib/utils/string.utils.ts +0 -23
  220. package/src/lib/utils/translation.utils.ts +0 -87
  221. package/src/public-api.ts +0 -127
  222. package/tsconfig.lib.json +0 -15
@@ -1,1379 +0,0 @@
1
- <div class="form-field mb-8px" *ngIf="isVisible" [class.has-error]="errorMessage">
2
-
3
- <!-- ══ ROW Layout ════════════════════════════════════════════════════════ -->
4
- <div *ngIf="isRow" class="form-row grid-row">
5
- <ng-container *ngFor="let child of config.children; trackBy: trackByFieldName">
6
- <div class="row-field" [style.gridColumn]="'span ' + getChildColSpan(child)" *ngIf="child.isEnabled !== false"
7
- [style.display]="rowFieldRef.isVisible === false ? 'none' : null">
8
- <lib-form-field #rowFieldRef [config]="child" [controller]="controller" [formGroup]="formGroup" [allowMulti]="allowMulti">
9
- </lib-form-field>
10
- </div>
11
- </ng-container>
12
- </div>
13
-
14
- <!-- ══ GROUP — allowMulti (repeater) ════════════════════════════════════ -->
15
- <div *ngIf="isGroup && config.sectionConfig?.allowMulti"
16
- class="group-section-wrapper mb-20px"
17
- [class.multi-save-active]="config.sectionConfig?.multiSaveConfig?.active">
18
-
19
- <!-- Multi-Save: header row with label + top-right Add button -->
20
- <div class="multi-save-header d-flex justify-content-between align-items-center mb-24"
21
- *ngIf="config.sectionConfig?.multiSaveConfig?.active">
22
- <h3 class="group-label" *ngIf="config.sectionConfig?.label">{{ config.sectionConfig!.label }}</h3>
23
- <lib-button [variant]="'outline'" [icon]="{type: 'material', value: 'add'}" (click)="addGroupInstance()"
24
- class="btn-add-multi">
25
- {{ addMultiLabel }}
26
- </lib-button>
27
- </div>
28
-
29
- <!-- Standard heading (non-multiSave) -->
30
- <h3 class="group-label"
31
- *ngIf="config.sectionConfig?.label && !config.sectionConfig?.multiSaveConfig?.active">{{
32
- config.sectionConfig!.label }}</h3>
33
-
34
- <!-- ── Standard (non-multiSave) repeater: accordion instances ─────────── -->
35
- <ng-container *ngIf="!config.sectionConfig?.multiSaveConfig?.active">
36
- <div *ngFor="let instance of instanceList; trackBy: trackByInstanceId; let i = index"
37
- class="group-accordion-instance"
38
- [class.is-expanded]="isGroupExpanded(i)">
39
-
40
- <!-- Accordion header -->
41
- <div class="group-accordion-header" (click)="toggleGroupAccordion(i)"
42
- role="button" [attr.aria-expanded]="isGroupExpanded(i)">
43
- <div class="accordion-header-left d-flex align-items-center gap-10">
44
- <span class="instance-badge">{{ i + 1 }}</span>
45
- <span class="instance-title">{{ config.sectionConfig!.label }} #{{ i + 1 }}</span>
46
- </div>
47
- <div class="accordion-header-right d-flex align-items-center gap-6">
48
- <button type="button" class="accordion-remove-btn"
49
- *ngIf="instanceList.length > 1"
50
- (click)="$event.stopPropagation(); removeGroupInstance(i)"
51
- aria-label="Remove">
52
- <mat-icon>delete_outline</mat-icon>
53
- </button>
54
- <mat-icon class="accordion-chevron">
55
- {{ isGroupExpanded(i) ? 'keyboard_arrow_up' : 'keyboard_arrow_down' }}
56
- </mat-icon>
57
- </div>
58
- </div>
59
-
60
- <!-- Accordion body (always mounted so form controls survive collapse) -->
61
- <div class="group-accordion-body" [hidden]="!isGroupExpanded(i)">
62
- <div class="group-fields sf-grid">
63
- <ng-container *ngFor="let field of config.sectionConfig!.children">
64
- <div class="sf-col" [style.gridColumn]="'span ' + (field.colSpan || 12)"
65
- *ngIf="field.isEnabled !== false"
66
- [style.display]="groupFieldRef.isVisible === false ? 'none' : null">
67
- <lib-form-field #groupFieldRef [config]="field" [controller]="instance.rowController" [formGroup]="instance.fg"
68
- [allowMulti]="false" [siblingFormArray]="groupFormArray" [siblingIndex]="i">
69
- </lib-form-field>
70
- </div>
71
- </ng-container>
72
- </div>
73
- </div>
74
- </div>
75
-
76
- <!-- Full-width dashed Add button -->
77
- <button type="button" class="btn-add-group" (click)="addGroupInstance()">
78
- <mat-icon>add</mat-icon> {{ addLabel }} {{ config.sectionConfig!.label }}
79
- </button>
80
- </ng-container>
81
-
82
- <!-- ── MultiSave: card instances ─────────────────────────────────────── -->
83
- <ng-container *ngIf="config.sectionConfig?.multiSaveConfig?.active">
84
- <div *ngFor="let instance of instanceList; trackBy: trackByInstanceId; let i = index"
85
- class="group-instance position-relative mb-16 overflow-hidden"
86
- [class.is-editing]="instance.isEditing"
87
- [class.is-card]="!instance.isEditing">
88
-
89
- <!-- Edit / new form view -->
90
- <div [hidden]="!instance.isEditing">
91
- <div class="group-fields sf-grid">
92
- <ng-container *ngFor="let field of config.sectionConfig!.children">
93
- <div class="sf-col" [style.gridColumn]="'span ' + (field.colSpan || 12)"
94
- *ngIf="field.isEnabled !== false"
95
- [style.display]="groupFieldRef.isVisible === false ? 'none' : null">
96
- <lib-form-field #groupFieldRef [config]="field" [controller]="instance.rowController" [formGroup]="instance.fg"
97
- [allowMulti]="false" [siblingFormArray]="groupFormArray" [siblingIndex]="i">
98
- </lib-form-field>
99
- </div>
100
- </ng-container>
101
- </div>
102
-
103
- <!-- Save / Cancel -->
104
- <div class="group-footer d-flex justify-content-end align-items-center gap-16 p-0-24"
105
- *ngIf="config.sectionConfig?.multiSaveConfig?.active">
106
- <span class="field-error color-error fs-0-75rem" *ngIf="multiSaveError">{{ multiSaveError }}</span>
107
- <div class="footer-actions d-flex gap-12">
108
- <lib-button [variant]="'outline'" (click)="cancelGroupInstance(i)">Cancel</lib-button>
109
- <lib-button [variant]="'primary'" (click)="saveGroupInstance(i)">Save</lib-button>
110
- </div>
111
- </div>
112
- </div>
113
-
114
- <!-- Card view (saved state) -->
115
- <ng-container *ngIf="!instance.isEditing">
116
- <div class="card-view d-flex justify-content-between align-items-center p-18px-24px"
117
- [class.is-expanded]="instance.isExpanded">
118
- <div class="card-content flex-1 d-flex flex-column gap-4 overflow-hidden">
119
- <span class="card-title font-weight-600 overflow-hidden fs-1rem c-111827">{{
120
- instance.fg.get(config.sectionConfig!.multiSaveConfig!.summaryField || '')?.value
121
- || '—' }}</span>
122
- </div>
123
- <div class="card-actions d-flex align-items-center gap-16 ml-20">
124
- <mat-icon class="icon-delete" (click)="removeGroupInstance(i, true)">delete_outline</mat-icon>
125
- <mat-icon class="icon-edit" (click)="editGroupInstance(i)">edit_outline</mat-icon>
126
- <mat-icon class="icon-expand" (click)="toggleExpandGroupInstance(i)">
127
- {{ instance.isExpanded ? 'keyboard_arrow_up' : 'keyboard_arrow_down' }}
128
- </mat-icon>
129
- </div>
130
- </div>
131
- </ng-container>
132
- </div>
133
- </ng-container>
134
- </div>
135
-
136
- <!-- ══ GROUP — single (non-repeater) ════════════════════════════════════ -->
137
- <div *ngIf="isGroup && config.sectionConfig && !config.sectionConfig.allowMulti"
138
- class="group-section-wrapper mb-20px">
139
- <h3 class="group-label" *ngIf="config.sectionConfig.label">{{ config.sectionConfig.label }}</h3>
140
- <div class="group-fields sf-grid">
141
- <ng-container *ngFor="let field of config.sectionConfig.children">
142
- <div class="sf-col" [style.gridColumn]="'span ' + (field.colSpan || 12)" *ngIf="field.isEnabled !== false"
143
- [style.display]="singleGroupFieldRef.isVisible === false ? 'none' : null">
144
- <lib-form-field #singleGroupFieldRef [config]="field" [controller]="controller" [formGroup]="groupFormGroup" [allowMulti]="false">
145
- </lib-form-field>
146
- </div>
147
- </ng-container>
148
- </div>
149
- </div>
150
-
151
-
152
- <!-- ══ Text Input ════════════════════════════════════════════════════════ -->
153
- <div *ngIf="isTextField" class="field-wrapper d-flex flex-column gap-6" [formGroup]="formGroup">
154
- <label *ngIf="config.label" class="field-label d-block font-poppins c-202124 fs-18px">
155
- {{ config.label }}
156
- <span class="required c-DC2626 ml-0-125rem" *ngIf="config.required">*</span>
157
- </label>
158
-
159
- <textarea *ngIf="config.subType === 'LONG'" class="field-input textarea" [placeholder]="config.placeholder || ''"
160
- [formControlName]="config.name!" [class.is-invalid]="errorMessage" rows="4">
161
- </textarea>
162
-
163
- <!-- Password input with show/hide toggle -->
164
- <div *ngIf="config.subType === 'PASSWORD'" class="password-wrapper position-relative d-flex align-items-center">
165
- <input [type]="showPassword ? 'text' : 'password'" class="field-input password-input"
166
- [placeholder]="config.placeholder || ''" [formControlName]="config.name!" [class.is-invalid]="errorMessage">
167
- <button type="button"
168
- class="password-toggle position-absolute cursor-pointer d-flex align-items-center justify-content-center b-none border-none c-6B7280 p-0-25rem"
169
- (click)="showPassword = !showPassword" tabindex="-1"
170
- [attr.aria-label]="showPassword ? 'Hide password' : 'Show password'">
171
- <mat-icon class="eye-icon">{{ showPassword ? 'visibility' : 'visibility_off' }}</mat-icon>
172
- </button>
173
- </div>
174
-
175
- <div class="input-group position-relative d-flex w-100" [class.readonly]="config.readonly">
176
- <span class="input-prefix br-none" *ngIf="config.prefix">{{ config.prefix }}</span>
177
-
178
- <input *ngIf="config.subType !== 'LONG' && config.subType !== 'PASSWORD'"
179
- [type]="config.subType === 'EMAIL' ? 'email' : config.subType === 'PHONE' ? 'tel' : 'text'" class="field-input"
180
- [placeholder]="config.placeholder || ''" [formControlName]="config.name!" [class.is-invalid]="errorMessage"
181
- [readonly]="config.readonly">
182
-
183
- <span class="input-suffix d-flex align-items-center font-weight-500" *ngIf="config.suffix">{{ config.suffix
184
- }}</span>
185
-
186
- <div class="readonly-icons position-absolute d-flex gap-8 pe-none" *ngIf="config.readonly">
187
- <mat-icon class="lock-icon">lock</mat-icon>
188
- </div>
189
-
190
- <!-- Suffix Action Icons (clickable) -->
191
- <div class="suffix-action-icons position-absolute d-flex gap-8 align-items-center"
192
- *ngIf="config.suffixActionIcons?.length && !config.readonly">
193
- <mat-icon class="suffix-action-icon"
194
- *ngFor="let action of config.suffixActionIcons"
195
- [style.color]="action.color || null"
196
- [title]="action.tooltip || ''"
197
- (click)="onSuffixActionClick(action.actionId)">
198
- {{ action.icon }}
199
- </mat-icon>
200
- </div>
201
- </div>
202
-
203
- <span class="field-hint c-6B7280 fs-0-75rem" *ngIf="config.hint && !errorMessage">{{ config.hint }}</span>
204
- <span class="field-error color-error fs-0-75rem" *ngIf="errorMessage">{{ errorMessage }}</span>
205
- <div class="char-count-hint font-poppins font-weight-400 text-14 text-right c-6B7280" *ngIf="showCharCount">
206
- {{ remainingCharacters }} characters remaining
207
- </div>
208
- </div>
209
-
210
- <!-- ══ Number Input ══════════════════════════════════════════════════════ -->
211
- <div *ngIf="isNumberField" class="field-wrapper d-flex flex-column gap-6" [formGroup]="formGroup">
212
- <label *ngIf="config.label" class="field-label d-block font-poppins c-202124 fs-18px">
213
- {{ config.label }}
214
- <span class="required c-DC2626 ml-0-125rem" *ngIf="config.required">*</span>
215
- </label>
216
-
217
- <div class="input-group position-relative d-flex w-100" [class.readonly]="config.readonly">
218
- <span class="input-prefix br-none" *ngIf="config.prefix">{{ config.prefix }}</span>
219
-
220
- <input type="number" class="field-input" [placeholder]="config.placeholder || ''" [formControlName]="config.name!"
221
- [min]="config.numberConfig?.min ?? null" [max]="config.numberConfig?.max ?? null"
222
- [step]="config.numberConfig?.step || 1" [class.is-invalid]="errorMessage" [readonly]="config.readonly">
223
-
224
- <span class="input-suffix d-flex align-items-center font-weight-500" *ngIf="config.suffix">{{ config.suffix
225
- }}</span>
226
-
227
- <div class="readonly-icons position-absolute d-flex gap-8 pe-none" *ngIf="config.readonly">
228
- <mat-icon class="lock-icon">lock</mat-icon>
229
- </div>
230
-
231
- <!-- Suffix Action Icons (clickable) -->
232
- <div class="suffix-action-icons position-absolute d-flex gap-8 align-items-center"
233
- *ngIf="config.suffixActionIcons?.length && !config.readonly">
234
- <mat-icon class="suffix-action-icon"
235
- *ngFor="let action of config.suffixActionIcons"
236
- [style.color]="action.color || null"
237
- [title]="action.tooltip || ''"
238
- (click)="onSuffixActionClick(action.actionId)">
239
- {{ action.icon }}
240
- </mat-icon>
241
- </div>
242
- </div>
243
-
244
- <span class="field-hint c-6B7280 fs-0-75rem" *ngIf="config.hint && !errorMessage">{{ config.hint }}</span>
245
- <span class="field-error color-error fs-0-75rem" *ngIf="errorMessage">{{ errorMessage }}</span>
246
- </div>
247
-
248
- <!-- ══ Date Input ════════════════════════════════════════════════════════ -->
249
- <div *ngIf="isDateField" class="field-wrapper d-flex flex-column gap-6" [formGroup]="formGroup">
250
- <label *ngIf="config.label" class="field-label d-block font-poppins c-202124 fs-18px">
251
- {{ config.label }}
252
- <span class="required c-DC2626 ml-0-125rem" *ngIf="config.required">*</span>
253
- </label>
254
-
255
- <div class="input-group position-relative d-flex w-100" [class.readonly]="config.readonly">
256
- <input matInput [matDatepicker]="datePicker" class="field-input date-input has-icon-right"
257
- [formControlName]="config.name!" [min]="effectiveMinDate" [max]="config.dateConfig?.maxDate"
258
- [class.is-invalid]="errorMessage" [placeholder]="config.placeholder || ''"
259
- [readonly]="config.readonly || config.dateConfig?.inputReadonly"
260
- (click)="!config.readonly && datePicker.open()">
261
- <div class="date-icon-wrapper position-absolute d-flex align-items-center justify-content-center"
262
- *ngIf="!config.readonly">
263
- <mat-datepicker-toggle matSuffix [for]="datePicker"></mat-datepicker-toggle>
264
- </div>
265
- <mat-datepicker #datePicker></mat-datepicker>
266
-
267
- <div class="readonly-icons position-absolute d-flex gap-8 pe-none" *ngIf="config.readonly">
268
- <mat-icon class="lock-icon">lock</mat-icon>
269
- </div>
270
- </div>
271
-
272
- <span class="field-hint c-6B7280 fs-0-75rem" *ngIf="config.hint && !errorMessage">{{ config.hint }}</span>
273
- <span class="field-error color-error fs-0-75rem" *ngIf="errorMessage">{{ errorMessage }}</span>
274
- </div>
275
-
276
- <!-- ══ Time Input ════════════════════════════════════════════════════════ -->
277
- <div *ngIf="isTimeField" class="field-wrapper d-flex flex-column gap-6" [formGroup]="formGroup">
278
- <!-- DEFAULT variant: existing native input — unchanged -->
279
- <ng-container *ngIf="!isWheelVariant">
280
- <label *ngIf="config.label" class="field-label d-block font-poppins c-202124 fs-18px">
281
- {{ config.label }}
282
- <span class="required c-DC2626 ml-0-125rem" *ngIf="config.required">*</span>
283
- </label>
284
-
285
- <div class="input-group position-relative d-flex w-100" [class.readonly]="config.readonly">
286
- <input type="time" class="field-input time-input" [formControlName]="config.name!"
287
- [min]="effectiveMinTime" [max]="effectiveMaxTime"
288
- [class.is-invalid]="errorMessage" [readonly]="config.readonly || config.timeConfig?.inputReadonly">
289
-
290
- <div class="readonly-icons position-absolute d-flex gap-8 pe-none" *ngIf="config.readonly">
291
- <mat-icon class="lock-icon">lock</mat-icon>
292
- </div>
293
- </div>
294
- </ng-container>
295
-
296
- <!-- WHEEL variant: drum-roll picker -->
297
- <lib-time-picker
298
- *ngIf="isWheelVariant"
299
- [formControlName]="config.name!"
300
- [label]="config.label || ''"
301
- [placeholder]="config.timeConfig?.placeholder || ''"
302
- [required]="config.required || false"
303
- [disabled]="config.readonly || false"
304
- [mode]="config.timeConfig?.mode || '12'"
305
- [minuteStep]="config.timeConfig?.minuteStep || 1"
306
- [minTime]="effectiveMinTime || undefined"
307
- [maxTime]="config.timeConfig?.maxTime"
308
- [errorMessage]="errorMessage"
309
- [labels]="timePickerLabels">
310
- </lib-time-picker>
311
-
312
- <span class="field-hint c-6B7280 fs-0-75rem" *ngIf="config.hint && !errorMessage">{{ config.hint }}</span>
313
- <span class="field-error color-error fs-0-75rem" *ngIf="errorMessage">{{ errorMessage }}</span>
314
- </div>
315
-
316
- <!-- ══ Autocomplete ═════════════════════════════════════════════════════ -->
317
- <div *ngIf="isAutocomplete" class="field-wrapper d-flex flex-column gap-6" [formGroup]="formGroup">
318
- <label *ngIf="config.label" class="field-label d-block font-poppins c-202124 fs-18px">
319
- {{ config.label }}
320
- <span class="required c-DC2626 ml-0-125rem" *ngIf="config.required">*</span>
321
- </label>
322
-
323
- <!-- Hidden real control (stores the code value) -->
324
- <input type="hidden" [formControlName]="config.name!">
325
-
326
- <div class="autocomplete-wrapper position-relative d-flex align-items-center w-100"
327
- [class.is-invalid]="errorMessage" [class.readonly]="config.readonly">
328
- <!-- Search icon -->
329
- <mat-icon class="ac-search-icon position-absolute fs-1-1rem c-9CA3AF pe-none">search</mat-icon>
330
-
331
- <input class="field-input ac-input" [formControl]="autocompleteInputCtrl" [matAutocomplete]="auto"
332
- [placeholder]="config.placeholder || 'Search…'" [readonly]="!!config.readonly" [class.is-invalid]="errorMessage"
333
- (blur)="onAutocompleteClear()" autocomplete="off">
334
-
335
- <!-- Clear button -->
336
- <button type="button"
337
- class="ac-clear-btn position-absolute d-flex align-items-center justify-content-center cursor-pointer rounded-50 b-none border-none c-9CA3AF p-0-2rem"
338
- *ngIf="autocompleteInputCtrl.value && !config.readonly"
339
- (click)="autocompleteInputCtrl.setValue(''); updateValue(null)" tabindex="-1" aria-label="Clear">
340
- <mat-icon>close</mat-icon>
341
- </button>
342
-
343
- <mat-autocomplete #auto="matAutocomplete" [panelWidth]="'auto'">
344
- <mat-option *ngFor="let option of filteredOptions" [value]="option.label"
345
- (onSelectionChange)="onAutocompleteSelected(option)">
346
- <span class="ac-option-label">{{ option.label }}</span>
347
-
348
- <!-- Dynamic display fields (image / email / phone / text) -->
349
- <div class="ac-display-fields d-flex flex-wrap gap-6 mt-2" *ngIf="option['displayMeta']?.length">
350
- <ng-container *ngFor="let field of option['displayMeta']">
351
-
352
- <!-- Image avatar -->
353
- <span *ngIf="field.type === 'image' && field.value" class="ac-df-item ac-df-image">
354
- <img [src]="field.value" [alt]="field.label || 'image'" class="ac-df-avatar">
355
- </span>
356
-
357
- <!-- Email -->
358
- <span *ngIf="field.type === 'email' && field.value" class="ac-df-item ac-df-chip">
359
- <mat-icon class="ac-df-icon">mail_outline</mat-icon>
360
- <span *ngIf="field.label" class="ac-df-label">{{ field.label }}</span>
361
- {{ field.value }}
362
- </span>
363
-
364
- <!-- Phone -->
365
- <span *ngIf="field.type === 'phone' && field.value" class="ac-df-item ac-df-chip" [class]="field.className">
366
- <mat-icon class="ac-df-icon">phone</mat-icon>
367
- <span *ngIf="field.label" class="ac-df-label">{{ field.label }}</span>
368
- {{ field.value }}
369
- </span>
370
-
371
- <!-- Custom / Icon-based / Generic Text -->
372
- <span *ngIf="field.type !== 'image' && field.type !== 'email' && field.type !== 'phone' && field.value"
373
- class="ac-df-item" [class.ac-df-chip]="!!field.icon" [class]="field.className">
374
- <mat-icon class="ac-df-icon" *ngIf="field.icon">{{ field.icon }}</mat-icon>
375
- <span *ngIf="field.label" class="ac-df-label">{{ field.label }}</span>
376
- {{ field.value }}
377
- </span>
378
-
379
- </ng-container>
380
- </div>
381
- </mat-option>
382
- <mat-option *ngIf="filteredOptions.length === 0" disabled class="ac-no-results fs-0-8125rem c-6B7280">
383
- No results found
384
- </mat-option>
385
- </mat-autocomplete>
386
-
387
- <div class="readonly-icons position-absolute d-flex gap-8 pe-none" *ngIf="config.readonly">
388
- <mat-icon class="lock-icon">lock</mat-icon>
389
- </div>
390
- </div>
391
-
392
- <span class="field-hint c-6B7280 fs-0-75rem" *ngIf="config.hint && !errorMessage">{{ config.hint }}</span>
393
- <span class="field-error color-error fs-0-75rem" *ngIf="errorMessage">{{ errorMessage }}</span>
394
- </div>
395
-
396
- <!-- ══ Dropdown ══════════════════════════════════════════════════════════ -->
397
- <div *ngIf="isDropdown" class="field-wrapper d-flex flex-column gap-6" [formGroup]="formGroup">
398
- <label *ngIf="config.label" class="field-label d-block font-poppins c-202124 fs-18px">
399
- {{ config.label }}
400
- <span class="required c-DC2626 ml-0-125rem" *ngIf="config.required">*</span>
401
- </label>
402
-
403
- <!-- SINGLE SELECT: native select (default, non-searchable) -->
404
- <div *ngIf="config.subType === 'SINGLE' && !isSearchableDropdown" class="native-select-wrapper">
405
- <select class="field-input" [formControlName]="config.name!"
406
- [class.is-invalid]="errorMessage" [disabled]="optionsLoading || config.disabled">
407
- <option [ngValue]="null" disabled selected>{{ optionsLoading ? (controller.labels['LBL_LOADING'] || 'Loading...') : (config.placeholder || selectPlaceholderLabel) }}</option>
408
- <option *ngFor="let option of displayLocalOptionList" [value]="option.code">
409
- {{ option.label }}
410
- </option>
411
- </select>
412
- <div *ngIf="optionsLoading" class="native-select-spinner" aria-hidden="true"></div>
413
- </div>
414
-
415
- <!-- SINGLE SELECT: custom searchable dropdown -->
416
- <div *ngIf="config.subType === 'SINGLE' && isSearchableDropdown" class="single-select-wrapper"
417
- [class.is-invalid]="errorMessage">
418
-
419
- <div #singleTrigger cdkOverlayOrigin #singleOrigin="cdkOverlayOrigin"
420
- class="field-input single-select-trigger d-flex align-items-center justify-content-between cursor-pointer"
421
- [class.ss-open]="isSingleDropdownOpen"
422
- (click)="!optionsLoading && toggleSingleDropdown($event)">
423
- <span *ngIf="singleSelectedLabel && !optionsLoading" class="single-select-value fs-0-9rem">
424
- {{ singleSelectedLabel }}
425
- </span>
426
- <span *ngIf="!singleSelectedLabel || optionsLoading" class="single-select-placeholder">
427
- {{ optionsLoading ? (controller.labels['LBL_LOADING'] || 'Loading...') : (config.placeholder || selectPlaceholderLabel) }}
428
- </span>
429
- <div class="d-flex align-items-center">
430
- <div *ngIf="optionsLoading" class="mini-spinner me-2" role="progressbar" aria-label="Loading options"></div>
431
- <mat-icon class="single-select-arrow">
432
- {{ isSingleDropdownOpen ? expandLessLabel : expandMoreLabel }}
433
- </mat-icon>
434
- </div>
435
- </div>
436
-
437
- <ng-template cdkConnectedOverlay
438
- [cdkConnectedOverlayOrigin]="singleOrigin"
439
- [cdkConnectedOverlayOpen]="isSingleDropdownOpen"
440
- [cdkConnectedOverlayPositions]="dropdownOverlayPositions"
441
- [cdkConnectedOverlayWidth]="singleTrigger.offsetWidth"
442
- [cdkConnectedOverlayViewportMargin]="8"
443
- [cdkConnectedOverlayPush]="true"
444
- (overlayOutsideClick)="onDropdownOverlayOutsideClick($event, singleTrigger)"
445
- (detach)="isSingleDropdownOpen = false">
446
- <div class="single-select-panel"
447
- (click)="$event.stopPropagation()">
448
-
449
- <!-- Search input -->
450
- <div class="dropdown-search-wrapper">
451
- <mat-icon class="dropdown-search-icon">search</mat-icon>
452
- <input type="text" class="dropdown-search-input"
453
- [placeholder]="searchPlaceholderLabel"
454
- [(ngModel)]="searchText" [ngModelOptions]="{standalone: true}"
455
- (input)="dropdownSearch$.next(searchText)"
456
- (click)="$event.stopPropagation()">
457
- <mat-icon *ngIf="searchText" class="dropdown-search-clear cursor-pointer"
458
- (click)="searchText = ''; dropdownSearch$.next(''); $event.stopPropagation()">close</mat-icon>
459
- </div>
460
-
461
- <!-- Options list -->
462
- <div class="single-select-options-list">
463
- <div *ngIf="optionsLoading" class="dropdown-loading-state">
464
- <div class="dropdown-spinner"></div>
465
- <span class="fs-0-875rem c-6B7280">{{ controller.labels['LBL_LOADING'] || 'Loading...' }}</span>
466
- </div>
467
-
468
- <ng-container *ngIf="!optionsLoading">
469
- <div *ngFor="let option of displayOptionList"
470
- class="single-select-option d-flex align-items-center gap-8 cursor-pointer"
471
- [class.selected]="formGroup.get(config.name!)?.value === option.code"
472
- (click)="selectSingleOption(option)">
473
- <span class="fs-0-875rem c-111827">{{ option.label }}</span>
474
- <mat-icon *ngIf="formGroup.get(config.name!)?.value === option.code" class="single-select-check">check</mat-icon>
475
- </div>
476
- <div *ngIf="!filteredOptionList?.length && localOptionList?.length"
477
- class="multi-select-empty fs-0-875rem c-6B7280">
478
- {{ noMatchingOptionsLabel }}
479
- </div>
480
- <div *ngIf="!localOptionList?.length"
481
- class="multi-select-empty fs-0-875rem c-6B7280">
482
- {{ noOptionsAvailableLabel }}
483
- </div>
484
- </ng-container>
485
- </div>
486
- </div>
487
- </ng-template>
488
- </div>
489
-
490
- <!-- MULTIPLE SELECT: custom panel with checkboxes -->
491
- <div *ngIf="config.subType === 'MULTIPLE'" class="multi-select-wrapper"
492
- [class.is-invalid]="errorMessage">
493
-
494
- <div #multiTrigger cdkOverlayOrigin #multiOrigin="cdkOverlayOrigin"
495
- class="field-input multi-select-trigger d-flex align-items-center justify-content-between cursor-pointer"
496
- [class.ms-open]="isMultiDropdownOpen"
497
- (click)="!optionsLoading && toggleMultiDropdown($event)">
498
- <span *ngIf="multiSelectedCount > 0 && !optionsLoading" class="multi-select-value fs-0-9rem">
499
- {{ multiSelectedCount }} selected
500
- </span>
501
- <span *ngIf="multiSelectedCount === 0 || optionsLoading" class="multi-select-placeholder">
502
- {{ optionsLoading ? (controller.labels['LBL_LOADING'] || 'Loading...') : (config.placeholder || selectPlaceholderLabel) }}
503
- </span>
504
- <div class="d-flex align-items-center">
505
- <div *ngIf="optionsLoading" class="mini-spinner me-2" role="progressbar" aria-label="Loading options"></div>
506
- <mat-icon class="multi-select-arrow">
507
- {{ isMultiDropdownOpen ? expandLessLabel : expandMoreLabel }}
508
- </mat-icon>
509
- </div>
510
- </div>
511
-
512
- <ng-template cdkConnectedOverlay
513
- [cdkConnectedOverlayOrigin]="multiOrigin"
514
- [cdkConnectedOverlayOpen]="isMultiDropdownOpen"
515
- [cdkConnectedOverlayPositions]="dropdownOverlayPositions"
516
- [cdkConnectedOverlayWidth]="multiTrigger.offsetWidth"
517
- [cdkConnectedOverlayViewportMargin]="8"
518
- [cdkConnectedOverlayPush]="true"
519
- (overlayOutsideClick)="onDropdownOverlayOutsideClick($event, multiTrigger)"
520
- (detach)="isMultiDropdownOpen = false">
521
- <div class="multi-select-panel"
522
- (click)="$event.stopPropagation()">
523
-
524
- <!-- Search input (shown only when searchConfig.enabled) -->
525
- <div class="dropdown-search-wrapper" *ngIf="isSearchableDropdown">
526
- <mat-icon class="dropdown-search-icon">search</mat-icon>
527
- <input type="text" class="dropdown-search-input"
528
- [placeholder]="searchPlaceholderLabel"
529
- [(ngModel)]="searchText" [ngModelOptions]="{standalone: true}"
530
- (input)="dropdownSearch$.next(searchText)"
531
- (click)="$event.stopPropagation()">
532
- <mat-icon *ngIf="searchText" class="dropdown-search-clear cursor-pointer"
533
- (click)="searchText = ''; dropdownSearch$.next(''); $event.stopPropagation()">close</mat-icon>
534
- </div>
535
-
536
- <!-- Select All checkbox (shown only when showSelectAll is true) -->
537
- <label *ngIf="config.optionConfig?.showSelectAll && filteredOptionList?.length"
538
- class="multi-select-option multi-select-all d-flex align-items-center gap-8 cursor-pointer">
539
- <input type="checkbox"
540
- [checked]="isAllSelected()"
541
- [indeterminate]="isSomeSelected()"
542
- [disabled]="!!config.disabled"
543
- (change)="toggleSelectAll($any($event.target).checked)">
544
- <span class="fs-0-875rem c-111827 fw-600">{{ selectAllLabel }}</span>
545
- </label>
546
-
547
- <!-- Options list -->
548
- <div class="multi-select-options-list">
549
- <div *ngIf="optionsLoading" class="dropdown-loading-state">
550
- <div class="dropdown-spinner"></div>
551
- <span class="fs-0-875rem c-6B7280">{{ controller.labels['LBL_LOADING'] || 'Loading...' }}</span>
552
- </div>
553
-
554
- <ng-container *ngIf="!optionsLoading">
555
- <label *ngFor="let option of filteredOptionList"
556
- class="multi-select-option d-flex align-items-center gap-8 cursor-pointer">
557
- <input type="checkbox"
558
- [checked]="isChecked(option.code)"
559
- [disabled]="!!config.disabled"
560
- (change)="onCheckboxListChange(option.code, $any($event.target).checked)">
561
- <span class="fs-0-875rem c-111827">{{ option.label }}</span>
562
- </label>
563
- <div *ngIf="!filteredOptionList?.length && localOptionList?.length"
564
- class="multi-select-empty fs-0-875rem c-6B7280">
565
- {{ noMatchingOptionsLabel }}
566
- </div>
567
- <div *ngIf="!localOptionList?.length"
568
- class="multi-select-empty fs-0-875rem c-6B7280">
569
- {{ noOptionsAvailableLabel }}
570
- </div>
571
- </ng-container>
572
- </div>
573
- </div>
574
- </ng-template>
575
- </div>
576
-
577
- <span class="field-hint c-6B7280 fs-0-75rem" *ngIf="config.hint && !errorMessage">{{ config.hint }}</span>
578
- <span class="field-error color-error fs-0-75rem" *ngIf="errorMessage">{{ errorMessage }}</span>
579
- </div>
580
-
581
- <!-- ══ Radio ═════════════════════════════════════════════════════════════ -->
582
- <div *ngIf="isRadio" class="field-wrapper d-flex flex-column gap-6" [formGroup]="formGroup">
583
- <label *ngIf="config.label" class="field-label d-block font-poppins c-202124 fs-18px">
584
- {{ config.label }}
585
- <span class="required c-DC2626 ml-0-125rem" *ngIf="config.required">*</span>
586
- </label>
587
-
588
- <div class="radio-group" [class.is-invalid]="errorMessage"
589
- [class]="config.optionConfig?.layout ? 'layout-' + config.optionConfig!.layout.toLowerCase() : ''">
590
- <label *ngFor="let option of localOptionList" class="radio-label"
591
- [class.card-item]="config.subType === 'CARD'"
592
- [class.selected]="formGroup.get(config.name!)?.value === option.code"
593
- [style.gridColumn]="config.optionConfig?.layout?.toUpperCase() === 'COLUMN' ? 'span ' + getOptionColSpan(option) : null">
594
- <input type="radio" [formControlName]="config.name!" [value]="option.code">
595
- <div class="option-content d-flex flex-column gap-4 flex-1 text-left">
596
- <span class="label-text text-16 c-1F2937">{{ option.label }}</span>
597
- <span class="option-hint text-13 color-gray" *ngIf="option.hint">{{ option.hint }}</span>
598
- </div>
599
- </label>
600
- </div>
601
-
602
- <span class="field-hint c-6B7280 fs-0-75rem" *ngIf="config.hint && !errorMessage">{{ config.hint }}</span>
603
- <span class="field-error color-error fs-0-75rem" *ngIf="errorMessage">{{ errorMessage }}</span>
604
- </div>
605
-
606
- <!-- ══ Checkbox ══════════════════════════════════════════════════════════ -->
607
- <div *ngIf="isCheckbox" class="field-wrapper d-flex flex-column gap-6" [formGroup]="formGroup">
608
- <label *ngIf="config.label && config.subType === 'LIST'"
609
- class="field-label d-block font-poppins c-202124 fs-18px">
610
- {{ config.label }}
611
- <span class="required c-DC2626 ml-0-125rem" *ngIf="config.required">*</span>
612
- </label>
613
-
614
- <div *ngIf="config.subType === 'BOOL'" class="checkbox-single">
615
- <label class="checkbox-label d-flex align-items-center gap-8 cursor-pointer fs-0-875rem c-111827">
616
- <input type="checkbox" [formControlName]="config.name!" [class.is-invalid]="errorMessage">
617
- <span>{{ config.label }}</span>
618
- </label>
619
- </div>
620
-
621
- <div *ngIf="config.subType === 'LIST' || config.subType === 'CARD'" class="checkbox-group d-flex flex-column gap-8"
622
- [class.is-invalid]="errorMessage"
623
- [class]="config.optionConfig?.layout ? 'layout-' + config.optionConfig!.layout.toLowerCase() : ''">
624
- <label *ngFor="let option of localOptionList"
625
- class="checkbox-label d-flex align-items-center gap-8 cursor-pointer fs-0-875rem c-111827"
626
- [class.card-item]="config.subType === 'CARD'" [class.selected]="isChecked(option.code)"
627
- [style.gridColumn]="config.optionConfig?.layout?.toUpperCase() === 'COLUMN' ? 'span ' + getOptionColSpan(option) : null">
628
- <input type="checkbox" [checked]="isChecked(option.code)" [disabled]="!!config.disabled"
629
- (change)="onCheckboxListChange(option.code, $any($event.target).checked)">
630
- <div class="option-content d-flex flex-column gap-4 flex-1 text-left">
631
- <span class="label-text text-16 c-1F2937">{{ option.label }}</span>
632
- <span class="option-hint text-13 color-gray" *ngIf="option.hint">{{ option.hint }}</span>
633
- </div>
634
- </label>
635
- </div>
636
-
637
- <span class="field-hint c-6B7280 fs-0-75rem" *ngIf="config.hint && !errorMessage">{{ config.hint }}</span>
638
- <span class="field-error color-error fs-0-75rem" *ngIf="errorMessage">{{ errorMessage }}</span>
639
- </div>
640
-
641
- <!-- ══ Chip ══════════════════════════════════════════════════════════════ -->
642
- <div *ngIf="isChip" class="field-wrapper d-flex flex-column gap-6" [formGroup]="formGroup">
643
- <label *ngIf="config.label" class="field-label d-block font-poppins c-202124 fs-18px">
644
- {{ config.label }}
645
- <span class="required c-DC2626 ml-0-125rem" *ngIf="config.required">*</span>
646
- </label>
647
-
648
- <div class="chip-group d-flex flex-wrap gap-8" [class.is-invalid]="errorMessage">
649
- <label *ngFor="let option of localOptionList"
650
- class="chip-label cursor-pointer p-6px-14px b-ffffff c-374151 b-1px-solid-D1D5DB br-20px fs-0-875rem"
651
- [class.selected]="isChecked(option.code)">
652
- <input type="checkbox" [checked]="isChecked(option.code)" [disabled]="!!config.disabled"
653
- (change)="onCheckboxListChange(option.code, $any($event.target).checked)" hidden>
654
- <span>{{ option.label }}</span>
655
- </label>
656
- </div>
657
-
658
- <span class="field-hint c-6B7280 fs-0-75rem" *ngIf="config.hint && !errorMessage">{{ config.hint }}</span>
659
- <span class="field-error color-error fs-0-75rem" *ngIf="errorMessage">{{ errorMessage }}</span>
660
- </div>
661
-
662
- <!-- ══ Switch ════════════════════════════════════════════════════════════ -->
663
- <div *ngIf="isSwitch" class="field-wrapper d-flex flex-column gap-6" [formGroup]="formGroup">
664
- <label class="switch-container d-flex justify-content-between align-items-center cursor-pointer">
665
- <span class="field-label d-block font-poppins c-202124 fs-18px">{{ config.label }}</span>
666
- <div class="switch position-relative">
667
- <input type="checkbox" [formControlName]="config.name!" [class.is-invalid]="errorMessage">
668
- <span class="slider position-absolute cursor-pointer"></span>
669
- </div>
670
- </label>
671
-
672
- <span class="field-hint c-6B7280 fs-0-75rem" *ngIf="config.hint && !errorMessage">{{ config.hint }}</span>
673
- <span class="field-error color-error fs-0-75rem" *ngIf="errorMessage">{{ errorMessage }}</span>
674
- </div>
675
-
676
- <!-- ══ Rich Text ═════════════════════════════════════════════════════════ -->
677
- <div *ngIf="isRichText" class="field-wrapper component-rich-text d-flex flex-column gap-6" [formGroup]="formGroup">
678
- <label *ngIf="config.label" class="field-label d-block font-poppins c-202124 fs-18px">
679
- {{ config.label }}
680
- <span class="required c-DC2626 ml-0-125rem" *ngIf="config.required">*</span>
681
- </label>
682
-
683
- <div class="rich-text-container" [class.is-invalid]="errorMessage">
684
- <quill-editor [formControlName]="config.name!" class="rich-text-editor d-block w-100"
685
- [placeholder]="config.richTextConfig?.placeholder || config.placeholder || ''"
686
- [styles]="{height: config.richTextConfig?.height || '200px'}"
687
- [modules]="richTextModules">
688
- </quill-editor>
689
- </div>
690
-
691
- <span class="field-hint c-6B7280 fs-0-75rem" *ngIf="config.hint && !errorMessage">{{ config.hint }}</span>
692
- <span class="field-error color-error fs-0-75rem" *ngIf="errorMessage">{{ errorMessage }}</span>
693
- <div class="char-count-hint font-poppins font-weight-400 text-14 text-right c-6B7280" *ngIf="showCharCount">
694
- {{ remainingCharacters }} characters remaining
695
- </div>
696
- </div>
697
-
698
- <!-- ══ Rating ════════════════════════════════════════════════════════════ -->
699
- <div *ngIf="isRating" class="field-wrapper d-flex flex-column gap-6" [formGroup]="formGroup">
700
- <label *ngIf="config.label" class="field-label d-block font-poppins c-202124 fs-18px">
701
- {{ config.label }}
702
- <span class="required c-DC2626 ml-0-125rem" *ngIf="config.required">*</span>
703
- </label>
704
-
705
- <div class="rating-group d-flex gap-4" [class.is-invalid]="errorMessage">
706
- <span *ngFor="let star of getStarArray()" class="star d-inline-flex align-items-center cursor-pointer"
707
- [class.filled]="isStarFilled(star)" [class.half]="isStarHalf(star)" (click)="onRatingChange(star, $event)">
708
- <mat-icon>{{ isStarFilled(star) || isStarHalf(star) ? 'star' : 'star_border' }}</mat-icon>
709
- </span>
710
- </div>
711
-
712
- <span class="field-hint c-6B7280 fs-0-75rem" *ngIf="config.hint && !errorMessage">{{ config.hint }}</span>
713
- <span class="field-error color-error fs-0-75rem" *ngIf="errorMessage">{{ errorMessage }}</span>
714
- </div>
715
-
716
- <!-- ══ Generated Field (read-only) ══════════════════════════════════════ -->
717
- <div *ngIf="isGenerated" class="field-wrapper d-flex flex-column gap-6">
718
- <label *ngIf="config.label" class="field-label d-block font-poppins c-202124 fs-18px">{{ config.label
719
- }}</label>
720
- <div class="generated-value fs-0-875rem p-0-625rem-0-875rem b-F3F4F6 b-1px-solid-E5E7EB br-8px c-6B7280">{{ value ||
721
- '-' }}</div>
722
- <span class="field-hint c-6B7280 fs-0-75rem" *ngIf="config.hint">{{ config.hint }}</span>
723
- </div>
724
-
725
- <!-- ══ File Upload ═══════════════════════════════════════════════════════ -->
726
- <div *ngIf="isFileUpload" class="field-wrapper d-flex flex-column gap-6" [formGroup]="formGroup">
727
- <label *ngIf="config.label" class="field-label d-block font-poppins c-202124 fs-18px">
728
- {{ config.label }}
729
- <span class="required c-DC2626 ml-0-125rem" *ngIf="config.required">*</span>
730
- </label>
731
-
732
- <!-- Drop Zone -->
733
- <div
734
- class="upload-drop-zone d-flex flex-column align-items-center justify-content-center gap-8 cursor-pointer text-center p-32px-24px us-none"
735
- [class.drag-over]="isDragOver" [class.has-files]="value?.length" [class.is-invalid]="errorMessage"
736
- (dragover)="onDragOver($event)" (dragleave)="onDragLeave($event)" (drop)="onFileDrop($event)"
737
- (click)="fileInput.click()">
738
-
739
- <!-- Icon with accent-colour pill background -->
740
- <div class="upload-icon-wrap mb-4">
741
- <div class="dropzone-icon-pill d-flex align-items-center justify-content-center">
742
- <mat-icon class="upload-cloud-icon">cloud_upload</mat-icon>
743
- </div>
744
- </div>
745
-
746
- <p class="upload-main-text font-weight-600 m-0 fs-0-9rem">Drag &amp; drop files here</p>
747
- <p class="upload-sub-text m-0 fs-0-8rem c-64748B">or <span class="upload-link">Browse files</span></p>
748
- <p class="upload-hint-text m-0 fs-0-78rem c-64748B" *ngIf="config.attachmentConfig?.acceptLabel">
749
- Supported: <span class="upload-formats font-weight-500">{{ config.attachmentConfig!.acceptLabel }}</span>
750
- </p>
751
- <p class="upload-hint-text m-0 fs-0-78rem c-64748B" *ngIf="!config.attachmentConfig?.acceptLabel && config.hint">
752
- {{ config.hint }}
753
- </p>
754
- <span class="upload-size-badge fs-0-72rem" *ngIf="config.attachmentConfig?.maxSizeMB">
755
- Max {{ config.attachmentConfig!.maxSizeMB }}MB
756
- </span>
757
-
758
- <!-- Hidden native file input -->
759
- <input #fileInput type="file" hidden [attr.multiple]="config.attachmentConfig?.multiple ? true : null"
760
- [attr.accept]="config.attachmentConfig?.accept || null" (change)="onFileSelected($event)">
761
- </div>
762
-
763
- <!-- Uploaded file list -->
764
- <div class="uploaded-list d-flex flex-column gap-8 mt-10" *ngIf="value?.length">
765
- <div *ngFor="let f of value; let i = index"
766
- class="uploaded-item d-flex align-items-center gap-10 p-10px-14px br-8px"
767
- [class.uploading]="f.isUploading">
768
-
769
- <!-- Uploading spinner -->
770
- <ng-container *ngIf="f.isUploading; else fileReady">
771
- <div class="upload-spinner rounded-50 b-2px-solid-E2E8F0"></div>
772
- <div class="file-info flex-1 d-flex flex-column">
773
- <span class="file-name font-weight-500 overflow-hidden fs-0-85rem" [title]="f.name">{{ f.name }}</span>
774
- <span class="file-size uploading-label fs-0-72rem">Uploading...</span>
775
- </div>
776
- </ng-container>
777
-
778
- <!-- Normal state once upload is done -->
779
- <ng-template #fileReady>
780
- <mat-icon class="file-type-icon">{{ getFileIcon(f.type) }}</mat-icon>
781
- <img *ngIf="f.type?.startsWith('image') && f.dataUrl" [src]="f.dataUrl" class="file-thumb rounded-4"
782
- alt="preview">
783
- <div class="file-info flex-1 d-flex flex-column">
784
- <span class="file-name font-weight-500 overflow-hidden fs-0-85rem" [title]="f.name">{{ f.name }}</span>
785
- <span class="file-size fs-0-72rem">{{ formatFileSize(f.size) }}</span>
786
- </div>
787
- </ng-template>
788
-
789
- <!-- Compact icon-only remove button -->
790
- <button type="button" class="file-remove-btn d-flex align-items-center justify-content-center rounded-50"
791
- [disabled]="f.isUploading" (click)="!f.isUploading && removeUploadedFile(i)"
792
- aria-label="Remove file">
793
- <mat-icon>close</mat-icon>
794
- </button>
795
- </div>
796
- </div>
797
-
798
- <!-- Validation / file errors -->
799
- <span class="field-error color-error fs-0-75rem" *ngIf="fileUploadError">{{ fileUploadError }}</span>
800
- <span class="field-error color-error fs-0-75rem" *ngIf="errorMessage && !fileUploadError">{{ errorMessage }}</span>
801
- <span class="field-hint c-6B7280 fs-0-75rem"
802
- *ngIf="config.hint && !errorMessage && !fileUploadError && !config.attachmentConfig?.acceptLabel">
803
- {{ config.hint }}
804
- </span>
805
- </div>
806
-
807
- <!-- ══ Media Upload (Type 2) ════════════════════════════════════════════ -->
808
- <div *ngIf="isMediaUpload" class="field-wrapper media-upload-wrapper d-flex flex-column gap-6 p-0 border-none b-none"
809
- [formGroup]="formGroup">
810
-
811
- <!-- Hidden file input lives outside *ngIf — triggered via ViewChild -->
812
- <input #mediaDeviceInput type="file" hidden multiple accept="image/*" (change)="onMediaFileSelected($event)">
813
-
814
- <!-- Two-column layout -->
815
- <div class="mu-layout d-grid align-items-start">
816
-
817
- <!-- ── LEFT PANEL ──────────────────────────────────────────────── -->
818
- <div class="mu-left d-flex flex-column gap-20">
819
-
820
- <!-- Header: title + max-items badge -->
821
- <div class="mu-header d-flex align-items-start flex-wrap gap-10">
822
- <h3 class="mu-title m-0 c-111827 fs-1-35rem">
823
- {{ config.label }}
824
- <span class="required c-DC2626 ml-0-125rem" *ngIf="config.required">*</span>
825
- </h3>
826
- <span
827
- class="mu-badge d-inline-flex align-items-center rounded-20 color-white font-weight-600 fs-0-72rem p-4px-12px b-111827"
828
- *ngIf="config.attachmentConfig?.maxFiles">
829
- {{ controller.labels['LBL_MEDIA_MAX_PREFIX'] || 'Maximum' }}
830
- {{ config.attachmentConfig?.maxFiles }}
831
- {{ controller.labels['LBL_MEDIA_MAX_SUFFIX'] || 'Image & Videos' }}
832
- </span>
833
- </div>
834
-
835
- <!-- Description -->
836
- <p class="mu-description m-0 fs-0-875rem c-6B7280" *ngIf="config.attachmentConfig?.description">
837
- {{ config.attachmentConfig?.description }}
838
- </p>
839
- <p class="mu-description m-0 fs-0-875rem c-6B7280"
840
- *ngIf="!config.attachmentConfig?.description && controller.labels['LBL_MEDIA_DESC']">
841
- {{ controller.labels['LBL_MEDIA_DESC'] }}
842
- </p>
843
-
844
- <!-- Feature bullet list -->
845
- <ul class="mu-features m-0 p-0 d-flex flex-column gap-8 ls-none"
846
- *ngIf="config.attachmentConfig?.features?.length || controller.labels['LBL_MEDIA_FEATURE_1']">
847
- <ng-container *ngIf="config.attachmentConfig?.features?.length">
848
- <li *ngFor="let f of config.attachmentConfig?.features"
849
- class="mu-feature-item d-flex align-items-center gap-8 fs-0-875rem c-374151">
850
- <mat-icon class="mu-check text-16 c-3B82F6">check</mat-icon>{{ f }}
851
- </li>
852
- </ng-container>
853
- <ng-container *ngIf="!config.attachmentConfig?.features?.length">
854
- <li *ngIf="controller.labels['LBL_MEDIA_FEATURE_1']"
855
- class="mu-feature-item d-flex align-items-center gap-8 fs-0-875rem c-374151">
856
- <mat-icon class="mu-check text-16 c-3B82F6">check</mat-icon>{{ controller.labels['LBL_MEDIA_FEATURE_1'] }}
857
- </li>
858
- <li *ngIf="controller.labels['LBL_MEDIA_FEATURE_2']"
859
- class="mu-feature-item d-flex align-items-center gap-8 fs-0-875rem c-374151">
860
- <mat-icon class="mu-check text-16 c-3B82F6">check</mat-icon>{{ controller.labels['LBL_MEDIA_FEATURE_2'] }}
861
- </li>
862
- <li *ngIf="controller.labels['LBL_MEDIA_FEATURE_3']"
863
- class="mu-feature-item d-flex align-items-center gap-8 fs-0-875rem c-374151">
864
- <mat-icon class="mu-check text-16 c-3B82F6">check</mat-icon>{{ controller.labels['LBL_MEDIA_FEATURE_3'] }}
865
- </li>
866
- </ng-container>
867
- </ul>
868
-
869
- <!-- Backdrop to close dropdown on outside click -->
870
- <div class="media-menu-backdrop" *ngIf="showMediaMenu"
871
- (click)="$event.stopPropagation(); showMediaMenu = false"></div>
872
-
873
- <!-- Add Media button + dropdown -->
874
- <div class="media-add-container position-relative" (click)="showMediaMenu = !showMediaMenu">
875
- <lib-button id="btn-add-media-{{ config.name }}" [variant]="'warning'"
876
- [icon]="{type: 'material', value: 'add_photo_alternate'}">
877
- {{ controller.labels['LBL_ADD_MEDIA'] || 'Add media' }}
878
- <mat-icon class="menu-chevron fs-18px">add</mat-icon>
879
- </lib-button>
880
-
881
- <div class="media-dropdown position-absolute rounded-12 overflow-hidden b-ffffff b-1px-solid-E5E7EB"
882
- *ngIf="showMediaMenu" role="menu" (click)="$event.stopPropagation()">
883
- <!-- Video -->
884
- <button id="btn-media-video-{{ config.name }}" type="button"
885
- class="media-dropdown-item d-flex align-items-center gap-12 w-100 cursor-pointer text-left b-none border-none p-12px-16px bb-1px-solid-F3F4F6"
886
- (click)="onMediaMenuVideo(); showMediaMenu = false" role="menuitem">
887
- <span
888
- class="media-drop-icon media-drop-icon--video d-flex align-items-center justify-content-center rounded-8"><mat-icon>videocam</mat-icon></span>
889
- <span class="media-drop-text d-flex flex-column flex-1">
890
- <span class="media-drop-label font-weight-600 fs-0-875rem c-111827">{{
891
- controller.labels['LBL_MEDIA_VIDEO'] || 'Video' }}</span>
892
- <span class="media-drop-desc c-6B7280 fs-0-75rem">{{ controller.labels['LBL_MEDIA_VIDEO_DESC'] || 'Add
893
- YouTube URL'
894
- }}</span>
895
- </span>
896
- </button>
897
- <!-- Device -->
898
- <button id="btn-media-device-{{ config.name }}" type="button"
899
- class="media-dropdown-item d-flex align-items-center gap-12 w-100 cursor-pointer text-left b-none border-none p-12px-16px bb-1px-solid-F3F4F6"
900
- (click)="onMediaMenuDevice(); showMediaMenu = false" role="menuitem">
901
- <span
902
- class="media-drop-icon media-drop-icon--device d-flex align-items-center justify-content-center rounded-8"><mat-icon>upload</mat-icon></span>
903
- <span class="media-drop-text d-flex flex-column flex-1">
904
- <span class="media-drop-label font-weight-600 fs-0-875rem c-111827">{{
905
- controller.labels['LBL_MEDIA_DEVICE'] || 'Upload from device'
906
- }}</span>
907
- <span class="media-drop-desc c-6B7280 fs-0-75rem">{{ controller.labels['LBL_MEDIA_DEVICE_DESC'] ||
908
- 'Select images from your
909
- computer' }}</span>
910
- </span>
911
- </button>
912
- <!-- Library -->
913
- <button id="btn-media-library-{{ config.name }}" type="button"
914
- class="media-dropdown-item d-flex align-items-center gap-12 w-100 cursor-pointer text-left b-none border-none p-12px-16px bb-1px-solid-F3F4F6"
915
- (click)="onMediaMenuLibrary(); showMediaMenu = false" role="menuitem">
916
- <span
917
- class="media-drop-icon media-drop-icon--library d-flex align-items-center justify-content-center rounded-8"><mat-icon>photo_library</mat-icon></span>
918
- <span class="media-drop-text d-flex flex-column flex-1">
919
- <span class="media-drop-label font-weight-600 fs-0-875rem c-111827">{{
920
- controller.labels['LBL_MEDIA_LIBRARY'] || 'Upload from library'
921
- }}</span>
922
- <span class="media-drop-desc c-6B7280 fs-0-75rem">{{ controller.labels['LBL_MEDIA_LIBRARY_DESC'] ||
923
- 'Choose from default
924
- images' }}</span>
925
- </span>
926
- </button>
927
- </div>
928
- </div>
929
-
930
- <!-- YouTube URL Input (inline below button) -->
931
- <div class="youtube-input-panel d-flex flex-column gap-8 p-16 rounded-10 b-FFFAF1 b-1px-solid-E5E7EB"
932
- *ngIf="showYoutubeInput">
933
- <label class="youtube-panel-label d-flex align-items-center gap-6 font-weight-600 fs-0-875rem c-111827">
934
- {{ controller.labels['LBL_YOUTUBE_URL'] || 'Video URL' }}
935
- </label>
936
- <div class="youtube-input-row d-flex gap-8">
937
- <input id="input-youtube-url-{{ config.name }}" type="url" class="field-input youtube-url-input"
938
- [(ngModel)]="youtubeUrlInput" [ngModelOptions]="{standalone: true}"
939
- [placeholder]="controller.labels['PH_YOUTUBE_URL'] || 'Video URL'" (keyup.enter)="addYoutubeMedia()">
940
- <lib-button id="btn-add-youtube-{{ config.name }}" [variant]="'secondary'" (click)="addYoutubeMedia()">
941
- {{ controller.labels['LBL_ADD'] || 'Add' }}
942
- </lib-button>
943
- </div>
944
- <span class="field-error color-error fs-0-75rem" *ngIf="youtubeUrlError">{{ youtubeUrlError }}</span>
945
- </div>
946
-
947
- <div
948
- class="media-upload-status d-flex align-items-center gap-8 mt-4 color-error rounded-8 font-weight-500 p-10px-14px b-FEF2F2 fs-0-85rem"
949
- *ngIf="mediaUploadError">
950
- <mat-icon class="status-icon fs-18px">error_outline</mat-icon>
951
- <span>{{ mediaUploadError }}</span>
952
- </div>
953
-
954
- <span class="field-error color-error fs-0-75rem" *ngIf="errorMessage">{{ errorMessage }}</span>
955
- <span class="field-hint c-6B7280 fs-0-75rem" *ngIf="config.hint && !errorMessage">{{ config.hint }}</span>
956
- </div>
957
- <!-- end left panel -->
958
-
959
- <!-- ── RIGHT PANEL (carousel) ──────────────────────────────────── -->
960
- <div class="mu-right d-flex flex-column gap-12">
961
-
962
- <!-- Carousel (when items exist) -->
963
- <div class="media-carousel-section d-flex flex-column gap-12" *ngIf="mediaItems.length">
964
- <div
965
- class="media-carousel-main position-relative w-100 overflow-hidden d-flex align-items-center justify-content-center br-12px b-0F172A">
966
- <button id="btn-carousel-prev-{{ config.name }}" type="button"
967
- class="carousel-nav carousel-nav--prev position-absolute rounded-50 cursor-pointer d-flex align-items-center justify-content-center border-none b-rgba-255-255-255-0-85"
968
- (click)="mediaCarouselPrev()" [disabled]="mediaCarouselIndex === 0" aria-label="Previous">
969
- <mat-icon>chevron_left</mat-icon>
970
- </button>
971
-
972
- <div class="carousel-viewer position-absolute d-flex align-items-center justify-content-center"
973
- *ngFor="let item of mediaItems; let i = index" [hidden]="i !== mediaCarouselIndex">
974
- <div *ngIf="item.isUploading"
975
- class="carousel-uploading d-flex flex-column align-items-center gap-12 c-94A3B8 fs-0-85rem">
976
- <div class="carousel-spinner rounded-50 b-3px-solid-rgba-255-255-255-0-15"></div>
977
- <span>{{ controller.labels['LBL_UPLOADING'] || 'Uploading…' }}</span>
978
- </div>
979
- <ng-container *ngIf="!item.isUploading && item.mediaType === 'youtube'">
980
- <iframe class="carousel-iframe w-100 h-100 br-12px" [src]="item.url | trustedUrl" frameborder="0"
981
- allowfullscreen
982
- allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture">
983
- </iframe>
984
- </ng-container>
985
- <ng-container *ngIf="!item.isUploading && item.mediaType === 'image'">
986
- <img class="carousel-image w-100 h-100 br-12px" [src]="item.url" alt="Media">
987
- </ng-container>
988
- <button id="btn-remove-media-{{ config.name }}-{{ i }}" type="button"
989
- class="carousel-remove-btn position-absolute rounded-50 cursor-pointer d-flex align-items-center justify-content-center border-none b-rgba-0-0-0-0-55"
990
- [disabled]="item.isUploading" (click)="removeMediaItem(i)" aria-label="Remove">
991
- <mat-icon>close</mat-icon>
992
- </button>
993
- </div>
994
-
995
- <button id="btn-carousel-next-{{ config.name }}" type="button"
996
- class="carousel-nav carousel-nav--next position-absolute rounded-50 cursor-pointer d-flex align-items-center justify-content-center border-none b-rgba-255-255-255-0-85"
997
- (click)="mediaCarouselNext()" [disabled]="mediaCarouselIndex === mediaItems.length - 1" aria-label="Next">
998
- <mat-icon>chevron_right</mat-icon>
999
- </button>
1000
-
1001
- <div class="carousel-dots position-absolute d-flex gap-6">
1002
- <span *ngFor="let item of mediaItems; let i = index"
1003
- class="carousel-dot rounded-50 cursor-pointer b-rgba-255-255-255-0-45"
1004
- [class.active]="i === mediaCarouselIndex" (click)="mediaGoTo(i)"></span>
1005
- </div>
1006
- </div>
1007
-
1008
- <!-- Thumbnail strip -->
1009
- <div class="media-thumbnail-strip d-flex flex-wrap gap-8 pb-4px">
1010
- <div *ngFor="let item of mediaThumbnails; let i = index"
1011
- class="media-thumb rounded-8 overflow-hidden cursor-pointer d-flex align-items-center justify-content-center b-2px-solid-transparent b-E2E8F0"
1012
- [class.active]="i === mediaCarouselIndex" (click)="mediaGoTo(i)">
1013
- <div *ngIf="item.isUploading"
1014
- class="thumb-uploading d-flex align-items-center justify-content-center w-100 h-100">
1015
- <div class="thumb-spinner rounded-50 b-2px-solid-E2E8F0"></div>
1016
- </div>
1017
- <img *ngIf="!item.isUploading && item.mediaType === 'youtube' && item.thumbnailUrl"
1018
- [src]="item.thumbnailUrl" class="thumb-img w-100 h-100" alt="Video thumbnail">
1019
- <div *ngIf="!item.isUploading && item.mediaType === 'youtube' && !item.thumbnailUrl"
1020
- class="thumb-yt-placeholder d-flex align-items-center justify-content-center w-100 h-100 b-1E293B c-EF4444">
1021
- <mat-icon>play_circle</mat-icon>
1022
- </div>
1023
- <img *ngIf="!item.isUploading && item.mediaType === 'image' && item.url" [src]="item.url"
1024
- class="thumb-img w-100 h-100" alt="Image thumbnail">
1025
- </div>
1026
- </div>
1027
- </div>
1028
-
1029
- <!-- Empty right-side placeholder -->
1030
- <div
1031
- class="mu-right-empty d-flex flex-column align-items-center justify-content-center gap-10 h-100 text-center p-24 br-12px b-FFFAF1 c-94A3B8 b-2px-dashed-CBD5E1"
1032
- *ngIf="!mediaItems.length" (click)="onMediaMenuDevice()">
1033
- <mat-icon class="mu-right-empty-icon fs-52px">perm_media</mat-icon>
1034
- <p>{{ controller.labels['LBL_ADD_MEDIA'] || 'Add media' }}</p>
1035
- </div>
1036
-
1037
- </div>
1038
- <!-- end right panel -->
1039
-
1040
- </div><!-- end mu-layout -->
1041
- </div>
1042
-
1043
-
1044
- <!-- ══ Library Image Picker Modal ════════════════════════════════════════ -->
1045
- <!-- Wrapper is always in DOM (hidden) so @ViewChild can move it to body -->
1046
- <div #libraryModal class="media-library-portal-host" [class.is-open]="showLibraryModal">
1047
-
1048
- <!-- Backdrop -->
1049
- <div class="media-library-overlay" (click)="closeLibraryModal()"></div>
1050
-
1051
- <!-- Modal card -->
1052
- <div class="media-library-modal d-flex flex-column overflow-hidden b-ffffff br-16px"
1053
- role="dialog" aria-modal="true">
1054
- <div class="library-modal-header d-flex align-items-start justify-content-between p-24px-28px bb-1px-solid-E5E7EB">
1055
- <div class="header-left d-flex flex-column gap-8">
1056
- <h3 class="library-modal-title m-0 color-dark fs-1-25rem">
1057
- {{ controller.labels['LBL_ADD_IMAGES'] || 'Add Images' }}
1058
- </h3>
1059
- <p class="library-modal-subtitle m-0 color-gray fs-0-85rem">
1060
- {{ controller.labels['LBL_LIBRARY_MODAL_DESC'] || 'Select images from your library.' }}
1061
- </p>
1062
- </div>
1063
- <button id="btn-close-library-{{ config.name }}" type="button"
1064
- class="library-close-btn d-flex align-items-center justify-content-center cursor-pointer rounded-50 border-none b-none c-9CA3AF"
1065
- (click)="closeLibraryModal()" aria-label="Close">
1066
- <mat-icon>close</mat-icon>
1067
- </button>
1068
- </div>
1069
-
1070
- <!-- Loading -->
1071
- <div class="library-loading" *ngIf="libraryLoading">
1072
- <div class="lib-spinner rounded-50 b-3px-solid-E2E8F0"></div>
1073
- <span>{{ controller.labels['LBL_LOADING'] || 'Loading…' }}</span>
1074
- </div>
1075
-
1076
- <!-- Error -->
1077
- <div class="library-error d-flex align-items-center gap-8 color-error b-FEF2F2 fs-0-875rem p-16px-24px"
1078
- *ngIf="libraryError && !libraryLoading">
1079
- <mat-icon>error_outline</mat-icon>
1080
- {{ libraryError }}
1081
- </div>
1082
-
1083
- <!-- Image grid -->
1084
- <div class="library-grid d-grid gap-16 flex-1 p-28px b-F9FAFB" *ngIf="!libraryLoading && libraryImages.length">
1085
- <div *ngFor="let img of libraryImages; let li = index" id="lib-img-{{ config.name }}-{{ li }}"
1086
- class="library-grid-item position-relative rounded-12 overflow-hidden cursor-pointer bg-white b-3px-solid-transparent"
1087
- [class.selected]="isLibraryItemSelected(img)" (click)="toggleLibraryItem(img)">
1088
- <img [src]="getLibraryItemUrl(img)" class="library-grid-img w-100 h-100 d-block" alt="Library image">
1089
- <div
1090
- class="library-check position-absolute bg-white rounded-50 d-flex align-items-center justify-content-center c-3B82F6"
1091
- *ngIf="isLibraryItemSelected(img)">
1092
- <mat-icon>check_circle</mat-icon>
1093
- </div>
1094
- <div class="library-overlay-hover position-absolute b-rgba-59-130-246-0-12"></div>
1095
- </div>
1096
- </div>
1097
-
1098
- <!-- Empty library -->
1099
- <div
1100
- class="library-empty d-flex flex-column align-items-center justify-content-center gap-12 flex-1 c-9CA3AF fs-0-875rem p-48px-24px"
1101
- *ngIf="!libraryLoading && !libraryError && libraryImages.length === 0">
1102
- <mat-icon>image_not_supported</mat-icon>
1103
- <span>{{ controller.labels['LBL_LIBRARY_EMPTY'] || 'No images found in library.' }}</span>
1104
- </div>
1105
-
1106
- <!-- Footer -->
1107
- <div class="library-modal-footer d-flex align-items-center justify-content-end bg-white p-20px-28px bt-1px-solid-E5E7EB">
1108
- <div class="library-footer-actions d-flex gap-12">
1109
- <lib-button id="btn-library-cancel-{{ config.name }}" [variant]="'outline'" (click)="closeLibraryModal()">
1110
- {{ controller.labels['LBL_CANCEL'] || 'Cancel' }}
1111
- </lib-button>
1112
- <lib-button id="btn-library-confirm-{{ config.name }}" [variant]="'primary'"
1113
- [disabled]="librarySelectedIds.size === 0" (click)="confirmLibrarySelection()">
1114
- {{ controller.labels['LBL_CONTINUE'] || 'Continue' }}
1115
- </lib-button>
1116
- </div>
1117
- </div>
1118
- </div>
1119
- </div>
1120
-
1121
-
1122
-
1123
- <!-- ══ Location Field ═══════════════════════════════════════════════════ -->
1124
- <div *ngIf="isLocation" class="field-wrapper location-field-wrapper d-flex flex-column gap-6 gap-12"
1125
- [formGroup]="formGroup">
1126
-
1127
- <!-- Field label -->
1128
- <label *ngIf="config.label" class="field-label d-block font-poppins c-202124 fs-18px">
1129
- {{ config.label }}
1130
- <span class="required c-DC2626 ml-0-125rem" *ngIf="config.required">*</span>
1131
- </label>
1132
- <p class="location-subtitle m-0 c-6B7280 fs-0-8125rem" *ngIf="config.hint">{{ config.hint }}</p>
1133
-
1134
- <!-- Three-tab bar -->
1135
- <div class="location-tabs d-flex gap-12 mb-24">
1136
- <lib-button class="loc-tab-btn flex-1" [variant]="locationActiveTab === 'VENUE' ? 'warning' : 'outline'"
1137
- (click)="onLocationTabChange('VENUE')">
1138
- {{ controller.labels['LBL_LOC_VENUE'] || 'Venue' }}
1139
- </lib-button>
1140
- <lib-button class="loc-tab-btn flex-1" [variant]="locationActiveTab === 'ONLINE' ? 'warning' : 'outline'"
1141
- (click)="onLocationTabChange('ONLINE')">
1142
- {{ controller.labels['LBL_LOC_ONLINE'] || 'Online Event' }}
1143
- </lib-button>
1144
- <lib-button class="loc-tab-btn flex-1" [variant]="locationActiveTab === 'TBA' ? 'warning' : 'outline'"
1145
- (click)="onLocationTabChange('TBA')">
1146
- {{ controller.labels['LBL_LOC_TBA'] || 'To be Announced' }}
1147
- </lib-button>
1148
- </div>
1149
-
1150
- <!-- VENUE TAB -->
1151
- <div *ngIf="locationActiveTab === 'VENUE'" class="loc-panel loc-venue-panel d-flex flex-column gap-12">
1152
-
1153
- <p class="loc-section-label m-0 font-weight-600 c-111827 fs-0-9rem">
1154
- {{ controller.labels['LBL_LOC_ADDRESS'] || 'Location address' }}
1155
- </p>
1156
-
1157
- <!-- Added venue rows -->
1158
- <div class="loc-venue-list d-flex flex-column gap-8" *ngIf="locationVenues.length > 0">
1159
- <div *ngFor="let venue of locationVenues; let i = index"
1160
- class="loc-venue-item d-flex align-items-center gap-10 p-10px-14px br-7px b-ffffff b-1px-solid-D1D5DB">
1161
- <mat-icon class="loc-venue-search-icon fs-18px c-9CA3AF">search</mat-icon>
1162
- <span class="loc-venue-text flex-1 overflow-hidden fs-0-875rem c-111827">{{ venue.address || venue.name ||
1163
- venue.description }}</span>
1164
- <button type="button"
1165
- class="loc-action-btn loc-delete-btn d-flex align-items-center justify-content-center cursor-pointer rounded-50 b-none border-none p-4px"
1166
- (click)="removeLocationVenue(i)">
1167
- <mat-icon>delete_outline</mat-icon>
1168
- </button>
1169
- </div>
1170
- </div>
1171
-
1172
- <!-- Location count badge -->
1173
- <p class="loc-count-text m-0 font-weight-600 fs-0-8125rem c-3B82F6"
1174
- *ngIf="locationVenues.length > 0 && config.locationConfig?.allowMulti">
1175
- {{ locationVenues.length }}&nbsp;{{ controller.labels['LBL_LOC_COUNT_SUFFIX'] || 'Locations Added!' }}
1176
- </p>
1177
-
1178
- <!-- Search input (hide when max reached) -->
1179
- <div class="loc-search-container position-relative" *ngIf="!locationMaxReached">
1180
- <div class="loc-search-wrapper position-relative d-flex align-items-center mt-4">
1181
- <mat-icon class="loc-search-icon position-absolute fs-1-1rem c-9CA3AF pe-none">search</mat-icon>
1182
- <input
1183
- class="field-input loc-search-input w-100 font-poppins flex-1 fs-0-875rem c-111827 br-7px br-8px bc-F3F4F6 pl-2-4rem bc-DC2626 pt-0-625rem pb-0-625rem pl-16px pr-16px bc-ffffff b-1px-solid-D1D5DB pr-3-5rem"
1184
- [placeholder]="config.locationConfig?.venuePlaceholder || (controller.labels['PH_LOC_VENUE'] || 'Type to search venue...')"
1185
- [value]="locationSearchText" (input)="handleLocationSearchInput($event)" (blur)="hideLocationSuggestions()"
1186
- autocomplete="off" [class.is-invalid]="errorMessage">
1187
- </div>
1188
- <!-- Suggestions dropdown -->
1189
- <div class="loc-suggestions-panel position-absolute overflow-hidden br-8px b-ffffff b-1px-solid-D1D5DB"
1190
- *ngIf="locationShowSuggestions && locationSuggestions.length">
1191
- <div *ngFor="let sug of locationSuggestions"
1192
- class="loc-suggestion-item d-flex align-items-center gap-10 cursor-pointer p-10px-14px"
1193
- (mousedown)="onLocationSuggestionSelect(sug)">
1194
- <mat-icon class="loc-suggestion-icon fs-18px c-E53E3E">place</mat-icon>
1195
- <span class="loc-suggestion-text overflow-hidden fs-0-875rem c-374151">{{ sug.description }}</span>
1196
- </div>
1197
- </div>
1198
- </div>
1199
-
1200
- <!-- Add another button -->
1201
- <button type="button"
1202
- class="loc-add-btn d-inline-flex align-items-center gap-6 cursor-pointer font-weight-600 p-0 b-none border-none fs-0-875rem c-1A56DB"
1203
- *ngIf="locationVenues.length > 0 && !locationMaxReached && config.locationConfig?.allowMulti">
1204
- <mat-icon>add_circle_outline</mat-icon>
1205
- <span>{{ controller.labels['LBL_LOC_ADD_ANOTHER'] || 'Add another Location' }}</span>
1206
- </button>
1207
-
1208
- <!-- Map -->
1209
- <div class="loc-map-container overflow-hidden br-10px b-1px-solid-E5E7EB"
1210
- *ngIf="config.locationConfig?.showMap !== false">
1211
- <ng-container *ngIf="config.locationConfig?.googleMapsApiKey; else simpleEmbed">
1212
- <div [id]="'loc-map-' + config.name" class="loc-map-frame w-100 d-block border-none"
1213
- [style.height]="config.locationConfig?.mapHeight || '300px'"></div>
1214
- </ng-container>
1215
- <ng-template #simpleEmbed>
1216
- <iframe class="loc-map-frame w-100 d-block border-none"
1217
- [style.height]="config.locationConfig?.mapHeight || '300px'" [src]="getLocationMapEmbedUrl() | trustedUrl"
1218
- frameborder="0" allowfullscreen loading="lazy">
1219
- </iframe>
1220
- </ng-template>
1221
- </div>
1222
-
1223
- <!-- Map hint -->
1224
- <p class="loc-map-hint m-0 text-center fs-0-78rem c-6B7280">
1225
- {{ controller.labels['LBL_LOC_MAP_HINT'] || 'Type the venue address. A map will appear to assist you.' }}
1226
- </p>
1227
- </div>
1228
-
1229
- <!-- ONLINE TAB -->
1230
- <div *ngIf="locationActiveTab === 'ONLINE'" class="loc-panel loc-online-panel d-flex flex-column gap-12">
1231
- <p class="loc-section-label m-0 font-weight-600 c-111827 fs-0-9rem">
1232
- {{ controller.labels['LBL_LOC_ONLINE_URL'] || 'Event URL' }}
1233
- </p>
1234
- <div class="loc-search-wrapper position-relative d-flex align-items-center mt-4">
1235
- <mat-icon class="loc-search-icon position-absolute fs-1-1rem c-9CA3AF pe-none">link</mat-icon>
1236
- <input
1237
- class="field-input loc-search-input w-100 font-poppins flex-1 fs-0-875rem c-111827 br-7px br-8px bc-F3F4F6 pl-2-4rem bc-DC2626 pt-0-625rem pb-0-625rem pl-16px pr-16px bc-ffffff b-1px-solid-D1D5DB pr-3-5rem"
1238
- type="url"
1239
- [placeholder]="config.locationConfig?.onlinePlaceholder || (controller.labels['PH_LOC_ONLINE'] || 'https://zoom.us/j/...')"
1240
- [value]="locationOnlineUrl" (input)="onLocationUrlChange($any($event.target).value)"
1241
- [class.is-invalid]="errorMessage">
1242
- </div>
1243
- </div>
1244
-
1245
- <!-- TBA TAB -->
1246
- <div *ngIf="locationActiveTab === 'TBA'"
1247
- class="loc-panel loc-tba-panel d-flex flex-column gap-12 justify-content-center">
1248
- <div
1249
- class="loc-tba-content d-flex flex-column align-items-center justify-content-center text-center gap-12 p-32px-24px b-F9FAFB b-1px-dashed-D1D5DB br-10px">
1250
- <mat-icon class="loc-tba-icon fs-40px c-9CA3AF">schedule</mat-icon>
1251
- <p class="loc-tba-text m-0 c-6B7280 fs-0-9rem">
1252
- {{ controller.labels['LBL_LOC_TBA_DESC'] || "This event's location is yet to be announced. Check back later
1253
- for updates." }}
1254
- </p>
1255
- </div>
1256
- </div>
1257
-
1258
- <!-- Hidden real form control -->
1259
- <input type="hidden" [formControlName]="config.name!">
1260
-
1261
- <span class="field-error color-error fs-0-75rem" *ngIf="errorMessage">{{ errorMessage }}</span>
1262
- </div>
1263
-
1264
- <!-- ══ Link List Input ════════════════════════════════════════════════════ -->
1265
- <div *ngIf="isLinkList" class="field-wrapper d-flex flex-column gap-6" [formGroup]="formGroup">
1266
- <label *ngIf="config.label" class="field-label d-block font-poppins c-202124 fs-18px">
1267
- {{ controller.labels[config.label] || config.label }}
1268
- <span class="required c-DC2626 ml-0-125rem" *ngIf="config.required">*</span>
1269
- </label>
1270
-
1271
- <!-- Top Links list (if listPosition === 'top') -->
1272
- <div *ngIf="linkListConfig?.listPosition === 'top' && linkListItems.length > 0" class="link-list-container mb-8px">
1273
- <ul class="link-items-list p-0 m-0"
1274
- [style.display]="'grid'"
1275
- [style.gridTemplateColumns]="'repeat(12, 1fr)'"
1276
- [style.gap]="'12px 16px'">
1277
- <li *ngFor="let item of linkListItems; let idx = index"
1278
- class="link-list-item"
1279
- [style.gridColumn]="'span ' + (linkListConfig?.colSpan || 12)">
1280
- <!-- Normal display mode -->
1281
- <ng-container *ngIf="editingLinkIndex !== idx">
1282
- <span class="link-url-text text-truncate font-weight-500 color-333">{{ getLinkDisplayUrl(item) }}</span>
1283
- <div class="link-item-actions d-flex align-items-center gap-12px">
1284
- <button type="button" class="action-btn edit-btn"
1285
- *ngIf="linkListConfig?.editable !== false" (click)="startEditLink(idx)" [title]="controller.labels['COMMON.ACTIONS.EDIT'] || 'Edit'">
1286
- <mat-icon>edit</mat-icon>
1287
- </button>
1288
- <button type="button" class="action-btn delete-btn"
1289
- *ngIf="linkListConfig?.deleteable !== false" (click)="deleteLinkItem(idx)" [title]="controller.labels['COMMON.ACTIONS.DELETE'] || 'Delete'">
1290
- <mat-icon>delete_outline</mat-icon>
1291
- </button>
1292
- </div>
1293
- </ng-container>
1294
-
1295
- <!-- Inline Edit mode -->
1296
- <ng-container *ngIf="editingLinkIndex === idx">
1297
- <div class="edit-input-wrapper d-flex align-items-center gap-8px w-100">
1298
- <input type="text" class="field-input flex-1 py-4px" [(ngModel)]="editingLinkValue"
1299
- [ngModelOptions]="{standalone: true}" (keydown.enter)="saveEditLink(idx)" (keydown.escape)="cancelEditLink()"
1300
- (input)="linkValidationError = ''">
1301
- <button type="button" class="action-btn save-btn" (click)="saveEditLink(idx)" [title]="controller.labels['COMMON.ACTIONS.SAVE'] || 'Save'">
1302
- <mat-icon>check</mat-icon>
1303
- </button>
1304
- <button type="button" class="action-btn cancel-btn" (click)="cancelEditLink()" [title]="controller.labels['COMMON.ACTIONS.CANCEL'] || 'Cancel'">
1305
- <mat-icon>close</mat-icon>
1306
- </button>
1307
- </div>
1308
- </ng-container>
1309
- </li>
1310
- </ul>
1311
- </div>
1312
-
1313
- <!-- Input text + plus icon -->
1314
- <div class="input-group position-relative d-flex w-100">
1315
- <input type="text" class="field-input pe-40px" [placeholder]="controller.labels[config.placeholder!] || config.placeholder || controller.labels['COMMON.ENTER_LINK'] || 'Enter link...'"
1316
- [(ngModel)]="newLinkInputValue" [ngModelOptions]="{standalone: true}" (keydown.enter)="addLinkItem($event)"
1317
- [disabled]="config.readonly || config.disabled"
1318
- (input)="linkValidationError = ''">
1319
-
1320
- <button type="button" class="position-absolute right-8px top-50-percent translate-y-minus-50 d-flex align-items-center justify-content-center b-none bg-none p-0 cursor-pointer color-primary"
1321
- [disabled]="config.readonly || config.disabled || !newLinkInputValue?.trim()"
1322
- (click)="addLinkItem()" style="right: 8px; top: 50%; transform: translateY(-50%); z-index: 10;">
1323
- <mat-icon>add</mat-icon>
1324
- </button>
1325
- </div>
1326
-
1327
- <!-- Validation error message -->
1328
- <span class="field-error color-error fs-0-75rem mt-4px d-block" *ngIf="linkValidationError">{{ linkValidationError }}</span>
1329
-
1330
- <!-- Bottom Links list (if listPosition !== 'top') -->
1331
- <div *ngIf="linkListConfig?.listPosition !== 'top' && linkListItems.length > 0" class="link-list-container mt-8px">
1332
- <ul class="link-items-list p-0 m-0"
1333
- [style.display]="'grid'"
1334
- [style.gridTemplateColumns]="'repeat(12, 1fr)'"
1335
- [style.gap]="'12px 16px'">
1336
- <li *ngFor="let item of linkListItems; let idx = index"
1337
- class="link-list-item"
1338
- [style.gridColumn]="'span ' + (linkListConfig?.colSpan || 12)">
1339
- <!-- Normal display mode -->
1340
- <ng-container *ngIf="editingLinkIndex !== idx">
1341
- <span class="link-url-text text-truncate font-weight-500 color-333">{{ getLinkDisplayUrl(item) }}</span>
1342
- <div class="link-item-actions d-flex align-items-center gap-12px">
1343
- <button type="button" class="action-btn edit-btn"
1344
- *ngIf="linkListConfig?.editable !== false" (click)="startEditLink(idx)" [title]="controller.labels['COMMON.ACTIONS.EDIT'] || 'Edit'">
1345
- <mat-icon>edit</mat-icon>
1346
- </button>
1347
- <button type="button" class="action-btn delete-btn"
1348
- *ngIf="linkListConfig?.deleteable !== false" (click)="deleteLinkItem(idx)" [title]="controller.labels['COMMON.ACTIONS.DELETE'] || 'Delete'">
1349
- <mat-icon>delete_outline</mat-icon>
1350
- </button>
1351
- </div>
1352
- </ng-container>
1353
-
1354
- <!-- Inline Edit mode -->
1355
- <ng-container *ngIf="editingLinkIndex === idx">
1356
- <div class="edit-input-wrapper d-flex align-items-center gap-8px w-100">
1357
- <input type="text" class="field-input flex-1 py-4px" [(ngModel)]="editingLinkValue"
1358
- [ngModelOptions]="{standalone: true}" (keydown.enter)="saveEditLink(idx)" (keydown.escape)="cancelEditLink()"
1359
- (input)="linkValidationError = ''">
1360
- <button type="button" class="action-btn save-btn" (click)="saveEditLink(idx)" [title]="controller.labels['COMMON.ACTIONS.SAVE'] || 'Save'">
1361
- <mat-icon>check</mat-icon>
1362
- </button>
1363
- <button type="button" class="action-btn cancel-btn" (click)="cancelEditLink()" [title]="controller.labels['COMMON.ACTIONS.CANCEL'] || 'Cancel'">
1364
- <mat-icon>close</mat-icon>
1365
- </button>
1366
- </div>
1367
- </ng-container>
1368
- </li>
1369
- </ul>
1370
- </div>
1371
-
1372
- <!-- Hidden real form control -->
1373
- <input type="hidden" [formControlName]="config.name!">
1374
-
1375
- <span class="field-hint c-6B7280 fs-0-75rem" *ngIf="config.hint && !errorMessage">{{ controller.labels[config.hint] || config.hint }}</span>
1376
- <span class="field-error color-error fs-0-75rem" *ngIf="errorMessage">{{ errorMessage }}</span>
1377
- </div>
1378
-
1379
- </div>