commons-shared-web-ui 0.0.54 → 0.0.55

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 (225) hide show
  1. package/.claude/settings.local.json +10 -0
  2. package/documentation/alert.md +123 -0
  3. package/documentation/button-dropdown.md +126 -0
  4. package/documentation/button.md +184 -0
  5. package/documentation/cards-usage-guidelines.md +131 -0
  6. package/documentation/configurable-form.md +605 -0
  7. package/documentation/confirmation-modal.md +250 -0
  8. package/documentation/filter-sidebar.md +178 -0
  9. package/documentation/filter-table-selector.md +228 -0
  10. package/documentation/form-builder.md +599 -0
  11. package/documentation/form-components.md +384 -0
  12. package/documentation/form-field-configuration.md +250 -0
  13. package/documentation/nav.md +427 -0
  14. package/documentation/pagination.md +181 -0
  15. package/documentation/side-nav-documentation.md +169 -0
  16. package/documentation/smart-form.md +2765 -0
  17. package/documentation/smart-table.md +1221 -0
  18. package/documentation/snackbar.md +118 -0
  19. package/documentation/style-externalization.md +88 -0
  20. package/documentation/summary-card.md +279 -0
  21. package/documentation/table-column-configuration.md +221 -0
  22. package/documentation/time-picker.md +274 -0
  23. package/ng-package.json +30 -0
  24. package/package.json +53 -34
  25. package/src/lib/modules/alert/alert.models.ts +6 -0
  26. package/src/lib/modules/alert/alert.module.ts +16 -0
  27. package/src/lib/modules/alert/components/alert/alert.component.html +27 -0
  28. package/src/lib/modules/alert/components/alert/alert.component.scss +92 -0
  29. package/src/lib/modules/alert/components/alert/alert.component.ts +81 -0
  30. package/src/lib/modules/button/button.models.ts +13 -0
  31. package/src/lib/modules/button/button.module.ts +16 -0
  32. package/src/lib/modules/button/components/button/button.component.html +22 -0
  33. package/src/lib/modules/button/components/button/button.component.scss +88 -0
  34. package/src/lib/modules/button/components/button/button.component.ts +67 -0
  35. package/src/lib/modules/button-dropdown/button-dropdown.models.ts +26 -0
  36. package/src/lib/modules/button-dropdown/button-dropdown.module.ts +22 -0
  37. package/src/lib/modules/button-dropdown/button-dropdown.theme.scss +87 -0
  38. package/src/lib/modules/button-dropdown/components/button-dropdown/button-dropdown.component.html +41 -0
  39. package/src/lib/modules/button-dropdown/components/button-dropdown/button-dropdown.component.scss +135 -0
  40. package/src/lib/modules/button-dropdown/components/button-dropdown/button-dropdown.component.ts +160 -0
  41. package/src/lib/modules/configurable-form/component/configurable-form.component.html +294 -0
  42. package/src/lib/modules/configurable-form/component/configurable-form.component.scss +503 -0
  43. package/src/lib/modules/configurable-form/component/configurable-form.component.ts +628 -0
  44. package/src/lib/modules/configurable-form/configurable-form.examples.ts +154 -0
  45. package/src/lib/modules/configurable-form/configurable-form.model.ts +131 -0
  46. package/src/lib/modules/configurable-form/configurable-form.module.ts +19 -0
  47. package/src/lib/modules/confirmation-modal/components/confirmation-modal/confirmation-modal.component.html +77 -0
  48. package/src/lib/modules/confirmation-modal/components/confirmation-modal/confirmation-modal.component.scss +395 -0
  49. package/src/lib/modules/confirmation-modal/components/confirmation-modal/confirmation-modal.component.ts +266 -0
  50. package/src/lib/modules/confirmation-modal/confirmation-modal.models.ts +71 -0
  51. package/src/lib/modules/confirmation-modal/confirmation-modal.module.ts +20 -0
  52. package/src/lib/modules/filter/components/filter/filter.component.html +131 -0
  53. package/src/lib/modules/filter/components/filter/filter.component.scss +245 -0
  54. package/src/lib/modules/filter/components/filter/filter.component.ts +216 -0
  55. package/src/lib/modules/filter/filter.models.ts +88 -0
  56. package/src/lib/modules/filter/filter.module.ts +24 -0
  57. package/src/lib/modules/filter-sidebar/components/filter-sidebar/filter-sidebar.component.html +112 -0
  58. package/src/lib/modules/filter-sidebar/components/filter-sidebar/filter-sidebar.component.scss +186 -0
  59. package/src/lib/modules/filter-sidebar/components/filter-sidebar/filter-sidebar.component.ts +163 -0
  60. package/src/lib/modules/filter-sidebar/filter-sidebar.models.ts +95 -0
  61. package/src/lib/modules/filter-sidebar/filter-sidebar.module.ts +24 -0
  62. package/src/lib/modules/filter-table-selector/components/filter-table-selector/filter-table-selector.component.html +73 -0
  63. package/src/lib/modules/filter-table-selector/components/filter-table-selector/filter-table-selector.component.scss +321 -0
  64. package/src/lib/modules/filter-table-selector/components/filter-table-selector/filter-table-selector.component.ts +361 -0
  65. package/src/lib/modules/filter-table-selector/filter-table-selector.models.ts +91 -0
  66. package/src/lib/modules/filter-table-selector/filter-table-selector.module.ts +22 -0
  67. package/src/lib/modules/form-builder/components/field-configurator/configurator-config-panel/configurator-config-panel.component.html +63 -0
  68. package/src/lib/modules/form-builder/components/field-configurator/configurator-config-panel/configurator-config-panel.component.scss +496 -0
  69. package/src/lib/modules/form-builder/components/field-configurator/configurator-config-panel/configurator-config-panel.component.ts +445 -0
  70. package/src/lib/modules/form-builder/components/field-configurator/configurator-tree/configurator-tree.component.html +75 -0
  71. package/src/lib/modules/form-builder/components/field-configurator/configurator-tree/configurator-tree.component.scss +210 -0
  72. package/src/lib/modules/form-builder/components/field-configurator/configurator-tree/configurator-tree.component.ts +55 -0
  73. package/src/lib/modules/form-builder/components/field-configurator/field-configurator.component.html +25 -0
  74. package/src/lib/modules/form-builder/components/field-configurator/field-configurator.component.scss +82 -0
  75. package/src/lib/modules/form-builder/components/field-configurator/field-configurator.component.ts +95 -0
  76. package/src/lib/modules/form-builder/components/field-selection/field-selection.component.html +20 -0
  77. package/src/lib/modules/form-builder/components/field-selection/field-selection.component.scss +37 -0
  78. package/src/lib/modules/form-builder/components/field-selection/field-selection.component.ts +94 -0
  79. package/src/lib/modules/form-builder/components/field-selection/group-node/group-node.component.html +46 -0
  80. package/src/lib/modules/form-builder/components/field-selection/group-node/group-node.component.scss +102 -0
  81. package/src/lib/modules/form-builder/components/field-selection/group-node/group-node.component.ts +50 -0
  82. package/src/lib/modules/form-builder/components/field-selection/selection-field-node/selection-field-node.component.html +35 -0
  83. package/src/lib/modules/form-builder/components/field-selection/selection-field-node/selection-field-node.component.scss +67 -0
  84. package/src/lib/modules/form-builder/components/field-selection/selection-field-node/selection-field-node.component.ts +34 -0
  85. package/src/lib/modules/form-builder/components/field-selection/selection-section-node/selection-section-node.component.html +68 -0
  86. package/src/lib/modules/form-builder/components/field-selection/selection-section-node/selection-section-node.component.scss +113 -0
  87. package/src/lib/modules/form-builder/components/field-selection/selection-section-node/selection-section-node.component.ts +74 -0
  88. package/src/lib/modules/form-builder/configs/field-type-schema.map.ts +533 -0
  89. package/src/lib/modules/form-builder/form-builder.module.ts +36 -0
  90. package/src/lib/modules/form-builder/index.ts +9 -0
  91. package/src/lib/modules/form-builder/models/builder.models.ts +7 -0
  92. package/src/lib/modules/form-builder/models/field-configurator.models.ts +38 -0
  93. package/src/lib/modules/form-builder/models/field-selection.models.ts +51 -0
  94. package/src/lib/modules/form-builder/services/field-configurator.service.ts +258 -0
  95. package/src/lib/modules/form-builder/services/field-selection.service.ts +299 -0
  96. package/src/lib/modules/form-builder/services/form-schema-tree.service.ts +670 -0
  97. package/src/lib/modules/form-builder/tokens/builder.tokens.ts +10 -0
  98. package/src/lib/modules/form-builder/utils/constants.ts +43 -0
  99. package/src/lib/modules/form-components/components/checkbox/checkbox.component.html +29 -0
  100. package/src/lib/modules/form-components/components/checkbox/checkbox.component.scss +111 -0
  101. package/src/lib/modules/form-components/components/checkbox/checkbox.component.ts +207 -0
  102. package/src/lib/modules/form-components/components/checkbox/checkbox.models.ts +35 -0
  103. package/src/lib/modules/form-components/components/datepicker/datepicker.component.html +42 -0
  104. package/src/lib/modules/form-components/components/datepicker/datepicker.component.scss +115 -0
  105. package/src/lib/modules/form-components/components/datepicker/datepicker.component.ts +267 -0
  106. package/src/lib/modules/form-components/components/datepicker/datepicker.models.ts +45 -0
  107. package/src/lib/modules/form-components/components/dropdown/dropdown.component.html +74 -0
  108. package/src/lib/modules/form-components/components/dropdown/dropdown.component.scss +252 -0
  109. package/src/lib/modules/form-components/components/dropdown/dropdown.component.ts +377 -0
  110. package/src/lib/modules/form-components/components/dropdown/dropdown.models.ts +53 -0
  111. package/src/lib/modules/form-components/components/input/input.component.html +51 -0
  112. package/src/lib/modules/form-components/components/input/input.component.scss +128 -0
  113. package/src/lib/modules/form-components/components/input/input.component.ts +250 -0
  114. package/src/lib/modules/form-components/components/input/input.models.ts +55 -0
  115. package/src/lib/modules/form-components/components/radio/radio.component.html +22 -0
  116. package/src/lib/modules/form-components/components/radio/radio.component.scss +107 -0
  117. package/src/lib/modules/form-components/components/radio/radio.component.ts +181 -0
  118. package/src/lib/modules/form-components/components/radio/radio.models.ts +39 -0
  119. package/src/lib/modules/form-components/components/search/search.component.html +15 -0
  120. package/src/lib/modules/form-components/components/search/search.component.scss +87 -0
  121. package/src/lib/modules/form-components/components/search/search.component.ts +213 -0
  122. package/src/lib/modules/form-components/components/search/search.models.ts +40 -0
  123. package/src/lib/modules/form-components/components/toggle/toggle.component.html +15 -0
  124. package/src/lib/modules/form-components/components/toggle/toggle.component.scss +81 -0
  125. package/src/lib/modules/form-components/components/toggle/toggle.component.ts +166 -0
  126. package/src/lib/modules/form-components/components/toggle/toggle.models.ts +27 -0
  127. package/src/lib/modules/form-components/directives/click-outside.directive.ts +22 -0
  128. package/src/lib/modules/form-components/form-components.module.ts +42 -0
  129. package/src/lib/modules/form-field-configuration/components/_ffc-controls.scss +89 -0
  130. package/src/lib/modules/form-field-configuration/components/config-field-node/config-field-node.component.html +68 -0
  131. package/src/lib/modules/form-field-configuration/components/config-field-node/config-field-node.component.scss +73 -0
  132. package/src/lib/modules/form-field-configuration/components/config-field-node/config-field-node.component.ts +34 -0
  133. package/src/lib/modules/form-field-configuration/components/config-section-node/config-section-node.component.html +78 -0
  134. package/src/lib/modules/form-field-configuration/components/config-section-node/config-section-node.component.scss +83 -0
  135. package/src/lib/modules/form-field-configuration/components/config-section-node/config-section-node.component.ts +94 -0
  136. package/src/lib/modules/form-field-configuration/components/form-field-configuration/form-field-configuration.component.html +38 -0
  137. package/src/lib/modules/form-field-configuration/components/form-field-configuration/form-field-configuration.component.scss +42 -0
  138. package/src/lib/modules/form-field-configuration/components/form-field-configuration/form-field-configuration.component.ts +92 -0
  139. package/src/lib/modules/form-field-configuration/form-field-configuration.module.ts +23 -0
  140. package/src/lib/modules/form-field-configuration/index.ts +7 -0
  141. package/src/lib/modules/form-field-configuration/models/field-configuration.models.ts +62 -0
  142. package/src/lib/modules/form-field-configuration/services/config-schema-tree.service.ts +159 -0
  143. package/src/lib/modules/form-field-configuration/services/field-configuration.service.ts +228 -0
  144. package/src/lib/modules/material/material.module.ts +94 -0
  145. package/src/lib/modules/nav/components/nav/nav.component.html +34 -0
  146. package/src/lib/modules/nav/components/nav/nav.component.scss +171 -0
  147. package/src/lib/modules/nav/components/nav/nav.component.ts +82 -0
  148. package/src/lib/modules/nav/nav.models.ts +31 -0
  149. package/src/lib/modules/nav/nav.module.ts +17 -0
  150. package/src/lib/modules/pagination/components/pagination/pagination.component.html +52 -0
  151. package/src/lib/modules/pagination/components/pagination/pagination.component.scss +155 -0
  152. package/src/lib/modules/pagination/components/pagination/pagination.component.ts +109 -0
  153. package/src/lib/modules/pagination/pagination.module.ts +17 -0
  154. package/src/lib/modules/side-nav/components/side-nav/side-nav.component.html +56 -0
  155. package/src/lib/modules/side-nav/components/side-nav/side-nav.component.scss +342 -0
  156. package/src/lib/modules/side-nav/components/side-nav/side-nav.component.ts +135 -0
  157. package/src/lib/modules/side-nav/side-nav.models.ts +38 -0
  158. package/src/lib/modules/side-nav/side-nav.module.ts +16 -0
  159. package/src/lib/modules/smart-form/components/form-field/form-field.component.html +1330 -0
  160. package/src/lib/modules/smart-form/components/form-field/form-field.component.scss +2112 -0
  161. package/src/lib/modules/smart-form/components/form-field/form-field.component.ts +3006 -0
  162. package/src/lib/modules/smart-form/components/form-section/form-section.component.html +65 -0
  163. package/src/lib/modules/smart-form/components/form-section/form-section.component.scss +209 -0
  164. package/src/lib/modules/smart-form/components/form-section/form-section.component.ts +131 -0
  165. package/src/lib/modules/smart-form/components/smart-form/smart-form.component.html +253 -0
  166. package/src/lib/modules/smart-form/components/smart-form/smart-form.component.scss +689 -0
  167. package/src/lib/modules/smart-form/components/smart-form/smart-form.component.ts +1131 -0
  168. package/src/lib/modules/smart-form/index.ts +10 -0
  169. package/src/lib/modules/smart-form/models/form-schema.model.ts +793 -0
  170. package/src/lib/modules/smart-form/models/hierarchy-config.model.ts +21 -0
  171. package/src/lib/modules/smart-form/services/expression.service.ts +75 -0
  172. package/src/lib/modules/smart-form/services/smart-form-controller.service.ts +67 -0
  173. package/src/lib/modules/smart-form/smart-form.examples.ts +1324 -0
  174. package/src/lib/modules/smart-form/smart-form.module.ts +38 -0
  175. package/src/lib/modules/smart-form/utils/translation.utils.ts +82 -0
  176. package/src/lib/modules/smart-form/utils/trusted-url.pipe.ts +25 -0
  177. package/src/lib/modules/smart-form/utils/validation.utils.ts +98 -0
  178. package/src/lib/modules/smart-table/components/smart-table/smart-table.component.html +283 -0
  179. package/src/lib/modules/smart-table/components/smart-table/smart-table.component.scss +685 -0
  180. package/src/lib/modules/smart-table/components/smart-table/smart-table.component.ts +1128 -0
  181. package/src/lib/modules/smart-table/models/table-config.model.ts +213 -0
  182. package/src/lib/modules/smart-table/smart-table.module.ts +30 -0
  183. package/src/lib/modules/smart-table/utils/safe-html.pipe.ts +28 -0
  184. package/src/lib/modules/smart-table/utils/smart-table.utils.ts +18 -0
  185. package/src/lib/modules/snackbar/components/snackbar.component.html +41 -0
  186. package/src/lib/modules/snackbar/components/snackbar.component.scss +99 -0
  187. package/src/lib/modules/snackbar/components/snackbar.component.ts +18 -0
  188. package/src/lib/modules/snackbar/models/snackbar.models.ts +10 -0
  189. package/src/lib/modules/snackbar/services/snackbar.service.ts +40 -0
  190. package/src/lib/modules/snackbar/snackbar.module.ts +11 -0
  191. package/src/lib/modules/summary-card/components/summary-card/summary-card.component.html +47 -0
  192. package/src/lib/modules/summary-card/components/summary-card/summary-card.component.scss +199 -0
  193. package/src/lib/modules/summary-card/components/summary-card/summary-card.component.ts +126 -0
  194. package/src/lib/modules/summary-card/summary-card.module.ts +18 -0
  195. package/src/lib/modules/table-column-configuration/components/_tcc-controls.scss +53 -0
  196. package/src/lib/modules/table-column-configuration/components/config-column-node/config-column-node.component.html +47 -0
  197. package/src/lib/modules/table-column-configuration/components/config-column-node/config-column-node.component.scss +50 -0
  198. package/src/lib/modules/table-column-configuration/components/config-column-node/config-column-node.component.ts +33 -0
  199. package/src/lib/modules/table-column-configuration/components/table-column-configuration/table-column-configuration.component.html +30 -0
  200. package/src/lib/modules/table-column-configuration/components/table-column-configuration/table-column-configuration.component.scss +36 -0
  201. package/src/lib/modules/table-column-configuration/components/table-column-configuration/table-column-configuration.component.ts +78 -0
  202. package/src/lib/modules/table-column-configuration/index.ts +5 -0
  203. package/src/lib/modules/table-column-configuration/models/table-column-configuration.models.ts +20 -0
  204. package/src/lib/modules/table-column-configuration/services/table-column-configuration.service.ts +77 -0
  205. package/src/lib/modules/table-column-configuration/table-column-configuration.module.ts +21 -0
  206. package/src/lib/modules/time-picker/components/time-picker/time-picker.component.html +37 -0
  207. package/src/lib/modules/time-picker/components/time-picker/time-picker.component.scss +102 -0
  208. package/src/lib/modules/time-picker/components/time-picker/time-picker.component.ts +178 -0
  209. package/src/lib/modules/time-picker/components/time-wheel-panel/time-wheel-panel.component.html +78 -0
  210. package/src/lib/modules/time-picker/components/time-wheel-panel/time-wheel-panel.component.scss +226 -0
  211. package/src/lib/modules/time-picker/components/time-wheel-panel/time-wheel-panel.component.ts +595 -0
  212. package/src/lib/modules/time-picker/models/time-picker.models.ts +49 -0
  213. package/src/lib/modules/time-picker/time-picker.module.ts +23 -0
  214. package/src/lib/modules/time-picker/time-picker.theme.scss +87 -0
  215. package/src/lib/shared-ui.module.ts +55 -0
  216. package/src/lib/styles/global.scss +10 -0
  217. package/src/lib/utils/constants.ts +11 -0
  218. package/src/lib/utils/storage.utils.ts +37 -0
  219. package/src/lib/utils/string.utils.ts +23 -0
  220. package/src/lib/utils/translation.utils.ts +87 -0
  221. package/src/public-api.ts +127 -0
  222. package/tsconfig.lib.json +15 -0
  223. package/fesm2022/commons-shared-web-ui.mjs +0 -14823
  224. package/fesm2022/commons-shared-web-ui.mjs.map +0 -1
  225. package/index.d.ts +0 -4679
