@acorex/platform 21.0.0-next.8 → 21.0.0-next.85
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/contracts/README.md +3 -0
- package/fesm2022/acorex-platform-auth.mjs +201 -81
- package/fesm2022/acorex-platform-auth.mjs.map +1 -1
- package/fesm2022/acorex-platform-common-common-settings.provider-Ytey9uhY.mjs +177 -0
- package/fesm2022/acorex-platform-common-common-settings.provider-Ytey9uhY.mjs.map +1 -0
- package/fesm2022/acorex-platform-common.mjs +4490 -880
- package/fesm2022/acorex-platform-common.mjs.map +1 -1
- package/fesm2022/acorex-platform-contracts.mjs +38 -0
- package/fesm2022/acorex-platform-contracts.mjs.map +1 -0
- package/fesm2022/acorex-platform-core.mjs +2612 -667
- 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 +1255 -209
- 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 +6493 -1920
- package/fesm2022/acorex-platform-layout-components.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-designer.mjs +612 -287
- package/fesm2022/acorex-platform-layout-designer.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-entity-attachments-page.component-CwG6n_Yb.mjs +376 -0
- package/fesm2022/acorex-platform-layout-entity-attachments-page.component-CwG6n_Yb.mjs.map +1 -0
- package/fesm2022/acorex-platform-layout-entity-file-list-popup.component--IeMZLIr.mjs +100 -0
- package/fesm2022/acorex-platform-layout-entity-file-list-popup.component--IeMZLIr.mjs.map +1 -0
- package/fesm2022/acorex-platform-layout-entity.mjs +22502 -9964
- package/fesm2022/acorex-platform-layout-entity.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-views.mjs +1395 -387
- package/fesm2022/acorex-platform-layout-views.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-widget-core.mjs +2226 -524
- 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-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-J0zcGKBX.mjs +116 -0
- package/fesm2022/acorex-platform-layout-widgets-repeater-widget-column.component-J0zcGKBX.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-DjpZU6gz.mjs} +6 -6
- package/fesm2022/acorex-platform-layout-widgets-tabular-data-edit-popup.component-DjpZU6gz.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-gX-3Kx9I.mjs} +5 -5
- package/fesm2022/acorex-platform-layout-widgets-tabular-data-view-popup.component-gX-3Kx9I.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 +18746 -16718
- 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-CWLfNqV0.mjs +160 -0
- package/fesm2022/acorex-platform-themes-default-entity-master-create-view.component-CWLfNqV0.mjs.map +1 -0
- package/fesm2022/acorex-platform-themes-default-entity-master-modify-view.component-C7cT82K2.mjs +120 -0
- package/fesm2022/acorex-platform-themes-default-entity-master-modify-view.component-C7cT82K2.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-Br9p5aXT.mjs} +21 -28
- package/fesm2022/acorex-platform-themes-default-entity-master-single-view.component-Br9p5aXT.mjs.map +1 -0
- package/fesm2022/acorex-platform-themes-default-error-401.component-B1nsdpTY.mjs +48 -0
- package/fesm2022/acorex-platform-themes-default-error-401.component-B1nsdpTY.mjs.map +1 -0
- package/fesm2022/acorex-platform-themes-default-error-404.component-D4UvRe8u.mjs +42 -0
- package/fesm2022/acorex-platform-themes-default-error-404.component-D4UvRe8u.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 +2337 -94
- 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-BjuzSe0T.mjs} +52 -33
- package/fesm2022/acorex-platform-themes-shared-settings.provider-BjuzSe0T.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 +793 -612
- package/fesm2022/acorex-platform-themes-shared.mjs.map +1 -1
- package/fesm2022/acorex-platform-workflow.mjs +997 -274
- package/fesm2022/acorex-platform-workflow.mjs.map +1 -1
- package/fesm2022/acorex-platform.mjs.map +1 -1
- package/package.json +41 -35
- package/{auth/index.d.ts → types/acorex-platform-auth.d.ts} +40 -19
- package/types/acorex-platform-common.d.ts +3673 -0
- package/types/acorex-platform-contracts.d.ts +940 -0
- package/{core/index.d.ts → types/acorex-platform-core.d.ts} +1029 -170
- 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} +262 -55
- package/types/acorex-platform-layout-components.d.ts +3329 -0
- package/{layout/designer/index.d.ts → types/acorex-platform-layout-designer.d.ts} +96 -18
- package/types/acorex-platform-layout-entity.d.ts +4512 -0
- package/{layout/views/index.d.ts → types/acorex-platform-layout-views.d.ts} +334 -91
- package/{layout/widget-core/index.d.ts → types/acorex-platform-layout-widget-core.d.ts} +438 -162
- package/{layout/widgets/index.d.ts → types/acorex-platform-layout-widgets.d.ts} +1152 -507
- 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} +268 -11
- package/{themes/shared/index.d.ts → types/acorex-platform-themes-shared.d.ts} +30 -2
- package/{workflow/index.d.ts → types/acorex-platform-workflow.d.ts} +436 -748
- package/common/index.d.ts +0 -2470
- 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-D3qZa5fM.mjs +0 -1542
- package/fesm2022/acorex-platform-themes-default-entity-master-list-view.component-D3qZa5fM.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 +0 -31
- 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 +0 -25
- 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-Dz0cylyQ.mjs +0 -65
- package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-column.component-Dz0cylyQ.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 -1670
- package/layout/entity/index.d.ts +0 -2294
- package/runtime/index.d.ts +0 -307
- /package/{index.d.ts → types/acorex-platform.d.ts} +0 -0
|
@@ -1,14 +1,19 @@
|
|
|
1
|
-
import * as
|
|
2
|
-
import { CommonModule } from '@angular/common';
|
|
1
|
+
import * as i5 from '@angular/common';
|
|
2
|
+
import { CommonModule, DOCUMENT } 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,
|
|
4
|
+
import { Injectable, inject, input, model, signal, computed, effect, output, viewChild, ChangeDetectionStrategy, Component, NgModule, EventEmitter, ElementRef, DestroyRef, Output } from '@angular/core';
|
|
5
|
+
import { provideCommandSetups, AXPCommandService } from '@acorex/platform/runtime';
|
|
5
6
|
import { AXPopupService } from '@acorex/components/popup';
|
|
7
|
+
import * as i4 from '@acorex/platform/core';
|
|
8
|
+
import { normalizeKeyboardShortcuts, AXPHookService, findOverlayContainerAncestor, getNestedVisibleOverlayPanes, getTopVisibleOverlayContainer, AXPExpressionEvaluatorService, AXPComponentSlotModule, AXPContextStore } from '@acorex/platform/core';
|
|
6
9
|
import * as i1 from '@acorex/platform/layout/widget-core';
|
|
7
|
-
import { AXPWidgetSerializationHelper, AXPWidgetContainerComponent, AXPPageStatus, AXPWidgetCoreModule } from '@acorex/platform/layout/widget-core';
|
|
8
|
-
import { cloneDeep, isNil, set, isEqual } from 'lodash-es';
|
|
10
|
+
import { AXPWidgetSerializationHelper, AXPWidgetContainerComponent, AXPPageStatus, AXPWidgetCoreModule, AXPWidgetRegistryService } from '@acorex/platform/layout/widget-core';
|
|
11
|
+
import { cloneDeep, isNil, set, isEqual, merge } from 'lodash-es';
|
|
9
12
|
import * as i2 from '@acorex/components/form';
|
|
10
13
|
import { AXFormComponent, AXFormModule } from '@acorex/components/form';
|
|
11
14
|
import { Subject, debounceTime, distinctUntilChanged, startWith } from 'rxjs';
|
|
15
|
+
import { AXP_PREVIEW_WIDGET_FIELD_COMMAND_KEY } from '@acorex/platform/contracts';
|
|
16
|
+
import { AXOverlayService } from '@acorex/cdk/overlay';
|
|
12
17
|
import * as i1$1 from '@acorex/components/button';
|
|
13
18
|
import { AXButtonModule } from '@acorex/components/button';
|
|
14
19
|
import * as i2$1 from '@acorex/components/decorators';
|
|
@@ -16,9 +21,194 @@ import { AXDecoratorModule } from '@acorex/components/decorators';
|
|
|
16
21
|
import * as i3 from '@acorex/components/loading';
|
|
17
22
|
import { AXLoadingModule } from '@acorex/components/loading';
|
|
18
23
|
import { AXBasePageComponent } from '@acorex/components/page';
|
|
19
|
-
import * as
|
|
20
|
-
import { AXTranslationModule } from '@acorex/core/translation';
|
|
21
|
-
import {
|
|
24
|
+
import * as i6 from '@acorex/core/translation';
|
|
25
|
+
import { AXTranslationService, AXTranslationModule } from '@acorex/core/translation';
|
|
26
|
+
import { AXPUnsavedChangesConfirmService } from '@acorex/platform/common';
|
|
27
|
+
import { AXP_ENTITY_DEFINITION_CRUD_SERVICE } from '@acorex/platform/domain';
|
|
28
|
+
|
|
29
|
+
//#region ---- Dialog Action Shortcut Utilities ----
|
|
30
|
+
const DEFAULT_CANCEL_DIALOG_ACTION_SHORTCUTS = ['Esc'];
|
|
31
|
+
const DEFAULT_SUBMIT_DIALOG_ACTION_SHORTCUTS = ['Enter', 'ctrl+s'];
|
|
32
|
+
const PRIMARY_DIALOG_ACTION_COMMANDS = new Set(['submit', 'create', 'entity-form-done']);
|
|
33
|
+
/**
|
|
34
|
+
* Parses a shortcut string such as `Enter`, `Escape`, or `ctrl+shift+s`.
|
|
35
|
+
*/
|
|
36
|
+
function parseDialogActionShortcut(shortcut) {
|
|
37
|
+
const parts = shortcut
|
|
38
|
+
.trim()
|
|
39
|
+
.toLowerCase()
|
|
40
|
+
.split('+')
|
|
41
|
+
.map((part) => part.trim())
|
|
42
|
+
.filter(Boolean);
|
|
43
|
+
const key = normalizeShortcutToken(parts.pop() ?? '');
|
|
44
|
+
return {
|
|
45
|
+
ctrl: parts.includes('ctrl') || parts.includes('control'),
|
|
46
|
+
shift: parts.includes('shift'),
|
|
47
|
+
alt: parts.includes('alt') || parts.includes('option'),
|
|
48
|
+
meta: parts.includes('meta') || parts.includes('cmd') || parts.includes('command'),
|
|
49
|
+
key,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Returns true when the keyboard event matches the given shortcut definition.
|
|
54
|
+
*/
|
|
55
|
+
function matchesDialogActionShortcut(event, shortcut) {
|
|
56
|
+
const parsed = parseDialogActionShortcut(shortcut);
|
|
57
|
+
if (parsed.ctrl !== event.ctrlKey) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
if (parsed.shift !== event.shiftKey) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
if (parsed.alt !== event.altKey) {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
if (parsed.meta !== event.metaKey) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
return normalizeShortcutKey(event) === normalizeShortcutToken(parsed.key);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Resolves footer action shortcuts: defaults when omitted, merge when extras are provided, none when `[]`.
|
|
73
|
+
*/
|
|
74
|
+
function resolveDialogActionShortcuts(defaults, overrides) {
|
|
75
|
+
if (overrides !== undefined) {
|
|
76
|
+
if (overrides.length === 0) {
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
return flattenDialogActionShortcutChords([...defaults, ...overrides]);
|
|
80
|
+
}
|
|
81
|
+
return flattenDialogActionShortcutChords(defaults);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Applies built-in footer shortcuts when actions are declared without `shortcuts`
|
|
85
|
+
* (e.g. workflow `show-layout-popup` raw action config).
|
|
86
|
+
*/
|
|
87
|
+
function resolveConfiguredFooterActionShortcuts(command, explicit) {
|
|
88
|
+
if (explicit !== undefined) {
|
|
89
|
+
return resolveDialogActionShortcuts([], explicit);
|
|
90
|
+
}
|
|
91
|
+
if (command === 'cancel') {
|
|
92
|
+
return resolveDialogActionShortcuts(DEFAULT_CANCEL_DIALOG_ACTION_SHORTCUTS, undefined);
|
|
93
|
+
}
|
|
94
|
+
if (command && PRIMARY_DIALOG_ACTION_COMMANDS.has(command)) {
|
|
95
|
+
return resolveDialogActionShortcuts(DEFAULT_SUBMIT_DIALOG_ACTION_SHORTCUTS, undefined);
|
|
96
|
+
}
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
function flattenDialogActionShortcutChords(shortcuts) {
|
|
100
|
+
const chords = normalizeKeyboardShortcuts(shortcuts).flatMap((shortcut) => shortcut.keys);
|
|
101
|
+
const deduped = dedupeDialogActionShortcuts(chords);
|
|
102
|
+
return deduped.length ? deduped : undefined;
|
|
103
|
+
}
|
|
104
|
+
function dedupeDialogActionShortcuts(shortcuts) {
|
|
105
|
+
const seen = new Set();
|
|
106
|
+
const result = [];
|
|
107
|
+
for (const shortcut of shortcuts) {
|
|
108
|
+
const token = normalizeShortcutToken(shortcut);
|
|
109
|
+
if (!token || seen.has(token)) {
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
seen.add(token);
|
|
113
|
+
result.push(shortcut.trim());
|
|
114
|
+
}
|
|
115
|
+
return result;
|
|
116
|
+
}
|
|
117
|
+
function normalizeShortcutToken(key) {
|
|
118
|
+
const normalized = key.trim().toLowerCase();
|
|
119
|
+
if (normalized === 'esc') {
|
|
120
|
+
return 'escape';
|
|
121
|
+
}
|
|
122
|
+
return normalized;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Whether a shortcut should fire given the current focus target.
|
|
126
|
+
* Enter without modifiers is suppressed in multiline / rich-text fields.
|
|
127
|
+
*/
|
|
128
|
+
function shouldTriggerDialogActionShortcut(event, shortcut) {
|
|
129
|
+
const parsed = parseDialogActionShortcut(shortcut);
|
|
130
|
+
const isPlainEnter = parsed.key === 'enter' && !parsed.ctrl && !parsed.shift && !parsed.alt && !parsed.meta;
|
|
131
|
+
if (!isPlainEnter) {
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
return !isKeyboardTargetMultilineEditable(event.target);
|
|
135
|
+
}
|
|
136
|
+
function normalizeShortcutKey(event) {
|
|
137
|
+
if (event.key === 'Enter') {
|
|
138
|
+
return 'enter';
|
|
139
|
+
}
|
|
140
|
+
if (event.key === 'Escape') {
|
|
141
|
+
return 'escape';
|
|
142
|
+
}
|
|
143
|
+
if (event.key === ' ') {
|
|
144
|
+
return 'space';
|
|
145
|
+
}
|
|
146
|
+
if (event.key.length === 1) {
|
|
147
|
+
return event.key.toLowerCase();
|
|
148
|
+
}
|
|
149
|
+
const codeMatch = event.code.match(/^Key([A-Z])$/);
|
|
150
|
+
if (codeMatch) {
|
|
151
|
+
return codeMatch[1].toLowerCase();
|
|
152
|
+
}
|
|
153
|
+
const digitMatch = event.code.match(/^Digit([0-9])$/);
|
|
154
|
+
if (digitMatch) {
|
|
155
|
+
return digitMatch[1];
|
|
156
|
+
}
|
|
157
|
+
return event.key.toLowerCase();
|
|
158
|
+
}
|
|
159
|
+
function isKeyboardTargetMultilineEditable(target) {
|
|
160
|
+
if (!(target instanceof HTMLElement)) {
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
if (target.isContentEditable) {
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
const textarea = target.closest('textarea');
|
|
167
|
+
if (textarea && !textarea.readOnly && !textarea.disabled) {
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
const richText = target.closest('[contenteditable="true"]');
|
|
171
|
+
return richText instanceof HTMLElement;
|
|
172
|
+
}
|
|
173
|
+
//#endregion
|
|
174
|
+
|
|
175
|
+
/** Fallback {@link AXPDialogRef} when the popup is dismissed without a footer action (e.g. header close). */
|
|
176
|
+
function createDismissedDialogRef(context = () => ({})) {
|
|
177
|
+
return {
|
|
178
|
+
close: () => { },
|
|
179
|
+
context,
|
|
180
|
+
action: () => 'cancel',
|
|
181
|
+
setLoading: () => { },
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
//#endregion
|
|
185
|
+
|
|
186
|
+
//#region ---- Imports ----
|
|
187
|
+
//#endregion
|
|
188
|
+
//#region ---- Before open ----
|
|
189
|
+
/**
|
|
190
|
+
* Runs after dialog options and context are prepared and **before** footer customization and popup open.
|
|
191
|
+
* Listeners may mutate {@link AXPLayoutBuilderDialogBeforeOpenPayload.context} by reference.
|
|
192
|
+
*/
|
|
193
|
+
const AXP_LAYOUT_BUILDER_DIALOG_BEFORE_OPEN_HOOK_KEY = 'layout-builder.dialog.before-open';
|
|
194
|
+
//#endregion
|
|
195
|
+
//#region ---- Footer actions ----
|
|
196
|
+
/**
|
|
197
|
+
* Runs after builder-defined footer actions exist and **before** the dialog opens.
|
|
198
|
+
* Listeners receive the live `actions.footer.prefix` / `suffix` arrays (same references as the dialog)
|
|
199
|
+
* so they may push, splice, filter, or replace items. They may also mutate {@link AXPLayoutBuilderDialogFooterPayload.context} by reference.
|
|
200
|
+
*/
|
|
201
|
+
const AXP_LAYOUT_BUILDER_DIALOG_CONFIG_HOOK_KEY = 'layout-builder.dialog.config';
|
|
202
|
+
//#endregion
|
|
203
|
+
//#region ---- Context updates (after open) ----
|
|
204
|
+
/**
|
|
205
|
+
* Runs whenever the dialog layout builder context changes (debounced upstream), **after** the popup is visible.
|
|
206
|
+
* Use for side effects that depend on live context (for example values updated by widgets after render).
|
|
207
|
+
* Payload mirrors `AXPDialogRendererComponent` semantics: {@link AXPLayoutBuilderDialogContextChangedPayload.getContext},
|
|
208
|
+
* {@link AXPLayoutBuilderDialogContextChangedPayload.patchContext}, and optional loading state.
|
|
209
|
+
*/
|
|
210
|
+
const AXP_LAYOUT_BUILDER_DIALOG_CONTEXT_CHANGED_HOOK_KEY = 'layout-builder.dialog.context-changed';
|
|
211
|
+
//#endregion
|
|
22
212
|
|
|
23
213
|
class AXPLayoutConversionService {
|
|
24
214
|
constructor() {
|
|
@@ -158,6 +348,10 @@ class AXPLayoutConversionService {
|
|
|
158
348
|
if (!editorWidget.mode) {
|
|
159
349
|
editorWidget.mode = fieldMode;
|
|
160
350
|
}
|
|
351
|
+
const hintOpts = field.description != null &&
|
|
352
|
+
(typeof field.description !== 'string' || field.description.trim().length > 0)
|
|
353
|
+
? { hint: field.description, hintDisplayMode: 'note' }
|
|
354
|
+
: {};
|
|
161
355
|
return {
|
|
162
356
|
type: 'form-field',
|
|
163
357
|
name: field.path,
|
|
@@ -165,8 +359,8 @@ class AXPLayoutConversionService {
|
|
|
165
359
|
options: {
|
|
166
360
|
label: field.title,
|
|
167
361
|
badge: field.badge,
|
|
168
|
-
description: field.description,
|
|
169
362
|
showLabel: true,
|
|
363
|
+
...hintOpts,
|
|
170
364
|
},
|
|
171
365
|
children: [editorWidget], // The editor widget becomes a child of form-field
|
|
172
366
|
};
|
|
@@ -209,7 +403,7 @@ class AXPLayoutConversionService {
|
|
|
209
403
|
path: formFieldNode.name || editorWidget.name || `field-${Date.now()}`,
|
|
210
404
|
title: formFieldNode.options?.['label'],
|
|
211
405
|
badge: formFieldNode.options?.['badge'],
|
|
212
|
-
description: formFieldNode.options?.['
|
|
406
|
+
description: formFieldNode.options?.['hint'],
|
|
213
407
|
widget: editorWidget,
|
|
214
408
|
mode: formFieldNode.mode,
|
|
215
409
|
};
|
|
@@ -222,9 +416,16 @@ class AXPLayoutConversionService {
|
|
|
222
416
|
const keyParts = [];
|
|
223
417
|
keyParts.push(`groups:${formDefinition.groups.length}`);
|
|
224
418
|
formDefinition.groups.forEach((group, groupIndex) => {
|
|
225
|
-
|
|
419
|
+
// Include group.mode so view vs edit (or mixed) layouts do not share a cached widget tree.
|
|
420
|
+
const groupModePart = group.mode ?? '_';
|
|
421
|
+
keyParts.push(`g${groupIndex}:${group.name}:${group.parameters.length}:${groupModePart}`);
|
|
422
|
+
keyParts.push(`gL${groupIndex}:${JSON.stringify(group.title ?? null)}:${JSON.stringify(group.description ?? null)}`);
|
|
226
423
|
group.parameters.forEach((param, paramIndex) => {
|
|
227
|
-
|
|
424
|
+
// Field mode must be part of the key; otherwise metadata forms that only differ by
|
|
425
|
+
// view/edit (same paths and widget types) incorrectly reuse the first cached tree.
|
|
426
|
+
const fieldModePart = param.mode ?? '_';
|
|
427
|
+
keyParts.push(`p${groupIndex}.${paramIndex}:${param.path}:${param.widget.type}:${fieldModePart}`);
|
|
428
|
+
keyParts.push(`pL${groupIndex}.${paramIndex}:${JSON.stringify(param.title ?? null)}:${JSON.stringify(param.description ?? null)}:${JSON.stringify(param.badge ?? null)}`);
|
|
228
429
|
});
|
|
229
430
|
});
|
|
230
431
|
if (formDefinition.mode) {
|
|
@@ -285,16 +486,31 @@ class AXPLayoutConversionService {
|
|
|
285
486
|
}
|
|
286
487
|
return Math.abs(hash).toString(36); // Convert to base36 for shorter string
|
|
287
488
|
}
|
|
288
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
289
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
489
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLayoutConversionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
490
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLayoutConversionService, providedIn: 'root' }); }
|
|
290
491
|
}
|
|
291
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
492
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLayoutConversionService, decorators: [{
|
|
292
493
|
type: Injectable,
|
|
293
494
|
args: [{
|
|
294
495
|
providedIn: 'root',
|
|
295
496
|
}]
|
|
296
497
|
}] });
|
|
297
498
|
|
|
499
|
+
//#region ---- Dialog Close Confirmation Utilities ----
|
|
500
|
+
function normalizeDialogCloseConfirmation(value) {
|
|
501
|
+
if (value === undefined || value === false) {
|
|
502
|
+
return undefined;
|
|
503
|
+
}
|
|
504
|
+
if (value === true) {
|
|
505
|
+
return { enabled: true };
|
|
506
|
+
}
|
|
507
|
+
if (value.enabled === false) {
|
|
508
|
+
return undefined;
|
|
509
|
+
}
|
|
510
|
+
return { enabled: true, ...value };
|
|
511
|
+
}
|
|
512
|
+
//#endregion
|
|
513
|
+
|
|
298
514
|
//#region ---- Inheritance Utilities ----
|
|
299
515
|
/**
|
|
300
516
|
* Resolves inherited properties from context and local values
|
|
@@ -361,17 +577,13 @@ function collectDefaultValues(node, context = {}, isTopLevel = true) {
|
|
|
361
577
|
const result = isTopLevel ? cloneDeep(context) : context;
|
|
362
578
|
// Check if this node has a defaultValue and a path
|
|
363
579
|
// 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;
|
|
580
|
+
const defaultValue = node.defaultValue !== undefined ? node.defaultValue : node.options?.defaultValue;
|
|
367
581
|
if (defaultValue !== undefined && !isNil(defaultValue) && node.path) {
|
|
368
582
|
// Check if path exists in context using lodash get equivalent check
|
|
369
583
|
const currentValue = getNestedValue(result, node.path);
|
|
370
584
|
if (currentValue === undefined) {
|
|
371
585
|
// 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);
|
|
586
|
+
const clonedValue = defaultValue instanceof Date ? new Date(defaultValue.getTime()) : cloneDeep(defaultValue);
|
|
375
587
|
set(result, node.path, clonedValue);
|
|
376
588
|
}
|
|
377
589
|
}
|
|
@@ -402,17 +614,18 @@ function getNestedValue(obj, path) {
|
|
|
402
614
|
class AXPLayoutBuilderService {
|
|
403
615
|
constructor() {
|
|
404
616
|
this.popupService = inject(AXPopupService);
|
|
617
|
+
this.hookService = inject(AXPHookService, { optional: true }) ?? undefined;
|
|
405
618
|
}
|
|
406
619
|
/**
|
|
407
620
|
* Create a new layout builder
|
|
408
621
|
*/
|
|
409
622
|
create() {
|
|
410
|
-
return new LayoutBuilder(this.popupService);
|
|
623
|
+
return new LayoutBuilder(this.popupService, this.hookService);
|
|
411
624
|
}
|
|
412
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
413
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
625
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLayoutBuilderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
626
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLayoutBuilderService, providedIn: 'root' }); }
|
|
414
627
|
}
|
|
415
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
628
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLayoutBuilderService, decorators: [{
|
|
416
629
|
type: Injectable,
|
|
417
630
|
args: [{
|
|
418
631
|
providedIn: 'root',
|
|
@@ -425,8 +638,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImpo
|
|
|
425
638
|
* Open/Closed: Extensible through container delegates
|
|
426
639
|
*/
|
|
427
640
|
class LayoutBuilder {
|
|
428
|
-
constructor(popupService) {
|
|
641
|
+
constructor(popupService, hookService) {
|
|
429
642
|
this.popupService = popupService;
|
|
643
|
+
this.hookService = hookService;
|
|
430
644
|
this.root = {
|
|
431
645
|
children: [],
|
|
432
646
|
mode: 'edit',
|
|
@@ -453,7 +667,19 @@ class LayoutBuilder {
|
|
|
453
667
|
if (delegate) {
|
|
454
668
|
delegate(container);
|
|
455
669
|
}
|
|
456
|
-
|
|
670
|
+
const built = container.build();
|
|
671
|
+
// Step/dialog content usually calls flex() once; replace the default empty flex root instead of nesting flex inside flex.
|
|
672
|
+
if (this.root.type === 'flex-layout' &&
|
|
673
|
+
(!this.root.children || this.root.children.length === 0) &&
|
|
674
|
+
!this.root.options) {
|
|
675
|
+
this.root = built;
|
|
676
|
+
}
|
|
677
|
+
else {
|
|
678
|
+
if (!this.root.children) {
|
|
679
|
+
this.root.children = [];
|
|
680
|
+
}
|
|
681
|
+
this.root.children.push(built);
|
|
682
|
+
}
|
|
457
683
|
return this;
|
|
458
684
|
}
|
|
459
685
|
panel(delegate) {
|
|
@@ -496,7 +722,7 @@ class LayoutBuilder {
|
|
|
496
722
|
if (!this.popupService) {
|
|
497
723
|
throw new Error('LayoutBuilder requires AXPopupService to create dialogs. Please inject it in the service constructor.');
|
|
498
724
|
}
|
|
499
|
-
const container = new DialogContainerBuilder(this.popupService);
|
|
725
|
+
const container = new DialogContainerBuilder(this.popupService, this.hookService);
|
|
500
726
|
if (delegate) {
|
|
501
727
|
delegate(container);
|
|
502
728
|
}
|
|
@@ -521,11 +747,21 @@ class LayoutBuilder {
|
|
|
521
747
|
return this;
|
|
522
748
|
}
|
|
523
749
|
build() {
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
mode:
|
|
750
|
+
const r = this.root;
|
|
751
|
+
const node = {
|
|
752
|
+
type: r.type,
|
|
753
|
+
...(r.mode !== undefined ? { mode: r.mode } : {}),
|
|
754
|
+
...(r.children !== undefined ? { children: r.children } : {}),
|
|
755
|
+
...(r.options !== undefined ? { options: r.options } : {}),
|
|
756
|
+
...(r.name !== undefined ? { name: r.name } : {}),
|
|
757
|
+
...(r.path !== undefined ? { path: r.path } : {}),
|
|
758
|
+
...(r.visible !== undefined ? { visible: r.visible } : {}),
|
|
759
|
+
...(r.defaultValue !== undefined ? { defaultValue: r.defaultValue } : {}),
|
|
760
|
+
...(r.triggers !== undefined ? { triggers: r.triggers } : {}),
|
|
761
|
+
...(r.meta !== undefined ? { meta: r.meta } : {}),
|
|
762
|
+
...(r.valueTransforms !== undefined ? { valueTransforms: r.valueTransforms } : {}),
|
|
528
763
|
};
|
|
764
|
+
return node;
|
|
529
765
|
}
|
|
530
766
|
/**
|
|
531
767
|
* Converts the built widget node to JSON string
|
|
@@ -594,6 +830,7 @@ class BaseContainerBuilder {
|
|
|
594
830
|
'number-editor',
|
|
595
831
|
'select-editor',
|
|
596
832
|
'lookup-editor',
|
|
833
|
+
'entity-definition-provider-editor',
|
|
597
834
|
'selection-list-editor',
|
|
598
835
|
'date-time-editor',
|
|
599
836
|
'toggle-editor',
|
|
@@ -696,24 +933,30 @@ class BaseContainerMixin extends BaseContainerBuilder {
|
|
|
696
933
|
class LayoutContainerMixin extends BaseContainerMixin {
|
|
697
934
|
layout(value) {
|
|
698
935
|
// Map layout intent to grid item sizing so containers like `form-field`
|
|
699
|
-
// can span multiple columns inside grid/fieldset layouts.
|
|
936
|
+
// can span multiple columns/rows inside grid/fieldset layouts.
|
|
700
937
|
if (!this.containerState.options)
|
|
701
938
|
this.containerState.options = {};
|
|
702
939
|
if (typeof value === 'number') {
|
|
703
|
-
// Direct numeric shorthand → colSpan
|
|
704
940
|
this.containerState.options.colSpan = value;
|
|
705
941
|
}
|
|
706
942
|
else if (value) {
|
|
707
|
-
// Try to extract a reasonable colSpan from breakpoint positions
|
|
708
943
|
const positions = value.positions;
|
|
709
944
|
if (positions) {
|
|
710
|
-
const
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
945
|
+
const placement = positions?.lg ?? positions?.xl ?? positions?.xxl ?? positions?.md ?? positions?.sm;
|
|
946
|
+
if (placement) {
|
|
947
|
+
const opts = this.containerState.options;
|
|
948
|
+
if (placement.colSpan != null)
|
|
949
|
+
opts.colSpan = placement.colSpan;
|
|
950
|
+
if (placement.colStart != null)
|
|
951
|
+
opts.colStart = placement.colStart;
|
|
952
|
+
if (placement.colEnd != null)
|
|
953
|
+
opts.colEnd = placement.colEnd;
|
|
954
|
+
if (placement.rowSpan != null)
|
|
955
|
+
opts.rowSpan = placement.rowSpan;
|
|
956
|
+
if (placement.rowStart != null)
|
|
957
|
+
opts.rowStart = placement.rowStart;
|
|
958
|
+
if (placement.rowEnd != null)
|
|
959
|
+
opts.rowEnd = placement.rowEnd;
|
|
717
960
|
}
|
|
718
961
|
}
|
|
719
962
|
}
|
|
@@ -932,6 +1175,7 @@ class WidgetContainerMixin extends ChildContainerMixin {
|
|
|
932
1175
|
'number-editor',
|
|
933
1176
|
'select-editor',
|
|
934
1177
|
'lookup-editor',
|
|
1178
|
+
'entity-definition-provider-editor',
|
|
935
1179
|
'selection-list-editor',
|
|
936
1180
|
'date-time-editor',
|
|
937
1181
|
'toggle-editor',
|
|
@@ -987,12 +1231,73 @@ class FlexContainerBuilder extends WidgetContainerMixin {
|
|
|
987
1231
|
* Grid Container Builder - Liskov Substitution Principle
|
|
988
1232
|
* Extends WidgetContainerMixin to inherit all common functionality
|
|
989
1233
|
*/
|
|
1234
|
+
/**
|
|
1235
|
+
* Extracts flat grid-item options from AXPGridLayoutOptions for grid-item-layout widget.
|
|
1236
|
+
* Uses first available breakpoint (lg, xl, md, sm).
|
|
1237
|
+
*/
|
|
1238
|
+
/**
|
|
1239
|
+
* Deep-merges grid breakpoint buckets so sequential fluent calls (e.g. setColumns then setGap)
|
|
1240
|
+
* do not wipe sibling keys under options.grid.default.
|
|
1241
|
+
*/
|
|
1242
|
+
function mergeAXPGridContainerOptions(prev, patch) {
|
|
1243
|
+
const next = { ...(prev ?? {}), ...patch };
|
|
1244
|
+
if (prev?.grid?.default || patch.grid?.default) {
|
|
1245
|
+
next.grid = {
|
|
1246
|
+
...(prev?.grid ?? {}),
|
|
1247
|
+
...(patch.grid ?? {}),
|
|
1248
|
+
default: {
|
|
1249
|
+
...(prev?.grid?.default ?? {}),
|
|
1250
|
+
...(patch.grid?.default ?? {}),
|
|
1251
|
+
},
|
|
1252
|
+
};
|
|
1253
|
+
}
|
|
1254
|
+
return next;
|
|
1255
|
+
}
|
|
1256
|
+
function toGridItemOptions(layoutOptions) {
|
|
1257
|
+
if (!layoutOptions?.positions)
|
|
1258
|
+
return { colSpan: 12 };
|
|
1259
|
+
const positions = layoutOptions.positions;
|
|
1260
|
+
const placement = positions['lg'] ?? positions['xl'] ?? positions['xxl'] ?? positions['md'] ?? positions['sm'];
|
|
1261
|
+
if (!placement)
|
|
1262
|
+
return { colSpan: 12 };
|
|
1263
|
+
const opts = {};
|
|
1264
|
+
if (placement['colSpan'] != null)
|
|
1265
|
+
opts['colSpan'] = placement['colSpan'];
|
|
1266
|
+
if (placement['colStart'] != null)
|
|
1267
|
+
opts['colStart'] = placement['colStart'];
|
|
1268
|
+
if (placement['colEnd'] != null)
|
|
1269
|
+
opts['colEnd'] = placement['colEnd'];
|
|
1270
|
+
if (placement['rowSpan'] != null)
|
|
1271
|
+
opts['rowSpan'] = placement['rowSpan'];
|
|
1272
|
+
if (placement['rowStart'] != null)
|
|
1273
|
+
opts['rowStart'] = placement['rowStart'];
|
|
1274
|
+
if (placement['rowEnd'] != null)
|
|
1275
|
+
opts['rowEnd'] = placement['rowEnd'];
|
|
1276
|
+
if (Object.keys(opts).length === 0)
|
|
1277
|
+
opts['colSpan'] = 12;
|
|
1278
|
+
return opts;
|
|
1279
|
+
}
|
|
990
1280
|
class GridContainerBuilder extends WidgetContainerMixin {
|
|
991
1281
|
constructor() {
|
|
992
1282
|
super('grid-layout');
|
|
993
1283
|
}
|
|
994
1284
|
setOptions(options) {
|
|
995
|
-
this.containerState.options =
|
|
1285
|
+
this.containerState.options = mergeAXPGridContainerOptions(this.containerState.options, options);
|
|
1286
|
+
return this;
|
|
1287
|
+
}
|
|
1288
|
+
item(layoutOptions, delegate) {
|
|
1289
|
+
const fieldset = new FieldsetContainerBuilder();
|
|
1290
|
+
fieldset.withInheritanceContext(this.inheritanceContext);
|
|
1291
|
+
delegate(fieldset);
|
|
1292
|
+
const fieldsetNode = fieldset.build();
|
|
1293
|
+
const gridItemOptions = toGridItemOptions(layoutOptions);
|
|
1294
|
+
const gridItemNode = {
|
|
1295
|
+
type: 'grid-item-layout',
|
|
1296
|
+
options: gridItemOptions,
|
|
1297
|
+
children: [fieldsetNode],
|
|
1298
|
+
};
|
|
1299
|
+
this.ensureChildren();
|
|
1300
|
+
this.containerState.children.push(gridItemNode);
|
|
996
1301
|
return this;
|
|
997
1302
|
}
|
|
998
1303
|
// Individual fluent methods for Grid
|
|
@@ -1164,10 +1469,34 @@ class FormFieldBuilder extends LayoutContainerMixin {
|
|
|
1164
1469
|
child.type(type);
|
|
1165
1470
|
child.name(finalName);
|
|
1166
1471
|
child.path(widgetPath);
|
|
1167
|
-
//
|
|
1168
|
-
const { name: _, ...cleanOptions } = (options || {});
|
|
1472
|
+
// Extract extended properties from options (triggers, meta, valueTransforms, mode, visible, defaultValue)
|
|
1473
|
+
const { name: _, triggers, meta, valueTransforms, mode: extendedMode, visible: extendedVisible, defaultValue: extendedDefaultValue, children: extendedChildren, ...cleanOptions } = (options || {});
|
|
1169
1474
|
child.withInheritanceContext(this.inheritanceContext);
|
|
1170
1475
|
child.options(cleanOptions);
|
|
1476
|
+
// Apply extended properties if provided
|
|
1477
|
+
if (extendedMode !== undefined) {
|
|
1478
|
+
child.mode(extendedMode);
|
|
1479
|
+
}
|
|
1480
|
+
if (extendedVisible !== undefined) {
|
|
1481
|
+
child.visible(extendedVisible);
|
|
1482
|
+
}
|
|
1483
|
+
if (extendedDefaultValue !== undefined) {
|
|
1484
|
+
child.defaultValue(extendedDefaultValue);
|
|
1485
|
+
}
|
|
1486
|
+
// Set triggers, meta, and valueTransforms directly on widgetState
|
|
1487
|
+
// These are part of AXPWidgetNode but not handled by WidgetBuilder methods
|
|
1488
|
+
if (triggers !== undefined) {
|
|
1489
|
+
child.widgetState.triggers = triggers;
|
|
1490
|
+
}
|
|
1491
|
+
if (meta !== undefined) {
|
|
1492
|
+
child.widgetState.meta = meta;
|
|
1493
|
+
}
|
|
1494
|
+
if (valueTransforms !== undefined) {
|
|
1495
|
+
child.widgetState.valueTransforms = valueTransforms;
|
|
1496
|
+
}
|
|
1497
|
+
if (extendedChildren !== undefined) {
|
|
1498
|
+
child.widgetState.children = extendedChildren;
|
|
1499
|
+
}
|
|
1171
1500
|
// IMPORTANT: Store the widget builder, don't build it yet!
|
|
1172
1501
|
// This allows properties set after this method (like disabled, readonly) to be applied
|
|
1173
1502
|
this.childWidget = child;
|
|
@@ -1399,7 +1728,7 @@ class ListWidgetBuilder extends WidgetContainerMixin {
|
|
|
1399
1728
|
* Uses composition instead of inheritance for cleaner separation
|
|
1400
1729
|
*/
|
|
1401
1730
|
class DialogContainerBuilder {
|
|
1402
|
-
constructor(popupService) {
|
|
1731
|
+
constructor(popupService, hookService) {
|
|
1403
1732
|
this.dialogState = {
|
|
1404
1733
|
type: 'flex-layout', // This will be overridden when content layout exists
|
|
1405
1734
|
children: [],
|
|
@@ -1422,6 +1751,7 @@ class DialogContainerBuilder {
|
|
|
1422
1751
|
else {
|
|
1423
1752
|
this.popupService = inject(AXPopupService);
|
|
1424
1753
|
}
|
|
1754
|
+
this.hookService = hookService ?? inject(AXPHookService, { optional: true }) ?? undefined;
|
|
1425
1755
|
}
|
|
1426
1756
|
setOptions(options) {
|
|
1427
1757
|
this.dialogState.dialogOptions = { ...this.dialogState.dialogOptions, ...options };
|
|
@@ -1461,6 +1791,18 @@ class DialogContainerBuilder {
|
|
|
1461
1791
|
}
|
|
1462
1792
|
return this;
|
|
1463
1793
|
}
|
|
1794
|
+
onAction(handler) {
|
|
1795
|
+
this.dialogState.dialogOptions ??= {
|
|
1796
|
+
title: '',
|
|
1797
|
+
size: 'md',
|
|
1798
|
+
closeButton: false,
|
|
1799
|
+
};
|
|
1800
|
+
this.dialogState.dialogOptions.onAction = handler;
|
|
1801
|
+
return this;
|
|
1802
|
+
}
|
|
1803
|
+
confirmCloseWhenDirty(options) {
|
|
1804
|
+
return this.setOptions({ confirmCloseWhenDirty: options ?? true });
|
|
1805
|
+
}
|
|
1464
1806
|
addCustomAction(action) {
|
|
1465
1807
|
// Add to actions based on position
|
|
1466
1808
|
const position = action.position || 'suffix';
|
|
@@ -1507,20 +1849,44 @@ class DialogContainerBuilder {
|
|
|
1507
1849
|
const dialogNode = this.build();
|
|
1508
1850
|
// Import the dialog renderer component dynamically
|
|
1509
1851
|
const { AXPDialogRendererComponent } = await Promise.resolve().then(function () { return dialogRenderer_component; });
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1852
|
+
this.dialogState.dialogOptions ??= {};
|
|
1853
|
+
if (this.dialogState.dialogOptions.context == null || typeof this.dialogState.dialogOptions.context !== 'object') {
|
|
1854
|
+
this.dialogState.dialogOptions.context = {};
|
|
1855
|
+
}
|
|
1856
|
+
const initialContext = this.dialogState.dialogOptions.context;
|
|
1857
|
+
this.dialogState.actions ??= { footer: { prefix: [], suffix: [] } };
|
|
1858
|
+
this.dialogState.actions.footer ??= { prefix: [], suffix: [] };
|
|
1859
|
+
this.dialogState.actions.footer.prefix ??= [];
|
|
1860
|
+
this.dialogState.actions.footer.suffix ??= [];
|
|
1861
|
+
const hookService = this.hookService;
|
|
1862
|
+
if (hookService) {
|
|
1863
|
+
const beforePayload = {
|
|
1864
|
+
context: initialContext,
|
|
1865
|
+
dialogOptions: this.dialogState.dialogOptions,
|
|
1866
|
+
};
|
|
1867
|
+
await hookService.runAsync(AXP_LAYOUT_BUILDER_DIALOG_BEFORE_OPEN_HOOK_KEY, beforePayload);
|
|
1868
|
+
}
|
|
1869
|
+
const confirmCloseWhenDirty = normalizeDialogCloseConfirmation(this.dialogState.dialogOptions?.confirmCloseWhenDirty);
|
|
1513
1870
|
// Create dialog configuration
|
|
1514
1871
|
const dialogConfig = {
|
|
1515
1872
|
title: this.dialogState.dialogOptions?.title || '',
|
|
1516
|
-
|
|
1517
|
-
|
|
1873
|
+
//TODO: why we need message?
|
|
1874
|
+
//message: this.dialogState.dialogOptions?.message,
|
|
1875
|
+
context: initialContext,
|
|
1518
1876
|
definition: dialogNode,
|
|
1877
|
+
metadata: this.dialogState.dialogOptions.metadata,
|
|
1519
1878
|
actions: this.dialogState.actions,
|
|
1879
|
+
onAction: this.dialogState.dialogOptions?.onAction,
|
|
1880
|
+
confirmCloseWhenDirty,
|
|
1520
1881
|
};
|
|
1882
|
+
//
|
|
1883
|
+
if (hookService) {
|
|
1884
|
+
await hookService.runAsync(AXP_LAYOUT_BUILDER_DIALOG_CONFIG_HOOK_KEY, dialogConfig);
|
|
1885
|
+
}
|
|
1521
1886
|
// The Promise resolves when user clicks an action button
|
|
1522
1887
|
return new Promise(async (resolve) => {
|
|
1523
|
-
|
|
1888
|
+
let flag = false;
|
|
1889
|
+
await this.popupService.open(AXPDialogRendererComponent, {
|
|
1524
1890
|
title: dialogConfig.title,
|
|
1525
1891
|
size: this.dialogState.dialogOptions?.size || 'md',
|
|
1526
1892
|
closeButton: this.dialogState.dialogOptions?.closeButton || false,
|
|
@@ -1529,11 +1895,14 @@ class DialogContainerBuilder {
|
|
|
1529
1895
|
data: {
|
|
1530
1896
|
config: dialogConfig,
|
|
1531
1897
|
callBack: (result) => {
|
|
1532
|
-
|
|
1898
|
+
flag = true;
|
|
1533
1899
|
resolve(result);
|
|
1534
1900
|
},
|
|
1535
1901
|
},
|
|
1536
1902
|
});
|
|
1903
|
+
if (!flag) {
|
|
1904
|
+
resolve(createDismissedDialogRef());
|
|
1905
|
+
}
|
|
1537
1906
|
});
|
|
1538
1907
|
}
|
|
1539
1908
|
}
|
|
@@ -1548,6 +1917,7 @@ class WidgetBuilder {
|
|
|
1548
1917
|
this.widgetState = {
|
|
1549
1918
|
type: 'widget',
|
|
1550
1919
|
options: {},
|
|
1920
|
+
children: [],
|
|
1551
1921
|
};
|
|
1552
1922
|
this.inheritanceContext = {};
|
|
1553
1923
|
if (name) {
|
|
@@ -1611,6 +1981,7 @@ class WidgetBuilder {
|
|
|
1611
1981
|
this.widgetState.options = {};
|
|
1612
1982
|
}
|
|
1613
1983
|
this.widgetState.options['visible'] = condition;
|
|
1984
|
+
this.widgetState.visible = condition;
|
|
1614
1985
|
this.inheritanceContext.visible = condition;
|
|
1615
1986
|
return this;
|
|
1616
1987
|
}
|
|
@@ -1642,6 +2013,10 @@ class WidgetBuilder {
|
|
|
1642
2013
|
this.inheritanceContext.direction = direction;
|
|
1643
2014
|
return this;
|
|
1644
2015
|
}
|
|
2016
|
+
children(children) {
|
|
2017
|
+
this.widgetState.children = children;
|
|
2018
|
+
return this;
|
|
2019
|
+
}
|
|
1645
2020
|
// Inheritance context methods
|
|
1646
2021
|
withInheritanceContext(context) {
|
|
1647
2022
|
this.inheritanceContext = mergeInheritanceContext(context);
|
|
@@ -1665,6 +2040,7 @@ class WidgetBuilder {
|
|
|
1665
2040
|
}
|
|
1666
2041
|
if (resolved.visible !== undefined) {
|
|
1667
2042
|
this.widgetState.options['visible'] = resolved.visible;
|
|
2043
|
+
this.widgetState.visible = resolved.visible;
|
|
1668
2044
|
}
|
|
1669
2045
|
if (context.defaultValue !== undefined) {
|
|
1670
2046
|
this.widgetState.defaultValue = context.defaultValue;
|
|
@@ -1675,14 +2051,29 @@ class WidgetBuilder {
|
|
|
1675
2051
|
return { ...this.inheritanceContext };
|
|
1676
2052
|
}
|
|
1677
2053
|
build() {
|
|
1678
|
-
|
|
2054
|
+
const node = {
|
|
1679
2055
|
name: this.widgetState.name,
|
|
1680
2056
|
type: this.widgetState.type,
|
|
1681
2057
|
options: this.widgetState.options,
|
|
1682
2058
|
mode: this.widgetState.mode,
|
|
1683
2059
|
path: this.widgetState.path,
|
|
1684
2060
|
defaultValue: this.widgetState.defaultValue,
|
|
2061
|
+
children: this.widgetState.children,
|
|
1685
2062
|
};
|
|
2063
|
+
// Add extended properties if they exist
|
|
2064
|
+
if (this.widgetState.triggers !== undefined) {
|
|
2065
|
+
node.triggers = this.widgetState.triggers;
|
|
2066
|
+
}
|
|
2067
|
+
if (this.widgetState.meta !== undefined) {
|
|
2068
|
+
node.meta = this.widgetState.meta;
|
|
2069
|
+
}
|
|
2070
|
+
if (this.widgetState.valueTransforms !== undefined) {
|
|
2071
|
+
node.valueTransforms = this.widgetState.valueTransforms;
|
|
2072
|
+
}
|
|
2073
|
+
if (this.widgetState.visible !== undefined) {
|
|
2074
|
+
node.visible = this.widgetState.visible;
|
|
2075
|
+
}
|
|
2076
|
+
return node;
|
|
1686
2077
|
}
|
|
1687
2078
|
}
|
|
1688
2079
|
//#region ---- Action Builder Implementation ----
|
|
@@ -1690,35 +2081,48 @@ class ActionBuilder {
|
|
|
1690
2081
|
constructor(dialogBuilder) {
|
|
1691
2082
|
this.dialogBuilder = dialogBuilder;
|
|
1692
2083
|
}
|
|
1693
|
-
cancel(text) {
|
|
2084
|
+
cancel(text, options) {
|
|
1694
2085
|
if (!this.dialogBuilder['dialogState'].actions.footer.suffix) {
|
|
1695
2086
|
this.dialogBuilder['dialogState'].actions.footer.suffix = [];
|
|
1696
2087
|
}
|
|
1697
2088
|
this.dialogBuilder['dialogState'].actions.footer.suffix.push({
|
|
1698
2089
|
title: text || '@general:actions.cancel.title',
|
|
1699
|
-
icon: 'fa-times',
|
|
1700
2090
|
color: 'default',
|
|
1701
2091
|
command: { name: 'cancel' },
|
|
2092
|
+
shortcuts: resolveDialogActionShortcuts(DEFAULT_CANCEL_DIALOG_ACTION_SHORTCUTS, options?.shortcuts),
|
|
1702
2093
|
});
|
|
1703
2094
|
return this;
|
|
1704
2095
|
}
|
|
1705
|
-
submit(text) {
|
|
2096
|
+
submit(text, options) {
|
|
1706
2097
|
if (!this.dialogBuilder['dialogState'].actions.footer.suffix) {
|
|
1707
2098
|
this.dialogBuilder['dialogState'].actions.footer.suffix = [];
|
|
1708
2099
|
}
|
|
1709
2100
|
this.dialogBuilder['dialogState'].actions.footer.suffix.push({
|
|
1710
2101
|
title: text || '@general:actions.submit.title',
|
|
1711
|
-
icon: 'fa-check',
|
|
1712
2102
|
color: 'primary',
|
|
1713
2103
|
command: { name: 'submit', options: { validate: true } },
|
|
2104
|
+
shortcuts: resolveDialogActionShortcuts(DEFAULT_SUBMIT_DIALOG_ACTION_SHORTCUTS, options?.shortcuts),
|
|
1714
2105
|
});
|
|
1715
2106
|
return this;
|
|
1716
2107
|
}
|
|
1717
|
-
custom(action) {
|
|
1718
|
-
|
|
1719
|
-
|
|
2108
|
+
custom(action, options) {
|
|
2109
|
+
const item = {
|
|
2110
|
+
...action,
|
|
2111
|
+
shortcuts: resolveDialogActionShortcuts(action.shortcuts ?? [], options?.shortcuts),
|
|
2112
|
+
};
|
|
2113
|
+
const position = item.position ?? 'suffix';
|
|
2114
|
+
if (position === 'prefix') {
|
|
2115
|
+
if (!this.dialogBuilder['dialogState'].actions.footer.prefix) {
|
|
2116
|
+
this.dialogBuilder['dialogState'].actions.footer.prefix = [];
|
|
2117
|
+
}
|
|
2118
|
+
this.dialogBuilder['dialogState'].actions.footer.prefix.push(item);
|
|
2119
|
+
}
|
|
2120
|
+
else {
|
|
2121
|
+
if (!this.dialogBuilder['dialogState'].actions.footer.suffix) {
|
|
2122
|
+
this.dialogBuilder['dialogState'].actions.footer.suffix = [];
|
|
2123
|
+
}
|
|
2124
|
+
this.dialogBuilder['dialogState'].actions.footer.suffix.push(item);
|
|
1720
2125
|
}
|
|
1721
|
-
this.dialogBuilder['dialogState'].actions.footer.suffix.push(action);
|
|
1722
2126
|
return this;
|
|
1723
2127
|
}
|
|
1724
2128
|
}
|
|
@@ -1867,22 +2271,27 @@ class AXPLayoutRendererComponent {
|
|
|
1867
2271
|
/**
|
|
1868
2272
|
* Form definition containing groups and fields OR widget tree
|
|
1869
2273
|
*/
|
|
1870
|
-
this.layout = input.required(...(ngDevMode ? [{ debugName: "layout" }] : []));
|
|
2274
|
+
this.layout = input.required(...(ngDevMode ? [{ debugName: "layout" }] : /* istanbul ignore next */ []));
|
|
1871
2275
|
/**
|
|
1872
2276
|
* Form context/model data
|
|
1873
2277
|
*/
|
|
1874
|
-
this.context = model({}, ...(ngDevMode ? [{ debugName: "context" }] : []));
|
|
2278
|
+
this.context = model({}, ...(ngDevMode ? [{ debugName: "context" }] : /* istanbul ignore next */ []));
|
|
1875
2279
|
/**
|
|
1876
2280
|
* Form appearance and density styling (normal, compact, spacious)
|
|
1877
2281
|
*/
|
|
1878
|
-
this.look = input('fieldset', ...(ngDevMode ? [{ debugName: "look" }] : []));
|
|
2282
|
+
this.look = input('fieldset', ...(ngDevMode ? [{ debugName: "look" }] : /* istanbul ignore next */ []));
|
|
1879
2283
|
/**
|
|
1880
2284
|
* Default form mode. Can be overridden by section/group and field.
|
|
1881
2285
|
*/
|
|
1882
|
-
this.mode = input('edit', ...(ngDevMode ? [{ debugName: "mode" }] : []));
|
|
2286
|
+
this.mode = input('edit', ...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
|
|
1883
2287
|
//#endregion
|
|
1884
2288
|
//#region ---- Widget Tree Conversion ----
|
|
1885
|
-
this.widgetTree = signal(null, ...(ngDevMode ? [{ debugName: "widgetTree" }] : []));
|
|
2289
|
+
this.widgetTree = signal(null, ...(ngDevMode ? [{ debugName: "widgetTree" }] : /* istanbul ignore next */ []));
|
|
2290
|
+
/**
|
|
2291
|
+
* Prefer explicit {@link AXPWidgetNode.mode} on the root node (e.g. dialog flex `mode('view')`)
|
|
2292
|
+
* so nested widgets resolve view vs edit correctly; fall back to the layout `mode` input.
|
|
2293
|
+
*/
|
|
2294
|
+
this.effectiveRenderMode = computed(() => this.widgetTree()?.mode ?? this.mode(), ...(ngDevMode ? [{ debugName: "effectiveRenderMode" }] : /* istanbul ignore next */ []));
|
|
1886
2295
|
/**
|
|
1887
2296
|
* Convert layout data to widget tree when inputs change
|
|
1888
2297
|
*/
|
|
@@ -1907,7 +2316,7 @@ class AXPLayoutRendererComponent {
|
|
|
1907
2316
|
if (!isEqual(prev, tree)) {
|
|
1908
2317
|
this.widgetTree.set(tree);
|
|
1909
2318
|
}
|
|
1910
|
-
}, ...(ngDevMode ? [{ debugName: "conversionEffect" }] : []));
|
|
2319
|
+
}, ...(ngDevMode ? [{ debugName: "conversionEffect" }] : /* istanbul ignore next */ []));
|
|
1911
2320
|
//#endregion
|
|
1912
2321
|
//#region ---- Outputs ----
|
|
1913
2322
|
/**
|
|
@@ -1920,12 +2329,12 @@ class AXPLayoutRendererComponent {
|
|
|
1920
2329
|
this.validityChange = output();
|
|
1921
2330
|
//#endregion
|
|
1922
2331
|
//#region ---- Properties ----
|
|
1923
|
-
this.form = viewChild(AXFormComponent, ...(ngDevMode ? [{ debugName: "form" }] : []));
|
|
1924
|
-
this.container = viewChild(AXPWidgetContainerComponent, ...(ngDevMode ? [{ debugName: "container" }] : []));
|
|
2332
|
+
this.form = viewChild(AXFormComponent, ...(ngDevMode ? [{ debugName: "form" }] : /* istanbul ignore next */ []));
|
|
2333
|
+
this.container = viewChild(AXPWidgetContainerComponent, ...(ngDevMode ? [{ debugName: "container" }] : /* istanbul ignore next */ []));
|
|
1925
2334
|
/**
|
|
1926
2335
|
* Internal context signal for reactivity
|
|
1927
2336
|
*/
|
|
1928
|
-
this.internalContext = signal({}, ...(ngDevMode ? [{ debugName: "internalContext" }] : []));
|
|
2337
|
+
this.internalContext = signal({}, ...(ngDevMode ? [{ debugName: "internalContext" }] : /* istanbul ignore next */ []));
|
|
1929
2338
|
/**
|
|
1930
2339
|
* Initial context for reset functionality
|
|
1931
2340
|
*/
|
|
@@ -1938,7 +2347,7 @@ class AXPLayoutRendererComponent {
|
|
|
1938
2347
|
this.#contextSyncEffect = effect(() => {
|
|
1939
2348
|
const ctx = this.context() ?? {};
|
|
1940
2349
|
this.contextUpdateSubject.next(ctx);
|
|
1941
|
-
}, ...(ngDevMode ? [{ debugName: "#contextSyncEffect" }] : []));
|
|
2350
|
+
}, ...(ngDevMode ? [{ debugName: "#contextSyncEffect" }] : /* istanbul ignore next */ []));
|
|
1942
2351
|
/**
|
|
1943
2352
|
* Effect to handle widget tree status changes
|
|
1944
2353
|
*/
|
|
@@ -1947,7 +2356,7 @@ class AXPLayoutRendererComponent {
|
|
|
1947
2356
|
if (widgetTree) {
|
|
1948
2357
|
this.container()?.builderService.setStatus(AXPPageStatus.Rendered);
|
|
1949
2358
|
}
|
|
1950
|
-
}, ...(ngDevMode ? [{ debugName: "#widgetStatusEffect" }] : []));
|
|
2359
|
+
}, ...(ngDevMode ? [{ debugName: "#widgetStatusEffect" }] : /* istanbul ignore next */ []));
|
|
1951
2360
|
}
|
|
1952
2361
|
//#endregion
|
|
1953
2362
|
//#region ---- Lifecycle Methods ----
|
|
@@ -2087,24 +2496,32 @@ class AXPLayoutRendererComponent {
|
|
|
2087
2496
|
isWidgetNode(data) {
|
|
2088
2497
|
return data && typeof data === 'object' && 'type' in data && typeof data.type === 'string';
|
|
2089
2498
|
}
|
|
2090
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
2091
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
2499
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLayoutRendererComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2500
|
+
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: `
|
|
2092
2501
|
<ax-form>
|
|
2093
2502
|
<axp-widgets-container [context]="internalContext()" (onContextChanged)="handleContextChanged($event)">
|
|
2094
2503
|
@if (widgetTree()) {
|
|
2095
|
-
<ng-container
|
|
2504
|
+
<ng-container
|
|
2505
|
+
axp-widget-renderer
|
|
2506
|
+
[node]="widgetTree()!"
|
|
2507
|
+
[mode]="effectiveRenderMode()"
|
|
2508
|
+
></ng-container>
|
|
2096
2509
|
}
|
|
2097
2510
|
</axp-widgets-container>
|
|
2098
2511
|
</ax-form>
|
|
2099
|
-
`, isInline: true, styles: [":host{display:block;width:100%}\n"], dependencies: [{ kind: "ngmodule", type:
|
|
2512
|
+
`, 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 }); }
|
|
2100
2513
|
}
|
|
2101
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
2514
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLayoutRendererComponent, decorators: [{
|
|
2102
2515
|
type: Component,
|
|
2103
|
-
args: [{ selector: 'axp-layout-renderer', standalone: true, imports: [
|
|
2516
|
+
args: [{ selector: 'axp-layout-renderer', standalone: true, imports: [AXPWidgetCoreModule, AXFormModule], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
2104
2517
|
<ax-form>
|
|
2105
2518
|
<axp-widgets-container [context]="internalContext()" (onContextChanged)="handleContextChanged($event)">
|
|
2106
2519
|
@if (widgetTree()) {
|
|
2107
|
-
<ng-container
|
|
2520
|
+
<ng-container
|
|
2521
|
+
axp-widget-renderer
|
|
2522
|
+
[node]="widgetTree()!"
|
|
2523
|
+
[mode]="effectiveRenderMode()"
|
|
2524
|
+
></ng-container>
|
|
2108
2525
|
}
|
|
2109
2526
|
</axp-widgets-container>
|
|
2110
2527
|
</ax-form>
|
|
@@ -2112,74 +2529,321 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImpo
|
|
|
2112
2529
|
}], 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 }] }] } });
|
|
2113
2530
|
|
|
2114
2531
|
class LayoutBuilderModule {
|
|
2115
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
2116
|
-
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "
|
|
2117
|
-
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "
|
|
2532
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: LayoutBuilderModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
|
|
2533
|
+
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.9", ngImport: i0, type: LayoutBuilderModule, imports: [CommonModule, AXPLayoutRendererComponent], exports: [AXPLayoutRendererComponent] }); }
|
|
2534
|
+
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: LayoutBuilderModule, providers: [
|
|
2535
|
+
AXPLayoutBuilderService,
|
|
2536
|
+
provideCommandSetups([
|
|
2537
|
+
{
|
|
2538
|
+
key: AXP_PREVIEW_WIDGET_FIELD_COMMAND_KEY,
|
|
2539
|
+
command: () => Promise.resolve().then(function () { return previewWidgetField_command; }).then((c) => c.AXPPreviewWidgetFieldCommand),
|
|
2540
|
+
},
|
|
2541
|
+
]),
|
|
2542
|
+
], imports: [CommonModule, AXPLayoutRendererComponent] }); }
|
|
2118
2543
|
}
|
|
2119
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
2544
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: LayoutBuilderModule, decorators: [{
|
|
2120
2545
|
type: NgModule,
|
|
2121
2546
|
args: [{
|
|
2122
2547
|
imports: [CommonModule, AXPLayoutRendererComponent],
|
|
2123
|
-
providers: [
|
|
2548
|
+
providers: [
|
|
2549
|
+
AXPLayoutBuilderService,
|
|
2550
|
+
provideCommandSetups([
|
|
2551
|
+
{
|
|
2552
|
+
key: AXP_PREVIEW_WIDGET_FIELD_COMMAND_KEY,
|
|
2553
|
+
command: () => Promise.resolve().then(function () { return previewWidgetField_command; }).then((c) => c.AXPPreviewWidgetFieldCommand),
|
|
2554
|
+
},
|
|
2555
|
+
]),
|
|
2556
|
+
],
|
|
2124
2557
|
exports: [AXPLayoutRendererComponent],
|
|
2125
2558
|
}]
|
|
2126
2559
|
}] });
|
|
2127
2560
|
|
|
2561
|
+
//#region ---- Contracts barrel ----
|
|
2128
2562
|
//#endregion
|
|
2129
2563
|
|
|
2564
|
+
/**
|
|
2565
|
+
* Returns true when a nested overlay is open and Esc should close it first.
|
|
2566
|
+
* Returns false when only the dialog shell is on top.
|
|
2567
|
+
* Returns null when the Acorex overlay service does not expose open-state queries yet.
|
|
2568
|
+
*/
|
|
2569
|
+
function hasOpenNestedOverlayFromAcorexService(overlayService, dialogOverlay) {
|
|
2570
|
+
const service = overlayService;
|
|
2571
|
+
if (typeof service.hasOpenAnchoredOverlay === 'function') {
|
|
2572
|
+
if (!service.hasOpenAnchoredOverlay()) {
|
|
2573
|
+
return false;
|
|
2574
|
+
}
|
|
2575
|
+
if (typeof service.hasOverlayAbove === 'function') {
|
|
2576
|
+
return service.hasOverlayAbove(dialogOverlay);
|
|
2577
|
+
}
|
|
2578
|
+
return true;
|
|
2579
|
+
}
|
|
2580
|
+
if (typeof service.hasOverlayAbove === 'function') {
|
|
2581
|
+
return service.hasOverlayAbove(dialogOverlay);
|
|
2582
|
+
}
|
|
2583
|
+
return null;
|
|
2584
|
+
}
|
|
2585
|
+
/**
|
|
2586
|
+
* DOM fallback until {@link AXOverlayService} exposes open-overlay queries.
|
|
2587
|
+
*
|
|
2588
|
+
* Dialog popups use a centered `.ax-overlay-container` without `.ax-overlay-pane`.
|
|
2589
|
+
* Widget popovers (select, datetime, dropdown) use anchored containers with `.ax-overlay-pane`.
|
|
2590
|
+
*/
|
|
2591
|
+
function shouldDeferEscapeToNestedOverlayFromDom(document, dialogHost) {
|
|
2592
|
+
const dialogOverlay = findOverlayContainerAncestor(dialogHost);
|
|
2593
|
+
if (getNestedVisibleOverlayPanes(document, dialogOverlay).length > 0) {
|
|
2594
|
+
return true;
|
|
2595
|
+
}
|
|
2596
|
+
const topContainer = getTopVisibleOverlayContainer(document);
|
|
2597
|
+
if (!topContainer) {
|
|
2598
|
+
return false;
|
|
2599
|
+
}
|
|
2600
|
+
if (!dialogOverlay) {
|
|
2601
|
+
return topContainer.contains(dialogHost) === false;
|
|
2602
|
+
}
|
|
2603
|
+
return topContainer !== dialogOverlay;
|
|
2604
|
+
}
|
|
2605
|
+
/**
|
|
2606
|
+
* When a nested overlay is above this dialog (widget popover, confirm box, etc.), footer shortcuts
|
|
2607
|
+
* should not run on the parent dialog.
|
|
2608
|
+
*/
|
|
2609
|
+
function shouldDeferDialogShortcutsToNestedOverlay(document, dialogHost, overlayService) {
|
|
2610
|
+
const dialogOverlay = findOverlayContainerAncestor(dialogHost);
|
|
2611
|
+
if (overlayService) {
|
|
2612
|
+
const fromService = hasOpenNestedOverlayFromAcorexService(overlayService, dialogOverlay);
|
|
2613
|
+
if (fromService !== null) {
|
|
2614
|
+
return fromService;
|
|
2615
|
+
}
|
|
2616
|
+
}
|
|
2617
|
+
return shouldDeferEscapeToNestedOverlayFromDom(document, dialogHost);
|
|
2618
|
+
}
|
|
2619
|
+
|
|
2620
|
+
/**
|
|
2621
|
+
* Builds the `show()` resolve payload for layout-builder custom footer commands (fallback path).
|
|
2622
|
+
*/
|
|
2623
|
+
function buildLayoutBuilderCustomActionRef(createDialogRef, command) {
|
|
2624
|
+
return {
|
|
2625
|
+
...createDialogRef(command),
|
|
2626
|
+
action: () => command,
|
|
2627
|
+
};
|
|
2628
|
+
}
|
|
2629
|
+
/**
|
|
2630
|
+
* Keeps legacy popup `data.context` / `data.action` side fields in sync for custom footer commands.
|
|
2631
|
+
*/
|
|
2632
|
+
function syncLegacyDialogDataSideFields(data, context, action) {
|
|
2633
|
+
if (!data) {
|
|
2634
|
+
return;
|
|
2635
|
+
}
|
|
2636
|
+
data.context = context;
|
|
2637
|
+
data.action = action;
|
|
2638
|
+
}
|
|
2639
|
+
/**
|
|
2640
|
+
* Cancel from an `onAction` handler should route through the dirty-close gate only when configured.
|
|
2641
|
+
* Otherwise preserve the original resolve-then-close sequence.
|
|
2642
|
+
*/
|
|
2643
|
+
function shouldRouteOnActionCancelThroughDismissGate(confirmCloseWhenDirtyEnabled) {
|
|
2644
|
+
return confirmCloseWhenDirtyEnabled === true;
|
|
2645
|
+
}
|
|
2646
|
+
|
|
2647
|
+
/** Debounce after widget count stabilizes before re-evaluating footer actions. */
|
|
2648
|
+
const DIALOG_WIDGET_COUNT_STABLE_DELAY_MS = 350;
|
|
2130
2649
|
class AXPDialogRendererComponent extends AXBasePageComponent {
|
|
2131
2650
|
constructor() {
|
|
2132
2651
|
super(...arguments);
|
|
2133
2652
|
this.result = new EventEmitter();
|
|
2134
2653
|
this.expressionEvaluator = inject(AXPExpressionEvaluatorService);
|
|
2135
|
-
this.
|
|
2654
|
+
this.commandService = inject(AXPCommandService);
|
|
2655
|
+
this.hookService = inject(AXPHookService, { optional: true });
|
|
2656
|
+
this.unsavedChangesConfirm = inject(AXPUnsavedChangesConfirmService);
|
|
2657
|
+
this.overlayService = inject(AXOverlayService);
|
|
2658
|
+
this.translationService = inject(AXTranslationService);
|
|
2659
|
+
this.document = inject(DOCUMENT);
|
|
2660
|
+
this.host = inject((ElementRef));
|
|
2661
|
+
/** Ensures `show()` resolves once when the dialog closes (footer action or header close). */
|
|
2662
|
+
this.callbackInvoked = false;
|
|
2663
|
+
/** Skips dirty confirmation on the next {@link onClosing} (successful submit / programmatic close). */
|
|
2664
|
+
this.skipNextOnClosingDirtyCheck = false;
|
|
2665
|
+
/** Blocks re-entrant {@link onClosing} while the first close gate is still resolving. */
|
|
2666
|
+
this.closeGateInProgress = false;
|
|
2667
|
+
this.destroyRef = inject(DestroyRef);
|
|
2668
|
+
this.context = signal({}, ...(ngDevMode ? [{ debugName: "context" }] : /* istanbul ignore next */ []));
|
|
2136
2669
|
// This will be set by the popup service automatically - same as dynamic-dialog
|
|
2137
2670
|
this.callBack = () => { };
|
|
2138
|
-
this.isDialogLoading = signal(false, ...(ngDevMode ? [{ debugName: "isDialogLoading" }] : []));
|
|
2671
|
+
this.isDialogLoading = signal(false, ...(ngDevMode ? [{ debugName: "isDialogLoading" }] : /* istanbul ignore next */ []));
|
|
2139
2672
|
// Aggregated actions for footer rendering
|
|
2140
|
-
this.footerPrefix = signal([], ...(ngDevMode ? [{ debugName: "footerPrefix" }] : []));
|
|
2141
|
-
this.footerSuffix = signal([], ...(ngDevMode ? [{ debugName: "footerSuffix" }] : []));
|
|
2673
|
+
this.footerPrefix = signal([], ...(ngDevMode ? [{ debugName: "footerPrefix" }] : /* istanbul ignore next */ []));
|
|
2674
|
+
this.footerSuffix = signal([], ...(ngDevMode ? [{ debugName: "footerSuffix" }] : /* istanbul ignore next */ []));
|
|
2675
|
+
/**
|
|
2676
|
+
* Correlate layout context snapshots for distributed hooks (`layout-builder.dialog.context-changed`).
|
|
2677
|
+
*/
|
|
2678
|
+
this.contextChangedHooksSessionKey = typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function'
|
|
2679
|
+
? crypto.randomUUID()
|
|
2680
|
+
: `layout-dialog-ctx-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
2681
|
+
/**
|
|
2682
|
+
* Capture-phase footer shortcuts — runs before widget editors (select, date, text) that stop keydown bubbling.
|
|
2683
|
+
*/
|
|
2684
|
+
this.onDialogShortcutCapture = (event) => {
|
|
2685
|
+
if (this.isDialogLoading()) {
|
|
2686
|
+
return;
|
|
2687
|
+
}
|
|
2688
|
+
// Unsaved-changes confirm is modal — parent footer shortcuts must not run underneath it.
|
|
2689
|
+
if (this.unsavedChangesConfirm.isConfirmPending()) {
|
|
2690
|
+
return;
|
|
2691
|
+
}
|
|
2692
|
+
if (shouldDeferDialogShortcutsToNestedOverlay(this.document, this.host.nativeElement, this.overlayService)) {
|
|
2693
|
+
return;
|
|
2694
|
+
}
|
|
2695
|
+
if (event.key === 'Escape' && this.config?.confirmCloseWhenDirty?.enabled) {
|
|
2696
|
+
event.preventDefault();
|
|
2697
|
+
event.stopImmediatePropagation();
|
|
2698
|
+
this.requestDismiss();
|
|
2699
|
+
return;
|
|
2700
|
+
}
|
|
2701
|
+
const actions = [...this.footerPrefixActions(), ...this.footerSuffixActions()];
|
|
2702
|
+
for (const action of actions) {
|
|
2703
|
+
if (action.disabled || action.hidden || !action.shortcuts?.length) {
|
|
2704
|
+
continue;
|
|
2705
|
+
}
|
|
2706
|
+
for (const shortcut of action.shortcuts) {
|
|
2707
|
+
if (event.key === 'Escape' && this.config?.confirmCloseWhenDirty?.enabled) {
|
|
2708
|
+
continue;
|
|
2709
|
+
}
|
|
2710
|
+
if (!matchesDialogActionShortcut(event, shortcut) || !shouldTriggerDialogActionShortcut(event, shortcut)) {
|
|
2711
|
+
continue;
|
|
2712
|
+
}
|
|
2713
|
+
event.preventDefault();
|
|
2714
|
+
event.stopImmediatePropagation();
|
|
2715
|
+
void this.executeAction(action);
|
|
2716
|
+
return;
|
|
2717
|
+
}
|
|
2718
|
+
}
|
|
2719
|
+
};
|
|
2142
2720
|
//#endregion
|
|
2143
2721
|
//#region ---- View Accessors ----
|
|
2144
2722
|
// Access the internal layout renderer to reach the widgets container injector
|
|
2145
|
-
this.layoutRenderer = viewChild(AXPLayoutRendererComponent, ...(ngDevMode ? [{ debugName: "layoutRenderer" }] : []));
|
|
2146
|
-
this.#
|
|
2147
|
-
let count = 0;
|
|
2723
|
+
this.layoutRenderer = viewChild(AXPLayoutRendererComponent, ...(ngDevMode ? [{ debugName: "layoutRenderer" }] : /* istanbul ignore next */ []));
|
|
2724
|
+
this.#widgetActionsEffect = effect(() => {
|
|
2148
2725
|
this.aggregateAndEvaluateActions();
|
|
2149
2726
|
if (!this.widgetCoreService) {
|
|
2150
2727
|
const renderer = this.layoutRenderer();
|
|
2151
2728
|
const container = renderer?.getContainer();
|
|
2152
2729
|
this.widgetCoreService = container?.builderService ?? null;
|
|
2153
|
-
|
|
2730
|
+
return;
|
|
2154
2731
|
}
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
if (this.debounceTimer) {
|
|
2159
|
-
clearTimeout(this.debounceTimer);
|
|
2160
|
-
}
|
|
2161
|
-
// Set new timer to call after 200ms of no count changes
|
|
2162
|
-
this.debounceTimer = setTimeout(() => {
|
|
2163
|
-
this.aggregateAndEvaluateActions();
|
|
2164
|
-
}, 200);
|
|
2732
|
+
this.widgetCoreService.registeredWidgetsCount();
|
|
2733
|
+
if (this.debounceTimer) {
|
|
2734
|
+
clearTimeout(this.debounceTimer);
|
|
2165
2735
|
}
|
|
2166
|
-
|
|
2736
|
+
this.debounceTimer = setTimeout(() => {
|
|
2737
|
+
this.aggregateAndEvaluateActions();
|
|
2738
|
+
}, DIALOG_WIDGET_COUNT_STABLE_DELAY_MS);
|
|
2739
|
+
}, ...(ngDevMode ? [{ debugName: "#widgetActionsEffect" }] : /* istanbul ignore next */ []));
|
|
2167
2740
|
}
|
|
2168
2741
|
//#endregion
|
|
2169
2742
|
//#region ---- Lifecycle ----
|
|
2170
2743
|
ngOnInit() {
|
|
2171
|
-
|
|
2172
|
-
this.context.set(
|
|
2744
|
+
const initialContext = this.config?.context || {};
|
|
2745
|
+
this.context.set(initialContext);
|
|
2746
|
+
this.document.addEventListener('keydown', this.onDialogShortcutCapture, true);
|
|
2747
|
+
this.destroyRef.onDestroy(() => {
|
|
2748
|
+
this.document.removeEventListener('keydown', this.onDialogShortcutCapture, true);
|
|
2749
|
+
});
|
|
2750
|
+
void this.invokeLayoutContextChangedHooks();
|
|
2173
2751
|
}
|
|
2174
|
-
|
|
2752
|
+
//#endregion
|
|
2753
|
+
//#region ---- Popup Close Gate ----
|
|
2754
|
+
/**
|
|
2755
|
+
* Popup shell hook — handles header **X** and **Esc** (when `closeButton` is enabled).
|
|
2756
|
+
* This is the only place that prompts for unsaved changes.
|
|
2757
|
+
*/
|
|
2758
|
+
async onClosing(e) {
|
|
2759
|
+
if (this.unsavedChangesConfirm.isConfirmPending() || this.closeGateInProgress) {
|
|
2760
|
+
e.cancel = true;
|
|
2761
|
+
return;
|
|
2762
|
+
}
|
|
2763
|
+
this.closeGateInProgress = true;
|
|
2764
|
+
try {
|
|
2765
|
+
if (this.skipNextOnClosingDirtyCheck) {
|
|
2766
|
+
this.skipNextOnClosingDirtyCheck = false;
|
|
2767
|
+
this.completeDismissResolve();
|
|
2768
|
+
return;
|
|
2769
|
+
}
|
|
2770
|
+
if (!this.config?.confirmCloseWhenDirty?.enabled) {
|
|
2771
|
+
this.completeDismissResolve();
|
|
2772
|
+
return;
|
|
2773
|
+
}
|
|
2774
|
+
if (!(await this.confirmDismissIfDirty())) {
|
|
2775
|
+
e.cancel = true;
|
|
2776
|
+
this.pendingDismissResolvePayload = undefined;
|
|
2777
|
+
return;
|
|
2778
|
+
}
|
|
2779
|
+
this.completeDismissResolve();
|
|
2780
|
+
}
|
|
2781
|
+
finally {
|
|
2782
|
+
this.closeGateInProgress = false;
|
|
2783
|
+
}
|
|
2784
|
+
}
|
|
2785
|
+
/**
|
|
2786
|
+
* Footer cancel and other in-app dismiss actions. Routes through `super.close()` so the
|
|
2787
|
+
* popup shell invokes {@link onClosing} exactly once (same path as **X** / **Esc**).
|
|
2788
|
+
*/
|
|
2789
|
+
requestDismiss(resolvePayload) {
|
|
2790
|
+
this.pendingDismissResolvePayload = resolvePayload ?? this.createDialogRef('cancel');
|
|
2791
|
+
super.close();
|
|
2792
|
+
}
|
|
2793
|
+
/** Invokes `show()` callback with the pending payload or a default cancel {@link AXPDialogRef}. */
|
|
2794
|
+
completeDismissResolve() {
|
|
2795
|
+
const payload = this.pendingDismissResolvePayload;
|
|
2796
|
+
this.pendingDismissResolvePayload = undefined;
|
|
2797
|
+
if (payload !== undefined) {
|
|
2798
|
+
this.resolveDialog(payload);
|
|
2799
|
+
return;
|
|
2800
|
+
}
|
|
2801
|
+
if (!this.callbackInvoked) {
|
|
2802
|
+
this.resolveDialog(this.createDialogRef('cancel'));
|
|
2803
|
+
}
|
|
2804
|
+
}
|
|
2805
|
+
//#endregion
|
|
2806
|
+
//#region ---- Dirty State ----
|
|
2807
|
+
getWidgetContainer() {
|
|
2808
|
+
return this.layoutRenderer()?.getContainer();
|
|
2809
|
+
}
|
|
2810
|
+
#widgetActionsEffect;
|
|
2175
2811
|
//#endregion
|
|
2176
2812
|
handleContextChanged(event) {
|
|
2177
2813
|
this.context.set(event);
|
|
2178
2814
|
this.aggregateAndEvaluateActions();
|
|
2815
|
+
void this.invokeLayoutContextChangedHooks();
|
|
2179
2816
|
}
|
|
2180
2817
|
handleContextInitiated(event) {
|
|
2181
2818
|
this.context.set(event);
|
|
2182
2819
|
this.aggregateAndEvaluateActions();
|
|
2820
|
+
void this.invokeLayoutContextChangedHooks();
|
|
2821
|
+
}
|
|
2822
|
+
async invokeLayoutContextChangedHooks() {
|
|
2823
|
+
const meta = this.config?.metadata;
|
|
2824
|
+
if (!this.hookService) {
|
|
2825
|
+
return;
|
|
2826
|
+
}
|
|
2827
|
+
const payload = {
|
|
2828
|
+
sessionKey: this.contextChangedHooksSessionKey,
|
|
2829
|
+
getContext: () => {
|
|
2830
|
+
const store = this.getWidgetContainer()?.contextService;
|
|
2831
|
+
return (store?.data() ?? this.context() ?? {});
|
|
2832
|
+
},
|
|
2833
|
+
metadata: meta,
|
|
2834
|
+
patchContext: (partial) => {
|
|
2835
|
+
const merged = merge({}, this.context(), partial);
|
|
2836
|
+
this.context.set(merged);
|
|
2837
|
+
this.layoutRenderer()?.updateContext(merged);
|
|
2838
|
+
},
|
|
2839
|
+
setLoading: (loading) => this.isDialogLoading.set(loading),
|
|
2840
|
+
};
|
|
2841
|
+
try {
|
|
2842
|
+
await this.hookService.runAsync(AXP_LAYOUT_BUILDER_DIALOG_CONTEXT_CHANGED_HOOK_KEY, payload);
|
|
2843
|
+
}
|
|
2844
|
+
catch {
|
|
2845
|
+
// Hook providers are best-effort; avoid breaking the dialog lifecycle.
|
|
2846
|
+
}
|
|
2183
2847
|
}
|
|
2184
2848
|
footerPrefixActions() {
|
|
2185
2849
|
return this.footerPrefix();
|
|
@@ -2194,38 +2858,177 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
|
|
|
2194
2858
|
return this.isDialogLoading();
|
|
2195
2859
|
}
|
|
2196
2860
|
async executeAction(action) {
|
|
2197
|
-
const cmd = action.command;
|
|
2198
|
-
if (cmd
|
|
2861
|
+
const cmd = this.resolveActionCommandName(action.command);
|
|
2862
|
+
if (this.shouldValidateBeforeAction(cmd)) {
|
|
2199
2863
|
const isValid = await this.layoutRenderer()?.validate();
|
|
2200
2864
|
if (!isValid?.result) {
|
|
2201
2865
|
return;
|
|
2202
2866
|
}
|
|
2203
2867
|
}
|
|
2204
|
-
|
|
2868
|
+
//TODO: matin, why we need this? maybe we can remove it?
|
|
2869
|
+
if (cmd?.startsWith('widget:')) {
|
|
2205
2870
|
const parsed = this.parseWidgetCommand(cmd);
|
|
2206
2871
|
if (parsed.widgetName && parsed.action) {
|
|
2207
|
-
await this.
|
|
2872
|
+
await this.invokeWidget(parsed.widgetName, parsed.action, {});
|
|
2208
2873
|
await this.aggregateAndEvaluateActions();
|
|
2209
2874
|
return;
|
|
2210
2875
|
}
|
|
2211
2876
|
}
|
|
2212
|
-
|
|
2213
|
-
|
|
2877
|
+
if (cmd && this.commandService.exists(cmd)) {
|
|
2878
|
+
const dialogRef = this.createDialogRef(cmd);
|
|
2879
|
+
const integration = (this.config.metadata ?? {});
|
|
2880
|
+
try {
|
|
2881
|
+
const cmdResult = await this.commandService.execute(cmd, { dialogRef, integration });
|
|
2882
|
+
if (!cmdResult?.success) {
|
|
2883
|
+
return;
|
|
2884
|
+
}
|
|
2885
|
+
if (this.shouldKeepDialogOpenAfterCommandResult(cmdResult)) {
|
|
2886
|
+
return;
|
|
2887
|
+
}
|
|
2888
|
+
this.resolveDialog(cmdResult);
|
|
2889
|
+
await this.closeWithOptionalSkipValidate(cmdResult);
|
|
2890
|
+
}
|
|
2891
|
+
catch (error) {
|
|
2892
|
+
console.error('Error executing action', cmd, error);
|
|
2893
|
+
}
|
|
2894
|
+
return;
|
|
2895
|
+
}
|
|
2896
|
+
const context = this.context();
|
|
2897
|
+
const onAction = this.config?.onAction;
|
|
2898
|
+
// `onAction` return value is passed through to `show()` unchanged — see dialog-resolve.util.ts.
|
|
2899
|
+
if (onAction) {
|
|
2900
|
+
const dialogRef = this.createDialogRef(cmd);
|
|
2901
|
+
try {
|
|
2902
|
+
this.isDialogLoading.set(true);
|
|
2903
|
+
const result = await Promise.resolve(onAction(dialogRef));
|
|
2904
|
+
if (this.shouldKeepDialogOpenAfterCommandResult(result)) {
|
|
2905
|
+
return;
|
|
2906
|
+
}
|
|
2907
|
+
if (cmd === 'cancel' &&
|
|
2908
|
+
shouldRouteOnActionCancelThroughDismissGate(this.config?.confirmCloseWhenDirty?.enabled)) {
|
|
2909
|
+
this.isDialogLoading.set(false);
|
|
2910
|
+
this.requestDismiss(result);
|
|
2911
|
+
return;
|
|
2912
|
+
}
|
|
2913
|
+
this.resolveDialog(result);
|
|
2914
|
+
await this.closeWithOptionalSkipValidate(result);
|
|
2915
|
+
}
|
|
2916
|
+
catch {
|
|
2917
|
+
// Handler threw: stay open for retry, actions remain clickable
|
|
2918
|
+
}
|
|
2919
|
+
finally {
|
|
2920
|
+
this.isDialogLoading.set(false);
|
|
2921
|
+
}
|
|
2922
|
+
return;
|
|
2923
|
+
}
|
|
2924
|
+
// Fallback: layout-builder custom footer commands (e.g. signature-apply, upload-image).
|
|
2925
|
+
// See dialog-resolve.util.ts — resolves {@link AXPDialogRef}; does not auto-close except cancel.
|
|
2926
|
+
const result = { context, action: cmd };
|
|
2214
2927
|
this.dialogResult = result;
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2928
|
+
syncLegacyDialogDataSideFields(this.data, result.context, result.action);
|
|
2929
|
+
const dialogRefPayload = buildLayoutBuilderCustomActionRef((command) => this.createDialogRef(command), cmd);
|
|
2930
|
+
if (cmd === 'cancel') {
|
|
2931
|
+
this.requestDismiss(dialogRefPayload);
|
|
2932
|
+
return;
|
|
2218
2933
|
}
|
|
2219
|
-
this.
|
|
2220
|
-
|
|
2221
|
-
|
|
2934
|
+
this.resolveDialog(dialogRefPayload);
|
|
2935
|
+
}
|
|
2936
|
+
isDialogDirty() {
|
|
2937
|
+
const confirmOptions = this.config?.confirmCloseWhenDirty;
|
|
2938
|
+
if (!confirmOptions?.enabled) {
|
|
2939
|
+
return false;
|
|
2940
|
+
}
|
|
2941
|
+
const container = this.getWidgetContainer();
|
|
2942
|
+
if (!container?.isSavedCommitted()) {
|
|
2943
|
+
return false;
|
|
2944
|
+
}
|
|
2945
|
+
const store = container.contextService;
|
|
2946
|
+
if (typeof confirmOptions.isDirty === 'function') {
|
|
2947
|
+
return confirmOptions.isDirty(store.data(), store.saved());
|
|
2948
|
+
}
|
|
2949
|
+
return container.isFormDirty();
|
|
2950
|
+
}
|
|
2951
|
+
confirmDismissIfDirty() {
|
|
2952
|
+
const confirmOptions = this.config?.confirmCloseWhenDirty;
|
|
2953
|
+
if (!confirmOptions?.enabled || !this.isDialogDirty()) {
|
|
2954
|
+
return Promise.resolve(true);
|
|
2955
|
+
}
|
|
2956
|
+
return this.unsavedChangesConfirm.confirmIfDirty(true, {
|
|
2957
|
+
enabled: confirmOptions.enabled,
|
|
2958
|
+
title: confirmOptions.title,
|
|
2959
|
+
message: confirmOptions.message,
|
|
2960
|
+
});
|
|
2961
|
+
}
|
|
2962
|
+
/** Whether the layout form should be validated before running this footer command. */
|
|
2963
|
+
shouldValidateBeforeAction(cmd) {
|
|
2964
|
+
if (!cmd || cmd === 'cancel' || cmd === 'entity-form-done' || cmd === 'entity-form-next-step') {
|
|
2965
|
+
return false;
|
|
2966
|
+
}
|
|
2967
|
+
if (cmd.startsWith('widget:')) {
|
|
2968
|
+
return false;
|
|
2969
|
+
}
|
|
2970
|
+
if (this.commandService.exists(cmd)) {
|
|
2971
|
+
return false;
|
|
2972
|
+
}
|
|
2973
|
+
return true;
|
|
2974
|
+
}
|
|
2975
|
+
/** True when a footer handler or command result asks to leave the dialog open (`keepDialogOpen` on the result or `result.data`). */
|
|
2976
|
+
shouldKeepDialogOpenAfterCommandResult(result) {
|
|
2977
|
+
if (!result || typeof result !== 'object') {
|
|
2978
|
+
return false;
|
|
2979
|
+
}
|
|
2980
|
+
const top = result;
|
|
2981
|
+
if (top.keepDialogOpen === true) {
|
|
2982
|
+
return true;
|
|
2983
|
+
}
|
|
2984
|
+
if (top.data != null && typeof top.data === 'object' && 'keepDialogOpen' in top.data) {
|
|
2985
|
+
return top.data.keepDialogOpen === true;
|
|
2986
|
+
}
|
|
2987
|
+
return false;
|
|
2988
|
+
}
|
|
2989
|
+
/** Resolves the dialog `show()` promise exactly once. */
|
|
2990
|
+
resolveDialog(result) {
|
|
2991
|
+
if (this.callbackInvoked) {
|
|
2992
|
+
return;
|
|
2993
|
+
}
|
|
2994
|
+
this.callbackInvoked = true;
|
|
2995
|
+
this.callBack(result);
|
|
2996
|
+
}
|
|
2997
|
+
createDialogRef(actionCmd) {
|
|
2998
|
+
return {
|
|
2999
|
+
close: (res) => {
|
|
3000
|
+
void this.closeWithOptionalSkipValidate(res);
|
|
2222
3001
|
},
|
|
2223
3002
|
context: () => this.context(),
|
|
2224
|
-
action: () =>
|
|
2225
|
-
setLoading: (loading) =>
|
|
2226
|
-
|
|
3003
|
+
action: () => actionCmd,
|
|
3004
|
+
setLoading: (loading) => this.isDialogLoading.set(loading),
|
|
3005
|
+
patchContext: (partial) => {
|
|
3006
|
+
const merged = merge({}, this.context(), partial);
|
|
3007
|
+
this.context.set(merged);
|
|
3008
|
+
this.layoutRenderer()?.updateContext(merged);
|
|
2227
3009
|
},
|
|
2228
|
-
|
|
3010
|
+
invokeWidget: (widgetName, method, opts) => this.invokeWidget(widgetName, method, opts ?? {}),
|
|
3011
|
+
};
|
|
3012
|
+
}
|
|
3013
|
+
async closeWithOptionalSkipValidate(result) {
|
|
3014
|
+
if (result && typeof result === 'object' && result.skipValidate) {
|
|
3015
|
+
this.result.emit(result);
|
|
3016
|
+
this.skipNextOnClosingDirtyCheck = true;
|
|
3017
|
+
this.pendingDismissResolvePayload = undefined;
|
|
3018
|
+
super.close(result);
|
|
3019
|
+
return;
|
|
3020
|
+
}
|
|
3021
|
+
await this.close(result, { skipDirtyConfirm: true });
|
|
3022
|
+
}
|
|
3023
|
+
/** Resolves footer/widget action command to a string (e.g. `cancel`, `submit`, `widget:...`). */
|
|
3024
|
+
resolveActionCommandName(command) {
|
|
3025
|
+
if (typeof command === 'string') {
|
|
3026
|
+
return command;
|
|
3027
|
+
}
|
|
3028
|
+
if (command && typeof command === 'object' && 'name' in command) {
|
|
3029
|
+
return command.name;
|
|
3030
|
+
}
|
|
3031
|
+
return undefined;
|
|
2229
3032
|
}
|
|
2230
3033
|
parseWidgetCommand(cmd) {
|
|
2231
3034
|
// Expected 'widget:<widgetName>.<action>'
|
|
@@ -2237,7 +3040,7 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
|
|
|
2237
3040
|
return {};
|
|
2238
3041
|
return { widgetName: rest.slice(0, dot), action: rest.slice(dot + 1) };
|
|
2239
3042
|
}
|
|
2240
|
-
async
|
|
3043
|
+
async invokeWidget(widgetName, apiMethod, opts) {
|
|
2241
3044
|
if (!this.widgetCoreService)
|
|
2242
3045
|
return;
|
|
2243
3046
|
try {
|
|
@@ -2247,18 +3050,26 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
|
|
|
2247
3050
|
if (typeof fn === 'function') {
|
|
2248
3051
|
await Promise.resolve(fn({
|
|
2249
3052
|
close: (result) => {
|
|
2250
|
-
this.
|
|
3053
|
+
void this.closeWithOptionalSkipValidate(result);
|
|
2251
3054
|
},
|
|
2252
3055
|
context: () => this.context(),
|
|
2253
3056
|
setLoading: (loading) => {
|
|
2254
|
-
this.isDialogLoading.set(loading);
|
|
3057
|
+
(opts.setLoading ?? ((v) => this.isDialogLoading.set(v)))(loading);
|
|
2255
3058
|
},
|
|
2256
3059
|
}));
|
|
3060
|
+
// Footer predicates (e.g. wizard step) must refresh when the widget advances outside executeAction (e.g. dialogRef.invokeWidget after entity-form continue).
|
|
3061
|
+
await this.aggregateAndEvaluateActions();
|
|
2257
3062
|
}
|
|
2258
3063
|
}
|
|
2259
|
-
catch {
|
|
3064
|
+
catch {
|
|
3065
|
+
//
|
|
3066
|
+
}
|
|
2260
3067
|
}
|
|
2261
|
-
async close(result) {
|
|
3068
|
+
async close(result, options) {
|
|
3069
|
+
if (options?.skipDirtyConfirm) {
|
|
3070
|
+
this.skipNextOnClosingDirtyCheck = true;
|
|
3071
|
+
this.pendingDismissResolvePayload = undefined;
|
|
3072
|
+
}
|
|
2262
3073
|
if (result) {
|
|
2263
3074
|
const isValid = await this.layoutRenderer()?.validate();
|
|
2264
3075
|
if (isValid?.result) {
|
|
@@ -2306,6 +3117,8 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
|
|
|
2306
3117
|
zone: 'footer',
|
|
2307
3118
|
placement,
|
|
2308
3119
|
scope: a.scope,
|
|
3120
|
+
predicateApiWidgetName: a.predicateApiWidgetName,
|
|
3121
|
+
shortcuts: resolveConfiguredFooterActionShortcuts(typeof a.command === 'string' ? a.command : a.command?.name, a.shortcuts),
|
|
2309
3122
|
});
|
|
2310
3123
|
const prefix = (footer?.prefix || []).map((a) => mapOne(a, 'prefix'));
|
|
2311
3124
|
const suffix = (footer?.suffix || []).map((a) => mapOne(a, 'suffix'));
|
|
@@ -2315,16 +3128,18 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
|
|
|
2315
3128
|
const out = [];
|
|
2316
3129
|
for (const a of actions) {
|
|
2317
3130
|
const parsed = typeof a.command === 'string' ? this.parseWidgetCommand(a.command) : {};
|
|
2318
|
-
const
|
|
3131
|
+
const widgetNameForApi = parsed.widgetName ?? a.predicateApiWidgetName;
|
|
3132
|
+
const api = widgetNameForApi ? await this.resolveApi(widgetNameForApi) : undefined;
|
|
2319
3133
|
const scope = {
|
|
2320
3134
|
api,
|
|
2321
|
-
widget: { name:
|
|
3135
|
+
widget: { name: widgetNameForApi },
|
|
2322
3136
|
dialog: { context: this.context() },
|
|
2323
3137
|
context: this.context(),
|
|
2324
3138
|
};
|
|
2325
3139
|
const disabled = await this.evalBool(a.disabled, scope);
|
|
2326
3140
|
const hidden = await this.evalBool(a.hidden, scope);
|
|
2327
|
-
|
|
3141
|
+
const resolvedTitle = (await this.evalActionTitle(a.title, scope)) ?? a.title;
|
|
3142
|
+
out.push({ ...a, disabled, hidden, title: resolvedTitle });
|
|
2328
3143
|
}
|
|
2329
3144
|
return out;
|
|
2330
3145
|
}
|
|
@@ -2342,6 +3157,25 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
|
|
|
2342
3157
|
}
|
|
2343
3158
|
return value;
|
|
2344
3159
|
}
|
|
3160
|
+
/** Resolves footer action title when it contains {{ ... }} (e.g. wizard labels from dialog context). */
|
|
3161
|
+
async evalActionTitle(value, scope) {
|
|
3162
|
+
if (value == null || typeof value !== 'string' || !value.includes('{{')) {
|
|
3163
|
+
return value;
|
|
3164
|
+
}
|
|
3165
|
+
try {
|
|
3166
|
+
const result = await this.expressionEvaluator.evaluate(value, scope);
|
|
3167
|
+
if (typeof result === 'string' && result.length > 0) {
|
|
3168
|
+
return result;
|
|
3169
|
+
}
|
|
3170
|
+
if (result != null && result !== false) {
|
|
3171
|
+
return String(result);
|
|
3172
|
+
}
|
|
3173
|
+
}
|
|
3174
|
+
catch {
|
|
3175
|
+
//
|
|
3176
|
+
}
|
|
3177
|
+
return value;
|
|
3178
|
+
}
|
|
2345
3179
|
async resolveApi(widgetName) {
|
|
2346
3180
|
try {
|
|
2347
3181
|
await this.widgetCoreService?.waitForWidget(widgetName, 2000);
|
|
@@ -2352,9 +3186,13 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
|
|
|
2352
3186
|
return undefined;
|
|
2353
3187
|
}
|
|
2354
3188
|
}
|
|
2355
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
2356
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
3189
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPDialogRendererComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
3190
|
+
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: `
|
|
3191
|
+
<axp-component-slot name="dialog-header" [context]="context()"></axp-component-slot>
|
|
2357
3192
|
<div class="ax-p-4">
|
|
3193
|
+
<!-- @if (config.message) {
|
|
3194
|
+
<p class="ax-mb-4 ax-leading-relaxed">{{ config.message | translate | async }}</p>
|
|
3195
|
+
} -->
|
|
2358
3196
|
<axp-layout-renderer
|
|
2359
3197
|
[layout]="config.definition"
|
|
2360
3198
|
[context]="context()"
|
|
@@ -2364,46 +3202,57 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
|
|
|
2364
3202
|
</axp-layout-renderer>
|
|
2365
3203
|
</div>
|
|
2366
3204
|
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
@for (action of footerSuffixActions(); track $index) {
|
|
2385
|
-
<ax-button
|
|
2386
|
-
[disabled]="action.disabled || isSubmitting()"
|
|
2387
|
-
[text]="(action.title | translate | async)!"
|
|
2388
|
-
[look]="'solid'"
|
|
2389
|
-
[color]="action.color"
|
|
2390
|
-
(onClick)="executeAction(action)"
|
|
2391
|
-
>
|
|
2392
|
-
@if (isFormLoading()) {
|
|
2393
|
-
<ax-loading></ax-loading>
|
|
2394
|
-
}
|
|
2395
|
-
@if (action.icon) {
|
|
3205
|
+
<!-- Custom footer slot: if it has content, default footer is hidden -->
|
|
3206
|
+
<axp-component-slot name="dialog-footer" #footerSlot="slot" [context]="context()"></axp-component-slot>
|
|
3207
|
+
@if (footerSlot.isEmpty()) {
|
|
3208
|
+
<ax-footer>
|
|
3209
|
+
<ax-prefix>
|
|
3210
|
+
<axp-component-slot name="dialog-footer-prefix" [context]="context()"></axp-component-slot>
|
|
3211
|
+
@for (action of footerPrefixActions(); track $index) {
|
|
3212
|
+
<ax-button
|
|
3213
|
+
[disabled]="action.disabled || isFormLoading()"
|
|
3214
|
+
[text]="(action.title | translate | async)!"
|
|
3215
|
+
[look]="'outline'"
|
|
3216
|
+
[color]="action.color"
|
|
3217
|
+
(onClick)="executeAction(action)"
|
|
3218
|
+
>
|
|
3219
|
+
@if (isFormLoading()) {
|
|
3220
|
+
<ax-loading></ax-loading>
|
|
3221
|
+
}
|
|
2396
3222
|
<ax-prefix>
|
|
2397
|
-
|
|
3223
|
+
@if (action.icon) {
|
|
3224
|
+
<ax-icon [icon]="action.icon"></ax-icon>
|
|
3225
|
+
}
|
|
2398
3226
|
</ax-prefix>
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
3227
|
+
</ax-button>
|
|
3228
|
+
}
|
|
3229
|
+
</ax-prefix>
|
|
3230
|
+
<ax-suffix>
|
|
3231
|
+
@for (action of footerSuffixActions(); track $index) {
|
|
3232
|
+
<ax-button
|
|
3233
|
+
[disabled]="action.disabled || isSubmitting()"
|
|
3234
|
+
[text]="(action.title | translate | async)!"
|
|
3235
|
+
[look]="'solid'"
|
|
3236
|
+
[color]="action.color"
|
|
3237
|
+
(onClick)="executeAction(action)"
|
|
3238
|
+
>
|
|
3239
|
+
@if (isFormLoading()) {
|
|
3240
|
+
<ax-loading></ax-loading>
|
|
3241
|
+
}
|
|
3242
|
+
@if (action.icon) {
|
|
3243
|
+
<ax-prefix>
|
|
3244
|
+
<ax-icon [icon]="action.icon"></ax-icon>
|
|
3245
|
+
</ax-prefix>
|
|
3246
|
+
}
|
|
3247
|
+
</ax-button>
|
|
3248
|
+
}
|
|
3249
|
+
<axp-component-slot name="dialog-footer-suffix" [context]="context()"></axp-component-slot>
|
|
3250
|
+
</ax-suffix>
|
|
3251
|
+
</ax-footer>
|
|
3252
|
+
}
|
|
3253
|
+
`, 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 }); }
|
|
2405
3254
|
}
|
|
2406
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
3255
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPDialogRendererComponent, decorators: [{
|
|
2407
3256
|
type: Component,
|
|
2408
3257
|
args: [{
|
|
2409
3258
|
selector: 'axp-dialog-renderer',
|
|
@@ -2415,9 +3264,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImpo
|
|
|
2415
3264
|
AXDecoratorModule,
|
|
2416
3265
|
AXLoadingModule,
|
|
2417
3266
|
AXTranslationModule,
|
|
3267
|
+
AXPComponentSlotModule,
|
|
2418
3268
|
],
|
|
3269
|
+
providers: [AXPContextStore],
|
|
3270
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
2419
3271
|
template: `
|
|
3272
|
+
<axp-component-slot name="dialog-header" [context]="context()"></axp-component-slot>
|
|
2420
3273
|
<div class="ax-p-4">
|
|
3274
|
+
<!-- @if (config.message) {
|
|
3275
|
+
<p class="ax-mb-4 ax-leading-relaxed">{{ config.message | translate | async }}</p>
|
|
3276
|
+
} -->
|
|
2421
3277
|
<axp-layout-renderer
|
|
2422
3278
|
[layout]="config.definition"
|
|
2423
3279
|
[context]="context()"
|
|
@@ -2427,48 +3283,57 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImpo
|
|
|
2427
3283
|
</axp-layout-renderer>
|
|
2428
3284
|
</div>
|
|
2429
3285
|
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
@for (action of footerSuffixActions(); track $index) {
|
|
2448
|
-
<ax-button
|
|
2449
|
-
[disabled]="action.disabled || isSubmitting()"
|
|
2450
|
-
[text]="(action.title | translate | async)!"
|
|
2451
|
-
[look]="'solid'"
|
|
2452
|
-
[color]="action.color"
|
|
2453
|
-
(onClick)="executeAction(action)"
|
|
2454
|
-
>
|
|
2455
|
-
@if (isFormLoading()) {
|
|
2456
|
-
<ax-loading></ax-loading>
|
|
2457
|
-
}
|
|
2458
|
-
@if (action.icon) {
|
|
3286
|
+
<!-- Custom footer slot: if it has content, default footer is hidden -->
|
|
3287
|
+
<axp-component-slot name="dialog-footer" #footerSlot="slot" [context]="context()"></axp-component-slot>
|
|
3288
|
+
@if (footerSlot.isEmpty()) {
|
|
3289
|
+
<ax-footer>
|
|
3290
|
+
<ax-prefix>
|
|
3291
|
+
<axp-component-slot name="dialog-footer-prefix" [context]="context()"></axp-component-slot>
|
|
3292
|
+
@for (action of footerPrefixActions(); track $index) {
|
|
3293
|
+
<ax-button
|
|
3294
|
+
[disabled]="action.disabled || isFormLoading()"
|
|
3295
|
+
[text]="(action.title | translate | async)!"
|
|
3296
|
+
[look]="'outline'"
|
|
3297
|
+
[color]="action.color"
|
|
3298
|
+
(onClick)="executeAction(action)"
|
|
3299
|
+
>
|
|
3300
|
+
@if (isFormLoading()) {
|
|
3301
|
+
<ax-loading></ax-loading>
|
|
3302
|
+
}
|
|
2459
3303
|
<ax-prefix>
|
|
2460
|
-
|
|
3304
|
+
@if (action.icon) {
|
|
3305
|
+
<ax-icon [icon]="action.icon"></ax-icon>
|
|
3306
|
+
}
|
|
2461
3307
|
</ax-prefix>
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
3308
|
+
</ax-button>
|
|
3309
|
+
}
|
|
3310
|
+
</ax-prefix>
|
|
3311
|
+
<ax-suffix>
|
|
3312
|
+
@for (action of footerSuffixActions(); track $index) {
|
|
3313
|
+
<ax-button
|
|
3314
|
+
[disabled]="action.disabled || isSubmitting()"
|
|
3315
|
+
[text]="(action.title | translate | async)!"
|
|
3316
|
+
[look]="'solid'"
|
|
3317
|
+
[color]="action.color"
|
|
3318
|
+
(onClick)="executeAction(action)"
|
|
3319
|
+
>
|
|
3320
|
+
@if (isFormLoading()) {
|
|
3321
|
+
<ax-loading></ax-loading>
|
|
3322
|
+
}
|
|
3323
|
+
@if (action.icon) {
|
|
3324
|
+
<ax-prefix>
|
|
3325
|
+
<ax-icon [icon]="action.icon"></ax-icon>
|
|
3326
|
+
</ax-prefix>
|
|
3327
|
+
}
|
|
3328
|
+
</ax-button>
|
|
3329
|
+
}
|
|
3330
|
+
<axp-component-slot name="dialog-footer-suffix" [context]="context()"></axp-component-slot>
|
|
3331
|
+
</ax-suffix>
|
|
3332
|
+
</ax-footer>
|
|
3333
|
+
}
|
|
2467
3334
|
`,
|
|
2468
3335
|
}]
|
|
2469
|
-
}], propDecorators: {
|
|
2470
|
-
type: Input
|
|
2471
|
-
}], result: [{
|
|
3336
|
+
}], propDecorators: { result: [{
|
|
2472
3337
|
type: Output
|
|
2473
3338
|
}], layoutRenderer: [{ type: i0.ViewChild, args: [i0.forwardRef(() => AXPLayoutRendererComponent), { isSignal: true }] }] } });
|
|
2474
3339
|
|
|
@@ -2477,9 +3342,190 @@ var dialogRenderer_component = /*#__PURE__*/Object.freeze({
|
|
|
2477
3342
|
AXPDialogRendererComponent: AXPDialogRendererComponent
|
|
2478
3343
|
});
|
|
2479
3344
|
|
|
3345
|
+
//#region ---- Imports ----
|
|
3346
|
+
/**
|
|
3347
|
+
* `customWidget` only forwards keys from its options bag into the built node via `addSingleWidget`.
|
|
3348
|
+
* Designer / configurator persist `defaultValue` (and other extended fields) on the widget node root;
|
|
3349
|
+
* spreading `options` alone drops them, so preview never applied defaults.
|
|
3350
|
+
*/
|
|
3351
|
+
/**
|
|
3352
|
+
* Widget options are sometimes persisted with an extra nesting (`options.options`) when context
|
|
3353
|
+
* was merged incorrectly. Flatten so list/data-source resolution sees `dataSource` at the top level.
|
|
3354
|
+
*/
|
|
3355
|
+
function optionsBagForPreview(node) {
|
|
3356
|
+
const raw = (node.options ?? {});
|
|
3357
|
+
const inner = raw['options'];
|
|
3358
|
+
if (inner !== undefined && typeof inner === 'object' && !Array.isArray(inner)) {
|
|
3359
|
+
const { options: _nested, ...rest } = raw;
|
|
3360
|
+
return { ...rest, ...inner };
|
|
3361
|
+
}
|
|
3362
|
+
return { ...raw };
|
|
3363
|
+
}
|
|
3364
|
+
function extendedNodePropsForPreview(node) {
|
|
3365
|
+
const out = {};
|
|
3366
|
+
if (node.defaultValue !== undefined) {
|
|
3367
|
+
out['defaultValue'] = node.defaultValue;
|
|
3368
|
+
}
|
|
3369
|
+
if (node.triggers !== undefined) {
|
|
3370
|
+
out['triggers'] = node.triggers;
|
|
3371
|
+
}
|
|
3372
|
+
if (node.meta !== undefined) {
|
|
3373
|
+
out['meta'] = node.meta;
|
|
3374
|
+
}
|
|
3375
|
+
if (node.valueTransforms !== undefined) {
|
|
3376
|
+
out['valueTransforms'] = node.valueTransforms;
|
|
3377
|
+
}
|
|
3378
|
+
if (node.visible !== undefined) {
|
|
3379
|
+
out['visible'] = node.visible;
|
|
3380
|
+
}
|
|
3381
|
+
if (node.mode !== undefined) {
|
|
3382
|
+
out['mode'] = node.mode;
|
|
3383
|
+
}
|
|
3384
|
+
if (node.children !== undefined) {
|
|
3385
|
+
out['children'] = node.children;
|
|
3386
|
+
}
|
|
3387
|
+
return out;
|
|
3388
|
+
}
|
|
3389
|
+
//#endregion
|
|
3390
|
+
//#region ---- Command ----
|
|
3391
|
+
/**
|
|
3392
|
+
* Opens a dialog that previews a widget configuration (same behavior as the preview button on
|
|
3393
|
+
* `axp-widget-field-configurator`). Invoked from that component and from entity list actions.
|
|
3394
|
+
*/
|
|
3395
|
+
class AXPPreviewWidgetFieldCommand {
|
|
3396
|
+
constructor() {
|
|
3397
|
+
this.formBuilderService = inject(AXPLayoutBuilderService);
|
|
3398
|
+
this.widgetRegistry = inject(AXPWidgetRegistryService);
|
|
3399
|
+
this.translationService = inject(AXTranslationService);
|
|
3400
|
+
this.crudService = inject(AXP_ENTITY_DEFINITION_CRUD_SERVICE, { optional: true });
|
|
3401
|
+
}
|
|
3402
|
+
async execute(input) {
|
|
3403
|
+
try {
|
|
3404
|
+
const merged = this.mergeInvocation(input);
|
|
3405
|
+
const currentWidget = this.normalizeWidget(merged['widget'] ?? merged['interface']);
|
|
3406
|
+
if (!currentWidget?.type) {
|
|
3407
|
+
return {
|
|
3408
|
+
success: false,
|
|
3409
|
+
message: {
|
|
3410
|
+
text: (await this.translationService.translateAsync('@general:messages.invalid-data')) || 'Invalid data',
|
|
3411
|
+
},
|
|
3412
|
+
};
|
|
3413
|
+
}
|
|
3414
|
+
const fieldName = String(merged['fieldName'] ?? merged['name'] ?? 'Field');
|
|
3415
|
+
const rawTitle = (merged['fieldTitle'] ?? merged['title']);
|
|
3416
|
+
const fieldTitleLabel = this.resolveFieldTitleLabel(rawTitle, fieldName);
|
|
3417
|
+
const dialogTitle = (await this.resolveWidgetDisplayTitle(currentWidget.type)) || currentWidget.type || fieldTitleLabel;
|
|
3418
|
+
const previewWidgetOptions = {
|
|
3419
|
+
...optionsBagForPreview(currentWidget),
|
|
3420
|
+
name: fieldName,
|
|
3421
|
+
...extendedNodePropsForPreview(currentWidget),
|
|
3422
|
+
};
|
|
3423
|
+
const dialogOutcome = await this.formBuilderService
|
|
3424
|
+
.create()
|
|
3425
|
+
.dialog((dialog) => {
|
|
3426
|
+
dialog
|
|
3427
|
+
.setTitle(dialogTitle)
|
|
3428
|
+
.setSize('md')
|
|
3429
|
+
.setCloseButton(true)
|
|
3430
|
+
.setContext({})
|
|
3431
|
+
.content((layoutBuilder) => {
|
|
3432
|
+
layoutBuilder.formField(fieldTitleLabel, (formField) => {
|
|
3433
|
+
formField.customWidget(currentWidget.type, previewWidgetOptions);
|
|
3434
|
+
});
|
|
3435
|
+
})
|
|
3436
|
+
.setActions((actions) => actions.cancel('@general:actions.close.title'));
|
|
3437
|
+
})
|
|
3438
|
+
.show();
|
|
3439
|
+
const cancelled = this.isCancelDialogOutcome(dialogOutcome);
|
|
3440
|
+
return {
|
|
3441
|
+
success: !cancelled,
|
|
3442
|
+
message: { text: '' },
|
|
3443
|
+
};
|
|
3444
|
+
}
|
|
3445
|
+
catch (error) {
|
|
3446
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
3447
|
+
return {
|
|
3448
|
+
success: false,
|
|
3449
|
+
message: { text: message },
|
|
3450
|
+
};
|
|
3451
|
+
}
|
|
3452
|
+
}
|
|
3453
|
+
mergeInvocation(input) {
|
|
3454
|
+
const contextOptions = input.__context__?.options;
|
|
3455
|
+
const ctxData = input.__context__?.data;
|
|
3456
|
+
const { __context__: _ctx, ...rest } = input;
|
|
3457
|
+
return {
|
|
3458
|
+
...(ctxData ?? {}),
|
|
3459
|
+
...(contextOptions ?? {}),
|
|
3460
|
+
...rest,
|
|
3461
|
+
};
|
|
3462
|
+
}
|
|
3463
|
+
normalizeWidget(raw) {
|
|
3464
|
+
if (raw == null)
|
|
3465
|
+
return null;
|
|
3466
|
+
if (typeof raw === 'string') {
|
|
3467
|
+
const t = raw.trim();
|
|
3468
|
+
return t ? { type: t, options: {} } : null;
|
|
3469
|
+
}
|
|
3470
|
+
if (typeof raw === 'object' && !Array.isArray(raw) && 'type' in raw) {
|
|
3471
|
+
const w = raw;
|
|
3472
|
+
return w.type ? cloneDeep(w) : null;
|
|
3473
|
+
}
|
|
3474
|
+
return null;
|
|
3475
|
+
}
|
|
3476
|
+
resolveFieldTitleLabel(raw, fallback) {
|
|
3477
|
+
let source = fallback;
|
|
3478
|
+
if (raw !== undefined && raw !== null) {
|
|
3479
|
+
if (typeof raw === 'string') {
|
|
3480
|
+
if (raw.trim() !== '') {
|
|
3481
|
+
source = raw;
|
|
3482
|
+
}
|
|
3483
|
+
}
|
|
3484
|
+
else if (typeof raw === 'object' && !Array.isArray(raw) && Object.keys(raw).length > 0) {
|
|
3485
|
+
source = raw;
|
|
3486
|
+
}
|
|
3487
|
+
}
|
|
3488
|
+
return this.translationService.resolve(source);
|
|
3489
|
+
}
|
|
3490
|
+
isCancelDialogOutcome(outcome) {
|
|
3491
|
+
if (outcome == null) {
|
|
3492
|
+
return false;
|
|
3493
|
+
}
|
|
3494
|
+
const ref = outcome;
|
|
3495
|
+
if (typeof ref.action !== 'function') {
|
|
3496
|
+
return false;
|
|
3497
|
+
}
|
|
3498
|
+
return ref.action() === 'cancel';
|
|
3499
|
+
}
|
|
3500
|
+
async resolveWidgetDisplayTitle(widgetType) {
|
|
3501
|
+
const crud = this.crudService;
|
|
3502
|
+
if (crud) {
|
|
3503
|
+
const interfaces = await crud.listInterfaces();
|
|
3504
|
+
const iface = interfaces.find((d) => d.name === widgetType);
|
|
3505
|
+
return iface?.title ?? iface?.name;
|
|
3506
|
+
}
|
|
3507
|
+
const config = this.widgetRegistry.getOptional(widgetType);
|
|
3508
|
+
if (!config) {
|
|
3509
|
+
return undefined;
|
|
3510
|
+
}
|
|
3511
|
+
const resolved = this.translationService.resolve(config.title);
|
|
3512
|
+
return resolved || undefined;
|
|
3513
|
+
}
|
|
3514
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPPreviewWidgetFieldCommand, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
3515
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPPreviewWidgetFieldCommand }); }
|
|
3516
|
+
}
|
|
3517
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPPreviewWidgetFieldCommand, decorators: [{
|
|
3518
|
+
type: Injectable
|
|
3519
|
+
}] });
|
|
3520
|
+
|
|
3521
|
+
var previewWidgetField_command = /*#__PURE__*/Object.freeze({
|
|
3522
|
+
__proto__: null,
|
|
3523
|
+
AXPPreviewWidgetFieldCommand: AXPPreviewWidgetFieldCommand
|
|
3524
|
+
});
|
|
3525
|
+
|
|
2480
3526
|
/**
|
|
2481
3527
|
* Generated bundle index. Do not edit.
|
|
2482
3528
|
*/
|
|
2483
3529
|
|
|
2484
|
-
export { AXPDialogRendererComponent, AXPLayoutBuilderService, AXPLayoutConversionService, AXPLayoutRendererComponent, LayoutBuilderModule };
|
|
3530
|
+
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, DEFAULT_CANCEL_DIALOG_ACTION_SHORTCUTS, DEFAULT_SUBMIT_DIALOG_ACTION_SHORTCUTS, LayoutBuilderModule, createDismissedDialogRef };
|
|
2485
3531
|
//# sourceMappingURL=acorex-platform-layout-builder.mjs.map
|