@acorex/platform 21.0.0-next.70 → 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-common.mjs +11 -0
- package/fesm2022/acorex-platform-common.mjs.map +1 -1
- package/fesm2022/acorex-platform-core.mjs +332 -76
- package/fesm2022/acorex-platform-core.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-builder.mjs +563 -44
- package/fesm2022/acorex-platform-layout-builder.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-components.mjs +83 -67
- package/fesm2022/acorex-platform-layout-components.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-entity.mjs +543 -237
- package/fesm2022/acorex-platform-layout-entity.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-views.mjs +319 -66
- package/fesm2022/acorex-platform-layout-views.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-widget-core.mjs +54 -6
- package/fesm2022/acorex-platform-layout-widget-core.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-widgets.mjs +198 -91
- package/fesm2022/acorex-platform-layout-widgets.mjs.map +1 -1
- package/fesm2022/acorex-platform-themes-default.mjs +13 -14
- package/fesm2022/acorex-platform-themes-default.mjs.map +1 -1
- package/fesm2022/acorex-platform-themes-shared.mjs +50 -30
- package/fesm2022/acorex-platform-themes-shared.mjs.map +1 -1
- package/package.json +1 -1
- package/types/acorex-platform-common.d.ts +11 -0
- package/types/acorex-platform-core.d.ts +137 -47
- package/types/acorex-platform-layout-builder.d.ts +90 -13
- package/types/acorex-platform-layout-components.d.ts +21 -17
- package/types/acorex-platform-layout-entity.d.ts +63 -10
- package/types/acorex-platform-layout-views.d.ts +68 -6
- package/types/acorex-platform-layout-widget-core.d.ts +43 -8
- package/types/acorex-platform-layout-widgets.d.ts +21 -6
- package/types/acorex-platform-themes-default.d.ts +24 -4
- package/types/acorex-platform-themes-shared.d.ts +6 -0
- package/types/acorex-platform-workflow.d.ts +1 -1
|
@@ -1,28 +1,171 @@
|
|
|
1
1
|
import * as i5 from '@angular/common';
|
|
2
|
-
import { CommonModule } 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, computed, effect, output, viewChild, ChangeDetectionStrategy, Component, NgModule, EventEmitter, Output } from '@angular/core';
|
|
4
|
+
import { Injectable, inject, input, model, signal, computed, effect, output, viewChild, ChangeDetectionStrategy, Component, NgModule, EventEmitter, ElementRef, DestroyRef, Output } from '@angular/core';
|
|
5
5
|
import { provideCommandSetups, AXPCommandService } from '@acorex/platform/runtime';
|
|
6
6
|
import { AXPopupService } from '@acorex/components/popup';
|
|
7
7
|
import * as i4 from '@acorex/platform/core';
|
|
8
|
-
import { AXPHookService, AXPExpressionEvaluatorService, AXPComponentSlotModule, AXPContextStore } from '@acorex/platform/core';
|
|
8
|
+
import { AXPHookService, AXPExpressionEvaluatorService, captureFormContextBaseline, isFormContextDirty, AXPComponentSlotModule, AXPContextStore } from '@acorex/platform/core';
|
|
9
9
|
import * as i1 from '@acorex/platform/layout/widget-core';
|
|
10
10
|
import { AXPWidgetSerializationHelper, AXPWidgetContainerComponent, AXPPageStatus, AXPWidgetCoreModule, AXPWidgetRegistryService } from '@acorex/platform/layout/widget-core';
|
|
11
11
|
import { cloneDeep, isNil, set, isEqual, merge } from 'lodash-es';
|
|
12
12
|
import * as i2 from '@acorex/components/form';
|
|
13
13
|
import { AXFormComponent, AXFormModule } from '@acorex/components/form';
|
|
14
14
|
import { Subject, debounceTime, distinctUntilChanged, startWith } from 'rxjs';
|
|
15
|
+
import { AXOverlayService } from '@acorex/cdk/overlay';
|
|
15
16
|
import * as i1$1 from '@acorex/components/button';
|
|
16
17
|
import { AXButtonModule } from '@acorex/components/button';
|
|
18
|
+
import { AXDialogService } from '@acorex/components/dialog';
|
|
17
19
|
import * as i2$1 from '@acorex/components/decorators';
|
|
18
20
|
import { AXDecoratorModule } from '@acorex/components/decorators';
|
|
19
21
|
import * as i3 from '@acorex/components/loading';
|
|
20
22
|
import { AXLoadingModule } from '@acorex/components/loading';
|
|
21
23
|
import { AXBasePageComponent } from '@acorex/components/page';
|
|
22
24
|
import * as i6 from '@acorex/core/translation';
|
|
23
|
-
import {
|
|
25
|
+
import { AXTranslationService, AXTranslationModule } from '@acorex/core/translation';
|
|
24
26
|
import { AXP_ENTITY_DEFINITION_CRUD_SERVICE } from '@acorex/platform/domain';
|
|
25
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
|
+
|
|
26
169
|
/** Fallback {@link AXPDialogRef} when the popup is dismissed without a footer action (e.g. header close). */
|
|
27
170
|
function createDismissedDialogRef(context = () => ({})) {
|
|
28
171
|
return {
|
|
@@ -347,6 +490,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
347
490
|
}]
|
|
348
491
|
}] });
|
|
349
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
|
+
|
|
350
508
|
//#region ---- Inheritance Utilities ----
|
|
351
509
|
/**
|
|
352
510
|
* Resolves inherited properties from context and local values
|
|
@@ -1636,6 +1794,9 @@ class DialogContainerBuilder {
|
|
|
1636
1794
|
this.dialogState.dialogOptions.onAction = handler;
|
|
1637
1795
|
return this;
|
|
1638
1796
|
}
|
|
1797
|
+
confirmCloseWhenDirty(options) {
|
|
1798
|
+
return this.setOptions({ confirmCloseWhenDirty: options ?? true });
|
|
1799
|
+
}
|
|
1639
1800
|
addCustomAction(action) {
|
|
1640
1801
|
// Add to actions based on position
|
|
1641
1802
|
const position = action.position || 'suffix';
|
|
@@ -1699,6 +1860,7 @@ class DialogContainerBuilder {
|
|
|
1699
1860
|
};
|
|
1700
1861
|
await hookService.runAsync(AXP_LAYOUT_BUILDER_DIALOG_BEFORE_OPEN_HOOK_KEY, beforePayload);
|
|
1701
1862
|
}
|
|
1863
|
+
const confirmCloseWhenDirty = normalizeDialogCloseConfirmation(this.dialogState.dialogOptions?.confirmCloseWhenDirty);
|
|
1702
1864
|
// Create dialog configuration
|
|
1703
1865
|
const dialogConfig = {
|
|
1704
1866
|
title: this.dialogState.dialogOptions?.title || '',
|
|
@@ -1709,6 +1871,7 @@ class DialogContainerBuilder {
|
|
|
1709
1871
|
metadata: this.dialogState.dialogOptions.metadata,
|
|
1710
1872
|
actions: this.dialogState.actions,
|
|
1711
1873
|
onAction: this.dialogState.dialogOptions?.onAction,
|
|
1874
|
+
confirmCloseWhenDirty,
|
|
1712
1875
|
};
|
|
1713
1876
|
//
|
|
1714
1877
|
if (hookService) {
|
|
@@ -1912,7 +2075,7 @@ class ActionBuilder {
|
|
|
1912
2075
|
constructor(dialogBuilder) {
|
|
1913
2076
|
this.dialogBuilder = dialogBuilder;
|
|
1914
2077
|
}
|
|
1915
|
-
cancel(text) {
|
|
2078
|
+
cancel(text, options) {
|
|
1916
2079
|
if (!this.dialogBuilder['dialogState'].actions.footer.suffix) {
|
|
1917
2080
|
this.dialogBuilder['dialogState'].actions.footer.suffix = [];
|
|
1918
2081
|
}
|
|
@@ -1920,10 +2083,11 @@ class ActionBuilder {
|
|
|
1920
2083
|
title: text || '@general:actions.cancel.title',
|
|
1921
2084
|
color: 'default',
|
|
1922
2085
|
command: { name: 'cancel' },
|
|
2086
|
+
shortcuts: resolveDialogActionShortcuts(DEFAULT_CANCEL_DIALOG_ACTION_SHORTCUTS, options?.shortcuts),
|
|
1923
2087
|
});
|
|
1924
2088
|
return this;
|
|
1925
2089
|
}
|
|
1926
|
-
submit(text) {
|
|
2090
|
+
submit(text, options) {
|
|
1927
2091
|
if (!this.dialogBuilder['dialogState'].actions.footer.suffix) {
|
|
1928
2092
|
this.dialogBuilder['dialogState'].actions.footer.suffix = [];
|
|
1929
2093
|
}
|
|
@@ -1931,22 +2095,27 @@ class ActionBuilder {
|
|
|
1931
2095
|
title: text || '@general:actions.submit.title',
|
|
1932
2096
|
color: 'primary',
|
|
1933
2097
|
command: { name: 'submit', options: { validate: true } },
|
|
2098
|
+
shortcuts: resolveDialogActionShortcuts(DEFAULT_SUBMIT_DIALOG_ACTION_SHORTCUTS, options?.shortcuts),
|
|
1934
2099
|
});
|
|
1935
2100
|
return this;
|
|
1936
2101
|
}
|
|
1937
|
-
custom(action) {
|
|
1938
|
-
const
|
|
2102
|
+
custom(action, options) {
|
|
2103
|
+
const item = {
|
|
2104
|
+
...action,
|
|
2105
|
+
shortcuts: resolveDialogActionShortcuts(action.shortcuts ?? [], options?.shortcuts),
|
|
2106
|
+
};
|
|
2107
|
+
const position = item.position ?? 'suffix';
|
|
1939
2108
|
if (position === 'prefix') {
|
|
1940
2109
|
if (!this.dialogBuilder['dialogState'].actions.footer.prefix) {
|
|
1941
2110
|
this.dialogBuilder['dialogState'].actions.footer.prefix = [];
|
|
1942
2111
|
}
|
|
1943
|
-
this.dialogBuilder['dialogState'].actions.footer.prefix.push(
|
|
2112
|
+
this.dialogBuilder['dialogState'].actions.footer.prefix.push(item);
|
|
1944
2113
|
}
|
|
1945
2114
|
else {
|
|
1946
2115
|
if (!this.dialogBuilder['dialogState'].actions.footer.suffix) {
|
|
1947
2116
|
this.dialogBuilder['dialogState'].actions.footer.suffix = [];
|
|
1948
2117
|
}
|
|
1949
|
-
this.dialogBuilder['dialogState'].actions.footer.suffix.push(
|
|
2118
|
+
this.dialogBuilder['dialogState'].actions.footer.suffix.push(item);
|
|
1950
2119
|
}
|
|
1951
2120
|
return this;
|
|
1952
2121
|
}
|
|
@@ -2386,6 +2555,144 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
2386
2555
|
}]
|
|
2387
2556
|
}] });
|
|
2388
2557
|
|
|
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
|
+
}
|
|
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;
|
|
2389
2696
|
class AXPDialogRendererComponent extends AXBasePageComponent {
|
|
2390
2697
|
constructor() {
|
|
2391
2698
|
super(...arguments);
|
|
@@ -2393,8 +2700,18 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
|
|
|
2393
2700
|
this.expressionEvaluator = inject(AXPExpressionEvaluatorService);
|
|
2394
2701
|
this.commandService = inject(AXPCommandService);
|
|
2395
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));
|
|
2396
2708
|
/** Ensures `show()` resolves once when the dialog closes (footer action or header close). */
|
|
2397
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);
|
|
2398
2715
|
this.context = signal({}, ...(ngDevMode ? [{ debugName: "context" }] : /* istanbul ignore next */ []));
|
|
2399
2716
|
// This will be set by the popup service automatically - same as dynamic-dialog
|
|
2400
2717
|
this.callBack = () => { };
|
|
@@ -2408,39 +2725,191 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
|
|
|
2408
2725
|
this.contextChangedHooksSessionKey = typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function'
|
|
2409
2726
|
? crypto.randomUUID()
|
|
2410
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
|
+
};
|
|
2411
2767
|
//#endregion
|
|
2412
2768
|
//#region ---- View Accessors ----
|
|
2413
2769
|
// Access the internal layout renderer to reach the widgets container injector
|
|
2414
2770
|
this.layoutRenderer = viewChild(AXPLayoutRendererComponent, ...(ngDevMode ? [{ debugName: "layoutRenderer" }] : /* istanbul ignore next */ []));
|
|
2415
|
-
this.#
|
|
2416
|
-
let count = 0;
|
|
2771
|
+
this.#widgetActionsEffect = effect(() => {
|
|
2417
2772
|
this.aggregateAndEvaluateActions();
|
|
2418
2773
|
if (!this.widgetCoreService) {
|
|
2419
2774
|
const renderer = this.layoutRenderer();
|
|
2420
2775
|
const container = renderer?.getContainer();
|
|
2421
2776
|
this.widgetCoreService = container?.builderService ?? null;
|
|
2422
|
-
|
|
2777
|
+
return;
|
|
2423
2778
|
}
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
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;
|
|
2790
|
+
}
|
|
2791
|
+
const store = this.getWidgetContextStore();
|
|
2792
|
+
if (!store) {
|
|
2793
|
+
return;
|
|
2434
2794
|
}
|
|
2435
|
-
|
|
2795
|
+
store.data();
|
|
2796
|
+
this.widgetCoreService?.registeredWidgetsCount();
|
|
2797
|
+
this.scheduleDirtyBaselineCapture();
|
|
2798
|
+
}, ...(ngDevMode ? [{ debugName: "#dirtyBaselineEffect" }] : /* istanbul ignore next */ []));
|
|
2436
2799
|
}
|
|
2437
2800
|
//#endregion
|
|
2438
2801
|
//#region ---- Lifecycle ----
|
|
2439
2802
|
ngOnInit() {
|
|
2440
|
-
this.
|
|
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
|
+
});
|
|
2441
2818
|
void this.invokeLayoutContextChangedHooks();
|
|
2442
2819
|
}
|
|
2443
|
-
|
|
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
|
+
}
|
|
2910
|
+
}
|
|
2911
|
+
#widgetActionsEffect;
|
|
2912
|
+
#dirtyBaselineEffect;
|
|
2444
2913
|
//#endregion
|
|
2445
2914
|
handleContextChanged(event) {
|
|
2446
2915
|
this.context.set(event);
|
|
@@ -2459,7 +2928,10 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
|
|
|
2459
2928
|
}
|
|
2460
2929
|
const payload = {
|
|
2461
2930
|
sessionKey: this.contextChangedHooksSessionKey,
|
|
2462
|
-
getContext: () =>
|
|
2931
|
+
getContext: () => {
|
|
2932
|
+
const store = this.getWidgetContextStore();
|
|
2933
|
+
return (store?.data() ?? this.context() ?? {});
|
|
2934
|
+
},
|
|
2463
2935
|
metadata: meta,
|
|
2464
2936
|
patchContext: (partial) => {
|
|
2465
2937
|
const merged = merge({}, this.context(), partial);
|
|
@@ -2525,6 +2997,7 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
|
|
|
2525
2997
|
}
|
|
2526
2998
|
const context = this.context();
|
|
2527
2999
|
const onAction = this.config?.onAction;
|
|
3000
|
+
// `onAction` return value is passed through to `show()` unchanged — see dialog-resolve.util.ts.
|
|
2528
3001
|
if (onAction) {
|
|
2529
3002
|
const dialogRef = this.createDialogRef(cmd);
|
|
2530
3003
|
try {
|
|
@@ -2533,6 +3006,12 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
|
|
|
2533
3006
|
if (this.shouldKeepDialogOpenAfterCommandResult(result)) {
|
|
2534
3007
|
return;
|
|
2535
3008
|
}
|
|
3009
|
+
if (cmd === 'cancel' &&
|
|
3010
|
+
shouldRouteOnActionCancelThroughDismissGate(this.config?.confirmCloseWhenDirty?.enabled)) {
|
|
3011
|
+
this.isDialogLoading.set(false);
|
|
3012
|
+
this.requestDismiss(result);
|
|
3013
|
+
return;
|
|
3014
|
+
}
|
|
2536
3015
|
this.resolveDialog(result);
|
|
2537
3016
|
await this.closeWithOptionalSkipValidate(result);
|
|
2538
3017
|
}
|
|
@@ -2544,21 +3023,57 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
|
|
|
2544
3023
|
}
|
|
2545
3024
|
return;
|
|
2546
3025
|
}
|
|
2547
|
-
// Fallback:
|
|
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.
|
|
2548
3028
|
const result = { context, action: cmd };
|
|
2549
3029
|
this.dialogResult = result;
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
this.data.action = result.action;
|
|
2553
|
-
}
|
|
2554
|
-
this.resolveDialog({
|
|
2555
|
-
...this.createDialogRef(cmd),
|
|
2556
|
-
action: () => result.action,
|
|
2557
|
-
});
|
|
2558
|
-
// Without `onAction`, only the configured cancel action dismisses the dialog (not submit/custom).
|
|
3030
|
+
syncLegacyDialogDataSideFields(this.data, result.context, result.action);
|
|
3031
|
+
const dialogRefPayload = buildLayoutBuilderCustomActionRef((command) => this.createDialogRef(command), cmd);
|
|
2559
3032
|
if (cmd === 'cancel') {
|
|
2560
|
-
|
|
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;
|
|
2561
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;
|
|
2562
3077
|
}
|
|
2563
3078
|
/** Whether the layout form should be validated before running this footer command. */
|
|
2564
3079
|
shouldValidateBeforeAction(cmd) {
|
|
@@ -2614,10 +3129,12 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
|
|
|
2614
3129
|
async closeWithOptionalSkipValidate(result) {
|
|
2615
3130
|
if (result && typeof result === 'object' && result.skipValidate) {
|
|
2616
3131
|
this.result.emit(result);
|
|
2617
|
-
|
|
3132
|
+
this.skipNextOnClosingDirtyCheck = true;
|
|
3133
|
+
this.pendingDismissResolvePayload = undefined;
|
|
3134
|
+
super.close(result);
|
|
2618
3135
|
return;
|
|
2619
3136
|
}
|
|
2620
|
-
await this.close(result);
|
|
3137
|
+
await this.close(result, { skipDirtyConfirm: true });
|
|
2621
3138
|
}
|
|
2622
3139
|
/** Resolves footer/widget action command to a string (e.g. `cancel`, `submit`, `widget:...`). */
|
|
2623
3140
|
resolveActionCommandName(command) {
|
|
@@ -2664,9 +3181,10 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
|
|
|
2664
3181
|
//
|
|
2665
3182
|
}
|
|
2666
3183
|
}
|
|
2667
|
-
async close(result) {
|
|
2668
|
-
if (
|
|
2669
|
-
this.
|
|
3184
|
+
async close(result, options) {
|
|
3185
|
+
if (options?.skipDirtyConfirm) {
|
|
3186
|
+
this.skipNextOnClosingDirtyCheck = true;
|
|
3187
|
+
this.pendingDismissResolvePayload = undefined;
|
|
2670
3188
|
}
|
|
2671
3189
|
if (result) {
|
|
2672
3190
|
const isValid = await this.layoutRenderer()?.validate();
|
|
@@ -2716,6 +3234,7 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
|
|
|
2716
3234
|
placement,
|
|
2717
3235
|
scope: a.scope,
|
|
2718
3236
|
predicateApiWidgetName: a.predicateApiWidgetName,
|
|
3237
|
+
shortcuts: resolveConfiguredFooterActionShortcuts(typeof a.command === 'string' ? a.command : a.command?.name, a.shortcuts),
|
|
2719
3238
|
});
|
|
2720
3239
|
const prefix = (footer?.prefix || []).map((a) => mapOne(a, 'prefix'));
|
|
2721
3240
|
const suffix = (footer?.suffix || []).map((a) => mapOne(a, 'suffix'));
|
|
@@ -3124,5 +3643,5 @@ var previewWidgetField_command = /*#__PURE__*/Object.freeze({
|
|
|
3124
3643
|
* Generated bundle index. Do not edit.
|
|
3125
3644
|
*/
|
|
3126
3645
|
|
|
3127
|
-
export { AXPDialogRendererComponent, AXPLayoutBuilderService, AXPLayoutConversionService, AXPLayoutRendererComponent, AXPPreviewWidgetFieldCommand, AXP_LAYOUT_BUILDER_DIALOG_BEFORE_OPEN_HOOK_KEY, AXP_LAYOUT_BUILDER_DIALOG_CONFIG_HOOK_KEY, AXP_LAYOUT_BUILDER_DIALOG_CONTEXT_CHANGED_HOOK_KEY, AXP_PREVIEW_WIDGET_FIELD_COMMAND_KEY, LayoutBuilderModule, createDismissedDialogRef };
|
|
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 };
|
|
3128
3647
|
//# sourceMappingURL=acorex-platform-layout-builder.mjs.map
|