commons-shared-web-ui 0.0.61 → 0.0.62

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 (223) 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 +2819 -0
  17. package/documentation/smart-table.md +1297 -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 +92 -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/components/button-dropdown/button-dropdown.component.html +41 -0
  38. package/src/lib/modules/button-dropdown/components/button-dropdown/button-dropdown.component.scss +135 -0
  39. package/src/lib/modules/button-dropdown/components/button-dropdown/button-dropdown.component.ts +160 -0
  40. package/src/lib/modules/configurable-form/component/configurable-form.component.html +294 -0
  41. package/src/lib/modules/configurable-form/component/configurable-form.component.scss +503 -0
  42. package/src/lib/modules/configurable-form/component/configurable-form.component.ts +628 -0
  43. package/src/lib/modules/configurable-form/configurable-form.examples.ts +154 -0
  44. package/src/lib/modules/configurable-form/configurable-form.model.ts +131 -0
  45. package/src/lib/modules/configurable-form/configurable-form.module.ts +19 -0
  46. package/src/lib/modules/confirmation-modal/components/confirmation-modal/confirmation-modal.component.html +77 -0
  47. package/src/lib/modules/confirmation-modal/components/confirmation-modal/confirmation-modal.component.scss +395 -0
  48. package/src/lib/modules/confirmation-modal/components/confirmation-modal/confirmation-modal.component.ts +266 -0
  49. package/src/lib/modules/confirmation-modal/confirmation-modal.models.ts +71 -0
  50. package/src/lib/modules/confirmation-modal/confirmation-modal.module.ts +20 -0
  51. package/src/lib/modules/filter/components/filter/filter.component.html +131 -0
  52. package/src/lib/modules/filter/components/filter/filter.component.scss +245 -0
  53. package/src/lib/modules/filter/components/filter/filter.component.ts +216 -0
  54. package/src/lib/modules/filter/filter.models.ts +88 -0
  55. package/src/lib/modules/filter/filter.module.ts +24 -0
  56. package/src/lib/modules/filter-sidebar/components/filter-sidebar/filter-sidebar.component.html +112 -0
  57. package/src/lib/modules/filter-sidebar/components/filter-sidebar/filter-sidebar.component.scss +186 -0
  58. package/src/lib/modules/filter-sidebar/components/filter-sidebar/filter-sidebar.component.ts +163 -0
  59. package/src/lib/modules/filter-sidebar/filter-sidebar.models.ts +95 -0
  60. package/src/lib/modules/filter-sidebar/filter-sidebar.module.ts +24 -0
  61. package/src/lib/modules/filter-table-selector/components/filter-table-selector/filter-table-selector.component.html +73 -0
  62. package/src/lib/modules/filter-table-selector/components/filter-table-selector/filter-table-selector.component.scss +321 -0
  63. package/src/lib/modules/filter-table-selector/components/filter-table-selector/filter-table-selector.component.ts +361 -0
  64. package/src/lib/modules/filter-table-selector/filter-table-selector.models.ts +91 -0
  65. package/src/lib/modules/filter-table-selector/filter-table-selector.module.ts +22 -0
  66. package/src/lib/modules/form-builder/components/field-configurator/configurator-config-panel/configurator-config-panel.component.html +63 -0
  67. package/src/lib/modules/form-builder/components/field-configurator/configurator-config-panel/configurator-config-panel.component.scss +496 -0
  68. package/src/lib/modules/form-builder/components/field-configurator/configurator-config-panel/configurator-config-panel.component.ts +445 -0
  69. package/src/lib/modules/form-builder/components/field-configurator/configurator-tree/configurator-tree.component.html +75 -0
  70. package/src/lib/modules/form-builder/components/field-configurator/configurator-tree/configurator-tree.component.scss +210 -0
  71. package/src/lib/modules/form-builder/components/field-configurator/configurator-tree/configurator-tree.component.ts +55 -0
  72. package/src/lib/modules/form-builder/components/field-configurator/field-configurator.component.html +25 -0
  73. package/src/lib/modules/form-builder/components/field-configurator/field-configurator.component.scss +82 -0
  74. package/src/lib/modules/form-builder/components/field-configurator/field-configurator.component.ts +95 -0
  75. package/src/lib/modules/form-builder/components/field-selection/field-selection.component.html +20 -0
  76. package/src/lib/modules/form-builder/components/field-selection/field-selection.component.scss +37 -0
  77. package/src/lib/modules/form-builder/components/field-selection/field-selection.component.ts +94 -0
  78. package/src/lib/modules/form-builder/components/field-selection/group-node/group-node.component.html +46 -0
  79. package/src/lib/modules/form-builder/components/field-selection/group-node/group-node.component.scss +102 -0
  80. package/src/lib/modules/form-builder/components/field-selection/group-node/group-node.component.ts +50 -0
  81. package/src/lib/modules/form-builder/components/field-selection/selection-field-node/selection-field-node.component.html +35 -0
  82. package/src/lib/modules/form-builder/components/field-selection/selection-field-node/selection-field-node.component.scss +67 -0
  83. package/src/lib/modules/form-builder/components/field-selection/selection-field-node/selection-field-node.component.ts +34 -0
  84. package/src/lib/modules/form-builder/components/field-selection/selection-section-node/selection-section-node.component.html +68 -0
  85. package/src/lib/modules/form-builder/components/field-selection/selection-section-node/selection-section-node.component.scss +113 -0
  86. package/src/lib/modules/form-builder/components/field-selection/selection-section-node/selection-section-node.component.ts +74 -0
  87. package/src/lib/modules/form-builder/configs/field-type-schema.map.ts +533 -0
  88. package/src/lib/modules/form-builder/form-builder.module.ts +36 -0
  89. package/src/lib/modules/form-builder/index.ts +9 -0
  90. package/src/lib/modules/form-builder/models/builder.models.ts +7 -0
  91. package/src/lib/modules/form-builder/models/field-configurator.models.ts +38 -0
  92. package/src/lib/modules/form-builder/models/field-selection.models.ts +51 -0
  93. package/src/lib/modules/form-builder/services/field-configurator.service.ts +258 -0
  94. package/src/lib/modules/form-builder/services/field-selection.service.ts +299 -0
  95. package/src/lib/modules/form-builder/services/form-schema-tree.service.ts +670 -0
  96. package/src/lib/modules/form-builder/tokens/builder.tokens.ts +10 -0
  97. package/src/lib/modules/form-builder/utils/constants.ts +43 -0
  98. package/src/lib/modules/form-components/components/checkbox/checkbox.component.html +29 -0
  99. package/src/lib/modules/form-components/components/checkbox/checkbox.component.scss +111 -0
  100. package/src/lib/modules/form-components/components/checkbox/checkbox.component.ts +207 -0
  101. package/src/lib/modules/form-components/components/checkbox/checkbox.models.ts +35 -0
  102. package/src/lib/modules/form-components/components/datepicker/datepicker.component.html +42 -0
  103. package/src/lib/modules/form-components/components/datepicker/datepicker.component.scss +115 -0
  104. package/src/lib/modules/form-components/components/datepicker/datepicker.component.ts +267 -0
  105. package/src/lib/modules/form-components/components/datepicker/datepicker.models.ts +45 -0
  106. package/src/lib/modules/form-components/components/dropdown/dropdown.component.html +74 -0
  107. package/src/lib/modules/form-components/components/dropdown/dropdown.component.scss +252 -0
  108. package/src/lib/modules/form-components/components/dropdown/dropdown.component.ts +377 -0
  109. package/src/lib/modules/form-components/components/dropdown/dropdown.models.ts +53 -0
  110. package/src/lib/modules/form-components/components/input/input.component.html +51 -0
  111. package/src/lib/modules/form-components/components/input/input.component.scss +128 -0
  112. package/src/lib/modules/form-components/components/input/input.component.ts +250 -0
  113. package/src/lib/modules/form-components/components/input/input.models.ts +55 -0
  114. package/src/lib/modules/form-components/components/radio/radio.component.html +22 -0
  115. package/src/lib/modules/form-components/components/radio/radio.component.scss +107 -0
  116. package/src/lib/modules/form-components/components/radio/radio.component.ts +181 -0
  117. package/src/lib/modules/form-components/components/radio/radio.models.ts +39 -0
  118. package/src/lib/modules/form-components/components/search/search.component.html +15 -0
  119. package/src/lib/modules/form-components/components/search/search.component.scss +87 -0
  120. package/src/lib/modules/form-components/components/search/search.component.ts +213 -0
  121. package/src/lib/modules/form-components/components/search/search.models.ts +40 -0
  122. package/src/lib/modules/form-components/components/toggle/toggle.component.html +15 -0
  123. package/src/lib/modules/form-components/components/toggle/toggle.component.scss +81 -0
  124. package/src/lib/modules/form-components/components/toggle/toggle.component.ts +166 -0
  125. package/src/lib/modules/form-components/components/toggle/toggle.models.ts +27 -0
  126. package/src/lib/modules/form-components/directives/click-outside.directive.ts +22 -0
  127. package/src/lib/modules/form-components/form-components.module.ts +42 -0
  128. package/src/lib/modules/form-field-configuration/components/_ffc-controls.scss +89 -0
  129. package/src/lib/modules/form-field-configuration/components/config-field-node/config-field-node.component.html +68 -0
  130. package/src/lib/modules/form-field-configuration/components/config-field-node/config-field-node.component.scss +73 -0
  131. package/src/lib/modules/form-field-configuration/components/config-field-node/config-field-node.component.ts +34 -0
  132. package/src/lib/modules/form-field-configuration/components/config-section-node/config-section-node.component.html +78 -0
  133. package/src/lib/modules/form-field-configuration/components/config-section-node/config-section-node.component.scss +83 -0
  134. package/src/lib/modules/form-field-configuration/components/config-section-node/config-section-node.component.ts +94 -0
  135. package/src/lib/modules/form-field-configuration/components/form-field-configuration/form-field-configuration.component.html +38 -0
  136. package/src/lib/modules/form-field-configuration/components/form-field-configuration/form-field-configuration.component.scss +42 -0
  137. package/src/lib/modules/form-field-configuration/components/form-field-configuration/form-field-configuration.component.ts +92 -0
  138. package/src/lib/modules/form-field-configuration/form-field-configuration.module.ts +23 -0
  139. package/src/lib/modules/form-field-configuration/index.ts +7 -0
  140. package/src/lib/modules/form-field-configuration/models/field-configuration.models.ts +62 -0
  141. package/src/lib/modules/form-field-configuration/services/config-schema-tree.service.ts +159 -0
  142. package/src/lib/modules/form-field-configuration/services/field-configuration.service.ts +228 -0
  143. package/src/lib/modules/material/material.module.ts +94 -0
  144. package/src/lib/modules/nav/components/nav/nav.component.html +34 -0
  145. package/src/lib/modules/nav/components/nav/nav.component.scss +171 -0
  146. package/src/lib/modules/nav/components/nav/nav.component.ts +82 -0
  147. package/src/lib/modules/nav/nav.models.ts +31 -0
  148. package/src/lib/modules/nav/nav.module.ts +17 -0
  149. package/src/lib/modules/pagination/components/pagination/pagination.component.html +52 -0
  150. package/src/lib/modules/pagination/components/pagination/pagination.component.scss +155 -0
  151. package/src/lib/modules/pagination/components/pagination/pagination.component.ts +109 -0
  152. package/src/lib/modules/pagination/pagination.module.ts +17 -0
  153. package/src/lib/modules/side-nav/components/side-nav/side-nav.component.html +56 -0
  154. package/src/lib/modules/side-nav/components/side-nav/side-nav.component.scss +342 -0
  155. package/src/lib/modules/side-nav/components/side-nav/side-nav.component.ts +135 -0
  156. package/src/lib/modules/side-nav/side-nav.models.ts +38 -0
  157. package/src/lib/modules/side-nav/side-nav.module.ts +16 -0
  158. package/src/lib/modules/smart-form/components/form-field/form-field.component.html +1379 -0
  159. package/src/lib/modules/smart-form/components/form-field/form-field.component.scss +2255 -0
  160. package/src/lib/modules/smart-form/components/form-field/form-field.component.ts +3174 -0
  161. package/src/lib/modules/smart-form/components/form-section/form-section.component.html +67 -0
  162. package/src/lib/modules/smart-form/components/form-section/form-section.component.scss +209 -0
  163. package/src/lib/modules/smart-form/components/form-section/form-section.component.ts +142 -0
  164. package/src/lib/modules/smart-form/components/smart-form/smart-form.component.html +253 -0
  165. package/src/lib/modules/smart-form/components/smart-form/smart-form.component.scss +689 -0
  166. package/src/lib/modules/smart-form/components/smart-form/smart-form.component.ts +1134 -0
  167. package/src/lib/modules/smart-form/index.ts +10 -0
  168. package/src/lib/modules/smart-form/models/form-schema.model.ts +803 -0
  169. package/src/lib/modules/smart-form/models/hierarchy-config.model.ts +22 -0
  170. package/src/lib/modules/smart-form/services/expression.service.ts +75 -0
  171. package/src/lib/modules/smart-form/services/smart-form-controller.service.ts +67 -0
  172. package/src/lib/modules/smart-form/smart-form.examples.ts +1324 -0
  173. package/src/lib/modules/smart-form/smart-form.module.ts +40 -0
  174. package/src/lib/modules/smart-form/smart-form.theme.scss +1 -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 +1219 -0
  181. package/src/lib/modules/smart-table/models/table-config.model.ts +247 -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/shared-ui.module.ts +55 -0
  215. package/src/lib/utils/constants.ts +11 -0
  216. package/src/lib/utils/storage.utils.ts +37 -0
  217. package/src/lib/utils/string.utils.ts +23 -0
  218. package/src/lib/utils/translation.utils.ts +87 -0
  219. package/src/public-api.ts +127 -0
  220. package/tsconfig.lib.json +15 -0
  221. package/fesm2022/commons-shared-web-ui.mjs +0 -16097
  222. package/fesm2022/commons-shared-web-ui.mjs.map +0 -1
  223. package/index.d.ts +0 -5022
