@acorex/platform 20.6.0-next.8 → 21.0.0-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/auth/index.d.ts +91 -12
  2. package/common/index.d.ts +615 -44
  3. package/core/index.d.ts +718 -422
  4. package/fesm2022/acorex-platform-auth.mjs +152 -39
  5. package/fesm2022/acorex-platform-auth.mjs.map +1 -1
  6. package/fesm2022/acorex-platform-common.mjs +1009 -112
  7. package/fesm2022/acorex-platform-common.mjs.map +1 -1
  8. package/fesm2022/acorex-platform-core.mjs +887 -408
  9. package/fesm2022/acorex-platform-core.mjs.map +1 -1
  10. package/fesm2022/acorex-platform-domain.mjs +99 -11
  11. package/fesm2022/acorex-platform-domain.mjs.map +1 -1
  12. package/fesm2022/acorex-platform-layout-builder.mjs +555 -492
  13. package/fesm2022/acorex-platform-layout-builder.mjs.map +1 -1
  14. package/fesm2022/acorex-platform-layout-components.mjs +2446 -2733
  15. package/fesm2022/acorex-platform-layout-components.mjs.map +1 -1
  16. package/fesm2022/acorex-platform-layout-designer.mjs +9 -9
  17. package/fesm2022/acorex-platform-layout-designer.mjs.map +1 -1
  18. package/fesm2022/acorex-platform-layout-entity.mjs +9708 -4721
  19. package/fesm2022/acorex-platform-layout-entity.mjs.map +1 -1
  20. package/fesm2022/acorex-platform-layout-views.mjs +32 -26
  21. package/fesm2022/acorex-platform-layout-views.mjs.map +1 -1
  22. package/fesm2022/acorex-platform-layout-widget-core.mjs +252 -182
  23. package/fesm2022/acorex-platform-layout-widget-core.mjs.map +1 -1
  24. package/fesm2022/{acorex-platform-layout-widgets-file-list-popup.component-D0y-9nE5.mjs → acorex-platform-layout-widgets-file-list-popup.component-CxrsI6Hn.mjs} +2 -2
  25. package/fesm2022/acorex-platform-layout-widgets-file-list-popup.component-CxrsI6Hn.mjs.map +1 -0
  26. package/fesm2022/acorex-platform-layout-widgets-image-preview.popup-V31OpYah.mjs +30 -0
  27. package/fesm2022/acorex-platform-layout-widgets-image-preview.popup-V31OpYah.mjs.map +1 -0
  28. package/fesm2022/{acorex-platform-layout-widgets-tabular-data-edit-popup.component-C1l2KSDa.mjs → acorex-platform-layout-widgets-tabular-data-edit-popup.component-Ck7-wpT2.mjs} +2 -2
  29. package/fesm2022/acorex-platform-layout-widgets-tabular-data-edit-popup.component-Ck7-wpT2.mjs.map +1 -0
  30. package/fesm2022/{acorex-platform-layout-widgets-tabular-data-view-popup.component-D-31ej0C.mjs → acorex-platform-layout-widgets-tabular-data-view-popup.component-y8vjUiVs.mjs} +2 -2
  31. package/fesm2022/acorex-platform-layout-widgets-tabular-data-view-popup.component-y8vjUiVs.mjs.map +1 -0
  32. package/fesm2022/acorex-platform-layout-widgets.mjs +9789 -6817
  33. package/fesm2022/acorex-platform-layout-widgets.mjs.map +1 -1
  34. package/fesm2022/acorex-platform-runtime.mjs +79 -3
  35. package/fesm2022/acorex-platform-runtime.mjs.map +1 -1
  36. package/fesm2022/acorex-platform-themes-default-entity-master-create-view.component-VIGuU5M4.mjs +157 -0
  37. package/fesm2022/acorex-platform-themes-default-entity-master-create-view.component-VIGuU5M4.mjs.map +1 -0
  38. package/fesm2022/acorex-platform-themes-default-entity-master-list-view.component-DyDa_hyd.mjs +1542 -0
  39. package/fesm2022/acorex-platform-themes-default-entity-master-list-view.component-DyDa_hyd.mjs.map +1 -0
  40. package/fesm2022/acorex-platform-themes-default-entity-master-modify-view.component-Ua3ZA5hk.mjs +101 -0
  41. package/fesm2022/acorex-platform-themes-default-entity-master-modify-view.component-Ua3ZA5hk.mjs.map +1 -0
  42. package/fesm2022/{acorex-platform-themes-default-entity-master-single-view.component-B_P0a5KW.mjs → acorex-platform-themes-default-entity-master-single-view.component-eMBby9k4.mjs} +3 -3
  43. package/fesm2022/acorex-platform-themes-default-entity-master-single-view.component-eMBby9k4.mjs.map +1 -0
  44. package/fesm2022/acorex-platform-themes-default.mjs +282 -43
  45. package/fesm2022/acorex-platform-themes-default.mjs.map +1 -1
  46. package/fesm2022/acorex-platform-themes-shared-icon-chooser-column.component-C0EpfU2k.mjs +55 -0
  47. package/fesm2022/acorex-platform-themes-shared-icon-chooser-column.component-C0EpfU2k.mjs.map +1 -0
  48. package/fesm2022/acorex-platform-themes-shared.mjs +42 -137
  49. package/fesm2022/acorex-platform-themes-shared.mjs.map +1 -1
  50. package/fesm2022/acorex-platform-workflow.mjs +658 -45
  51. package/fesm2022/acorex-platform-workflow.mjs.map +1 -1
  52. package/layout/builder/index.d.ts +10 -34
  53. package/layout/components/index.d.ts +694 -375
  54. package/layout/designer/index.d.ts +4 -4
  55. package/layout/entity/index.d.ts +802 -183
  56. package/layout/views/index.d.ts +5 -58
  57. package/layout/widget-core/index.d.ts +63 -75
  58. package/layout/widgets/README.md +0 -1
  59. package/layout/widgets/index.d.ts +498 -129
  60. package/package.json +5 -5
  61. package/runtime/index.d.ts +36 -8
  62. package/themes/default/index.d.ts +44 -75
  63. package/themes/shared/index.d.ts +11 -49
  64. package/workflow/index.d.ts +401 -90
  65. package/fesm2022/acorex-platform-layout-entity-create-entity.command-DGeylNSY.mjs +0 -52
  66. package/fesm2022/acorex-platform-layout-entity-create-entity.command-DGeylNSY.mjs.map +0 -1
  67. package/fesm2022/acorex-platform-layout-widgets-extra-properties-schema-widget-edit.component-fhhZOWul.mjs +0 -50
  68. package/fesm2022/acorex-platform-layout-widgets-extra-properties-schema-widget-edit.component-fhhZOWul.mjs.map +0 -1
  69. package/fesm2022/acorex-platform-layout-widgets-extra-properties-schema-widget-view.component-C3Qbs0fz.mjs +0 -42
  70. package/fesm2022/acorex-platform-layout-widgets-extra-properties-schema-widget-view.component-C3Qbs0fz.mjs.map +0 -1
  71. package/fesm2022/acorex-platform-layout-widgets-extra-properties-values-widget-edit.component-CngQBUlN.mjs +0 -55
  72. package/fesm2022/acorex-platform-layout-widgets-extra-properties-values-widget-edit.component-CngQBUlN.mjs.map +0 -1
  73. package/fesm2022/acorex-platform-layout-widgets-extra-properties-values-widget-view.component-DSNo9e4W.mjs +0 -50
  74. package/fesm2022/acorex-platform-layout-widgets-extra-properties-values-widget-view.component-DSNo9e4W.mjs.map +0 -1
  75. package/fesm2022/acorex-platform-layout-widgets-extra-properties-widget-edit.component-CL0CwEHX.mjs +0 -48
  76. package/fesm2022/acorex-platform-layout-widgets-extra-properties-widget-edit.component-CL0CwEHX.mjs.map +0 -1
  77. package/fesm2022/acorex-platform-layout-widgets-extra-properties-widget-view.component-B6Fi0xTw.mjs +0 -42
  78. package/fesm2022/acorex-platform-layout-widgets-extra-properties-widget-view.component-B6Fi0xTw.mjs.map +0 -1
  79. package/fesm2022/acorex-platform-layout-widgets-file-list-popup.component-D0y-9nE5.mjs.map +0 -1
  80. package/fesm2022/acorex-platform-layout-widgets-tabular-data-edit-popup.component-C1l2KSDa.mjs.map +0 -1
  81. package/fesm2022/acorex-platform-layout-widgets-tabular-data-view-popup.component-D-31ej0C.mjs.map +0 -1
  82. package/fesm2022/acorex-platform-themes-default-entity-master-create-view.component-WbPPqDON.mjs +0 -115
  83. package/fesm2022/acorex-platform-themes-default-entity-master-create-view.component-WbPPqDON.mjs.map +0 -1
  84. package/fesm2022/acorex-platform-themes-default-entity-master-list-view.component-CD7rJIMh.mjs +0 -803
  85. package/fesm2022/acorex-platform-themes-default-entity-master-list-view.component-CD7rJIMh.mjs.map +0 -1
  86. package/fesm2022/acorex-platform-themes-default-entity-master-modify-view.component-BTA6h7Xd.mjs +0 -101
  87. package/fesm2022/acorex-platform-themes-default-entity-master-modify-view.component-BTA6h7Xd.mjs.map +0 -1
  88. package/fesm2022/acorex-platform-themes-default-entity-master-single-view.component-B_P0a5KW.mjs.map +0 -1