@@ -0,0 +1,670 @@
1
+ import { Injectable } from '@angular/core';
2
+ import {
3
+ FormSchema,
4
+ FieldConfig,
5
+ SectionConfig,
6
+ } from '../../smart-form/models/form-schema.model';
7
+ import {
8
+ SelectionGroupNode,
9
+ SelectionSectionNode,
10
+ SelectionFieldNode,
11
+ } from '../models/field-selection.models';
12
+ import {
13
+ ConfiguratorTreeNode,
14
+ } from '../models/field-configurator.models';
15
+
16
+ /**
17
+ * Service to parse a FormSchema into selection/configurator tree structures,
18
+ * and to rebuild a modified FormSchema from those trees.
19
+ */
20
+ @Injectable({ providedIn: 'root' })
21
+ export class FormSchemaTreeService {
22
+
23
+ // ────────────────────────────────────────────────────────────────────────────
24
+ // SELECTION TREE
25
+ // ────────────────────────────────────────────────────────────────────────────
26
+
27
+ /**
28
+ * Parse a FormSchema into SelectionGroupNodes for the field-selection UI.
29
+ * Stepper forms produce one group per step; section forms produce one group.
30
+ */
31
+ parseSchemaToSelectionGroups(schema: FormSchema): SelectionGroupNode[] {
32
+ if (schema.formType === 'STEPPER' && schema.stepperConfig?.children?.length) {
33
+ return schema.stepperConfig.children
34
+ .filter(child => child.type === 'GROUP')
35
+ .map(groupField => this._buildGroupNode(groupField));
36
+ }
37
+
38
+ // SECTION form → single virtual group. This group has no backing FieldConfig of its
39
+ // own (it's the schema root, not a child), so its enabled state is DERIVED from its
40
+ // sections: on if at least one child section is on, off only once every child is off.
41
+ // This flag is not just cosmetic — smart-form.component.html reads sectionConfig.isEnabled
42
+ // to decide whether the whole form section renders for end users, so it must reflect the
43
+ // real combined state of its children rather than being an independently-clicked flag.
44
+ // The master toggle click itself still cascades enable/disable to all children (see
45
+ // FieldSelectionService.toggleGroup); this is just what happens to the header on re-parse.
46
+ if (schema.sectionConfig?.children?.length) {
47
+ const sections = this._buildSectionsFromChildren(schema.sectionConfig.children);
48
+ return [{
49
+ label: schema.label || 'Form',
50
+ groupFieldConfig: null,
51
+ enabled: sections.length === 0 || sections.some(s => s.enabled),
52
+ expanded: true, // Collapse by default for better UX
53
+ sections,
54
+ }];
55
+ }
56
+
57
+ return [];
58
+ }
59
+
60
+ /**
61
+ * Rebuild a FormSchema from the selection tree, updating visible/disabled/required
62
+ * flags on each FieldConfig. Does NOT mutate the original schema.
63
+ */
64
+ applySelectionToSchema(
65
+ groups: SelectionGroupNode[],
66
+ originalSchema: FormSchema,
67
+ ): FormSchema {
68
+ const schema: FormSchema = JSON.parse(JSON.stringify(originalSchema));
69
+
70
+ if (schema.formType === 'STEPPER' && schema.stepperConfig?.children?.length) {
71
+ const stepChildren = schema.stepperConfig.children.filter(c => c.type === 'GROUP');
72
+ groups.forEach((group, gi) => {
73
+ if (gi < stepChildren.length) {
74
+ const groupField = stepChildren[gi];
75
+ groupField.isEnabled = group.enabled;
76
+ groupField.visible = group.enabled; // Backward compatibility
77
+ groupField.disabled = !group.enabled;
78
+ if (groupField.sectionConfig?.children) {
79
+ this._applySectionsToChildren(group.sections, groupField.sectionConfig.children);
80
+ }
81
+ }
82
+ });
83
+ } else if (schema.sectionConfig?.children?.length && groups.length > 0) {
84
+ schema.sectionConfig.isEnabled = groups[0].enabled;
85
+ this._applySectionsToChildren(groups[0].sections, schema.sectionConfig.children);
86
+ }
87
+
88
+ return schema;
89
+ }
90
+
91
+ // ────────────────────────────────────────────────────────────────────────────
92
+ // CONFIGURATOR TREE
93
+ // ────────────────────────────────────────────────────────────────────────────
94
+
95
+ /**
96
+ * Parse a FormSchema into a flat ConfiguratorTreeNode[] for the left-panel tree.
97
+ */
98
+ parseSchemaToConfiguratorTree(schema: FormSchema): ConfiguratorTreeNode[] {
99
+ if (schema.formType === 'STEPPER' && schema.stepperConfig?.children?.length) {
100
+ return schema.stepperConfig.children
101
+ .filter(c => c.type === 'GROUP')
102
+ .map((groupField, gi) => this._buildConfiguratorGroupNode(groupField, [gi]));
103
+ }
104
+
105
+ if (schema.sectionConfig?.children?.length) {
106
+ return this._buildConfiguratorChildNodes(schema.sectionConfig.children, []);
107
+ }
108
+
109
+ return [];
110
+ }
111
+
112
+ /**
113
+ * Find a FieldConfig inside a FormSchema by its index path.
114
+ */
115
+ findFieldByPath(schema: FormSchema, path: number[]): FieldConfig | null {
116
+ if (path.length === 0) return null;
117
+
118
+ let children: FieldConfig[] | undefined;
119
+ if (schema.formType === 'STEPPER') {
120
+ children = schema.stepperConfig?.children;
121
+ } else {
122
+ children = schema.sectionConfig?.children;
123
+ }
124
+ if (!children) return null;
125
+
126
+ let current: FieldConfig | undefined;
127
+ for (let i = 0; i < path.length; i++) {
128
+ const idx = path[i];
129
+ if (!children || idx >= children.length) return null;
130
+ current = children[idx];
131
+
132
+ if (i < path.length - 1) {
133
+ // Navigate deeper
134
+ if (current.sectionConfig?.children) {
135
+ children = current.sectionConfig.children;
136
+ } else if (current.children) {
137
+ children = current.children;
138
+ } else {
139
+ return null;
140
+ }
141
+ }
142
+ }
143
+
144
+ return current ?? null;
145
+ }
146
+
147
+ /**
148
+ * Apply a partial patch to a FieldConfig located at the given path.
149
+ * Returns a new FormSchema (does not mutate the original).
150
+ */
151
+ /**
152
+ * Known top-level keys of FieldConfig.
153
+ * Used to strip stale group-name keys (e.g. textConstraintsTextConfig) that
154
+ * may have been introduced by previous incorrect patch operations.
155
+ */
156
+ private readonly _VALID_FIELD_KEYS = new Set([
157
+ 'name', 'type', 'subType', 'label', 'placeholder', 'hint',
158
+ 'required', 'disabled', 'readonly', 'isEnabled', 'visible',
159
+ 'colSpan', 'defaultValue', 'prefix', 'suffix', 'className',
160
+ 'payloadPath', 'errorMessage', 'visibilityExpression', 'onValidate',
161
+ 'children', 'sectionConfig', 'rows',
162
+ 'textConfig', 'emailConfig', 'phoneConfig', 'numberConfig',
163
+ 'dateConfig', 'optionConfig', 'attachmentConfig', 'richTextConfig',
164
+ 'ratingConfig', 'locationConfig', 'generatedConfig', 'rangeConfig',
165
+ ]);
166
+
167
+ applyFieldPatchToSchema(
168
+ path: number[],
169
+ patch: Partial<FieldConfig>,
170
+ originalSchema: FormSchema,
171
+ ): FormSchema {
172
+ const schema: FormSchema = JSON.parse(JSON.stringify(originalSchema));
173
+ const field = this.findFieldByPath(schema, path) as Record<string, any> | null;
174
+ if (field) {
175
+ // 1. Strip stale keys that don't belong to FieldConfig (e.g. old group names)
176
+ for (const key of Object.keys(field)) {
177
+ if (!this._VALID_FIELD_KEYS.has(key)) {
178
+ delete field[key];
179
+ }
180
+ }
181
+ // 2. Deep-merge the new patch on top of the cleaned field
182
+ FormSchemaTreeService.deepMerge(field, patch as any);
183
+ // 3. Clean up empty-string / null leaves from nested config objects.
184
+ // e.g. user clears minDate → dateConfig.minDate becomes "" → remove it.
185
+ // If the entire config object ends up empty → remove the key.
186
+ FormSchemaTreeService.cleanEmptyConfigValues(field);
187
+ }
188
+ return schema;
189
+ }
190
+
191
+ /**
192
+ * Remove empty-string and null values from all typed config sub-objects on a field.
193
+ * If all values in a config sub-object are empty, the key is deleted entirely.
194
+ */
195
+ public static cleanEmptyConfigValues(field: Record<string, any>): void {
196
+ const CONFIG_KEYS = [
197
+ 'textConfig', 'emailConfig', 'phoneConfig', 'numberConfig',
198
+ 'dateConfig', 'optionConfig', 'attachmentConfig', 'richTextConfig',
199
+ 'ratingConfig', 'locationConfig', 'generatedConfig', 'rangeConfig',
200
+ ];
201
+ for (const key of CONFIG_KEYS) {
202
+ if (field[key] && typeof field[key] === 'object' && !Array.isArray(field[key])) {
203
+ field[key] = FormSchemaTreeService._removeEmptyLeaves(field[key]);
204
+ if (Object.keys(field[key]).length === 0) {
205
+ delete field[key];
206
+ }
207
+ }
208
+ }
209
+ }
210
+
211
+ /**
212
+ * Recursively strip empty-string, null, and undefined values from a plain object.
213
+ * Nested objects that become empty after stripping are also removed.
214
+ */
215
+ private static _removeEmptyLeaves(obj: Record<string, any>): Record<string, any> {
216
+ const result: Record<string, any> = {};
217
+ for (const [key, val] of Object.entries(obj)) {
218
+ if (val === '' || val === null || val === undefined) continue;
219
+ if (typeof val === 'object' && !Array.isArray(val)) {
220
+ const nested = FormSchemaTreeService._removeEmptyLeaves(val);
221
+ if (Object.keys(nested).length > 0) result[key] = nested;
222
+ } else {
223
+ result[key] = val;
224
+ }
225
+ }
226
+ return result;
227
+ }
228
+
229
+ /**
230
+ * Recursively merge `source` into `target` in-place.
231
+ * Objects are merged key-by-key; primitives/arrays replace the existing value.
232
+ */
233
+ public static deepMerge(target: Record<string, any>, source: Record<string, any>): void {
234
+ for (const key of Object.keys(source)) {
235
+ const srcVal = source[key];
236
+ const tgtVal = target[key];
237
+ if (
238
+ srcVal !== null &&
239
+ typeof srcVal === 'object' &&
240
+ !Array.isArray(srcVal) &&
241
+ tgtVal !== null &&
242
+ typeof tgtVal === 'object' &&
243
+ !Array.isArray(tgtVal)
244
+ ) {
245
+ // Both sides are plain objects — merge recursively
246
+ this.deepMerge(tgtVal, srcVal);
247
+ } else {
248
+ target[key] = srcVal;
249
+ }
250
+ }
251
+ }
252
+
253
+ /**
254
+ * Convert a flat object with dot-notation keys into a nested object.
255
+ * E.g. { 'a.b.c': 1 } -> { a: { b: { c: 1 } } }
256
+ */
257
+ public static inflatePatch(flatPatch: Record<string, any>): Record<string, any> {
258
+ const nested: Record<string, any> = {};
259
+
260
+ for (const [key, value] of Object.entries(flatPatch)) {
261
+ const parts = key.split('.');
262
+ let current = nested;
263
+
264
+ for (let i = 0; i < parts.length - 1; i++) {
265
+ const part = parts[i];
266
+ if (!(part in current) || typeof current[part] !== 'object' || current[part] === null) {
267
+ current[part] = {};
268
+ }
269
+ current = current[part];
270
+ }
271
+
272
+ current[parts[parts.length - 1]] = value;
273
+ }
274
+
275
+ return nested;
276
+ }
277
+
278
+ /**
279
+ * Convert a nested object into a flat object with dot-notation keys.
280
+ * E.g. { a: { b: { c: 1 } } } -> { 'a.b.c': 1 }
281
+ */
282
+ public static flattenObject(obj: Record<string, any>, prefix = ''): Record<string, any> {
283
+ const flat: Record<string, any> = {};
284
+
285
+ for (const [key, value] of Object.entries(obj)) {
286
+ const newKey = prefix ? `${prefix}.${key}` : key;
287
+ if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
288
+ Object.assign(flat, this.flattenObject(value, newKey));
289
+ } else {
290
+ flat[newKey] = value;
291
+ }
292
+ }
293
+
294
+ return flat;
295
+ }
296
+
297
+ // ────────────────────────────────────────────────────────────────────────────
298
+ // FIELD TYPE MAPPING (FormSchema type → builder field type)
299
+ // ────────────────────────────────────────────────────────────────────────────
300
+
301
+ /**
302
+ * Map a FormSchema FieldConfig.type to the builder fieldType key
303
+ * used in field-type-schema.map.ts for config UI generation.
304
+ */
305
+ mapFieldTypeToBuilderType(type: string, subType?: string): string {
306
+ const typeMap: Record<string, string> = {
307
+ 'TEXT_INPUT': 'shortText',
308
+ 'TEXT': 'shortText',
309
+ 'TEXTAREA': 'longText',
310
+ 'RICH_TEXT': 'richText',
311
+ 'NUMBER_INPUT': 'number',
312
+ 'NUMBER': 'number',
313
+ 'DATE': 'date',
314
+ 'TIME': 'time',
315
+ 'DROPDOWN': 'dropdown',
316
+ 'SELECT': 'dropdown',
317
+ 'MULTI_SELECT': 'multiSelect',
318
+ 'AUTOCOMPLETE': 'autocomplete',
319
+ 'RADIO': 'radio',
320
+ 'CHECKBOX': 'checkbox',
321
+ 'SWITCH': 'toggle',
322
+ 'FILE_UPLOAD': 'file',
323
+ 'RATING': 'rating',
324
+ 'GENERATED': 'generated',
325
+ 'CHIP': 'chip',
326
+ 'LOCATION': 'location',
327
+ 'GROUP': 'group',
328
+ 'SUBFIELDS': 'section',
329
+ };
330
+
331
+ // Refinement by subType
332
+ if (type === 'TEXT_INPUT' && subType) {
333
+ switch (subType) {
334
+ case 'LONG': return 'longText';
335
+ case 'EMAIL': return 'email';
336
+ case 'PHONE': return 'phone';
337
+ case 'PASSWORD': return 'password';
338
+ default: return 'shortText';
339
+ }
340
+ }
341
+
342
+ if (type === 'NUMBER_INPUT') {
343
+ if (subType === 'DECIMAL') return 'decimal';
344
+ return 'number';
345
+ }
346
+
347
+ if (type === 'DATE') {
348
+ if (subType === 'DATETIME') return 'dateTime';
349
+ return 'date';
350
+ }
351
+
352
+ if (type === 'CHECKBOX' && subType === 'LIST') {
353
+ return 'multiCheckbox';
354
+ }
355
+
356
+ return typeMap[type] ?? 'shortText';
357
+ }
358
+
359
+ // ────────────────────────────────────────────────────────────────────────────
360
+ // PRIVATE — Selection tree builders
361
+ // ────────────────────────────────────────────────────────────────────────────
362
+
363
+ private _buildGroupNode(groupField: FieldConfig): SelectionGroupNode {
364
+ const sections: SelectionSectionNode[] = [];
365
+
366
+ if (groupField.sectionConfig?.children) {
367
+ const built = this._buildSectionsFromChildren(groupField.sectionConfig.children);
368
+ sections.push(...built);
369
+ }
370
+
371
+ return {
372
+ label: groupField.sectionConfig?.label || groupField.label || groupField.name || 'Group',
373
+ groupFieldConfig: groupField,
374
+ enabled: groupField.isEnabled ?? groupField.visible ?? true,
375
+ expanded: true, // Collapse by default for better UX - user can expand as needed
376
+ sections,
377
+ };
378
+ }
379
+
380
+ /**
381
+ * Build sections from a children array. Fields that are GROUP/SUBFIELDS
382
+ * become sections; other leaf fields are collected into a "default" section.
383
+ */
384
+ private _buildSectionsFromChildren(children: FieldConfig[]): SelectionSectionNode[] {
385
+ const sections: SelectionSectionNode[] = [];
386
+ let leafFields: SelectionFieldNode[] = [];
387
+
388
+ for (const child of children) {
389
+ if (child.type === 'GROUP' || (child.type === 'SUBFIELDS') || child.sectionConfig) {
390
+ // Flush any accumulated leaf fields into a default section
391
+ if (leafFields.length > 0) {
392
+ sections.push(this._createDefaultSection(leafFields));
393
+ leafFields = [];
394
+ }
395
+ sections.push(this._buildSectionNode(child));
396
+ } else if (child.type === 'ROW' && child.children?.length) {
397
+ // ROW children are leaf fields
398
+ for (const rowChild of child.children) {
399
+ leafFields.push(this._buildFieldNode(rowChild));
400
+ }
401
+ } else {
402
+ leafFields.push(this._buildFieldNode(child));
403
+ }
404
+ }
405
+
406
+ // Flush remaining leaf fields
407
+ if (leafFields.length > 0) {
408
+ sections.push(this._createDefaultSection(leafFields));
409
+ }
410
+
411
+ return sections;
412
+ }
413
+
414
+ private _buildSectionNode(field: FieldConfig): SelectionSectionNode {
415
+ const subsections: SelectionSectionNode[] = [];
416
+ const fields: SelectionFieldNode[] = [];
417
+
418
+ const children = field.sectionConfig?.children ?? field.children ?? [];
419
+ for (const child of children) {
420
+ if (child.type === 'GROUP' || child.sectionConfig) {
421
+ subsections.push(this._buildSectionNode(child));
422
+ } else if (child.type === 'ROW' && child.children?.length) {
423
+ for (const rowChild of child.children) {
424
+ fields.push(this._buildFieldNode(rowChild));
425
+ }
426
+ } else {
427
+ fields.push(this._buildFieldNode(child));
428
+ }
429
+ }
430
+
431
+ const isVisible = field.isEnabled ?? field.visible ?? true;
432
+ return {
433
+ label: field.sectionConfig?.label || field.label || field.name || 'Section',
434
+ sectionConfig: field.sectionConfig ?? null,
435
+ parentFieldConfig: field,
436
+ enabled: isVisible && field.disabled !== true,
437
+ expanded: false, // Collapse by default for better UX - user can expand as needed
438
+ fields,
439
+ subsections,
440
+ };
441
+ }
442
+
443
+ private _createDefaultSection(fields: SelectionFieldNode[]): SelectionSectionNode {
444
+ return {
445
+ label: 'Fields',
446
+ sectionConfig: null,
447
+ parentFieldConfig: null,
448
+ enabled: true,
449
+ expanded: false, // Collapse by default for better UX - user can expand as needed
450
+ fields,
451
+ subsections: [],
452
+ };
453
+ }
454
+
455
+ private _buildFieldNode(field: FieldConfig): SelectionFieldNode {
456
+ return {
457
+ fieldConfig: field,
458
+ selected: field.isEnabled ?? field.visible ?? true,
459
+ isLocked: field.readonly === true,
460
+ };
461
+ }
462
+
463
+ // ────────────────────────────────────────────────────────────────────────────
464
+ // PRIVATE — Apply selection back to FormSchema children
465
+ // ────────────────────────────────────────────────────────────────────────────
466
+
467
+ private _applySectionsToChildren(
468
+ sections: SelectionSectionNode[],
469
+ children: FieldConfig[],
470
+ ): void {
471
+ // Only sections built from an actual GROUP/section field (parentFieldConfig set) map
472
+ // 1:1 to a GROUP-typed schema child here. _buildSectionsFromChildren also inserts
473
+ // "default" leaf-bundle sections (parentFieldConfig: null) for stray top-level fields
474
+ // that aren't wrapped in a GROUP — those don't correspond to any schema child in this
475
+ // loop at all (their fields are applied via _findSelectionField below instead). Without
476
+ // filtering them out, a single leading/interleaved leaf field shifts `sectionIdx` out of
477
+ // alignment, silently applying one section's enabled/disabled state to the WRONG group
478
+ // (e.g. toggling "Languages" off ends up disabling "Address Details" instead).
479
+ const structuralSections = sections.filter(s => !!s.parentFieldConfig);
480
+ let sectionIdx = 0;
481
+ let fieldIdx = 0;
482
+
483
+ for (const child of children) {
484
+ if (child.type === 'GROUP' || child.sectionConfig) {
485
+ if (sectionIdx < structuralSections.length) {
486
+ const section = structuralSections[sectionIdx];
487
+ child.isEnabled = section.enabled;
488
+ child.visible = section.enabled; // Backward compatibility
489
+ child.disabled = !section.enabled;
490
+ const innerChildren = child.sectionConfig?.children ?? child.children ?? [];
491
+ if (innerChildren.length > 0) {
492
+ this._applySectionsToChildren(section.subsections, innerChildren);
493
+ // Also apply field selections
494
+ this._applyFieldsToChildren(section.fields, innerChildren);
495
+ }
496
+ sectionIdx++;
497
+ }
498
+ } else if (child.type === 'ROW' && child.children?.length) {
499
+ for (const rowChild of child.children) {
500
+ const matchedField = this._findSelectionField(sections, rowChild, fieldIdx);
501
+ if (matchedField) {
502
+ rowChild.isEnabled = matchedField.selected;
503
+ rowChild.visible = matchedField.selected; // Backward compatibility
504
+ fieldIdx++;
505
+ }
506
+ }
507
+ } else {
508
+ const matchedField = this._findSelectionField(sections, child, fieldIdx);
509
+ if (matchedField) {
510
+ child.isEnabled = matchedField.selected;
511
+ child.visible = matchedField.selected; // Backward compatibility
512
+ fieldIdx++;
513
+ }
514
+ }
515
+ }
516
+ }
517
+
518
+ private _applyFieldsToChildren(
519
+ selectionFields: SelectionFieldNode[],
520
+ children: FieldConfig[],
521
+ ): void {
522
+ let fi = 0;
523
+ for (const child of children) {
524
+ if (child.type === 'GROUP' || child.sectionConfig) continue;
525
+ if (child.type === 'ROW' && child.children?.length) {
526
+ for (const rowChild of child.children) {
527
+ if (fi < selectionFields.length) {
528
+ rowChild.isEnabled = selectionFields[fi].selected;
529
+ rowChild.visible = selectionFields[fi].selected; // Backward compatibility
530
+ fi++;
531
+ }
532
+ }
533
+ } else {
534
+ if (fi < selectionFields.length) {
535
+ child.isEnabled = selectionFields[fi].selected;
536
+ child.visible = selectionFields[fi].selected; // Backward compatibility
537
+ fi++;
538
+ }
539
+ }
540
+ }
541
+ }
542
+
543
+ private _findSelectionField(
544
+ sections: SelectionSectionNode[],
545
+ _field: FieldConfig,
546
+ _index: number,
547
+ ): SelectionFieldNode | null {
548
+ // Flatten all fields from all sections
549
+ for (const section of sections) {
550
+ for (const f of section.fields) {
551
+ if (f.fieldConfig === _field || f.fieldConfig.name === _field.name) {
552
+ return f;
553
+ }
554
+ }
555
+ }
556
+ return null;
557
+ }
558
+
559
+ // ────────────────────────────────────────────────────────────────────────────
560
+ // PRIVATE — Configurator tree builders
561
+ // ────────────────────────────────────────────────────────────────────────────
562
+
563
+ private _buildConfiguratorGroupNode(
564
+ groupField: FieldConfig,
565
+ basePath: number[],
566
+ ): ConfiguratorTreeNode {
567
+ const children: ConfiguratorTreeNode[] = [];
568
+ const sectionChildren = groupField.sectionConfig?.children ?? [];
569
+
570
+ sectionChildren.forEach((child, ci) => {
571
+ if (child.isEnabled === false || child.visible === false) return;
572
+ const childPath = [...basePath, ci];
573
+ if (child.type === 'GROUP' || child.type === 'SUBFIELDS' || child.subType === 'SECTION' || child.sectionConfig) {
574
+ children.push(this._buildConfiguratorSectionNode(child, childPath));
575
+ } else if (child.type === 'ROW' && child.children?.length) {
576
+ child.children.forEach((rowChild, ri) => {
577
+ if (rowChild.isEnabled !== false && rowChild.visible !== false) {
578
+ children.push(this._buildConfiguratorFieldNode(rowChild, [...childPath, ri]));
579
+ }
580
+ });
581
+ } else {
582
+ children.push(this._buildConfiguratorFieldNode(child, childPath));
583
+ }
584
+ });
585
+
586
+ return {
587
+ id: groupField.name || `group-${basePath.join('-')}`,
588
+ label: groupField.sectionConfig?.label || groupField.label || 'Group',
589
+ type: 'group',
590
+ expanded: false,
591
+ fieldConfig: groupField,
592
+ path: basePath,
593
+ children,
594
+ };
595
+ }
596
+
597
+ private _buildConfiguratorSectionNode(
598
+ field: FieldConfig,
599
+ basePath: number[],
600
+ ): ConfiguratorTreeNode {
601
+ const children: ConfiguratorTreeNode[] = [];
602
+ const sectionChildren = field.sectionConfig?.children ?? field.children ?? [];
603
+
604
+ sectionChildren.forEach((child, ci) => {
605
+ if (child.isEnabled === false || child.visible === false) return;
606
+ const childPath = [...basePath, ci];
607
+ if (child.type === 'GROUP' || child.type === 'SUBFIELDS' || child.subType === 'SECTION' || child.sectionConfig) {
608
+ children.push(this._buildConfiguratorSectionNode(child, childPath));
609
+ } else if (child.type === 'ROW' && child.children?.length) {
610
+ child.children.forEach((rowChild, ri) => {
611
+ if (rowChild.isEnabled !== false && rowChild.visible !== false) {
612
+ children.push(this._buildConfiguratorFieldNode(rowChild, [...childPath, ri]));
613
+ }
614
+ });
615
+ } else {
616
+ children.push(this._buildConfiguratorFieldNode(child, childPath));
617
+ }
618
+ });
619
+
620
+ return {
621
+ id: field.name || `section-${basePath.join('-')}`,
622
+ label: field.sectionConfig?.label || field.label || 'Section',
623
+ type: 'section',
624
+ expanded: false,
625
+ fieldConfig: field,
626
+ path: basePath,
627
+ children,
628
+ };
629
+ }
630
+
631
+ private _buildConfiguratorFieldNode(
632
+ field: FieldConfig,
633
+ path: number[],
634
+ ): ConfiguratorTreeNode {
635
+ return {
636
+ id: field.name || `field-${path.join('-')}`,
637
+ label: field.label || field.name || 'Field',
638
+ type: 'field',
639
+ expanded: false,
640
+ fieldConfig: field,
641
+ path,
642
+ children: [],
643
+ };
644
+ }
645
+
646
+ private _buildConfiguratorChildNodes(
647
+ children: FieldConfig[],
648
+ basePath: number[],
649
+ ): ConfiguratorTreeNode[] {
650
+ const nodes: ConfiguratorTreeNode[] = [];
651
+
652
+ children.forEach((child, ci) => {
653
+ if (child.isEnabled === false || child.visible === false) return;
654
+ const childPath = [...basePath, ci];
655
+ if (child.type === 'GROUP' || child.sectionConfig) {
656
+ nodes.push(this._buildConfiguratorSectionNode(child, childPath));
657
+ } else if (child.type === 'ROW' && child.children?.length) {
658
+ child.children.forEach((rowChild, ri) => {
659
+ if (rowChild.isEnabled !== false && rowChild.visible !== false) {
660
+ nodes.push(this._buildConfiguratorFieldNode(rowChild, [...childPath, ri]));
661
+ }
662
+ });
663
+ } else {
664
+ nodes.push(this._buildConfiguratorFieldNode(child, childPath));
665
+ }
666
+ });
667
+
668
+ return nodes;
669
+ }
670
+ }
@@ -0,0 +1,10 @@
1
+ import { InjectionToken } from '@angular/core';
2
+ import { FieldTypeSchemaMap } from '../models/builder.models';
3
+
4
+ /**
5
+ * Optional token to override the field-type → config-schema mapping
6
+ * used by the standalone FieldConfiguratorComponent.
7
+ */
8
+ export const BUILDER_FIELD_TYPE_SCHEMA_MAP = new InjectionToken<FieldTypeSchemaMap>(
9
+ 'BUILDER_FIELD_TYPE_SCHEMA_MAP'
10
+ );