@@ -0,0 +1,51 @@
1
+ import { FieldConfig, SectionConfig } from '../../smart-form/models/form-schema.model';
2
+
3
+ /**
4
+ * Wraps a FieldConfig leaf with selection metadata for the UI tree.
5
+ */
6
+ export interface SelectionFieldNode {
7
+ /** Reference back to the original FieldConfig */
8
+ fieldConfig: FieldConfig;
9
+ /** Whether this field is selected (checkbox state) */
10
+ selected: boolean;
11
+ /** If true, the field cannot be deselected by the user */
12
+ isLocked: boolean;
13
+ }
14
+
15
+ /**
16
+ * Wraps a section (SUBFIELDS / nested sectionConfig) with toggle + tree metadata.
17
+ * Sections can recursively contain child sections.
18
+ */
19
+ export interface SelectionSectionNode {
20
+ /** Section label */
21
+ label: string;
22
+ /** The original sectionConfig reference (if applicable) */
23
+ sectionConfig: SectionConfig | null;
24
+ /** The parent FieldConfig that owns this sectionConfig (type GROUP/SUBFIELDS) */
25
+ parentFieldConfig: FieldConfig | null;
26
+ /** Whether the section toggle is ON */
27
+ enabled: boolean;
28
+ /** Whether the section is visually expanded in the tree */
29
+ expanded: boolean;
30
+ /** Leaf fields inside this section */
31
+ fields: SelectionFieldNode[];
32
+ /** Recursive child sections */
33
+ subsections: SelectionSectionNode[];
34
+ }
35
+
36
+ /**
37
+ * Top-level group node (maps to a GROUP field in stepperConfig.children
38
+ * or a top-level sectionConfig in a SECTION form).
39
+ */
40
+ export interface SelectionGroupNode {
41
+ /** Group label */
42
+ label: string;
43
+ /** The original FieldConfig of type GROUP (stepper forms) or null (section form) */
44
+ groupFieldConfig: FieldConfig | null;
45
+ /** Whether the entire group toggle is ON */
46
+ enabled: boolean;
47
+ /** Whether the group is visually expanded */
48
+ expanded: boolean;
49
+ /** Sections inside this group */
50
+ sections: SelectionSectionNode[];
51
+ }
@@ -0,0 +1,258 @@
1
+ import { Injectable, computed, signal } from '@angular/core';
2
+ import { FormSchema, FieldConfig } from '../../smart-form/models/form-schema.model';
3
+ import {
4
+ ConfiguratorTreeNode,
5
+ ConfiguratorFieldInfo,
6
+ } from '../models/field-configurator.models';
7
+ import { FormSchemaTreeService } from '../services/form-schema-tree.service';
8
+
9
+ interface FieldConfiguratorState {
10
+ tree: ConfiguratorTreeNode[];
11
+ selectedFieldPath: number[] | null;
12
+ schema: FormSchema | null;
13
+ }
14
+
15
+ /**
16
+ * Signal-based service for the field-configurator component.
17
+ * Manages the tree + selected field + config panel state.
18
+ */
19
+ @Injectable()
20
+ export class FieldConfiguratorService {
21
+ private readonly _state = signal<FieldConfiguratorState>({
22
+ tree: [],
23
+ selectedFieldPath: null,
24
+ schema: null,
25
+ });
26
+
27
+ private readonly treeService = new FormSchemaTreeService();
28
+
29
+ // ── Computed slices ─────────────────────────────────────────────
30
+ readonly tree = computed(() => this._state().tree);
31
+ readonly schema = computed(() => this._state().schema);
32
+ readonly selectedFieldPath = computed(() => this._state().selectedFieldPath);
33
+
34
+ readonly selectedField = computed<FieldConfig | null>(() => {
35
+ const { schema, selectedFieldPath } = this._state();
36
+ if (!schema || !selectedFieldPath) return null;
37
+ return this.treeService.findFieldByPath(schema, selectedFieldPath);
38
+ });
39
+
40
+ readonly selectedFieldInfo = computed<ConfiguratorFieldInfo | null>(() => {
41
+ const field = this.selectedField();
42
+ const path = this.selectedFieldPath();
43
+ if (!field || !path) return null;
44
+
45
+ // Walk the tree to find group/section labels
46
+ const labels = this._getParentLabels(this._state().tree, path);
47
+
48
+ return {
49
+ fieldConfig: field,
50
+ groupLabel: labels.groupLabel,
51
+ sectionLabel: labels.sectionLabel,
52
+ path,
53
+ };
54
+ });
55
+
56
+ /**
57
+ * Returns the builder-compatible field type string for the currently selected field.
58
+ * Used to look up the config schema from field-type-schema.map.ts.
59
+ */
60
+ readonly selectedFieldBuilderType = computed<string | null>(() => {
61
+ const field = this.selectedField();
62
+ if (!field) return null;
63
+ return this.treeService.mapFieldTypeToBuilderType(field.type, field.subType);
64
+ });
65
+
66
+ // ── Mutators ────────────────────────────────────────────────────
67
+
68
+ /**
69
+ * Initialize the store from a FormSchema.
70
+ */
71
+ loadSchema(schema: FormSchema, preserveSelection = false): void {
72
+ // If it's the exact same object reference and we are not forcing a reset, skip.
73
+ if (this._state().schema === schema && preserveSelection) return;
74
+
75
+ const tree = this.treeService.parseSchemaToConfiguratorTree(schema);
76
+
77
+ this._state.update(s => {
78
+ // If preserveSelection is truthy, we definitely keep it.
79
+ // Otherwise, we still try to keep it IF the path is still valid in the new schema.
80
+ let newPath = s.selectedFieldPath;
81
+ if (!preserveSelection && newPath) {
82
+ const stillExists = !!this.treeService.findFieldByPath(schema, newPath);
83
+ if (!stillExists) newPath = null;
84
+ }
85
+
86
+ return {
87
+ ...s,
88
+ tree,
89
+ selectedFieldPath: newPath,
90
+ schema: JSON.parse(JSON.stringify(schema)),
91
+ };
92
+ });
93
+ }
94
+
95
+ /**
96
+ * Select a field by its path. Pass null to deselect.
97
+ */
98
+ selectField(path: number[] | null): void {
99
+ this._state.update(s => ({ ...s, selectedFieldPath: path }));
100
+ }
101
+
102
+ /**
103
+ * Toggle a tree node's expanded state.
104
+ */
105
+ toggleNodeExpanded(nodeId: string): void {
106
+ this._state.update(s => ({
107
+ ...s,
108
+ tree: this._toggleExpanded(s.tree, nodeId),
109
+ }));
110
+ }
111
+
112
+ /**
113
+ * Apply a config patch to the currently selected field.
114
+ * Rebuilds the schema and tree nodes immutably.
115
+ */
116
+ patchSelectedField(patch: Partial<FieldConfig>): void {
117
+ const { schema, selectedFieldPath, tree } = this._state();
118
+ if (!schema || !selectedFieldPath) return;
119
+
120
+ const newSchema = this.treeService.applyFieldPatchToSchema(
121
+ selectedFieldPath,
122
+ patch,
123
+ schema,
124
+ );
125
+
126
+ // Rebuild the tree to reflect label changes etc.
127
+ const newTree = this.treeService.parseSchemaToConfiguratorTree(newSchema);
128
+ // Preserve expanded states from the old tree
129
+ this._restoreExpandedStates(tree, newTree);
130
+
131
+ this._state.set({
132
+ schema: newSchema,
133
+ selectedFieldPath,
134
+ tree: newTree,
135
+ });
136
+ }
137
+
138
+ /**
139
+ * Change the type (and optionally subType) of the currently selected field.
140
+ * Mapping goes from builderType keys (e.g. 'richText') → {type, subType?}.
141
+ * DROPDOWN fields cannot be changed and are guarded here as well.
142
+ */
143
+ changeFieldType(builderType: string): void {
144
+ const currentField = this.selectedField();
145
+ if (!currentField) return;
146
+ // Guard: never change a DROPDOWN-based field
147
+ const t = currentField.type?.toUpperCase();
148
+ if (t === 'DROPDOWN' || t === 'MULTI_SELECT') return;
149
+
150
+ const TYPE_MAP: Record<string, { type: string; subType?: string }> = {
151
+ shortText: { type: 'TEXT_INPUT' },
152
+ longText: { type: 'TEXT_INPUT', subType: 'LONG' },
153
+ email: { type: 'TEXT_INPUT', subType: 'EMAIL' },
154
+ phone: { type: 'TEXT_INPUT', subType: 'PHONE' },
155
+ password: { type: 'TEXT_INPUT', subType: 'PASSWORD' },
156
+ number: { type: 'NUMBER_INPUT', subType: 'INTEGER' },
157
+ decimal: { type: 'NUMBER_INPUT', subType: 'DECIMAL' },
158
+ date: { type: 'DATE', subType: 'SINGLE' },
159
+ time: { type: 'TIME', subType: 'SINGLE' },
160
+ dateTime: { type: 'DATE', subType: 'DATETIME' },
161
+ checkbox: { type: 'CHECKBOX', subType: 'BOOL' },
162
+ multiCheckbox: { type: 'CHECKBOX', subType: 'LIST' },
163
+ radio: { type: 'RADIO', subType: 'SINGLE' },
164
+ toggle: { type: 'SWITCH', subType: 'BOOL' },
165
+ chip: { type: 'CHIP', subType: 'MULTIPLE' },
166
+ richText: { type: 'RICH_TEXT' },
167
+ file: { type: 'FILE_UPLOAD', subType: 'SINGLE' },
168
+ rating: { type: 'RATING', subType: 'STAR' },
169
+ location: { type: 'LOCATION' },
170
+ generated: { type: 'GENERATED', subType: 'FORMULA' },
171
+ multiSelect: { type: 'MULTI_SELECT' },
172
+ autocomplete: { type: 'AUTOCOMPLETE', subType: 'SINGLE' },
173
+ dropdown: { type: 'DROPDOWN', subType: 'SINGLE' },
174
+ multiDropdown: { type: 'MULTI_SELECT' },
175
+ };
176
+
177
+ const mapping = TYPE_MAP[builderType];
178
+ if (!mapping) return;
179
+
180
+ const patch: Partial<FieldConfig> = { type: mapping.type };
181
+ if (mapping.subType !== undefined) {
182
+ patch.subType = mapping.subType;
183
+ } else {
184
+ // Clear stale subType from previous field type
185
+ patch.subType = undefined;
186
+ }
187
+ this.patchSelectedField(patch);
188
+ }
189
+
190
+ /**
191
+ * Get the current schema (with all applied modifications).
192
+ */
193
+ getCurrentSchema(): FormSchema | null {
194
+ return this._state().schema;
195
+ }
196
+
197
+ // ── Private helpers ─────────────────────────────────────────────
198
+
199
+ private _toggleExpanded(nodes: ConfiguratorTreeNode[], nodeId: string): ConfiguratorTreeNode[] {
200
+ return nodes.map(n => {
201
+ if (n.id === nodeId) {
202
+ return { ...n, expanded: !n.expanded };
203
+ }
204
+ if (n.children.length > 0) {
205
+ return { ...n, children: this._toggleExpanded(n.children, nodeId) };
206
+ }
207
+ return n;
208
+ });
209
+ }
210
+
211
+ private _restoreExpandedStates(
212
+ oldNodes: ConfiguratorTreeNode[],
213
+ newNodes: ConfiguratorTreeNode[],
214
+ ): void {
215
+ for (const newNode of newNodes) {
216
+ const oldNode = oldNodes.find(o => o.id === newNode.id);
217
+ if (oldNode) {
218
+ newNode.expanded = oldNode.expanded;
219
+ if (oldNode.children.length > 0 && newNode.children.length > 0) {
220
+ this._restoreExpandedStates(oldNode.children, newNode.children);
221
+ }
222
+ }
223
+ }
224
+ }
225
+
226
+ private _getParentLabels(
227
+ nodes: ConfiguratorTreeNode[],
228
+ path: number[],
229
+ ): { groupLabel: string; sectionLabel: string } {
230
+ let groupLabel = '';
231
+ let sectionLabel = '';
232
+
233
+ const findInNodes = (nodeList: ConfiguratorTreeNode[], targetPath: number[]): boolean => {
234
+ for (const node of nodeList) {
235
+ if (this._pathsEqual(node.path, targetPath)) {
236
+ return true;
237
+ }
238
+ if (node.children.length > 0) {
239
+ const found = findInNodes(node.children, targetPath);
240
+ if (found) {
241
+ if (node.type === 'group') groupLabel = node.label;
242
+ if (node.type === 'section') sectionLabel = node.label;
243
+ return true;
244
+ }
245
+ }
246
+ }
247
+ return false;
248
+ };
249
+
250
+ findInNodes(nodes, path);
251
+ return { groupLabel, sectionLabel };
252
+ }
253
+
254
+ private _pathsEqual(a: number[], b: number[]): boolean {
255
+ if (a.length !== b.length) return false;
256
+ return a.every((val, i) => val === b[i]);
257
+ }
258
+ }
@@ -0,0 +1,299 @@
1
+ import { Injectable, computed, signal } from '@angular/core';
2
+ import { FormSchema } from '../../smart-form/models/form-schema.model';
3
+ import {
4
+ SelectionGroupNode,
5
+ SelectionSectionNode,
6
+ SelectionFieldNode,
7
+ } from '../models/field-selection.models';
8
+ import { FormSchemaTreeService } from '../services/form-schema-tree.service';
9
+
10
+ interface FieldSelectionState {
11
+ groups: SelectionGroupNode[];
12
+ originalSchema: FormSchema | null;
13
+ }
14
+
15
+ /**
16
+ * Signal-based service for the field-selection component.
17
+ * Manages group/section/field enable/disable toggles.
18
+ */
19
+ @Injectable()
20
+ export class FieldSelectionService {
21
+ private readonly _state = signal<FieldSelectionState>({
22
+ groups: [],
23
+ originalSchema: null,
24
+ });
25
+
26
+ private readonly treeService = new FormSchemaTreeService();
27
+
28
+ // ── Computed slices ─────────────────────────────────────────────
29
+ readonly groups = computed(() => this._state().groups);
30
+ readonly originalSchema = computed(() => this._state().originalSchema);
31
+
32
+ readonly totalFieldCount = computed(() => {
33
+ let count = 0;
34
+ const countFields = (sections: SelectionSectionNode[]): void => {
35
+ for (const s of sections) {
36
+ count += s.fields.length;
37
+ countFields(s.subsections);
38
+ }
39
+ };
40
+ for (const g of this._state().groups) {
41
+ countFields(g.sections);
42
+ }
43
+ return count;
44
+ });
45
+
46
+ readonly selectedFieldCount = computed(() => {
47
+ let count = 0;
48
+ const countSelected = (sections: SelectionSectionNode[]): void => {
49
+ for (const s of sections) {
50
+ if (!s.enabled) continue;
51
+ count += s.fields.filter(f => f.selected).length;
52
+ countSelected(s.subsections);
53
+ }
54
+ };
55
+ for (const g of this._state().groups) {
56
+ if (!g.enabled) continue;
57
+ countSelected(g.sections);
58
+ }
59
+ return count;
60
+ });
61
+
62
+ // ── Mutators ────────────────────────────────────────────────────
63
+
64
+ /**
65
+ * Initialize the store from a FormSchema.
66
+ */
67
+ loadSchema(schema: FormSchema): void {
68
+ const groups = this.treeService.parseSchemaToSelectionGroups(schema);
69
+ this._state.set({ groups, originalSchema: schema });
70
+ }
71
+
72
+ /**
73
+ * Load a schema while preserving the expanded/collapsed state of sections and groups.
74
+ * Used when the schema structure hasn't changed but field selections have.
75
+ * This prevents the UI from unexpectedly collapsing sections when the user toggles a field.
76
+ */
77
+ loadSchemaPreservingExpanded(schema: FormSchema): void {
78
+ const oldGroups = this._state().groups;
79
+ const newGroups = this.treeService.parseSchemaToSelectionGroups(schema);
80
+
81
+ // Merge old expansion states into new groups
82
+ const mergedGroups = newGroups.map((newGroup, gi) => ({
83
+ ...newGroup,
84
+ expanded: oldGroups[gi]?.expanded ?? newGroup.expanded,
85
+ sections: this._mergeExpandedSections(newGroup.sections, oldGroups[gi]?.sections ?? []),
86
+ }));
87
+
88
+ this._state.set({ groups: mergedGroups, originalSchema: schema });
89
+ }
90
+
91
+ /**
92
+ * Toggle a group's enabled state. Disabling cascades to all sections + fields.
93
+ * When enabling, all fields are selected (unless locked).
94
+ */
95
+ toggleGroup(groupIndex: number): void {
96
+ this._state.update(s => ({
97
+ ...s,
98
+ groups: s.groups.map((g, i) => {
99
+ if (i !== groupIndex) return g;
100
+ const enabled = !g.enabled;
101
+ return {
102
+ ...g,
103
+ enabled,
104
+ sections: enabled ? this._enableAllSections(g.sections) : this._disableAllSections(g.sections),
105
+ };
106
+ }),
107
+ }));
108
+ }
109
+
110
+ /**
111
+ * Toggle a section's enabled state.
112
+ * Disabling cascades to all child fields (and subsections).
113
+ */
114
+ toggleSection(groupIndex: number, sectionPath: number[]): void {
115
+ this._state.update(s => ({
116
+ ...s,
117
+ groups: s.groups.map((g, gi) => {
118
+ if (gi !== groupIndex) return g;
119
+ return {
120
+ ...g,
121
+ sections: this._toggleSectionAtPath(g.sections, sectionPath, 0),
122
+ };
123
+ }),
124
+ }));
125
+ }
126
+
127
+ /**
128
+ * Toggle a section's expanded (collapsed/expanded) state.
129
+ */
130
+ toggleSectionExpanded(groupIndex: number, sectionPath: number[]): void {
131
+ this._state.update(s => ({
132
+ ...s,
133
+ groups: s.groups.map((g, gi) => {
134
+ if (gi !== groupIndex) return g;
135
+ return {
136
+ ...g,
137
+ sections: this._toggleExpandedAtPath(g.sections, sectionPath, 0),
138
+ };
139
+ }),
140
+ }));
141
+ }
142
+
143
+ /**
144
+ * Toggle a group's expanded (collapsed/expanded) state.
145
+ */
146
+ toggleGroupExpanded(groupIndex: number): void {
147
+ this._state.update(s => ({
148
+ ...s,
149
+ groups: s.groups.map((g, i) =>
150
+ i === groupIndex ? { ...g, expanded: !g.expanded } : g
151
+ ),
152
+ }));
153
+ }
154
+
155
+ /**
156
+ * Toggle a field's selected (checkbox) state.
157
+ */
158
+ toggleField(groupIndex: number, sectionPath: number[], fieldIndex: number): void {
159
+ this._state.update(s => ({
160
+ ...s,
161
+ groups: s.groups.map((g, gi) => {
162
+ if (gi !== groupIndex) return g;
163
+ return {
164
+ ...g,
165
+ sections: this._toggleFieldAtPath(g.sections, sectionPath, 0, fieldIndex),
166
+ };
167
+ }),
168
+ }));
169
+ }
170
+
171
+ /**
172
+ * Build and return the updated FormSchema with selection changes applied.
173
+ */
174
+ buildUpdatedSchema(): FormSchema | null {
175
+ const { groups, originalSchema } = this._state();
176
+ if (!originalSchema) return null;
177
+ return this.treeService.applySelectionToSchema(groups, originalSchema);
178
+ }
179
+
180
+ // ── Private helpers ─────────────────────────────────────────────
181
+
182
+ private _disableAllSections(sections: SelectionSectionNode[]): SelectionSectionNode[] {
183
+ return sections.map(s => ({
184
+ ...s,
185
+ enabled: false,
186
+ fields: s.fields.map(f => ({ ...f, selected: false })),
187
+ subsections: this._disableAllSections(s.subsections),
188
+ }));
189
+ }
190
+
191
+ /**
192
+ * Enable all sections and their fields (except locked ones).
193
+ * Used when toggling a section/group back ON to restore field selections.
194
+ */
195
+ private _enableAllSections(sections: SelectionSectionNode[]): SelectionSectionNode[] {
196
+ return sections.map(s => ({
197
+ ...s,
198
+ enabled: true,
199
+ fields: s.fields.map(f => ({ ...f, selected: !f.isLocked })),
200
+ subsections: this._enableAllSections(s.subsections),
201
+ }));
202
+ }
203
+
204
+ private _toggleSectionAtPath(
205
+ sections: SelectionSectionNode[],
206
+ path: number[],
207
+ depth: number,
208
+ ): SelectionSectionNode[] {
209
+ return sections.map((s, i) => {
210
+ if (i !== path[depth]) return s;
211
+
212
+ if (depth === path.length - 1) {
213
+ // This is the target section
214
+ const enabled = !s.enabled;
215
+ return {
216
+ ...s,
217
+ enabled,
218
+ // When enabling: select all non-locked fields and enable subsections
219
+ // When disabling: deselect all fields and disable subsections
220
+ fields: enabled ? s.fields.map(f => ({ ...f, selected: !f.isLocked })) : s.fields.map(f => ({ ...f, selected: false })),
221
+ subsections: enabled ? this._enableAllSections(s.subsections) : this._disableAllSections(s.subsections),
222
+ };
223
+ }
224
+
225
+ // Recurse into subsections
226
+ return {
227
+ ...s,
228
+ subsections: this._toggleSectionAtPath(s.subsections, path, depth + 1),
229
+ };
230
+ });
231
+ }
232
+
233
+ private _toggleExpandedAtPath(
234
+ sections: SelectionSectionNode[],
235
+ path: number[],
236
+ depth: number,
237
+ ): SelectionSectionNode[] {
238
+ return sections.map((s, i) => {
239
+ if (i !== path[depth]) return s;
240
+
241
+ if (depth === path.length - 1) {
242
+ return { ...s, expanded: !s.expanded };
243
+ }
244
+
245
+ return {
246
+ ...s,
247
+ subsections: this._toggleExpandedAtPath(s.subsections, path, depth + 1),
248
+ };
249
+ });
250
+ }
251
+
252
+ private _toggleFieldAtPath(
253
+ sections: SelectionSectionNode[],
254
+ sectionPath: number[],
255
+ depth: number,
256
+ fieldIndex: number,
257
+ ): SelectionSectionNode[] {
258
+ return sections.map((s, i) => {
259
+ if (i !== sectionPath[depth]) return s;
260
+
261
+ if (depth === sectionPath.length - 1) {
262
+ return {
263
+ ...s,
264
+ fields: s.fields.map((f, fi) =>
265
+ fi === fieldIndex && !f.isLocked
266
+ ? { ...f, selected: !f.selected }
267
+ : f
268
+ ),
269
+ };
270
+ }
271
+
272
+ return {
273
+ ...s,
274
+ subsections: this._toggleFieldAtPath(s.subsections, sectionPath, depth + 1, fieldIndex),
275
+ };
276
+ });
277
+ }
278
+
279
+ /**
280
+ * Recursively merge expansion state from old sections into new sections.
281
+ * Preserves which sections were expanded/collapsed by the user.
282
+ */
283
+ private _mergeExpandedSections(
284
+ newSections: SelectionSectionNode[],
285
+ oldSections: SelectionSectionNode[],
286
+ ): SelectionSectionNode[] {
287
+ return newSections.map((newSection, ni) => {
288
+ const oldSection = oldSections[ni];
289
+ return {
290
+ ...newSection,
291
+ expanded: oldSection?.expanded ?? newSection.expanded,
292
+ subsections: this._mergeExpandedSections(
293
+ newSection.subsections,
294
+ oldSection?.subsections ?? [],
295
+ ),
296
+ };
297
+ });
298
+ }
299
+ }