@@ -1,14 +1,13 @@
1
1
  import * as i4 from '@angular/common';
2
2
  import { CommonModule } from '@angular/common';
3
3
  import * as i0 from '@angular/core';
4
- import { inject, Injectable, input, model, signal, effect, output, viewChild, ChangeDetectionStrategy, Component, NgModule, EventEmitter, Output, Input } from '@angular/core';
4
+ import { Injectable, inject, input, model, signal, effect, output, viewChild, ChangeDetectionStrategy, Component, NgModule, EventEmitter, Output, Input } from '@angular/core';
5
5
  import { AXPopupService } from '@acorex/components/popup';
6
+ import { cloneDeep, isNil, set, isEqual } from 'lodash-es';
6
7
  import * as i2 from '@acorex/components/form';
7
8
  import { AXFormComponent, AXFormModule } from '@acorex/components/form';
8
- import { AXPExpressionEvaluatorService } from '@acorex/platform/core';
9
9
  import * as i1 from '@acorex/platform/layout/widget-core';
10
10
  import { AXPWidgetContainerComponent, AXPPageStatus, AXPWidgetCoreModule } from '@acorex/platform/layout/widget-core';
11
- import { isEqual, get, cloneDeep } from 'lodash-es';
12
11
  import { Subject, debounceTime, distinctUntilChanged, startWith } from 'rxjs';
13
12
  import * as i1$1 from '@acorex/components/button';
14
13
  import { AXButtonModule } from '@acorex/components/button';
@@ -19,6 +18,282 @@ import { AXLoadingModule } from '@acorex/components/loading';
19
18
  import { AXBasePageComponent } from '@acorex/components/page';
20
19
  import * as i5 from '@acorex/core/translation';
21
20
  import { AXTranslationModule } from '@acorex/core/translation';
