@acorex/platform 21.0.0-next.5 → 21.0.0-next.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/acorex-platform-auth.mjs +281 -23
- package/fesm2022/acorex-platform-auth.mjs.map +1 -1
- package/fesm2022/acorex-platform-common-common-settings.provider-Bi1RYif5.mjs +163 -0
- package/fesm2022/acorex-platform-common-common-settings.provider-Bi1RYif5.mjs.map +1 -0
- package/fesm2022/acorex-platform-common.mjs +1047 -263
- package/fesm2022/acorex-platform-common.mjs.map +1 -1
- package/fesm2022/acorex-platform-core.mjs +1138 -510
- package/fesm2022/acorex-platform-core.mjs.map +1 -1
- package/fesm2022/acorex-platform-domain.mjs +557 -826
- package/fesm2022/acorex-platform-domain.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-builder.mjs +804 -186
- package/fesm2022/acorex-platform-layout-builder.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-components-binding-expression-editor-popup.component-CXEdvDTf.mjs +121 -0
- package/fesm2022/acorex-platform-layout-components-binding-expression-editor-popup.component-CXEdvDTf.mjs.map +1 -0
- package/fesm2022/acorex-platform-layout-components.mjs +6208 -2344
- package/fesm2022/acorex-platform-layout-components.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-designer.mjs +456 -204
- package/fesm2022/acorex-platform-layout-designer.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-entity.mjs +18632 -10286
- package/fesm2022/acorex-platform-layout-entity.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-views.mjs +538 -168
- package/fesm2022/acorex-platform-layout-views.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-widget-core.mjs +720 -456
- package/fesm2022/acorex-platform-layout-widget-core.mjs.map +1 -1
- package/fesm2022/{acorex-platform-layout-widgets-button-widget-designer.component-C3VoBb_b.mjs → acorex-platform-layout-widgets-button-widget-designer.component-Dy7jF-oD.mjs} +10 -10
- package/fesm2022/acorex-platform-layout-widgets-button-widget-designer.component-Dy7jF-oD.mjs.map +1 -0
- package/fesm2022/acorex-platform-layout-widgets-file-list-popup.component-CDYAGBku.mjs +103 -0
- package/fesm2022/acorex-platform-layout-widgets-file-list-popup.component-CDYAGBku.mjs.map +1 -0
- package/fesm2022/{acorex-platform-layout-widgets-image-preview.popup-V31OpYah.mjs → acorex-platform-layout-widgets-image-preview.popup-C_EPAvCU.mjs} +6 -7
- package/fesm2022/acorex-platform-layout-widgets-image-preview.popup-C_EPAvCU.mjs.map +1 -0
- package/fesm2022/{acorex-platform-layout-widgets-page-widget-designer.component-BtZMBxYp.mjs → acorex-platform-layout-widgets-page-widget-designer.component-D10yO28c.mjs} +12 -12
- package/fesm2022/acorex-platform-layout-widgets-page-widget-designer.component-D10yO28c.mjs.map +1 -0
- package/fesm2022/acorex-platform-layout-widgets-repeater-widget-column.component-BGO75IMz.mjs +116 -0
- package/fesm2022/acorex-platform-layout-widgets-repeater-widget-column.component-BGO75IMz.mjs.map +1 -0
- package/fesm2022/{acorex-platform-layout-widgets-tabular-data-edit-popup.component-Ck7-wpT2.mjs → acorex-platform-layout-widgets-tabular-data-edit-popup.component-DmzNTYiS.mjs} +6 -6
- package/fesm2022/acorex-platform-layout-widgets-tabular-data-edit-popup.component-DmzNTYiS.mjs.map +1 -0
- package/fesm2022/{acorex-platform-layout-widgets-tabular-data-view-popup.component-y8vjUiVs.mjs → acorex-platform-layout-widgets-tabular-data-view-popup.component-BNG_588B.mjs} +5 -5
- package/fesm2022/acorex-platform-layout-widgets-tabular-data-view-popup.component-BNG_588B.mjs.map +1 -0
- package/fesm2022/{acorex-platform-layout-widgets-text-block-widget-designer.component-Df1BFkSa.mjs → acorex-platform-layout-widgets-text-block-widget-designer.component-Vo4fWHtX.mjs} +6 -6
- package/fesm2022/acorex-platform-layout-widgets-text-block-widget-designer.component-Vo4fWHtX.mjs.map +1 -0
- package/fesm2022/acorex-platform-layout-widgets.mjs +8728 -4269
- package/fesm2022/acorex-platform-layout-widgets.mjs.map +1 -1
- package/fesm2022/acorex-platform-native.mjs +8 -7
- package/fesm2022/acorex-platform-native.mjs.map +1 -1
- package/fesm2022/acorex-platform-runtime.mjs +391 -166
- package/fesm2022/acorex-platform-runtime.mjs.map +1 -1
- package/fesm2022/acorex-platform-themes-default-entity-master-create-view.component-Cx1lLUaR.mjs +160 -0
- package/fesm2022/acorex-platform-themes-default-entity-master-create-view.component-Cx1lLUaR.mjs.map +1 -0
- package/fesm2022/acorex-platform-themes-default-entity-master-modify-view.component-AOrcgjDF.mjs +120 -0
- package/fesm2022/acorex-platform-themes-default-entity-master-modify-view.component-AOrcgjDF.mjs.map +1 -0
- package/fesm2022/{acorex-platform-themes-default-entity-master-single-view.component-eMBby9k4.mjs → acorex-platform-themes-default-entity-master-single-view.component-BfCeUU5F.mjs} +19 -26
- package/fesm2022/acorex-platform-themes-default-entity-master-single-view.component-BfCeUU5F.mjs.map +1 -0
- package/fesm2022/{acorex-platform-themes-default-error-401.component-cfREo88K.mjs → acorex-platform-themes-default-error-401.component-C7EYJzSr.mjs} +4 -4
- package/fesm2022/acorex-platform-themes-default-error-401.component-C7EYJzSr.mjs.map +1 -0
- package/fesm2022/{acorex-platform-themes-default-error-404.component-CdCV5ZoA.mjs → acorex-platform-themes-default-error-404.component-7MVLMwIa.mjs} +4 -4
- package/fesm2022/acorex-platform-themes-default-error-404.component-7MVLMwIa.mjs.map +1 -0
- package/fesm2022/acorex-platform-themes-default-error-offline.component-DR6G8gPC.mjs +19 -0
- package/fesm2022/acorex-platform-themes-default-error-offline.component-DR6G8gPC.mjs.map +1 -0
- package/fesm2022/acorex-platform-themes-default.mjs +1836 -67
- package/fesm2022/acorex-platform-themes-default.mjs.map +1 -1
- package/fesm2022/{acorex-platform-themes-shared-icon-chooser-column.component-C0EpfU2k.mjs → acorex-platform-themes-shared-icon-chooser-column.component-CqkWJYdv.mjs} +6 -6
- package/fesm2022/acorex-platform-themes-shared-icon-chooser-column.component-CqkWJYdv.mjs.map +1 -0
- package/fesm2022/{acorex-platform-themes-shared-icon-chooser-view.component-9W52W6Nu.mjs → acorex-platform-themes-shared-icon-chooser-view.component-BOTuLdWN.mjs} +6 -6
- package/fesm2022/acorex-platform-themes-shared-icon-chooser-view.component-BOTuLdWN.mjs.map +1 -0
- package/fesm2022/{acorex-platform-themes-shared-settings.provider-DSs1o1M6.mjs → acorex-platform-themes-shared-settings.provider-DK6R87Lf.mjs} +24 -25
- package/fesm2022/acorex-platform-themes-shared-settings.provider-DK6R87Lf.mjs.map +1 -0
- package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-column.component-D566Kdvy.mjs +94 -0
- package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-column.component-D566Kdvy.mjs.map +1 -0
- package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-view.component-D7-rCGl7.mjs +86 -0
- package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-view.component-D7-rCGl7.mjs.map +1 -0
- package/fesm2022/acorex-platform-themes-shared.mjs +674 -573
- package/fesm2022/acorex-platform-themes-shared.mjs.map +1 -1
- package/fesm2022/acorex-platform-workflow.mjs +1715 -535
- package/fesm2022/acorex-platform-workflow.mjs.map +1 -1
- package/fesm2022/acorex-platform.mjs.map +1 -1
- package/package.json +37 -37
- package/{auth/index.d.ts → types/acorex-platform-auth.d.ts} +241 -4
- package/{common/index.d.ts → types/acorex-platform-common.d.ts} +598 -80
- package/{core/index.d.ts → types/acorex-platform-core.d.ts} +595 -132
- package/{domain/index.d.ts → types/acorex-platform-domain.d.ts} +744 -412
- package/{layout/builder/index.d.ts → types/acorex-platform-layout-builder.d.ts} +193 -48
- package/types/acorex-platform-layout-components.d.ts +2979 -0
- package/{layout/designer/index.d.ts → types/acorex-platform-layout-designer.d.ts} +96 -18
- package/{layout/entity/index.d.ts → types/acorex-platform-layout-entity.d.ts} +1601 -261
- package/{layout/views/index.d.ts → types/acorex-platform-layout-views.d.ts} +116 -55
- package/{layout/widget-core/index.d.ts → types/acorex-platform-layout-widget-core.d.ts} +272 -124
- package/{layout/widgets/index.d.ts → types/acorex-platform-layout-widgets.d.ts} +1055 -157
- package/{native/index.d.ts → types/acorex-platform-native.d.ts} +0 -7
- package/types/acorex-platform-runtime.d.ts +571 -0
- package/{themes/default/index.d.ts → types/acorex-platform-themes-default.d.ts} +122 -5
- package/{themes/shared/index.d.ts → types/acorex-platform-themes-shared.d.ts} +1 -1
- package/types/acorex-platform-workflow.d.ts +1884 -0
- package/fesm2022/acorex-platform-common-common-settings.provider-zhqNP3xb.mjs +0 -71
- package/fesm2022/acorex-platform-common-common-settings.provider-zhqNP3xb.mjs.map +0 -1
- package/fesm2022/acorex-platform-layout-widgets-button-widget-designer.component-C3VoBb_b.mjs.map +0 -1
- package/fesm2022/acorex-platform-layout-widgets-file-list-popup.component-CxrsI6Hn.mjs +0 -135
- package/fesm2022/acorex-platform-layout-widgets-file-list-popup.component-CxrsI6Hn.mjs.map +0 -1
- package/fesm2022/acorex-platform-layout-widgets-image-preview.popup-V31OpYah.mjs.map +0 -1
- package/fesm2022/acorex-platform-layout-widgets-page-widget-designer.component-BtZMBxYp.mjs.map +0 -1
- package/fesm2022/acorex-platform-layout-widgets-tabular-data-edit-popup.component-Ck7-wpT2.mjs.map +0 -1
- package/fesm2022/acorex-platform-layout-widgets-tabular-data-view-popup.component-y8vjUiVs.mjs.map +0 -1
- package/fesm2022/acorex-platform-layout-widgets-text-block-widget-designer.component-Df1BFkSa.mjs.map +0 -1
- package/fesm2022/acorex-platform-themes-default-entity-master-create-view.component-VIGuU5M4.mjs +0 -157
- package/fesm2022/acorex-platform-themes-default-entity-master-create-view.component-VIGuU5M4.mjs.map +0 -1
- package/fesm2022/acorex-platform-themes-default-entity-master-list-view.component-DyDa_hyd.mjs +0 -1542
- package/fesm2022/acorex-platform-themes-default-entity-master-list-view.component-DyDa_hyd.mjs.map +0 -1
- package/fesm2022/acorex-platform-themes-default-entity-master-modify-view.component-Ua3ZA5hk.mjs +0 -101
- package/fesm2022/acorex-platform-themes-default-entity-master-modify-view.component-Ua3ZA5hk.mjs.map +0 -1
- package/fesm2022/acorex-platform-themes-default-entity-master-single-view.component-eMBby9k4.mjs.map +0 -1
- package/fesm2022/acorex-platform-themes-default-error-401.component-cfREo88K.mjs.map +0 -1
- package/fesm2022/acorex-platform-themes-default-error-404.component-CdCV5ZoA.mjs.map +0 -1
- package/fesm2022/acorex-platform-themes-default-error-offline.component-E7SzBcAt.mjs +0 -19
- package/fesm2022/acorex-platform-themes-default-error-offline.component-E7SzBcAt.mjs.map +0 -1
- package/fesm2022/acorex-platform-themes-shared-icon-chooser-column.component-C0EpfU2k.mjs.map +0 -1
- package/fesm2022/acorex-platform-themes-shared-icon-chooser-view.component-9W52W6Nu.mjs.map +0 -1
- package/fesm2022/acorex-platform-themes-shared-settings.provider-DSs1o1M6.mjs.map +0 -1
- package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-column.component-DTnfRy5f.mjs +0 -65
- package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-column.component-DTnfRy5f.mjs.map +0 -1
- package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-view.component-DY0JtT1v.mjs +0 -64
- package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-view.component-DY0JtT1v.mjs.map +0 -1
- package/layout/components/index.d.ts +0 -1669
- package/runtime/index.d.ts +0 -307
- package/workflow/index.d.ts +0 -1808
- /package/{index.d.ts → types/acorex-platform.d.ts} +0 -0
|
@@ -1,13 +1,16 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as i5 from '@angular/common';
|
|
2
2
|
import { CommonModule } from '@angular/common';
|
|
3
3
|
import * as i0 from '@angular/core';
|
|
4
|
-
import { Injectable, inject, input, model, signal, effect, output, viewChild, ChangeDetectionStrategy, Component, NgModule, EventEmitter, Output
|
|
4
|
+
import { Injectable, inject, input, model, signal, computed, effect, output, viewChild, ChangeDetectionStrategy, Component, NgModule, EventEmitter, Output } from '@angular/core';
|
|
5
|
+
import { provideCommandSetups, AXPCommandService } from '@acorex/platform/runtime';
|
|
5
6
|
import { AXPopupService } from '@acorex/components/popup';
|
|
6
|
-
import
|
|
7
|
+
import * as i4 from '@acorex/platform/core';
|
|
8
|
+
import { AXPHookService, AXPExpressionEvaluatorService, AXPComponentSlotModule, AXPContextStore } from '@acorex/platform/core';
|
|
9
|
+
import * as i1 from '@acorex/platform/layout/widget-core';
|
|
10
|
+
import { AXPWidgetSerializationHelper, AXPWidgetContainerComponent, AXPPageStatus, AXPWidgetCoreModule, AXPWidgetRegistryService } from '@acorex/platform/layout/widget-core';
|
|
11
|
+
import { cloneDeep, isNil, set, isEqual, merge } from 'lodash-es';
|
|
7
12
|
import * as i2 from '@acorex/components/form';
|
|
8
13
|
import { AXFormComponent, AXFormModule } from '@acorex/components/form';
|
|
9
|
-
import * as i1 from '@acorex/platform/layout/widget-core';
|
|
10
|
-
import { AXPWidgetContainerComponent, AXPPageStatus, AXPWidgetCoreModule } from '@acorex/platform/layout/widget-core';
|
|
11
14
|
import { Subject, debounceTime, distinctUntilChanged, startWith } from 'rxjs';
|
|
12
15
|
import * as i1$1 from '@acorex/components/button';
|
|
13
16
|
import { AXButtonModule } from '@acorex/components/button';
|
|
@@ -16,9 +19,36 @@ import { AXDecoratorModule } from '@acorex/components/decorators';
|
|
|
16
19
|
import * as i3 from '@acorex/components/loading';
|
|
17
20
|
import { AXLoadingModule } from '@acorex/components/loading';
|
|
18
21
|
import { AXBasePageComponent } from '@acorex/components/page';
|
|
19
|
-
import * as
|
|
20
|
-
import { AXTranslationModule } from '@acorex/core/translation';
|
|
21
|
-
import {
|
|
22
|
+
import * as i6 from '@acorex/core/translation';
|
|
23
|
+
import { AXTranslationModule, AXTranslationService } from '@acorex/core/translation';
|
|
24
|
+
import { AXP_ENTITY_DEFINITION_CRUD_SERVICE } from '@acorex/platform/domain';
|
|
25
|
+
|
|
26
|
+
//#region ---- Imports ----
|
|
27
|
+
//#endregion
|
|
28
|
+
//#region ---- Before open ----
|
|
29
|
+
/**
|
|
30
|
+
* Runs after dialog options and context are prepared and **before** footer customization and popup open.
|
|
31
|
+
* Listeners may mutate {@link AXPLayoutBuilderDialogBeforeOpenPayload.context} by reference.
|
|
32
|
+
*/
|
|
33
|
+
const AXP_LAYOUT_BUILDER_DIALOG_BEFORE_OPEN_HOOK_KEY = 'layout-builder.dialog.before-open';
|
|
34
|
+
//#endregion
|
|
35
|
+
//#region ---- Footer actions ----
|
|
36
|
+
/**
|
|
37
|
+
* Runs after builder-defined footer actions exist and **before** the dialog opens.
|
|
38
|
+
* Listeners receive the live `actions.footer.prefix` / `suffix` arrays (same references as the dialog)
|
|
39
|
+
* so they may push, splice, filter, or replace items. They may also mutate {@link AXPLayoutBuilderDialogFooterPayload.context} by reference.
|
|
40
|
+
*/
|
|
41
|
+
const AXP_LAYOUT_BUILDER_DIALOG_CONFIG_HOOK_KEY = 'layout-builder.dialog.config';
|
|
42
|
+
//#endregion
|
|
43
|
+
//#region ---- Context updates (after open) ----
|
|
44
|
+
/**
|
|
45
|
+
* Runs whenever the dialog layout builder context changes (debounced upstream), **after** the popup is visible.
|
|
46
|
+
* Use for side effects that depend on live context (for example values updated by widgets after render).
|
|
47
|
+
* Payload mirrors `AXPDialogRendererComponent` semantics: {@link AXPLayoutBuilderDialogContextChangedPayload.getContext},
|
|
48
|
+
* {@link AXPLayoutBuilderDialogContextChangedPayload.patchContext}, and optional loading state.
|
|
49
|
+
*/
|
|
50
|
+
const AXP_LAYOUT_BUILDER_DIALOG_CONTEXT_CHANGED_HOOK_KEY = 'layout-builder.dialog.context-changed';
|
|
51
|
+
//#endregion
|
|
22
52
|
|
|
23
53
|
class AXPLayoutConversionService {
|
|
24
54
|
constructor() {
|
|
@@ -158,6 +188,10 @@ class AXPLayoutConversionService {
|
|
|
158
188
|
if (!editorWidget.mode) {
|
|
159
189
|
editorWidget.mode = fieldMode;
|
|
160
190
|
}
|
|
191
|
+
const hintOpts = field.description != null &&
|
|
192
|
+
(typeof field.description !== 'string' || field.description.trim().length > 0)
|
|
193
|
+
? { hint: field.description, hintDisplayMode: 'note' }
|
|
194
|
+
: {};
|
|
161
195
|
return {
|
|
162
196
|
type: 'form-field',
|
|
163
197
|
name: field.path,
|
|
@@ -165,8 +199,8 @@ class AXPLayoutConversionService {
|
|
|
165
199
|
options: {
|
|
166
200
|
label: field.title,
|
|
167
201
|
badge: field.badge,
|
|
168
|
-
description: field.description,
|
|
169
202
|
showLabel: true,
|
|
203
|
+
...hintOpts,
|
|
170
204
|
},
|
|
171
205
|
children: [editorWidget], // The editor widget becomes a child of form-field
|
|
172
206
|
};
|
|
@@ -209,7 +243,7 @@ class AXPLayoutConversionService {
|
|
|
209
243
|
path: formFieldNode.name || editorWidget.name || `field-${Date.now()}`,
|
|
210
244
|
title: formFieldNode.options?.['label'],
|
|
211
245
|
badge: formFieldNode.options?.['badge'],
|
|
212
|
-
description: formFieldNode.options?.['
|
|
246
|
+
description: formFieldNode.options?.['hint'],
|
|
213
247
|
widget: editorWidget,
|
|
214
248
|
mode: formFieldNode.mode,
|
|
215
249
|
};
|
|
@@ -222,9 +256,16 @@ class AXPLayoutConversionService {
|
|
|
222
256
|
const keyParts = [];
|
|
223
257
|
keyParts.push(`groups:${formDefinition.groups.length}`);
|
|
224
258
|
formDefinition.groups.forEach((group, groupIndex) => {
|
|
225
|
-
|
|
259
|
+
// Include group.mode so view vs edit (or mixed) layouts do not share a cached widget tree.
|
|
260
|
+
const groupModePart = group.mode ?? '_';
|
|
261
|
+
keyParts.push(`g${groupIndex}:${group.name}:${group.parameters.length}:${groupModePart}`);
|
|
262
|
+
keyParts.push(`gL${groupIndex}:${JSON.stringify(group.title ?? null)}:${JSON.stringify(group.description ?? null)}`);
|
|
226
263
|
group.parameters.forEach((param, paramIndex) => {
|
|
227
|
-
|
|
264
|
+
// Field mode must be part of the key; otherwise metadata forms that only differ by
|
|
265
|
+
// view/edit (same paths and widget types) incorrectly reuse the first cached tree.
|
|
266
|
+
const fieldModePart = param.mode ?? '_';
|
|
267
|
+
keyParts.push(`p${groupIndex}.${paramIndex}:${param.path}:${param.widget.type}:${fieldModePart}`);
|
|
268
|
+
keyParts.push(`pL${groupIndex}.${paramIndex}:${JSON.stringify(param.title ?? null)}:${JSON.stringify(param.description ?? null)}:${JSON.stringify(param.badge ?? null)}`);
|
|
228
269
|
});
|
|
229
270
|
});
|
|
230
271
|
if (formDefinition.mode) {
|
|
@@ -285,10 +326,10 @@ class AXPLayoutConversionService {
|
|
|
285
326
|
}
|
|
286
327
|
return Math.abs(hash).toString(36); // Convert to base36 for shorter string
|
|
287
328
|
}
|
|
288
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
289
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
329
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLayoutConversionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
330
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLayoutConversionService, providedIn: 'root' }); }
|
|
290
331
|
}
|
|
291
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
332
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLayoutConversionService, decorators: [{
|
|
292
333
|
type: Injectable,
|
|
293
334
|
args: [{
|
|
294
335
|
providedIn: 'root',
|
|
@@ -361,17 +402,13 @@ function collectDefaultValues(node, context = {}, isTopLevel = true) {
|
|
|
361
402
|
const result = isTopLevel ? cloneDeep(context) : context;
|
|
362
403
|
// Check if this node has a defaultValue and a path
|
|
363
404
|
// Note: We check for both node.defaultValue and also look in node.options.defaultValue as fallback
|
|
364
|
-
const defaultValue = node.defaultValue !== undefined
|
|
365
|
-
? node.defaultValue
|
|
366
|
-
: node.options?.defaultValue;
|
|
405
|
+
const defaultValue = node.defaultValue !== undefined ? node.defaultValue : node.options?.defaultValue;
|
|
367
406
|
if (defaultValue !== undefined && !isNil(defaultValue) && node.path) {
|
|
368
407
|
// Check if path exists in context using lodash get equivalent check
|
|
369
408
|
const currentValue = getNestedValue(result, node.path);
|
|
370
409
|
if (currentValue === undefined) {
|
|
371
410
|
// Clone the defaultValue to avoid reference issues (especially for Date objects)
|
|
372
|
-
const clonedValue = defaultValue instanceof Date
|
|
373
|
-
? new Date(defaultValue.getTime())
|
|
374
|
-
: cloneDeep(defaultValue);
|
|
411
|
+
const clonedValue = defaultValue instanceof Date ? new Date(defaultValue.getTime()) : cloneDeep(defaultValue);
|
|
375
412
|
set(result, node.path, clonedValue);
|
|
376
413
|
}
|
|
377
414
|
}
|
|
@@ -402,17 +439,18 @@ function getNestedValue(obj, path) {
|
|
|
402
439
|
class AXPLayoutBuilderService {
|
|
403
440
|
constructor() {
|
|
404
441
|
this.popupService = inject(AXPopupService);
|
|
442
|
+
this.hookService = inject(AXPHookService, { optional: true }) ?? undefined;
|
|
405
443
|
}
|
|
406
444
|
/**
|
|
407
445
|
* Create a new layout builder
|
|
408
446
|
*/
|
|
409
447
|
create() {
|
|
410
|
-
return new LayoutBuilder(this.popupService);
|
|
448
|
+
return new LayoutBuilder(this.popupService, this.hookService);
|
|
411
449
|
}
|
|
412
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
413
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
450
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLayoutBuilderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
451
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLayoutBuilderService, providedIn: 'root' }); }
|
|
414
452
|
}
|
|
415
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
453
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLayoutBuilderService, decorators: [{
|
|
416
454
|
type: Injectable,
|
|
417
455
|
args: [{
|
|
418
456
|
providedIn: 'root',
|
|
@@ -425,8 +463,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImpo
|
|
|
425
463
|
* Open/Closed: Extensible through container delegates
|
|
426
464
|
*/
|
|
427
465
|
class LayoutBuilder {
|
|
428
|
-
constructor(popupService) {
|
|
466
|
+
constructor(popupService, hookService) {
|
|
429
467
|
this.popupService = popupService;
|
|
468
|
+
this.hookService = hookService;
|
|
430
469
|
this.root = {
|
|
431
470
|
children: [],
|
|
432
471
|
mode: 'edit',
|
|
@@ -496,7 +535,7 @@ class LayoutBuilder {
|
|
|
496
535
|
if (!this.popupService) {
|
|
497
536
|
throw new Error('LayoutBuilder requires AXPopupService to create dialogs. Please inject it in the service constructor.');
|
|
498
537
|
}
|
|
499
|
-
const container = new DialogContainerBuilder(this.popupService);
|
|
538
|
+
const container = new DialogContainerBuilder(this.popupService, this.hookService);
|
|
500
539
|
if (delegate) {
|
|
501
540
|
delegate(container);
|
|
502
541
|
}
|
|
@@ -521,11 +560,30 @@ class LayoutBuilder {
|
|
|
521
560
|
return this;
|
|
522
561
|
}
|
|
523
562
|
build() {
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
mode:
|
|
563
|
+
const r = this.root;
|
|
564
|
+
const node = {
|
|
565
|
+
type: r.type,
|
|
566
|
+
...(r.mode !== undefined ? { mode: r.mode } : {}),
|
|
567
|
+
...(r.children !== undefined ? { children: r.children } : {}),
|
|
568
|
+
...(r.options !== undefined ? { options: r.options } : {}),
|
|
569
|
+
...(r.name !== undefined ? { name: r.name } : {}),
|
|
570
|
+
...(r.path !== undefined ? { path: r.path } : {}),
|
|
571
|
+
...(r.visible !== undefined ? { visible: r.visible } : {}),
|
|
572
|
+
...(r.defaultValue !== undefined ? { defaultValue: r.defaultValue } : {}),
|
|
573
|
+
...(r.triggers !== undefined ? { triggers: r.triggers } : {}),
|
|
574
|
+
...(r.meta !== undefined ? { meta: r.meta } : {}),
|
|
575
|
+
...(r.valueTransforms !== undefined ? { valueTransforms: r.valueTransforms } : {}),
|
|
528
576
|
};
|
|
577
|
+
return node;
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* Converts the built widget node to JSON string
|
|
581
|
+
* @param options - Serialization options
|
|
582
|
+
* @returns JSON string representation of the widget node
|
|
583
|
+
*/
|
|
584
|
+
toJson(options) {
|
|
585
|
+
const node = this.build();
|
|
586
|
+
return AXPWidgetSerializationHelper.toJson(node, options);
|
|
529
587
|
}
|
|
530
588
|
}
|
|
531
589
|
//#endregion
|
|
@@ -585,6 +643,7 @@ class BaseContainerBuilder {
|
|
|
585
643
|
'number-editor',
|
|
586
644
|
'select-editor',
|
|
587
645
|
'lookup-editor',
|
|
646
|
+
'entity-definition-provider-editor',
|
|
588
647
|
'selection-list-editor',
|
|
589
648
|
'date-time-editor',
|
|
590
649
|
'toggle-editor',
|
|
@@ -687,24 +746,30 @@ class BaseContainerMixin extends BaseContainerBuilder {
|
|
|
687
746
|
class LayoutContainerMixin extends BaseContainerMixin {
|
|
688
747
|
layout(value) {
|
|
689
748
|
// Map layout intent to grid item sizing so containers like `form-field`
|
|
690
|
-
// can span multiple columns inside grid/fieldset layouts.
|
|
749
|
+
// can span multiple columns/rows inside grid/fieldset layouts.
|
|
691
750
|
if (!this.containerState.options)
|
|
692
751
|
this.containerState.options = {};
|
|
693
752
|
if (typeof value === 'number') {
|
|
694
|
-
// Direct numeric shorthand → colSpan
|
|
695
753
|
this.containerState.options.colSpan = value;
|
|
696
754
|
}
|
|
697
755
|
else if (value) {
|
|
698
|
-
// Try to extract a reasonable colSpan from breakpoint positions
|
|
699
756
|
const positions = value.positions;
|
|
700
757
|
if (positions) {
|
|
701
|
-
const
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
758
|
+
const placement = positions?.lg ?? positions?.xl ?? positions?.xxl ?? positions?.md ?? positions?.sm;
|
|
759
|
+
if (placement) {
|
|
760
|
+
const opts = this.containerState.options;
|
|
761
|
+
if (placement.colSpan != null)
|
|
762
|
+
opts.colSpan = placement.colSpan;
|
|
763
|
+
if (placement.colStart != null)
|
|
764
|
+
opts.colStart = placement.colStart;
|
|
765
|
+
if (placement.colEnd != null)
|
|
766
|
+
opts.colEnd = placement.colEnd;
|
|
767
|
+
if (placement.rowSpan != null)
|
|
768
|
+
opts.rowSpan = placement.rowSpan;
|
|
769
|
+
if (placement.rowStart != null)
|
|
770
|
+
opts.rowStart = placement.rowStart;
|
|
771
|
+
if (placement.rowEnd != null)
|
|
772
|
+
opts.rowEnd = placement.rowEnd;
|
|
708
773
|
}
|
|
709
774
|
}
|
|
710
775
|
}
|
|
@@ -923,6 +988,7 @@ class WidgetContainerMixin extends ChildContainerMixin {
|
|
|
923
988
|
'number-editor',
|
|
924
989
|
'select-editor',
|
|
925
990
|
'lookup-editor',
|
|
991
|
+
'entity-definition-provider-editor',
|
|
926
992
|
'selection-list-editor',
|
|
927
993
|
'date-time-editor',
|
|
928
994
|
'toggle-editor',
|
|
@@ -978,12 +1044,73 @@ class FlexContainerBuilder extends WidgetContainerMixin {
|
|
|
978
1044
|
* Grid Container Builder - Liskov Substitution Principle
|
|
979
1045
|
* Extends WidgetContainerMixin to inherit all common functionality
|
|
980
1046
|
*/
|
|
1047
|
+
/**
|
|
1048
|
+
* Extracts flat grid-item options from AXPGridLayoutOptions for grid-item-layout widget.
|
|
1049
|
+
* Uses first available breakpoint (lg, xl, md, sm).
|
|
1050
|
+
*/
|
|
1051
|
+
/**
|
|
1052
|
+
* Deep-merges grid breakpoint buckets so sequential fluent calls (e.g. setColumns then setGap)
|
|
1053
|
+
* do not wipe sibling keys under options.grid.default.
|
|
1054
|
+
*/
|
|
1055
|
+
function mergeAXPGridContainerOptions(prev, patch) {
|
|
1056
|
+
const next = { ...(prev ?? {}), ...patch };
|
|
1057
|
+
if (prev?.grid?.default || patch.grid?.default) {
|
|
1058
|
+
next.grid = {
|
|
1059
|
+
...(prev?.grid ?? {}),
|
|
1060
|
+
...(patch.grid ?? {}),
|
|
1061
|
+
default: {
|
|
1062
|
+
...(prev?.grid?.default ?? {}),
|
|
1063
|
+
...(patch.grid?.default ?? {}),
|
|
1064
|
+
},
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
return next;
|
|
1068
|
+
}
|
|
1069
|
+
function toGridItemOptions(layoutOptions) {
|
|
1070
|
+
if (!layoutOptions?.positions)
|
|
1071
|
+
return { colSpan: 12 };
|
|
1072
|
+
const positions = layoutOptions.positions;
|
|
1073
|
+
const placement = positions['lg'] ?? positions['xl'] ?? positions['xxl'] ?? positions['md'] ?? positions['sm'];
|
|
1074
|
+
if (!placement)
|
|
1075
|
+
return { colSpan: 12 };
|
|
1076
|
+
const opts = {};
|
|
1077
|
+
if (placement['colSpan'] != null)
|
|
1078
|
+
opts['colSpan'] = placement['colSpan'];
|
|
1079
|
+
if (placement['colStart'] != null)
|
|
1080
|
+
opts['colStart'] = placement['colStart'];
|
|
1081
|
+
if (placement['colEnd'] != null)
|
|
1082
|
+
opts['colEnd'] = placement['colEnd'];
|
|
1083
|
+
if (placement['rowSpan'] != null)
|
|
1084
|
+
opts['rowSpan'] = placement['rowSpan'];
|
|
1085
|
+
if (placement['rowStart'] != null)
|
|
1086
|
+
opts['rowStart'] = placement['rowStart'];
|
|
1087
|
+
if (placement['rowEnd'] != null)
|
|
1088
|
+
opts['rowEnd'] = placement['rowEnd'];
|
|
1089
|
+
if (Object.keys(opts).length === 0)
|
|
1090
|
+
opts['colSpan'] = 12;
|
|
1091
|
+
return opts;
|
|
1092
|
+
}
|
|
981
1093
|
class GridContainerBuilder extends WidgetContainerMixin {
|
|
982
1094
|
constructor() {
|
|
983
1095
|
super('grid-layout');
|
|
984
1096
|
}
|
|
985
1097
|
setOptions(options) {
|
|
986
|
-
this.containerState.options =
|
|
1098
|
+
this.containerState.options = mergeAXPGridContainerOptions(this.containerState.options, options);
|
|
1099
|
+
return this;
|
|
1100
|
+
}
|
|
1101
|
+
item(layoutOptions, delegate) {
|
|
1102
|
+
const fieldset = new FieldsetContainerBuilder();
|
|
1103
|
+
fieldset.withInheritanceContext(this.inheritanceContext);
|
|
1104
|
+
delegate(fieldset);
|
|
1105
|
+
const fieldsetNode = fieldset.build();
|
|
1106
|
+
const gridItemOptions = toGridItemOptions(layoutOptions);
|
|
1107
|
+
const gridItemNode = {
|
|
1108
|
+
type: 'grid-item-layout',
|
|
1109
|
+
options: gridItemOptions,
|
|
1110
|
+
children: [fieldsetNode],
|
|
1111
|
+
};
|
|
1112
|
+
this.ensureChildren();
|
|
1113
|
+
this.containerState.children.push(gridItemNode);
|
|
987
1114
|
return this;
|
|
988
1115
|
}
|
|
989
1116
|
// Individual fluent methods for Grid
|
|
@@ -1155,10 +1282,34 @@ class FormFieldBuilder extends LayoutContainerMixin {
|
|
|
1155
1282
|
child.type(type);
|
|
1156
1283
|
child.name(finalName);
|
|
1157
1284
|
child.path(widgetPath);
|
|
1158
|
-
//
|
|
1159
|
-
const { name: _, ...cleanOptions } = (options || {});
|
|
1285
|
+
// Extract extended properties from options (triggers, meta, valueTransforms, mode, visible, defaultValue)
|
|
1286
|
+
const { name: _, triggers, meta, valueTransforms, mode: extendedMode, visible: extendedVisible, defaultValue: extendedDefaultValue, children: extendedChildren, ...cleanOptions } = (options || {});
|
|
1160
1287
|
child.withInheritanceContext(this.inheritanceContext);
|
|
1161
1288
|
child.options(cleanOptions);
|
|
1289
|
+
// Apply extended properties if provided
|
|
1290
|
+
if (extendedMode !== undefined) {
|
|
1291
|
+
child.mode(extendedMode);
|
|
1292
|
+
}
|
|
1293
|
+
if (extendedVisible !== undefined) {
|
|
1294
|
+
child.visible(extendedVisible);
|
|
1295
|
+
}
|
|
1296
|
+
if (extendedDefaultValue !== undefined) {
|
|
1297
|
+
child.defaultValue(extendedDefaultValue);
|
|
1298
|
+
}
|
|
1299
|
+
// Set triggers, meta, and valueTransforms directly on widgetState
|
|
1300
|
+
// These are part of AXPWidgetNode but not handled by WidgetBuilder methods
|
|
1301
|
+
if (triggers !== undefined) {
|
|
1302
|
+
child.widgetState.triggers = triggers;
|
|
1303
|
+
}
|
|
1304
|
+
if (meta !== undefined) {
|
|
1305
|
+
child.widgetState.meta = meta;
|
|
1306
|
+
}
|
|
1307
|
+
if (valueTransforms !== undefined) {
|
|
1308
|
+
child.widgetState.valueTransforms = valueTransforms;
|
|
1309
|
+
}
|
|
1310
|
+
if (extendedChildren !== undefined) {
|
|
1311
|
+
child.widgetState.children = extendedChildren;
|
|
1312
|
+
}
|
|
1162
1313
|
// IMPORTANT: Store the widget builder, don't build it yet!
|
|
1163
1314
|
// This allows properties set after this method (like disabled, readonly) to be applied
|
|
1164
1315
|
this.childWidget = child;
|
|
@@ -1390,7 +1541,7 @@ class ListWidgetBuilder extends WidgetContainerMixin {
|
|
|
1390
1541
|
* Uses composition instead of inheritance for cleaner separation
|
|
1391
1542
|
*/
|
|
1392
1543
|
class DialogContainerBuilder {
|
|
1393
|
-
constructor(popupService) {
|
|
1544
|
+
constructor(popupService, hookService) {
|
|
1394
1545
|
this.dialogState = {
|
|
1395
1546
|
type: 'flex-layout', // This will be overridden when content layout exists
|
|
1396
1547
|
children: [],
|
|
@@ -1413,6 +1564,7 @@ class DialogContainerBuilder {
|
|
|
1413
1564
|
else {
|
|
1414
1565
|
this.popupService = inject(AXPopupService);
|
|
1415
1566
|
}
|
|
1567
|
+
this.hookService = hookService ?? inject(AXPHookService, { optional: true }) ?? undefined;
|
|
1416
1568
|
}
|
|
1417
1569
|
setOptions(options) {
|
|
1418
1570
|
this.dialogState.dialogOptions = { ...this.dialogState.dialogOptions, ...options };
|
|
@@ -1452,6 +1604,15 @@ class DialogContainerBuilder {
|
|
|
1452
1604
|
}
|
|
1453
1605
|
return this;
|
|
1454
1606
|
}
|
|
1607
|
+
onAction(handler) {
|
|
1608
|
+
this.dialogState.dialogOptions ??= {
|
|
1609
|
+
title: '',
|
|
1610
|
+
size: 'md',
|
|
1611
|
+
closeButton: false,
|
|
1612
|
+
};
|
|
1613
|
+
this.dialogState.dialogOptions.onAction = handler;
|
|
1614
|
+
return this;
|
|
1615
|
+
}
|
|
1455
1616
|
addCustomAction(action) {
|
|
1456
1617
|
// Add to actions based on position
|
|
1457
1618
|
const position = action.position || 'suffix';
|
|
@@ -1498,20 +1659,42 @@ class DialogContainerBuilder {
|
|
|
1498
1659
|
const dialogNode = this.build();
|
|
1499
1660
|
// Import the dialog renderer component dynamically
|
|
1500
1661
|
const { AXPDialogRendererComponent } = await Promise.resolve().then(function () { return dialogRenderer_component; });
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1662
|
+
this.dialogState.dialogOptions ??= {};
|
|
1663
|
+
if (this.dialogState.dialogOptions.context == null || typeof this.dialogState.dialogOptions.context !== 'object') {
|
|
1664
|
+
this.dialogState.dialogOptions.context = {};
|
|
1665
|
+
}
|
|
1666
|
+
const initialContext = this.dialogState.dialogOptions.context;
|
|
1667
|
+
this.dialogState.actions ??= { footer: { prefix: [], suffix: [] } };
|
|
1668
|
+
this.dialogState.actions.footer ??= { prefix: [], suffix: [] };
|
|
1669
|
+
this.dialogState.actions.footer.prefix ??= [];
|
|
1670
|
+
this.dialogState.actions.footer.suffix ??= [];
|
|
1671
|
+
const hookService = this.hookService;
|
|
1672
|
+
if (hookService) {
|
|
1673
|
+
const beforePayload = {
|
|
1674
|
+
context: initialContext,
|
|
1675
|
+
dialogOptions: this.dialogState.dialogOptions,
|
|
1676
|
+
};
|
|
1677
|
+
await hookService.runAsync(AXP_LAYOUT_BUILDER_DIALOG_BEFORE_OPEN_HOOK_KEY, beforePayload);
|
|
1678
|
+
}
|
|
1504
1679
|
// Create dialog configuration
|
|
1505
1680
|
const dialogConfig = {
|
|
1506
1681
|
title: this.dialogState.dialogOptions?.title || '',
|
|
1507
|
-
|
|
1508
|
-
|
|
1682
|
+
//TODO: why we need message?
|
|
1683
|
+
//message: this.dialogState.dialogOptions?.message,
|
|
1684
|
+
context: initialContext,
|
|
1509
1685
|
definition: dialogNode,
|
|
1686
|
+
metadata: this.dialogState.dialogOptions.metadata,
|
|
1510
1687
|
actions: this.dialogState.actions,
|
|
1688
|
+
onAction: this.dialogState.dialogOptions?.onAction,
|
|
1511
1689
|
};
|
|
1690
|
+
//
|
|
1691
|
+
if (hookService) {
|
|
1692
|
+
await hookService.runAsync(AXP_LAYOUT_BUILDER_DIALOG_CONFIG_HOOK_KEY, dialogConfig);
|
|
1693
|
+
}
|
|
1512
1694
|
// The Promise resolves when user clicks an action button
|
|
1513
1695
|
return new Promise(async (resolve) => {
|
|
1514
|
-
|
|
1696
|
+
let flag = false;
|
|
1697
|
+
await this.popupService.open(AXPDialogRendererComponent, {
|
|
1515
1698
|
title: dialogConfig.title,
|
|
1516
1699
|
size: this.dialogState.dialogOptions?.size || 'md',
|
|
1517
1700
|
closeButton: this.dialogState.dialogOptions?.closeButton || false,
|
|
@@ -1520,11 +1703,14 @@ class DialogContainerBuilder {
|
|
|
1520
1703
|
data: {
|
|
1521
1704
|
config: dialogConfig,
|
|
1522
1705
|
callBack: (result) => {
|
|
1523
|
-
|
|
1706
|
+
flag = true;
|
|
1524
1707
|
resolve(result);
|
|
1525
1708
|
},
|
|
1526
1709
|
},
|
|
1527
1710
|
});
|
|
1711
|
+
if (!flag) {
|
|
1712
|
+
resolve({ success: false });
|
|
1713
|
+
}
|
|
1528
1714
|
});
|
|
1529
1715
|
}
|
|
1530
1716
|
}
|
|
@@ -1539,6 +1725,7 @@ class WidgetBuilder {
|
|
|
1539
1725
|
this.widgetState = {
|
|
1540
1726
|
type: 'widget',
|
|
1541
1727
|
options: {},
|
|
1728
|
+
children: [],
|
|
1542
1729
|
};
|
|
1543
1730
|
this.inheritanceContext = {};
|
|
1544
1731
|
if (name) {
|
|
@@ -1602,6 +1789,7 @@ class WidgetBuilder {
|
|
|
1602
1789
|
this.widgetState.options = {};
|
|
1603
1790
|
}
|
|
1604
1791
|
this.widgetState.options['visible'] = condition;
|
|
1792
|
+
this.widgetState.visible = condition;
|
|
1605
1793
|
this.inheritanceContext.visible = condition;
|
|
1606
1794
|
return this;
|
|
1607
1795
|
}
|
|
@@ -1633,6 +1821,10 @@ class WidgetBuilder {
|
|
|
1633
1821
|
this.inheritanceContext.direction = direction;
|
|
1634
1822
|
return this;
|
|
1635
1823
|
}
|
|
1824
|
+
children(children) {
|
|
1825
|
+
this.widgetState.children = children;
|
|
1826
|
+
return this;
|
|
1827
|
+
}
|
|
1636
1828
|
// Inheritance context methods
|
|
1637
1829
|
withInheritanceContext(context) {
|
|
1638
1830
|
this.inheritanceContext = mergeInheritanceContext(context);
|
|
@@ -1656,6 +1848,7 @@ class WidgetBuilder {
|
|
|
1656
1848
|
}
|
|
1657
1849
|
if (resolved.visible !== undefined) {
|
|
1658
1850
|
this.widgetState.options['visible'] = resolved.visible;
|
|
1851
|
+
this.widgetState.visible = resolved.visible;
|
|
1659
1852
|
}
|
|
1660
1853
|
if (context.defaultValue !== undefined) {
|
|
1661
1854
|
this.widgetState.defaultValue = context.defaultValue;
|
|
@@ -1666,14 +1859,29 @@ class WidgetBuilder {
|
|
|
1666
1859
|
return { ...this.inheritanceContext };
|
|
1667
1860
|
}
|
|
1668
1861
|
build() {
|
|
1669
|
-
|
|
1862
|
+
const node = {
|
|
1670
1863
|
name: this.widgetState.name,
|
|
1671
1864
|
type: this.widgetState.type,
|
|
1672
1865
|
options: this.widgetState.options,
|
|
1673
1866
|
mode: this.widgetState.mode,
|
|
1674
1867
|
path: this.widgetState.path,
|
|
1675
1868
|
defaultValue: this.widgetState.defaultValue,
|
|
1869
|
+
children: this.widgetState.children,
|
|
1676
1870
|
};
|
|
1871
|
+
// Add extended properties if they exist
|
|
1872
|
+
if (this.widgetState.triggers !== undefined) {
|
|
1873
|
+
node.triggers = this.widgetState.triggers;
|
|
1874
|
+
}
|
|
1875
|
+
if (this.widgetState.meta !== undefined) {
|
|
1876
|
+
node.meta = this.widgetState.meta;
|
|
1877
|
+
}
|
|
1878
|
+
if (this.widgetState.valueTransforms !== undefined) {
|
|
1879
|
+
node.valueTransforms = this.widgetState.valueTransforms;
|
|
1880
|
+
}
|
|
1881
|
+
if (this.widgetState.visible !== undefined) {
|
|
1882
|
+
node.visible = this.widgetState.visible;
|
|
1883
|
+
}
|
|
1884
|
+
return node;
|
|
1677
1885
|
}
|
|
1678
1886
|
}
|
|
1679
1887
|
//#region ---- Action Builder Implementation ----
|
|
@@ -1687,7 +1895,6 @@ class ActionBuilder {
|
|
|
1687
1895
|
}
|
|
1688
1896
|
this.dialogBuilder['dialogState'].actions.footer.suffix.push({
|
|
1689
1897
|
title: text || '@general:actions.cancel.title',
|
|
1690
|
-
icon: 'fa-times',
|
|
1691
1898
|
color: 'default',
|
|
1692
1899
|
command: { name: 'cancel' },
|
|
1693
1900
|
});
|
|
@@ -1699,17 +1906,25 @@ class ActionBuilder {
|
|
|
1699
1906
|
}
|
|
1700
1907
|
this.dialogBuilder['dialogState'].actions.footer.suffix.push({
|
|
1701
1908
|
title: text || '@general:actions.submit.title',
|
|
1702
|
-
icon: 'fa-check',
|
|
1703
1909
|
color: 'primary',
|
|
1704
1910
|
command: { name: 'submit', options: { validate: true } },
|
|
1705
1911
|
});
|
|
1706
1912
|
return this;
|
|
1707
1913
|
}
|
|
1708
1914
|
custom(action) {
|
|
1709
|
-
|
|
1710
|
-
|
|
1915
|
+
const position = action.position ?? 'suffix';
|
|
1916
|
+
if (position === 'prefix') {
|
|
1917
|
+
if (!this.dialogBuilder['dialogState'].actions.footer.prefix) {
|
|
1918
|
+
this.dialogBuilder['dialogState'].actions.footer.prefix = [];
|
|
1919
|
+
}
|
|
1920
|
+
this.dialogBuilder['dialogState'].actions.footer.prefix.push(action);
|
|
1921
|
+
}
|
|
1922
|
+
else {
|
|
1923
|
+
if (!this.dialogBuilder['dialogState'].actions.footer.suffix) {
|
|
1924
|
+
this.dialogBuilder['dialogState'].actions.footer.suffix = [];
|
|
1925
|
+
}
|
|
1926
|
+
this.dialogBuilder['dialogState'].actions.footer.suffix.push(action);
|
|
1711
1927
|
}
|
|
1712
|
-
this.dialogBuilder['dialogState'].actions.footer.suffix.push(action);
|
|
1713
1928
|
return this;
|
|
1714
1929
|
}
|
|
1715
1930
|
}
|
|
@@ -1858,22 +2073,27 @@ class AXPLayoutRendererComponent {
|
|
|
1858
2073
|
/**
|
|
1859
2074
|
* Form definition containing groups and fields OR widget tree
|
|
1860
2075
|
*/
|
|
1861
|
-
this.layout = input.required(...(ngDevMode ? [{ debugName: "layout" }] : []));
|
|
2076
|
+
this.layout = input.required(...(ngDevMode ? [{ debugName: "layout" }] : /* istanbul ignore next */ []));
|
|
1862
2077
|
/**
|
|
1863
2078
|
* Form context/model data
|
|
1864
2079
|
*/
|
|
1865
|
-
this.context = model({}, ...(ngDevMode ? [{ debugName: "context" }] : []));
|
|
2080
|
+
this.context = model({}, ...(ngDevMode ? [{ debugName: "context" }] : /* istanbul ignore next */ []));
|
|
1866
2081
|
/**
|
|
1867
2082
|
* Form appearance and density styling (normal, compact, spacious)
|
|
1868
2083
|
*/
|
|
1869
|
-
this.look = input('fieldset', ...(ngDevMode ? [{ debugName: "look" }] : []));
|
|
2084
|
+
this.look = input('fieldset', ...(ngDevMode ? [{ debugName: "look" }] : /* istanbul ignore next */ []));
|
|
1870
2085
|
/**
|
|
1871
2086
|
* Default form mode. Can be overridden by section/group and field.
|
|
1872
2087
|
*/
|
|
1873
|
-
this.mode = input('edit', ...(ngDevMode ? [{ debugName: "mode" }] : []));
|
|
2088
|
+
this.mode = input('edit', ...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
|
|
1874
2089
|
//#endregion
|
|
1875
2090
|
//#region ---- Widget Tree Conversion ----
|
|
1876
|
-
this.widgetTree = signal(null, ...(ngDevMode ? [{ debugName: "widgetTree" }] : []));
|
|
2091
|
+
this.widgetTree = signal(null, ...(ngDevMode ? [{ debugName: "widgetTree" }] : /* istanbul ignore next */ []));
|
|
2092
|
+
/**
|
|
2093
|
+
* Prefer explicit {@link AXPWidgetNode.mode} on the root node (e.g. dialog flex `mode('view')`)
|
|
2094
|
+
* so nested widgets resolve view vs edit correctly; fall back to the layout `mode` input.
|
|
2095
|
+
*/
|
|
2096
|
+
this.effectiveRenderMode = computed(() => this.widgetTree()?.mode ?? this.mode(), ...(ngDevMode ? [{ debugName: "effectiveRenderMode" }] : /* istanbul ignore next */ []));
|
|
1877
2097
|
/**
|
|
1878
2098
|
* Convert layout data to widget tree when inputs change
|
|
1879
2099
|
*/
|
|
@@ -1898,7 +2118,7 @@ class AXPLayoutRendererComponent {
|
|
|
1898
2118
|
if (!isEqual(prev, tree)) {
|
|
1899
2119
|
this.widgetTree.set(tree);
|
|
1900
2120
|
}
|
|
1901
|
-
}, ...(ngDevMode ? [{ debugName: "conversionEffect" }] : []));
|
|
2121
|
+
}, ...(ngDevMode ? [{ debugName: "conversionEffect" }] : /* istanbul ignore next */ []));
|
|
1902
2122
|
//#endregion
|
|
1903
2123
|
//#region ---- Outputs ----
|
|
1904
2124
|
/**
|
|
@@ -1911,12 +2131,12 @@ class AXPLayoutRendererComponent {
|
|
|
1911
2131
|
this.validityChange = output();
|
|
1912
2132
|
//#endregion
|
|
1913
2133
|
//#region ---- Properties ----
|
|
1914
|
-
this.form = viewChild(AXFormComponent, ...(ngDevMode ? [{ debugName: "form" }] : []));
|
|
1915
|
-
this.container = viewChild(AXPWidgetContainerComponent, ...(ngDevMode ? [{ debugName: "container" }] : []));
|
|
2134
|
+
this.form = viewChild(AXFormComponent, ...(ngDevMode ? [{ debugName: "form" }] : /* istanbul ignore next */ []));
|
|
2135
|
+
this.container = viewChild(AXPWidgetContainerComponent, ...(ngDevMode ? [{ debugName: "container" }] : /* istanbul ignore next */ []));
|
|
1916
2136
|
/**
|
|
1917
2137
|
* Internal context signal for reactivity
|
|
1918
2138
|
*/
|
|
1919
|
-
this.internalContext = signal({}, ...(ngDevMode ? [{ debugName: "internalContext" }] : []));
|
|
2139
|
+
this.internalContext = signal({}, ...(ngDevMode ? [{ debugName: "internalContext" }] : /* istanbul ignore next */ []));
|
|
1920
2140
|
/**
|
|
1921
2141
|
* Initial context for reset functionality
|
|
1922
2142
|
*/
|
|
@@ -1929,7 +2149,7 @@ class AXPLayoutRendererComponent {
|
|
|
1929
2149
|
this.#contextSyncEffect = effect(() => {
|
|
1930
2150
|
const ctx = this.context() ?? {};
|
|
1931
2151
|
this.contextUpdateSubject.next(ctx);
|
|
1932
|
-
}, ...(ngDevMode ? [{ debugName: "#contextSyncEffect" }] : []));
|
|
2152
|
+
}, ...(ngDevMode ? [{ debugName: "#contextSyncEffect" }] : /* istanbul ignore next */ []));
|
|
1933
2153
|
/**
|
|
1934
2154
|
* Effect to handle widget tree status changes
|
|
1935
2155
|
*/
|
|
@@ -1938,7 +2158,7 @@ class AXPLayoutRendererComponent {
|
|
|
1938
2158
|
if (widgetTree) {
|
|
1939
2159
|
this.container()?.builderService.setStatus(AXPPageStatus.Rendered);
|
|
1940
2160
|
}
|
|
1941
|
-
}, ...(ngDevMode ? [{ debugName: "#widgetStatusEffect" }] : []));
|
|
2161
|
+
}, ...(ngDevMode ? [{ debugName: "#widgetStatusEffect" }] : /* istanbul ignore next */ []));
|
|
1942
2162
|
}
|
|
1943
2163
|
//#endregion
|
|
1944
2164
|
//#region ---- Lifecycle Methods ----
|
|
@@ -2078,40 +2298,67 @@ class AXPLayoutRendererComponent {
|
|
|
2078
2298
|
isWidgetNode(data) {
|
|
2079
2299
|
return data && typeof data === 'object' && 'type' in data && typeof data.type === 'string';
|
|
2080
2300
|
}
|
|
2081
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
2082
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
2301
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLayoutRendererComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2302
|
+
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: `
|
|
2083
2303
|
<ax-form>
|
|
2084
2304
|
<axp-widgets-container [context]="internalContext()" (onContextChanged)="handleContextChanged($event)">
|
|
2085
2305
|
@if (widgetTree()) {
|
|
2086
|
-
<ng-container
|
|
2306
|
+
<ng-container
|
|
2307
|
+
axp-widget-renderer
|
|
2308
|
+
[node]="widgetTree()!"
|
|
2309
|
+
[mode]="effectiveRenderMode()"
|
|
2310
|
+
></ng-container>
|
|
2087
2311
|
}
|
|
2088
2312
|
</axp-widgets-container>
|
|
2089
2313
|
</ax-form>
|
|
2090
|
-
`, isInline: true, styles: [":host{display:block;width:100%}\n"], dependencies: [{ kind: "ngmodule", type:
|
|
2314
|
+
`, 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 }); }
|
|
2091
2315
|
}
|
|
2092
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
2316
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLayoutRendererComponent, decorators: [{
|
|
2093
2317
|
type: Component,
|
|
2094
|
-
args: [{ selector: 'axp-layout-renderer', standalone: true, imports: [
|
|
2318
|
+
args: [{ selector: 'axp-layout-renderer', standalone: true, imports: [AXPWidgetCoreModule, AXFormModule], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
2095
2319
|
<ax-form>
|
|
2096
2320
|
<axp-widgets-container [context]="internalContext()" (onContextChanged)="handleContextChanged($event)">
|
|
2097
2321
|
@if (widgetTree()) {
|
|
2098
|
-
<ng-container
|
|
2322
|
+
<ng-container
|
|
2323
|
+
axp-widget-renderer
|
|
2324
|
+
[node]="widgetTree()!"
|
|
2325
|
+
[mode]="effectiveRenderMode()"
|
|
2326
|
+
></ng-container>
|
|
2099
2327
|
}
|
|
2100
2328
|
</axp-widgets-container>
|
|
2101
2329
|
</ax-form>
|
|
2102
2330
|
`, styles: [":host{display:block;width:100%}\n"] }]
|
|
2103
2331
|
}], 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 }] }] } });
|
|
2104
2332
|
|
|
2333
|
+
/** Registration key for {@link AXPPreviewWidgetFieldCommand}; lives alone so `LayoutBuilderModule` can reference it without static-importing the command implementation. */
|
|
2334
|
+
const AXP_PREVIEW_WIDGET_FIELD_COMMAND_KEY = 'Widget:Preview';
|
|
2335
|
+
|
|
2105
2336
|
class LayoutBuilderModule {
|
|
2106
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
2107
|
-
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "
|
|
2108
|
-
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "
|
|
2337
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: LayoutBuilderModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
|
|
2338
|
+
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.9", ngImport: i0, type: LayoutBuilderModule, imports: [CommonModule, AXPLayoutRendererComponent], exports: [AXPLayoutRendererComponent] }); }
|
|
2339
|
+
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: LayoutBuilderModule, providers: [
|
|
2340
|
+
AXPLayoutBuilderService,
|
|
2341
|
+
provideCommandSetups([
|
|
2342
|
+
{
|
|
2343
|
+
key: AXP_PREVIEW_WIDGET_FIELD_COMMAND_KEY,
|
|
2344
|
+
command: () => Promise.resolve().then(function () { return previewWidgetField_command; }).then((c) => c.AXPPreviewWidgetFieldCommand),
|
|
2345
|
+
},
|
|
2346
|
+
]),
|
|
2347
|
+
], imports: [CommonModule, AXPLayoutRendererComponent] }); }
|
|
2109
2348
|
}
|
|
2110
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
2349
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: LayoutBuilderModule, decorators: [{
|
|
2111
2350
|
type: NgModule,
|
|
2112
2351
|
args: [{
|
|
2113
2352
|
imports: [CommonModule, AXPLayoutRendererComponent],
|
|
2114
|
-
providers: [
|
|
2353
|
+
providers: [
|
|
2354
|
+
AXPLayoutBuilderService,
|
|
2355
|
+
provideCommandSetups([
|
|
2356
|
+
{
|
|
2357
|
+
key: AXP_PREVIEW_WIDGET_FIELD_COMMAND_KEY,
|
|
2358
|
+
command: () => Promise.resolve().then(function () { return previewWidgetField_command; }).then((c) => c.AXPPreviewWidgetFieldCommand),
|
|
2359
|
+
},
|
|
2360
|
+
]),
|
|
2361
|
+
],
|
|
2115
2362
|
exports: [AXPLayoutRendererComponent],
|
|
2116
2363
|
}]
|
|
2117
2364
|
}] });
|
|
@@ -2123,17 +2370,25 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
|
|
|
2123
2370
|
super(...arguments);
|
|
2124
2371
|
this.result = new EventEmitter();
|
|
2125
2372
|
this.expressionEvaluator = inject(AXPExpressionEvaluatorService);
|
|
2126
|
-
this.
|
|
2373
|
+
this.commandService = inject(AXPCommandService);
|
|
2374
|
+
this.hookService = inject(AXPHookService, { optional: true });
|
|
2375
|
+
this.context = signal({}, ...(ngDevMode ? [{ debugName: "context" }] : /* istanbul ignore next */ []));
|
|
2127
2376
|
// This will be set by the popup service automatically - same as dynamic-dialog
|
|
2128
2377
|
this.callBack = () => { };
|
|
2129
|
-
this.isDialogLoading = signal(false, ...(ngDevMode ? [{ debugName: "isDialogLoading" }] : []));
|
|
2378
|
+
this.isDialogLoading = signal(false, ...(ngDevMode ? [{ debugName: "isDialogLoading" }] : /* istanbul ignore next */ []));
|
|
2130
2379
|
// Aggregated actions for footer rendering
|
|
2131
|
-
this.footerPrefix = signal([], ...(ngDevMode ? [{ debugName: "footerPrefix" }] : []));
|
|
2132
|
-
this.footerSuffix = signal([], ...(ngDevMode ? [{ debugName: "footerSuffix" }] : []));
|
|
2380
|
+
this.footerPrefix = signal([], ...(ngDevMode ? [{ debugName: "footerPrefix" }] : /* istanbul ignore next */ []));
|
|
2381
|
+
this.footerSuffix = signal([], ...(ngDevMode ? [{ debugName: "footerSuffix" }] : /* istanbul ignore next */ []));
|
|
2382
|
+
/**
|
|
2383
|
+
* Correlate layout context snapshots for distributed hooks (`layout-builder.dialog.context-changed`).
|
|
2384
|
+
*/
|
|
2385
|
+
this.contextChangedHooksSessionKey = typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function'
|
|
2386
|
+
? crypto.randomUUID()
|
|
2387
|
+
: `layout-dialog-ctx-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
2133
2388
|
//#endregion
|
|
2134
2389
|
//#region ---- View Accessors ----
|
|
2135
2390
|
// Access the internal layout renderer to reach the widgets container injector
|
|
2136
|
-
this.layoutRenderer = viewChild(AXPLayoutRendererComponent, ...(ngDevMode ? [{ debugName: "layoutRenderer" }] : []));
|
|
2391
|
+
this.layoutRenderer = viewChild(AXPLayoutRendererComponent, ...(ngDevMode ? [{ debugName: "layoutRenderer" }] : /* istanbul ignore next */ []));
|
|
2137
2392
|
this.#eff = effect(() => {
|
|
2138
2393
|
let count = 0;
|
|
2139
2394
|
this.aggregateAndEvaluateActions();
|
|
@@ -2141,10 +2396,10 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
|
|
|
2141
2396
|
const renderer = this.layoutRenderer();
|
|
2142
2397
|
const container = renderer?.getContainer();
|
|
2143
2398
|
this.widgetCoreService = container?.builderService ?? null;
|
|
2144
|
-
count = this.widgetCoreService
|
|
2399
|
+
count = this.widgetCoreService?.registeredWidgetsCount();
|
|
2145
2400
|
}
|
|
2146
2401
|
else {
|
|
2147
|
-
count = this.widgetCoreService
|
|
2402
|
+
count = this.widgetCoreService?.registeredWidgetsCount();
|
|
2148
2403
|
// Clear existing timer
|
|
2149
2404
|
if (this.debounceTimer) {
|
|
2150
2405
|
clearTimeout(this.debounceTimer);
|
|
@@ -2154,23 +2409,48 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
|
|
|
2154
2409
|
this.aggregateAndEvaluateActions();
|
|
2155
2410
|
}, 200);
|
|
2156
2411
|
}
|
|
2157
|
-
}, ...(ngDevMode ? [{ debugName: "#eff" }] : []));
|
|
2412
|
+
}, ...(ngDevMode ? [{ debugName: "#eff" }] : /* istanbul ignore next */ []));
|
|
2158
2413
|
}
|
|
2159
2414
|
//#endregion
|
|
2160
2415
|
//#region ---- Lifecycle ----
|
|
2161
2416
|
ngOnInit() {
|
|
2162
|
-
// Initialize context with provided context
|
|
2163
2417
|
this.context.set(this.config?.context || {});
|
|
2418
|
+
void this.invokeLayoutContextChangedHooks();
|
|
2164
2419
|
}
|
|
2165
2420
|
#eff;
|
|
2166
2421
|
//#endregion
|
|
2167
2422
|
handleContextChanged(event) {
|
|
2168
2423
|
this.context.set(event);
|
|
2169
2424
|
this.aggregateAndEvaluateActions();
|
|
2425
|
+
void this.invokeLayoutContextChangedHooks();
|
|
2170
2426
|
}
|
|
2171
2427
|
handleContextInitiated(event) {
|
|
2172
2428
|
this.context.set(event);
|
|
2173
2429
|
this.aggregateAndEvaluateActions();
|
|
2430
|
+
void this.invokeLayoutContextChangedHooks();
|
|
2431
|
+
}
|
|
2432
|
+
async invokeLayoutContextChangedHooks() {
|
|
2433
|
+
const meta = this.config?.metadata;
|
|
2434
|
+
if (!this.hookService) {
|
|
2435
|
+
return;
|
|
2436
|
+
}
|
|
2437
|
+
const payload = {
|
|
2438
|
+
sessionKey: this.contextChangedHooksSessionKey,
|
|
2439
|
+
getContext: () => (this.context() ?? {}),
|
|
2440
|
+
metadata: meta,
|
|
2441
|
+
patchContext: (partial) => {
|
|
2442
|
+
const merged = merge({}, this.context(), partial);
|
|
2443
|
+
this.context.set(merged);
|
|
2444
|
+
this.layoutRenderer()?.updateContext(merged);
|
|
2445
|
+
},
|
|
2446
|
+
setLoading: (loading) => this.isDialogLoading.set(loading),
|
|
2447
|
+
};
|
|
2448
|
+
try {
|
|
2449
|
+
await this.hookService.runAsync(AXP_LAYOUT_BUILDER_DIALOG_CONTEXT_CHANGED_HOOK_KEY, payload);
|
|
2450
|
+
}
|
|
2451
|
+
catch {
|
|
2452
|
+
// Hook providers are best-effort; avoid breaking the dialog lifecycle.
|
|
2453
|
+
}
|
|
2174
2454
|
}
|
|
2175
2455
|
footerPrefixActions() {
|
|
2176
2456
|
return this.footerPrefix();
|
|
@@ -2185,38 +2465,138 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
|
|
|
2185
2465
|
return this.isDialogLoading();
|
|
2186
2466
|
}
|
|
2187
2467
|
async executeAction(action) {
|
|
2188
|
-
const cmd = action.command;
|
|
2189
|
-
if (cmd
|
|
2468
|
+
const cmd = this.resolveActionCommandName(action.command);
|
|
2469
|
+
if (this.shouldValidateBeforeAction(cmd)) {
|
|
2190
2470
|
const isValid = await this.layoutRenderer()?.validate();
|
|
2191
2471
|
if (!isValid?.result) {
|
|
2192
2472
|
return;
|
|
2193
2473
|
}
|
|
2194
2474
|
}
|
|
2195
|
-
|
|
2475
|
+
//TODO: matin, why we need this? maybe we can remove it?
|
|
2476
|
+
if (cmd?.startsWith('widget:')) {
|
|
2196
2477
|
const parsed = this.parseWidgetCommand(cmd);
|
|
2197
2478
|
if (parsed.widgetName && parsed.action) {
|
|
2198
|
-
await this.
|
|
2479
|
+
await this.invokeWidget(parsed.widgetName, parsed.action, {});
|
|
2199
2480
|
await this.aggregateAndEvaluateActions();
|
|
2200
2481
|
return;
|
|
2201
2482
|
}
|
|
2202
2483
|
}
|
|
2484
|
+
if (cmd && this.commandService.exists(cmd)) {
|
|
2485
|
+
const dialogRef = this.createDialogRef(cmd);
|
|
2486
|
+
const integration = (this.config.metadata ?? {});
|
|
2487
|
+
try {
|
|
2488
|
+
const cmdResult = await this.commandService.execute(cmd, { dialogRef, integration });
|
|
2489
|
+
if (!cmdResult?.success) {
|
|
2490
|
+
return;
|
|
2491
|
+
}
|
|
2492
|
+
if (this.shouldKeepDialogOpenAfterCommandResult(cmdResult)) {
|
|
2493
|
+
return;
|
|
2494
|
+
}
|
|
2495
|
+
this.callBack(cmdResult);
|
|
2496
|
+
await this.closeWithOptionalSkipValidate(cmdResult);
|
|
2497
|
+
}
|
|
2498
|
+
catch (error) {
|
|
2499
|
+
console.error('Error executing action', cmd, error);
|
|
2500
|
+
}
|
|
2501
|
+
return;
|
|
2502
|
+
}
|
|
2503
|
+
const context = this.context();
|
|
2504
|
+
const onAction = this.config?.onAction;
|
|
2505
|
+
if (onAction) {
|
|
2506
|
+
const dialogRef = this.createDialogRef(cmd);
|
|
2507
|
+
try {
|
|
2508
|
+
this.isDialogLoading.set(true);
|
|
2509
|
+
const result = await Promise.resolve(onAction(dialogRef));
|
|
2510
|
+
if (this.shouldKeepDialogOpenAfterCommandResult(result)) {
|
|
2511
|
+
return;
|
|
2512
|
+
}
|
|
2513
|
+
this.callBack(result);
|
|
2514
|
+
await this.closeWithOptionalSkipValidate(result);
|
|
2515
|
+
}
|
|
2516
|
+
catch {
|
|
2517
|
+
// Handler threw: stay open for retry, actions remain clickable
|
|
2518
|
+
}
|
|
2519
|
+
finally {
|
|
2520
|
+
this.isDialogLoading.set(false);
|
|
2521
|
+
}
|
|
2522
|
+
return;
|
|
2523
|
+
}
|
|
2203
2524
|
// Fallback: treat as regular dialog action (cancel/confirm/custom)
|
|
2204
|
-
const result = { context
|
|
2525
|
+
const result = { context, action: cmd };
|
|
2205
2526
|
this.dialogResult = result;
|
|
2206
2527
|
if (this.data) {
|
|
2207
2528
|
this.data.context = result.context;
|
|
2208
2529
|
this.data.action = result.action;
|
|
2209
2530
|
}
|
|
2210
2531
|
this.callBack({
|
|
2211
|
-
|
|
2212
|
-
|
|
2532
|
+
...this.createDialogRef(cmd),
|
|
2533
|
+
action: () => result.action,
|
|
2534
|
+
});
|
|
2535
|
+
// Without `onAction`, only the configured cancel action dismisses the dialog (not submit/custom).
|
|
2536
|
+
if (cmd === 'cancel') {
|
|
2537
|
+
await this.close(result);
|
|
2538
|
+
}
|
|
2539
|
+
}
|
|
2540
|
+
/** Whether the layout form should be validated before running this footer command. */
|
|
2541
|
+
shouldValidateBeforeAction(cmd) {
|
|
2542
|
+
if (!cmd || cmd === 'cancel' || cmd === 'entity-form-done') {
|
|
2543
|
+
return false;
|
|
2544
|
+
}
|
|
2545
|
+
if (cmd.startsWith('widget:')) {
|
|
2546
|
+
return false;
|
|
2547
|
+
}
|
|
2548
|
+
if (this.commandService.exists(cmd)) {
|
|
2549
|
+
return false;
|
|
2550
|
+
}
|
|
2551
|
+
return true;
|
|
2552
|
+
}
|
|
2553
|
+
/** True when a footer handler or command result asks to leave the dialog open (`keepDialogOpen` on the result or `result.data`). */
|
|
2554
|
+
shouldKeepDialogOpenAfterCommandResult(result) {
|
|
2555
|
+
if (!result || typeof result !== 'object') {
|
|
2556
|
+
return false;
|
|
2557
|
+
}
|
|
2558
|
+
const top = result;
|
|
2559
|
+
if (top.keepDialogOpen === true) {
|
|
2560
|
+
return true;
|
|
2561
|
+
}
|
|
2562
|
+
if (top.data != null && typeof top.data === 'object' && 'keepDialogOpen' in top.data) {
|
|
2563
|
+
return top.data.keepDialogOpen === true;
|
|
2564
|
+
}
|
|
2565
|
+
return false;
|
|
2566
|
+
}
|
|
2567
|
+
createDialogRef(actionCmd) {
|
|
2568
|
+
return {
|
|
2569
|
+
close: (res) => {
|
|
2570
|
+
void this.closeWithOptionalSkipValidate(res);
|
|
2213
2571
|
},
|
|
2214
2572
|
context: () => this.context(),
|
|
2215
|
-
action: () =>
|
|
2216
|
-
setLoading: (loading) =>
|
|
2217
|
-
|
|
2573
|
+
action: () => actionCmd,
|
|
2574
|
+
setLoading: (loading) => this.isDialogLoading.set(loading),
|
|
2575
|
+
patchContext: (partial) => {
|
|
2576
|
+
const merged = merge({}, this.context(), partial);
|
|
2577
|
+
this.context.set(merged);
|
|
2578
|
+
this.layoutRenderer()?.updateContext(merged);
|
|
2218
2579
|
},
|
|
2219
|
-
|
|
2580
|
+
invokeWidget: (widgetName, method, opts) => this.invokeWidget(widgetName, method, opts ?? {}),
|
|
2581
|
+
};
|
|
2582
|
+
}
|
|
2583
|
+
async closeWithOptionalSkipValidate(result) {
|
|
2584
|
+
if (result && typeof result === 'object' && result.skipValidate) {
|
|
2585
|
+
this.result.emit(result);
|
|
2586
|
+
await super.close(result);
|
|
2587
|
+
return;
|
|
2588
|
+
}
|
|
2589
|
+
await this.close(result);
|
|
2590
|
+
}
|
|
2591
|
+
/** Resolves footer/widget action command to a string (e.g. `cancel`, `submit`, `widget:...`). */
|
|
2592
|
+
resolveActionCommandName(command) {
|
|
2593
|
+
if (typeof command === 'string') {
|
|
2594
|
+
return command;
|
|
2595
|
+
}
|
|
2596
|
+
if (command && typeof command === 'object' && 'name' in command) {
|
|
2597
|
+
return command.name;
|
|
2598
|
+
}
|
|
2599
|
+
return undefined;
|
|
2220
2600
|
}
|
|
2221
2601
|
parseWidgetCommand(cmd) {
|
|
2222
2602
|
// Expected 'widget:<widgetName>.<action>'
|
|
@@ -2228,7 +2608,7 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
|
|
|
2228
2608
|
return {};
|
|
2229
2609
|
return { widgetName: rest.slice(0, dot), action: rest.slice(dot + 1) };
|
|
2230
2610
|
}
|
|
2231
|
-
async
|
|
2611
|
+
async invokeWidget(widgetName, apiMethod, opts) {
|
|
2232
2612
|
if (!this.widgetCoreService)
|
|
2233
2613
|
return;
|
|
2234
2614
|
try {
|
|
@@ -2238,16 +2618,20 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
|
|
|
2238
2618
|
if (typeof fn === 'function') {
|
|
2239
2619
|
await Promise.resolve(fn({
|
|
2240
2620
|
close: (result) => {
|
|
2241
|
-
this.
|
|
2621
|
+
void this.closeWithOptionalSkipValidate(result);
|
|
2242
2622
|
},
|
|
2243
2623
|
context: () => this.context(),
|
|
2244
2624
|
setLoading: (loading) => {
|
|
2245
|
-
this.isDialogLoading.set(loading);
|
|
2625
|
+
(opts.setLoading ?? ((v) => this.isDialogLoading.set(v)))(loading);
|
|
2246
2626
|
},
|
|
2247
2627
|
}));
|
|
2628
|
+
// Footer predicates (e.g. wizard step) must refresh when the widget advances outside executeAction (e.g. dialogRef.invokeWidget after entity-form continue).
|
|
2629
|
+
await this.aggregateAndEvaluateActions();
|
|
2248
2630
|
}
|
|
2249
2631
|
}
|
|
2250
|
-
catch {
|
|
2632
|
+
catch {
|
|
2633
|
+
//
|
|
2634
|
+
}
|
|
2251
2635
|
}
|
|
2252
2636
|
async close(result) {
|
|
2253
2637
|
if (result) {
|
|
@@ -2297,6 +2681,7 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
|
|
|
2297
2681
|
zone: 'footer',
|
|
2298
2682
|
placement,
|
|
2299
2683
|
scope: a.scope,
|
|
2684
|
+
predicateApiWidgetName: a.predicateApiWidgetName,
|
|
2300
2685
|
});
|
|
2301
2686
|
const prefix = (footer?.prefix || []).map((a) => mapOne(a, 'prefix'));
|
|
2302
2687
|
const suffix = (footer?.suffix || []).map((a) => mapOne(a, 'suffix'));
|
|
@@ -2306,16 +2691,18 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
|
|
|
2306
2691
|
const out = [];
|
|
2307
2692
|
for (const a of actions) {
|
|
2308
2693
|
const parsed = typeof a.command === 'string' ? this.parseWidgetCommand(a.command) : {};
|
|
2309
|
-
const
|
|
2694
|
+
const widgetNameForApi = parsed.widgetName ?? a.predicateApiWidgetName;
|
|
2695
|
+
const api = widgetNameForApi ? await this.resolveApi(widgetNameForApi) : undefined;
|
|
2310
2696
|
const scope = {
|
|
2311
2697
|
api,
|
|
2312
|
-
widget: { name:
|
|
2698
|
+
widget: { name: widgetNameForApi },
|
|
2313
2699
|
dialog: { context: this.context() },
|
|
2314
2700
|
context: this.context(),
|
|
2315
2701
|
};
|
|
2316
2702
|
const disabled = await this.evalBool(a.disabled, scope);
|
|
2317
2703
|
const hidden = await this.evalBool(a.hidden, scope);
|
|
2318
|
-
|
|
2704
|
+
const resolvedTitle = (await this.evalActionTitle(a.title, scope)) ?? a.title;
|
|
2705
|
+
out.push({ ...a, disabled, hidden, title: resolvedTitle });
|
|
2319
2706
|
}
|
|
2320
2707
|
return out;
|
|
2321
2708
|
}
|
|
@@ -2333,6 +2720,25 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
|
|
|
2333
2720
|
}
|
|
2334
2721
|
return value;
|
|
2335
2722
|
}
|
|
2723
|
+
/** Resolves footer action title when it contains {{ ... }} (e.g. wizard labels from dialog context). */
|
|
2724
|
+
async evalActionTitle(value, scope) {
|
|
2725
|
+
if (value == null || typeof value !== 'string' || !value.includes('{{')) {
|
|
2726
|
+
return value;
|
|
2727
|
+
}
|
|
2728
|
+
try {
|
|
2729
|
+
const result = await this.expressionEvaluator.evaluate(value, scope);
|
|
2730
|
+
if (typeof result === 'string' && result.length > 0) {
|
|
2731
|
+
return result;
|
|
2732
|
+
}
|
|
2733
|
+
if (result != null && result !== false) {
|
|
2734
|
+
return String(result);
|
|
2735
|
+
}
|
|
2736
|
+
}
|
|
2737
|
+
catch {
|
|
2738
|
+
//
|
|
2739
|
+
}
|
|
2740
|
+
return value;
|
|
2741
|
+
}
|
|
2336
2742
|
async resolveApi(widgetName) {
|
|
2337
2743
|
try {
|
|
2338
2744
|
await this.widgetCoreService?.waitForWidget(widgetName, 2000);
|
|
@@ -2343,9 +2749,13 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
|
|
|
2343
2749
|
return undefined;
|
|
2344
2750
|
}
|
|
2345
2751
|
}
|
|
2346
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
2347
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
2752
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPDialogRendererComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
2753
|
+
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: `
|
|
2754
|
+
<axp-component-slot name="dialog-header" [context]="context()"></axp-component-slot>
|
|
2348
2755
|
<div class="ax-p-4">
|
|
2756
|
+
<!-- @if (config.message) {
|
|
2757
|
+
<p class="ax-mb-4 ax-leading-relaxed">{{ config.message | translate | async }}</p>
|
|
2758
|
+
} -->
|
|
2349
2759
|
<axp-layout-renderer
|
|
2350
2760
|
[layout]="config.definition"
|
|
2351
2761
|
[context]="context()"
|
|
@@ -2355,46 +2765,57 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
|
|
|
2355
2765
|
</axp-layout-renderer>
|
|
2356
2766
|
</div>
|
|
2357
2767
|
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
@for (action of footerSuffixActions(); track $index) {
|
|
2376
|
-
<ax-button
|
|
2377
|
-
[disabled]="action.disabled || isSubmitting()"
|
|
2378
|
-
[text]="(action.title | translate | async)!"
|
|
2379
|
-
[look]="'solid'"
|
|
2380
|
-
[color]="action.color"
|
|
2381
|
-
(onClick)="executeAction(action)"
|
|
2382
|
-
>
|
|
2383
|
-
@if (isFormLoading()) {
|
|
2384
|
-
<ax-loading></ax-loading>
|
|
2385
|
-
}
|
|
2386
|
-
@if (action.icon) {
|
|
2768
|
+
<!-- Custom footer slot: if it has content, default footer is hidden -->
|
|
2769
|
+
<axp-component-slot name="dialog-footer" #footerSlot="slot" [context]="context()"></axp-component-slot>
|
|
2770
|
+
@if (footerSlot.isEmpty()) {
|
|
2771
|
+
<ax-footer>
|
|
2772
|
+
<ax-prefix>
|
|
2773
|
+
<axp-component-slot name="dialog-footer-prefix" [context]="context()"></axp-component-slot>
|
|
2774
|
+
@for (action of footerPrefixActions(); track $index) {
|
|
2775
|
+
<ax-button
|
|
2776
|
+
[disabled]="action.disabled || isFormLoading()"
|
|
2777
|
+
[text]="(action.title | translate | async)!"
|
|
2778
|
+
[look]="'outline'"
|
|
2779
|
+
[color]="action.color"
|
|
2780
|
+
(onClick)="executeAction(action)"
|
|
2781
|
+
>
|
|
2782
|
+
@if (isFormLoading()) {
|
|
2783
|
+
<ax-loading></ax-loading>
|
|
2784
|
+
}
|
|
2387
2785
|
<ax-prefix>
|
|
2388
|
-
|
|
2786
|
+
@if (action.icon) {
|
|
2787
|
+
<ax-icon [icon]="action.icon"></ax-icon>
|
|
2788
|
+
}
|
|
2389
2789
|
</ax-prefix>
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2790
|
+
</ax-button>
|
|
2791
|
+
}
|
|
2792
|
+
</ax-prefix>
|
|
2793
|
+
<ax-suffix>
|
|
2794
|
+
@for (action of footerSuffixActions(); track $index) {
|
|
2795
|
+
<ax-button
|
|
2796
|
+
[disabled]="action.disabled || isSubmitting()"
|
|
2797
|
+
[text]="(action.title | translate | async)!"
|
|
2798
|
+
[look]="'solid'"
|
|
2799
|
+
[color]="action.color"
|
|
2800
|
+
(onClick)="executeAction(action)"
|
|
2801
|
+
>
|
|
2802
|
+
@if (isFormLoading()) {
|
|
2803
|
+
<ax-loading></ax-loading>
|
|
2804
|
+
}
|
|
2805
|
+
@if (action.icon) {
|
|
2806
|
+
<ax-prefix>
|
|
2807
|
+
<ax-icon [icon]="action.icon"></ax-icon>
|
|
2808
|
+
</ax-prefix>
|
|
2809
|
+
}
|
|
2810
|
+
</ax-button>
|
|
2811
|
+
}
|
|
2812
|
+
<axp-component-slot name="dialog-footer-suffix" [context]="context()"></axp-component-slot>
|
|
2813
|
+
</ax-suffix>
|
|
2814
|
+
</ax-footer>
|
|
2815
|
+
}
|
|
2816
|
+
`, 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 }); }
|
|
2396
2817
|
}
|
|
2397
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
2818
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPDialogRendererComponent, decorators: [{
|
|
2398
2819
|
type: Component,
|
|
2399
2820
|
args: [{
|
|
2400
2821
|
selector: 'axp-dialog-renderer',
|
|
@@ -2406,9 +2827,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImpo
|
|
|
2406
2827
|
AXDecoratorModule,
|
|
2407
2828
|
AXLoadingModule,
|
|
2408
2829
|
AXTranslationModule,
|
|
2830
|
+
AXPComponentSlotModule,
|
|
2409
2831
|
],
|
|
2832
|
+
providers: [AXPContextStore],
|
|
2833
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
2410
2834
|
template: `
|
|
2835
|
+
<axp-component-slot name="dialog-header" [context]="context()"></axp-component-slot>
|
|
2411
2836
|
<div class="ax-p-4">
|
|
2837
|
+
<!-- @if (config.message) {
|
|
2838
|
+
<p class="ax-mb-4 ax-leading-relaxed">{{ config.message | translate | async }}</p>
|
|
2839
|
+
} -->
|
|
2412
2840
|
<axp-layout-renderer
|
|
2413
2841
|
[layout]="config.definition"
|
|
2414
2842
|
[context]="context()"
|
|
@@ -2418,48 +2846,57 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImpo
|
|
|
2418
2846
|
</axp-layout-renderer>
|
|
2419
2847
|
</div>
|
|
2420
2848
|
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
@for (action of footerSuffixActions(); track $index) {
|
|
2439
|
-
<ax-button
|
|
2440
|
-
[disabled]="action.disabled || isSubmitting()"
|
|
2441
|
-
[text]="(action.title | translate | async)!"
|
|
2442
|
-
[look]="'solid'"
|
|
2443
|
-
[color]="action.color"
|
|
2444
|
-
(onClick)="executeAction(action)"
|
|
2445
|
-
>
|
|
2446
|
-
@if (isFormLoading()) {
|
|
2447
|
-
<ax-loading></ax-loading>
|
|
2448
|
-
}
|
|
2449
|
-
@if (action.icon) {
|
|
2849
|
+
<!-- Custom footer slot: if it has content, default footer is hidden -->
|
|
2850
|
+
<axp-component-slot name="dialog-footer" #footerSlot="slot" [context]="context()"></axp-component-slot>
|
|
2851
|
+
@if (footerSlot.isEmpty()) {
|
|
2852
|
+
<ax-footer>
|
|
2853
|
+
<ax-prefix>
|
|
2854
|
+
<axp-component-slot name="dialog-footer-prefix" [context]="context()"></axp-component-slot>
|
|
2855
|
+
@for (action of footerPrefixActions(); track $index) {
|
|
2856
|
+
<ax-button
|
|
2857
|
+
[disabled]="action.disabled || isFormLoading()"
|
|
2858
|
+
[text]="(action.title | translate | async)!"
|
|
2859
|
+
[look]="'outline'"
|
|
2860
|
+
[color]="action.color"
|
|
2861
|
+
(onClick)="executeAction(action)"
|
|
2862
|
+
>
|
|
2863
|
+
@if (isFormLoading()) {
|
|
2864
|
+
<ax-loading></ax-loading>
|
|
2865
|
+
}
|
|
2450
2866
|
<ax-prefix>
|
|
2451
|
-
|
|
2867
|
+
@if (action.icon) {
|
|
2868
|
+
<ax-icon [icon]="action.icon"></ax-icon>
|
|
2869
|
+
}
|
|
2452
2870
|
</ax-prefix>
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2871
|
+
</ax-button>
|
|
2872
|
+
}
|
|
2873
|
+
</ax-prefix>
|
|
2874
|
+
<ax-suffix>
|
|
2875
|
+
@for (action of footerSuffixActions(); track $index) {
|
|
2876
|
+
<ax-button
|
|
2877
|
+
[disabled]="action.disabled || isSubmitting()"
|
|
2878
|
+
[text]="(action.title | translate | async)!"
|
|
2879
|
+
[look]="'solid'"
|
|
2880
|
+
[color]="action.color"
|
|
2881
|
+
(onClick)="executeAction(action)"
|
|
2882
|
+
>
|
|
2883
|
+
@if (isFormLoading()) {
|
|
2884
|
+
<ax-loading></ax-loading>
|
|
2885
|
+
}
|
|
2886
|
+
@if (action.icon) {
|
|
2887
|
+
<ax-prefix>
|
|
2888
|
+
<ax-icon [icon]="action.icon"></ax-icon>
|
|
2889
|
+
</ax-prefix>
|
|
2890
|
+
}
|
|
2891
|
+
</ax-button>
|
|
2892
|
+
}
|
|
2893
|
+
<axp-component-slot name="dialog-footer-suffix" [context]="context()"></axp-component-slot>
|
|
2894
|
+
</ax-suffix>
|
|
2895
|
+
</ax-footer>
|
|
2896
|
+
}
|
|
2458
2897
|
`,
|
|
2459
2898
|
}]
|
|
2460
|
-
}], propDecorators: {
|
|
2461
|
-
type: Input
|
|
2462
|
-
}], result: [{
|
|
2899
|
+
}], propDecorators: { result: [{
|
|
2463
2900
|
type: Output
|
|
2464
2901
|
}], layoutRenderer: [{ type: i0.ViewChild, args: [i0.forwardRef(() => AXPLayoutRendererComponent), { isSignal: true }] }] } });
|
|
2465
2902
|
|
|
@@ -2468,9 +2905,190 @@ var dialogRenderer_component = /*#__PURE__*/Object.freeze({
|
|
|
2468
2905
|
AXPDialogRendererComponent: AXPDialogRendererComponent
|
|
2469
2906
|
});
|
|
2470
2907
|
|
|
2908
|
+
//#region ---- Imports ----
|
|
2909
|
+
/**
|
|
2910
|
+
* `customWidget` only forwards keys from its options bag into the built node via `addSingleWidget`.
|
|
2911
|
+
* Designer / configurator persist `defaultValue` (and other extended fields) on the widget node root;
|
|
2912
|
+
* spreading `options` alone drops them, so preview never applied defaults.
|
|
2913
|
+
*/
|
|
2914
|
+
/**
|
|
2915
|
+
* Widget options are sometimes persisted with an extra nesting (`options.options`) when context
|
|
2916
|
+
* was merged incorrectly. Flatten so list/data-source resolution sees `dataSource` at the top level.
|
|
2917
|
+
*/
|
|
2918
|
+
function optionsBagForPreview(node) {
|
|
2919
|
+
const raw = (node.options ?? {});
|
|
2920
|
+
const inner = raw['options'];
|
|
2921
|
+
if (inner !== undefined && typeof inner === 'object' && !Array.isArray(inner)) {
|
|
2922
|
+
const { options: _nested, ...rest } = raw;
|
|
2923
|
+
return { ...rest, ...inner };
|
|
2924
|
+
}
|
|
2925
|
+
return { ...raw };
|
|
2926
|
+
}
|
|
2927
|
+
function extendedNodePropsForPreview(node) {
|
|
2928
|
+
const out = {};
|
|
2929
|
+
if (node.defaultValue !== undefined) {
|
|
2930
|
+
out['defaultValue'] = node.defaultValue;
|
|
2931
|
+
}
|
|
2932
|
+
if (node.triggers !== undefined) {
|
|
2933
|
+
out['triggers'] = node.triggers;
|
|
2934
|
+
}
|
|
2935
|
+
if (node.meta !== undefined) {
|
|
2936
|
+
out['meta'] = node.meta;
|
|
2937
|
+
}
|
|
2938
|
+
if (node.valueTransforms !== undefined) {
|
|
2939
|
+
out['valueTransforms'] = node.valueTransforms;
|
|
2940
|
+
}
|
|
2941
|
+
if (node.visible !== undefined) {
|
|
2942
|
+
out['visible'] = node.visible;
|
|
2943
|
+
}
|
|
2944
|
+
if (node.mode !== undefined) {
|
|
2945
|
+
out['mode'] = node.mode;
|
|
2946
|
+
}
|
|
2947
|
+
if (node.children !== undefined) {
|
|
2948
|
+
out['children'] = node.children;
|
|
2949
|
+
}
|
|
2950
|
+
return out;
|
|
2951
|
+
}
|
|
2952
|
+
//#endregion
|
|
2953
|
+
//#region ---- Command ----
|
|
2954
|
+
/**
|
|
2955
|
+
* Opens a dialog that previews a widget configuration (same behavior as the preview button on
|
|
2956
|
+
* `axp-widget-field-configurator`). Invoked from that component and from entity list actions.
|
|
2957
|
+
*/
|
|
2958
|
+
class AXPPreviewWidgetFieldCommand {
|
|
2959
|
+
constructor() {
|
|
2960
|
+
this.formBuilderService = inject(AXPLayoutBuilderService);
|
|
2961
|
+
this.widgetRegistry = inject(AXPWidgetRegistryService);
|
|
2962
|
+
this.translationService = inject(AXTranslationService);
|
|
2963
|
+
this.crudService = inject(AXP_ENTITY_DEFINITION_CRUD_SERVICE, { optional: true });
|
|
2964
|
+
}
|
|
2965
|
+
async execute(input) {
|
|
2966
|
+
try {
|
|
2967
|
+
const merged = this.mergeInvocation(input);
|
|
2968
|
+
const currentWidget = this.normalizeWidget(merged['widget'] ?? merged['interface']);
|
|
2969
|
+
if (!currentWidget?.type) {
|
|
2970
|
+
return {
|
|
2971
|
+
success: false,
|
|
2972
|
+
message: {
|
|
2973
|
+
text: (await this.translationService.translateAsync('@general:messages.invalid-data')) || 'Invalid data',
|
|
2974
|
+
},
|
|
2975
|
+
};
|
|
2976
|
+
}
|
|
2977
|
+
const fieldName = String(merged['fieldName'] ?? merged['name'] ?? 'Field');
|
|
2978
|
+
const rawTitle = (merged['fieldTitle'] ?? merged['title']);
|
|
2979
|
+
const fieldTitleLabel = this.resolveFieldTitleLabel(rawTitle, fieldName);
|
|
2980
|
+
const dialogTitle = (await this.resolveWidgetDisplayTitle(currentWidget.type)) || currentWidget.type || fieldTitleLabel;
|
|
2981
|
+
const previewWidgetOptions = {
|
|
2982
|
+
...optionsBagForPreview(currentWidget),
|
|
2983
|
+
name: fieldName,
|
|
2984
|
+
...extendedNodePropsForPreview(currentWidget),
|
|
2985
|
+
};
|
|
2986
|
+
const dialogOutcome = await this.formBuilderService
|
|
2987
|
+
.create()
|
|
2988
|
+
.dialog((dialog) => {
|
|
2989
|
+
dialog
|
|
2990
|
+
.setTitle(dialogTitle)
|
|
2991
|
+
.setSize('md')
|
|
2992
|
+
.setCloseButton(true)
|
|
2993
|
+
.setContext({})
|
|
2994
|
+
.content((layoutBuilder) => {
|
|
2995
|
+
layoutBuilder.formField(fieldTitleLabel, (formField) => {
|
|
2996
|
+
formField.customWidget(currentWidget.type, previewWidgetOptions);
|
|
2997
|
+
});
|
|
2998
|
+
})
|
|
2999
|
+
.setActions((actions) => actions.cancel('@general:actions.close.title'));
|
|
3000
|
+
})
|
|
3001
|
+
.show();
|
|
3002
|
+
const cancelled = this.isCancelDialogOutcome(dialogOutcome);
|
|
3003
|
+
return {
|
|
3004
|
+
success: !cancelled,
|
|
3005
|
+
message: { text: '' },
|
|
3006
|
+
};
|
|
3007
|
+
}
|
|
3008
|
+
catch (error) {
|
|
3009
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
3010
|
+
return {
|
|
3011
|
+
success: false,
|
|
3012
|
+
message: { text: message },
|
|
3013
|
+
};
|
|
3014
|
+
}
|
|
3015
|
+
}
|
|
3016
|
+
mergeInvocation(input) {
|
|
3017
|
+
const contextOptions = input.__context__?.options;
|
|
3018
|
+
const ctxData = input.__context__?.data;
|
|
3019
|
+
const { __context__: _ctx, ...rest } = input;
|
|
3020
|
+
return {
|
|
3021
|
+
...(ctxData ?? {}),
|
|
3022
|
+
...(contextOptions ?? {}),
|
|
3023
|
+
...rest,
|
|
3024
|
+
};
|
|
3025
|
+
}
|
|
3026
|
+
normalizeWidget(raw) {
|
|
3027
|
+
if (raw == null)
|
|
3028
|
+
return null;
|
|
3029
|
+
if (typeof raw === 'string') {
|
|
3030
|
+
const t = raw.trim();
|
|
3031
|
+
return t ? { type: t, options: {} } : null;
|
|
3032
|
+
}
|
|
3033
|
+
if (typeof raw === 'object' && !Array.isArray(raw) && 'type' in raw) {
|
|
3034
|
+
const w = raw;
|
|
3035
|
+
return w.type ? cloneDeep(w) : null;
|
|
3036
|
+
}
|
|
3037
|
+
return null;
|
|
3038
|
+
}
|
|
3039
|
+
resolveFieldTitleLabel(raw, fallback) {
|
|
3040
|
+
let source = fallback;
|
|
3041
|
+
if (raw !== undefined && raw !== null) {
|
|
3042
|
+
if (typeof raw === 'string') {
|
|
3043
|
+
if (raw.trim() !== '') {
|
|
3044
|
+
source = raw;
|
|
3045
|
+
}
|
|
3046
|
+
}
|
|
3047
|
+
else if (typeof raw === 'object' && !Array.isArray(raw) && Object.keys(raw).length > 0) {
|
|
3048
|
+
source = raw;
|
|
3049
|
+
}
|
|
3050
|
+
}
|
|
3051
|
+
return this.translationService.resolve(source);
|
|
3052
|
+
}
|
|
3053
|
+
isCancelDialogOutcome(outcome) {
|
|
3054
|
+
if (outcome == null) {
|
|
3055
|
+
return false;
|
|
3056
|
+
}
|
|
3057
|
+
const ref = outcome;
|
|
3058
|
+
if (typeof ref.action !== 'function') {
|
|
3059
|
+
return false;
|
|
3060
|
+
}
|
|
3061
|
+
return ref.action() === 'cancel';
|
|
3062
|
+
}
|
|
3063
|
+
async resolveWidgetDisplayTitle(widgetType) {
|
|
3064
|
+
const crud = this.crudService;
|
|
3065
|
+
if (crud) {
|
|
3066
|
+
const interfaces = await crud.listInterfaces();
|
|
3067
|
+
const iface = interfaces.find((d) => d.name === widgetType);
|
|
3068
|
+
return iface?.title ?? iface?.name;
|
|
3069
|
+
}
|
|
3070
|
+
const config = this.widgetRegistry.getOptional(widgetType);
|
|
3071
|
+
if (!config) {
|
|
3072
|
+
return undefined;
|
|
3073
|
+
}
|
|
3074
|
+
const resolved = this.translationService.resolve(config.title);
|
|
3075
|
+
return resolved || undefined;
|
|
3076
|
+
}
|
|
3077
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPPreviewWidgetFieldCommand, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
3078
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPPreviewWidgetFieldCommand }); }
|
|
3079
|
+
}
|
|
3080
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPPreviewWidgetFieldCommand, decorators: [{
|
|
3081
|
+
type: Injectable
|
|
3082
|
+
}] });
|
|
3083
|
+
|
|
3084
|
+
var previewWidgetField_command = /*#__PURE__*/Object.freeze({
|
|
3085
|
+
__proto__: null,
|
|
3086
|
+
AXPPreviewWidgetFieldCommand: AXPPreviewWidgetFieldCommand
|
|
3087
|
+
});
|
|
3088
|
+
|
|
2471
3089
|
/**
|
|
2472
3090
|
* Generated bundle index. Do not edit.
|
|
2473
3091
|
*/
|
|
2474
3092
|
|
|
2475
|
-
export { AXPDialogRendererComponent, AXPLayoutBuilderService, AXPLayoutConversionService, AXPLayoutRendererComponent, LayoutBuilderModule };
|
|
3093
|
+
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, AXP_PREVIEW_WIDGET_FIELD_COMMAND_KEY, LayoutBuilderModule };
|
|
2476
3094
|
//# sourceMappingURL=acorex-platform-layout-builder.mjs.map
|