@acorex/platform 0.0.0-ACOREX

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 (116) hide show
  1. package/README.md +7 -0
  2. package/auth/README.md +3 -0
  3. package/common/README.md +3 -0
  4. package/core/README.md +4 -0
  5. package/fesm2022/acorex-platform-auth.mjs +1362 -0
  6. package/fesm2022/acorex-platform-auth.mjs.map +1 -0
  7. package/fesm2022/acorex-platform-common-common-settings.provider-G9XcXXOG.mjs +127 -0
  8. package/fesm2022/acorex-platform-common-common-settings.provider-G9XcXXOG.mjs.map +1 -0
  9. package/fesm2022/acorex-platform-common.mjs +4601 -0
  10. package/fesm2022/acorex-platform-common.mjs.map +1 -0
  11. package/fesm2022/acorex-platform-core.mjs +4374 -0
  12. package/fesm2022/acorex-platform-core.mjs.map +1 -0
  13. package/fesm2022/acorex-platform-domain.mjs +3234 -0
  14. package/fesm2022/acorex-platform-domain.mjs.map +1 -0
  15. package/fesm2022/acorex-platform-layout-builder.mjs +2847 -0
  16. package/fesm2022/acorex-platform-layout-builder.mjs.map +1 -0
  17. package/fesm2022/acorex-platform-layout-components-binding-expression-editor-popup.component-CXEdvDTf.mjs +121 -0
  18. package/fesm2022/acorex-platform-layout-components-binding-expression-editor-popup.component-CXEdvDTf.mjs.map +1 -0
  19. package/fesm2022/acorex-platform-layout-components.mjs +8583 -0
  20. package/fesm2022/acorex-platform-layout-components.mjs.map +1 -0
  21. package/fesm2022/acorex-platform-layout-designer.mjs +2474 -0
  22. package/fesm2022/acorex-platform-layout-designer.mjs.map +1 -0
  23. package/fesm2022/acorex-platform-layout-entity.mjs +19150 -0
  24. package/fesm2022/acorex-platform-layout-entity.mjs.map +1 -0
  25. package/fesm2022/acorex-platform-layout-views.mjs +1468 -0
  26. package/fesm2022/acorex-platform-layout-views.mjs.map +1 -0
  27. package/fesm2022/acorex-platform-layout-widget-core.mjs +2950 -0
  28. package/fesm2022/acorex-platform-layout-widget-core.mjs.map +1 -0
  29. package/fesm2022/acorex-platform-layout-widgets-button-widget-designer.component-Dy7jF-oD.mjs +72 -0
  30. package/fesm2022/acorex-platform-layout-widgets-button-widget-designer.component-Dy7jF-oD.mjs.map +1 -0
  31. package/fesm2022/acorex-platform-layout-widgets-file-list-popup.component-9uCkMxcc.mjs +158 -0
  32. package/fesm2022/acorex-platform-layout-widgets-file-list-popup.component-9uCkMxcc.mjs.map +1 -0
  33. package/fesm2022/acorex-platform-layout-widgets-image-preview.popup-C_EPAvCU.mjs +29 -0
  34. package/fesm2022/acorex-platform-layout-widgets-image-preview.popup-C_EPAvCU.mjs.map +1 -0
  35. package/fesm2022/acorex-platform-layout-widgets-page-widget-designer.component-D10yO28c.mjs +172 -0
  36. package/fesm2022/acorex-platform-layout-widgets-page-widget-designer.component-D10yO28c.mjs.map +1 -0
  37. package/fesm2022/acorex-platform-layout-widgets-repeater-widget-column.component-BGQqY5Mw.mjs +111 -0
  38. package/fesm2022/acorex-platform-layout-widgets-repeater-widget-column.component-BGQqY5Mw.mjs.map +1 -0
  39. package/fesm2022/acorex-platform-layout-widgets-tabular-data-edit-popup.component-DmzNTYiS.mjs +274 -0
  40. package/fesm2022/acorex-platform-layout-widgets-tabular-data-edit-popup.component-DmzNTYiS.mjs.map +1 -0
  41. package/fesm2022/acorex-platform-layout-widgets-tabular-data-view-popup.component-BNG_588B.mjs +64 -0
  42. package/fesm2022/acorex-platform-layout-widgets-tabular-data-view-popup.component-BNG_588B.mjs.map +1 -0
  43. package/fesm2022/acorex-platform-layout-widgets-text-block-widget-designer.component-Vo4fWHtX.mjs +34 -0
  44. package/fesm2022/acorex-platform-layout-widgets-text-block-widget-designer.component-Vo4fWHtX.mjs.map +1 -0
  45. package/fesm2022/acorex-platform-layout-widgets.mjs +29791 -0
  46. package/fesm2022/acorex-platform-layout-widgets.mjs.map +1 -0
  47. package/fesm2022/acorex-platform-native.mjs +155 -0
  48. package/fesm2022/acorex-platform-native.mjs.map +1 -0
  49. package/fesm2022/acorex-platform-runtime-catalog-command-definition.mjs +20 -0
  50. package/fesm2022/acorex-platform-runtime-catalog-command-definition.mjs.map +1 -0
  51. package/fesm2022/acorex-platform-runtime-catalog-query-definition.mjs +20 -0
  52. package/fesm2022/acorex-platform-runtime-catalog-query-definition.mjs.map +1 -0
  53. package/fesm2022/acorex-platform-runtime.mjs +899 -0
  54. package/fesm2022/acorex-platform-runtime.mjs.map +1 -0
  55. package/fesm2022/acorex-platform-themes-default-entity-master-create-view.component-Cvvr4HnL.mjs +160 -0
  56. package/fesm2022/acorex-platform-themes-default-entity-master-create-view.component-Cvvr4HnL.mjs.map +1 -0
  57. package/fesm2022/acorex-platform-themes-default-entity-master-modify-view.component-TYoLN1Jq.mjs +120 -0
  58. package/fesm2022/acorex-platform-themes-default-entity-master-modify-view.component-TYoLN1Jq.mjs.map +1 -0
  59. package/fesm2022/acorex-platform-themes-default-entity-master-single-view.component-C2z5Lq9y.mjs +237 -0
  60. package/fesm2022/acorex-platform-themes-default-entity-master-single-view.component-C2z5Lq9y.mjs.map +1 -0
  61. package/fesm2022/acorex-platform-themes-default-error-401.component-C7EYJzSr.mjs +31 -0
  62. package/fesm2022/acorex-platform-themes-default-error-401.component-C7EYJzSr.mjs.map +1 -0
  63. package/fesm2022/acorex-platform-themes-default-error-404.component-7MVLMwIa.mjs +25 -0
  64. package/fesm2022/acorex-platform-themes-default-error-404.component-7MVLMwIa.mjs.map +1 -0
  65. package/fesm2022/acorex-platform-themes-default-error-offline.component-DR6G8gPC.mjs +19 -0
  66. package/fesm2022/acorex-platform-themes-default-error-offline.component-DR6G8gPC.mjs.map +1 -0
  67. package/fesm2022/acorex-platform-themes-default.mjs +2589 -0
  68. package/fesm2022/acorex-platform-themes-default.mjs.map +1 -0
  69. package/fesm2022/acorex-platform-themes-shared-icon-chooser-column.component-CqkWJYdv.mjs +55 -0
  70. package/fesm2022/acorex-platform-themes-shared-icon-chooser-column.component-CqkWJYdv.mjs.map +1 -0
  71. package/fesm2022/acorex-platform-themes-shared-icon-chooser-view.component-BOTuLdWN.mjs +57 -0
  72. package/fesm2022/acorex-platform-themes-shared-icon-chooser-view.component-BOTuLdWN.mjs.map +1 -0
  73. package/fesm2022/acorex-platform-themes-shared-settings.provider-DSs1o1M6.mjs +168 -0
  74. package/fesm2022/acorex-platform-themes-shared-settings.provider-DSs1o1M6.mjs.map +1 -0
  75. package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-column.component-CHfrTtol.mjs +65 -0
  76. package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-column.component-CHfrTtol.mjs.map +1 -0
  77. package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-view.component-BSmvnUVq.mjs +64 -0
  78. package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-view.component-BSmvnUVq.mjs.map +1 -0
  79. package/fesm2022/acorex-platform-themes-shared.mjs +2125 -0
  80. package/fesm2022/acorex-platform-themes-shared.mjs.map +1 -0
  81. package/fesm2022/acorex-platform-workflow.mjs +2501 -0
  82. package/fesm2022/acorex-platform-workflow.mjs.map +1 -0
  83. package/fesm2022/acorex-platform.mjs +6 -0
  84. package/fesm2022/acorex-platform.mjs.map +1 -0
  85. package/layout/builder/README.md +1578 -0
  86. package/layout/components/README.md +3 -0
  87. package/layout/designer/README.md +4 -0
  88. package/layout/entity/README.md +4 -0
  89. package/layout/views/README.md +3 -0
  90. package/layout/widget-core/README.md +4 -0
  91. package/layout/widgets/README.md +3 -0
  92. package/native/README.md +4 -0
  93. package/package.json +103 -0
  94. package/runtime/README.md +3 -0
  95. package/themes/default/README.md +3 -0
  96. package/themes/shared/README.md +3 -0
  97. package/types/acorex-platform-auth.d.ts +680 -0
  98. package/types/acorex-platform-common.d.ts +2926 -0
  99. package/types/acorex-platform-core.d.ts +2896 -0
  100. package/types/acorex-platform-domain.d.ts +2353 -0
  101. package/types/acorex-platform-layout-builder.d.ts +926 -0
  102. package/types/acorex-platform-layout-components.d.ts +2903 -0
  103. package/types/acorex-platform-layout-designer.d.ts +422 -0
  104. package/types/acorex-platform-layout-entity.d.ts +3189 -0
  105. package/types/acorex-platform-layout-views.d.ts +667 -0
  106. package/types/acorex-platform-layout-widget-core.d.ts +1086 -0
  107. package/types/acorex-platform-layout-widgets.d.ts +5478 -0
  108. package/types/acorex-platform-native.d.ts +28 -0
  109. package/types/acorex-platform-runtime-catalog-command-definition.d.ts +137 -0
  110. package/types/acorex-platform-runtime-catalog-query-definition.d.ts +125 -0
  111. package/types/acorex-platform-runtime.d.ts +470 -0
  112. package/types/acorex-platform-themes-default.d.ts +573 -0
  113. package/types/acorex-platform-themes-shared.d.ts +170 -0
  114. package/types/acorex-platform-workflow.d.ts +1806 -0
  115. package/types/acorex-platform.d.ts +2 -0
  116. package/workflow/README.md +4 -0