21
+ import { AXPExpressionEvaluatorService } from '@acorex/platform/core';
22
+
23
+ class AXPLayoutConversionService {
24
+ constructor() {
25
+ //#region ---- Caching ----
26
+ this.widgetTreeCache = new Map();
27
+ this.formDefinitionCache = new Map();
28
+ }
29
+ //#endregion
30
+ //#region ---- Public Methods ----
31
+ /**
32
+ * Convert AXPDynamicFormDefinition to AXPWidgetNode tree structure
33
+ * Groups become Fieldset Layouts with Form Field widgets as children
34
+ * Fields become Form Field widgets with Editor widgets as children
35
+ */
36
+ convertFormDefinition(formDefinition) {
37
+ // Create cache key based on form definition content
38
+ const cacheKey = this.createFormDefinitionCacheKey(formDefinition);
39
+ // Check cache first
40
+ if (this.widgetTreeCache.has(cacheKey)) {
41
+ return this.widgetTreeCache.get(cacheKey);
42
+ }
43
+ // Generate widget tree
44
+ const widgetTree = {
45
+ type: 'grid-layout',
46
+ name: 'dynamic-form-container',
47
+ mode: formDefinition.mode, // Preserve form-level mode
48
+ options: {
49
+ title: 'Dynamic Form',
50
+ grid: {
51
+ default: {
52
+ columns: 1,
53
+ gap: '1rem',
54
+ },
55
+ },
56
+ },
57
+ children: formDefinition.groups.map((group) => this.createGroupAsFieldsetWidget(group, formDefinition.mode)),
58
+ };
59
+ // Cache the result
60
+ this.widgetTreeCache.set(cacheKey, widgetTree);
61
+ return widgetTree;
62
+ }
63
+ /**
64
+ * Convert AXPWidgetNode tree back to AXPDynamicFormDefinition
65
+ * Parses Fieldset Layouts back to Groups
66
+ * Parses Form Field widgets back to Fields
67
+ */
68
+ convertWidgetTreeToFormDefinition(widgetTree) {
69
+ // Create cache key based on widget tree content
70
+ const cacheKey = this.createWidgetTreeCacheKey(widgetTree);
71
+ // Check cache first
72
+ if (this.formDefinitionCache.has(cacheKey)) {
73
+ return this.formDefinitionCache.get(cacheKey);
74
+ }
75
+ // Parse widget tree
76
+ const groups = [];
77
+ if (widgetTree.children) {
78
+ widgetTree.children.forEach((child) => {
79
+ if (child.type === 'fieldset-layout') {
80
+ const group = this.extractGroupFromFieldset(child);
81
+ groups.push(group);
82
+ }
83
+ });
84
+ }
85
+ const formDefinition = { groups };
86
+ // Cache the result
87
+ this.formDefinitionCache.set(cacheKey, formDefinition);
88
+ return formDefinition;
89
+ }
90
+ /**
91
+ * Validate that a widget tree represents a valid dynamic form structure
92
+ */
93
+ validateFormWidgetTree(widgetTree) {
94
+ if (!widgetTree || widgetTree.type !== 'grid-layout') {
95
+ return false;
96
+ }
97
+ if (!widgetTree.children || widgetTree.children.length === 0) {
98
+ return true; // Empty form is valid
99
+ }
100
+ // Check that all children are fieldset-layout widgets
101
+ return widgetTree.children.every((child) => child.type === 'fieldset-layout' &&
102
+ child.children &&
103
+ child.children.every((formField) => formField.type === 'form-field' && formField.children && formField.children.length > 0));
104
+ }
105
+ /**
106
+ * Clear all caches
107
+ */
108
+ clearCaches() {
109
+ this.widgetTreeCache.clear();
110
+ this.formDefinitionCache.clear();
111
+ }
112
+ /**
113
+ * Get cache statistics
114
+ */
115
+ getCacheStats() {
116
+ return {
117
+ widgetTreeCacheSize: this.widgetTreeCache.size,
118
+ formDefinitionCacheSize: this.formDefinitionCache.size,
119
+ };
120
+ }
121
+ //#endregion
122
+ //#region ---- Private Methods ----
123
+ /**
124
+ * Convert a single group to Fieldset widget structure
125
+ */
126
+ createGroupAsFieldsetWidget(group, formMode) {
127
+ // Determine columns count from layout or default to 1
128
+ const columnsCount = 1;
129
+ // Use group mode if set, otherwise inherit from form mode
130
+ const groupMode = group.mode || formMode;
131
+ return {
132
+ type: 'fieldset-layout',
133
+ name: group.name,
134
+ mode: groupMode,
135
+ options: {
136
+ title: group.title,
137
+ description: group.description,
138
+ cols: columnsCount,
139
+ look: group.look || 'container', // Default to 'container' if not specified
140
+ },
141
+ children: this.createFieldWidgets(group.parameters, columnsCount, groupMode),
142
+ };
143
+ }
144
+ /**
145
+ * Convert fields to Form Field widgets
146
+ */
147
+ createFieldWidgets(fields, columnsCount, groupMode) {
148
+ return fields.map((field) => this.createFormFieldWidget(field, groupMode));
149
+ }
150
+ /**
151
+ * Convert a single field to Form Field widget with editor as child
152
+ */
153
+ createFormFieldWidget(field, groupMode) {
154
+ // Use field mode if set, otherwise inherit from group mode
155
+ const fieldMode = field.mode || groupMode;
156
+ // Ensure the editor widget also has the mode set
157
+ const editorWidget = { ...field.widget };
158
+ if (!editorWidget.mode) {
159
+ editorWidget.mode = fieldMode;
160
+ }
161
+ return {
162
+ type: 'form-field',
163
+ name: field.path,
164
+ mode: fieldMode,
165
+ options: {
166
+ label: field.title,
167
+ badge: field.badge,
168
+ description: field.description,
169
+ showLabel: true,
170
+ },
171
+ children: [editorWidget], // The editor widget becomes a child of form-field
172
+ };
173
+ }
174
+ /**
175
+ * Extract group information from Fieldset Layout widget
176
+ */
177
+ extractGroupFromFieldset(fieldsetNode) {
178
+ const columnsCount = fieldsetNode.options?.['cols'] || 1;
179
+ // Extract fields directly from fieldset children
180
+ const fields = [];
181
+ if (fieldsetNode.children) {
182
+ fieldsetNode.children.forEach((formField) => {
183
+ if (formField.type === 'form-field' && formField.children && formField.children.length > 0) {
184
+ const field = this.extractFieldFromFormWidget(formField);
185
+ if (field) {
186
+ fields.push(field);
187
+ }
188
+ }
189
+ });
190
+ }
191
+ return {
192
+ name: fieldsetNode.name || `group-${Date.now()}`,
193
+ title: fieldsetNode.options?.['title'],
194
+ description: fieldsetNode.options?.['description'],
195
+ parameters: fields,
196
+ mode: fieldsetNode.mode,
197
+ look: fieldsetNode.options?.['look'],
198
+ };
199
+ }
200
+ /**
201
+ * Extract field information from Form Field widget
202
+ */
203
+ extractFieldFromFormWidget(formFieldNode) {
204
+ if (!formFieldNode.children || formFieldNode.children.length === 0) {
205
+ return null;
206
+ }
207
+ const editorWidget = formFieldNode.children[0];
208
+ return {
209
+ path: formFieldNode.name || editorWidget.name || `field-${Date.now()}`,
210
+ title: formFieldNode.options?.['label'],
211
+ badge: formFieldNode.options?.['badge'],
212
+ description: formFieldNode.options?.['description'],
213
+ widget: editorWidget,
214
+ mode: formFieldNode.mode,
215
+ };
216
+ }
217
+ /**
218
+ * Create cache key for form definition
219
+ */
220
+ createFormDefinitionCacheKey(formDefinition) {
221
+ // Create a hash-like key instead of full JSON string
222
+ const keyParts = [];
223
+ keyParts.push(`groups:${formDefinition.groups.length}`);
224
+ formDefinition.groups.forEach((group, groupIndex) => {
225
+ keyParts.push(`g${groupIndex}:${group.name}:${group.parameters.length}`);
226
+ group.parameters.forEach((param, paramIndex) => {
227
+ keyParts.push(`p${groupIndex}.${paramIndex}:${param.path}:${param.widget.type}`);
228
+ });
229
+ });
230
+ if (formDefinition.mode) {
231
+ keyParts.push(`mode:${formDefinition.mode}`);
232
+ }
233
+ // Join with delimiter and create a shorter hash
234
+ const keyString = keyParts.join('|');
235
+ // If still too long, create a simple hash
236
+ if (keyString.length > 100) {
237
+ return this.createSimpleHash(keyString);
238
+ }
239
+ return keyString;
240
+ }
241
+ /**
242
+ * Create cache key for widget tree
243
+ */
244
+ createWidgetTreeCacheKey(widgetTree) {
245
+ // Create a hash-like key instead of full JSON string
246
+ const keyParts = [];
247
+ keyParts.push(`type:${widgetTree.type}`);
248
+ if (widgetTree.name) {
249
+ keyParts.push(`name:${widgetTree.name}`);
250
+ }
251
+ if (widgetTree.children) {
252
+ keyParts.push(`children:${widgetTree.children.length}`);
253
+ widgetTree.children.forEach((child, index) => {
254
+ keyParts.push(`c${index}:${child.type}`);
255
+ if (child.children) {
256
+ keyParts.push(`cc${index}:${child.children.length}`);
257
+ child.children.forEach((grandChild, gIndex) => {
258
+ keyParts.push(`gc${index}.${gIndex}:${grandChild.type}`);
259
+ if (grandChild.children) {
260
+ keyParts.push(`gcc${index}.${gIndex}:${grandChild.children.length}`);
261
+ }
262
+ });
263
+ }
264
+ });
265
+ }
266
+ // Join with delimiter and create a shorter hash
267
+ const keyString = keyParts.join('|');
268
+ // If still too long, create a simple hash
269
+ if (keyString.length > 100) {
270
+ return this.createSimpleHash(keyString);
271
+ }
272
+ return keyString;
273
+ }
274
+ /**
275
+ * Create a simple hash from a string
276
+ */
277
+ createSimpleHash(str) {
278
+ let hash = 0;
279
+ if (str.length === 0)
280
+ return hash.toString();
281
+ for (let i = 0; i < str.length; i++) {
282
+ const char = str.charCodeAt(i);
283
+ hash = (hash << 5) - hash + char;
284
+ hash = hash & hash; // Convert to 32-bit integer
285
+ }
286
+ return Math.abs(hash).toString(36); // Convert to base36 for shorter string
287
+ }
288
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPLayoutConversionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
289
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPLayoutConversionService, providedIn: 'root' }); }
290
+ }
291
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPLayoutConversionService, decorators: [{
292
+ type: Injectable,
293
+ args: [{
294
+ providedIn: 'root',
295
+ }]
296
+ }] });
22
297
 
