platformcommons-web-lib 1.0.0
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.
- package/commons-shared-web-ui-1.0.0.tgz +0 -0
- package/documentation/alert.md +123 -0
- package/documentation/button-dropdown.md +126 -0
- package/documentation/button.md +184 -0
- package/documentation/cards-usage-guidelines.md +131 -0
- package/documentation/configurable-form.md +605 -0
- package/documentation/confirmation-modal.md +250 -0
- package/documentation/filter-sidebar.md +178 -0
- package/documentation/filter-table-selector.md +228 -0
- package/documentation/form-builder.md +597 -0
- package/documentation/form-components.md +384 -0
- package/documentation/nav.md +427 -0
- package/documentation/pagination.md +181 -0
- package/documentation/side-nav-documentation.md +169 -0
- package/documentation/smart-form.md +2177 -0
- package/documentation/smart-table.md +1198 -0
- package/documentation/snackbar.md +118 -0
- package/documentation/style-externalization.md +88 -0
- package/documentation/summary-card.md +279 -0
- package/ng-package.json +28 -0
- package/package.json +54 -0
- package/src/lib/modules/alert/alert.models.ts +6 -0
- package/src/lib/modules/alert/alert.module.ts +16 -0
- package/src/lib/modules/alert/alert.theme.scss +85 -0
- package/src/lib/modules/alert/components/alert/alert.component.html +27 -0
- package/src/lib/modules/alert/components/alert/alert.component.scss +92 -0
- package/src/lib/modules/alert/components/alert/alert.component.ts +81 -0
- package/src/lib/modules/button/button.models.ts +13 -0
- package/src/lib/modules/button/button.module.ts +16 -0
- package/src/lib/modules/button/button.theme.scss +121 -0
- package/src/lib/modules/button/components/button/button.component.html +22 -0
- package/src/lib/modules/button/components/button/button.component.scss +88 -0
- package/src/lib/modules/button/components/button/button.component.ts +67 -0
- package/src/lib/modules/button-dropdown/button-dropdown.models.ts +26 -0
- package/src/lib/modules/button-dropdown/button-dropdown.module.ts +22 -0
- package/src/lib/modules/button-dropdown/button-dropdown.theme.scss +87 -0
- package/src/lib/modules/button-dropdown/components/button-dropdown/button-dropdown.component.html +41 -0
- package/src/lib/modules/button-dropdown/components/button-dropdown/button-dropdown.component.scss +135 -0
- package/src/lib/modules/button-dropdown/components/button-dropdown/button-dropdown.component.ts +160 -0
- package/src/lib/modules/configurable-form/component/configurable-form.component.html +294 -0
- package/src/lib/modules/configurable-form/component/configurable-form.component.scss +503 -0
- package/src/lib/modules/configurable-form/component/configurable-form.component.ts +628 -0
- package/src/lib/modules/configurable-form/configurable-form.examples.ts +154 -0
- package/src/lib/modules/configurable-form/configurable-form.model.ts +131 -0
- package/src/lib/modules/configurable-form/configurable-form.module.ts +19 -0
- package/src/lib/modules/configurable-form/configurable-form.theme.scss +78 -0
- package/src/lib/modules/confirmation-modal/components/confirmation-modal/confirmation-modal.component.html +77 -0
- package/src/lib/modules/confirmation-modal/components/confirmation-modal/confirmation-modal.component.scss +395 -0
- package/src/lib/modules/confirmation-modal/components/confirmation-modal/confirmation-modal.component.ts +266 -0
- package/src/lib/modules/confirmation-modal/confirmation-modal.models.ts +71 -0
- package/src/lib/modules/confirmation-modal/confirmation-modal.module.ts +20 -0
- package/src/lib/modules/confirmation-modal/confirmation-modal.theme.scss +87 -0
- package/src/lib/modules/filter/components/filter/filter.component.html +131 -0
- package/src/lib/modules/filter/components/filter/filter.component.scss +245 -0
- package/src/lib/modules/filter/components/filter/filter.component.ts +216 -0
- package/src/lib/modules/filter/filter.models.ts +88 -0
- package/src/lib/modules/filter/filter.module.ts +24 -0
- package/src/lib/modules/filter/filter.theme.scss +92 -0
- package/src/lib/modules/filter-sidebar/components/filter-sidebar/filter-sidebar.component.html +112 -0
- package/src/lib/modules/filter-sidebar/components/filter-sidebar/filter-sidebar.component.scss +186 -0
- package/src/lib/modules/filter-sidebar/components/filter-sidebar/filter-sidebar.component.ts +163 -0
- package/src/lib/modules/filter-sidebar/filter-sidebar.models.ts +95 -0
- package/src/lib/modules/filter-sidebar/filter-sidebar.module.ts +24 -0
- package/src/lib/modules/filter-sidebar/filter-sidebar.theme.scss +38 -0
- package/src/lib/modules/filter-table-selector/components/filter-table-selector/filter-table-selector.component.html +73 -0
- package/src/lib/modules/filter-table-selector/components/filter-table-selector/filter-table-selector.component.scss +321 -0
- package/src/lib/modules/filter-table-selector/components/filter-table-selector/filter-table-selector.component.ts +361 -0
- package/src/lib/modules/filter-table-selector/filter-table-selector.models.ts +91 -0
- package/src/lib/modules/filter-table-selector/filter-table-selector.module.ts +22 -0
- package/src/lib/modules/filter-table-selector/filter-table-selector.theme.scss +36 -0
- package/src/lib/modules/form-builder/components/field-configurator/configurator-config-panel/configurator-config-panel.component.html +63 -0
- package/src/lib/modules/form-builder/components/field-configurator/configurator-config-panel/configurator-config-panel.component.scss +496 -0
- package/src/lib/modules/form-builder/components/field-configurator/configurator-config-panel/configurator-config-panel.component.ts +445 -0
- package/src/lib/modules/form-builder/components/field-configurator/configurator-tree/configurator-tree.component.html +75 -0
- package/src/lib/modules/form-builder/components/field-configurator/configurator-tree/configurator-tree.component.scss +210 -0
- package/src/lib/modules/form-builder/components/field-configurator/configurator-tree/configurator-tree.component.ts +55 -0
- package/src/lib/modules/form-builder/components/field-configurator/field-configurator.component.html +25 -0
- package/src/lib/modules/form-builder/components/field-configurator/field-configurator.component.scss +82 -0
- package/src/lib/modules/form-builder/components/field-configurator/field-configurator.component.ts +95 -0
- package/src/lib/modules/form-builder/components/field-selection/field-selection.component.html +20 -0
- package/src/lib/modules/form-builder/components/field-selection/field-selection.component.scss +37 -0
- package/src/lib/modules/form-builder/components/field-selection/field-selection.component.ts +94 -0
- package/src/lib/modules/form-builder/components/field-selection/group-node/group-node.component.html +46 -0
- package/src/lib/modules/form-builder/components/field-selection/group-node/group-node.component.scss +102 -0
- package/src/lib/modules/form-builder/components/field-selection/group-node/group-node.component.ts +50 -0
- package/src/lib/modules/form-builder/components/field-selection/selection-field-node/selection-field-node.component.html +35 -0
- package/src/lib/modules/form-builder/components/field-selection/selection-field-node/selection-field-node.component.scss +67 -0
- package/src/lib/modules/form-builder/components/field-selection/selection-field-node/selection-field-node.component.ts +34 -0
- package/src/lib/modules/form-builder/components/field-selection/selection-section-node/selection-section-node.component.html +68 -0
- package/src/lib/modules/form-builder/components/field-selection/selection-section-node/selection-section-node.component.scss +113 -0
- package/src/lib/modules/form-builder/components/field-selection/selection-section-node/selection-section-node.component.ts +74 -0
- package/src/lib/modules/form-builder/configs/field-type-schema.map.ts +533 -0
- package/src/lib/modules/form-builder/form-builder.module.ts +36 -0
- package/src/lib/modules/form-builder/form-builder.theme.scss +212 -0
- package/src/lib/modules/form-builder/index.ts +9 -0
- package/src/lib/modules/form-builder/models/builder.models.ts +7 -0
- package/src/lib/modules/form-builder/models/field-configurator.models.ts +38 -0
- package/src/lib/modules/form-builder/models/field-selection.models.ts +51 -0
- package/src/lib/modules/form-builder/services/field-configurator.service.ts +258 -0
- package/src/lib/modules/form-builder/services/field-selection.service.ts +300 -0
- package/src/lib/modules/form-builder/services/form-schema-tree.service.ts +652 -0
- package/src/lib/modules/form-builder/tokens/builder.tokens.ts +10 -0
- package/src/lib/modules/form-builder/utils/constants.ts +43 -0
- package/src/lib/modules/form-components/components/checkbox/_theme.scss +63 -0
- package/src/lib/modules/form-components/components/checkbox/checkbox.component.html +29 -0
- package/src/lib/modules/form-components/components/checkbox/checkbox.component.scss +111 -0
- package/src/lib/modules/form-components/components/checkbox/checkbox.component.ts +207 -0
- package/src/lib/modules/form-components/components/checkbox/checkbox.models.ts +35 -0
- package/src/lib/modules/form-components/components/datepicker/_theme.scss +82 -0
- package/src/lib/modules/form-components/components/datepicker/datepicker.component.html +42 -0
- package/src/lib/modules/form-components/components/datepicker/datepicker.component.scss +115 -0
- package/src/lib/modules/form-components/components/datepicker/datepicker.component.ts +267 -0
- package/src/lib/modules/form-components/components/datepicker/datepicker.models.ts +45 -0
- package/src/lib/modules/form-components/components/dropdown/_theme.scss +91 -0
- package/src/lib/modules/form-components/components/dropdown/dropdown.component.html +74 -0
- package/src/lib/modules/form-components/components/dropdown/dropdown.component.scss +252 -0
- package/src/lib/modules/form-components/components/dropdown/dropdown.component.ts +377 -0
- package/src/lib/modules/form-components/components/dropdown/dropdown.models.ts +53 -0
- package/src/lib/modules/form-components/components/input/_theme.scss +77 -0
- package/src/lib/modules/form-components/components/input/input.component.html +51 -0
- package/src/lib/modules/form-components/components/input/input.component.scss +128 -0
- package/src/lib/modules/form-components/components/input/input.component.ts +250 -0
- package/src/lib/modules/form-components/components/input/input.models.ts +55 -0
- package/src/lib/modules/form-components/components/radio/_theme.scss +61 -0
- package/src/lib/modules/form-components/components/radio/radio.component.html +22 -0
- package/src/lib/modules/form-components/components/radio/radio.component.scss +107 -0
- package/src/lib/modules/form-components/components/radio/radio.component.ts +181 -0
- package/src/lib/modules/form-components/components/radio/radio.models.ts +39 -0
- package/src/lib/modules/form-components/components/search/_theme.scss +73 -0
- package/src/lib/modules/form-components/components/search/search.component.html +15 -0
- package/src/lib/modules/form-components/components/search/search.component.scss +87 -0
- package/src/lib/modules/form-components/components/search/search.component.ts +213 -0
- package/src/lib/modules/form-components/components/search/search.models.ts +40 -0
- package/src/lib/modules/form-components/components/toggle/_theme.scss +45 -0
- package/src/lib/modules/form-components/components/toggle/toggle.component.html +15 -0
- package/src/lib/modules/form-components/components/toggle/toggle.component.scss +81 -0
- package/src/lib/modules/form-components/components/toggle/toggle.component.ts +166 -0
- package/src/lib/modules/form-components/components/toggle/toggle.models.ts +27 -0
- package/src/lib/modules/form-components/directives/click-outside.directive.ts +22 -0
- package/src/lib/modules/form-components/form-components.module.ts +41 -0
- package/src/lib/modules/form-components/form-components.theme.scss +25 -0
- package/src/lib/modules/material/material.module.ts +94 -0
- package/src/lib/modules/nav/components/nav/nav.component.html +34 -0
- package/src/lib/modules/nav/components/nav/nav.component.scss +171 -0
- package/src/lib/modules/nav/components/nav/nav.component.ts +82 -0
- package/src/lib/modules/nav/nav.models.ts +31 -0
- package/src/lib/modules/nav/nav.module.ts +17 -0
- package/src/lib/modules/nav/nav.theme.scss +86 -0
- package/src/lib/modules/pagination/components/pagination/pagination.component.html +52 -0
- package/src/lib/modules/pagination/components/pagination/pagination.component.scss +155 -0
- package/src/lib/modules/pagination/components/pagination/pagination.component.ts +109 -0
- package/src/lib/modules/pagination/pagination.module.ts +17 -0
- package/src/lib/modules/pagination/pagination.theme.scss +66 -0
- package/src/lib/modules/side-nav/components/side-nav/side-nav.component.html +56 -0
- package/src/lib/modules/side-nav/components/side-nav/side-nav.component.scss +342 -0
- package/src/lib/modules/side-nav/components/side-nav/side-nav.component.ts +135 -0
- package/src/lib/modules/side-nav/side-nav.models.ts +38 -0
- package/src/lib/modules/side-nav/side-nav.module.ts +16 -0
- package/src/lib/modules/side-nav/side-nav.theme.scss +111 -0
- package/src/lib/modules/smart-form/components/form-field/form-field.component.html +1109 -0
- package/src/lib/modules/smart-form/components/form-field/form-field.component.scss +1860 -0
- package/src/lib/modules/smart-form/components/form-field/form-field.component.ts +2232 -0
- package/src/lib/modules/smart-form/components/form-section/form-section.component.html +64 -0
- package/src/lib/modules/smart-form/components/form-section/form-section.component.scss +209 -0
- package/src/lib/modules/smart-form/components/form-section/form-section.component.ts +119 -0
- package/src/lib/modules/smart-form/components/smart-form/smart-form.component.html +253 -0
- package/src/lib/modules/smart-form/components/smart-form/smart-form.component.scss +689 -0
- package/src/lib/modules/smart-form/components/smart-form/smart-form.component.ts +1087 -0
- package/src/lib/modules/smart-form/index.ts +10 -0
- package/src/lib/modules/smart-form/models/form-schema.model.ts +700 -0
- package/src/lib/modules/smart-form/models/hierarchy-config.model.ts +21 -0
- package/src/lib/modules/smart-form/services/expression.service.ts +75 -0
- package/src/lib/modules/smart-form/services/smart-form-controller.service.ts +65 -0
- package/src/lib/modules/smart-form/smart-form.examples.ts +1324 -0
- package/src/lib/modules/smart-form/smart-form.module.ts +36 -0
- package/src/lib/modules/smart-form/smart-form.theme.scss +890 -0
- package/src/lib/modules/smart-form/utils/translation.utils.ts +82 -0
- package/src/lib/modules/smart-form/utils/trusted-url.pipe.ts +25 -0
- package/src/lib/modules/smart-form/utils/validation.utils.ts +98 -0
- package/src/lib/modules/smart-table/components/smart-table/smart-table.component.html +283 -0
- package/src/lib/modules/smart-table/components/smart-table/smart-table.component.scss +685 -0
- package/src/lib/modules/smart-table/components/smart-table/smart-table.component.ts +1118 -0
- package/src/lib/modules/smart-table/models/table-config.model.ts +202 -0
- package/src/lib/modules/smart-table/smart-table.module.ts +30 -0
- package/src/lib/modules/smart-table/smart-table.theme.scss +335 -0
- package/src/lib/modules/smart-table/utils/safe-html.pipe.ts +22 -0
- package/src/lib/modules/smart-table/utils/smart-table.utils.ts +18 -0
- package/src/lib/modules/snackbar/components/snackbar.component.html +41 -0
- package/src/lib/modules/snackbar/components/snackbar.component.scss +99 -0
- package/src/lib/modules/snackbar/components/snackbar.component.ts +18 -0
- package/src/lib/modules/snackbar/models/snackbar.models.ts +10 -0
- package/src/lib/modules/snackbar/services/snackbar.service.ts +40 -0
- package/src/lib/modules/snackbar/snackbar.module.ts +11 -0
- package/src/lib/modules/snackbar/snackbar.theme.scss +93 -0
- package/src/lib/modules/summary-card/components/summary-card/summary-card.component.html +47 -0
- package/src/lib/modules/summary-card/components/summary-card/summary-card.component.scss +199 -0
- package/src/lib/modules/summary-card/components/summary-card/summary-card.component.ts +126 -0
- package/src/lib/modules/summary-card/summary-card.module.ts +18 -0
- package/src/lib/modules/summary-card/summary-card.theme.scss +176 -0
- package/src/lib/shared-ui.module.ts +44 -0
- package/src/lib/styles/global.scss +152 -0
- package/src/lib/styles/utilities.scss +250 -0
- package/src/lib/utils/constants.ts +11 -0
- package/src/lib/utils/storage.utils.ts +37 -0
- package/src/lib/utils/string.utils.ts +23 -0
- package/src/lib/utils/translation.utils.ts +87 -0
- package/src/public-api.ts +104 -0
- package/tsconfig.lib.json +15 -0
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Component,
|
|
3
|
+
ChangeDetectionStrategy,
|
|
4
|
+
ChangeDetectorRef,
|
|
5
|
+
Input,
|
|
6
|
+
Output,
|
|
7
|
+
EventEmitter,
|
|
8
|
+
OnChanges,
|
|
9
|
+
SimpleChanges,
|
|
10
|
+
} from '@angular/core';
|
|
11
|
+
import { FieldConfig, FormSchema } from '../../../../smart-form/models/form-schema.model';
|
|
12
|
+
import { ConfiguratorFieldInfo } from '../../../models/field-configurator.models';
|
|
13
|
+
import { SWITCHABLE_FIELD_TYPES } from '../../../utils/constants';
|
|
14
|
+
import { SnackbarService } from '../../../../snackbar/services/snackbar.service';
|
|
15
|
+
|
|
16
|
+
@Component({
|
|
17
|
+
selector: 'lib-configurator-config-panel',
|
|
18
|
+
standalone: false,
|
|
19
|
+
templateUrl: './configurator-config-panel.component.html',
|
|
20
|
+
styleUrls: ['./configurator-config-panel.component.scss'],
|
|
21
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
22
|
+
})
|
|
23
|
+
export class ConfiguratorConfigPanelComponent implements OnChanges {
|
|
24
|
+
constructor(
|
|
25
|
+
private cdr: ChangeDetectorRef,
|
|
26
|
+
private snackbarService: SnackbarService
|
|
27
|
+
) {}
|
|
28
|
+
|
|
29
|
+
@Input() selectedField: FieldConfig | null = null;
|
|
30
|
+
@Input() selectedFieldInfo: ConfiguratorFieldInfo | null = null;
|
|
31
|
+
@Input() builderFieldType: string | null = null;
|
|
32
|
+
@Input() fieldTypeSchemaMap: Record<string, FormSchema> = {};
|
|
33
|
+
|
|
34
|
+
@Output() configChange = new EventEmitter<Record<string, unknown>>();
|
|
35
|
+
@Output() typeChange = new EventEmitter<string>();
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* When false, all `optionConfig.*` fields (apiUrl, dataPath, labelPath, etc.)
|
|
39
|
+
* are hidden from the config panel. Business users don't need to configure
|
|
40
|
+
* option URLs — these are pre-filled by the developer in the master JSON.
|
|
41
|
+
*/
|
|
42
|
+
@Input() showOptionConfig: boolean = true;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Field names that are mandatory and cannot be disabled.
|
|
46
|
+
* When the currently selected field is in this list, IS ENABLED / READ ONLY /
|
|
47
|
+
* DISABLED controls are hidden from the Field State step.
|
|
48
|
+
*/
|
|
49
|
+
@Input() lockedFieldNames: string[] = [];
|
|
50
|
+
|
|
51
|
+
/** Exposed to the template for the native type-switcher select */
|
|
52
|
+
readonly switchableFieldTypes = SWITCHABLE_FIELD_TYPES;
|
|
53
|
+
|
|
54
|
+
configFormJson: string = '';
|
|
55
|
+
configInitialValues: Record<string, unknown> = {};
|
|
56
|
+
showConfigForm: boolean = false;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* The type currently being PREVIEWED in the config form.
|
|
60
|
+
* Starts equal to builderFieldType; changes as user picks a new type.
|
|
61
|
+
* Emitted to the parent on Apply.
|
|
62
|
+
*/
|
|
63
|
+
currentBuilderType: string | null = null;
|
|
64
|
+
|
|
65
|
+
/** Identity key of the currently rendered field — used to detect real field switches. */
|
|
66
|
+
private _currentFieldKey: string = '';
|
|
67
|
+
|
|
68
|
+
/** True when the current field type is DROPDOWN — type switching is disabled for these fields. */
|
|
69
|
+
get isDropdownType(): boolean {
|
|
70
|
+
const t = this.selectedField?.type?.toUpperCase();
|
|
71
|
+
return t === 'DROPDOWN' || t === 'MULTI_SELECT';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
ngOnChanges(changes: SimpleChanges): void {
|
|
75
|
+
if (
|
|
76
|
+
(changes['selectedField'] || changes['builderFieldType'] || changes['fieldTypeSchemaMap'])
|
|
77
|
+
&& this.selectedField
|
|
78
|
+
&& this.builderFieldType
|
|
79
|
+
) {
|
|
80
|
+
const key = `${this.selectedField.name}__${this.builderFieldType}`;
|
|
81
|
+
if (key !== this._currentFieldKey) {
|
|
82
|
+
this._currentFieldKey = key;
|
|
83
|
+
// Reset the previewed type whenever we switch to a different field
|
|
84
|
+
this.currentBuilderType = this.builderFieldType;
|
|
85
|
+
this._buildConfigFormForType(this.builderFieldType);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ─── Type Switcher ────────────────────────────────────────────────────────
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Called when the user picks a new type from the native select in the header.
|
|
94
|
+
* Immediately rebuilds the config form to show fields for the new type.
|
|
95
|
+
* The actual field-level type change is only committed when Apply is clicked.
|
|
96
|
+
*/
|
|
97
|
+
onTypeSelectChange(event: Event): void {
|
|
98
|
+
const newType = (event.target as HTMLSelectElement).value;
|
|
99
|
+
if (!newType || newType === this.currentBuilderType) return;
|
|
100
|
+
if (!this.fieldTypeSchemaMap[newType]) {
|
|
101
|
+
console.warn(`[ConfigPanel] No schema found for field type: ${newType}`);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
this.currentBuilderType = newType;
|
|
105
|
+
this._buildConfigFormForType(newType);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ─── Form Actions ─────────────────────────────────────────────────────────
|
|
109
|
+
|
|
110
|
+
onActionClick(event: { id: string; formData: Record<string, unknown> }): void {
|
|
111
|
+
if (event.id === 'apply') {
|
|
112
|
+
const patch = this._buildPatchFromFormData(event.formData);
|
|
113
|
+
|
|
114
|
+
// Emit type change only if the user picked a different type
|
|
115
|
+
if (this.currentBuilderType && this.currentBuilderType !== this.builderFieldType) {
|
|
116
|
+
this.typeChange.emit(this.currentBuilderType);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
this.configChange.emit(patch);
|
|
120
|
+
|
|
121
|
+
// Show success notification to user
|
|
122
|
+
const fieldLabel = this.selectedField?.label || this.selectedField?.name || 'Field';
|
|
123
|
+
this.snackbarService.success(`${fieldLabel} configuration updated successfully`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ─── Private: Build the config form JSON ─────────────────────────────────
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Rebuild the SmartForm for the given builder type key.
|
|
131
|
+
* Uses a destroy/recreate cycle (showConfigForm toggle + setTimeout) to
|
|
132
|
+
* guarantee a completely fresh SmartForm instance is mounted.
|
|
133
|
+
*/
|
|
134
|
+
private _buildConfigFormForType(builderType: string): void {
|
|
135
|
+
if (!this.fieldTypeSchemaMap || !this.selectedField) return;
|
|
136
|
+
|
|
137
|
+
let schema = this.fieldTypeSchemaMap[builderType];
|
|
138
|
+
if (!schema) {
|
|
139
|
+
this.showConfigForm = false;
|
|
140
|
+
this.configFormJson = '';
|
|
141
|
+
this.cdr.markForCheck();
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// When optionConfig should be hidden, filter it out of the schema
|
|
146
|
+
if (!this.showOptionConfig) {
|
|
147
|
+
schema = this._filterSchemaForOptionConfig(schema);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// When a mandatory/locked field is selected, hide IS ENABLED / READ ONLY / DISABLED
|
|
151
|
+
const isLockedField = this.selectedField?.name
|
|
152
|
+
? this.lockedFieldNames.includes(this.selectedField.name)
|
|
153
|
+
: false;
|
|
154
|
+
if (isLockedField) {
|
|
155
|
+
schema = this._filterSchemaForLockedField(schema);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// 1. Hide current SmartForm instance (renders false in the current CD cycle
|
|
159
|
+
// since ngOnChanges fires before the view is rendered)
|
|
160
|
+
this.showConfigForm = false;
|
|
161
|
+
|
|
162
|
+
// 2. Prepare new schema + initial values
|
|
163
|
+
this.configFormJson = JSON.stringify(schema);
|
|
164
|
+
this.configInitialValues = this._extractInitialValuesFromField(this.selectedField);
|
|
165
|
+
|
|
166
|
+
// 3. Re-mount on next tick — markForCheck schedules a proper CD pass
|
|
167
|
+
// rather than forcing a synchronous check mid-cycle
|
|
168
|
+
setTimeout(() => {
|
|
169
|
+
this.showConfigForm = true;
|
|
170
|
+
this.cdr.markForCheck();
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Deep-clones the schema and removes any field whose `name` matches developer-only fields.
|
|
176
|
+
* This prevents end users from modifying system-level configurations like:
|
|
177
|
+
* - optionConfig.* (API URLs, data paths, etc.)
|
|
178
|
+
* - payloadPath (payload mapping)
|
|
179
|
+
* - className (CSS styling)
|
|
180
|
+
* - name (system identifier)
|
|
181
|
+
*
|
|
182
|
+
* Also removes entire sections that become empty after filtering.
|
|
183
|
+
*/
|
|
184
|
+
private _filterSchemaForOptionConfig(schema: FormSchema): FormSchema {
|
|
185
|
+
// List of developer-only field names that should be hidden from end users
|
|
186
|
+
const DEVELOPER_ONLY_FIELDS = [
|
|
187
|
+
'payloadPath', // Payload mapping configuration
|
|
188
|
+
'className', // CSS class for styling
|
|
189
|
+
'name', // System field identifier
|
|
190
|
+
];
|
|
191
|
+
|
|
192
|
+
const isDeveloperOnlyField = (f: any): boolean => {
|
|
193
|
+
if (typeof f?.name !== 'string') return false;
|
|
194
|
+
|
|
195
|
+
// Hide optionConfig.* fields (API URL, data path, label path, etc.)
|
|
196
|
+
if (f.name.startsWith('optionConfig.')) return true;
|
|
197
|
+
|
|
198
|
+
// Hide other developer-only fields
|
|
199
|
+
if (DEVELOPER_ONLY_FIELDS.includes(f.name)) return true;
|
|
200
|
+
|
|
201
|
+
return false;
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const filterChildren = (children: any[]): any[] =>
|
|
205
|
+
children
|
|
206
|
+
.map((child: any) => {
|
|
207
|
+
// ROW — filter its inline children
|
|
208
|
+
if (child.type === 'ROW' && Array.isArray(child.children)) {
|
|
209
|
+
const filtered = child.children.filter((c: any) => !isDeveloperOnlyField(c));
|
|
210
|
+
return filtered.length ? { ...child, children: filtered } : null;
|
|
211
|
+
}
|
|
212
|
+
// GROUP / section wrapper — recurse into sectionConfig.children
|
|
213
|
+
if (child.sectionConfig?.children) {
|
|
214
|
+
const filteredChildren = filterChildren(child.sectionConfig.children);
|
|
215
|
+
// Drop the entire section if it becomes empty
|
|
216
|
+
if (!filteredChildren.length) return null;
|
|
217
|
+
return {
|
|
218
|
+
...child,
|
|
219
|
+
sectionConfig: { ...child.sectionConfig, children: filteredChildren },
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
// Leaf field
|
|
223
|
+
if (isDeveloperOnlyField(child)) return null;
|
|
224
|
+
return child;
|
|
225
|
+
})
|
|
226
|
+
.filter(Boolean);
|
|
227
|
+
|
|
228
|
+
const topChildren = schema.sectionConfig?.children
|
|
229
|
+
? filterChildren(schema.sectionConfig.children)
|
|
230
|
+
: [];
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
...schema,
|
|
234
|
+
sectionConfig: schema.sectionConfig
|
|
235
|
+
? { ...schema.sectionConfig, children: topChildren }
|
|
236
|
+
: schema.sectionConfig,
|
|
237
|
+
} as FormSchema;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Remove IS ENABLED, READ ONLY, and DISABLED controls from the config schema
|
|
242
|
+
* when the selected field is a mandatory/locked field.
|
|
243
|
+
*/
|
|
244
|
+
private _filterSchemaForLockedField(schema: FormSchema): FormSchema {
|
|
245
|
+
const LOCKED_HIDDEN_FIELDS = new Set(['isEnabled', 'readonly', 'disabled']);
|
|
246
|
+
|
|
247
|
+
const filterChildren = (children: any[]): any[] =>
|
|
248
|
+
children
|
|
249
|
+
.map((child: any) => {
|
|
250
|
+
if (child.type === 'ROW' && Array.isArray(child.children)) {
|
|
251
|
+
const filtered = child.children.filter((c: any) => !LOCKED_HIDDEN_FIELDS.has(c.name));
|
|
252
|
+
return filtered.length ? { ...child, children: filtered } : null;
|
|
253
|
+
}
|
|
254
|
+
if (child.sectionConfig?.children) {
|
|
255
|
+
const filteredChildren = filterChildren(child.sectionConfig.children);
|
|
256
|
+
if (!filteredChildren.length) return null;
|
|
257
|
+
return { ...child, sectionConfig: { ...child.sectionConfig, children: filteredChildren } };
|
|
258
|
+
}
|
|
259
|
+
if (LOCKED_HIDDEN_FIELDS.has(child.name)) return null;
|
|
260
|
+
return child;
|
|
261
|
+
})
|
|
262
|
+
.filter(Boolean);
|
|
263
|
+
|
|
264
|
+
const topChildren = schema.sectionConfig?.children
|
|
265
|
+
? filterChildren(schema.sectionConfig.children)
|
|
266
|
+
: [];
|
|
267
|
+
|
|
268
|
+
return {
|
|
269
|
+
...schema,
|
|
270
|
+
sectionConfig: schema.sectionConfig
|
|
271
|
+
? { ...schema.sectionConfig, children: topChildren }
|
|
272
|
+
: schema.sectionConfig,
|
|
273
|
+
} as FormSchema;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// ─── Private: Extract initial values from FieldConfig ─────────────────────
|
|
277
|
+
|
|
278
|
+
private _extractInitialValuesFromField(field: FieldConfig): Record<string, unknown> {
|
|
279
|
+
const v: Record<string, unknown> = {};
|
|
280
|
+
|
|
281
|
+
const set = (key: string, val: unknown) => {
|
|
282
|
+
if (val !== undefined && val !== null) v[key] = val;
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
// ── Common FieldConfig properties ─────────────────────────────────────────
|
|
286
|
+
set('label', field.label);
|
|
287
|
+
set('name', field.name);
|
|
288
|
+
set('colSpan', field.colSpan);
|
|
289
|
+
set('placeholder', field.placeholder);
|
|
290
|
+
set('hint', field.hint);
|
|
291
|
+
set('defaultValue', field.defaultValue);
|
|
292
|
+
set('payloadPath', field.payloadPath);
|
|
293
|
+
set('className', field.className);
|
|
294
|
+
set('prefix', field.prefix);
|
|
295
|
+
set('suffix', field.suffix);
|
|
296
|
+
set('required', field.required);
|
|
297
|
+
set('readonly', field.readonly);
|
|
298
|
+
set('disabled', field.disabled);
|
|
299
|
+
set('isEnabled', field.isEnabled ?? field.visible);
|
|
300
|
+
set('errorMessage', field.errorMessage);
|
|
301
|
+
set('visibilityExpression', field.visibilityExpression);
|
|
302
|
+
|
|
303
|
+
if (field.textConfig) {
|
|
304
|
+
const tc = field.textConfig;
|
|
305
|
+
set('textConfig.length.min', tc.length?.min);
|
|
306
|
+
set('textConfig.length.max', tc.length?.max);
|
|
307
|
+
set('textConfig.pattern', tc.pattern);
|
|
308
|
+
set('textConfig.patternMessage', tc.patternMessage);
|
|
309
|
+
set('textConfig.showCharCount', tc.showCharCount);
|
|
310
|
+
set('textConfig.matchField', tc.matchField);
|
|
311
|
+
}
|
|
312
|
+
if (field.emailConfig) {
|
|
313
|
+
const ec = field.emailConfig;
|
|
314
|
+
set('emailConfig.defaultDomain', ec.defaultDomain);
|
|
315
|
+
set('emailConfig.allowedDomains', ec.allowedDomains?.join(', '));
|
|
316
|
+
set('emailConfig.blockedDomains', ec.blockedDomains?.join(', '));
|
|
317
|
+
}
|
|
318
|
+
if (field.phoneConfig) {
|
|
319
|
+
const pc = field.phoneConfig;
|
|
320
|
+
set('phoneConfig.defaultCountryCode', pc.defaultCountryCode);
|
|
321
|
+
set('phoneConfig.prefixCountryCode', pc.prefixCountryCode);
|
|
322
|
+
set('phoneConfig.supportedCountryCodeLocale', pc.supportedCountryCodeLocale?.join(', '));
|
|
323
|
+
}
|
|
324
|
+
if (field.numberConfig) {
|
|
325
|
+
const nc = field.numberConfig;
|
|
326
|
+
set('numberConfig.min', nc.min);
|
|
327
|
+
set('numberConfig.max', nc.max);
|
|
328
|
+
set('numberConfig.step', nc.step);
|
|
329
|
+
set('numberConfig.precision', nc.precision);
|
|
330
|
+
}
|
|
331
|
+
if (field.dateConfig) {
|
|
332
|
+
const dc = field.dateConfig;
|
|
333
|
+
set('dateConfig.minDate', dc.minDate);
|
|
334
|
+
set('dateConfig.maxDate', dc.maxDate);
|
|
335
|
+
set('dateConfig.allowFuture', dc.allowFuture);
|
|
336
|
+
}
|
|
337
|
+
if (field.optionConfig) {
|
|
338
|
+
const oc = field.optionConfig;
|
|
339
|
+
if (oc.optionList?.length) set('optionConfig.optionList', JSON.stringify(oc.optionList, null, 2));
|
|
340
|
+
set('optionConfig.apiUrl', oc.apiUrl || oc.optionUrl);
|
|
341
|
+
set('optionConfig.dataPath', oc.dataPath);
|
|
342
|
+
set('optionConfig.labelPath', oc.labelPath);
|
|
343
|
+
set('optionConfig.valuePath', oc.valuePath);
|
|
344
|
+
set('optionConfig.sortBy', oc.sortBy);
|
|
345
|
+
set('optionConfig.layout', oc.layout);
|
|
346
|
+
}
|
|
347
|
+
if (field.attachmentConfig) {
|
|
348
|
+
const ac = field.attachmentConfig;
|
|
349
|
+
set('attachmentConfig.multiple', ac.multiple);
|
|
350
|
+
set('attachmentConfig.maxFiles', ac.maxFiles);
|
|
351
|
+
set('attachmentConfig.maxSizeMB', ac.maxSizeMB);
|
|
352
|
+
set('attachmentConfig.accept', ac.accept);
|
|
353
|
+
set('attachmentConfig.acceptLabel', ac.acceptLabel);
|
|
354
|
+
set('attachmentConfig.uploadUrl', ac.uploadUrl);
|
|
355
|
+
set('attachmentConfig.deleteUrl', ac.deleteUrl);
|
|
356
|
+
}
|
|
357
|
+
if (field.richTextConfig) {
|
|
358
|
+
const rc = field.richTextConfig;
|
|
359
|
+
set('richTextConfig.height', rc.height);
|
|
360
|
+
set('richTextConfig.maxLength', rc.maxLength);
|
|
361
|
+
set('richTextConfig.placeholder', rc.placeholder);
|
|
362
|
+
set('richTextConfig.showCharCount', rc.showCharCount);
|
|
363
|
+
}
|
|
364
|
+
if (field.ratingConfig) {
|
|
365
|
+
const rat = field.ratingConfig;
|
|
366
|
+
set('ratingConfig.maxRating', rat.maxRating);
|
|
367
|
+
set('ratingConfig.allowHalf', rat.allowHalf);
|
|
368
|
+
}
|
|
369
|
+
if (field.locationConfig) {
|
|
370
|
+
const lc = field.locationConfig;
|
|
371
|
+
set('locationConfig.defaultTab', lc.defaultTab);
|
|
372
|
+
set('locationConfig.maxLocations', lc.maxLocations);
|
|
373
|
+
set('locationConfig.allowMulti', lc.allowMulti);
|
|
374
|
+
set('locationConfig.showMap', lc.showMap);
|
|
375
|
+
set('locationConfig.googleMapsApiKey', lc.googleMapsApiKey);
|
|
376
|
+
set('locationConfig.venuePlaceholder', lc.venuePlaceholder);
|
|
377
|
+
set('locationConfig.onlinePlaceholder', lc.onlinePlaceholder);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return v;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// ─── Private: Convert flat dot-notation form data → nested FieldConfig patch
|
|
384
|
+
|
|
385
|
+
private _buildPatchFromFormData(formData: Record<string, unknown>): Record<string, unknown> {
|
|
386
|
+
const patch: Record<string, unknown> = {};
|
|
387
|
+
|
|
388
|
+
const setNested = (obj: any, dotPath: string, val: unknown): void => {
|
|
389
|
+
const parts = dotPath.split('.');
|
|
390
|
+
let cur = obj;
|
|
391
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
392
|
+
const part = parts[i];
|
|
393
|
+
if (typeof cur[part] !== 'object' || cur[part] === null) {
|
|
394
|
+
cur[part] = {};
|
|
395
|
+
}
|
|
396
|
+
cur = cur[part];
|
|
397
|
+
}
|
|
398
|
+
cur[parts[parts.length - 1]] = val;
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
const flatData: Record<string, unknown> = {};
|
|
402
|
+
for (const [key, val] of Object.entries(formData)) {
|
|
403
|
+
if (val !== null && typeof val === 'object' && !Array.isArray(val)) {
|
|
404
|
+
for (const [innerKey, innerVal] of Object.entries(val as Record<string, unknown>)) {
|
|
405
|
+
flatData[innerKey] = innerVal;
|
|
406
|
+
}
|
|
407
|
+
} else {
|
|
408
|
+
flatData[key] = val;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
for (const [key, val] of Object.entries(flatData)) {
|
|
413
|
+
if (val === null || val === undefined) continue;
|
|
414
|
+
if (key.startsWith('row-')) continue;
|
|
415
|
+
setNested(patch, key, val);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// Post-process: parse JSON strings for optionConfig.optionList
|
|
419
|
+
if (patch['optionConfig'] && (patch['optionConfig'] as any)['optionList']) {
|
|
420
|
+
try {
|
|
421
|
+
const raw = (patch['optionConfig'] as any)['optionList'];
|
|
422
|
+
if (typeof raw === 'string') {
|
|
423
|
+
(patch['optionConfig'] as any)['optionList'] = JSON.parse(raw);
|
|
424
|
+
}
|
|
425
|
+
} catch {
|
|
426
|
+
// Ignore invalid JSON — leave as-is
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Post-process: split comma-separated strings for email/phone arrays
|
|
431
|
+
if (patch['emailConfig']) {
|
|
432
|
+
const ec = patch['emailConfig'] as any;
|
|
433
|
+
if (typeof ec['allowedDomains'] === 'string') ec['allowedDomains'] = ec['allowedDomains'].split(',').map((s: string) => s.trim()).filter(Boolean);
|
|
434
|
+
if (typeof ec['blockedDomains'] === 'string') ec['blockedDomains'] = ec['blockedDomains'].split(',').map((s: string) => s.trim()).filter(Boolean);
|
|
435
|
+
}
|
|
436
|
+
if (patch['phoneConfig']) {
|
|
437
|
+
const pc = patch['phoneConfig'] as any;
|
|
438
|
+
if (typeof pc['supportedCountryCodeLocale'] === 'string') {
|
|
439
|
+
pc['supportedCountryCodeLocale'] = pc['supportedCountryCodeLocale'].split(',').map((s: string) => s.trim()).filter(Boolean);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
return patch;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
<div class="fb-cfg-tree">
|
|
2
|
+
<div class="fb-cfg-tree__header">
|
|
3
|
+
<h4 class="fb-cfg-tree__title">Field Tree</h4>
|
|
4
|
+
<p class="fb-cfg-tree__subtitle">Select a field to configure</p>
|
|
5
|
+
</div>
|
|
6
|
+
|
|
7
|
+
<div class="fb-cfg-tree__scrollable">
|
|
8
|
+
@for (node of tree; track trackByNodeId($index, node)) {
|
|
9
|
+
<ng-container *ngTemplateOutlet="treeNodeTpl; context: { $implicit: node, depth: 0 }"></ng-container>
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
@if (tree.length === 0) {
|
|
13
|
+
<div class="fb-cfg-tree__empty">
|
|
14
|
+
No fields to configure.
|
|
15
|
+
</div>
|
|
16
|
+
}
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<!-- Recursive tree node template -->
|
|
21
|
+
<ng-template #treeNodeTpl let-node let-depth="depth">
|
|
22
|
+
@if (node.type === 'field') {
|
|
23
|
+
<!-- Field node (clickable leaf) -->
|
|
24
|
+
<div
|
|
25
|
+
class="fb-cfg-tree-item"
|
|
26
|
+
[class.fb-cfg-tree-item--active]="isSelected(node)"
|
|
27
|
+
(click)="onFieldSelect(node)"
|
|
28
|
+
[style.padding-left.px]="16 + depth * 16"
|
|
29
|
+
>
|
|
30
|
+
<span class="fb-cfg-tree-item__drag" title="Drag to reorder">
|
|
31
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
|
32
|
+
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
33
|
+
<circle cx="9" cy="5" r="1"/><circle cx="9" cy="12" r="1"/>
|
|
34
|
+
<circle cx="9" cy="19" r="1"/><circle cx="15" cy="5" r="1"/>
|
|
35
|
+
<circle cx="15" cy="12" r="1"/><circle cx="15" cy="19" r="1"/>
|
|
36
|
+
</svg>
|
|
37
|
+
</span>
|
|
38
|
+
<div class="fb-cfg-tree-item__content">
|
|
39
|
+
<span class="fb-cfg-tree-item__label">{{ node.label }}</span>
|
|
40
|
+
@if (node.fieldConfig?.type) {
|
|
41
|
+
<span class="fb-cfg-tree-item__type">{{ node.fieldConfig.type }}</span>
|
|
42
|
+
}
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
} @else {
|
|
46
|
+
<!-- Group or Section node (expandable) -->
|
|
47
|
+
<div
|
|
48
|
+
class="fb-cfg-tree-section"
|
|
49
|
+
[style.padding-left.px]="8 + depth * 12"
|
|
50
|
+
>
|
|
51
|
+
<div class="fb-cfg-tree-section__header" (click)="onNodeClick(node)">
|
|
52
|
+
<div class="fb-cfg-tree-section__title-group">
|
|
53
|
+
<span
|
|
54
|
+
class="fb-cfg-tree-section__chevron"
|
|
55
|
+
[class.fb-cfg-tree-section__chevron--expanded]="node.expanded"
|
|
56
|
+
>
|
|
57
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
|
58
|
+
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
59
|
+
<polyline points="9 18 15 12 9 6"></polyline>
|
|
60
|
+
</svg>
|
|
61
|
+
</span>
|
|
62
|
+
<span class="fb-cfg-tree-section__label">{{ node.label }}</span>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
@if (node.expanded && node.children.length > 0) {
|
|
67
|
+
<div class="fb-cfg-tree-section__children">
|
|
68
|
+
@for (child of node.children; track trackByNodeId($index, child)) {
|
|
69
|
+
<ng-container *ngTemplateOutlet="treeNodeTpl; context: { $implicit: child, depth: depth + 1 }"></ng-container>
|
|
70
|
+
}
|
|
71
|
+
</div>
|
|
72
|
+
}
|
|
73
|
+
</div>
|
|
74
|
+
}
|
|
75
|
+
</ng-template>
|