@acorex-platform/framework-client 0.0.10

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