23
298
  //#region ---- Inheritance Utilities ----
24
299
  /**
@@ -74,6 +349,54 @@ function generateValueWidgetPath(widgetName, formFieldName, formFieldLabel) {
74
349
  }
75
350
  return generateRandomId();
76
351
  }
352
+ /**
353
+ * Collects default values from widget node tree and merges them into context
354
+ * Only sets values for paths that don't already exist in the context
355
+ * @param node - The widget node to process
356
+ * @param context - The context object to modify in place (cloned at top level only)
357
+ * @param isTopLevel - Internal flag to track if this is the top-level call
358
+ */
359
+ function collectDefaultValues(node, context = {}, isTopLevel = true) {
360
+ // Clone context only at the top level, then modify in place for recursive calls
361
+ const result = isTopLevel ? cloneDeep(context) : context;
362
+ // Check if this node has a defaultValue and a path
363
+ // Note: We check for both node.defaultValue and also look in node.options.defaultValue as fallback
364
+ const defaultValue = node.defaultValue !== undefined
365
+ ? node.defaultValue
366
+ : node.options?.defaultValue;
367
+ if (defaultValue !== undefined && !isNil(defaultValue) && node.path) {
368
+ // Check if path exists in context using lodash get equivalent check
369
+ const currentValue = getNestedValue(result, node.path);
370
+ if (currentValue === undefined) {
371
+ // Clone the defaultValue to avoid reference issues (especially for Date objects)
372
+ const clonedValue = defaultValue instanceof Date
373
+ ? new Date(defaultValue.getTime())
374
+ : cloneDeep(defaultValue);
375
+ set(result, node.path, clonedValue);
376
+ }
377
+ }
378
+ // Recursively process children - pass result object to accumulate values in place
379
+ if (node.children && Array.isArray(node.children)) {
380
+ for (const child of node.children) {
381
+ collectDefaultValues(child, result, false);
382
+ }
383
+ }
384
+ return result;
385
+ }
386
+ /**
387
+ * Gets a nested value from an object using dot notation path
388
+ */
389
+ function getNestedValue(obj, path) {
390
+ const keys = path.split('.');
391
+ let current = obj;
392
+ for (const key of keys) {
393
+ if (current === undefined || current === null || typeof current !== 'object') {
394
+ return undefined;
395
+ }
396
+ current = current[key];
397
+ }
398
+ return current;
399
+ }
77
400
  //#endregion
78
401
  //#region ---- Service Implementation ----
79
402
  class AXPLayoutBuilderService {
@@ -480,6 +803,26 @@ class ChildContainerMixin extends LayoutContainerMixin {
480
803
  this.containerState.children.push(field.build());
481
804
  return this;
482
805
  }
806
+ dynamicForm(definition) {
807
+ // Get conversion service using inject() - works in any context in Angular 14+
808
+ let conversionService;
809
+ try {
810
+ conversionService = inject(AXPLayoutConversionService);
811
+ }
812
+ catch {
813
+ // Fallback: create service instance if inject() fails (shouldn't happen in normal usage)
814
+ conversionService = new AXPLayoutConversionService();
815
+ }
816
+ // Convert form definition to widget node
817
+ const widgetNode = conversionService.convertFormDefinition(definition);
818
+ // Add the widget node's children to this container
819
+ // The converted widget node has a root grid-layout, we want its children (fieldsets)
820
+ if (widgetNode.children && widgetNode.children.length > 0) {
821
+ this.ensureChildren();
822
+ this.containerState.children.push(...widgetNode.children);
823
+ }
824
+ return this;
825
+ }
483
826
  }
484
827
  /**
485
828
  * Widget container mixin - Interface Segregation Principle
@@ -540,8 +883,56 @@ class WidgetContainerMixin extends ChildContainerMixin {
540
883
  this.containerState.children.push(container.build());
541
884
  return this;
542
885
  }
543
- customWidget(type, options) {
544
- this.addWidget(type, options);
886
+ customWidget(type, optionsOrDelegate, delegate) {
887
+ const child = new WidgetBuilder();
888
+ child.type(type);
889
+ // Determine if second parameter is delegate or options
890
+ const isDelegate = typeof optionsOrDelegate === 'function';
891
+ const options = isDelegate ? undefined : optionsOrDelegate;
892
+ const actualDelegate = isDelegate ? optionsOrDelegate : delegate;
893
+ // Apply inheritance context BEFORE setting options or calling delegate
894
+ child.withInheritanceContext(this.inheritanceContext);
895
+ // If options are provided (non-delegate overload), handle them
896
+ if (!isDelegate && options !== undefined) {
897
+ const widgetOptions = options;
898
+ const path = widgetOptions.path;
899
+ const name = widgetOptions.name;
900
+ // Set name and path in widget state, not options
901
+ if (name) {
902
+ child.name(name);
903
+ }
904
+ if (path) {
905
+ child.path(path);
906
+ }
907
+ // Remove name and path from options
908
+ const { name: _, path: __, ...cleanOptions } = widgetOptions;
909
+ child.options(cleanOptions);
910
+ }
911
+ // If delegate is provided, call it to allow setting path, options, and other control methods
912
+ if (actualDelegate) {
913
+ actualDelegate(child);
914
+ }
915
+ // Build the widget to check if path is set (for value widgets)
916
+ const builtWidget = child.build();
917
+ // Check if value widget requires path
918
+ const valueWidgetTypes = [
919
+ 'text-editor',
920
+ 'large-text-editor',
921
+ 'rich-text-editor',
922
+ 'password-editor',
923
+ 'number-editor',
924
+ 'select-editor',
925
+ 'lookup-editor',
926
+ 'selection-list-editor',
927
+ 'date-time-editor',
928
+ 'toggle-editor',
929
+ 'color-editor',
930
+ ];
931
+ if (valueWidgetTypes.includes(type) && !builtWidget.path) {
932
+ throw new Error(`Value widget '${type}' requires a 'path' property`);
933
+ }
934
+ this.ensureChildren();
935
+ this.containerState.children.push(builtWidget);
545
936
  return this;
546
937
  }
547
938
  }
@@ -725,6 +1116,14 @@ class FormFieldBuilder extends LayoutContainerMixin {
725
1116
  setShowLabel(showLabel) {
726
1117
  return this.setOptions({ showLabel });
727
1118
  }
1119
+ defaultValue(value) {
1120
+ this.containerState.defaultValue = value;
1121
+ this.inheritanceContext.defaultValue = value;
1122
+ if (this.childWidget) {
1123
+ this.childWidget.withInheritanceContext(this.inheritanceContext);
1124
+ }
1125
+ return this;
1126
+ }
728
1127
  // Single widget methods with automatic path generation
729
1128
  addSingleWidget(type, options) {
730
1129
  if (this.hasWidget) {
@@ -916,7 +1315,7 @@ class FieldsetContainerBuilder extends LayoutContainerMixin {
916
1315
  */
