@acorex/platform 21.0.0-next.7 → 21.0.0-next.71
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/acorex-platform-auth.mjs +281 -23
- package/fesm2022/acorex-platform-auth.mjs.map +1 -1
- package/fesm2022/acorex-platform-common-common-settings.provider-Bi1RYif5.mjs +163 -0
- package/fesm2022/acorex-platform-common-common-settings.provider-Bi1RYif5.mjs.map +1 -0
- package/fesm2022/acorex-platform-common.mjs +1381 -276
- package/fesm2022/acorex-platform-common.mjs.map +1 -1
- package/fesm2022/acorex-platform-core.mjs +1538 -611
- 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 +1372 -210
- 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 +6298 -1929
- package/fesm2022/acorex-platform-layout-components.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-designer.mjs +456 -204
- package/fesm2022/acorex-platform-layout-designer.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-entity-attachments-page.component-D8iQnT-R.mjs +371 -0
- package/fesm2022/acorex-platform-layout-entity-attachments-page.component-D8iQnT-R.mjs.map +1 -0
- package/fesm2022/acorex-platform-layout-entity-file-list-popup.component-_yrP5SQe.mjs +100 -0
- package/fesm2022/acorex-platform-layout-entity-file-list-popup.component-_yrP5SQe.mjs.map +1 -0
- package/fesm2022/acorex-platform-layout-entity.mjs +22537 -9975
- package/fesm2022/acorex-platform-layout-entity.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-views.mjs +865 -218
- package/fesm2022/acorex-platform-layout-views.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-widget-core.mjs +2138 -487
- 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-BcpRkpJp.mjs} +6 -6
- package/fesm2022/acorex-platform-layout-widgets-tabular-data-edit-popup.component-BcpRkpJp.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-DQtK4lxl.mjs} +5 -5
- package/fesm2022/acorex-platform-layout-widgets-tabular-data-view-popup.component-DQtK4lxl.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 +10434 -7982
- 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-cfREo88K.mjs → acorex-platform-themes-default-error-401.component-C7EYJzSr.mjs} +4 -4
- package/fesm2022/acorex-platform-themes-default-error-401.component-C7EYJzSr.mjs.map +1 -0
- package/fesm2022/{acorex-platform-themes-default-error-404.component-CdCV5ZoA.mjs → acorex-platform-themes-default-error-404.component-7MVLMwIa.mjs} +4 -4
- package/fesm2022/acorex-platform-themes-default-error-404.component-7MVLMwIa.mjs.map +1 -0
- package/fesm2022/acorex-platform-themes-default-error-offline.component-DR6G8gPC.mjs +19 -0
- package/fesm2022/acorex-platform-themes-default-error-offline.component-DR6G8gPC.mjs.map +1 -0
- package/fesm2022/acorex-platform-themes-default.mjs +2289 -90
- 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 +790 -612
- package/fesm2022/acorex-platform-themes-shared.mjs.map +1 -1
- package/fesm2022/acorex-platform-workflow.mjs +978 -238
- package/fesm2022/acorex-platform-workflow.mjs.map +1 -1
- package/fesm2022/acorex-platform.mjs.map +1 -1
- package/package.json +40 -38
- package/{auth/index.d.ts → types/acorex-platform-auth.d.ts} +241 -4
- package/{common/index.d.ts → types/acorex-platform-common.d.ts} +833 -89
- package/{core/index.d.ts → types/acorex-platform-core.d.ts} +779 -164
- 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} +277 -55
- package/types/acorex-platform-layout-components.d.ts +3257 -0
- package/{layout/designer/index.d.ts → types/acorex-platform-layout-designer.d.ts} +96 -18
- package/types/acorex-platform-layout-entity.d.ts +4492 -0
- package/{layout/views/index.d.ts → types/acorex-platform-layout-views.d.ts} +247 -62
- package/{layout/widget-core/index.d.ts → types/acorex-platform-layout-widget-core.d.ts} +437 -131
- package/{layout/widgets/index.d.ts → types/acorex-platform-layout-widgets.d.ts} +1140 -506
- 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} +254 -7
- 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} +620 -617
- 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-DfJEx_bs.mjs +0 -1542
- package/fesm2022/acorex-platform-themes-default-entity-master-list-view.component-DfJEx_bs.mjs.map +0 -1
- package/fesm2022/acorex-platform-themes-default-entity-master-modify-view.component-Ua3ZA5hk.mjs +0 -101
- package/fesm2022/acorex-platform-themes-default-entity-master-modify-view.component-Ua3ZA5hk.mjs.map +0 -1
- package/fesm2022/acorex-platform-themes-default-entity-master-single-view.component-eMBby9k4.mjs.map +0 -1
- package/fesm2022/acorex-platform-themes-default-error-401.component-cfREo88K.mjs.map +0 -1
- package/fesm2022/acorex-platform-themes-default-error-404.component-CdCV5ZoA.mjs.map +0 -1
- package/fesm2022/acorex-platform-themes-default-error-offline.component-E7SzBcAt.mjs +0 -19
- package/fesm2022/acorex-platform-themes-default-error-offline.component-E7SzBcAt.mjs.map +0 -1
- package/fesm2022/acorex-platform-themes-shared-icon-chooser-column.component-C0EpfU2k.mjs.map +0 -1
- package/fesm2022/acorex-platform-themes-shared-icon-chooser-view.component-9W52W6Nu.mjs.map +0 -1
- package/fesm2022/acorex-platform-themes-shared-settings.provider-DSs1o1M6.mjs.map +0 -1
- package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-column.component-DTnfRy5f.mjs +0 -65
- package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-column.component-DTnfRy5f.mjs.map +0 -1
- package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-view.component-DY0JtT1v.mjs +0 -64
- package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-view.component-DY0JtT1v.mjs.map +0 -1
- package/layout/components/index.d.ts +0 -1669
- package/layout/entity/index.d.ts +0 -2287
- package/runtime/index.d.ts +0 -307
- /package/{index.d.ts → types/acorex-platform.d.ts} +0 -0
|
@@ -1,24 +1,208 @@
|
|
|
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 { AXPHookService, AXPExpressionEvaluatorService, captureFormContextBaseline, isFormContextDirty, 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 { AXOverlayService } from '@acorex/cdk/overlay';
|
|
12
16
|
import * as i1$1 from '@acorex/components/button';
|
|
13
17
|
import { AXButtonModule } from '@acorex/components/button';
|
|
18
|
+
import { AXDialogService } from '@acorex/components/dialog';
|
|
14
19
|
import * as i2$1 from '@acorex/components/decorators';
|
|
15
20
|
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 { AXP_ENTITY_DEFINITION_CRUD_SERVICE } from '@acorex/platform/domain';
|
|
27
|
+
|
|
28
|
+
//#region ---- Dialog Action Shortcut Utilities ----
|
|
29
|
+
const DEFAULT_CANCEL_DIALOG_ACTION_SHORTCUTS = ['Esc'];
|
|
30
|
+
const DEFAULT_SUBMIT_DIALOG_ACTION_SHORTCUTS = ['Enter', 'ctrl+s'];
|
|
31
|
+
const PRIMARY_DIALOG_ACTION_COMMANDS = new Set(['submit', 'create', 'entity-form-done']);
|
|
32
|
+
/**
|
|
33
|
+
* Parses a shortcut string such as `Enter`, `Escape`, or `ctrl+shift+s`.
|
|
34
|
+
*/
|
|
35
|
+
function parseDialogActionShortcut(shortcut) {
|
|
36
|
+
const parts = shortcut
|
|
37
|
+
.trim()
|
|
38
|
+
.toLowerCase()
|
|
39
|
+
.split('+')
|
|
40
|
+
.map((part) => part.trim())
|
|
41
|
+
.filter(Boolean);
|
|
42
|
+
const key = normalizeShortcutToken(parts.pop() ?? '');
|
|
43
|
+
return {
|
|
44
|
+
ctrl: parts.includes('ctrl') || parts.includes('control'),
|
|
45
|
+
shift: parts.includes('shift'),
|
|
46
|
+
alt: parts.includes('alt') || parts.includes('option'),
|
|
47
|
+
meta: parts.includes('meta') || parts.includes('cmd') || parts.includes('command'),
|
|
48
|
+
key,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Returns true when the keyboard event matches the given shortcut definition.
|
|
53
|
+
*/
|
|
54
|
+
function matchesDialogActionShortcut(event, shortcut) {
|
|
55
|
+
const parsed = parseDialogActionShortcut(shortcut);
|
|
56
|
+
if (parsed.ctrl !== event.ctrlKey) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
if (parsed.shift !== event.shiftKey) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
if (parsed.alt !== event.altKey) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
if (parsed.meta !== event.metaKey) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
return normalizeShortcutKey(event) === normalizeShortcutToken(parsed.key);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Resolves footer action shortcuts: defaults when omitted, merge when extras are provided, none when `[]`.
|
|
72
|
+
*/
|
|
73
|
+
function resolveDialogActionShortcuts(defaults, overrides) {
|
|
74
|
+
if (overrides !== undefined) {
|
|
75
|
+
if (overrides.length === 0) {
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
return dedupeDialogActionShortcuts([...defaults, ...overrides]);
|
|
79
|
+
}
|
|
80
|
+
return defaults.length ? [...defaults] : undefined;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Applies built-in footer shortcuts when actions are declared without `shortcuts`
|
|
84
|
+
* (e.g. workflow `show-layout-popup` raw action config).
|
|
85
|
+
*/
|
|
86
|
+
function resolveConfiguredFooterActionShortcuts(command, explicit) {
|
|
87
|
+
if (explicit !== undefined) {
|
|
88
|
+
return resolveDialogActionShortcuts([], explicit);
|
|
89
|
+
}
|
|
90
|
+
if (command === 'cancel') {
|
|
91
|
+
return resolveDialogActionShortcuts(DEFAULT_CANCEL_DIALOG_ACTION_SHORTCUTS, undefined);
|
|
92
|
+
}
|
|
93
|
+
if (command && PRIMARY_DIALOG_ACTION_COMMANDS.has(command)) {
|
|
94
|
+
return resolveDialogActionShortcuts(DEFAULT_SUBMIT_DIALOG_ACTION_SHORTCUTS, undefined);
|
|
95
|
+
}
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
function dedupeDialogActionShortcuts(shortcuts) {
|
|
99
|
+
const seen = new Set();
|
|
100
|
+
const result = [];
|
|
101
|
+
for (const shortcut of shortcuts) {
|
|
102
|
+
const token = normalizeShortcutToken(shortcut);
|
|
103
|
+
if (!token || seen.has(token)) {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
seen.add(token);
|
|
107
|
+
result.push(shortcut.trim());
|
|
108
|
+
}
|
|
109
|
+
return result;
|
|
110
|
+
}
|
|
111
|
+
function normalizeShortcutToken(key) {
|
|
112
|
+
const normalized = key.trim().toLowerCase();
|
|
113
|
+
if (normalized === 'esc') {
|
|
114
|
+
return 'escape';
|
|
115
|
+
}
|
|
116
|
+
return normalized;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Whether a shortcut should fire given the current focus target.
|
|
120
|
+
* Enter without modifiers is suppressed in multiline / rich-text fields.
|
|
121
|
+
*/
|
|
122
|
+
function shouldTriggerDialogActionShortcut(event, shortcut) {
|
|
123
|
+
const parsed = parseDialogActionShortcut(shortcut);
|
|
124
|
+
const isPlainEnter = parsed.key === 'enter' && !parsed.ctrl && !parsed.shift && !parsed.alt && !parsed.meta;
|
|
125
|
+
if (!isPlainEnter) {
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
return !isKeyboardTargetMultilineEditable(event.target);
|
|
129
|
+
}
|
|
130
|
+
function normalizeShortcutKey(event) {
|
|
131
|
+
if (event.key === 'Enter') {
|
|
132
|
+
return 'enter';
|
|
133
|
+
}
|
|
134
|
+
if (event.key === 'Escape') {
|
|
135
|
+
return 'escape';
|
|
136
|
+
}
|
|
137
|
+
if (event.key === ' ') {
|
|
138
|
+
return 'space';
|
|
139
|
+
}
|
|
140
|
+
if (event.key.length === 1) {
|
|
141
|
+
return event.key.toLowerCase();
|
|
142
|
+
}
|
|
143
|
+
const codeMatch = event.code.match(/^Key([A-Z])$/);
|
|
144
|
+
if (codeMatch) {
|
|
145
|
+
return codeMatch[1].toLowerCase();
|
|
146
|
+
}
|
|
147
|
+
const digitMatch = event.code.match(/^Digit([0-9])$/);
|
|
148
|
+
if (digitMatch) {
|
|
149
|
+
return digitMatch[1];
|
|
150
|
+
}
|
|
151
|
+
return event.key.toLowerCase();
|
|
152
|
+
}
|
|
153
|
+
function isKeyboardTargetMultilineEditable(target) {
|
|
154
|
+
if (!(target instanceof HTMLElement)) {
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
if (target.isContentEditable) {
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
const textarea = target.closest('textarea');
|
|
161
|
+
if (textarea && !textarea.readOnly && !textarea.disabled) {
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
const richText = target.closest('[contenteditable="true"]');
|
|
165
|
+
return richText instanceof HTMLElement;
|
|
166
|
+
}
|
|
167
|
+
//#endregion
|
|
168
|
+
|
|
169
|
+
/** Fallback {@link AXPDialogRef} when the popup is dismissed without a footer action (e.g. header close). */
|
|
170
|
+
function createDismissedDialogRef(context = () => ({})) {
|
|
171
|
+
return {
|
|
172
|
+
close: () => { },
|
|
173
|
+
context,
|
|
174
|
+
action: () => 'cancel',
|
|
175
|
+
setLoading: () => { },
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
//#endregion
|
|
179
|
+
|
|
180
|
+
//#region ---- Imports ----
|
|
181
|
+
//#endregion
|
|
182
|
+
//#region ---- Before open ----
|
|
183
|
+
/**
|
|
184
|
+
* Runs after dialog options and context are prepared and **before** footer customization and popup open.
|
|
185
|
+
* Listeners may mutate {@link AXPLayoutBuilderDialogBeforeOpenPayload.context} by reference.
|
|
186
|
+
*/
|
|
187
|
+
const AXP_LAYOUT_BUILDER_DIALOG_BEFORE_OPEN_HOOK_KEY = 'layout-builder.dialog.before-open';
|
|
188
|
+
//#endregion
|
|
189
|
+
//#region ---- Footer actions ----
|
|
190
|
+
/**
|
|
191
|
+
* Runs after builder-defined footer actions exist and **before** the dialog opens.
|
|
192
|
+
* Listeners receive the live `actions.footer.prefix` / `suffix` arrays (same references as the dialog)
|
|
193
|
+
* so they may push, splice, filter, or replace items. They may also mutate {@link AXPLayoutBuilderDialogFooterPayload.context} by reference.
|
|
194
|
+
*/
|
|
195
|
+
const AXP_LAYOUT_BUILDER_DIALOG_CONFIG_HOOK_KEY = 'layout-builder.dialog.config';
|
|
196
|
+
//#endregion
|
|
197
|
+
//#region ---- Context updates (after open) ----
|
|
198
|
+
/**
|
|
199
|
+
* Runs whenever the dialog layout builder context changes (debounced upstream), **after** the popup is visible.
|
|
200
|
+
* Use for side effects that depend on live context (for example values updated by widgets after render).
|
|
201
|
+
* Payload mirrors `AXPDialogRendererComponent` semantics: {@link AXPLayoutBuilderDialogContextChangedPayload.getContext},
|
|
202
|
+
* {@link AXPLayoutBuilderDialogContextChangedPayload.patchContext}, and optional loading state.
|
|
203
|
+
*/
|
|
204
|
+
const AXP_LAYOUT_BUILDER_DIALOG_CONTEXT_CHANGED_HOOK_KEY = 'layout-builder.dialog.context-changed';
|
|
205
|
+
//#endregion
|
|
22
206
|
|
|
23
207
|
class AXPLayoutConversionService {
|
|
24
208
|
constructor() {
|
|
@@ -158,6 +342,10 @@ class AXPLayoutConversionService {
|
|
|
158
342
|
if (!editorWidget.mode) {
|
|
159
343
|
editorWidget.mode = fieldMode;
|
|
160
344
|
}
|
|
345
|
+
const hintOpts = field.description != null &&
|
|
346
|
+
(typeof field.description !== 'string' || field.description.trim().length > 0)
|
|
347
|
+
? { hint: field.description, hintDisplayMode: 'note' }
|
|
348
|
+
: {};
|
|
161
349
|
return {
|
|
162
350
|
type: 'form-field',
|
|
163
351
|
name: field.path,
|
|
@@ -165,8 +353,8 @@ class AXPLayoutConversionService {
|
|
|
165
353
|
options: {
|
|
166
354
|
label: field.title,
|
|
167
355
|
badge: field.badge,
|
|
168
|
-
description: field.description,
|
|
169
356
|
showLabel: true,
|
|
357
|
+
...hintOpts,
|
|
170
358
|
},
|
|
171
359
|
children: [editorWidget], // The editor widget becomes a child of form-field
|
|
172
360
|
};
|
|
@@ -209,7 +397,7 @@ class AXPLayoutConversionService {
|
|
|
209
397
|
path: formFieldNode.name || editorWidget.name || `field-${Date.now()}`,
|
|
210
398
|
title: formFieldNode.options?.['label'],
|
|
211
399
|
badge: formFieldNode.options?.['badge'],
|
|
212
|
-
description: formFieldNode.options?.['
|
|
400
|
+
description: formFieldNode.options?.['hint'],
|
|
213
401
|
widget: editorWidget,
|
|
214
402
|
mode: formFieldNode.mode,
|
|
215
403
|
};
|
|
@@ -222,9 +410,16 @@ class AXPLayoutConversionService {
|
|
|
222
410
|
const keyParts = [];
|
|
223
411
|
keyParts.push(`groups:${formDefinition.groups.length}`);
|
|
224
412
|
formDefinition.groups.forEach((group, groupIndex) => {
|
|
225
|
-
|
|
413
|
+
// Include group.mode so view vs edit (or mixed) layouts do not share a cached widget tree.
|
|
414
|
+
const groupModePart = group.mode ?? '_';
|
|
415
|
+
keyParts.push(`g${groupIndex}:${group.name}:${group.parameters.length}:${groupModePart}`);
|
|
416
|
+
keyParts.push(`gL${groupIndex}:${JSON.stringify(group.title ?? null)}:${JSON.stringify(group.description ?? null)}`);
|
|
226
417
|
group.parameters.forEach((param, paramIndex) => {
|
|
227
|
-
|
|
418
|
+
// Field mode must be part of the key; otherwise metadata forms that only differ by
|
|
419
|
+
// view/edit (same paths and widget types) incorrectly reuse the first cached tree.
|
|
420
|
+
const fieldModePart = param.mode ?? '_';
|
|
421
|
+
keyParts.push(`p${groupIndex}.${paramIndex}:${param.path}:${param.widget.type}:${fieldModePart}`);
|
|
422
|
+
keyParts.push(`pL${groupIndex}.${paramIndex}:${JSON.stringify(param.title ?? null)}:${JSON.stringify(param.description ?? null)}:${JSON.stringify(param.badge ?? null)}`);
|
|
228
423
|
});
|
|
229
424
|
});
|
|
230
425
|
if (formDefinition.mode) {
|
|
@@ -285,16 +480,31 @@ class AXPLayoutConversionService {
|
|
|
285
480
|
}
|
|
286
481
|
return Math.abs(hash).toString(36); // Convert to base36 for shorter string
|
|
287
482
|
}
|
|
288
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
289
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
483
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLayoutConversionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
484
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLayoutConversionService, providedIn: 'root' }); }
|
|
290
485
|
}
|
|
291
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
486
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLayoutConversionService, decorators: [{
|
|
292
487
|
type: Injectable,
|
|
293
488
|
args: [{
|
|
294
489
|
providedIn: 'root',
|
|
295
490
|
}]
|
|
296
491
|
}] });
|
|
297
492
|
|
|
493
|
+
//#region ---- Dialog Close Confirmation Utilities ----
|
|
494
|
+
function normalizeDialogCloseConfirmation(value) {
|
|
495
|
+
if (value === undefined || value === false) {
|
|
496
|
+
return undefined;
|
|
497
|
+
}
|
|
498
|
+
if (value === true) {
|
|
499
|
+
return { enabled: true };
|
|
500
|
+
}
|
|
501
|
+
if (value.enabled === false) {
|
|
502
|
+
return undefined;
|
|
503
|
+
}
|
|
504
|
+
return { enabled: true, ...value };
|
|
505
|
+
}
|
|
506
|
+
//#endregion
|
|
507
|
+
|
|
298
508
|
//#region ---- Inheritance Utilities ----
|
|
299
509
|
/**
|
|
300
510
|
* Resolves inherited properties from context and local values
|
|
@@ -361,17 +571,13 @@ function collectDefaultValues(node, context = {}, isTopLevel = true) {
|
|
|
361
571
|
const result = isTopLevel ? cloneDeep(context) : context;
|
|
362
572
|
// Check if this node has a defaultValue and a path
|
|
363
573
|
// 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;
|
|
574
|
+
const defaultValue = node.defaultValue !== undefined ? node.defaultValue : node.options?.defaultValue;
|
|
367
575
|
if (defaultValue !== undefined && !isNil(defaultValue) && node.path) {
|
|
368
576
|
// Check if path exists in context using lodash get equivalent check
|
|
369
577
|
const currentValue = getNestedValue(result, node.path);
|
|
370
578
|
if (currentValue === undefined) {
|
|
371
579
|
// 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);
|
|
580
|
+
const clonedValue = defaultValue instanceof Date ? new Date(defaultValue.getTime()) : cloneDeep(defaultValue);
|
|
375
581
|
set(result, node.path, clonedValue);
|
|
376
582
|
}
|
|
377
583
|
}
|
|
@@ -402,17 +608,18 @@ function getNestedValue(obj, path) {
|
|
|
402
608
|
class AXPLayoutBuilderService {
|
|
403
609
|
constructor() {
|
|
404
610
|
this.popupService = inject(AXPopupService);
|
|
611
|
+
this.hookService = inject(AXPHookService, { optional: true }) ?? undefined;
|
|
405
612
|
}
|
|
406
613
|
/**
|
|
407
614
|
* Create a new layout builder
|
|
408
615
|
*/
|
|
409
616
|
create() {
|
|
410
|
-
return new LayoutBuilder(this.popupService);
|
|
617
|
+
return new LayoutBuilder(this.popupService, this.hookService);
|
|
411
618
|
}
|
|
412
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
413
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
619
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLayoutBuilderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
620
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLayoutBuilderService, providedIn: 'root' }); }
|
|
414
621
|
}
|
|
415
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
622
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLayoutBuilderService, decorators: [{
|
|
416
623
|
type: Injectable,
|
|
417
624
|
args: [{
|
|
418
625
|
providedIn: 'root',
|
|
@@ -425,8 +632,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImpo
|
|
|
425
632
|
* Open/Closed: Extensible through container delegates
|
|
426
633
|
*/
|
|
427
634
|
class LayoutBuilder {
|
|
428
|
-
constructor(popupService) {
|
|
635
|
+
constructor(popupService, hookService) {
|
|
429
636
|
this.popupService = popupService;
|
|
637
|
+
this.hookService = hookService;
|
|
430
638
|
this.root = {
|
|
431
639
|
children: [],
|
|
432
640
|
mode: 'edit',
|
|
@@ -453,7 +661,19 @@ class LayoutBuilder {
|
|
|
453
661
|
if (delegate) {
|
|
454
662
|
delegate(container);
|
|
455
663
|
}
|
|
456
|
-
|
|
664
|
+
const built = container.build();
|
|
665
|
+
// Step/dialog content usually calls flex() once; replace the default empty flex root instead of nesting flex inside flex.
|
|
666
|
+
if (this.root.type === 'flex-layout' &&
|
|
667
|
+
(!this.root.children || this.root.children.length === 0) &&
|
|
668
|
+
!this.root.options) {
|
|
669
|
+
this.root = built;
|
|
670
|
+
}
|
|
671
|
+
else {
|
|
672
|
+
if (!this.root.children) {
|
|
673
|
+
this.root.children = [];
|
|
674
|
+
}
|
|
675
|
+
this.root.children.push(built);
|
|
676
|
+
}
|
|
457
677
|
return this;
|
|
458
678
|
}
|
|
459
679
|
panel(delegate) {
|
|
@@ -496,7 +716,7 @@ class LayoutBuilder {
|
|
|
496
716
|
if (!this.popupService) {
|
|
497
717
|
throw new Error('LayoutBuilder requires AXPopupService to create dialogs. Please inject it in the service constructor.');
|
|
498
718
|
}
|
|
499
|
-
const container = new DialogContainerBuilder(this.popupService);
|
|
719
|
+
const container = new DialogContainerBuilder(this.popupService, this.hookService);
|
|
500
720
|
if (delegate) {
|
|
501
721
|
delegate(container);
|
|
502
722
|
}
|
|
@@ -521,11 +741,21 @@ class LayoutBuilder {
|
|
|
521
741
|
return this;
|
|
522
742
|
}
|
|
523
743
|
build() {
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
mode:
|
|
744
|
+
const r = this.root;
|
|
745
|
+
const node = {
|
|
746
|
+
type: r.type,
|
|
747
|
+
...(r.mode !== undefined ? { mode: r.mode } : {}),
|
|
748
|
+
...(r.children !== undefined ? { children: r.children } : {}),
|
|
749
|
+
...(r.options !== undefined ? { options: r.options } : {}),
|
|
750
|
+
...(r.name !== undefined ? { name: r.name } : {}),
|
|
751
|
+
...(r.path !== undefined ? { path: r.path } : {}),
|
|
752
|
+
...(r.visible !== undefined ? { visible: r.visible } : {}),
|
|
753
|
+
...(r.defaultValue !== undefined ? { defaultValue: r.defaultValue } : {}),
|
|
754
|
+
...(r.triggers !== undefined ? { triggers: r.triggers } : {}),
|
|
755
|
+
...(r.meta !== undefined ? { meta: r.meta } : {}),
|
|
756
|
+
...(r.valueTransforms !== undefined ? { valueTransforms: r.valueTransforms } : {}),
|
|
528
757
|
};
|
|
758
|
+
return node;
|
|
529
759
|
}
|
|
530
760
|
/**
|
|
531
761
|
* Converts the built widget node to JSON string
|
|
@@ -594,6 +824,7 @@ class BaseContainerBuilder {
|
|
|
594
824
|
'number-editor',
|
|
595
825
|
'select-editor',
|
|
596
826
|
'lookup-editor',
|
|
827
|
+
'entity-definition-provider-editor',
|
|
597
828
|
'selection-list-editor',
|
|
598
829
|
'date-time-editor',
|
|
599
830
|
'toggle-editor',
|
|
@@ -696,24 +927,30 @@ class BaseContainerMixin extends BaseContainerBuilder {
|
|
|
696
927
|
class LayoutContainerMixin extends BaseContainerMixin {
|
|
697
928
|
layout(value) {
|
|
698
929
|
// Map layout intent to grid item sizing so containers like `form-field`
|
|
699
|
-
// can span multiple columns inside grid/fieldset layouts.
|
|
930
|
+
// can span multiple columns/rows inside grid/fieldset layouts.
|
|
700
931
|
if (!this.containerState.options)
|
|
701
932
|
this.containerState.options = {};
|
|
702
933
|
if (typeof value === 'number') {
|
|
703
|
-
// Direct numeric shorthand → colSpan
|
|
704
934
|
this.containerState.options.colSpan = value;
|
|
705
935
|
}
|
|
706
936
|
else if (value) {
|
|
707
|
-
// Try to extract a reasonable colSpan from breakpoint positions
|
|
708
937
|
const positions = value.positions;
|
|
709
938
|
if (positions) {
|
|
710
|
-
const
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
939
|
+
const placement = positions?.lg ?? positions?.xl ?? positions?.xxl ?? positions?.md ?? positions?.sm;
|
|
940
|
+
if (placement) {
|
|
941
|
+
const opts = this.containerState.options;
|
|
942
|
+
if (placement.colSpan != null)
|
|
943
|
+
opts.colSpan = placement.colSpan;
|
|
944
|
+
if (placement.colStart != null)
|
|
945
|
+
opts.colStart = placement.colStart;
|
|
946
|
+
if (placement.colEnd != null)
|
|
947
|
+
opts.colEnd = placement.colEnd;
|
|
948
|
+
if (placement.rowSpan != null)
|
|
949
|
+
opts.rowSpan = placement.rowSpan;
|
|
950
|
+
if (placement.rowStart != null)
|
|
951
|
+
opts.rowStart = placement.rowStart;
|
|
952
|
+
if (placement.rowEnd != null)
|
|
953
|
+
opts.rowEnd = placement.rowEnd;
|
|
717
954
|
}
|
|
718
955
|
}
|
|
719
956
|
}
|
|
@@ -932,6 +1169,7 @@ class WidgetContainerMixin extends ChildContainerMixin {
|
|
|
932
1169
|
'number-editor',
|
|
933
1170
|
'select-editor',
|
|
934
1171
|
'lookup-editor',
|
|
1172
|
+
'entity-definition-provider-editor',
|
|
935
1173
|
'selection-list-editor',
|
|
936
1174
|
'date-time-editor',
|
|
937
1175
|
'toggle-editor',
|
|
@@ -987,12 +1225,73 @@ class FlexContainerBuilder extends WidgetContainerMixin {
|
|
|
987
1225
|
* Grid Container Builder - Liskov Substitution Principle
|
|
988
1226
|
* Extends WidgetContainerMixin to inherit all common functionality
|
|
989
1227
|
*/
|
|
1228
|
+
/**
|
|
1229
|
+
* Extracts flat grid-item options from AXPGridLayoutOptions for grid-item-layout widget.
|
|
1230
|
+
* Uses first available breakpoint (lg, xl, md, sm).
|
|
1231
|
+
*/
|
|
1232
|
+
/**
|
|
1233
|
+
* Deep-merges grid breakpoint buckets so sequential fluent calls (e.g. setColumns then setGap)
|
|
1234
|
+
* do not wipe sibling keys under options.grid.default.
|
|
1235
|
+
*/
|
|
1236
|
+
function mergeAXPGridContainerOptions(prev, patch) {
|
|
1237
|
+
const next = { ...(prev ?? {}), ...patch };
|
|
1238
|
+
if (prev?.grid?.default || patch.grid?.default) {
|
|
1239
|
+
next.grid = {
|
|
1240
|
+
...(prev?.grid ?? {}),
|
|
1241
|
+
...(patch.grid ?? {}),
|
|
1242
|
+
default: {
|
|
1243
|
+
...(prev?.grid?.default ?? {}),
|
|
1244
|
+
...(patch.grid?.default ?? {}),
|
|
1245
|
+
},
|
|
1246
|
+
};
|
|
1247
|
+
}
|
|
1248
|
+
return next;
|
|
1249
|
+
}
|
|
1250
|
+
function toGridItemOptions(layoutOptions) {
|
|
1251
|
+
if (!layoutOptions?.positions)
|
|
1252
|
+
return { colSpan: 12 };
|
|
1253
|
+
const positions = layoutOptions.positions;
|
|
1254
|
+
const placement = positions['lg'] ?? positions['xl'] ?? positions['xxl'] ?? positions['md'] ?? positions['sm'];
|
|
1255
|
+
if (!placement)
|
|
1256
|
+
return { colSpan: 12 };
|
|
1257
|
+
const opts = {};
|
|
1258
|
+
if (placement['colSpan'] != null)
|
|
1259
|
+
opts['colSpan'] = placement['colSpan'];
|
|
1260
|
+
if (placement['colStart'] != null)
|
|
1261
|
+
opts['colStart'] = placement['colStart'];
|
|
1262
|
+
if (placement['colEnd'] != null)
|
|
1263
|
+
opts['colEnd'] = placement['colEnd'];
|
|
1264
|
+
if (placement['rowSpan'] != null)
|
|
1265
|
+
opts['rowSpan'] = placement['rowSpan'];
|
|
1266
|
+
if (placement['rowStart'] != null)
|
|
1267
|
+
opts['rowStart'] = placement['rowStart'];
|
|
1268
|
+
if (placement['rowEnd'] != null)
|
|
1269
|
+
opts['rowEnd'] = placement['rowEnd'];
|
|
1270
|
+
if (Object.keys(opts).length === 0)
|
|
1271
|
+
opts['colSpan'] = 12;
|
|
1272
|
+
return opts;
|
|
1273
|
+
}
|
|
990
1274
|
class GridContainerBuilder extends WidgetContainerMixin {
|
|
991
1275
|
constructor() {
|
|
992
1276
|
super('grid-layout');
|
|
993
1277
|
}
|
|
994
1278
|
setOptions(options) {
|
|
995
|
-
this.containerState.options =
|
|
1279
|
+
this.containerState.options = mergeAXPGridContainerOptions(this.containerState.options, options);
|
|
1280
|
+
return this;
|
|
1281
|
+
}
|
|
1282
|
+
item(layoutOptions, delegate) {
|
|
1283
|
+
const fieldset = new FieldsetContainerBuilder();
|
|
1284
|
+
fieldset.withInheritanceContext(this.inheritanceContext);
|
|
1285
|
+
delegate(fieldset);
|
|
1286
|
+
const fieldsetNode = fieldset.build();
|
|
1287
|
+
const gridItemOptions = toGridItemOptions(layoutOptions);
|
|
1288
|
+
const gridItemNode = {
|
|
1289
|
+
type: 'grid-item-layout',
|
|
1290
|
+
options: gridItemOptions,
|
|
1291
|
+
children: [fieldsetNode],
|
|
1292
|
+
};
|
|
1293
|
+
this.ensureChildren();
|
|
1294
|
+
this.containerState.children.push(gridItemNode);
|
|
996
1295
|
return this;
|
|
997
1296
|
}
|
|
998
1297
|
// Individual fluent methods for Grid
|
|
@@ -1164,10 +1463,34 @@ class FormFieldBuilder extends LayoutContainerMixin {
|
|
|
1164
1463
|
child.type(type);
|
|
1165
1464
|
child.name(finalName);
|
|
1166
1465
|
child.path(widgetPath);
|
|
1167
|
-
//
|
|
1168
|
-
const { name: _, ...cleanOptions } = (options || {});
|
|
1466
|
+
// Extract extended properties from options (triggers, meta, valueTransforms, mode, visible, defaultValue)
|
|
1467
|
+
const { name: _, triggers, meta, valueTransforms, mode: extendedMode, visible: extendedVisible, defaultValue: extendedDefaultValue, children: extendedChildren, ...cleanOptions } = (options || {});
|
|
1169
1468
|
child.withInheritanceContext(this.inheritanceContext);
|
|
1170
1469
|
child.options(cleanOptions);
|
|
1470
|
+
// Apply extended properties if provided
|
|
1471
|
+
if (extendedMode !== undefined) {
|
|
1472
|
+
child.mode(extendedMode);
|
|
1473
|
+
}
|
|
1474
|
+
if (extendedVisible !== undefined) {
|
|
1475
|
+
child.visible(extendedVisible);
|
|
1476
|
+
}
|
|
1477
|
+
if (extendedDefaultValue !== undefined) {
|
|
1478
|
+
child.defaultValue(extendedDefaultValue);
|
|
1479
|
+
}
|
|
1480
|
+
// Set triggers, meta, and valueTransforms directly on widgetState
|
|
1481
|
+
// These are part of AXPWidgetNode but not handled by WidgetBuilder methods
|
|
1482
|
+
if (triggers !== undefined) {
|
|
1483
|
+
child.widgetState.triggers = triggers;
|
|
1484
|
+
}
|
|
1485
|
+
if (meta !== undefined) {
|
|
1486
|
+
child.widgetState.meta = meta;
|
|
1487
|
+
}
|
|
1488
|
+
if (valueTransforms !== undefined) {
|
|
1489
|
+
child.widgetState.valueTransforms = valueTransforms;
|
|
1490
|
+
}
|
|
1491
|
+
if (extendedChildren !== undefined) {
|
|
1492
|
+
child.widgetState.children = extendedChildren;
|
|
1493
|
+
}
|
|
1171
1494
|
// IMPORTANT: Store the widget builder, don't build it yet!
|
|
1172
1495
|
// This allows properties set after this method (like disabled, readonly) to be applied
|
|
1173
1496
|
this.childWidget = child;
|
|
@@ -1399,7 +1722,7 @@ class ListWidgetBuilder extends WidgetContainerMixin {
|
|
|
1399
1722
|
* Uses composition instead of inheritance for cleaner separation
|
|
1400
1723
|
*/
|
|
1401
1724
|
class DialogContainerBuilder {
|
|
1402
|
-
constructor(popupService) {
|
|
1725
|
+
constructor(popupService, hookService) {
|
|
1403
1726
|
this.dialogState = {
|
|
1404
1727
|
type: 'flex-layout', // This will be overridden when content layout exists
|
|
1405
1728
|
children: [],
|
|
@@ -1422,6 +1745,7 @@ class DialogContainerBuilder {
|
|
|
1422
1745
|
else {
|
|
1423
1746
|
this.popupService = inject(AXPopupService);
|
|
1424
1747
|
}
|
|
1748
|
+
this.hookService = hookService ?? inject(AXPHookService, { optional: true }) ?? undefined;
|
|
1425
1749
|
}
|
|
1426
1750
|
setOptions(options) {
|
|
1427
1751
|
this.dialogState.dialogOptions = { ...this.dialogState.dialogOptions, ...options };
|
|
@@ -1461,6 +1785,18 @@ class DialogContainerBuilder {
|
|
|
1461
1785
|
}
|
|
1462
1786
|
return this;
|
|
1463
1787
|
}
|
|
1788
|
+
onAction(handler) {
|
|
1789
|
+
this.dialogState.dialogOptions ??= {
|
|
1790
|
+
title: '',
|
|
1791
|
+
size: 'md',
|
|
1792
|
+
closeButton: false,
|
|
1793
|
+
};
|
|
1794
|
+
this.dialogState.dialogOptions.onAction = handler;
|
|
1795
|
+
return this;
|
|
1796
|
+
}
|
|
1797
|
+
confirmCloseWhenDirty(options) {
|
|
1798
|
+
return this.setOptions({ confirmCloseWhenDirty: options ?? true });
|
|
1799
|
+
}
|
|
1464
1800
|
addCustomAction(action) {
|
|
1465
1801
|
// Add to actions based on position
|
|
1466
1802
|
const position = action.position || 'suffix';
|
|
@@ -1507,20 +1843,44 @@ class DialogContainerBuilder {
|
|
|
1507
1843
|
const dialogNode = this.build();
|
|
1508
1844
|
// Import the dialog renderer component dynamically
|
|
1509
1845
|
const { AXPDialogRendererComponent } = await Promise.resolve().then(function () { return dialogRenderer_component; });
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1846
|
+
this.dialogState.dialogOptions ??= {};
|
|
1847
|
+
if (this.dialogState.dialogOptions.context == null || typeof this.dialogState.dialogOptions.context !== 'object') {
|
|
1848
|
+
this.dialogState.dialogOptions.context = {};
|
|
1849
|
+
}
|
|
1850
|
+
const initialContext = this.dialogState.dialogOptions.context;
|
|
1851
|
+
this.dialogState.actions ??= { footer: { prefix: [], suffix: [] } };
|
|
1852
|
+
this.dialogState.actions.footer ??= { prefix: [], suffix: [] };
|
|
1853
|
+
this.dialogState.actions.footer.prefix ??= [];
|
|
1854
|
+
this.dialogState.actions.footer.suffix ??= [];
|
|
1855
|
+
const hookService = this.hookService;
|
|
1856
|
+
if (hookService) {
|
|
1857
|
+
const beforePayload = {
|
|
1858
|
+
context: initialContext,
|
|
1859
|
+
dialogOptions: this.dialogState.dialogOptions,
|
|
1860
|
+
};
|
|
1861
|
+
await hookService.runAsync(AXP_LAYOUT_BUILDER_DIALOG_BEFORE_OPEN_HOOK_KEY, beforePayload);
|
|
1862
|
+
}
|
|
1863
|
+
const confirmCloseWhenDirty = normalizeDialogCloseConfirmation(this.dialogState.dialogOptions?.confirmCloseWhenDirty);
|
|
1513
1864
|
// Create dialog configuration
|
|
1514
1865
|
const dialogConfig = {
|
|
1515
1866
|
title: this.dialogState.dialogOptions?.title || '',
|
|
1516
|
-
|
|
1517
|
-
|
|
1867
|
+
//TODO: why we need message?
|
|
1868
|
+
//message: this.dialogState.dialogOptions?.message,
|
|
1869
|
+
context: initialContext,
|
|
1518
1870
|
definition: dialogNode,
|
|
1871
|
+
metadata: this.dialogState.dialogOptions.metadata,
|
|
1519
1872
|
actions: this.dialogState.actions,
|
|
1873
|
+
onAction: this.dialogState.dialogOptions?.onAction,
|
|
1874
|
+
confirmCloseWhenDirty,
|
|
1520
1875
|
};
|
|
1876
|
+
//
|
|
1877
|
+
if (hookService) {
|
|
1878
|
+
await hookService.runAsync(AXP_LAYOUT_BUILDER_DIALOG_CONFIG_HOOK_KEY, dialogConfig);
|
|
1879
|
+
}
|
|
1521
1880
|
// The Promise resolves when user clicks an action button
|
|
1522
1881
|
return new Promise(async (resolve) => {
|
|
1523
|
-
|
|
1882
|
+
let flag = false;
|
|
1883
|
+
await this.popupService.open(AXPDialogRendererComponent, {
|
|
1524
1884
|
title: dialogConfig.title,
|
|
1525
1885
|
size: this.dialogState.dialogOptions?.size || 'md',
|
|
1526
1886
|
closeButton: this.dialogState.dialogOptions?.closeButton || false,
|
|
@@ -1529,11 +1889,14 @@ class DialogContainerBuilder {
|
|
|
1529
1889
|
data: {
|
|
1530
1890
|
config: dialogConfig,
|
|
1531
1891
|
callBack: (result) => {
|
|
1532
|
-
|
|
1892
|
+
flag = true;
|
|
1533
1893
|
resolve(result);
|
|
1534
1894
|
},
|
|
1535
1895
|
},
|
|
1536
1896
|
});
|
|
1897
|
+
if (!flag) {
|
|
1898
|
+
resolve(createDismissedDialogRef());
|
|
1899
|
+
}
|
|
1537
1900
|
});
|
|
1538
1901
|
}
|
|
1539
1902
|
}
|
|
@@ -1548,6 +1911,7 @@ class WidgetBuilder {
|
|
|
1548
1911
|
this.widgetState = {
|
|
1549
1912
|
type: 'widget',
|
|
1550
1913
|
options: {},
|
|
1914
|
+
children: [],
|
|
1551
1915
|
};
|
|
1552
1916
|
this.inheritanceContext = {};
|
|
1553
1917
|
if (name) {
|
|
@@ -1611,6 +1975,7 @@ class WidgetBuilder {
|
|
|
1611
1975
|
this.widgetState.options = {};
|
|
1612
1976
|
}
|
|
1613
1977
|
this.widgetState.options['visible'] = condition;
|
|
1978
|
+
this.widgetState.visible = condition;
|
|
1614
1979
|
this.inheritanceContext.visible = condition;
|
|
1615
1980
|
return this;
|
|
1616
1981
|
}
|
|
@@ -1642,6 +2007,10 @@ class WidgetBuilder {
|
|
|
1642
2007
|
this.inheritanceContext.direction = direction;
|
|
1643
2008
|
return this;
|
|
1644
2009
|
}
|
|
2010
|
+
children(children) {
|
|
2011
|
+
this.widgetState.children = children;
|
|
2012
|
+
return this;
|
|
2013
|
+
}
|
|
1645
2014
|
// Inheritance context methods
|
|
1646
2015
|
withInheritanceContext(context) {
|
|
1647
2016
|
this.inheritanceContext = mergeInheritanceContext(context);
|
|
@@ -1665,6 +2034,7 @@ class WidgetBuilder {
|
|
|
1665
2034
|
}
|
|
1666
2035
|
if (resolved.visible !== undefined) {
|
|
1667
2036
|
this.widgetState.options['visible'] = resolved.visible;
|
|
2037
|
+
this.widgetState.visible = resolved.visible;
|
|
1668
2038
|
}
|
|
1669
2039
|
if (context.defaultValue !== undefined) {
|
|
1670
2040
|
this.widgetState.defaultValue = context.defaultValue;
|
|
@@ -1675,14 +2045,29 @@ class WidgetBuilder {
|
|
|
1675
2045
|
return { ...this.inheritanceContext };
|
|
1676
2046
|
}
|
|
1677
2047
|
build() {
|
|
1678
|
-
|
|
2048
|
+
const node = {
|
|
1679
2049
|
name: this.widgetState.name,
|
|
1680
2050
|
type: this.widgetState.type,
|
|
1681
2051
|
options: this.widgetState.options,
|
|
1682
2052
|
mode: this.widgetState.mode,
|
|
1683
2053
|
path: this.widgetState.path,
|
|
1684
2054
|
defaultValue: this.widgetState.defaultValue,
|
|
2055
|
+
children: this.widgetState.children,
|
|
1685
2056
|
};
|
|
2057
|
+
// Add extended properties if they exist
|
|
2058
|
+
if (this.widgetState.triggers !== undefined) {
|
|
2059
|
+
node.triggers = this.widgetState.triggers;
|
|
2060
|
+
}
|
|
2061
|
+
if (this.widgetState.meta !== undefined) {
|
|
2062
|
+
node.meta = this.widgetState.meta;
|
|
2063
|
+
}
|
|
2064
|
+
if (this.widgetState.valueTransforms !== undefined) {
|
|
2065
|
+
node.valueTransforms = this.widgetState.valueTransforms;
|
|
2066
|
+
}
|
|
2067
|
+
if (this.widgetState.visible !== undefined) {
|
|
2068
|
+
node.visible = this.widgetState.visible;
|
|
2069
|
+
}
|
|
2070
|
+
return node;
|
|
1686
2071
|
}
|
|
1687
2072
|
}
|
|
1688
2073
|
//#region ---- Action Builder Implementation ----
|
|
@@ -1690,35 +2075,48 @@ class ActionBuilder {
|
|
|
1690
2075
|
constructor(dialogBuilder) {
|
|
1691
2076
|
this.dialogBuilder = dialogBuilder;
|
|
1692
2077
|
}
|
|
1693
|
-
cancel(text) {
|
|
2078
|
+
cancel(text, options) {
|
|
1694
2079
|
if (!this.dialogBuilder['dialogState'].actions.footer.suffix) {
|
|
1695
2080
|
this.dialogBuilder['dialogState'].actions.footer.suffix = [];
|
|
1696
2081
|
}
|
|
1697
2082
|
this.dialogBuilder['dialogState'].actions.footer.suffix.push({
|
|
1698
2083
|
title: text || '@general:actions.cancel.title',
|
|
1699
|
-
icon: 'fa-times',
|
|
1700
2084
|
color: 'default',
|
|
1701
2085
|
command: { name: 'cancel' },
|
|
2086
|
+
shortcuts: resolveDialogActionShortcuts(DEFAULT_CANCEL_DIALOG_ACTION_SHORTCUTS, options?.shortcuts),
|
|
1702
2087
|
});
|
|
1703
2088
|
return this;
|
|
1704
2089
|
}
|
|
1705
|
-
submit(text) {
|
|
2090
|
+
submit(text, options) {
|
|
1706
2091
|
if (!this.dialogBuilder['dialogState'].actions.footer.suffix) {
|
|
1707
2092
|
this.dialogBuilder['dialogState'].actions.footer.suffix = [];
|
|
1708
2093
|
}
|
|
1709
2094
|
this.dialogBuilder['dialogState'].actions.footer.suffix.push({
|
|
1710
2095
|
title: text || '@general:actions.submit.title',
|
|
1711
|
-
icon: 'fa-check',
|
|
1712
2096
|
color: 'primary',
|
|
1713
2097
|
command: { name: 'submit', options: { validate: true } },
|
|
2098
|
+
shortcuts: resolveDialogActionShortcuts(DEFAULT_SUBMIT_DIALOG_ACTION_SHORTCUTS, options?.shortcuts),
|
|
1714
2099
|
});
|
|
1715
2100
|
return this;
|
|
1716
2101
|
}
|
|
1717
|
-
custom(action) {
|
|
1718
|
-
|
|
1719
|
-
|
|
2102
|
+
custom(action, options) {
|
|
2103
|
+
const item = {
|
|
2104
|
+
...action,
|
|
2105
|
+
shortcuts: resolveDialogActionShortcuts(action.shortcuts ?? [], options?.shortcuts),
|
|
2106
|
+
};
|
|
2107
|
+
const position = item.position ?? 'suffix';
|
|
2108
|
+
if (position === 'prefix') {
|
|
2109
|
+
if (!this.dialogBuilder['dialogState'].actions.footer.prefix) {
|
|
2110
|
+
this.dialogBuilder['dialogState'].actions.footer.prefix = [];
|
|
2111
|
+
}
|
|
2112
|
+
this.dialogBuilder['dialogState'].actions.footer.prefix.push(item);
|
|
2113
|
+
}
|
|
2114
|
+
else {
|
|
2115
|
+
if (!this.dialogBuilder['dialogState'].actions.footer.suffix) {
|
|
2116
|
+
this.dialogBuilder['dialogState'].actions.footer.suffix = [];
|
|
2117
|
+
}
|
|
2118
|
+
this.dialogBuilder['dialogState'].actions.footer.suffix.push(item);
|
|
1720
2119
|
}
|
|
1721
|
-
this.dialogBuilder['dialogState'].actions.footer.suffix.push(action);
|
|
1722
2120
|
return this;
|
|
1723
2121
|
}
|
|
1724
2122
|
}
|
|
@@ -1867,22 +2265,27 @@ class AXPLayoutRendererComponent {
|
|
|
1867
2265
|
/**
|
|
1868
2266
|
* Form definition containing groups and fields OR widget tree
|
|
1869
2267
|
*/
|
|
1870
|
-
this.layout = input.required(...(ngDevMode ? [{ debugName: "layout" }] : []));
|
|
2268
|
+
this.layout = input.required(...(ngDevMode ? [{ debugName: "layout" }] : /* istanbul ignore next */ []));
|
|
1871
2269
|
/**
|
|
1872
2270
|
* Form context/model data
|
|
1873
2271
|
*/
|
|
1874
|
-
this.context = model({}, ...(ngDevMode ? [{ debugName: "context" }] : []));
|
|
2272
|
+
this.context = model({}, ...(ngDevMode ? [{ debugName: "context" }] : /* istanbul ignore next */ []));
|
|
1875
2273
|
/**
|
|
1876
2274
|
* Form appearance and density styling (normal, compact, spacious)
|
|
1877
2275
|
*/
|
|
1878
|
-
this.look = input('fieldset', ...(ngDevMode ? [{ debugName: "look" }] : []));
|
|
2276
|
+
this.look = input('fieldset', ...(ngDevMode ? [{ debugName: "look" }] : /* istanbul ignore next */ []));
|
|
1879
2277
|
/**
|
|
1880
2278
|
* Default form mode. Can be overridden by section/group and field.
|
|
1881
2279
|
*/
|
|
1882
|
-
this.mode = input('edit', ...(ngDevMode ? [{ debugName: "mode" }] : []));
|
|
2280
|
+
this.mode = input('edit', ...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
|
|
1883
2281
|
//#endregion
|
|
1884
2282
|
//#region ---- Widget Tree Conversion ----
|
|
1885
|
-
this.widgetTree = signal(null, ...(ngDevMode ? [{ debugName: "widgetTree" }] : []));
|
|
2283
|
+
this.widgetTree = signal(null, ...(ngDevMode ? [{ debugName: "widgetTree" }] : /* istanbul ignore next */ []));
|
|
2284
|
+
/**
|
|
2285
|
+
* Prefer explicit {@link AXPWidgetNode.mode} on the root node (e.g. dialog flex `mode('view')`)
|
|
2286
|
+
* so nested widgets resolve view vs edit correctly; fall back to the layout `mode` input.
|
|
2287
|
+
*/
|
|
2288
|
+
this.effectiveRenderMode = computed(() => this.widgetTree()?.mode ?? this.mode(), ...(ngDevMode ? [{ debugName: "effectiveRenderMode" }] : /* istanbul ignore next */ []));
|
|
1886
2289
|
/**
|
|
1887
2290
|
* Convert layout data to widget tree when inputs change
|
|
1888
2291
|
*/
|
|
@@ -1907,7 +2310,7 @@ class AXPLayoutRendererComponent {
|
|
|
1907
2310
|
if (!isEqual(prev, tree)) {
|
|
1908
2311
|
this.widgetTree.set(tree);
|
|
1909
2312
|
}
|
|
1910
|
-
}, ...(ngDevMode ? [{ debugName: "conversionEffect" }] : []));
|
|
2313
|
+
}, ...(ngDevMode ? [{ debugName: "conversionEffect" }] : /* istanbul ignore next */ []));
|
|
1911
2314
|
//#endregion
|
|
1912
2315
|
//#region ---- Outputs ----
|
|
1913
2316
|
/**
|
|
@@ -1920,12 +2323,12 @@ class AXPLayoutRendererComponent {
|
|
|
1920
2323
|
this.validityChange = output();
|
|
1921
2324
|
//#endregion
|
|
1922
2325
|
//#region ---- Properties ----
|
|
1923
|
-
this.form = viewChild(AXFormComponent, ...(ngDevMode ? [{ debugName: "form" }] : []));
|
|
1924
|
-
this.container = viewChild(AXPWidgetContainerComponent, ...(ngDevMode ? [{ debugName: "container" }] : []));
|
|
2326
|
+
this.form = viewChild(AXFormComponent, ...(ngDevMode ? [{ debugName: "form" }] : /* istanbul ignore next */ []));
|
|
2327
|
+
this.container = viewChild(AXPWidgetContainerComponent, ...(ngDevMode ? [{ debugName: "container" }] : /* istanbul ignore next */ []));
|
|
1925
2328
|
/**
|
|
1926
2329
|
* Internal context signal for reactivity
|
|
1927
2330
|
*/
|
|
1928
|
-
this.internalContext = signal({}, ...(ngDevMode ? [{ debugName: "internalContext" }] : []));
|
|
2331
|
+
this.internalContext = signal({}, ...(ngDevMode ? [{ debugName: "internalContext" }] : /* istanbul ignore next */ []));
|
|
1929
2332
|
/**
|
|
1930
2333
|
* Initial context for reset functionality
|
|
1931
2334
|
*/
|
|
@@ -1938,7 +2341,7 @@ class AXPLayoutRendererComponent {
|
|
|
1938
2341
|
this.#contextSyncEffect = effect(() => {
|
|
1939
2342
|
const ctx = this.context() ?? {};
|
|
1940
2343
|
this.contextUpdateSubject.next(ctx);
|
|
1941
|
-
}, ...(ngDevMode ? [{ debugName: "#contextSyncEffect" }] : []));
|
|
2344
|
+
}, ...(ngDevMode ? [{ debugName: "#contextSyncEffect" }] : /* istanbul ignore next */ []));
|
|
1942
2345
|
/**
|
|
1943
2346
|
* Effect to handle widget tree status changes
|
|
1944
2347
|
*/
|
|
@@ -1947,7 +2350,7 @@ class AXPLayoutRendererComponent {
|
|
|
1947
2350
|
if (widgetTree) {
|
|
1948
2351
|
this.container()?.builderService.setStatus(AXPPageStatus.Rendered);
|
|
1949
2352
|
}
|
|
1950
|
-
}, ...(ngDevMode ? [{ debugName: "#widgetStatusEffect" }] : []));
|
|
2353
|
+
}, ...(ngDevMode ? [{ debugName: "#widgetStatusEffect" }] : /* istanbul ignore next */ []));
|
|
1951
2354
|
}
|
|
1952
2355
|
//#endregion
|
|
1953
2356
|
//#region ---- Lifecycle Methods ----
|
|
@@ -2087,99 +2490,462 @@ class AXPLayoutRendererComponent {
|
|
|
2087
2490
|
isWidgetNode(data) {
|
|
2088
2491
|
return data && typeof data === 'object' && 'type' in data && typeof data.type === 'string';
|
|
2089
2492
|
}
|
|
2090
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
2091
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
2493
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLayoutRendererComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2494
|
+
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
2495
|
<ax-form>
|
|
2093
2496
|
<axp-widgets-container [context]="internalContext()" (onContextChanged)="handleContextChanged($event)">
|
|
2094
2497
|
@if (widgetTree()) {
|
|
2095
|
-
<ng-container
|
|
2498
|
+
<ng-container
|
|
2499
|
+
axp-widget-renderer
|
|
2500
|
+
[node]="widgetTree()!"
|
|
2501
|
+
[mode]="effectiveRenderMode()"
|
|
2502
|
+
></ng-container>
|
|
2096
2503
|
}
|
|
2097
2504
|
</axp-widgets-container>
|
|
2098
2505
|
</ax-form>
|
|
2099
|
-
`, isInline: true, styles: [":host{display:block;width:100%}\n"], dependencies: [{ kind: "ngmodule", type:
|
|
2506
|
+
`, 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
2507
|
}
|
|
2101
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
2508
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLayoutRendererComponent, decorators: [{
|
|
2102
2509
|
type: Component,
|
|
2103
|
-
args: [{ selector: 'axp-layout-renderer', standalone: true, imports: [
|
|
2510
|
+
args: [{ selector: 'axp-layout-renderer', standalone: true, imports: [AXPWidgetCoreModule, AXFormModule], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
2104
2511
|
<ax-form>
|
|
2105
2512
|
<axp-widgets-container [context]="internalContext()" (onContextChanged)="handleContextChanged($event)">
|
|
2106
2513
|
@if (widgetTree()) {
|
|
2107
|
-
<ng-container
|
|
2514
|
+
<ng-container
|
|
2515
|
+
axp-widget-renderer
|
|
2516
|
+
[node]="widgetTree()!"
|
|
2517
|
+
[mode]="effectiveRenderMode()"
|
|
2518
|
+
></ng-container>
|
|
2108
2519
|
}
|
|
2109
2520
|
</axp-widgets-container>
|
|
2110
2521
|
</ax-form>
|
|
2111
2522
|
`, styles: [":host{display:block;width:100%}\n"] }]
|
|
2112
2523
|
}], 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
2524
|
|
|
2525
|
+
/** Registration key for {@link AXPPreviewWidgetFieldCommand}; lives alone so `LayoutBuilderModule` can reference it without static-importing the command implementation. */
|
|
2526
|
+
const AXP_PREVIEW_WIDGET_FIELD_COMMAND_KEY = 'Widget:Preview';
|
|
2527
|
+
|
|
2114
2528
|
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: "
|
|
2529
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: LayoutBuilderModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
|
|
2530
|
+
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.9", ngImport: i0, type: LayoutBuilderModule, imports: [CommonModule, AXPLayoutRendererComponent], exports: [AXPLayoutRendererComponent] }); }
|
|
2531
|
+
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: LayoutBuilderModule, providers: [
|
|
2532
|
+
AXPLayoutBuilderService,
|
|
2533
|
+
provideCommandSetups([
|
|
2534
|
+
{
|
|
2535
|
+
key: AXP_PREVIEW_WIDGET_FIELD_COMMAND_KEY,
|
|
2536
|
+
command: () => Promise.resolve().then(function () { return previewWidgetField_command; }).then((c) => c.AXPPreviewWidgetFieldCommand),
|
|
2537
|
+
},
|
|
2538
|
+
]),
|
|
2539
|
+
], imports: [CommonModule, AXPLayoutRendererComponent] }); }
|
|
2118
2540
|
}
|
|
2119
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
2541
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: LayoutBuilderModule, decorators: [{
|
|
2120
2542
|
type: NgModule,
|
|
2121
2543
|
args: [{
|
|
2122
2544
|
imports: [CommonModule, AXPLayoutRendererComponent],
|
|
2123
|
-
providers: [
|
|
2545
|
+
providers: [
|
|
2546
|
+
AXPLayoutBuilderService,
|
|
2547
|
+
provideCommandSetups([
|
|
2548
|
+
{
|
|
2549
|
+
key: AXP_PREVIEW_WIDGET_FIELD_COMMAND_KEY,
|
|
2550
|
+
command: () => Promise.resolve().then(function () { return previewWidgetField_command; }).then((c) => c.AXPPreviewWidgetFieldCommand),
|
|
2551
|
+
},
|
|
2552
|
+
]),
|
|
2553
|
+
],
|
|
2124
2554
|
exports: [AXPLayoutRendererComponent],
|
|
2125
2555
|
}]
|
|
2126
2556
|
}] });
|
|
2127
2557
|
|
|
2128
|
-
|
|
2558
|
+
const OVERLAY_CONTAINER_SELECTOR = '.ax-overlay-container';
|
|
2559
|
+
const OVERLAY_PANE_SELECTOR = '.ax-overlay-pane';
|
|
2560
|
+
/**
|
|
2561
|
+
* Returns true when a nested overlay is open and Esc should close it first.
|
|
2562
|
+
* Returns false when only the dialog shell is on top.
|
|
2563
|
+
* Returns null when the Acorex overlay service does not expose open-state queries yet.
|
|
2564
|
+
*/
|
|
2565
|
+
function hasOpenNestedOverlayFromAcorexService(overlayService, dialogOverlay) {
|
|
2566
|
+
const service = overlayService;
|
|
2567
|
+
if (typeof service.hasOpenAnchoredOverlay === 'function') {
|
|
2568
|
+
if (!service.hasOpenAnchoredOverlay()) {
|
|
2569
|
+
return false;
|
|
2570
|
+
}
|
|
2571
|
+
if (typeof service.hasOverlayAbove === 'function') {
|
|
2572
|
+
return service.hasOverlayAbove(dialogOverlay);
|
|
2573
|
+
}
|
|
2574
|
+
return true;
|
|
2575
|
+
}
|
|
2576
|
+
if (typeof service.hasOverlayAbove === 'function') {
|
|
2577
|
+
return service.hasOverlayAbove(dialogOverlay);
|
|
2578
|
+
}
|
|
2579
|
+
return null;
|
|
2580
|
+
}
|
|
2581
|
+
/**
|
|
2582
|
+
* Returns true when the overlay element is rendered and visible in the viewport.
|
|
2583
|
+
*/
|
|
2584
|
+
function isVisibleOverlayElement(element) {
|
|
2585
|
+
if (!(element instanceof HTMLElement)) {
|
|
2586
|
+
return false;
|
|
2587
|
+
}
|
|
2588
|
+
const style = getComputedStyle(element);
|
|
2589
|
+
if (style.display === 'none' || style.visibility === 'hidden' || Number(style.opacity) === 0) {
|
|
2590
|
+
return false;
|
|
2591
|
+
}
|
|
2592
|
+
const rect = element.getBoundingClientRect();
|
|
2593
|
+
return rect.width > 0 && rect.height > 0;
|
|
2594
|
+
}
|
|
2595
|
+
/**
|
|
2596
|
+
* Collects visible overlay containers in stacking order (later / higher z-index wins).
|
|
2597
|
+
*/
|
|
2598
|
+
function getVisibleOverlayContainers(document) {
|
|
2599
|
+
return Array.from(document.querySelectorAll(OVERLAY_CONTAINER_SELECTOR))
|
|
2600
|
+
.filter(isVisibleOverlayElement)
|
|
2601
|
+
.sort(compareOverlayStackOrder);
|
|
2602
|
+
}
|
|
2603
|
+
/**
|
|
2604
|
+
* Compares two overlay containers by z-index, then DOM order.
|
|
2605
|
+
*/
|
|
2606
|
+
function compareOverlayStackOrder(a, b) {
|
|
2607
|
+
const za = Number(getComputedStyle(a).zIndex) || 0;
|
|
2608
|
+
const zb = Number(getComputedStyle(b).zIndex) || 0;
|
|
2609
|
+
if (za !== zb) {
|
|
2610
|
+
return za - zb;
|
|
2611
|
+
}
|
|
2612
|
+
const position = a.compareDocumentPosition(b);
|
|
2613
|
+
if (position & Node.DOCUMENT_POSITION_FOLLOWING) {
|
|
2614
|
+
return -1;
|
|
2615
|
+
}
|
|
2616
|
+
if (position & Node.DOCUMENT_POSITION_PRECEDING) {
|
|
2617
|
+
return 1;
|
|
2618
|
+
}
|
|
2619
|
+
return 0;
|
|
2620
|
+
}
|
|
2621
|
+
/**
|
|
2622
|
+
* Returns visible anchored overlay panes that belong to widget popovers, not the dialog shell.
|
|
2623
|
+
*/
|
|
2624
|
+
function getNestedOverlayPanes(document, dialogOverlay) {
|
|
2625
|
+
return Array.from(document.querySelectorAll(OVERLAY_PANE_SELECTOR)).filter((pane) => isVisibleOverlayElement(pane) && !dialogOverlay?.contains(pane));
|
|
2626
|
+
}
|
|
2627
|
+
/**
|
|
2628
|
+
* DOM fallback until {@link AXOverlayService} exposes open-overlay queries.
|
|
2629
|
+
*
|
|
2630
|
+
* Dialog popups use a centered `.ax-overlay-container` without `.ax-overlay-pane`.
|
|
2631
|
+
* Widget popovers (select, datetime, dropdown) use anchored containers with `.ax-overlay-pane`.
|
|
2632
|
+
*/
|
|
2633
|
+
function shouldDeferEscapeToNestedOverlayFromDom(document, dialogHost) {
|
|
2634
|
+
const dialogOverlay = dialogHost.closest(OVERLAY_CONTAINER_SELECTOR);
|
|
2635
|
+
if (getNestedOverlayPanes(document, dialogOverlay).length > 0) {
|
|
2636
|
+
return true;
|
|
2637
|
+
}
|
|
2638
|
+
const visibleContainers = getVisibleOverlayContainers(document);
|
|
2639
|
+
if (!visibleContainers.length) {
|
|
2640
|
+
return false;
|
|
2641
|
+
}
|
|
2642
|
+
const topContainer = visibleContainers[visibleContainers.length - 1];
|
|
2643
|
+
if (!dialogOverlay) {
|
|
2644
|
+
return topContainer.contains(dialogHost) === false;
|
|
2645
|
+
}
|
|
2646
|
+
return topContainer !== dialogOverlay;
|
|
2647
|
+
}
|
|
2648
|
+
/**
|
|
2649
|
+
* When a nested overlay is above this dialog (widget popover, confirm box, etc.), footer shortcuts
|
|
2650
|
+
* should not run on the parent dialog.
|
|
2651
|
+
*/
|
|
2652
|
+
function shouldDeferDialogShortcutsToNestedOverlay(document, dialogHost, overlayService) {
|
|
2653
|
+
const dialogOverlay = dialogHost.closest(OVERLAY_CONTAINER_SELECTOR);
|
|
2654
|
+
if (overlayService) {
|
|
2655
|
+
const fromService = hasOpenNestedOverlayFromAcorexService(overlayService, dialogOverlay);
|
|
2656
|
+
if (fromService !== null) {
|
|
2657
|
+
return fromService;
|
|
2658
|
+
}
|
|
2659
|
+
}
|
|
2660
|
+
return shouldDeferEscapeToNestedOverlayFromDom(document, dialogHost);
|
|
2661
|
+
}
|
|
2662
|
+
|
|
2663
|
+
/**
|
|
2664
|
+
* Builds the `show()` resolve payload for layout-builder custom footer commands (fallback path).
|
|
2665
|
+
*/
|
|
2666
|
+
function buildLayoutBuilderCustomActionRef(createDialogRef, command) {
|
|
2667
|
+
return {
|
|
2668
|
+
...createDialogRef(command),
|
|
2669
|
+
action: () => command,
|
|
2670
|
+
};
|
|
2671
|
+
}
|
|
2672
|
+
/**
|
|
2673
|
+
* Keeps legacy popup `data.context` / `data.action` side fields in sync for custom footer commands.
|
|
2674
|
+
*/
|
|
2675
|
+
function syncLegacyDialogDataSideFields(data, context, action) {
|
|
2676
|
+
if (!data) {
|
|
2677
|
+
return;
|
|
2678
|
+
}
|
|
2679
|
+
data.context = context;
|
|
2680
|
+
data.action = action;
|
|
2681
|
+
}
|
|
2682
|
+
/**
|
|
2683
|
+
* Cancel from an `onAction` handler should route through the dirty-close gate only when configured.
|
|
2684
|
+
* Otherwise preserve the original resolve-then-close sequence.
|
|
2685
|
+
*/
|
|
2686
|
+
function shouldRouteOnActionCancelThroughDismissGate(confirmCloseWhenDirtyEnabled) {
|
|
2687
|
+
return confirmCloseWhenDirtyEnabled === true;
|
|
2688
|
+
}
|
|
2129
2689
|
|
|
2690
|
+
/** Idle period after the last context change before capturing the clean baseline. */
|
|
2691
|
+
const DIALOG_DIRTY_BASELINE_IDLE_MS = 500;
|
|
2692
|
+
/** Hard fallback when idle detection never settles. */
|
|
2693
|
+
const DIALOG_DIRTY_BASELINE_FALLBACK_DELAY_MS = 2000;
|
|
2694
|
+
/** Debounce after widget count stabilizes before re-evaluating footer actions. */
|
|
2695
|
+
const DIALOG_WIDGET_COUNT_STABLE_DELAY_MS = 350;
|
|
2130
2696
|
class AXPDialogRendererComponent extends AXBasePageComponent {
|
|
2131
2697
|
constructor() {
|
|
2132
2698
|
super(...arguments);
|
|
2133
2699
|
this.result = new EventEmitter();
|
|
2134
2700
|
this.expressionEvaluator = inject(AXPExpressionEvaluatorService);
|
|
2135
|
-
this.
|
|
2701
|
+
this.commandService = inject(AXPCommandService);
|
|
2702
|
+
this.hookService = inject(AXPHookService, { optional: true });
|
|
2703
|
+
this.dialogService = inject(AXDialogService);
|
|
2704
|
+
this.overlayService = inject(AXOverlayService);
|
|
2705
|
+
this.translationService = inject(AXTranslationService);
|
|
2706
|
+
this.document = inject(DOCUMENT);
|
|
2707
|
+
this.host = inject((ElementRef));
|
|
2708
|
+
/** Ensures `show()` resolves once when the dialog closes (footer action or header close). */
|
|
2709
|
+
this.callbackInvoked = false;
|
|
2710
|
+
/** True after the post-init baseline snapshot has been captured. */
|
|
2711
|
+
this.dirtyBaselineCaptured = false;
|
|
2712
|
+
/** Skips dirty confirmation on the next {@link onClosing} (successful submit / programmatic close). */
|
|
2713
|
+
this.skipNextOnClosingDirtyCheck = false;
|
|
2714
|
+
this.destroyRef = inject(DestroyRef);
|
|
2715
|
+
this.context = signal({}, ...(ngDevMode ? [{ debugName: "context" }] : /* istanbul ignore next */ []));
|
|
2136
2716
|
// This will be set by the popup service automatically - same as dynamic-dialog
|
|
2137
2717
|
this.callBack = () => { };
|
|
2138
|
-
this.isDialogLoading = signal(false, ...(ngDevMode ? [{ debugName: "isDialogLoading" }] : []));
|
|
2718
|
+
this.isDialogLoading = signal(false, ...(ngDevMode ? [{ debugName: "isDialogLoading" }] : /* istanbul ignore next */ []));
|
|
2139
2719
|
// Aggregated actions for footer rendering
|
|
2140
|
-
this.footerPrefix = signal([], ...(ngDevMode ? [{ debugName: "footerPrefix" }] : []));
|
|
2141
|
-
this.footerSuffix = signal([], ...(ngDevMode ? [{ debugName: "footerSuffix" }] : []));
|
|
2720
|
+
this.footerPrefix = signal([], ...(ngDevMode ? [{ debugName: "footerPrefix" }] : /* istanbul ignore next */ []));
|
|
2721
|
+
this.footerSuffix = signal([], ...(ngDevMode ? [{ debugName: "footerSuffix" }] : /* istanbul ignore next */ []));
|
|
2722
|
+
/**
|
|
2723
|
+
* Correlate layout context snapshots for distributed hooks (`layout-builder.dialog.context-changed`).
|
|
2724
|
+
*/
|
|
2725
|
+
this.contextChangedHooksSessionKey = typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function'
|
|
2726
|
+
? crypto.randomUUID()
|
|
2727
|
+
: `layout-dialog-ctx-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
2728
|
+
/**
|
|
2729
|
+
* Capture-phase footer shortcuts — runs before widget editors (select, date, text) that stop keydown bubbling.
|
|
2730
|
+
*/
|
|
2731
|
+
this.onDialogShortcutCapture = (event) => {
|
|
2732
|
+
if (this.isDialogLoading()) {
|
|
2733
|
+
return;
|
|
2734
|
+
}
|
|
2735
|
+
// Unsaved-changes confirm is modal — parent footer shortcuts must not run underneath it.
|
|
2736
|
+
if (this.dismissConfirmPromise) {
|
|
2737
|
+
return;
|
|
2738
|
+
}
|
|
2739
|
+
if (shouldDeferDialogShortcutsToNestedOverlay(this.document, this.host.nativeElement, this.overlayService)) {
|
|
2740
|
+
return;
|
|
2741
|
+
}
|
|
2742
|
+
if (event.key === 'Escape' && this.config?.confirmCloseWhenDirty?.enabled) {
|
|
2743
|
+
event.preventDefault();
|
|
2744
|
+
event.stopImmediatePropagation();
|
|
2745
|
+
this.requestDismiss();
|
|
2746
|
+
return;
|
|
2747
|
+
}
|
|
2748
|
+
const actions = [...this.footerPrefixActions(), ...this.footerSuffixActions()];
|
|
2749
|
+
for (const action of actions) {
|
|
2750
|
+
if (action.disabled || action.hidden || !action.shortcuts?.length) {
|
|
2751
|
+
continue;
|
|
2752
|
+
}
|
|
2753
|
+
for (const shortcut of action.shortcuts) {
|
|
2754
|
+
if (event.key === 'Escape' && this.config?.confirmCloseWhenDirty?.enabled) {
|
|
2755
|
+
continue;
|
|
2756
|
+
}
|
|
2757
|
+
if (!matchesDialogActionShortcut(event, shortcut) || !shouldTriggerDialogActionShortcut(event, shortcut)) {
|
|
2758
|
+
continue;
|
|
2759
|
+
}
|
|
2760
|
+
event.preventDefault();
|
|
2761
|
+
event.stopImmediatePropagation();
|
|
2762
|
+
void this.executeAction(action);
|
|
2763
|
+
return;
|
|
2764
|
+
}
|
|
2765
|
+
}
|
|
2766
|
+
};
|
|
2142
2767
|
//#endregion
|
|
2143
2768
|
//#region ---- View Accessors ----
|
|
2144
2769
|
// 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;
|
|
2770
|
+
this.layoutRenderer = viewChild(AXPLayoutRendererComponent, ...(ngDevMode ? [{ debugName: "layoutRenderer" }] : /* istanbul ignore next */ []));
|
|
2771
|
+
this.#widgetActionsEffect = effect(() => {
|
|
2148
2772
|
this.aggregateAndEvaluateActions();
|
|
2149
2773
|
if (!this.widgetCoreService) {
|
|
2150
2774
|
const renderer = this.layoutRenderer();
|
|
2151
2775
|
const container = renderer?.getContainer();
|
|
2152
2776
|
this.widgetCoreService = container?.builderService ?? null;
|
|
2153
|
-
|
|
2777
|
+
return;
|
|
2154
2778
|
}
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2779
|
+
this.widgetCoreService.registeredWidgetsCount();
|
|
2780
|
+
if (this.debounceTimer) {
|
|
2781
|
+
clearTimeout(this.debounceTimer);
|
|
2782
|
+
}
|
|
2783
|
+
this.debounceTimer = setTimeout(() => {
|
|
2784
|
+
this.aggregateAndEvaluateActions();
|
|
2785
|
+
}, DIALOG_WIDGET_COUNT_STABLE_DELAY_MS);
|
|
2786
|
+
}, ...(ngDevMode ? [{ debugName: "#widgetActionsEffect" }] : /* istanbul ignore next */ []));
|
|
2787
|
+
this.#dirtyBaselineEffect = effect(() => {
|
|
2788
|
+
if (this.dirtyBaselineCaptured) {
|
|
2789
|
+
return;
|
|
2165
2790
|
}
|
|
2166
|
-
|
|
2791
|
+
const store = this.getWidgetContextStore();
|
|
2792
|
+
if (!store) {
|
|
2793
|
+
return;
|
|
2794
|
+
}
|
|
2795
|
+
store.data();
|
|
2796
|
+
this.widgetCoreService?.registeredWidgetsCount();
|
|
2797
|
+
this.scheduleDirtyBaselineCapture();
|
|
2798
|
+
}, ...(ngDevMode ? [{ debugName: "#dirtyBaselineEffect" }] : /* istanbul ignore next */ []));
|
|
2167
2799
|
}
|
|
2168
2800
|
//#endregion
|
|
2169
2801
|
//#region ---- Lifecycle ----
|
|
2170
2802
|
ngOnInit() {
|
|
2171
|
-
|
|
2172
|
-
this.context.set(
|
|
2803
|
+
const initialContext = this.config?.context || {};
|
|
2804
|
+
this.context.set(initialContext);
|
|
2805
|
+
this.dirtyBaselineFallbackTimer = setTimeout(() => {
|
|
2806
|
+
this.captureDirtyBaselineIfNeeded();
|
|
2807
|
+
}, DIALOG_DIRTY_BASELINE_FALLBACK_DELAY_MS);
|
|
2808
|
+
this.document.addEventListener('keydown', this.onDialogShortcutCapture, true);
|
|
2809
|
+
this.destroyRef.onDestroy(() => {
|
|
2810
|
+
this.document.removeEventListener('keydown', this.onDialogShortcutCapture, true);
|
|
2811
|
+
if (this.dirtyBaselineIdleTimer) {
|
|
2812
|
+
clearTimeout(this.dirtyBaselineIdleTimer);
|
|
2813
|
+
}
|
|
2814
|
+
if (this.dirtyBaselineFallbackTimer) {
|
|
2815
|
+
clearTimeout(this.dirtyBaselineFallbackTimer);
|
|
2816
|
+
}
|
|
2817
|
+
});
|
|
2818
|
+
void this.invokeLayoutContextChangedHooks();
|
|
2819
|
+
}
|
|
2820
|
+
//#endregion
|
|
2821
|
+
//#region ---- Popup Close Gate ----
|
|
2822
|
+
/**
|
|
2823
|
+
* Popup shell hook — handles header **X** and **Esc** (when `closeButton` is enabled).
|
|
2824
|
+
* This is the only place that prompts for unsaved changes.
|
|
2825
|
+
*/
|
|
2826
|
+
async onClosing(e) {
|
|
2827
|
+
if (this.dismissConfirmPromise) {
|
|
2828
|
+
e.cancel = true;
|
|
2829
|
+
return;
|
|
2830
|
+
}
|
|
2831
|
+
if (this.skipNextOnClosingDirtyCheck) {
|
|
2832
|
+
this.skipNextOnClosingDirtyCheck = false;
|
|
2833
|
+
this.completeDismissResolve();
|
|
2834
|
+
return;
|
|
2835
|
+
}
|
|
2836
|
+
if (!this.config?.confirmCloseWhenDirty?.enabled) {
|
|
2837
|
+
this.completeDismissResolve();
|
|
2838
|
+
return;
|
|
2839
|
+
}
|
|
2840
|
+
if (!(await this.confirmDismissIfDirty())) {
|
|
2841
|
+
e.cancel = true;
|
|
2842
|
+
this.pendingDismissResolvePayload = undefined;
|
|
2843
|
+
return;
|
|
2844
|
+
}
|
|
2845
|
+
this.completeDismissResolve();
|
|
2846
|
+
}
|
|
2847
|
+
/**
|
|
2848
|
+
* Footer cancel and other in-app dismiss actions. Routes through `super.close()` so the
|
|
2849
|
+
* popup shell invokes {@link onClosing} exactly once (same path as **X** / **Esc**).
|
|
2850
|
+
*/
|
|
2851
|
+
requestDismiss(resolvePayload) {
|
|
2852
|
+
this.pendingDismissResolvePayload = resolvePayload ?? this.createDialogRef('cancel');
|
|
2853
|
+
super.close();
|
|
2854
|
+
}
|
|
2855
|
+
/** Invokes `show()` callback with the pending payload or a default cancel {@link AXPDialogRef}. */
|
|
2856
|
+
completeDismissResolve() {
|
|
2857
|
+
const payload = this.pendingDismissResolvePayload;
|
|
2858
|
+
this.pendingDismissResolvePayload = undefined;
|
|
2859
|
+
if (payload !== undefined) {
|
|
2860
|
+
this.resolveDialog(payload);
|
|
2861
|
+
return;
|
|
2862
|
+
}
|
|
2863
|
+
if (!this.callbackInvoked) {
|
|
2864
|
+
this.resolveDialog(this.createDialogRef('cancel'));
|
|
2865
|
+
}
|
|
2866
|
+
}
|
|
2867
|
+
//#endregion
|
|
2868
|
+
//#region ---- Dirty State ----
|
|
2869
|
+
getWidgetContextStore() {
|
|
2870
|
+
return this.layoutRenderer()?.getContainer()?.contextService;
|
|
2871
|
+
}
|
|
2872
|
+
scheduleDirtyBaselineCapture() {
|
|
2873
|
+
if (this.dirtyBaselineCaptured) {
|
|
2874
|
+
return;
|
|
2875
|
+
}
|
|
2876
|
+
if (this.dirtyBaselineIdleTimer) {
|
|
2877
|
+
clearTimeout(this.dirtyBaselineIdleTimer);
|
|
2878
|
+
}
|
|
2879
|
+
this.dirtyBaselineIdleTimer = setTimeout(() => {
|
|
2880
|
+
this.captureDirtyBaselineIfNeeded();
|
|
2881
|
+
}, DIALOG_DIRTY_BASELINE_IDLE_MS);
|
|
2882
|
+
}
|
|
2883
|
+
/**
|
|
2884
|
+
* Captures a dialog-local baseline once after widget init/normalization goes idle.
|
|
2885
|
+
*/
|
|
2886
|
+
captureDirtyBaselineIfNeeded() {
|
|
2887
|
+
if (this.dirtyBaselineCaptured) {
|
|
2888
|
+
return;
|
|
2889
|
+
}
|
|
2890
|
+
const store = this.getWidgetContextStore();
|
|
2891
|
+
if (!store) {
|
|
2892
|
+
return;
|
|
2893
|
+
}
|
|
2894
|
+
const widgetCount = this.widgetCoreService?.registeredWidgetsCount() ?? 0;
|
|
2895
|
+
if (widgetCount === 0) {
|
|
2896
|
+
return;
|
|
2897
|
+
}
|
|
2898
|
+
const snapshot = store.data();
|
|
2899
|
+
this.dirtyBaseline = captureFormContextBaseline(snapshot);
|
|
2900
|
+
store.commitBaseline();
|
|
2901
|
+
this.dirtyBaselineCaptured = true;
|
|
2902
|
+
if (this.dirtyBaselineIdleTimer) {
|
|
2903
|
+
clearTimeout(this.dirtyBaselineIdleTimer);
|
|
2904
|
+
this.dirtyBaselineIdleTimer = undefined;
|
|
2905
|
+
}
|
|
2906
|
+
if (this.dirtyBaselineFallbackTimer) {
|
|
2907
|
+
clearTimeout(this.dirtyBaselineFallbackTimer);
|
|
2908
|
+
this.dirtyBaselineFallbackTimer = undefined;
|
|
2909
|
+
}
|
|
2173
2910
|
}
|
|
2174
|
-
#
|
|
2911
|
+
#widgetActionsEffect;
|
|
2912
|
+
#dirtyBaselineEffect;
|
|
2175
2913
|
//#endregion
|
|
2176
2914
|
handleContextChanged(event) {
|
|
2177
2915
|
this.context.set(event);
|
|
2178
2916
|
this.aggregateAndEvaluateActions();
|
|
2917
|
+
void this.invokeLayoutContextChangedHooks();
|
|
2179
2918
|
}
|
|
2180
2919
|
handleContextInitiated(event) {
|
|
2181
2920
|
this.context.set(event);
|
|
2182
2921
|
this.aggregateAndEvaluateActions();
|
|
2922
|
+
void this.invokeLayoutContextChangedHooks();
|
|
2923
|
+
}
|
|
2924
|
+
async invokeLayoutContextChangedHooks() {
|
|
2925
|
+
const meta = this.config?.metadata;
|
|
2926
|
+
if (!this.hookService) {
|
|
2927
|
+
return;
|
|
2928
|
+
}
|
|
2929
|
+
const payload = {
|
|
2930
|
+
sessionKey: this.contextChangedHooksSessionKey,
|
|
2931
|
+
getContext: () => {
|
|
2932
|
+
const store = this.getWidgetContextStore();
|
|
2933
|
+
return (store?.data() ?? this.context() ?? {});
|
|
2934
|
+
},
|
|
2935
|
+
metadata: meta,
|
|
2936
|
+
patchContext: (partial) => {
|
|
2937
|
+
const merged = merge({}, this.context(), partial);
|
|
2938
|
+
this.context.set(merged);
|
|
2939
|
+
this.layoutRenderer()?.updateContext(merged);
|
|
2940
|
+
},
|
|
2941
|
+
setLoading: (loading) => this.isDialogLoading.set(loading),
|
|
2942
|
+
};
|
|
2943
|
+
try {
|
|
2944
|
+
await this.hookService.runAsync(AXP_LAYOUT_BUILDER_DIALOG_CONTEXT_CHANGED_HOOK_KEY, payload);
|
|
2945
|
+
}
|
|
2946
|
+
catch {
|
|
2947
|
+
// Hook providers are best-effort; avoid breaking the dialog lifecycle.
|
|
2948
|
+
}
|
|
2183
2949
|
}
|
|
2184
2950
|
footerPrefixActions() {
|
|
2185
2951
|
return this.footerPrefix();
|
|
@@ -2194,38 +2960,191 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
|
|
|
2194
2960
|
return this.isDialogLoading();
|
|
2195
2961
|
}
|
|
2196
2962
|
async executeAction(action) {
|
|
2197
|
-
const cmd = action.command;
|
|
2198
|
-
if (cmd
|
|
2963
|
+
const cmd = this.resolveActionCommandName(action.command);
|
|
2964
|
+
if (this.shouldValidateBeforeAction(cmd)) {
|
|
2199
2965
|
const isValid = await this.layoutRenderer()?.validate();
|
|
2200
2966
|
if (!isValid?.result) {
|
|
2201
2967
|
return;
|
|
2202
2968
|
}
|
|
2203
2969
|
}
|
|
2204
|
-
|
|
2970
|
+
//TODO: matin, why we need this? maybe we can remove it?
|
|
2971
|
+
if (cmd?.startsWith('widget:')) {
|
|
2205
2972
|
const parsed = this.parseWidgetCommand(cmd);
|
|
2206
2973
|
if (parsed.widgetName && parsed.action) {
|
|
2207
|
-
await this.
|
|
2974
|
+
await this.invokeWidget(parsed.widgetName, parsed.action, {});
|
|
2208
2975
|
await this.aggregateAndEvaluateActions();
|
|
2209
2976
|
return;
|
|
2210
2977
|
}
|
|
2211
2978
|
}
|
|
2212
|
-
|
|
2213
|
-
|
|
2979
|
+
if (cmd && this.commandService.exists(cmd)) {
|
|
2980
|
+
const dialogRef = this.createDialogRef(cmd);
|
|
2981
|
+
const integration = (this.config.metadata ?? {});
|
|
2982
|
+
try {
|
|
2983
|
+
const cmdResult = await this.commandService.execute(cmd, { dialogRef, integration });
|
|
2984
|
+
if (!cmdResult?.success) {
|
|
2985
|
+
return;
|
|
2986
|
+
}
|
|
2987
|
+
if (this.shouldKeepDialogOpenAfterCommandResult(cmdResult)) {
|
|
2988
|
+
return;
|
|
2989
|
+
}
|
|
2990
|
+
this.resolveDialog(cmdResult);
|
|
2991
|
+
await this.closeWithOptionalSkipValidate(cmdResult);
|
|
2992
|
+
}
|
|
2993
|
+
catch (error) {
|
|
2994
|
+
console.error('Error executing action', cmd, error);
|
|
2995
|
+
}
|
|
2996
|
+
return;
|
|
2997
|
+
}
|
|
2998
|
+
const context = this.context();
|
|
2999
|
+
const onAction = this.config?.onAction;
|
|
3000
|
+
// `onAction` return value is passed through to `show()` unchanged — see dialog-resolve.util.ts.
|
|
3001
|
+
if (onAction) {
|
|
3002
|
+
const dialogRef = this.createDialogRef(cmd);
|
|
3003
|
+
try {
|
|
3004
|
+
this.isDialogLoading.set(true);
|
|
3005
|
+
const result = await Promise.resolve(onAction(dialogRef));
|
|
3006
|
+
if (this.shouldKeepDialogOpenAfterCommandResult(result)) {
|
|
3007
|
+
return;
|
|
3008
|
+
}
|
|
3009
|
+
if (cmd === 'cancel' &&
|
|
3010
|
+
shouldRouteOnActionCancelThroughDismissGate(this.config?.confirmCloseWhenDirty?.enabled)) {
|
|
3011
|
+
this.isDialogLoading.set(false);
|
|
3012
|
+
this.requestDismiss(result);
|
|
3013
|
+
return;
|
|
3014
|
+
}
|
|
3015
|
+
this.resolveDialog(result);
|
|
3016
|
+
await this.closeWithOptionalSkipValidate(result);
|
|
3017
|
+
}
|
|
3018
|
+
catch {
|
|
3019
|
+
// Handler threw: stay open for retry, actions remain clickable
|
|
3020
|
+
}
|
|
3021
|
+
finally {
|
|
3022
|
+
this.isDialogLoading.set(false);
|
|
3023
|
+
}
|
|
3024
|
+
return;
|
|
3025
|
+
}
|
|
3026
|
+
// Fallback: layout-builder custom footer commands (e.g. signature-apply, upload-image).
|
|
3027
|
+
// See dialog-resolve.util.ts — resolves {@link AXPDialogRef}; does not auto-close except cancel.
|
|
3028
|
+
const result = { context, action: cmd };
|
|
2214
3029
|
this.dialogResult = result;
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
3030
|
+
syncLegacyDialogDataSideFields(this.data, result.context, result.action);
|
|
3031
|
+
const dialogRefPayload = buildLayoutBuilderCustomActionRef((command) => this.createDialogRef(command), cmd);
|
|
3032
|
+
if (cmd === 'cancel') {
|
|
3033
|
+
this.requestDismiss(dialogRefPayload);
|
|
3034
|
+
return;
|
|
3035
|
+
}
|
|
3036
|
+
this.resolveDialog(dialogRefPayload);
|
|
3037
|
+
}
|
|
3038
|
+
isDialogDirty() {
|
|
3039
|
+
const confirmOptions = this.config?.confirmCloseWhenDirty;
|
|
3040
|
+
if (!confirmOptions?.enabled || !this.dirtyBaselineCaptured) {
|
|
3041
|
+
return false;
|
|
3042
|
+
}
|
|
3043
|
+
const store = this.getWidgetContextStore();
|
|
3044
|
+
if (!store) {
|
|
3045
|
+
return false;
|
|
3046
|
+
}
|
|
3047
|
+
const current = store.data();
|
|
3048
|
+
const baseline = this.dirtyBaseline ?? store.initial();
|
|
3049
|
+
if (typeof confirmOptions.isDirty === 'function') {
|
|
3050
|
+
return confirmOptions.isDirty(current, baseline);
|
|
3051
|
+
}
|
|
3052
|
+
return (store.isUserDirty() ||
|
|
3053
|
+
(this.widgetCoreService?.hasDirtyWidgets() ?? false) ||
|
|
3054
|
+
isFormContextDirty(current, baseline));
|
|
3055
|
+
}
|
|
3056
|
+
async confirmDismissIfDirty() {
|
|
3057
|
+
if (this.dismissConfirmPromise) {
|
|
3058
|
+
return this.dismissConfirmPromise;
|
|
3059
|
+
}
|
|
3060
|
+
const confirmOptions = this.config?.confirmCloseWhenDirty;
|
|
3061
|
+
if (!confirmOptions?.enabled || !this.isDialogDirty()) {
|
|
3062
|
+
return true;
|
|
3063
|
+
}
|
|
3064
|
+
this.dismissConfirmPromise = this.promptUnsavedChangesConfirm(confirmOptions);
|
|
3065
|
+
try {
|
|
3066
|
+
return await this.dismissConfirmPromise;
|
|
3067
|
+
}
|
|
3068
|
+
finally {
|
|
3069
|
+
this.dismissConfirmPromise = undefined;
|
|
3070
|
+
}
|
|
3071
|
+
}
|
|
3072
|
+
async promptUnsavedChangesConfirm(confirmOptions) {
|
|
3073
|
+
const title = await this.translationService.translateAsync(confirmOptions.title ?? '@general:messages.unsaved-changes.title');
|
|
3074
|
+
const message = await this.translationService.translateAsync(confirmOptions.message ?? '@general:messages.unsaved-changes.message');
|
|
3075
|
+
const dialogResult = await this.dialogService.confirm(title, message, 'warning', 'horizontal', false, 'cancel');
|
|
3076
|
+
return dialogResult.result === true;
|
|
3077
|
+
}
|
|
3078
|
+
/** Whether the layout form should be validated before running this footer command. */
|
|
3079
|
+
shouldValidateBeforeAction(cmd) {
|
|
3080
|
+
if (!cmd || cmd === 'cancel' || cmd === 'entity-form-done' || cmd === 'entity-form-next-step') {
|
|
3081
|
+
return false;
|
|
3082
|
+
}
|
|
3083
|
+
if (cmd.startsWith('widget:')) {
|
|
3084
|
+
return false;
|
|
3085
|
+
}
|
|
3086
|
+
if (this.commandService.exists(cmd)) {
|
|
3087
|
+
return false;
|
|
2218
3088
|
}
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
3089
|
+
return true;
|
|
3090
|
+
}
|
|
3091
|
+
/** True when a footer handler or command result asks to leave the dialog open (`keepDialogOpen` on the result or `result.data`). */
|
|
3092
|
+
shouldKeepDialogOpenAfterCommandResult(result) {
|
|
3093
|
+
if (!result || typeof result !== 'object') {
|
|
3094
|
+
return false;
|
|
3095
|
+
}
|
|
3096
|
+
const top = result;
|
|
3097
|
+
if (top.keepDialogOpen === true) {
|
|
3098
|
+
return true;
|
|
3099
|
+
}
|
|
3100
|
+
if (top.data != null && typeof top.data === 'object' && 'keepDialogOpen' in top.data) {
|
|
3101
|
+
return top.data.keepDialogOpen === true;
|
|
3102
|
+
}
|
|
3103
|
+
return false;
|
|
3104
|
+
}
|
|
3105
|
+
/** Resolves the dialog `show()` promise exactly once. */
|
|
3106
|
+
resolveDialog(result) {
|
|
3107
|
+
if (this.callbackInvoked) {
|
|
3108
|
+
return;
|
|
3109
|
+
}
|
|
3110
|
+
this.callbackInvoked = true;
|
|
3111
|
+
this.callBack(result);
|
|
3112
|
+
}
|
|
3113
|
+
createDialogRef(actionCmd) {
|
|
3114
|
+
return {
|
|
3115
|
+
close: (res) => {
|
|
3116
|
+
void this.closeWithOptionalSkipValidate(res);
|
|
2222
3117
|
},
|
|
2223
3118
|
context: () => this.context(),
|
|
2224
|
-
action: () =>
|
|
2225
|
-
setLoading: (loading) =>
|
|
2226
|
-
|
|
3119
|
+
action: () => actionCmd,
|
|
3120
|
+
setLoading: (loading) => this.isDialogLoading.set(loading),
|
|
3121
|
+
patchContext: (partial) => {
|
|
3122
|
+
const merged = merge({}, this.context(), partial);
|
|
3123
|
+
this.context.set(merged);
|
|
3124
|
+
this.layoutRenderer()?.updateContext(merged);
|
|
2227
3125
|
},
|
|
2228
|
-
|
|
3126
|
+
invokeWidget: (widgetName, method, opts) => this.invokeWidget(widgetName, method, opts ?? {}),
|
|
3127
|
+
};
|
|
3128
|
+
}
|
|
3129
|
+
async closeWithOptionalSkipValidate(result) {
|
|
3130
|
+
if (result && typeof result === 'object' && result.skipValidate) {
|
|
3131
|
+
this.result.emit(result);
|
|
3132
|
+
this.skipNextOnClosingDirtyCheck = true;
|
|
3133
|
+
this.pendingDismissResolvePayload = undefined;
|
|
3134
|
+
super.close(result);
|
|
3135
|
+
return;
|
|
3136
|
+
}
|
|
3137
|
+
await this.close(result, { skipDirtyConfirm: true });
|
|
3138
|
+
}
|
|
3139
|
+
/** Resolves footer/widget action command to a string (e.g. `cancel`, `submit`, `widget:...`). */
|
|
3140
|
+
resolveActionCommandName(command) {
|
|
3141
|
+
if (typeof command === 'string') {
|
|
3142
|
+
return command;
|
|
3143
|
+
}
|
|
3144
|
+
if (command && typeof command === 'object' && 'name' in command) {
|
|
3145
|
+
return command.name;
|
|
3146
|
+
}
|
|
3147
|
+
return undefined;
|
|
2229
3148
|
}
|
|
2230
3149
|
parseWidgetCommand(cmd) {
|
|
2231
3150
|
// Expected 'widget:<widgetName>.<action>'
|
|
@@ -2237,7 +3156,7 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
|
|
|
2237
3156
|
return {};
|
|
2238
3157
|
return { widgetName: rest.slice(0, dot), action: rest.slice(dot + 1) };
|
|
2239
3158
|
}
|
|
2240
|
-
async
|
|
3159
|
+
async invokeWidget(widgetName, apiMethod, opts) {
|
|
2241
3160
|
if (!this.widgetCoreService)
|
|
2242
3161
|
return;
|
|
2243
3162
|
try {
|
|
@@ -2247,18 +3166,26 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
|
|
|
2247
3166
|
if (typeof fn === 'function') {
|
|
2248
3167
|
await Promise.resolve(fn({
|
|
2249
3168
|
close: (result) => {
|
|
2250
|
-
this.
|
|
3169
|
+
void this.closeWithOptionalSkipValidate(result);
|
|
2251
3170
|
},
|
|
2252
3171
|
context: () => this.context(),
|
|
2253
3172
|
setLoading: (loading) => {
|
|
2254
|
-
this.isDialogLoading.set(loading);
|
|
3173
|
+
(opts.setLoading ?? ((v) => this.isDialogLoading.set(v)))(loading);
|
|
2255
3174
|
},
|
|
2256
3175
|
}));
|
|
3176
|
+
// Footer predicates (e.g. wizard step) must refresh when the widget advances outside executeAction (e.g. dialogRef.invokeWidget after entity-form continue).
|
|
3177
|
+
await this.aggregateAndEvaluateActions();
|
|
2257
3178
|
}
|
|
2258
3179
|
}
|
|
2259
|
-
catch {
|
|
3180
|
+
catch {
|
|
3181
|
+
//
|
|
3182
|
+
}
|
|
2260
3183
|
}
|
|
2261
|
-
async close(result) {
|
|
3184
|
+
async close(result, options) {
|
|
3185
|
+
if (options?.skipDirtyConfirm) {
|
|
3186
|
+
this.skipNextOnClosingDirtyCheck = true;
|
|
3187
|
+
this.pendingDismissResolvePayload = undefined;
|
|
3188
|
+
}
|
|
2262
3189
|
if (result) {
|
|
2263
3190
|
const isValid = await this.layoutRenderer()?.validate();
|
|
2264
3191
|
if (isValid?.result) {
|
|
@@ -2306,6 +3233,8 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
|
|
|
2306
3233
|
zone: 'footer',
|
|
2307
3234
|
placement,
|
|
2308
3235
|
scope: a.scope,
|
|
3236
|
+
predicateApiWidgetName: a.predicateApiWidgetName,
|
|
3237
|
+
shortcuts: resolveConfiguredFooterActionShortcuts(typeof a.command === 'string' ? a.command : a.command?.name, a.shortcuts),
|
|
2309
3238
|
});
|
|
2310
3239
|
const prefix = (footer?.prefix || []).map((a) => mapOne(a, 'prefix'));
|
|
2311
3240
|
const suffix = (footer?.suffix || []).map((a) => mapOne(a, 'suffix'));
|
|
@@ -2315,16 +3244,18 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
|
|
|
2315
3244
|
const out = [];
|
|
2316
3245
|
for (const a of actions) {
|
|
2317
3246
|
const parsed = typeof a.command === 'string' ? this.parseWidgetCommand(a.command) : {};
|
|
2318
|
-
const
|
|
3247
|
+
const widgetNameForApi = parsed.widgetName ?? a.predicateApiWidgetName;
|
|
3248
|
+
const api = widgetNameForApi ? await this.resolveApi(widgetNameForApi) : undefined;
|
|
2319
3249
|
const scope = {
|
|
2320
3250
|
api,
|
|
2321
|
-
widget: { name:
|
|
3251
|
+
widget: { name: widgetNameForApi },
|
|
2322
3252
|
dialog: { context: this.context() },
|
|
2323
3253
|
context: this.context(),
|
|
2324
3254
|
};
|
|
2325
3255
|
const disabled = await this.evalBool(a.disabled, scope);
|
|
2326
3256
|
const hidden = await this.evalBool(a.hidden, scope);
|
|
2327
|
-
|
|
3257
|
+
const resolvedTitle = (await this.evalActionTitle(a.title, scope)) ?? a.title;
|
|
3258
|
+
out.push({ ...a, disabled, hidden, title: resolvedTitle });
|
|
2328
3259
|
}
|
|
2329
3260
|
return out;
|
|
2330
3261
|
}
|
|
@@ -2342,6 +3273,25 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
|
|
|
2342
3273
|
}
|
|
2343
3274
|
return value;
|
|
2344
3275
|
}
|
|
3276
|
+
/** Resolves footer action title when it contains {{ ... }} (e.g. wizard labels from dialog context). */
|
|
3277
|
+
async evalActionTitle(value, scope) {
|
|
3278
|
+
if (value == null || typeof value !== 'string' || !value.includes('{{')) {
|
|
3279
|
+
return value;
|
|
3280
|
+
}
|
|
3281
|
+
try {
|
|
3282
|
+
const result = await this.expressionEvaluator.evaluate(value, scope);
|
|
3283
|
+
if (typeof result === 'string' && result.length > 0) {
|
|
3284
|
+
return result;
|
|
3285
|
+
}
|
|
3286
|
+
if (result != null && result !== false) {
|
|
3287
|
+
return String(result);
|
|
3288
|
+
}
|
|
3289
|
+
}
|
|
3290
|
+
catch {
|
|
3291
|
+
//
|
|
3292
|
+
}
|
|
3293
|
+
return value;
|
|
3294
|
+
}
|
|
2345
3295
|
async resolveApi(widgetName) {
|
|
2346
3296
|
try {
|
|
2347
3297
|
await this.widgetCoreService?.waitForWidget(widgetName, 2000);
|
|
@@ -2352,9 +3302,13 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
|
|
|
2352
3302
|
return undefined;
|
|
2353
3303
|
}
|
|
2354
3304
|
}
|
|
2355
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
2356
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
3305
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPDialogRendererComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
3306
|
+
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: `
|
|
3307
|
+
<axp-component-slot name="dialog-header" [context]="context()"></axp-component-slot>
|
|
2357
3308
|
<div class="ax-p-4">
|
|
3309
|
+
<!-- @if (config.message) {
|
|
3310
|
+
<p class="ax-mb-4 ax-leading-relaxed">{{ config.message | translate | async }}</p>
|
|
3311
|
+
} -->
|
|
2358
3312
|
<axp-layout-renderer
|
|
2359
3313
|
[layout]="config.definition"
|
|
2360
3314
|
[context]="context()"
|
|
@@ -2364,46 +3318,57 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
|
|
|
2364
3318
|
</axp-layout-renderer>
|
|
2365
3319
|
</div>
|
|
2366
3320
|
|
|
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) {
|
|
3321
|
+
<!-- Custom footer slot: if it has content, default footer is hidden -->
|
|
3322
|
+
<axp-component-slot name="dialog-footer" #footerSlot="slot" [context]="context()"></axp-component-slot>
|
|
3323
|
+
@if (footerSlot.isEmpty()) {
|
|
3324
|
+
<ax-footer>
|
|
3325
|
+
<ax-prefix>
|
|
3326
|
+
<axp-component-slot name="dialog-footer-prefix" [context]="context()"></axp-component-slot>
|
|
3327
|
+
@for (action of footerPrefixActions(); track $index) {
|
|
3328
|
+
<ax-button
|
|
3329
|
+
[disabled]="action.disabled || isFormLoading()"
|
|
3330
|
+
[text]="(action.title | translate | async)!"
|
|
3331
|
+
[look]="'outline'"
|
|
3332
|
+
[color]="action.color"
|
|
3333
|
+
(onClick)="executeAction(action)"
|
|
3334
|
+
>
|
|
3335
|
+
@if (isFormLoading()) {
|
|
3336
|
+
<ax-loading></ax-loading>
|
|
3337
|
+
}
|
|
2396
3338
|
<ax-prefix>
|
|
2397
|
-
|
|
3339
|
+
@if (action.icon) {
|
|
3340
|
+
<ax-icon [icon]="action.icon"></ax-icon>
|
|
3341
|
+
}
|
|
2398
3342
|
</ax-prefix>
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
3343
|
+
</ax-button>
|
|
3344
|
+
}
|
|
3345
|
+
</ax-prefix>
|
|
3346
|
+
<ax-suffix>
|
|
3347
|
+
@for (action of footerSuffixActions(); track $index) {
|
|
3348
|
+
<ax-button
|
|
3349
|
+
[disabled]="action.disabled || isSubmitting()"
|
|
3350
|
+
[text]="(action.title | translate | async)!"
|
|
3351
|
+
[look]="'solid'"
|
|
3352
|
+
[color]="action.color"
|
|
3353
|
+
(onClick)="executeAction(action)"
|
|
3354
|
+
>
|
|
3355
|
+
@if (isFormLoading()) {
|
|
3356
|
+
<ax-loading></ax-loading>
|
|
3357
|
+
}
|
|
3358
|
+
@if (action.icon) {
|
|
3359
|
+
<ax-prefix>
|
|
3360
|
+
<ax-icon [icon]="action.icon"></ax-icon>
|
|
3361
|
+
</ax-prefix>
|
|
3362
|
+
}
|
|
3363
|
+
</ax-button>
|
|
3364
|
+
}
|
|
3365
|
+
<axp-component-slot name="dialog-footer-suffix" [context]="context()"></axp-component-slot>
|
|
3366
|
+
</ax-suffix>
|
|
3367
|
+
</ax-footer>
|
|
3368
|
+
}
|
|
3369
|
+
`, 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
3370
|
}
|
|
2406
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
3371
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPDialogRendererComponent, decorators: [{
|
|
2407
3372
|
type: Component,
|
|
2408
3373
|
args: [{
|
|
2409
3374
|
selector: 'axp-dialog-renderer',
|
|
@@ -2415,9 +3380,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImpo
|
|
|
2415
3380
|
AXDecoratorModule,
|
|
2416
3381
|
AXLoadingModule,
|
|
2417
3382
|
AXTranslationModule,
|
|
3383
|
+
AXPComponentSlotModule,
|
|
2418
3384
|
],
|
|
3385
|
+
providers: [AXPContextStore],
|
|
3386
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
2419
3387
|
template: `
|
|
3388
|
+
<axp-component-slot name="dialog-header" [context]="context()"></axp-component-slot>
|
|
2420
3389
|
<div class="ax-p-4">
|
|
3390
|
+
<!-- @if (config.message) {
|
|
3391
|
+
<p class="ax-mb-4 ax-leading-relaxed">{{ config.message | translate | async }}</p>
|
|
3392
|
+
} -->
|
|
2421
3393
|
<axp-layout-renderer
|
|
2422
3394
|
[layout]="config.definition"
|
|
2423
3395
|
[context]="context()"
|
|
@@ -2427,48 +3399,57 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImpo
|
|
|
2427
3399
|
</axp-layout-renderer>
|
|
2428
3400
|
</div>
|
|
2429
3401
|
|
|
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) {
|
|
3402
|
+
<!-- Custom footer slot: if it has content, default footer is hidden -->
|
|
3403
|
+
<axp-component-slot name="dialog-footer" #footerSlot="slot" [context]="context()"></axp-component-slot>
|
|
3404
|
+
@if (footerSlot.isEmpty()) {
|
|
3405
|
+
<ax-footer>
|
|
3406
|
+
<ax-prefix>
|
|
3407
|
+
<axp-component-slot name="dialog-footer-prefix" [context]="context()"></axp-component-slot>
|
|
3408
|
+
@for (action of footerPrefixActions(); track $index) {
|
|
3409
|
+
<ax-button
|
|
3410
|
+
[disabled]="action.disabled || isFormLoading()"
|
|
3411
|
+
[text]="(action.title | translate | async)!"
|
|
3412
|
+
[look]="'outline'"
|
|
3413
|
+
[color]="action.color"
|
|
3414
|
+
(onClick)="executeAction(action)"
|
|
3415
|
+
>
|
|
3416
|
+
@if (isFormLoading()) {
|
|
3417
|
+
<ax-loading></ax-loading>
|
|
3418
|
+
}
|
|
2459
3419
|
<ax-prefix>
|
|
2460
|
-
|
|
3420
|
+
@if (action.icon) {
|
|
3421
|
+
<ax-icon [icon]="action.icon"></ax-icon>
|
|
3422
|
+
}
|
|
2461
3423
|
</ax-prefix>
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
3424
|
+
</ax-button>
|
|
3425
|
+
}
|
|
3426
|
+
</ax-prefix>
|
|
3427
|
+
<ax-suffix>
|
|
3428
|
+
@for (action of footerSuffixActions(); track $index) {
|
|
3429
|
+
<ax-button
|
|
3430
|
+
[disabled]="action.disabled || isSubmitting()"
|
|
3431
|
+
[text]="(action.title | translate | async)!"
|
|
3432
|
+
[look]="'solid'"
|
|
3433
|
+
[color]="action.color"
|
|
3434
|
+
(onClick)="executeAction(action)"
|
|
3435
|
+
>
|
|
3436
|
+
@if (isFormLoading()) {
|
|
3437
|
+
<ax-loading></ax-loading>
|
|
3438
|
+
}
|
|
3439
|
+
@if (action.icon) {
|
|
3440
|
+
<ax-prefix>
|
|
3441
|
+
<ax-icon [icon]="action.icon"></ax-icon>
|
|
3442
|
+
</ax-prefix>
|
|
3443
|
+
}
|
|
3444
|
+
</ax-button>
|
|
3445
|
+
}
|
|
3446
|
+
<axp-component-slot name="dialog-footer-suffix" [context]="context()"></axp-component-slot>
|
|
3447
|
+
</ax-suffix>
|
|
3448
|
+
</ax-footer>
|
|
3449
|
+
}
|
|
2467
3450
|
`,
|
|
2468
3451
|
}]
|
|
2469
|
-
}], propDecorators: {
|
|
2470
|
-
type: Input
|
|
2471
|
-
}], result: [{
|
|
3452
|
+
}], propDecorators: { result: [{
|
|
2472
3453
|
type: Output
|
|
2473
3454
|
}], layoutRenderer: [{ type: i0.ViewChild, args: [i0.forwardRef(() => AXPLayoutRendererComponent), { isSignal: true }] }] } });
|
|
2474
3455
|
|
|
@@ -2477,9 +3458,190 @@ var dialogRenderer_component = /*#__PURE__*/Object.freeze({
|
|
|
2477
3458
|
AXPDialogRendererComponent: AXPDialogRendererComponent
|
|
2478
3459
|
});
|
|
2479
3460
|
|
|
3461
|
+
//#region ---- Imports ----
|
|
3462
|
+
/**
|
|
3463
|
+
* `customWidget` only forwards keys from its options bag into the built node via `addSingleWidget`.
|
|
3464
|
+
* Designer / configurator persist `defaultValue` (and other extended fields) on the widget node root;
|
|
3465
|
+
* spreading `options` alone drops them, so preview never applied defaults.
|
|
3466
|
+
*/
|
|
3467
|
+
/**
|
|
3468
|
+
* Widget options are sometimes persisted with an extra nesting (`options.options`) when context
|
|
3469
|
+
* was merged incorrectly. Flatten so list/data-source resolution sees `dataSource` at the top level.
|
|
3470
|
+
*/
|
|
3471
|
+
function optionsBagForPreview(node) {
|
|
3472
|
+
const raw = (node.options ?? {});
|
|
3473
|
+
const inner = raw['options'];
|
|
3474
|
+
if (inner !== undefined && typeof inner === 'object' && !Array.isArray(inner)) {
|
|
3475
|
+
const { options: _nested, ...rest } = raw;
|
|
3476
|
+
return { ...rest, ...inner };
|
|
3477
|
+
}
|
|
3478
|
+
return { ...raw };
|
|
3479
|
+
}
|
|
3480
|
+
function extendedNodePropsForPreview(node) {
|
|
3481
|
+
const out = {};
|
|
3482
|
+
if (node.defaultValue !== undefined) {
|
|
3483
|
+
out['defaultValue'] = node.defaultValue;
|
|
3484
|
+
}
|
|
3485
|
+
if (node.triggers !== undefined) {
|
|
3486
|
+
out['triggers'] = node.triggers;
|
|
3487
|
+
}
|
|
3488
|
+
if (node.meta !== undefined) {
|
|
3489
|
+
out['meta'] = node.meta;
|
|
3490
|
+
}
|
|
3491
|
+
if (node.valueTransforms !== undefined) {
|
|
3492
|
+
out['valueTransforms'] = node.valueTransforms;
|
|
3493
|
+
}
|
|
3494
|
+
if (node.visible !== undefined) {
|
|
3495
|
+
out['visible'] = node.visible;
|
|
3496
|
+
}
|
|
3497
|
+
if (node.mode !== undefined) {
|
|
3498
|
+
out['mode'] = node.mode;
|
|
3499
|
+
}
|
|
3500
|
+
if (node.children !== undefined) {
|
|
3501
|
+
out['children'] = node.children;
|
|
3502
|
+
}
|
|
3503
|
+
return out;
|
|
3504
|
+
}
|
|
3505
|
+
//#endregion
|
|
3506
|
+
//#region ---- Command ----
|
|
3507
|
+
/**
|
|
3508
|
+
* Opens a dialog that previews a widget configuration (same behavior as the preview button on
|
|
3509
|
+
* `axp-widget-field-configurator`). Invoked from that component and from entity list actions.
|
|
3510
|
+
*/
|
|
3511
|
+
class AXPPreviewWidgetFieldCommand {
|
|
3512
|
+
constructor() {
|
|
3513
|
+
this.formBuilderService = inject(AXPLayoutBuilderService);
|
|
3514
|
+
this.widgetRegistry = inject(AXPWidgetRegistryService);
|
|
3515
|
+
this.translationService = inject(AXTranslationService);
|
|
3516
|
+
this.crudService = inject(AXP_ENTITY_DEFINITION_CRUD_SERVICE, { optional: true });
|
|
3517
|
+
}
|
|
3518
|
+
async execute(input) {
|
|
3519
|
+
try {
|
|
3520
|
+
const merged = this.mergeInvocation(input);
|
|
3521
|
+
const currentWidget = this.normalizeWidget(merged['widget'] ?? merged['interface']);
|
|
3522
|
+
if (!currentWidget?.type) {
|
|
3523
|
+
return {
|
|
3524
|
+
success: false,
|
|
3525
|
+
message: {
|
|
3526
|
+
text: (await this.translationService.translateAsync('@general:messages.invalid-data')) || 'Invalid data',
|
|
3527
|
+
},
|
|
3528
|
+
};
|
|
3529
|
+
}
|
|
3530
|
+
const fieldName = String(merged['fieldName'] ?? merged['name'] ?? 'Field');
|
|
3531
|
+
const rawTitle = (merged['fieldTitle'] ?? merged['title']);
|
|
3532
|
+
const fieldTitleLabel = this.resolveFieldTitleLabel(rawTitle, fieldName);
|
|
3533
|
+
const dialogTitle = (await this.resolveWidgetDisplayTitle(currentWidget.type)) || currentWidget.type || fieldTitleLabel;
|
|
3534
|
+
const previewWidgetOptions = {
|
|
3535
|
+
...optionsBagForPreview(currentWidget),
|
|
3536
|
+
name: fieldName,
|
|
3537
|
+
...extendedNodePropsForPreview(currentWidget),
|
|
3538
|
+
};
|
|
3539
|
+
const dialogOutcome = await this.formBuilderService
|
|
3540
|
+
.create()
|
|
3541
|
+
.dialog((dialog) => {
|
|
3542
|
+
dialog
|
|
3543
|
+
.setTitle(dialogTitle)
|
|
3544
|
+
.setSize('md')
|
|
3545
|
+
.setCloseButton(true)
|
|
3546
|
+
.setContext({})
|
|
3547
|
+
.content((layoutBuilder) => {
|
|
3548
|
+
layoutBuilder.formField(fieldTitleLabel, (formField) => {
|
|
3549
|
+
formField.customWidget(currentWidget.type, previewWidgetOptions);
|
|
3550
|
+
});
|
|
3551
|
+
})
|
|
3552
|
+
.setActions((actions) => actions.cancel('@general:actions.close.title'));
|
|
3553
|
+
})
|
|
3554
|
+
.show();
|
|
3555
|
+
const cancelled = this.isCancelDialogOutcome(dialogOutcome);
|
|
3556
|
+
return {
|
|
3557
|
+
success: !cancelled,
|
|
3558
|
+
message: { text: '' },
|
|
3559
|
+
};
|
|
3560
|
+
}
|
|
3561
|
+
catch (error) {
|
|
3562
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
3563
|
+
return {
|
|
3564
|
+
success: false,
|
|
3565
|
+
message: { text: message },
|
|
3566
|
+
};
|
|
3567
|
+
}
|
|
3568
|
+
}
|
|
3569
|
+
mergeInvocation(input) {
|
|
3570
|
+
const contextOptions = input.__context__?.options;
|
|
3571
|
+
const ctxData = input.__context__?.data;
|
|
3572
|
+
const { __context__: _ctx, ...rest } = input;
|
|
3573
|
+
return {
|
|
3574
|
+
...(ctxData ?? {}),
|
|
3575
|
+
...(contextOptions ?? {}),
|
|
3576
|
+
...rest,
|
|
3577
|
+
};
|
|
3578
|
+
}
|
|
3579
|
+
normalizeWidget(raw) {
|
|
3580
|
+
if (raw == null)
|
|
3581
|
+
return null;
|
|
3582
|
+
if (typeof raw === 'string') {
|
|
3583
|
+
const t = raw.trim();
|
|
3584
|
+
return t ? { type: t, options: {} } : null;
|
|
3585
|
+
}
|
|
3586
|
+
if (typeof raw === 'object' && !Array.isArray(raw) && 'type' in raw) {
|
|
3587
|
+
const w = raw;
|
|
3588
|
+
return w.type ? cloneDeep(w) : null;
|
|
3589
|
+
}
|
|
3590
|
+
return null;
|
|
3591
|
+
}
|
|
3592
|
+
resolveFieldTitleLabel(raw, fallback) {
|
|
3593
|
+
let source = fallback;
|
|
3594
|
+
if (raw !== undefined && raw !== null) {
|
|
3595
|
+
if (typeof raw === 'string') {
|
|
3596
|
+
if (raw.trim() !== '') {
|
|
3597
|
+
source = raw;
|
|
3598
|
+
}
|
|
3599
|
+
}
|
|
3600
|
+
else if (typeof raw === 'object' && !Array.isArray(raw) && Object.keys(raw).length > 0) {
|
|
3601
|
+
source = raw;
|
|
3602
|
+
}
|
|
3603
|
+
}
|
|
3604
|
+
return this.translationService.resolve(source);
|
|
3605
|
+
}
|
|
3606
|
+
isCancelDialogOutcome(outcome) {
|
|
3607
|
+
if (outcome == null) {
|
|
3608
|
+
return false;
|
|
3609
|
+
}
|
|
3610
|
+
const ref = outcome;
|
|
3611
|
+
if (typeof ref.action !== 'function') {
|
|
3612
|
+
return false;
|
|
3613
|
+
}
|
|
3614
|
+
return ref.action() === 'cancel';
|
|
3615
|
+
}
|
|
3616
|
+
async resolveWidgetDisplayTitle(widgetType) {
|
|
3617
|
+
const crud = this.crudService;
|
|
3618
|
+
if (crud) {
|
|
3619
|
+
const interfaces = await crud.listInterfaces();
|
|
3620
|
+
const iface = interfaces.find((d) => d.name === widgetType);
|
|
3621
|
+
return iface?.title ?? iface?.name;
|
|
3622
|
+
}
|
|
3623
|
+
const config = this.widgetRegistry.getOptional(widgetType);
|
|
3624
|
+
if (!config) {
|
|
3625
|
+
return undefined;
|
|
3626
|
+
}
|
|
3627
|
+
const resolved = this.translationService.resolve(config.title);
|
|
3628
|
+
return resolved || undefined;
|
|
3629
|
+
}
|
|
3630
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPPreviewWidgetFieldCommand, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
3631
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPPreviewWidgetFieldCommand }); }
|
|
3632
|
+
}
|
|
3633
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPPreviewWidgetFieldCommand, decorators: [{
|
|
3634
|
+
type: Injectable
|
|
3635
|
+
}] });
|
|
3636
|
+
|
|
3637
|
+
var previewWidgetField_command = /*#__PURE__*/Object.freeze({
|
|
3638
|
+
__proto__: null,
|
|
3639
|
+
AXPPreviewWidgetFieldCommand: AXPPreviewWidgetFieldCommand
|
|
3640
|
+
});
|
|
3641
|
+
|
|
2480
3642
|
/**
|
|
2481
3643
|
* Generated bundle index. Do not edit.
|
|
2482
3644
|
*/
|
|
2483
3645
|
|
|
2484
|
-
export { AXPDialogRendererComponent, AXPLayoutBuilderService, AXPLayoutConversionService, AXPLayoutRendererComponent, LayoutBuilderModule };
|
|
3646
|
+
export { AXPDialogRendererComponent, AXPLayoutBuilderService, AXPLayoutConversionService, AXPLayoutRendererComponent, AXPPreviewWidgetFieldCommand, AXP_LAYOUT_BUILDER_DIALOG_BEFORE_OPEN_HOOK_KEY, AXP_LAYOUT_BUILDER_DIALOG_CONFIG_HOOK_KEY, AXP_LAYOUT_BUILDER_DIALOG_CONTEXT_CHANGED_HOOK_KEY, AXP_PREVIEW_WIDGET_FIELD_COMMAND_KEY, DEFAULT_CANCEL_DIALOG_ACTION_SHORTCUTS, DEFAULT_SUBMIT_DIALOG_ACTION_SHORTCUTS, LayoutBuilderModule, createDismissedDialogRef };
|
|
2485
3647
|
//# sourceMappingURL=acorex-platform-layout-builder.mjs.map
|