@@ -0,0 +1,2847 @@
1
+ import * as i5 from '@angular/common';
2
+ import { CommonModule } from '@angular/common';
3
+ import * as i0 from '@angular/core';
4
+ import { Injectable, inject, input, model, signal, effect, output, viewChild, ChangeDetectionStrategy, Component, NgModule, EventEmitter, Output } from '@angular/core';
5
+ import { provideCommandSetups } from '@acorex/platform/runtime';
6
+ import { AXPopupService } from '@acorex/components/popup';
7
+ import * as i1 from '@acorex/platform/layout/widget-core';
8
+ import { AXPWidgetSerializationHelper, AXPWidgetContainerComponent, AXPPageStatus, AXPWidgetCoreModule, AXPWidgetRegistryService } from '@acorex/platform/layout/widget-core';
9
+ import { cloneDeep, isNil, set, isEqual } from 'lodash-es';
10
+ import * as i2 from '@acorex/components/form';
11
+ import { AXFormComponent, AXFormModule } from '@acorex/components/form';
12
+ import { Subject, debounceTime, distinctUntilChanged, startWith } from 'rxjs';
13
+ import * as i1$1 from '@acorex/components/button';
14
+ import { AXButtonModule } from '@acorex/components/button';
15
+ import * as i2$1 from '@acorex/components/decorators';
16
+ import { AXDecoratorModule } from '@acorex/components/decorators';
17
+ import * as i3 from '@acorex/components/loading';
18
+ import { AXLoadingModule } from '@acorex/components/loading';
19
+ import { AXBasePageComponent } from '@acorex/components/page';
20
+ import * as i6 from '@acorex/core/translation';
21
+ import { AXTranslationModule, AXTranslationService } from '@acorex/core/translation';
22
+ import * as i4 from '@acorex/platform/core';
23
+ import { AXPExpressionEvaluatorService, AXPComponentSlotModule, AXPContextStore, AXPMultiLanguageStringResolverService } from '@acorex/platform/core';
24
+ import { AXP_ENTITY_DEFINITION_CRUD_SERVICE } from '@acorex/platform/domain';
25
+
26
+ class AXPLayoutConversionService {
27
+ constructor() {
28
+ //#region ---- Caching ----
29
+ this.widgetTreeCache = new Map();
30
+ this.formDefinitionCache = new Map();
31
+ }
32
+ //#endregion
33
+ //#region ---- Public Methods ----
34
+ /**
35
+ * Convert AXPDynamicFormDefinition to AXPWidgetNode tree structure
36
+ * Groups become Fieldset Layouts with Form Field widgets as children
37
+ * Fields become Form Field widgets with Editor widgets as children
38
+ */
39
+ convertFormDefinition(formDefinition) {
40
+ // Create cache key based on form definition content
41
+ const cacheKey = this.createFormDefinitionCacheKey(formDefinition);
42
+ // Check cache first
43
+ if (this.widgetTreeCache.has(cacheKey)) {
44
+ return this.widgetTreeCache.get(cacheKey);
45
+ }
46
+ // Generate widget tree
47
+ const widgetTree = {
48
+ type: 'grid-layout',
49
+ name: 'dynamic-form-container',
50
+ mode: formDefinition.mode, // Preserve form-level mode
51
+ options: {
52
+ title: 'Dynamic Form',
53
+ grid: {
54
+ default: {
55
+ columns: 1,
56
+ gap: '1rem',
57
+ },
58
+ },
59
+ },
60
+ children: formDefinition.groups.map((group) => this.createGroupAsFieldsetWidget(group, formDefinition.mode)),
61
+ };
62
+ // Cache the result
63
+ this.widgetTreeCache.set(cacheKey, widgetTree);
64
+ return widgetTree;
65
+ }
66
+ /**
67
+ * Convert AXPWidgetNode tree back to AXPDynamicFormDefinition
68
+ * Parses Fieldset Layouts back to Groups
69
+ * Parses Form Field widgets back to Fields
70
+ */
71
+ convertWidgetTreeToFormDefinition(widgetTree) {
72
+ // Create cache key based on widget tree content
73
+ const cacheKey = this.createWidgetTreeCacheKey(widgetTree);
74
+ // Check cache first
75
+ if (this.formDefinitionCache.has(cacheKey)) {
76
+ return this.formDefinitionCache.get(cacheKey);
77
+ }
78
+ // Parse widget tree
79
+ const groups = [];
80
+ if (widgetTree.children) {
81
+ widgetTree.children.forEach((child) => {
82
+ if (child.type === 'fieldset-layout') {
83
+ const group = this.extractGroupFromFieldset(child);
84
+ groups.push(group);
85
+ }
86
+ });
87
+ }
88
+ const formDefinition = { groups };
89
+ // Cache the result
90
+ this.formDefinitionCache.set(cacheKey, formDefinition);
91
+ return formDefinition;
92
+ }
93
+ /**
94
+ * Validate that a widget tree represents a valid dynamic form structure
95
+ */
96
+ validateFormWidgetTree(widgetTree) {
97
+ if (!widgetTree || widgetTree.type !== 'grid-layout') {
98
+ return false;
99
+ }
100
+ if (!widgetTree.children || widgetTree.children.length === 0) {
101
+ return true; // Empty form is valid
102
+ }
103
+ // Check that all children are fieldset-layout widgets
104
+ return widgetTree.children.every((child) => child.type === 'fieldset-layout' &&
105
+ child.children &&
106
+ child.children.every((formField) => formField.type === 'form-field' && formField.children && formField.children.length > 0));
107
+ }
108
+ /**
109
+ * Clear all caches
110
+ */
111
+ clearCaches() {
112
+ this.widgetTreeCache.clear();
113
+ this.formDefinitionCache.clear();
114
+ }
115
+ /**
116
+ * Get cache statistics
117
+ */
118
+ getCacheStats() {
119
+ return {
120
+ widgetTreeCacheSize: this.widgetTreeCache.size,
121
+ formDefinitionCacheSize: this.formDefinitionCache.size,
122
+ };
123
+ }
124
+ //#endregion
125
+ //#region ---- Private Methods ----
126
+ /**
127
+ * Convert a single group to Fieldset widget structure
128
+ */
129
+ createGroupAsFieldsetWidget(group, formMode) {
130
+ // Determine columns count from layout or default to 1
131
+ const columnsCount = 1;
132
+ // Use group mode if set, otherwise inherit from form mode
133
+ const groupMode = group.mode || formMode;
134
+ return {
135
+ type: 'fieldset-layout',
136
+ name: group.name,
137
+ mode: groupMode,
138
+ options: {
139
+ title: group.title,
140
+ description: group.description,
141
+ cols: columnsCount,
142
+ look: group.look || 'container', // Default to 'container' if not specified
143
+ },
144
+ children: this.createFieldWidgets(group.parameters, columnsCount, groupMode),
145
+ };
146
+ }
147
+ /**
148
+ * Convert fields to Form Field widgets
149
+ */
150
+ createFieldWidgets(fields, columnsCount, groupMode) {
151
+ return fields.map((field) => this.createFormFieldWidget(field, groupMode));
152
+ }
153
+ /**
154
+ * Convert a single field to Form Field widget with editor as child
155
+ */
156
+ createFormFieldWidget(field, groupMode) {
157
+ // Use field mode if set, otherwise inherit from group mode
158
+ const fieldMode = field.mode || groupMode;
159
+ // Ensure the editor widget also has the mode set
160
+ const editorWidget = { ...field.widget };
161
+ if (!editorWidget.mode) {
162
+ editorWidget.mode = fieldMode;
163
+ }
164
+ return {
165
+ type: 'form-field',
166
+ name: field.path,
167
+ mode: fieldMode,
168
+ options: {
169
+ label: field.title,
170
+ badge: field.badge,
171
+ description: field.description,
172
+ showLabel: true,
173
+ },
174
+ children: [editorWidget], // The editor widget becomes a child of form-field
175
+ };
176
+ }
177
+ /**
178
+ * Extract group information from Fieldset Layout widget
179
+ */
180
+ extractGroupFromFieldset(fieldsetNode) {
181
+ const columnsCount = fieldsetNode.options?.['cols'] || 1;
182
+ // Extract fields directly from fieldset children
183
+ const fields = [];
184
+ if (fieldsetNode.children) {
185
+ fieldsetNode.children.forEach((formField) => {
186
+ if (formField.type === 'form-field' && formField.children && formField.children.length > 0) {
187
+ const field = this.extractFieldFromFormWidget(formField);
188
+ if (field) {
189
+ fields.push(field);
190
+ }
191
+ }
192
+ });
193
+ }
194
+ return {
195
+ name: fieldsetNode.name || `group-${Date.now()}`,
196
+ title: fieldsetNode.options?.['title'],
197
+ description: fieldsetNode.options?.['description'],
198
+ parameters: fields,
199
+ mode: fieldsetNode.mode,
200
+ look: fieldsetNode.options?.['look'],
201
+ };
202
+ }
203
+ /**
204
+ * Extract field information from Form Field widget
205
+ */
206
+ extractFieldFromFormWidget(formFieldNode) {
207
+ if (!formFieldNode.children || formFieldNode.children.length === 0) {
208
+ return null;
209
+ }
210
+ const editorWidget = formFieldNode.children[0];
211
+ return {
212
+ path: formFieldNode.name || editorWidget.name || `field-${Date.now()}`,
213
+ title: formFieldNode.options?.['label'],
214
+ badge: formFieldNode.options?.['badge'],
215
+ description: formFieldNode.options?.['description'],
216
+ widget: editorWidget,
217
+ mode: formFieldNode.mode,
218
+ };
219
+ }
220
+ /**
221
+ * Create cache key for form definition
222
+ */
223
+ createFormDefinitionCacheKey(formDefinition) {
224
+ // Create a hash-like key instead of full JSON string
225
+ const keyParts = [];
226
+ keyParts.push(`groups:${formDefinition.groups.length}`);
227
+ formDefinition.groups.forEach((group, groupIndex) => {
228
+ // Include group.mode so view vs edit (or mixed) layouts do not share a cached widget tree.
229
+ const groupModePart = group.mode ?? '_';
230
+ keyParts.push(`g${groupIndex}:${group.name}:${group.parameters.length}:${groupModePart}`);
231
+ keyParts.push(`gL${groupIndex}:${JSON.stringify(group.title ?? null)}:${JSON.stringify(group.description ?? null)}`);
232
+ group.parameters.forEach((param, paramIndex) => {
233
+ // Field mode must be part of the key; otherwise metadata forms that only differ by
234
+ // view/edit (same paths and widget types) incorrectly reuse the first cached tree.
235
+ const fieldModePart = param.mode ?? '_';
236
+ keyParts.push(`p${groupIndex}.${paramIndex}:${param.path}:${param.widget.type}:${fieldModePart}`);
237
+ keyParts.push(`pL${groupIndex}.${paramIndex}:${JSON.stringify(param.title ?? null)}:${JSON.stringify(param.description ?? null)}:${JSON.stringify(param.badge ?? null)}`);
238
+ });
239
+ });
240
+ if (formDefinition.mode) {
241
+ keyParts.push(`mode:${formDefinition.mode}`);
242
+ }
243
+ // Join with delimiter and create a shorter hash
244
+ const keyString = keyParts.join('|');
245
+ // If still too long, create a simple hash
246
+ if (keyString.length > 100) {
247
+ return this.createSimpleHash(keyString);
248
+ }
249
+ return keyString;
250
+ }
251
+ /**
252
+ * Create cache key for widget tree
253
+ */
254
+ createWidgetTreeCacheKey(widgetTree) {
255
+ // Create a hash-like key instead of full JSON string
256
+ const keyParts = [];
257
+ keyParts.push(`type:${widgetTree.type}`);
258
+ if (widgetTree.name) {
259
+ keyParts.push(`name:${widgetTree.name}`);
260
+ }
261
+ if (widgetTree.children) {
262
+ keyParts.push(`children:${widgetTree.children.length}`);
263
+ widgetTree.children.forEach((child, index) => {
264
+ keyParts.push(`c${index}:${child.type}`);
265
+ if (child.children) {
266
+ keyParts.push(`cc${index}:${child.children.length}`);
267
+ child.children.forEach((grandChild, gIndex) => {
268
+ keyParts.push(`gc${index}.${gIndex}:${grandChild.type}`);
269
+ if (grandChild.children) {
270
+ keyParts.push(`gcc${index}.${gIndex}:${grandChild.children.length}`);
271
+ }
272
+ });
273
+ }
274
+ });
275
+ }
276
+ // Join with delimiter and create a shorter hash
277
+ const keyString = keyParts.join('|');
278
+ // If still too long, create a simple hash
279
+ if (keyString.length > 100) {
280
+ return this.createSimpleHash(keyString);
281
+ }
282
+ return keyString;
283
+ }
284
+ /**
285
+ * Create a simple hash from a string
286
+ */
287
+ createSimpleHash(str) {
288
+ let hash = 0;
289
+ if (str.length === 0)
290
+ return hash.toString();
291
+ for (let i = 0; i < str.length; i++) {
292
+ const char = str.charCodeAt(i);
293
+ hash = (hash << 5) - hash + char;
294
+ hash = hash & hash; // Convert to 32-bit integer
295
+ }
296
+ return Math.abs(hash).toString(36); // Convert to base36 for shorter string
297
+ }
298
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLayoutConversionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
299
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLayoutConversionService, providedIn: 'root' }); }
300
+ }
301
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLayoutConversionService, decorators: [{
302
+ type: Injectable,
303
+ args: [{
304
+ providedIn: 'root',
305
+ }]
306
+ }] });
307
+
308
+ //#region ---- Inheritance Utilities ----
309
+ /**
310
+ * Resolves inherited properties from context and local values
311
+ */
312
+ function resolveInheritedProperties(context, localValues = {}) {
313
+ return {
314
+ mode: localValues.mode ?? context.mode ?? 'edit',
315
+ disabled: localValues.disabled ?? context.disabled,
316
+ readonly: localValues.readonly ?? context.readonly,
317
+ direction: localValues.direction ?? context.direction,
318
+ visible: localValues.visible ?? context.visible,
319
+ };
320
+ }
321
+ /**
322
+ * Merges inheritance context with local overrides
323
+ */
324
+ function mergeInheritanceContext(parentContext, localOverrides = {}) {
325
+ return {
326
+ ...parentContext,
327
+ ...localOverrides,
328
+ };
329
+ }
330
+ /**
331
+ * Generates a random string for path/name generation
332
+ */
333
+ function generateRandomId() {
334
+ return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
335
+ }
336
+ /**
337
+ * Converts label to a valid path/name
338
+ */
339
+ function labelToPath(label) {
340
+ return label
341
+ .toLowerCase()
342
+ .replace(/[^a-z0-9\s]/g, '') // Remove special characters
343
+ .replace(/\s+/g, '_') // Replace spaces with underscores
344
+ .substring(0, 50); // Limit length
345
+ }
346
+ /**
347
+ * Generates path for value widgets based on hierarchy
348
+ */
349
+ function generateValueWidgetPath(widgetName, formFieldName, formFieldLabel) {
350
+ // Priority: widget name -> form field name -> generated from label -> random
351
+ if (widgetName) {
352
+ return widgetName;
353
+ }
354
+ if (formFieldName) {
355
+ return formFieldName;
356
+ }
357
+ if (formFieldLabel) {
358
+ return labelToPath(formFieldLabel);
359
+ }
360
+ return generateRandomId();
361
+ }
362
+ /**
363
+ * Collects default values from widget node tree and merges them into context
364
+ * Only sets values for paths that don't already exist in the context
365
+ * @param node - The widget node to process
366
+ * @param context - The context object to modify in place (cloned at top level only)
367
+ * @param isTopLevel - Internal flag to track if this is the top-level call
368
+ */
369
+ function collectDefaultValues(node, context = {}, isTopLevel = true) {
370
+ // Clone context only at the top level, then modify in place for recursive calls
371
+ const result = isTopLevel ? cloneDeep(context) : context;
372
+ // Check if this node has a defaultValue and a path
373
+ // Note: We check for both node.defaultValue and also look in node.options.defaultValue as fallback
374
+ const defaultValue = node.defaultValue !== undefined ? node.defaultValue : node.options?.defaultValue;
375
+ if (defaultValue !== undefined && !isNil(defaultValue) && node.path) {
376
+ // Check if path exists in context using lodash get equivalent check
377
+ const currentValue = getNestedValue(result, node.path);
378
+ if (currentValue === undefined) {
379
+ // Clone the defaultValue to avoid reference issues (especially for Date objects)
380
+ const clonedValue = defaultValue instanceof Date ? new Date(defaultValue.getTime()) : cloneDeep(defaultValue);
381
+ set(result, node.path, clonedValue);
382
+ }
383
+ }
384
+ // Recursively process children - pass result object to accumulate values in place
385
+ if (node.children && Array.isArray(node.children)) {
386
+ for (const child of node.children) {
387
+ collectDefaultValues(child, result, false);
388
+ }
389
+ }
390
+ return result;
391
+ }
392
+ /**
393
+ * Gets a nested value from an object using dot notation path
394
+ */
395
+ function getNestedValue(obj, path) {
396
+ const keys = path.split('.');
397
+ let current = obj;
398
+ for (const key of keys) {
399
+ if (current === undefined || current === null || typeof current !== 'object') {
400
+ return undefined;
401
+ }
402
+ current = current[key];
403
+ }
404
+ return current;
405
+ }
406
+ //#endregion
407
+ //#region ---- Service Implementation ----
408
+ class AXPLayoutBuilderService {
409
+ constructor() {
410
+ this.popupService = inject(AXPopupService);
411
+ }
412
+ /**
413
+ * Create a new layout builder
414
+ */
415
+ create() {
416
+ return new LayoutBuilder(this.popupService);
417
+ }
418
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLayoutBuilderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
419
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLayoutBuilderService, providedIn: 'root' }); }
420
+ }
421
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLayoutBuilderService, decorators: [{
422
+ type: Injectable,
423
+ args: [{
424
+ providedIn: 'root',
425
+ }]
426
+ }] });
427
+ //#endregion
428
+ //#region ---- Layout Builder Implementation ----
429
+ /**
430
+ * Main layout builder - Single Responsibility: Layout orchestration
431
+ * Open/Closed: Extensible through container delegates
432
+ */
433
+ class LayoutBuilder {
434
+ constructor(popupService) {
435
+ this.popupService = popupService;
436
+ this.root = {
437
+ children: [],
438
+ mode: 'edit',
439
+ type: 'flex-layout',
440
+ };
441
+ this.inheritanceContext = {
442
+ mode: 'edit',
443
+ };
444
+ }
445
+ // Predefined layout containers at root level - delegate pattern required
446
+ grid(delegate) {
447
+ const container = new GridContainerBuilder();
448
+ container.withInheritanceContext(this.inheritanceContext);
449
+ if (delegate) {
450
+ delegate(container);
451
+ }
452
+ //this.state.children!.push(container.build());
453
+ this.root = container.build();
454
+ return this;
455
+ }
456
+ flex(delegate) {
457
+ const container = new FlexContainerBuilder();
458
+ container.withInheritanceContext(this.inheritanceContext);
459
+ if (delegate) {
460
+ delegate(container);
461
+ }
462
+ this.root.children.push(container.build());
463
+ return this;
464
+ }
465
+ panel(delegate) {
466
+ const container = new PanelContainerBuilder();
467
+ container.withInheritanceContext(this.inheritanceContext);
468
+ if (delegate) {
469
+ delegate(container);
470
+ }
471
+ this.root.children.push(container.build());
472
+ return this;
473
+ }
474
+ page(delegate) {
475
+ const container = new PageContainerBuilder();
476
+ container.withInheritanceContext(this.inheritanceContext);
477
+ if (delegate) {
478
+ delegate(container);
479
+ }
480
+ this.root.children.push(container.build());
481
+ return this;
482
+ }
483
+ tabset(delegate) {
484
+ const container = new TabsetContainerBuilder();
485
+ container.withInheritanceContext(this.inheritanceContext);
486
+ if (delegate) {
487
+ delegate(container);
488
+ }
489
+ this.root.children.push(container.build());
490
+ return this;
491
+ }
492
+ fieldset(delegate) {
493
+ const container = new FieldsetContainerBuilder();
494
+ container.withInheritanceContext(this.inheritanceContext);
495
+ if (delegate) {
496
+ delegate(container);
497
+ }
498
+ this.root.children.push(container.build());
499
+ return this;
500
+ }
501
+ dialog(delegate) {
502
+ if (!this.popupService) {
503
+ throw new Error('LayoutBuilder requires AXPopupService to create dialogs. Please inject it in the service constructor.');
504
+ }
505
+ const container = new DialogContainerBuilder(this.popupService);
506
+ if (delegate) {
507
+ delegate(container);
508
+ }
509
+ return container;
510
+ }
511
+ stepWizard(delegate) {
512
+ const wizard = new StepWizardBuilder();
513
+ wizard.withInheritanceContext(this.inheritanceContext);
514
+ if (delegate) {
515
+ delegate(wizard);
516
+ }
517
+ this.root.children.push(wizard.build());
518
+ return this;
519
+ }
520
+ formField(label, delegate) {
521
+ const field = new FormFieldBuilder(label);
522
+ field.withInheritanceContext(this.inheritanceContext);
523
+ if (delegate) {
524
+ delegate(field);
525
+ }
526
+ this.root.children.push(field.build());
527
+ return this;
528
+ }
529
+ build() {
530
+ return {
531
+ type: this.root.type,
532
+ children: this.root.children,
533
+ mode: this.root.mode,
534
+ };
535
+ }
536
+ /**
537
+ * Converts the built widget node to JSON string
538
+ * @param options - Serialization options
539
+ * @returns JSON string representation of the widget node
540
+ */
541
+ toJson(options) {
542
+ const node = this.build();
543
+ return AXPWidgetSerializationHelper.toJson(node, options);
544
+ }
545
+ }
546
+ //#endregion
547
+ //#region ---- Container Builder Implementation ----
548
+ /**
549
+ * Abstract base container builder - Open/Closed Principle
550
+ * Provides common functionality for all container types
551
+ */
552
+ class BaseContainerBuilder {
553
+ constructor(containerType) {
554
+ this.containerState = {
555
+ mode: 'edit',
556
+ type: 'flex-layout',
557
+ children: [],
558
+ };
559
+ this.inheritanceContext = {};
560
+ this.containerState.type = containerType;
561
+ }
562
+ // Base methods shared by all containers
563
+ ensureChildren() {
564
+ this.containerState.children = this.containerState.children || [];
565
+ }
566
+ addWidget(type, options) {
567
+ const child = new WidgetBuilder();
568
+ child.type(type);
569
+ // For value widgets, ensure path is provided
570
+ const widgetOptions = options ?? {};
571
+ const path = widgetOptions.path;
572
+ const name = widgetOptions.name;
573
+ if (this.isValueWidget(type) && !path) {
574
+ throw new Error(`Value widget '${type}' requires a 'path' property`);
575
+ }
576
+ // Set name and path in widget state, not options
577
+ if (name) {
578
+ child.name(name);
579
+ }
580
+ if (path) {
581
+ child.path(path);
582
+ }
583
+ // Remove name and path from options
584
+ const { name: _, path: __, ...cleanOptions } = widgetOptions;
585
+ // IMPORTANT: Apply inheritance context BEFORE setting options
586
+ // This ensures that inherited properties (readonly, disabled, etc.) are applied first
587
+ // Then user-provided options will override them if explicitly set
588
+ child.withInheritanceContext(this.inheritanceContext);
589
+ child.options(cleanOptions);
590
+ this.ensureChildren();
591
+ this.containerState.children.push(child.build());
592
+ return this;
593
+ }
594
+ isValueWidget(type) {
595
+ const valueWidgetTypes = [
596
+ 'text-editor',
597
+ 'large-text-editor',
598
+ 'rich-text-editor',
599
+ 'password-editor',
600
+ 'number-editor',
601
+ 'select-editor',
602
+ 'lookup-editor',
603
+ 'entity-definition-provider-editor',
604
+ 'selection-list-editor',
605
+ 'date-time-editor',
606
+ 'toggle-editor',
607
+ 'color-editor',
608
+ ];
609
+ return valueWidgetTypes.includes(type);
610
+ }
611
+ build() {
612
+ const result = {
613
+ type: this.containerState.type,
614
+ children: this.containerState.children,
615
+ options: this.containerState.options,
616
+ mode: this.containerState.mode,
617
+ visible: this.containerState.visible,
618
+ };
619
+ // Add name with _form_field suffix for form fields
620
+ if (this.containerState.type === 'form-field') {
621
+ if (this.containerState.name) {
622
+ result.name = this.containerState.name;
623
+ }
624
+ else if (this.containerState.options?.['label']) {
625
+ result.name = labelToPath(this.containerState.options['label']) + '_form_field';
626
+ }
627
+ // Form fields don't have path
628
+ }
629
+ else {
630
+ // Other containers can have name and path
631
+ if (this.containerState.name) {
632
+ result.name = this.containerState.name;
633
+ }
634
+ if (this.containerState.path) {
635
+ result.path = this.containerState.path;
636
+ }
637
+ if (this.containerState.defaultValue !== undefined) {
638
+ result.defaultValue = this.containerState.defaultValue;
639
+ }
640
+ }
641
+ return result;
642
+ }
643
+ }
644
+ /**
645
+ * Base container builder mixin - Interface Segregation Principle
646
+ * Provides common container operations
647
+ */
648
+ class BaseContainerMixin extends BaseContainerBuilder {
649
+ name(name) {
650
+ this.containerState.name = name;
651
+ return this;
652
+ }
653
+ path(path) {
654
+ this.containerState.path = path;
655
+ return this;
656
+ }
657
+ mode(mode) {
658
+ this.containerState.mode = mode;
659
+ this.inheritanceContext.mode = mode;
660
+ return this;
661
+ }
662
+ visible(condition) {
663
+ if (!this.containerState.options)
664
+ this.containerState.options = {};
665
+ this.containerState.options['visible'] = condition;
666
+ this.inheritanceContext.visible = condition;
667
+ return this;
668
+ }
669
+ disabled(condition) {
670
+ if (!this.containerState.options)
671
+ this.containerState.options = {};
672
+ this.containerState.options['disabled'] = condition;
673
+ this.inheritanceContext.disabled = condition;
674
+ return this;
675
+ }
676
+ readonly(condition) {
677
+ if (!this.containerState.options)
678
+ this.containerState.options = {};
679
+ this.containerState.options['readonly'] = condition;
680
+ this.inheritanceContext.readonly = condition;
681
+ return this;
682
+ }
683
+ direction(direction) {
684
+ if (!this.containerState.options)
685
+ this.containerState.options = {};
686
+ this.containerState.options['direction'] = direction;
687
+ this.inheritanceContext.direction = direction;
688
+ return this;
689
+ }
690
+ // Inheritance context methods
691
+ withInheritanceContext(context) {
692
+ this.inheritanceContext = mergeInheritanceContext(context);
693
+ return this;
694
+ }
695
+ getInheritanceContext() {
696
+ return { ...this.inheritanceContext };
697
+ }
698
+ }
699
+ /**
700
+ * Layout container mixin - Interface Segregation Principle
701
+ * Provides layout-specific operations
702
+ */
703
+ class LayoutContainerMixin extends BaseContainerMixin {
704
+ layout(value) {
705
+ // Map layout intent to grid item sizing so containers like `form-field`
706
+ // can span multiple columns/rows inside grid/fieldset layouts.
707
+ if (!this.containerState.options)
708
+ this.containerState.options = {};
709
+ if (typeof value === 'number') {
710
+ this.containerState.options.colSpan = value;
711
+ }
712
+ else if (value) {
713
+ const positions = value.positions;
714
+ if (positions) {
715
+ const placement = positions?.lg ?? positions?.xl ?? positions?.xxl ?? positions?.md ?? positions?.sm;
716
+ if (placement) {
717
+ const opts = this.containerState.options;
718
+ if (placement.colSpan != null)
719
+ opts.colSpan = placement.colSpan;
720
+ if (placement.colStart != null)
721
+ opts.colStart = placement.colStart;
722
+ if (placement.colEnd != null)
723
+ opts.colEnd = placement.colEnd;
724
+ if (placement.rowSpan != null)
725
+ opts.rowSpan = placement.rowSpan;
726
+ if (placement.rowStart != null)
727
+ opts.rowStart = placement.rowStart;
728
+ if (placement.rowEnd != null)
729
+ opts.rowEnd = placement.rowEnd;
730
+ }
731
+ }
732
+ }
733
+ return this;
734
+ }
735
+ }
736
+ /**
737
+ * Child container mixin - Interface Segregation Principle
738
+ * Provides child container management
739
+ */
740
+ class ChildContainerMixin extends LayoutContainerMixin {
741
+ grid(delegate) {
742
+ const container = new GridContainerBuilder();
743
+ container.withInheritanceContext(this.inheritanceContext);
744
+ if (delegate) {
745
+ delegate(container);
746
+ }
747
+ this.ensureChildren();
748
+ this.containerState.children.push(container.build());
749
+ return this;
750
+ }
751
+ flex(delegate) {
752
+ const container = new FlexContainerBuilder();
753
+ container.withInheritanceContext(this.inheritanceContext);
754
+ if (delegate) {
755
+ delegate(container);
756
+ }
757
+ this.ensureChildren();
758
+ this.containerState.children.push(container.build());
759
+ return this;
760
+ }
761
+ panel(delegate) {
762
+ const container = new PanelContainerBuilder();
763
+ container.withInheritanceContext(this.inheritanceContext);
764
+ if (delegate) {
765
+ delegate(container);
766
+ }
767
+ this.ensureChildren();
768
+ this.containerState.children.push(container.build());
769
+ return this;
770
+ }
771
+ page(delegate) {
772
+ const container = new PageContainerBuilder();
773
+ container.withInheritanceContext(this.inheritanceContext);
774
+ if (delegate) {
775
+ delegate(container);
776
+ }
777
+ this.ensureChildren();
778
+ this.containerState.children.push(container.build());
779
+ return this;
780
+ }
781
+ tabset(delegate) {
782
+ const container = new TabsetContainerBuilder();
783
+ container.withInheritanceContext(this.inheritanceContext);
784
+ if (delegate) {
785
+ delegate(container);
786
+ }
787
+ this.ensureChildren();
788
+ this.containerState.children.push(container.build());
789
+ return this;
790
+ }
791
+ fieldset(delegate) {
792
+ const container = new FieldsetContainerBuilder();
793
+ container.withInheritanceContext(this.inheritanceContext);
794
+ if (delegate) {
795
+ delegate(container);
796
+ }
797
+ this.ensureChildren();
798
+ this.containerState.children.push(container.build());
799
+ return this;
800
+ }
801
+ dialog(delegate) {
802
+ const container = new DialogContainerBuilder(); // Will use inject() fallback
803
+ if (delegate) {
804
+ delegate(container);
805
+ }
806
+ return container;
807
+ }
808
+ stepWizard(delegate) {
809
+ const wizard = new StepWizardBuilder();
810
+ wizard.withInheritanceContext(this.inheritanceContext);
811
+ if (delegate) {
812
+ delegate(wizard);
813
+ }
814
+ this.ensureChildren();
815
+ this.containerState.children.push(wizard.build());
816
+ return this;
817
+ }
818
+ formField(label, delegate) {
819
+ const field = new FormFieldBuilder(label);
820
+ field.withInheritanceContext(this.inheritanceContext);
821
+ if (delegate) {
822
+ delegate(field);
823
+ }
824
+ this.ensureChildren();
825
+ this.containerState.children.push(field.build());
826
+ return this;
827
+ }
828
+ dynamicForm(definition) {
829
+ // Get conversion service using inject() - works in any context in Angular 14+
830
+ let conversionService;
831
+ try {
832
+ conversionService = inject(AXPLayoutConversionService);
833
+ }
834
+ catch {
835
+ // Fallback: create service instance if inject() fails (shouldn't happen in normal usage)
836
+ conversionService = new AXPLayoutConversionService();
837
+ }
838
+ // Convert form definition to widget node
839
+ const widgetNode = conversionService.convertFormDefinition(definition);
840
+ // Add the widget node's children to this container
841
+ // The converted widget node has a root grid-layout, we want its children (fieldsets)
842
+ if (widgetNode.children && widgetNode.children.length > 0) {
843
+ this.ensureChildren();
844
+ this.containerState.children.push(...widgetNode.children);
845
+ }
846
+ return this;
847
+ }
848
+ }
849
+ /**
850
+ * Widget container mixin - Interface Segregation Principle
851
+ * Provides widget creation operations
852
+ */
853
+ class WidgetContainerMixin extends ChildContainerMixin {
854
+ textBox(options) {
855
+ this.addWidget('text-editor', options);
856
+ return this;
857
+ }
858
+ largeTextBox(options) {
859
+ this.addWidget('large-text-editor', options);
860
+ return this;
861
+ }
862
+ richText(options) {
863
+ this.addWidget('rich-text-editor', options);
864
+ return this;
865
+ }
866
+ passwordBox(options) {
867
+ this.addWidget('password-editor', options);
868
+ return this;
869
+ }
870
+ numberBox(options) {
871
+ this.addWidget('number-editor', options);
872
+ return this;
873
+ }
874
+ selectBox(options) {
875
+ this.addWidget('select-editor', options);
876
+ return this;
877
+ }
878
+ lookupBox(options) {
879
+ this.addWidget('lookup-editor', options);
880
+ return this;
881
+ }
882
+ selectionList(options) {
883
+ this.addWidget('selection-list-editor', options);
884
+ return this;
885
+ }
886
+ dateTimeBox(options) {
887
+ this.addWidget('date-time-editor', options);
888
+ return this;
889
+ }
890
+ toggleSwitch(options) {
891
+ this.addWidget('toggle-editor', options);
892
+ return this;
893
+ }
894
+ colorBox(options) {
895
+ this.addWidget('color-editor', options);
896
+ return this;
897
+ }
898
+ list(delegate) {
899
+ const container = new ListWidgetBuilder();
900
+ container.withInheritanceContext(this.inheritanceContext);
901
+ if (delegate) {
902
+ delegate(container);
903
+ }
904
+ this.ensureChildren();
905
+ this.containerState.children.push(container.build());
906
+ return this;
907
+ }
908
+ customWidget(type, optionsOrDelegate, delegate) {
909
+ const child = new WidgetBuilder();
910
+ child.type(type);
911
+ // Determine if second parameter is delegate or options
912
+ const isDelegate = typeof optionsOrDelegate === 'function';
913
+ const options = isDelegate ? undefined : optionsOrDelegate;
914
+ const actualDelegate = isDelegate ? optionsOrDelegate : delegate;
915
+ // Apply inheritance context BEFORE setting options or calling delegate
916
+ child.withInheritanceContext(this.inheritanceContext);
917
+ // If options are provided (non-delegate overload), handle them
918
+ if (!isDelegate && options !== undefined) {
919
+ const widgetOptions = options;
920
+ const path = widgetOptions.path;
921
+ const name = widgetOptions.name;
922
+ // Set name and path in widget state, not options
923
+ if (name) {
924
+ child.name(name);
925
+ }
926
+ if (path) {
927
+ child.path(path);
928
+ }
929
+ // Remove name and path from options
930
+ const { name: _, path: __, ...cleanOptions } = widgetOptions;
931
+ child.options(cleanOptions);
932
+ }
933
+ // If delegate is provided, call it to allow setting path, options, and other control methods
934
+ if (actualDelegate) {
935
+ actualDelegate(child);
936
+ }
937
+ // Build the widget to check if path is set (for value widgets)
938
+ const builtWidget = child.build();
939
+ // Check if value widget requires path
940
+ const valueWidgetTypes = [
941
+ 'text-editor',
942
+ 'large-text-editor',
943
+ 'rich-text-editor',
944
+ 'password-editor',
945
+ 'number-editor',
946
+ 'select-editor',
947
+ 'lookup-editor',
948
+ 'entity-definition-provider-editor',
949
+ 'selection-list-editor',
950
+ 'date-time-editor',
951
+ 'toggle-editor',
952
+ 'color-editor',
953
+ ];
954
+ if (valueWidgetTypes.includes(type) && !builtWidget.path) {
955
+ throw new Error(`Value widget '${type}' requires a 'path' property`);
956
+ }
957
+ this.ensureChildren();
958
+ this.containerState.children.push(builtWidget);
959
+ return this;
960
+ }
961
+ }
962
+ /**
963
+ * Flex Container Builder - Liskov Substitution Principle
964
+ * Extends WidgetContainerMixin to inherit all common functionality
965
+ */
966
+ class FlexContainerBuilder extends WidgetContainerMixin {
967
+ constructor() {
968
+ super('flex-layout');
969
+ }
970
+ setOptions(options) {
971
+ this.containerState.options = { ...this.containerState.options, ...options };
972
+ return this;
973
+ }
974
+ // Individual fluent methods for Flex
975
+ setDirection(direction) {
976
+ return this.setOptions({ flexDirection: direction });
977
+ }
978
+ setWrap(wrap) {
979
+ return this.setOptions({ flexWrap: wrap });
980
+ }
981
+ setJustifyContent(justify) {
982
+ return this.setOptions({ justifyContent: justify });
983
+ }
984
+ setAlignItems(align) {
985
+ return this.setOptions({ alignItems: align });
986
+ }
987
+ setGap(gap) {
988
+ return this.setOptions({ gap });
989
+ }
990
+ setBackgroundColor(color) {
991
+ return this.setOptions({ backgroundColor: color });
992
+ }
993
+ setPadding(padding) {
994
+ return this.setOptions({ spacing: { padding } });
995
+ }
996
+ setMargin(margin) {
997
+ return this.setOptions({ spacing: { margin } });
998
+ }
999
+ }
1000
+ /**
1001
+ * Grid Container Builder - Liskov Substitution Principle
1002
+ * Extends WidgetContainerMixin to inherit all common functionality
1003
+ */
1004
+ /**
1005
+ * Extracts flat grid-item options from AXPGridLayoutOptions for grid-item-layout widget.
1006
+ * Uses first available breakpoint (lg, xl, md, sm).
1007
+ */
1008
+ function toGridItemOptions(layoutOptions) {
1009
+ if (!layoutOptions?.positions)
1010
+ return { colSpan: 12 };
1011
+ const positions = layoutOptions.positions;
1012
+ const placement = positions['lg'] ?? positions['xl'] ?? positions['xxl'] ?? positions['md'] ?? positions['sm'];
1013
+ if (!placement)
1014
+ return { colSpan: 12 };
1015
+ const opts = {};
1016
+ if (placement['colSpan'] != null)
1017
+ opts['colSpan'] = placement['colSpan'];
1018
+ if (placement['colStart'] != null)
1019
+ opts['colStart'] = placement['colStart'];
1020
+ if (placement['colEnd'] != null)
1021
+ opts['colEnd'] = placement['colEnd'];
1022
+ if (placement['rowSpan'] != null)
1023
+ opts['rowSpan'] = placement['rowSpan'];
1024
+ if (placement['rowStart'] != null)
1025
+ opts['rowStart'] = placement['rowStart'];
1026
+ if (placement['rowEnd'] != null)
1027
+ opts['rowEnd'] = placement['rowEnd'];
1028
+ if (Object.keys(opts).length === 0)
1029
+ opts['colSpan'] = 12;
1030
+ return opts;
1031
+ }
1032
+ class GridContainerBuilder extends WidgetContainerMixin {
1033
+ constructor() {
1034
+ super('grid-layout');
1035
+ }
1036
+ setOptions(options) {
1037
+ this.containerState.options = { ...this.containerState.options, ...options };
1038
+ return this;
1039
+ }
1040
+ item(layoutOptions, delegate) {
1041
+ const fieldset = new FieldsetContainerBuilder();
1042
+ fieldset.withInheritanceContext(this.inheritanceContext);
1043
+ delegate(fieldset);
1044
+ const fieldsetNode = fieldset.build();
1045
+ const gridItemOptions = toGridItemOptions(layoutOptions);
1046
+ const gridItemNode = {
1047
+ type: 'grid-item-layout',
1048
+ options: gridItemOptions,
1049
+ children: [fieldsetNode],
1050
+ };
1051
+ this.ensureChildren();
1052
+ this.containerState.children.push(gridItemNode);
1053
+ return this;
1054
+ }
1055
+ // Individual fluent methods for Grid
1056
+ setColumns(columns) {
1057
+ return this.setOptions({ grid: { default: { columns } } });
1058
+ }
1059
+ setRows(rows) {
1060
+ return this.setOptions({ grid: { default: { rows } } });
1061
+ }
1062
+ setGap(gap) {
1063
+ return this.setOptions({ grid: { default: { gap } } });
1064
+ }
1065
+ setJustifyItems(justify) {
1066
+ return this.setOptions({ grid: { default: { justifyItems: justify } } });
1067
+ }
1068
+ setAlignItems(align) {
1069
+ return this.setOptions({ grid: { default: { alignItems: align } } });
1070
+ }
1071
+ setAutoFlow(flow) {
1072
+ return this.setOptions({ grid: { default: { autoFlow: flow } } });
1073
+ }
1074
+ setBackgroundColor(color) {
1075
+ return this.setOptions({ backgroundColor: color });
1076
+ }
1077
+ setPadding(padding) {
1078
+ return this.setOptions({ spacing: { padding } });
1079
+ }
1080
+ setMargin(margin) {
1081
+ return this.setOptions({ spacing: { margin } });
1082
+ }
1083
+ }
1084
+ /**
1085
+ * Panel Container Builder - Liskov Substitution Principle
1086
+ * Extends WidgetContainerMixin to inherit all common functionality
1087
+ */
1088
+ class PanelContainerBuilder extends WidgetContainerMixin {
1089
+ constructor() {
1090
+ super('panel-layout');
1091
+ }
1092
+ setOptions(options) {
1093
+ this.containerState.options = { ...this.containerState.options, ...options };
1094
+ return this;
1095
+ }
1096
+ // Individual fluent methods for Panel
1097
+ setCaption(caption) {
1098
+ return this.setOptions({ caption });
1099
+ }
1100
+ setIcon(icon) {
1101
+ return this.setOptions({ icon });
1102
+ }
1103
+ setLook(look) {
1104
+ return this.setOptions({ look });
1105
+ }
1106
+ setShowHeader(show) {
1107
+ return this.setOptions({ showHeader: show });
1108
+ }
1109
+ setCollapsed(collapsed) {
1110
+ return this.setOptions({ collapsed });
1111
+ }
1112
+ }
1113
+ /**
1114
+ * Page Container Builder - Liskov Substitution Principle
1115
+ * Extends WidgetContainerMixin to inherit all common functionality
1116
+ */
1117
+ class PageContainerBuilder extends WidgetContainerMixin {
1118
+ constructor() {
1119
+ super('page-layout');
1120
+ }
1121
+ setOptions(options) {
1122
+ this.containerState.options = { ...this.containerState.options, ...options };
1123
+ return this;
1124
+ }
1125
+ // Individual fluent methods for Page
1126
+ setBackgroundColor(color) {
1127
+ return this.setOptions({ backgroundColor: color });
1128
+ }
1129
+ setTheme(theme) {
1130
+ return this.setOptions({ theme });
1131
+ }
1132
+ setHasHeader(hasHeader) {
1133
+ return this.setOptions({ hasHeader });
1134
+ }
1135
+ setHasFooter(hasFooter) {
1136
+ return this.setOptions({ hasFooter });
1137
+ }
1138
+ setDirection(direction) {
1139
+ return this.setOptions({ direction });
1140
+ }
1141
+ }
1142
+ /**
1143
+ * Tabset Container Builder - Liskov Substitution Principle
1144
+ * Extends WidgetContainerMixin to inherit all common functionality
1145
+ */
1146
+ class TabsetContainerBuilder extends WidgetContainerMixin {
1147
+ constructor() {
1148
+ super('tabset-layout');
1149
+ }
1150
+ setOptions(options) {
1151
+ this.containerState.options = { ...this.containerState.options, ...options };
1152
+ return this;
1153
+ }
1154
+ // Individual fluent methods for Tabset
1155
+ setLook(look) {
1156
+ return this.setOptions({ look });
1157
+ }
1158
+ setOrientation(orientation) {
1159
+ return this.setOptions({ orientation });
1160
+ }
1161
+ setActiveIndex(index) {
1162
+ return this.setOptions({ activeIndex: index });
1163
+ }
1164
+ }
1165
+ /**
1166
+ * Form Field Builder - Liskov Substitution Principle
1167
+ * Can only contain ONE widget with automatic path generation
1168
+ */
1169
+ class FormFieldBuilder extends LayoutContainerMixin {
1170
+ constructor(label) {
1171
+ super('form-field');
1172
+ this.hasWidget = false;
1173
+ this.containerState.options = { label, showLabel: true };
1174
+ }
1175
+ setOptions(options) {
1176
+ this.containerState.options = { ...this.containerState.options, ...options };
1177
+ return this;
1178
+ }
1179
+ setLabel(label) {
1180
+ return this.setOptions({ label });
1181
+ }
1182
+ setShowLabel(showLabel) {
1183
+ return this.setOptions({ showLabel });
1184
+ }
1185
+ defaultValue(value) {
1186
+ this.containerState.defaultValue = value;
1187
+ this.inheritanceContext.defaultValue = value;
1188
+ if (this.childWidget) {
1189
+ this.childWidget.withInheritanceContext(this.inheritanceContext);
1190
+ }
1191
+ return this;
1192
+ }
1193
+ // Single widget methods with automatic path generation
1194
+ addSingleWidget(type, options) {
1195
+ if (this.hasWidget) {
1196
+ throw new Error('Form field can only contain one widget');
1197
+ }
1198
+ const formFieldName = this.containerState.name;
1199
+ const formFieldPath = this.containerState.path; // Get explicit path from form field
1200
+ const formFieldLabel = this.containerState.options?.['label'];
1201
+ const widgetName = options?.name;
1202
+ // Generate widget path: explicit path -> widget name -> form field name -> label -> random
1203
+ let widgetPath;
1204
+ if (formFieldPath) {
1205
+ widgetPath = formFieldPath; // Use explicit form field path first
1206
+ }
1207
+ else if (widgetName) {
1208
+ widgetPath = widgetName;
1209
+ }
1210
+ else if (formFieldName) {
1211
+ widgetPath = formFieldName; // Use form field name as default path
1212
+ }
1213
+ else if (formFieldLabel) {
1214
+ widgetPath = labelToPath(formFieldLabel);
1215
+ }
1216
+ else {
1217
+ widgetPath = generateRandomId();
1218
+ }
1219
+ const finalName = widgetName || formFieldName || widgetPath;
1220
+ const child = new WidgetBuilder();
1221
+ child.type(type);
1222
+ child.name(finalName);
1223
+ child.path(widgetPath);
1224
+ // Extract extended properties from options (triggers, meta, valueTransforms, mode, visible, defaultValue)
1225
+ const { name: _, triggers, meta, valueTransforms, mode: extendedMode, visible: extendedVisible, defaultValue: extendedDefaultValue, children: extendedChildren, ...cleanOptions } = (options || {});
1226
+ child.withInheritanceContext(this.inheritanceContext);
1227
+ child.options(cleanOptions);
1228
+ // Apply extended properties if provided
1229
+ if (extendedMode !== undefined) {
1230
+ child.mode(extendedMode);
1231
+ }
1232
+ if (extendedVisible !== undefined) {
1233
+ child.visible(extendedVisible);
1234
+ }
1235
+ if (extendedDefaultValue !== undefined) {
1236
+ child.defaultValue(extendedDefaultValue);
1237
+ }
1238
+ // Set triggers, meta, and valueTransforms directly on widgetState
1239
+ // These are part of AXPWidgetNode but not handled by WidgetBuilder methods
1240
+ if (triggers !== undefined) {
1241
+ child.widgetState.triggers = triggers;
1242
+ }
1243
+ if (meta !== undefined) {
1244
+ child.widgetState.meta = meta;
1245
+ }
1246
+ if (valueTransforms !== undefined) {
1247
+ child.widgetState.valueTransforms = valueTransforms;
1248
+ }
1249
+ if (extendedChildren !== undefined) {
1250
+ child.widgetState.children = extendedChildren;
1251
+ }
1252
+ // IMPORTANT: Store the widget builder, don't build it yet!
1253
+ // This allows properties set after this method (like disabled, readonly) to be applied
1254
+ this.childWidget = child;
1255
+ this.hasWidget = true;
1256
+ return this;
1257
+ }
1258
+ textBox(options) {
1259
+ return this.addSingleWidget('text-editor', options);
1260
+ }
1261
+ largeTextBox(options) {
1262
+ return this.addSingleWidget('large-text-editor', options);
1263
+ }
1264
+ richText(options) {
1265
+ return this.addSingleWidget('rich-text-editor', options);
1266
+ }
1267
+ passwordBox(options) {
1268
+ return this.addSingleWidget('password-editor', options);
1269
+ }
1270
+ numberBox(options) {
1271
+ return this.addSingleWidget('number-editor', options);
1272
+ }
1273
+ selectBox(options) {
1274
+ return this.addSingleWidget('select-editor', options);
1275
+ }
1276
+ lookupBox(options) {
1277
+ return this.addSingleWidget('lookup-editor', options);
1278
+ }
1279
+ selectionList(options) {
1280
+ return this.addSingleWidget('selection-list-editor', options);
1281
+ }
1282
+ dateTimeBox(options) {
1283
+ return this.addSingleWidget('date-time-editor', options);
1284
+ }
1285
+ toggleSwitch(options) {
1286
+ return this.addSingleWidget('toggle-editor', options);
1287
+ }
1288
+ colorBox(options) {
1289
+ return this.addSingleWidget('color-editor', options);
1290
+ }
1291
+ customWidget(type, options) {
1292
+ return this.addSingleWidget(type, options);
1293
+ }
1294
+ // Override property setters to propagate changes to child widget
1295
+ disabled(condition) {
1296
+ super.disabled(condition);
1297
+ if (this.childWidget) {
1298
+ this.childWidget.withInheritanceContext(this.inheritanceContext);
1299
+ }
1300
+ return this;
1301
+ }
1302
+ readonly(condition) {
1303
+ super.readonly(condition);
1304
+ if (this.childWidget) {
1305
+ this.childWidget.withInheritanceContext(this.inheritanceContext);
1306
+ }
1307
+ return this;
1308
+ }
1309
+ visible(condition) {
1310
+ super.visible(condition);
1311
+ if (this.childWidget) {
1312
+ this.childWidget.withInheritanceContext(this.inheritanceContext);
1313
+ }
1314
+ return this;
1315
+ }
1316
+ direction(direction) {
1317
+ super.direction(direction);
1318
+ if (this.childWidget) {
1319
+ this.childWidget.withInheritanceContext(this.inheritanceContext);
1320
+ }
1321
+ return this;
1322
+ }
1323
+ mode(mode) {
1324
+ super.mode(mode);
1325
+ if (this.childWidget) {
1326
+ this.childWidget.withInheritanceContext(this.inheritanceContext);
1327
+ }
1328
+ return this;
1329
+ }
1330
+ // Override withInheritanceContext to pass it to the child widget if it exists
1331
+ withInheritanceContext(context) {
1332
+ // Call parent implementation first
1333
+ super.withInheritanceContext(context);
1334
+ // If we have a child widget, update its inheritance context too
1335
+ if (this.childWidget) {
1336
+ this.childWidget.withInheritanceContext(this.inheritanceContext);
1337
+ }
1338
+ return this;
1339
+ }
1340
+ // Override build() to build the child widget at the last moment
1341
+ build() {
1342
+ // Build the child widget and add it to children before building the form field
1343
+ if (this.childWidget) {
1344
+ this.ensureChildren();
1345
+ this.containerState.children.push(this.childWidget.build());
1346
+ }
1347
+ // Call parent build
1348
+ return super.build();
1349
+ }
1350
+ }
1351
+ /**
1352
+ * Fieldset Container Builder - Liskov Substitution Principle
1353
+ * Extends LayoutContainerMixin to inherit layout functionality
1354
+ * Specialized for form fields only
1355
+ */
1356
+ class FieldsetContainerBuilder extends LayoutContainerMixin {
1357
+ constructor() {
1358
+ super('fieldset-layout');
1359
+ this.containerState.options = {};
1360
+ }
1361
+ setOptions(options) {
1362
+ this.containerState.options = { ...this.containerState.options, ...options };
1363
+ return this;
1364
+ }
1365
+ // Individual fluent methods for Fieldset
1366
+ setTitle(title) {
1367
+ return this.setOptions({ title });
1368
+ }
1369
+ setDescription(description) {
1370
+ return this.setOptions({ description });
1371
+ }
1372
+ setIcon(icon) {
1373
+ return this.setOptions({ icon });
1374
+ }
1375
+ setCollapsible(collapsible) {
1376
+ return this.setOptions({ collapsible });
1377
+ }
1378
+ setIsOpen(isOpen) {
1379
+ return this.setOptions({ isOpen });
1380
+ }
1381
+ setLook(look) {
1382
+ return this.setOptions({ look });
1383
+ }
1384
+ setShowHeader(showHeader) {
1385
+ return this.setOptions({ showHeader });
1386
+ }
1387
+ setCols(cols) {
1388
+ return this.setOptions({ cols });
1389
+ }
1390
+ // Only form fields are allowed in fieldset
1391
+ formField(label, delegate) {
1392
+ const field = new FormFieldBuilder(label);
1393
+ field.withInheritanceContext(this.inheritanceContext);
1394
+ if (delegate) {
1395
+ delegate(field);
1396
+ }
1397
+ this.ensureChildren();
1398
+ this.containerState.children.push(field.build());
1399
+ return this;
1400
+ }
1401
+ }
1402
+ /**
1403
+ * List Widget Builder - Liskov Substitution Principle
1404
+ * Extends WidgetContainerMixin to inherit all common functionality
1405
+ */
1406
+ class ListWidgetBuilder extends WidgetContainerMixin {
1407
+ constructor() {
1408
+ super('data-list');
1409
+ }
1410
+ setOptions(options) {
1411
+ this.containerState.options = { ...this.containerState.options, ...options };
1412
+ return this;
1413
+ }
1414
+ // Individual fluent methods for List Widget
1415
+ setDataSource(dataSource) {
1416
+ return this.setOptions({ dataSource });
1417
+ }
1418
+ setColumns(columns) {
1419
+ return this.setOptions({ columns });
1420
+ }
1421
+ // Event handlers
1422
+ setOnRowClick(handler) {
1423
+ return this.setOptions({ onRowClick: handler });
1424
+ }
1425
+ setOnRowDoubleClick(handler) {
1426
+ return this.setOptions({ onRowDoubleClick: handler });
1427
+ }
1428
+ setOnSelectionChange(handler) {
1429
+ return this.setOptions({ onSelectionChange: handler });
1430
+ }
1431
+ setOnRowCommand(handler) {
1432
+ return this.setOptions({ onRowCommand: handler });
1433
+ }
1434
+ // Table features
1435
+ setPaging(paging) {
1436
+ return this.setOptions({ paging });
1437
+ }
1438
+ setShowHeader(show) {
1439
+ return this.setOptions({ showHeader: show });
1440
+ }
1441
+ setShowFooter(show) {
1442
+ return this.setOptions({ showFooter: show });
1443
+ }
1444
+ setFixHeader(fix) {
1445
+ return this.setOptions({ fixHeader: fix });
1446
+ }
1447
+ setFixFooter(fix) {
1448
+ return this.setOptions({ fixFooter: fix });
1449
+ }
1450
+ setFetchDataMode(mode) {
1451
+ return this.setOptions({ fetchDataMode: mode });
1452
+ }
1453
+ setParentField(field) {
1454
+ return this.setOptions({ parentField: field });
1455
+ }
1456
+ setMinHeight(height) {
1457
+ return this.setOptions({ minHeight: height });
1458
+ }
1459
+ // Selection & Index
1460
+ setShowIndex(show) {
1461
+ return this.setOptions({ showIndex: show });
1462
+ }
1463
+ setAllowSelection(allow) {
1464
+ return this.setOptions({ allowSelection: allow });
1465
+ }
1466
+ // Commands
1467
+ setPrimaryCommands(commands) {
1468
+ return this.setOptions({ primaryCommands: commands });
1469
+ }
1470
+ setSecondaryCommands(commands) {
1471
+ return this.setOptions({ secondaryCommands: commands });
1472
+ }
1473
+ // Loading
1474
+ setLoading(loading) {
1475
+ return this.setOptions({ loading });
1476
+ }
1477
+ }
1478
+ /**
1479
+ * Dialog Container Builder - Specialized for dialog functionality
1480
+ * Uses composition instead of inheritance for cleaner separation
1481
+ */
1482
+ class DialogContainerBuilder {
1483
+ constructor(popupService) {
1484
+ this.dialogState = {
1485
+ type: 'flex-layout', // This will be overridden when content layout exists
1486
+ children: [],
1487
+ mode: 'edit',
1488
+ dialogOptions: {
1489
+ title: '',
1490
+ size: 'md',
1491
+ closeButton: false,
1492
+ },
1493
+ actions: {
1494
+ footer: {
1495
+ prefix: [],
1496
+ suffix: [],
1497
+ },
1498
+ },
1499
+ };
1500
+ if (popupService) {
1501
+ this.popupService = popupService;
1502
+ }
1503
+ else {
1504
+ this.popupService = inject(AXPopupService);
1505
+ }
1506
+ }
1507
+ setOptions(options) {
1508
+ this.dialogState.dialogOptions = { ...this.dialogState.dialogOptions, ...options };
1509
+ return this;
1510
+ }
1511
+ // Individual fluent methods for Dialog
1512
+ setTitle(title) {
1513
+ return this.setOptions({ title });
1514
+ }
1515
+ setMessage(message) {
1516
+ return this.setOptions({ message });
1517
+ }
1518
+ setSize(size) {
1519
+ return this.setOptions({ size });
1520
+ }
1521
+ setCloseButton(closeButton) {
1522
+ return this.setOptions({ closeButton });
1523
+ }
1524
+ setContext(context) {
1525
+ return this.setOptions({ context });
1526
+ }
1527
+ content(delegate) {
1528
+ if (delegate) {
1529
+ // Create a flex container directly instead of through LayoutBuilder
1530
+ const flexContainer = new FlexContainerBuilder();
1531
+ flexContainer.setDirection('column');
1532
+ flexContainer.setGap('10px');
1533
+ delegate(flexContainer);
1534
+ this.contentLayout = flexContainer.build();
1535
+ }
1536
+ return this;
1537
+ }
1538
+ setActions(delegate) {
1539
+ if (delegate) {
1540
+ const actionBuilder = new ActionBuilder(this);
1541
+ delegate(actionBuilder);
1542
+ }
1543
+ return this;
1544
+ }
1545
+ onAction(handler) {
1546
+ (this.dialogState.dialogOptions ??= {}).onAction = handler;
1547
+ return this;
1548
+ }
1549
+ addCustomAction(action) {
1550
+ // Add to actions based on position
1551
+ const position = action.position || 'suffix';
1552
+ if (position === 'prefix') {
1553
+ if (!this.dialogState.actions.footer.prefix) {
1554
+ this.dialogState.actions.footer.prefix = [];
1555
+ }
1556
+ this.dialogState.actions.footer.prefix.push(action);
1557
+ }
1558
+ else {
1559
+ if (!this.dialogState.actions.footer.suffix) {
1560
+ this.dialogState.actions.footer.suffix = [];
1561
+ }
1562
+ this.dialogState.actions.footer.suffix.push(action);
1563
+ }
1564
+ return this;
1565
+ }
1566
+ // Build method to create dialog node
1567
+ build() {
1568
+ // If we have content layout, use it directly to avoid extra wrapper
1569
+ if (this.contentLayout) {
1570
+ return {
1571
+ ...this.contentLayout,
1572
+ // Add dialog-specific properties
1573
+ options: {
1574
+ ...this.contentLayout.options,
1575
+ ...this.dialogState.dialogOptions,
1576
+ },
1577
+ };
1578
+ }
1579
+ // Fallback to dialog state structure if no content
1580
+ const result = {
1581
+ ...this.dialogState,
1582
+ children: [],
1583
+ };
1584
+ // Add dialog-specific properties
1585
+ if (this.dialogState.dialogOptions) {
1586
+ result.options = { ...result.options, ...this.dialogState.dialogOptions };
1587
+ }
1588
+ return result;
1589
+ }
1590
+ // Dialog-specific methods
1591
+ async show() {
1592
+ const dialogNode = this.build();
1593
+ // Import the dialog renderer component dynamically
1594
+ const { AXPDialogRendererComponent } = await Promise.resolve().then(function () { return dialogRenderer_component; });
1595
+ // Collect default values from widget tree and merge into initial context
1596
+ const initialContext = this.dialogState.dialogOptions?.context || {};
1597
+ //TODO remove using collectDefaultValues and use initialContext directly for now:
1598
+ const contextWithDefaults = collectDefaultValues(dialogNode, initialContext);
1599
+ // Create dialog configuration
1600
+ const dialogConfig = {
1601
+ title: this.dialogState.dialogOptions?.title || '',
1602
+ message: this.dialogState.dialogOptions?.message,
1603
+ context: initialContext,
1604
+ definition: dialogNode,
1605
+ actions: this.dialogState.actions,
1606
+ onAction: this.dialogState.dialogOptions?.onAction,
1607
+ };
1608
+ // The Promise resolves when user clicks an action button
1609
+ return new Promise(async (resolve) => {
1610
+ this.popupService.open(AXPDialogRendererComponent, {
1611
+ title: dialogConfig.title,
1612
+ size: this.dialogState.dialogOptions?.size || 'md',
1613
+ closeButton: this.dialogState.dialogOptions?.closeButton || false,
1614
+ closeOnBackdropClick: false,
1615
+ draggable: false,
1616
+ data: {
1617
+ config: dialogConfig,
1618
+ callBack: (result) => {
1619
+ resolve(result);
1620
+ },
1621
+ },
1622
+ });
1623
+ });
1624
+ }
1625
+ }
1626
+ //#endregion
1627
+ //#region ---- Widget Builder Implementation ----
1628
+ /**
1629
+ * Widget Builder - Single Responsibility Principle
1630
+ * Handles individual widget configuration and building
1631
+ */
1632
+ class WidgetBuilder {
1633
+ constructor(name) {
1634
+ this.widgetState = {
1635
+ type: 'widget',
1636
+ options: {},
1637
+ children: [],
1638
+ };
1639
+ this.inheritanceContext = {};
1640
+ if (name) {
1641
+ this.widgetState.name = name;
1642
+ }
1643
+ }
1644
+ type(type) {
1645
+ this.widgetState.type = type;
1646
+ return this;
1647
+ }
1648
+ name(name) {
1649
+ this.widgetState.name = name;
1650
+ if (!this.widgetState.path) {
1651
+ this.widgetState.path = name;
1652
+ }
1653
+ return this;
1654
+ }
1655
+ path(path) {
1656
+ this.widgetState.path = path;
1657
+ return this;
1658
+ }
1659
+ options(options) {
1660
+ // Extract defaultValue from options - it's an exceptional parameter that updates context, not a widget option
1661
+ if (options && options.defaultValue !== undefined) {
1662
+ this.widgetState.defaultValue = options.defaultValue;
1663
+ // Remove defaultValue from options since it's stored on the node, not in options
1664
+ const { defaultValue: _, ...cleanOptions } = options;
1665
+ // Merge clean options instead of replacing to preserve inherited properties
1666
+ this.widgetState.options = { ...this.widgetState.options, ...cleanOptions };
1667
+ }
1668
+ else {
1669
+ // Merge options instead of replacing to preserve inherited properties
1670
+ this.widgetState.options = { ...this.widgetState.options, ...options };
1671
+ }
1672
+ return this;
1673
+ }
1674
+ layout(value) {
1675
+ if (typeof value === 'number') {
1676
+ this.widgetState.layout = {
1677
+ positions: {
1678
+ sm: { colSpan: 12 },
1679
+ md: { colSpan: 12 },
1680
+ lg: { colSpan: value },
1681
+ xl: { colSpan: value },
1682
+ xxl: { colSpan: value },
1683
+ },
1684
+ };
1685
+ }
1686
+ else {
1687
+ this.widgetState.layout = value;
1688
+ }
1689
+ return this;
1690
+ }
1691
+ mode(mode) {
1692
+ this.widgetState.mode = mode;
1693
+ this.inheritanceContext.mode = mode;
1694
+ return this;
1695
+ }
1696
+ visible(condition) {
1697
+ if (!this.widgetState.options) {
1698
+ this.widgetState.options = {};
1699
+ }
1700
+ this.widgetState.options['visible'] = condition;
1701
+ this.widgetState.visible = condition;
1702
+ this.inheritanceContext.visible = condition;
1703
+ return this;
1704
+ }
1705
+ disabled(condition) {
1706
+ if (!this.widgetState.options) {
1707
+ this.widgetState.options = {};
1708
+ }
1709
+ this.widgetState.options['disabled'] = condition;
1710
+ this.inheritanceContext.disabled = condition;
1711
+ return this;
1712
+ }
1713
+ defaultValue(defaultValue) {
1714
+ this.widgetState.defaultValue = defaultValue;
1715
+ return this;
1716
+ }
1717
+ readonly(condition) {
1718
+ if (!this.widgetState.options) {
1719
+ this.widgetState.options = {};
1720
+ }
1721
+ this.widgetState.options['readonly'] = condition;
1722
+ this.inheritanceContext.readonly = condition;
1723
+ return this;
1724
+ }
1725
+ direction(direction) {
1726
+ if (!this.widgetState.options) {
1727
+ this.widgetState.options = {};
1728
+ }
1729
+ this.widgetState.options['direction'] = direction;
1730
+ this.inheritanceContext.direction = direction;
1731
+ return this;
1732
+ }
1733
+ children(children) {
1734
+ this.widgetState.children = children;
1735
+ return this;
1736
+ }
1737
+ // Inheritance context methods
1738
+ withInheritanceContext(context) {
1739
+ this.inheritanceContext = mergeInheritanceContext(context);
1740
+ // Apply inherited properties to widget state
1741
+ const resolved = resolveInheritedProperties(context, this.inheritanceContext);
1742
+ // Always apply inherited properties (remove the conditions that check if already set)
1743
+ // This allows properties to be updated when inheritance context changes
1744
+ if (resolved.mode) {
1745
+ this.widgetState.mode = resolved.mode;
1746
+ }
1747
+ if (!this.widgetState.options)
1748
+ this.widgetState.options = {};
1749
+ if (resolved.disabled !== undefined) {
1750
+ this.widgetState.options['disabled'] = resolved.disabled;
1751
+ }
1752
+ if (resolved.readonly !== undefined) {
1753
+ this.widgetState.options['readonly'] = resolved.readonly;
1754
+ }
1755
+ if (resolved.direction !== undefined) {
1756
+ this.widgetState.options['direction'] = resolved.direction;
1757
+ }
1758
+ if (resolved.visible !== undefined) {
1759
+ this.widgetState.options['visible'] = resolved.visible;
1760
+ this.widgetState.visible = resolved.visible;
1761
+ }
1762
+ if (context.defaultValue !== undefined) {
1763
+ this.widgetState.defaultValue = context.defaultValue;
1764
+ }
1765
+ return this;
1766
+ }
1767
+ getInheritanceContext() {
1768
+ return { ...this.inheritanceContext };
1769
+ }
1770
+ build() {
1771
+ const node = {
1772
+ name: this.widgetState.name,
1773
+ type: this.widgetState.type,
1774
+ options: this.widgetState.options,
1775
+ mode: this.widgetState.mode,
1776
+ path: this.widgetState.path,
1777
+ defaultValue: this.widgetState.defaultValue,
1778
+ children: this.widgetState.children,
1779
+ };
1780
+ // Add extended properties if they exist
1781
+ if (this.widgetState.triggers !== undefined) {
1782
+ node.triggers = this.widgetState.triggers;
1783
+ }
1784
+ if (this.widgetState.meta !== undefined) {
1785
+ node.meta = this.widgetState.meta;
1786
+ }
1787
+ if (this.widgetState.valueTransforms !== undefined) {
1788
+ node.valueTransforms = this.widgetState.valueTransforms;
1789
+ }
1790
+ if (this.widgetState.visible !== undefined) {
1791
+ node.visible = this.widgetState.visible;
1792
+ }
1793
+ return node;
1794
+ }
1795
+ }
1796
+ //#region ---- Action Builder Implementation ----
1797
+ class ActionBuilder {
1798
+ constructor(dialogBuilder) {
1799
+ this.dialogBuilder = dialogBuilder;
1800
+ }
1801
+ cancel(text) {
1802
+ if (!this.dialogBuilder['dialogState'].actions.footer.suffix) {
1803
+ this.dialogBuilder['dialogState'].actions.footer.suffix = [];
1804
+ }
1805
+ this.dialogBuilder['dialogState'].actions.footer.suffix.push({
1806
+ title: text || '@general:actions.cancel.title',
1807
+ color: 'default',
1808
+ command: { name: 'cancel' },
1809
+ });
1810
+ return this;
1811
+ }
1812
+ submit(text) {
1813
+ if (!this.dialogBuilder['dialogState'].actions.footer.suffix) {
1814
+ this.dialogBuilder['dialogState'].actions.footer.suffix = [];
1815
+ }
1816
+ this.dialogBuilder['dialogState'].actions.footer.suffix.push({
1817
+ title: text || '@general:actions.submit.title',
1818
+ color: 'primary',
1819
+ command: { name: 'submit', options: { validate: true } },
1820
+ });
1821
+ return this;
1822
+ }
1823
+ custom(action) {
1824
+ const position = action.position ?? 'suffix';
1825
+ if (position === 'prefix') {
1826
+ if (!this.dialogBuilder['dialogState'].actions.footer.prefix) {
1827
+ this.dialogBuilder['dialogState'].actions.footer.prefix = [];
1828
+ }
1829
+ this.dialogBuilder['dialogState'].actions.footer.prefix.push(action);
1830
+ }
1831
+ else {
1832
+ if (!this.dialogBuilder['dialogState'].actions.footer.suffix) {
1833
+ this.dialogBuilder['dialogState'].actions.footer.suffix = [];
1834
+ }
1835
+ this.dialogBuilder['dialogState'].actions.footer.suffix.push(action);
1836
+ }
1837
+ return this;
1838
+ }
1839
+ }
1840
+ //#endregion
1841
+ //#region ---- Step Wizard Builder Implementation ----
1842
+ /**
1843
+ * Step Builder - Builds individual steps for step wizard
1844
+ */
1845
+ class StepBuilder {
1846
+ constructor(id, title) {
1847
+ this.id = id;
1848
+ this.title = title;
1849
+ this.skippable = false;
1850
+ this.inheritanceContext = {};
1851
+ }
1852
+ setIcon(icon) {
1853
+ this.icon = icon;
1854
+ return this;
1855
+ }
1856
+ setDescription(description) {
1857
+ this.description = description;
1858
+ return this;
1859
+ }
1860
+ setSkippable(skippable) {
1861
+ this.skippable = skippable;
1862
+ return this;
1863
+ }
1864
+ content(delegate) {
1865
+ if (delegate) {
1866
+ // Create LayoutBuilder in injection context (will be called from component)
1867
+ const layoutBuilder = new LayoutBuilder();
1868
+ // ✅ IMPORTANT: Propagate inheritance context to content
1869
+ layoutBuilder.inheritanceContext = { ...this.inheritanceContext };
1870
+ delegate(layoutBuilder);
1871
+ this.contentNode = layoutBuilder.build();
1872
+ }
1873
+ return this;
1874
+ }
1875
+ withInheritanceContext(context) {
1876
+ this.inheritanceContext = mergeInheritanceContext(context);
1877
+ return this;
1878
+ }
1879
+ build() {
1880
+ if (!this.contentNode) {
1881
+ throw new Error(`Step '${this.id}' must have content`);
1882
+ }
1883
+ return {
1884
+ id: this.id,
1885
+ title: this.title,
1886
+ icon: this.icon,
1887
+ description: this.description,
1888
+ skippable: this.skippable,
1889
+ content: this.contentNode,
1890
+ };
1891
+ }
1892
+ }
1893
+ /**
1894
+ * Step Wizard Builder - Builds step wizard widget
1895
+ */
1896
+ class StepWizardBuilder extends LayoutContainerMixin {
1897
+ constructor() {
1898
+ super('step-wizard');
1899
+ this.steps = [];
1900
+ this.linear = true;
1901
+ this.wizardDirection = 'horizontal';
1902
+ }
1903
+ setLinear(linear) {
1904
+ this.linear = linear;
1905
+ return this;
1906
+ }
1907
+ setDirection(direction) {
1908
+ this.wizardDirection = direction;
1909
+ return this;
1910
+ }
1911
+ setLook(look) {
1912
+ this.look = look;
1913
+ return this;
1914
+ }
1915
+ setShowActions(show) {
1916
+ this.showActions = show;
1917
+ return this;
1918
+ }
1919
+ setActions(actions) {
1920
+ this.wizardActions = actions;
1921
+ return this;
1922
+ }
1923
+ setGuards(guards) {
1924
+ this.guards = guards;
1925
+ return this;
1926
+ }
1927
+ setEvents(events) {
1928
+ this.events = events;
1929
+ return this;
1930
+ }
1931
+ step(id, title, delegate) {
1932
+ const stepBuilder = new StepBuilder(id, title);
1933
+ // ✅ IMPORTANT: Propagate inheritance context to step
1934
+ stepBuilder.withInheritanceContext(this.inheritanceContext);
1935
+ if (delegate) {
1936
+ delegate(stepBuilder);
1937
+ }
1938
+ this.steps.push(stepBuilder.build());
1939
+ return this;
1940
+ }
1941
+ build() {
1942
+ // ✅ Validation
1943
+ if (this.steps.length === 0) {
1944
+ throw new Error('StepWizard must have at least one step');
1945
+ }
1946
+ // ✅ Check duplicate IDs
1947
+ const ids = this.steps.map((s) => s.id);
1948
+ const duplicates = ids.filter((id, index) => ids.indexOf(id) !== index);
1949
+ if (duplicates.length > 0) {
1950
+ throw new Error(`Duplicate step IDs found: ${duplicates.join(', ')}`);
1951
+ }
1952
+ const definition = {
1953
+ steps: this.steps,
1954
+ linear: this.linear,
1955
+ direction: this.wizardDirection,
1956
+ look: this.look,
1957
+ // Do not auto-detect based on dialog context; keep explicit or undefined
1958
+ showActions: this.showActions,
1959
+ actions: this.wizardActions,
1960
+ guards: this.guards,
1961
+ events: this.events,
1962
+ };
1963
+ const node = {
1964
+ name: this.containerState.name,
1965
+ type: 'step-wizard',
1966
+ options: { definition },
1967
+ mode: this.containerState.mode,
1968
+ };
1969
+ return node;
1970
+ }
1971
+ }
1972
+
1973
+ class AXPLayoutRendererComponent {
1974
+ constructor() {
1975
+ this.conversionService = inject(AXPLayoutConversionService);
1976
+ /**
1977
+ * RxJS subjects for context management
1978
+ */
1979
+ this.contextUpdateSubject = new Subject();
1980
+ this.contextChangeSubject = new Subject();
1981
+ //#region ---- Inputs ----
1982
+ /**
1983
+ * Form definition containing groups and fields OR widget tree
1984
+ */
1985
+ this.layout = input.required(...(ngDevMode ? [{ debugName: "layout" }] : /* istanbul ignore next */ []));
1986
+ /**
1987
+ * Form context/model data
1988
+ */
1989
+ this.context = model({}, ...(ngDevMode ? [{ debugName: "context" }] : /* istanbul ignore next */ []));
1990
+ /**
1991
+ * Form appearance and density styling (normal, compact, spacious)
1992
+ */
1993
+ this.look = input('fieldset', ...(ngDevMode ? [{ debugName: "look" }] : /* istanbul ignore next */ []));
1994
+ /**
1995
+ * Default form mode. Can be overridden by section/group and field.
1996
+ */
1997
+ this.mode = input('edit', ...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
1998
+ //#endregion
1999
+ //#region ---- Widget Tree Conversion ----
2000
+ this.widgetTree = signal(null, ...(ngDevMode ? [{ debugName: "widgetTree" }] : /* istanbul ignore next */ []));
2001
+ /**
2002
+ * Convert layout data to widget tree when inputs change
2003
+ */
2004
+ this.conversionEffect = effect(() => {
2005
+ const inputData = this.layout();
2006
+ // Convert to widget tree
2007
+ let tree;
2008
+ if (this.isFormDefinition(inputData)) {
2009
+ // Convert form definition to widget tree
2010
+ tree = this.conversionService.convertFormDefinition(inputData);
2011
+ }
2012
+ else if (this.isWidgetNode(inputData)) {
2013
+ // Use widget tree directly
2014
+ tree = inputData;
2015
+ }
2016
+ else {
2017
+ console.warn('AXPLayoutRendererComponent: Invalid layout input. Expected AXPDynamicFormDefinition or AXPWidgetNode.', inputData);
2018
+ return;
2019
+ }
2020
+ // Update widget tree only if changed (Angular effect already prevents unnecessary runs)
2021
+ const prev = this.widgetTree();
2022
+ if (!isEqual(prev, tree)) {
2023
+ this.widgetTree.set(tree);
2024
+ }
2025
+ }, ...(ngDevMode ? [{ debugName: "conversionEffect" }] : /* istanbul ignore next */ []));
2026
+ //#endregion
2027
+ //#region ---- Outputs ----
2028
+ /**
2029
+ * Emitted when context change is initiated
2030
+ */
2031
+ this.contextInitiated = output();
2032
+ /**
2033
+ * Emitted when form becomes valid/invalid
2034
+ */
2035
+ this.validityChange = output();
2036
+ //#endregion
2037
+ //#region ---- Properties ----
2038
+ this.form = viewChild(AXFormComponent, ...(ngDevMode ? [{ debugName: "form" }] : /* istanbul ignore next */ []));
2039
+ this.container = viewChild(AXPWidgetContainerComponent, ...(ngDevMode ? [{ debugName: "container" }] : /* istanbul ignore next */ []));
2040
+ /**
2041
+ * Internal context signal for reactivity
2042
+ */
2043
+ this.internalContext = signal({}, ...(ngDevMode ? [{ debugName: "internalContext" }] : /* istanbul ignore next */ []));
2044
+ /**
2045
+ * Initial context for reset functionality
2046
+ */
2047
+ this.initialContext = {};
2048
+ //#endregion
2049
+ //#region ---- Effects ----
2050
+ /**
2051
+ * Effect to sync context changes from external to internal (optimized with RxJS)
2052
+ */
2053
+ this.#contextSyncEffect = effect(() => {
2054
+ const ctx = this.context() ?? {};
2055
+ this.contextUpdateSubject.next(ctx);
2056
+ }, ...(ngDevMode ? [{ debugName: "#contextSyncEffect" }] : /* istanbul ignore next */ []));
2057
+ /**
2058
+ * Effect to handle widget tree status changes
2059
+ */
2060
+ this.#widgetStatusEffect = effect(() => {
2061
+ const widgetTree = this.widgetTree();
2062
+ if (widgetTree) {
2063
+ this.container()?.builderService.setStatus(AXPPageStatus.Rendered);
2064
+ }
2065
+ }, ...(ngDevMode ? [{ debugName: "#widgetStatusEffect" }] : /* istanbul ignore next */ []));
2066
+ }
2067
+ //#endregion
2068
+ //#region ---- Lifecycle Methods ----
2069
+ ngOnInit() {
2070
+ // Initialize internal context with input context
2071
+ const ctx = this.context() ?? {};
2072
+ this.internalContext.set(ctx);
2073
+ // Store initial context for reset functionality
2074
+ this.initialContext = cloneDeep(ctx);
2075
+ // Setup RxJS streams for context management
2076
+ this.setupContextStreams();
2077
+ }
2078
+ //#endregion
2079
+ //#region ---- Effects ----
2080
+ /**
2081
+ * Effect to sync context changes from external to internal (optimized with RxJS)
2082
+ */
2083
+ #contextSyncEffect;
2084
+ /**
2085
+ * Effect to handle widget tree status changes
2086
+ */
2087
+ #widgetStatusEffect;
2088
+ //#endregion
2089
+ //#region ---- Event Handlers ----
2090
+ /**
2091
+ * Handle context change events from widget container (optimized with RxJS)
2092
+ */
2093
+ handleContextChanged(event) {
2094
+ if (event.state === 'initiated') {
2095
+ this.contextInitiated.emit(event.data);
2096
+ }
2097
+ else {
2098
+ this.contextChangeSubject.next(event.data ?? {});
2099
+ }
2100
+ }
2101
+ //#endregion
2102
+ //#region ---- Public Methods ----
2103
+ /**
2104
+ * Get the form component instance
2105
+ */
2106
+ getForm() {
2107
+ return this.form();
2108
+ }
2109
+ /**
2110
+ * Get the widget container component instance
2111
+ */
2112
+ getContainer() {
2113
+ return this.container();
2114
+ }
2115
+ /**
2116
+ * Get current form context
2117
+ */
2118
+ getContext() {
2119
+ return this.internalContext();
2120
+ }
2121
+ /**
2122
+ * Update form context programmatically
2123
+ */
2124
+ updateContext(context) {
2125
+ this.internalContext.set(context);
2126
+ }
2127
+ /**
2128
+ * Get the current widget tree
2129
+ */
2130
+ getWidgetTree() {
2131
+ return this.widgetTree();
2132
+ }
2133
+ /**
2134
+ * Validate the form
2135
+ */
2136
+ async validate() {
2137
+ const form = this.form();
2138
+ if (form) {
2139
+ const isValid = await form.validate();
2140
+ this.validityChange.emit(isValid.result);
2141
+ return isValid;
2142
+ }
2143
+ return {
2144
+ result: false,
2145
+ messages: [],
2146
+ rules: [],
2147
+ };
2148
+ }
2149
+ /**
2150
+ * Clear the form context
2151
+ */
2152
+ clear() {
2153
+ // Clear internal context
2154
+ this.internalContext.set({});
2155
+ // Update the model signal
2156
+ this.context.set({});
2157
+ }
2158
+ /**
2159
+ * Reset the form to its initial state
2160
+ */
2161
+ reset() {
2162
+ // Reset to initial context
2163
+ const resetContext = cloneDeep(this.initialContext);
2164
+ this.internalContext.set(resetContext);
2165
+ // Update the model signal
2166
+ this.context.set(resetContext);
2167
+ }
2168
+ //#endregion
2169
+ //#region ---- RxJS Stream Setup ----
2170
+ /**
2171
+ * Setup RxJS streams for context management
2172
+ */
2173
+ setupContextStreams() {
2174
+ // Debounced context updates from external source
2175
+ this.contextUpdateSubject
2176
+ .pipe(debounceTime(16), // ~60fps
2177
+ distinctUntilChanged((prev, curr) => isEqual(prev, curr)), startWith(this.context() ?? {}))
2178
+ .subscribe((ctx) => {
2179
+ this.internalContext.set(ctx);
2180
+ });
2181
+ // Debounced context changes from widgets
2182
+ this.contextChangeSubject
2183
+ .pipe(debounceTime(16), // ~60fps
2184
+ distinctUntilChanged((prev, curr) => isEqual(prev, curr)))
2185
+ .subscribe((ctx) => {
2186
+ this.internalContext.set(ctx);
2187
+ // Update the model signal directly - it will emit change events automatically
2188
+ this.context.set(this.internalContext());
2189
+ });
2190
+ }
2191
+ //#endregion
2192
+ //#region ---- Type Guards ----
2193
+ /**
2194
+ * Type guard to check if the input is a form definition
2195
+ */
2196
+ isFormDefinition(data) {
2197
+ return data && typeof data === 'object' && 'groups' in data && Array.isArray(data.groups);
2198
+ }
2199
+ /**
2200
+ * Type guard to check if the input is a widget node
2201
+ */
2202
+ isWidgetNode(data) {
2203
+ return data && typeof data === 'object' && 'type' in data && typeof data.type === 'string';
2204
+ }
2205
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLayoutRendererComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2206
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: AXPLayoutRendererComponent, isStandalone: true, selector: "axp-layout-renderer", inputs: { layout: { classPropertyName: "layout", publicName: "layout", isSignal: true, isRequired: true, transformFunction: null }, context: { classPropertyName: "context", publicName: "context", isSignal: true, isRequired: false, transformFunction: null }, look: { classPropertyName: "look", publicName: "look", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { context: "contextChange", contextInitiated: "contextInitiated", validityChange: "validityChange" }, viewQueries: [{ propertyName: "form", first: true, predicate: AXFormComponent, descendants: true, isSignal: true }, { propertyName: "container", first: true, predicate: AXPWidgetContainerComponent, descendants: true, isSignal: true }], ngImport: i0, template: `
2207
+ <ax-form>
2208
+ <axp-widgets-container [context]="internalContext()" (onContextChanged)="handleContextChanged($event)">
2209
+ @if (widgetTree()) {
2210
+ <ng-container axp-widget-renderer [node]="widgetTree()!" [mode]="mode()"></ng-container>
2211
+ }
2212
+ </axp-widgets-container>
2213
+ </ax-form>
2214
+ `, isInline: true, styles: [":host{display:block;width:100%}\n"], dependencies: [{ kind: "ngmodule", type: AXPWidgetCoreModule }, { kind: "component", type: i1.AXPWidgetContainerComponent, selector: "axp-widgets-container", inputs: ["context", "functions"], outputs: ["onContextChanged"] }, { kind: "directive", type: i1.AXPWidgetRendererDirective, selector: "[axp-widget-renderer]", inputs: ["parentNode", "index", "mode", "node"], outputs: ["onOptionsChanged", "onValueChanged", "onLoad"], exportAs: ["widgetRenderer"] }, { kind: "ngmodule", type: AXFormModule }, { kind: "component", type: i2.AXFormComponent, selector: "ax-form", inputs: ["disabled", "readonly", "labelMode", "look", "messageStyle", "updateOn", "inUserInteractionActive"], outputs: ["onValidate", "updateOnChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2215
+ }
2216
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLayoutRendererComponent, decorators: [{
2217
+ type: Component,
2218
+ args: [{ selector: 'axp-layout-renderer', standalone: true, imports: [AXPWidgetCoreModule, AXFormModule], changeDetection: ChangeDetectionStrategy.OnPush, template: `
2219
+ <ax-form>
2220
+ <axp-widgets-container [context]="internalContext()" (onContextChanged)="handleContextChanged($event)">
2221
+ @if (widgetTree()) {
2222
+ <ng-container axp-widget-renderer [node]="widgetTree()!" [mode]="mode()"></ng-container>
2223
+ }
2224
+ </axp-widgets-container>
2225
+ </ax-form>
2226
+ `, styles: [":host{display:block;width:100%}\n"] }]
2227
+ }], propDecorators: { layout: [{ type: i0.Input, args: [{ isSignal: true, alias: "layout", required: true }] }], context: [{ type: i0.Input, args: [{ isSignal: true, alias: "context", required: false }] }, { type: i0.Output, args: ["contextChange"] }], look: [{ type: i0.Input, args: [{ isSignal: true, alias: "look", required: false }] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], contextInitiated: [{ type: i0.Output, args: ["contextInitiated"] }], validityChange: [{ type: i0.Output, args: ["validityChange"] }], form: [{ type: i0.ViewChild, args: [i0.forwardRef(() => AXFormComponent), { isSignal: true }] }], container: [{ type: i0.ViewChild, args: [i0.forwardRef(() => AXPWidgetContainerComponent), { isSignal: true }] }] } });
2228
+
2229
+ /** Registration key for {@link AXPPreviewWidgetFieldCommand}; lives alone so `LayoutBuilderModule` can reference it without static-importing the command implementation. */
2230
+ const AXP_PREVIEW_WIDGET_FIELD_COMMAND_KEY = 'Widget:Preview';
2231
+
2232
+ class LayoutBuilderModule {
2233
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: LayoutBuilderModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
2234
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.9", ngImport: i0, type: LayoutBuilderModule, imports: [CommonModule, AXPLayoutRendererComponent], exports: [AXPLayoutRendererComponent] }); }
2235
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: LayoutBuilderModule, providers: [
2236
+ AXPLayoutBuilderService,
2237
+ provideCommandSetups([
2238
+ {
2239
+ key: AXP_PREVIEW_WIDGET_FIELD_COMMAND_KEY,
2240
+ command: () => Promise.resolve().then(function () { return previewWidgetField_command; }).then((c) => c.AXPPreviewWidgetFieldCommand),
2241
+ },
2242
+ ]),
2243
+ ], imports: [CommonModule, AXPLayoutRendererComponent] }); }
2244
+ }
2245
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: LayoutBuilderModule, decorators: [{
2246
+ type: NgModule,
2247
+ args: [{
2248
+ imports: [CommonModule, AXPLayoutRendererComponent],
2249
+ providers: [
2250
+ AXPLayoutBuilderService,
2251
+ provideCommandSetups([
2252
+ {
2253
+ key: AXP_PREVIEW_WIDGET_FIELD_COMMAND_KEY,
2254
+ command: () => Promise.resolve().then(function () { return previewWidgetField_command; }).then((c) => c.AXPPreviewWidgetFieldCommand),
2255
+ },
2256
+ ]),
2257
+ ],
2258
+ exports: [AXPLayoutRendererComponent],
2259
+ }]
2260
+ }] });
2261
+
2262
+ //#endregion
2263
+
2264
+ class AXPDialogRendererComponent extends AXBasePageComponent {
2265
+ constructor() {
2266
+ super(...arguments);
2267
+ this.result = new EventEmitter();
2268
+ this.expressionEvaluator = inject(AXPExpressionEvaluatorService);
2269
+ this.context = signal({}, ...(ngDevMode ? [{ debugName: "context" }] : /* istanbul ignore next */ []));
2270
+ // This will be set by the popup service automatically - same as dynamic-dialog
2271
+ this.callBack = () => { };
2272
+ this.isDialogLoading = signal(false, ...(ngDevMode ? [{ debugName: "isDialogLoading" }] : /* istanbul ignore next */ []));
2273
+ // Aggregated actions for footer rendering
2274
+ this.footerPrefix = signal([], ...(ngDevMode ? [{ debugName: "footerPrefix" }] : /* istanbul ignore next */ []));
2275
+ this.footerSuffix = signal([], ...(ngDevMode ? [{ debugName: "footerSuffix" }] : /* istanbul ignore next */ []));
2276
+ //#endregion
2277
+ //#region ---- View Accessors ----
2278
+ // Access the internal layout renderer to reach the widgets container injector
2279
+ this.layoutRenderer = viewChild(AXPLayoutRendererComponent, ...(ngDevMode ? [{ debugName: "layoutRenderer" }] : /* istanbul ignore next */ []));
2280
+ this.#eff = effect(() => {
2281
+ let count = 0;
2282
+ this.aggregateAndEvaluateActions();
2283
+ if (!this.widgetCoreService) {
2284
+ const renderer = this.layoutRenderer();
2285
+ const container = renderer?.getContainer();
2286
+ this.widgetCoreService = container?.builderService ?? null;
2287
+ count = this.widgetCoreService?.registeredWidgetsCount();
2288
+ }
2289
+ else {
2290
+ count = this.widgetCoreService?.registeredWidgetsCount();
2291
+ // Clear existing timer
2292
+ if (this.debounceTimer) {
2293
+ clearTimeout(this.debounceTimer);
2294
+ }
2295
+ // Set new timer to call after 200ms of no count changes
2296
+ this.debounceTimer = setTimeout(() => {
2297
+ this.aggregateAndEvaluateActions();
2298
+ }, 200);
2299
+ }
2300
+ }, ...(ngDevMode ? [{ debugName: "#eff" }] : /* istanbul ignore next */ []));
2301
+ }
2302
+ //#endregion
2303
+ //#region ---- Lifecycle ----
2304
+ ngOnInit() {
2305
+ this.context.set(this.config?.context || {});
2306
+ }
2307
+ #eff;
2308
+ //#endregion
2309
+ handleContextChanged(event) {
2310
+ this.context.set(event);
2311
+ this.aggregateAndEvaluateActions();
2312
+ }
2313
+ handleContextInitiated(event) {
2314
+ this.context.set(event);
2315
+ this.aggregateAndEvaluateActions();
2316
+ }
2317
+ footerPrefixActions() {
2318
+ return this.footerPrefix();
2319
+ }
2320
+ footerSuffixActions() {
2321
+ return this.footerSuffix();
2322
+ }
2323
+ isFormLoading() {
2324
+ return this.isDialogLoading();
2325
+ }
2326
+ isSubmitting() {
2327
+ return this.isDialogLoading();
2328
+ }
2329
+ async executeAction(action) {
2330
+ const cmd = this.resolveActionCommandName(action.command);
2331
+ if (cmd !== 'cancel') {
2332
+ const isValid = await this.layoutRenderer()?.validate();
2333
+ if (!isValid?.result) {
2334
+ return;
2335
+ }
2336
+ }
2337
+ if (cmd?.startsWith('widget:')) {
2338
+ const parsed = this.parseWidgetCommand(cmd);
2339
+ if (parsed.widgetName && parsed.action) {
2340
+ await this.executeWidgetApi(parsed.widgetName, parsed.action);
2341
+ await this.aggregateAndEvaluateActions();
2342
+ return;
2343
+ }
2344
+ }
2345
+ const context = this.context();
2346
+ const onAction = this.config?.onAction;
2347
+ if (onAction) {
2348
+ const dialogRef = {
2349
+ close: (res) => this.close(res),
2350
+ context: () => this.context(),
2351
+ action: () => action.command ?? undefined,
2352
+ setLoading: (loading) => this.isDialogLoading.set(loading),
2353
+ };
2354
+ try {
2355
+ this.isDialogLoading.set(true);
2356
+ const result = await Promise.resolve(onAction(dialogRef));
2357
+ this.callBack(result);
2358
+ this.close(result);
2359
+ }
2360
+ catch {
2361
+ // Handler threw: stay open for retry, actions remain clickable
2362
+ }
2363
+ finally {
2364
+ this.isDialogLoading.set(false);
2365
+ }
2366
+ return;
2367
+ }
2368
+ // Fallback: treat as regular dialog action (cancel/confirm/custom)
2369
+ const result = { context, action: cmd };
2370
+ this.dialogResult = result;
2371
+ if (this.data) {
2372
+ this.data.context = result.context;
2373
+ this.data.action = result.action;
2374
+ }
2375
+ this.callBack({
2376
+ close: (res) => {
2377
+ this.close(res);
2378
+ },
2379
+ context: () => this.context(),
2380
+ action: () => result.action,
2381
+ setLoading: (loading) => {
2382
+ this.isDialogLoading.set(loading);
2383
+ },
2384
+ });
2385
+ // Without `onAction`, only the configured cancel action dismisses the dialog (not submit/custom).
2386
+ if (cmd === 'cancel') {
2387
+ await this.close(result);
2388
+ }
2389
+ }
2390
+ /** Resolves footer/widget action command to a string (e.g. `cancel`, `submit`, `widget:...`). */
2391
+ resolveActionCommandName(command) {
2392
+ if (typeof command === 'string') {
2393
+ return command;
2394
+ }
2395
+ if (command && typeof command === 'object' && 'name' in command) {
2396
+ return command.name;
2397
+ }
2398
+ return undefined;
2399
+ }
2400
+ parseWidgetCommand(cmd) {
2401
+ // Expected 'widget:<widgetName>.<action>'
2402
+ if (!cmd?.startsWith('widget:'))
2403
+ return {};
2404
+ const rest = cmd.slice('widget:'.length);
2405
+ const dot = rest.lastIndexOf('.');
2406
+ if (dot <= 0)
2407
+ return {};
2408
+ return { widgetName: rest.slice(0, dot), action: rest.slice(dot + 1) };
2409
+ }
2410
+ async executeWidgetApi(widgetName, apiMethod) {
2411
+ if (!this.widgetCoreService)
2412
+ return;
2413
+ try {
2414
+ const widget = this.widgetCoreService.getWidget(widgetName);
2415
+ const api = widget?.api?.();
2416
+ const fn = api?.[apiMethod];
2417
+ if (typeof fn === 'function') {
2418
+ await Promise.resolve(fn({
2419
+ close: (result) => {
2420
+ this.close(result);
2421
+ },
2422
+ context: () => this.context(),
2423
+ setLoading: (loading) => {
2424
+ this.isDialogLoading.set(loading);
2425
+ },
2426
+ }));
2427
+ }
2428
+ }
2429
+ catch { }
2430
+ }
2431
+ async close(result) {
2432
+ if (result) {
2433
+ const isValid = await this.layoutRenderer()?.validate();
2434
+ if (isValid?.result) {
2435
+ this.result.emit(result);
2436
+ }
2437
+ }
2438
+ super.close(result);
2439
+ }
2440
+ // --- Actions aggregation & evaluation ---
2441
+ async aggregateAndEvaluateActions() {
2442
+ const widgetActions = await this.collectWidgetActions();
2443
+ const dialogActions = this.collectDialogActionsFromConfig();
2444
+ const all = [...widgetActions, ...dialogActions];
2445
+ const evaluated = await this.evaluatePredicates(all);
2446
+ const visible = evaluated.filter((a) => a.hidden !== true);
2447
+ const prefix = visible.filter((a) => (a.zone ?? 'footer') === 'footer' && (a.placement ?? 'suffix') === 'prefix');
2448
+ const suffix = visible.filter((a) => (a.zone ?? 'footer') === 'footer' && (a.placement ?? 'suffix') === 'suffix');
2449
+ this.footerPrefix.set(prefix);
2450
+ this.footerSuffix.set(suffix);
2451
+ }
2452
+ async collectWidgetActions() {
2453
+ if (!this.widgetCoreService)
2454
+ return [];
2455
+ const names = this.widgetCoreService.listRegisteredWidgetNames();
2456
+ const actions = [];
2457
+ for (const name of names) {
2458
+ try {
2459
+ const widget = this.widgetCoreService.getWidget(name);
2460
+ const widgetActs = widget?.actions?.() ?? [];
2461
+ actions.push(...widgetActs);
2462
+ }
2463
+ catch { }
2464
+ }
2465
+ return actions;
2466
+ }
2467
+ collectDialogActionsFromConfig() {
2468
+ const footer = this.config?.actions?.footer;
2469
+ const mapOne = (a, placement) => ({
2470
+ title: a.title,
2471
+ command: typeof a.command === 'string' ? a.command : a.command?.name,
2472
+ icon: a.icon,
2473
+ color: a.color,
2474
+ disabled: a.disabled,
2475
+ hidden: a.hidden,
2476
+ zone: 'footer',
2477
+ placement,
2478
+ scope: a.scope,
2479
+ });
2480
+ const prefix = (footer?.prefix || []).map((a) => mapOne(a, 'prefix'));
2481
+ const suffix = (footer?.suffix || []).map((a) => mapOne(a, 'suffix'));
2482
+ return [...prefix, ...suffix];
2483
+ }
2484
+ async evaluatePredicates(actions) {
2485
+ const out = [];
2486
+ for (const a of actions) {
2487
+ const parsed = typeof a.command === 'string' ? this.parseWidgetCommand(a.command) : {};
2488
+ const api = parsed.widgetName ? await this.resolveApi(parsed.widgetName) : undefined;
2489
+ const scope = {
2490
+ api,
2491
+ widget: { name: parsed.widgetName },
2492
+ dialog: { context: this.context() },
2493
+ context: this.context(),
2494
+ };
2495
+ const disabled = await this.evalBool(a.disabled, scope);
2496
+ const hidden = await this.evalBool(a.hidden, scope);
2497
+ out.push({ ...a, disabled, hidden });
2498
+ }
2499
+ return out;
2500
+ }
2501
+ async evalBool(value, scope) {
2502
+ if (typeof value === 'boolean')
2503
+ return value;
2504
+ if (typeof value === 'string') {
2505
+ try {
2506
+ const result = await this.expressionEvaluator.evaluate(value, scope);
2507
+ return !!result;
2508
+ }
2509
+ catch {
2510
+ return false;
2511
+ }
2512
+ }
2513
+ return value;
2514
+ }
2515
+ async resolveApi(widgetName) {
2516
+ try {
2517
+ await this.widgetCoreService?.waitForWidget(widgetName, 2000);
2518
+ const widget = this.widgetCoreService?.getWidget(widgetName);
2519
+ return widget?.api?.();
2520
+ }
2521
+ catch {
2522
+ return undefined;
2523
+ }
2524
+ }
2525
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPDialogRendererComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
2526
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: AXPDialogRendererComponent, isStandalone: true, selector: "axp-dialog-renderer", outputs: { result: "result" }, providers: [AXPContextStore], viewQueries: [{ propertyName: "layoutRenderer", first: true, predicate: AXPLayoutRendererComponent, descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: `
2527
+ <axp-component-slot name="dialog-header" [context]="context()"></axp-component-slot>
2528
+ <div class="ax-p-4">
2529
+ <axp-layout-renderer
2530
+ [layout]="config.definition"
2531
+ [context]="context()"
2532
+ (contextChange)="handleContextChanged($event)"
2533
+ (contextInitiated)="handleContextInitiated($event)"
2534
+ >
2535
+ </axp-layout-renderer>
2536
+ </div>
2537
+
2538
+ <!-- Custom footer slot: if it has content, default footer is hidden -->
2539
+ <axp-component-slot name="dialog-footer" #footerSlot="slot" [context]="context()"></axp-component-slot>
2540
+ @if (footerSlot.isEmpty()) {
2541
+ <ax-footer>
2542
+ <ax-prefix>
2543
+ <axp-component-slot name="dialog-footer-prefix" [context]="context()"></axp-component-slot>
2544
+ @for (action of footerPrefixActions(); track $index) {
2545
+ <ax-button
2546
+ [disabled]="action.disabled || isFormLoading()"
2547
+ [text]="(action.title | translate | async)!"
2548
+ [look]="'outline'"
2549
+ [color]="action.color"
2550
+ (onClick)="executeAction(action)"
2551
+ >
2552
+ <ax-prefix>
2553
+ <i class="{{ action.icon }}"></i>
2554
+ </ax-prefix>
2555
+ </ax-button>
2556
+ }
2557
+ </ax-prefix>
2558
+ <ax-suffix>
2559
+ @for (action of footerSuffixActions(); track $index) {
2560
+ <ax-button
2561
+ [disabled]="action.disabled || isSubmitting()"
2562
+ [text]="(action.title | translate | async)!"
2563
+ [look]="'solid'"
2564
+ [color]="action.color"
2565
+ (onClick)="executeAction(action)"
2566
+ >
2567
+ @if (isFormLoading()) {
2568
+ <ax-loading></ax-loading>
2569
+ }
2570
+ @if (action.icon) {
2571
+ <ax-prefix>
2572
+ <ax-icon icon="{{ action.icon }}"></ax-icon>
2573
+ </ax-prefix>
2574
+ }
2575
+ </ax-button>
2576
+ }
2577
+ <axp-component-slot name="dialog-footer-suffix" [context]="context()"></axp-component-slot>
2578
+ </ax-suffix>
2579
+ </ax-footer>
2580
+ }
2581
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: AXPLayoutRendererComponent, selector: "axp-layout-renderer", inputs: ["layout", "context", "look", "mode"], outputs: ["contextChange", "contextInitiated", "validityChange"] }, { kind: "ngmodule", type: AXButtonModule }, { kind: "component", type: i1$1.AXButtonComponent, selector: "ax-button", inputs: ["disabled", "size", "tabIndex", "color", "look", "text", "toggleable", "selected", "iconOnly", "type", "loadingText"], outputs: ["onBlur", "onFocus", "onClick", "selectedChange", "toggleableChange", "lookChange", "colorChange", "disabledChange", "loadingTextChange"] }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i2$1.AXDecoratorIconComponent, selector: "ax-icon", inputs: ["icon"] }, { kind: "component", type: i2$1.AXDecoratorGenericComponent, selector: "ax-footer, ax-header, ax-content, ax-divider, ax-form-hint, ax-prefix, ax-suffix, ax-text, ax-title, ax-subtitle, ax-placeholder, ax-overlay" }, { kind: "ngmodule", type: AXLoadingModule }, { kind: "component", type: i3.AXLoadingComponent, selector: "ax-loading", inputs: ["visible", "type", "context"], outputs: ["visibleChange"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "ngmodule", type: AXPComponentSlotModule }, { kind: "directive", type: i4.AXPComponentSlotDirective, selector: "axp-component-slot", inputs: ["name", "host", "context"], exportAs: ["slot"] }, { kind: "pipe", type: i5.AsyncPipe, name: "async" }, { kind: "pipe", type: i6.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2582
+ }
2583
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPDialogRendererComponent, decorators: [{
2584
+ type: Component,
2585
+ args: [{
2586
+ selector: 'axp-dialog-renderer',
2587
+ standalone: true,
2588
+ imports: [
2589
+ CommonModule,
2590
+ AXPLayoutRendererComponent,
2591
+ AXButtonModule,
2592
+ AXDecoratorModule,
2593
+ AXLoadingModule,
2594
+ AXTranslationModule,
2595
+ AXPComponentSlotModule,
2596
+ ],
2597
+ providers: [AXPContextStore],
2598
+ changeDetection: ChangeDetectionStrategy.OnPush,
2599
+ template: `
2600
+ <axp-component-slot name="dialog-header" [context]="context()"></axp-component-slot>
2601
+ <div class="ax-p-4">
2602
+ <axp-layout-renderer
2603
+ [layout]="config.definition"
2604
+ [context]="context()"
2605
+ (contextChange)="handleContextChanged($event)"
2606
+ (contextInitiated)="handleContextInitiated($event)"
2607
+ >
2608
+ </axp-layout-renderer>
2609
+ </div>
2610
+
2611
+ <!-- Custom footer slot: if it has content, default footer is hidden -->
2612
+ <axp-component-slot name="dialog-footer" #footerSlot="slot" [context]="context()"></axp-component-slot>
2613
+ @if (footerSlot.isEmpty()) {
2614
+ <ax-footer>
2615
+ <ax-prefix>
2616
+ <axp-component-slot name="dialog-footer-prefix" [context]="context()"></axp-component-slot>
2617
+ @for (action of footerPrefixActions(); track $index) {
2618
+ <ax-button
2619
+ [disabled]="action.disabled || isFormLoading()"
2620
+ [text]="(action.title | translate | async)!"
2621
+ [look]="'outline'"
2622
+ [color]="action.color"
2623
+ (onClick)="executeAction(action)"
2624
+ >
2625
+ <ax-prefix>
2626
+ <i class="{{ action.icon }}"></i>
2627
+ </ax-prefix>
2628
+ </ax-button>
2629
+ }
2630
+ </ax-prefix>
2631
+ <ax-suffix>
2632
+ @for (action of footerSuffixActions(); track $index) {
2633
+ <ax-button
2634
+ [disabled]="action.disabled || isSubmitting()"
2635
+ [text]="(action.title | translate | async)!"
2636
+ [look]="'solid'"
2637
+ [color]="action.color"
2638
+ (onClick)="executeAction(action)"
2639
+ >
2640
+ @if (isFormLoading()) {
2641
+ <ax-loading></ax-loading>
2642
+ }
2643
+ @if (action.icon) {
2644
+ <ax-prefix>
2645
+ <ax-icon icon="{{ action.icon }}"></ax-icon>
2646
+ </ax-prefix>
2647
+ }
2648
+ </ax-button>
2649
+ }
2650
+ <axp-component-slot name="dialog-footer-suffix" [context]="context()"></axp-component-slot>
2651
+ </ax-suffix>
2652
+ </ax-footer>
2653
+ }
2654
+ `,
2655
+ }]
2656
+ }], propDecorators: { result: [{
2657
+ type: Output
2658
+ }], layoutRenderer: [{ type: i0.ViewChild, args: [i0.forwardRef(() => AXPLayoutRendererComponent), { isSignal: true }] }] } });
2659
+
2660
+ var dialogRenderer_component = /*#__PURE__*/Object.freeze({
2661
+ __proto__: null,
2662
+ AXPDialogRendererComponent: AXPDialogRendererComponent
2663
+ });
2664
+
2665
+ //#region ---- Imports ----
2666
+ /**
2667
+ * `customWidget` only forwards keys from its options bag into the built node via `addSingleWidget`.
2668
+ * Designer / configurator persist `defaultValue` (and other extended fields) on the widget node root;
2669
+ * spreading `options` alone drops them, so preview never applied defaults.
2670
+ */
2671
+ /**
2672
+ * Widget options are sometimes persisted with an extra nesting (`options.options`) when context
2673
+ * was merged incorrectly. Flatten so list/data-source resolution sees `dataSource` at the top level.
2674
+ */
2675
+ function optionsBagForPreview(node) {
2676
+ const raw = (node.options ?? {});
2677
+ const inner = raw['options'];
2678
+ if (inner !== undefined && typeof inner === 'object' && !Array.isArray(inner)) {
2679
+ const { options: _nested, ...rest } = raw;
2680
+ return { ...rest, ...inner };
2681
+ }
2682
+ return { ...raw };
2683
+ }
2684
+ function extendedNodePropsForPreview(node) {
2685
+ const out = {};
2686
+ if (node.defaultValue !== undefined) {
2687
+ out['defaultValue'] = node.defaultValue;
2688
+ }
2689
+ if (node.triggers !== undefined) {
2690
+ out['triggers'] = node.triggers;
2691
+ }
2692
+ if (node.meta !== undefined) {
2693
+ out['meta'] = node.meta;
2694
+ }
2695
+ if (node.valueTransforms !== undefined) {
2696
+ out['valueTransforms'] = node.valueTransforms;
2697
+ }
2698
+ if (node.visible !== undefined) {
2699
+ out['visible'] = node.visible;
2700
+ }
2701
+ if (node.mode !== undefined) {
2702
+ out['mode'] = node.mode;
2703
+ }
2704
+ if (node.children !== undefined) {
2705
+ out['children'] = node.children;
2706
+ }
2707
+ return out;
2708
+ }
2709
+ //#endregion
2710
+ //#region ---- Command ----
2711
+ /**
2712
+ * Opens a dialog that previews a widget configuration (same behavior as the preview button on
2713
+ * `axp-widget-field-configurator`). Invoked from that component and from entity list actions.
2714
+ */
2715
+ class AXPPreviewWidgetFieldCommand {
2716
+ constructor() {
2717
+ this.formBuilderService = inject(AXPLayoutBuilderService);
2718
+ this.widgetRegistry = inject(AXPWidgetRegistryService);
2719
+ this.translationService = inject(AXTranslationService);
2720
+ this.mlResolver = inject(AXPMultiLanguageStringResolverService);
2721
+ this.crudService = inject(AXP_ENTITY_DEFINITION_CRUD_SERVICE, { optional: true });
2722
+ }
2723
+ async execute(input) {
2724
+ try {
2725
+ const merged = this.mergeInvocation(input);
2726
+ const currentWidget = this.normalizeWidget(merged['widget'] ?? merged['interface']);
2727
+ if (!currentWidget?.type) {
2728
+ return {
2729
+ success: false,
2730
+ message: { text: (await this.translationService.translateAsync('@general:messages.invalid-data')) || 'Invalid data' },
2731
+ };
2732
+ }
2733
+ const fieldName = String(merged['fieldName'] ?? merged['name'] ?? 'Field');
2734
+ const rawTitle = (merged['fieldTitle'] ?? merged['title']);
2735
+ const fieldTitleLabel = this.resolveFieldTitleLabel(rawTitle, fieldName);
2736
+ const dialogTitle = (await this.resolveWidgetDisplayTitle(currentWidget.type)) ||
2737
+ currentWidget.type ||
2738
+ fieldTitleLabel;
2739
+ const previewWidgetOptions = {
2740
+ ...optionsBagForPreview(currentWidget),
2741
+ name: fieldName,
2742
+ ...extendedNodePropsForPreview(currentWidget),
2743
+ };
2744
+ const dialogOutcome = await this.formBuilderService
2745
+ .create()
2746
+ .dialog((dialog) => {
2747
+ dialog
2748
+ .setTitle(dialogTitle)
2749
+ .setSize('md')
2750
+ .setCloseButton(true)
2751
+ .setContext({})
2752
+ .content((layoutBuilder) => {
2753
+ layoutBuilder.formField(fieldTitleLabel, (formField) => {
2754
+ formField.customWidget(currentWidget.type, previewWidgetOptions);
2755
+ });
2756
+ })
2757
+ .setActions((actions) => actions.cancel('@general:actions.close.title'));
2758
+ })
2759
+ .show();
2760
+ const cancelled = this.isCancelDialogOutcome(dialogOutcome);
2761
+ return {
2762
+ success: !cancelled,
2763
+ message: { text: '' },
2764
+ };
2765
+ }
2766
+ catch (error) {
2767
+ const message = error instanceof Error ? error.message : 'Unknown error';
2768
+ return {
2769
+ success: false,
2770
+ message: { text: message },
2771
+ };
2772
+ }
2773
+ }
2774
+ mergeInvocation(input) {
2775
+ const contextOptions = input.__context__?.options;
2776
+ const ctxData = input.__context__?.data;
2777
+ const { __context__: _ctx, ...rest } = input;
2778
+ return {
2779
+ ...(ctxData ?? {}),
2780
+ ...(contextOptions ?? {}),
2781
+ ...rest,
2782
+ };
2783
+ }
2784
+ normalizeWidget(raw) {
2785
+ if (raw == null)
2786
+ return null;
2787
+ if (typeof raw === 'string') {
2788
+ const t = raw.trim();
2789
+ return t ? { type: t, options: {} } : null;
2790
+ }
2791
+ if (typeof raw === 'object' && !Array.isArray(raw) && 'type' in raw) {
2792
+ const w = raw;
2793
+ return w.type ? cloneDeep(w) : null;
2794
+ }
2795
+ return null;
2796
+ }
2797
+ resolveFieldTitleLabel(raw, fallback) {
2798
+ let source = fallback;
2799
+ if (raw !== undefined && raw !== null) {
2800
+ if (typeof raw === 'string') {
2801
+ if (raw.trim() !== '') {
2802
+ source = raw;
2803
+ }
2804
+ }
2805
+ else if (typeof raw === 'object' && !Array.isArray(raw) && Object.keys(raw).length > 0) {
2806
+ source = raw;
2807
+ }
2808
+ }
2809
+ return this.mlResolver.resolve(source);
2810
+ }
2811
+ isCancelDialogOutcome(outcome) {
2812
+ if (outcome == null) {
2813
+ return false;
2814
+ }
2815
+ const ref = outcome;
2816
+ if (typeof ref.action !== 'function') {
2817
+ return false;
2818
+ }
2819
+ return ref.action() === 'cancel';
2820
+ }
2821
+ async resolveWidgetDisplayTitle(widgetType) {
2822
+ const crud = this.crudService;
2823
+ if (crud) {
2824
+ const interfaces = await crud.listInterfaces();
2825
+ const iface = interfaces.find((d) => d.name === widgetType);
2826
+ return iface?.title ?? iface?.name;
2827
+ }
2828
+ return this.widgetRegistry.resolve(widgetType)?.title;
2829
+ }
2830
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPPreviewWidgetFieldCommand, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2831
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPPreviewWidgetFieldCommand }); }
2832
+ }
2833
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPPreviewWidgetFieldCommand, decorators: [{
2834
+ type: Injectable
2835
+ }] });
2836
+
2837
+ var previewWidgetField_command = /*#__PURE__*/Object.freeze({
2838
+ __proto__: null,
2839
+ AXPPreviewWidgetFieldCommand: AXPPreviewWidgetFieldCommand
2840
+ });
2841
+
2842
+ /**
2843
+ * Generated bundle index. Do not edit.
2844
+ */
2845
+
2846
+ export { AXPDialogRendererComponent, AXPLayoutBuilderService, AXPLayoutConversionService, AXPLayoutRendererComponent, AXPPreviewWidgetFieldCommand, AXP_PREVIEW_WIDGET_FIELD_COMMAND_KEY, LayoutBuilderModule };
2847
+ //# sourceMappingURL=acorex-platform-layout-builder.mjs.map