917
1316
  class ListWidgetBuilder extends WidgetContainerMixin {
918
1317
  constructor() {
919
- super('list');
1318
+ super('data-list');
920
1319
  }
921
1320
  setOptions(options) {
922
1321
  this.containerState.options = { ...this.containerState.options, ...options };
@@ -1099,11 +1498,14 @@ class DialogContainerBuilder {
1099
1498
  const dialogNode = this.build();
1100
1499
  // Import the dialog renderer component dynamically
1101
1500
  const { AXPDialogRendererComponent } = await Promise.resolve().then(function () { return dialogRenderer_component; });
1501
+ // Collect default values from widget tree and merge into initial context
1502
+ const initialContext = this.dialogState.dialogOptions?.context || {};
1503
+ const contextWithDefaults = collectDefaultValues(dialogNode, initialContext);
1102
1504
  // Create dialog configuration
1103
1505
  const dialogConfig = {
1104
1506
  title: this.dialogState.dialogOptions?.title || '',
1105
1507
  message: this.dialogState.dialogOptions?.message,
1106
- context: this.dialogState.dialogOptions?.context || {},
1508
+ context: contextWithDefaults,
1107
1509
  definition: dialogNode,
1108
1510
  actions: this.dialogState.actions,
1109
1511
  };
@@ -1159,8 +1561,18 @@ class WidgetBuilder {
1159
1561
  return this;
1160
1562
  }
1161
1563
  options(options) {
1162
- // Merge options instead of replacing to preserve inherited properties
1163
- this.widgetState.options = { ...this.widgetState.options, ...options };
1564
+ // Extract defaultValue from options - it's an exceptional parameter that updates context, not a widget option
1565
+ if (options && options.defaultValue !== undefined) {
1566
+ this.widgetState.defaultValue = options.defaultValue;
1567
+ // Remove defaultValue from options since it's stored on the node, not in options
1568
+ const { defaultValue: _, ...cleanOptions } = options;
1569
+ // Merge clean options instead of replacing to preserve inherited properties
1570
+ this.widgetState.options = { ...this.widgetState.options, ...cleanOptions };
1571
+ }
1572
+ else {
1573
+ // Merge options instead of replacing to preserve inherited properties
1574
+ this.widgetState.options = { ...this.widgetState.options, ...options };
1575
+ }
1164
1576
  return this;
1165
1577
  }
1166
1578
  layout(value) {
@@ -1201,6 +1613,10 @@ class WidgetBuilder {
1201
1613
  this.inheritanceContext.disabled = condition;
1202
1614
  return this;
1203
1615
  }
1616
+ defaultValue(defaultValue) {
1617
+ this.widgetState.defaultValue = defaultValue;
1618
+ return this;
1619
+ }
1204
1620
  readonly(condition) {
1205
1621
  if (!this.widgetState.options) {
1206
1622
  this.widgetState.options = {};
@@ -1241,6 +1657,9 @@ class WidgetBuilder {
1241
1657
  if (resolved.visible !== undefined) {
1242
1658
  this.widgetState.options['visible'] = resolved.visible;
1243
1659
  }
1660
+ if (context.defaultValue !== undefined) {
1661
+ this.widgetState.defaultValue = context.defaultValue;
1662
+ }
1244
1663
  return this;
1245
1664
  }
1246
1665
  getInheritanceContext() {
@@ -1308,412 +1727,133 @@ class StepBuilder {
1308
1727
  }
1309
1728
  setIcon(icon) {
1310
1729
  this.icon = icon;
1311
- return this;
1312
- }
1313
- setDescription(description) {
1314
- this.description = description;
1315
- return this;
1316
- }
1317
- setSkippable(skippable) {
1318
- this.skippable = skippable;
1319
- return this;
1320
- }
1321
- content(delegate) {
1322
- if (delegate) {
1323
- // Create LayoutBuilder in injection context (will be called from component)
1324
- const layoutBuilder = new LayoutBuilder();
1325
- // ✅ IMPORTANT: Propagate inheritance context to content
1326
- layoutBuilder.inheritanceContext = { ...this.inheritanceContext };
1327
- delegate(layoutBuilder);
1328
- this.contentNode = layoutBuilder.build();
1329
- }
1330
- return this;
1331
- }
1332
- withInheritanceContext(context) {
1333
- this.inheritanceContext = mergeInheritanceContext(context);
1334
- return this;
1335
- }
1336
- build() {
1337
- if (!this.contentNode) {
1338
- throw new Error(`Step '${this.id}' must have content`);
1339
- }
1340
- return {
1341
- id: this.id,
1342
- title: this.title,
1343
- icon: this.icon,
1344
- description: this.description,
1345
- skippable: this.skippable,
1346
- content: this.contentNode,
1347
- };
1348
- }
1349
- }
1350
- /**
1351
- * Step Wizard Builder - Builds step wizard widget
1352
- */
1353
- class StepWizardBuilder extends LayoutContainerMixin {
1354
- constructor() {
1355
- super('step-wizard');
1356
- this.steps = [];
1357
- this.linear = true;
1358
- this.wizardDirection = 'horizontal';
1359
- }
1360
- setLinear(linear) {
1361
- this.linear = linear;
1362
- return this;
1363
- }
1364
- setDirection(direction) {
1365
- this.wizardDirection = direction;
1366
- return this;
1367
- }
1368
- setLook(look) {
1369
- this.look = look;
1370
- return this;
1371
- }
1372
- setShowActions(show) {
1373
- this.showActions = show;
1374
- return this;
1375
- }
1376
- setActions(actions) {
1377
- this.wizardActions = actions;
1378
- return this;
1379
- }
1380
- setGuards(guards) {
1381
- this.guards = guards;
1382
- return this;
1383
- }
1384
- setEvents(events) {
1385
- this.events = events;
1386
- return this;
1387
- }
1388
- step(id, title, delegate) {
1389
- const stepBuilder = new StepBuilder(id, title);
1390
- // ✅ IMPORTANT: Propagate inheritance context to step
1391
- stepBuilder.withInheritanceContext(this.inheritanceContext);
1392
- if (delegate) {
1393
- delegate(stepBuilder);
1394
- }
1395
- this.steps.push(stepBuilder.build());
1396
- return this;
1397
- }
1398
- build() {
1399
- // ✅ Validation
1400
- if (this.steps.length === 0) {
1401
- throw new Error('StepWizard must have at least one step');
1402
- }
1403
- // ✅ Check duplicate IDs
1404
- const ids = this.steps.map((s) => s.id);
1405
- const duplicates = ids.filter((id, index) => ids.indexOf(id) !== index);
1406
- if (duplicates.length > 0) {
1407
- throw new Error(`Duplicate step IDs found: ${duplicates.join(', ')}`);
1408
- }
1409
- const definition = {
1410
- steps: this.steps,
1411
- linear: this.linear,
1412
- direction: this.wizardDirection,
1413
- look: this.look,
1414
- // Do not auto-detect based on dialog context; keep explicit or undefined
1415
- showActions: this.showActions,
1416
- actions: this.wizardActions,
1417
- guards: this.guards,
1418
- events: this.events,
1419
- };
1420
- const node = {
1421
- name: this.containerState.name,
1422
- type: 'step-wizard',
1423
- options: { definition },
1424
- mode: this.containerState.mode,
1425
- };
1426
- return node;
1427
- }
1428
- }
1429
-
1430
- class AXPLayoutConversionService {
1431
- constructor() {
1432
- //#region ---- Caching ----
1433
- this.widgetTreeCache = new Map();
1434
- this.formDefinitionCache = new Map();
1435
- }
1436
- //#endregion
1437
- //#region ---- Public Methods ----
1438
- /**
1439
- * Convert AXPDynamicFormDefinition to AXPWidgetNode tree structure
1440
- * Groups become Fieldset Layouts with Form Field widgets as children
1441
- * Fields become Form Field widgets with Editor widgets as children
1442
- */
1443
- convertFormDefinition(formDefinition) {
1444
- // Create cache key based on form definition content
1445
- const cacheKey = this.createFormDefinitionCacheKey(formDefinition);
1446
- // Check cache first
1447
- if (this.widgetTreeCache.has(cacheKey)) {
1448
- return this.widgetTreeCache.get(cacheKey);
1449
- }
1450
- // Generate widget tree
1451
- const widgetTree = {
1452
- type: 'grid-layout',
1453
- name: 'dynamic-form-container',
1454
- options: {
1455
- title: 'Dynamic Form',
1456
- grid: {
1457
- default: {
1458
- columns: 1,
1459
- gap: '1rem',
1460
- },
1461
- },
1462
- },
1463
- children: formDefinition.groups.map((group) => this.createGroupAsFieldsetWidget(group)),
1464
- };
1465
- // Cache the result
1466
- this.widgetTreeCache.set(cacheKey, widgetTree);
1467
- return widgetTree;
1468
- }
1469
- /**
1470
- * Convert AXPWidgetNode tree back to AXPDynamicFormDefinition
1471
- * Parses Fieldset Layouts back to Groups
1472
- * Parses Form Field widgets back to Fields
1473
- */
1474
- convertWidgetTreeToFormDefinition(widgetTree) {
1475
- // Create cache key based on widget tree content
1476
- const cacheKey = this.createWidgetTreeCacheKey(widgetTree);
1477
- // Check cache first
1478
- if (this.formDefinitionCache.has(cacheKey)) {
1479
- return this.formDefinitionCache.get(cacheKey);
1480
- }
1481
- // Parse widget tree
1482
- const groups = [];
1483
- if (widgetTree.children) {
1484
- widgetTree.children.forEach((child) => {
1485
- if (child.type === 'fieldset-layout') {
1486
- const group = this.extractGroupFromFieldset(child);
1487
- groups.push(group);
1488
- }
1489
- });
1490
- }
1491
- const formDefinition = { groups };
1492
- // Cache the result
1493
- this.formDefinitionCache.set(cacheKey, formDefinition);
1494
- return formDefinition;
1495
- }
1496
- /**
1497
- * Validate that a widget tree represents a valid dynamic form structure
1498
- */
1499
- validateFormWidgetTree(widgetTree) {
1500
- if (!widgetTree || widgetTree.type !== 'grid-layout') {
1501
- return false;
1502
- }
1503
- if (!widgetTree.children || widgetTree.children.length === 0) {
1504
- return true; // Empty form is valid
1505
- }
1506
- // Check that all children are fieldset-layout widgets
1507
- return widgetTree.children.every((child) => child.type === 'fieldset-layout' &&
1508
- child.children &&
1509
- child.children.every((formField) => formField.type === 'form-field' && formField.children && formField.children.length > 0));
1510
- }
1511
- /**
1512
- * Clear all caches
1513
- */
1514
- clearCaches() {
1515
- this.widgetTreeCache.clear();
1516
- this.formDefinitionCache.clear();
1517
- }
1518
- /**
1519
- * Get cache statistics
1520
- */
1521
- getCacheStats() {
1522
- return {
1523
- widgetTreeCacheSize: this.widgetTreeCache.size,
1524
- formDefinitionCacheSize: this.formDefinitionCache.size,
1525
- };
1526
- }
1527
- //#endregion
1528
- //#region ---- Private Methods ----
1529
- /**
1530
- * Convert a single group to Fieldset widget structure
1531
- */
1532
- createGroupAsFieldsetWidget(group) {
1533
- // Determine columns count from layout or default to 1
1534
- const columnsCount = 1;
1535
- return {
1536
- type: 'fieldset-layout',
1537
- name: group.name,
1538
- mode: group.mode,
1539
- options: {
1540
- title: group.title,
1541
- description: group.description,
1542
- cols: columnsCount,
1543
- look: group.look || 'container', // Default to 'container' if not specified
1544
- },
1545
- children: this.createFieldWidgets(group.parameters, columnsCount),
1546
- };
1547
- }
1548
- /**
1549
- * Convert fields to Form Field widgets
1550
- */
1551
- createFieldWidgets(fields, columnsCount) {
1552
- return fields.map((field) => this.createFormFieldWidget(field));
1553
- }
1554
- /**
1555
- * Convert a single field to Form Field widget with editor as child
1556
- */
1557
- createFormFieldWidget(field) {
1558
- return {
1559
- type: 'form-field',
1560
- name: field.path,
1561
- options: {
1562
- label: field.title,
1563
- description: field.description,
1564
- showLabel: true,
1565
- },
1566
- children: [field.widget], // The editor widget becomes a child of form-field
1567
- };
1730
+ return this;
1568
1731
  }
1569
- /**
1570
- * Extract group information from Fieldset Layout widget
1571
- */
1572
- extractGroupFromFieldset(fieldsetNode) {
1573
- const columnsCount = fieldsetNode.options?.['cols'] || 1;
1574
- // Extract fields directly from fieldset children
1575
- const fields = [];
1576
- if (fieldsetNode.children) {
1577
- fieldsetNode.children.forEach((formField) => {
1578
- if (formField.type === 'form-field' && formField.children && formField.children.length > 0) {
1579
- const field = this.extractFieldFromFormWidget(formField);
1580
- if (field) {
1581
- fields.push(field);
1582
- }
1583
- }
1584
- });
1732
+ setDescription(description) {
1733
+ this.description = description;
1734
+ return this;
1735
+ }
1736
+ setSkippable(skippable) {
1737
+ this.skippable = skippable;
1738
+ return this;
1739
+ }
1740
+ content(delegate) {
1741
+ if (delegate) {
1742
+ // Create LayoutBuilder in injection context (will be called from component)
1743
+ const layoutBuilder = new LayoutBuilder();
1744
+ // ✅ IMPORTANT: Propagate inheritance context to content
1745
+ layoutBuilder.inheritanceContext = { ...this.inheritanceContext };
1746
+ delegate(layoutBuilder);
1747
+ this.contentNode = layoutBuilder.build();
1585
1748
  }
1586
- return {
1587
- name: fieldsetNode.name || `group-${Date.now()}`,
1588
- title: fieldsetNode.options?.['title'],
1589
- description: fieldsetNode.options?.['description'],
1590
- parameters: fields,
1591
- mode: fieldsetNode.mode,
1592
- look: fieldsetNode.options?.['look'],
1593
- };
1749
+ return this;
1594
1750
  }
1595
- /**
1596
- * Extract field information from Form Field widget
1597
- */
1598
- extractFieldFromFormWidget(formFieldNode) {
1599
- if (!formFieldNode.children || formFieldNode.children.length === 0) {
1600
- return null;
1751
+ withInheritanceContext(context) {
1752
+ this.inheritanceContext = mergeInheritanceContext(context);
1753
+ return this;
1754
+ }
1755
+ build() {
1756
+ if (!this.contentNode) {
1757
+ throw new Error(`Step '${this.id}' must have content`);
1601
1758
  }
1602
- const editorWidget = formFieldNode.children[0];
1603
1759
  return {
1604
- path: formFieldNode.name || editorWidget.name || `field-${Date.now()}`,
1605
- title: formFieldNode.options?.['label'],
1606
- description: formFieldNode.options?.['description'],
1607
- widget: editorWidget,
1608
- mode: formFieldNode.mode,
1760
+ id: this.id,
1761
+ title: this.title,
1762
+ icon: this.icon,
1763
+ description: this.description,
1764
+ skippable: this.skippable,
1765
+ content: this.contentNode,
1609
1766
  };
1610
1767
  }
1611
- /**
1612
- * Create cache key for form definition
1613
- */
1614
- createFormDefinitionCacheKey(formDefinition) {
1615
- // Create a hash-like key instead of full JSON string
1616
- const keyParts = [];
1617
- keyParts.push(`groups:${formDefinition.groups.length}`);
1618
- formDefinition.groups.forEach((group, groupIndex) => {
1619
- keyParts.push(`g${groupIndex}:${group.name}:${group.parameters.length}`);
1620
- group.parameters.forEach((param, paramIndex) => {
1621
- keyParts.push(`p${groupIndex}.${paramIndex}:${param.path}:${param.widget.type}`);
1622
- });
1623
- });
1624
- if (formDefinition.mode) {
1625
- keyParts.push(`mode:${formDefinition.mode}`);
1626
- }
1627
- // Join with delimiter and create a shorter hash
1628
- const keyString = keyParts.join('|');
1629
- // If still too long, create a simple hash
1630
- if (keyString.length > 100) {
1631
- return this.createSimpleHash(keyString);
1632
- }
1633
- return keyString;
1768
+ }
1769
+ /**
1770
+ * Step Wizard Builder - Builds step wizard widget
1771
+ */
1772
+ class StepWizardBuilder extends LayoutContainerMixin {
1773
+ constructor() {
1774
+ super('step-wizard');
1775
+ this.steps = [];
1776
+ this.linear = true;
1777
+ this.wizardDirection = 'horizontal';
1634
1778
  }
1635
- /**
1636
- * Create cache key for widget tree
1637
- */
1638
- createWidgetTreeCacheKey(widgetTree) {
1639
- // Create a hash-like key instead of full JSON string
1640
- const keyParts = [];
1641
- keyParts.push(`type:${widgetTree.type}`);
1642
- if (widgetTree.name) {
1643
- keyParts.push(`name:${widgetTree.name}`);
1644
- }
1645
- if (widgetTree.children) {
1646
- keyParts.push(`children:${widgetTree.children.length}`);
1647
- widgetTree.children.forEach((child, index) => {
1648
- keyParts.push(`c${index}:${child.type}`);
1649
- if (child.children) {
1650
- keyParts.push(`cc${index}:${child.children.length}`);
1651
- child.children.forEach((grandChild, gIndex) => {
1652
- keyParts.push(`gc${index}.${gIndex}:${grandChild.type}`);
1653
- if (grandChild.children) {
1654
- keyParts.push(`gcc${index}.${gIndex}:${grandChild.children.length}`);
1655
- }
1656
- });
1657
- }
1658
- });
1659
- }
1660
- // Join with delimiter and create a shorter hash
1661
- const keyString = keyParts.join('|');
1662
- // If still too long, create a simple hash
1663
- if (keyString.length > 100) {
1664
- return this.createSimpleHash(keyString);
1779
+ setLinear(linear) {
1780
+ this.linear = linear;
1781
+ return this;
1782
+ }
1783
+ setDirection(direction) {
1784
+ this.wizardDirection = direction;
1785
+ return this;
1786
+ }
1787
+ setLook(look) {
1788
+ this.look = look;
1789
+ return this;
1790
+ }
1791
+ setShowActions(show) {
1792
+ this.showActions = show;
1793
+ return this;
1794
+ }
1795
+ setActions(actions) {
1796
+ this.wizardActions = actions;
1797
+ return this;
1798
+ }
1799
+ setGuards(guards) {
1800
+ this.guards = guards;
1801
+ return this;
1802
+ }
1803
+ setEvents(events) {
1804
+ this.events = events;
1805
+ return this;
1806
+ }
1807
+ step(id, title, delegate) {
1808
+ const stepBuilder = new StepBuilder(id, title);
1809
+ // ✅ IMPORTANT: Propagate inheritance context to step
1810
+ stepBuilder.withInheritanceContext(this.inheritanceContext);
1811
+ if (delegate) {
1812
+ delegate(stepBuilder);
1665
1813
  }
1666
- return keyString;
1814
+ this.steps.push(stepBuilder.build());
1815
+ return this;
1667
1816
  }
1668
- /**
1669
- * Create a simple hash from a string
1670
- */
1671
- createSimpleHash(str) {
1672
- let hash = 0;
1673
- if (str.length === 0)
1674
- return hash.toString();
1675
- for (let i = 0; i < str.length; i++) {
1676
- const char = str.charCodeAt(i);
1677
- hash = (hash << 5) - hash + char;
1678
- hash = hash & hash; // Convert to 32-bit integer
1817
+ build() {
1818
+ // Validation
1819
+ if (this.steps.length === 0) {
1820
+ throw new Error('StepWizard must have at least one step');
1679
1821
  }
1680
- return Math.abs(hash).toString(36); // Convert to base36 for shorter string
1822
+ // Check duplicate IDs
1823
+ const ids = this.steps.map((s) => s.id);
1824
+ const duplicates = ids.filter((id, index) => ids.indexOf(id) !== index);
1825
+ if (duplicates.length > 0) {
1826
+ throw new Error(`Duplicate step IDs found: ${duplicates.join(', ')}`);
1827
+ }
1828
+ const definition = {
1829
+ steps: this.steps,
1830
+ linear: this.linear,
1831
+ direction: this.wizardDirection,
1832
+ look: this.look,
1833
+ // Do not auto-detect based on dialog context; keep explicit or undefined
1834
+ showActions: this.showActions,
1835
+ actions: this.wizardActions,
1836
+ guards: this.guards,
1837
+ events: this.events,
1838
+ };
1839
+ const node = {
1840
+ name: this.containerState.name,
1841
+ type: 'step-wizard',
1842
+ options: { definition },
1843
+ mode: this.containerState.mode,
1844
+ };
1845
+ return node;
1681
1846
  }
1682
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPLayoutConversionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1683
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPLayoutConversionService, providedIn: 'root' }); }
1684
1847
  }
1685
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPLayoutConversionService, decorators: [{
1686
- type: Injectable,
1687
- args: [{
1688
- providedIn: 'root',
1689
- }]
1690
- }] });
1691
1848
 
1692
1849
  class AXPLayoutRendererComponent {
1693
1850
  constructor() {
1694
- this.evaluatorService = inject(AXPExpressionEvaluatorService);
1695
1851
  this.conversionService = inject(AXPLayoutConversionService);
1696
- /**
1697
- * Tracks the latest scheduled evaluation to ensure last-write-wins for async evaluate
1698
- */
1699
- this.evaluationRunId = 0;
1700
1852
  /**
1701
1853
  * RxJS subjects for context management
1702
1854
  */
1703
1855
  this.contextUpdateSubject = new Subject();
1704
1856
  this.contextChangeSubject = new Subject();
1705
- /**
1706
- * Cache for expression evaluation results
1707
- */
1708
- this.expressionCache = new Map();
1709
- /**
1710
- * Cache for widget tree comparisons
1711
- */
1712
- this.widgetTreeCache = new Map();
1713
- /**
1714
- * Last layout hash for change detection
1715
- */
1716
- this.lastLayoutHash = '';
1717
1857
  //#region ---- Inputs ----
1718
1858
  /**
1719
1859
  * Form definition containing groups and fields OR widget tree
@@ -1735,36 +1875,29 @@ class AXPLayoutRendererComponent {
1735
1875
  //#region ---- Widget Tree Conversion ----
1736
1876
  this.widgetTree = signal(null, ...(ngDevMode ? [{ debugName: "widgetTree" }] : []));
1737
1877
  /**
1738
- * Convert and evaluate data when inputs change (optimized with RxJS)
1878
+ * Convert layout data to widget tree when inputs change
1739
1879
  */
1740
1880
  this.conversionEffect = effect(() => {
1741
1881
  const inputData = this.layout();
1742
- const ctx = this.internalContext();
1743
- const look = this.look();
1744
- const runId = ++this.evaluationRunId;
1745
- // Generate layout hash for change detection
1746
- const layoutHash = this.generateLayoutHash(inputData, look);
1747
- // Skip if layout hasn't changed
1748
- if (layoutHash === this.lastLayoutHash && this.widgetTree()) {
1882
+ // Convert to widget tree
1883
+ let tree;
1884
+ if (this.isFormDefinition(inputData)) {
1885
+ // Convert form definition to widget tree
1886
+ tree = this.conversionService.convertFormDefinition(inputData);
1887
+ }
1888
+ else if (this.isWidgetNode(inputData)) {
1889
+ // Use widget tree directly
1890
+ tree = inputData;
1891
+ }
1892
+ else {
1893
+ console.warn('AXPLayoutRendererComponent: Invalid layout input. Expected AXPDynamicFormDefinition or AXPWidgetNode.', inputData);
1749
1894
  return;
1750
1895
  }
1751
- this.lastLayoutHash = layoutHash;
1752
- (async () => {
1753
- // First evaluate expressions if needed
1754
- const evaluated = await this.expressionEvaluator(inputData, ctx);
1755
- // Ignore stale results
1756
- if (runId !== this.evaluationRunId) {
1757
- return;
1758
- }
1759
- // Convert to widget tree (this will also apply the layout look)
1760
- const tree = evaluated;
1761
- // Update widget tree
1762
- const prev = this.widgetTree();
1763
- if (!isEqual(prev, tree)) {
1764
- tree.mode = this.mode();
1765
- this.widgetTree.set(tree);
1766
- }
1767
- })();
1896
+ // Update widget tree only if changed (Angular effect already prevents unnecessary runs)
1897
+ const prev = this.widgetTree();
1898
+ if (!isEqual(prev, tree)) {
1899
+ this.widgetTree.set(tree);
1900
+ }
1768
1901
  }, ...(ngDevMode ? [{ debugName: "conversionEffect" }] : []));
1769
1902
  //#endregion
1770
1903
  //#region ---- Outputs ----
@@ -1807,32 +1940,6 @@ class AXPLayoutRendererComponent {
1807
1940
  }
1808
1941
  }, ...(ngDevMode ? [{ debugName: "#widgetStatusEffect" }] : []));
1809
1942
  }
1810
- async expressionEvaluator(expression, context) {
1811
- // Check if it's a form definition that needs conversion
1812
- if (this.isFormDefinition(expression)) {
1813
- return this.conversionService.convertFormDefinition(expression);
1814
- }
1815
- // Generate cache key using a more efficient method
1816
- const cacheKey = this.generateCacheKey(expression, context);
1817
- // Check cache first
1818
- if (this.expressionCache.has(cacheKey)) {
1819
- return this.expressionCache.get(cacheKey);
1820
- }
1821
- const scope = {
1822
- context: {
1823
- eval: (path) => get(context, path),
1824
- },
1825
- };
1826
- const result = await this.evaluatorService.evaluate(expression, scope);
1827
- // Cache result with LRU-like behavior
1828
- if (this.expressionCache.size > 50) {
1829
- // Clear half the cache when it gets too large
1830
- const keysToDelete = Array.from(this.expressionCache.keys()).slice(0, 25);
1831
- keysToDelete.forEach((key) => this.expressionCache.delete(key));
1832
- }
1833
- this.expressionCache.set(cacheKey, result);
1834
- return result;
1835
- }
1836
1943
  //#endregion
1837
1944
  //#region ---- Lifecycle Methods ----
1838
1945
  ngOnInit() {
@@ -1965,55 +2072,11 @@ class AXPLayoutRendererComponent {
1965
2072
  isFormDefinition(data) {
1966
2073
  return data && typeof data === 'object' && 'groups' in data && Array.isArray(data.groups);
1967
2074
  }
1968
- //#endregion
1969
- //#region ---- Utility Methods ----
1970
- /**
1971
- * Generate layout hash for change detection (short hash)
1972
- */
1973
- generateLayoutHash(layout, look) {
1974
- if (!layout)
1975
- return '';
1976
- // Generate short hash for large layout strings
1977
- const layoutStr = typeof layout === 'string' ? layout : JSON.stringify(layout);
1978
- const layoutHash = this.simpleHash(layoutStr);
1979
- return `${layoutHash}|${look}`;
1980
- }
1981
- /**
1982
- * Generate cache key for expression evaluation (short hash)
1983
- */
1984
- generateCacheKey(expression, context) {
1985
- // Use short hash for better performance
1986
- const exprStr = typeof expression === 'string' ? expression : JSON.stringify(expression);
1987
- const exprHash = this.simpleHash(exprStr);
1988
- const ctxHash = this.generateContextHash(context);
1989
- return `${exprHash}|${ctxHash}`;
1990
- }
1991
- /**
1992
- * Generate a simple hash for context change detection
1993
- */
1994
- generateContextHash(context) {
1995
- if (!context || typeof context !== 'object') {
1996
- return String(context);
1997
- }
1998
- // Generate short hash for context
1999
- const keys = Object.keys(context).sort();
2000
- const contextStr = keys.map((key) => `${key}:${context[key]}`).join('|');
2001
- return this.simpleHash(contextStr);
2002
- }
2003
2075
  /**
2004
- * Simple hash function for generating short keys
2076
+ * Type guard to check if the input is a widget node
2005
2077
  */
2006
- simpleHash(str) {
2007
- let hash = 0;
2008
- if (str.length === 0)
2009
- return hash.toString();
2010
- for (let i = 0; i < str.length; i++) {
2011
- const char = str.charCodeAt(i);
2012
- hash = (hash << 5) - hash + char;
2013
- hash = hash & hash; // Convert to 32-bit integer
2014
- }
2015
- // Convert to positive hex string
2016
- return Math.abs(hash).toString(16);
2078
+ isWidgetNode(data) {
2079
+ return data && typeof data === 'object' && 'type' in data && typeof data.type === 'string';
2017
2080
  }
2018
2081
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPLayoutRendererComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2019
2082
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.12", 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: `
@@ -2024,7 +2087,7 @@ class AXPLayoutRendererComponent {
2024
2087
  }
2025
2088
  </axp-widgets-container>
2026
2089
  </ax-form>
2027
- `, isInline: true, styles: [":host{display:block;width:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { 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"], exportAs: ["widgetRenderer"] }, { kind: "ngmodule", type: AXFormModule }, { kind: "component", type: i2.AXFormComponent, selector: "ax-form", inputs: ["disabled", "readonly", "labelMode", "look", "messageStyle", "updateOn"], outputs: ["onValidate", "updateOnChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2090
+ `, isInline: true, styles: [":host{display:block;width:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { 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"], outputs: ["onValidate", "updateOnChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2028
2091
  }
2029
2092
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPLayoutRendererComponent, decorators: [{
2030
2093
  type: Component,