@acorex-platform/framework-client 0.0.56 → 0.0.58
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-framework-client-common.mjs +31 -33
- package/fesm2022/acorex-platform-framework-client-common.mjs.map +1 -1
- package/fesm2022/acorex-platform-framework-client-core.mjs +157 -2
- package/fesm2022/acorex-platform-framework-client-core.mjs.map +1 -1
- package/fesm2022/acorex-platform-framework-client-layout-builder.mjs +58 -274
- package/fesm2022/acorex-platform-framework-client-layout-builder.mjs.map +1 -1
- package/fesm2022/{acorex-platform-framework-client-layout-components-binding-expression-editor-popup.component-BqPaHKPM.mjs → acorex-platform-framework-client-layout-components-binding-expression-editor-popup.component-CyZpdazq.mjs} +6 -6
- package/fesm2022/{acorex-platform-framework-client-layout-components-binding-expression-editor-popup.component-BqPaHKPM.mjs.map → acorex-platform-framework-client-layout-components-binding-expression-editor-popup.component-CyZpdazq.mjs.map} +1 -1
- package/fesm2022/acorex-platform-framework-client-layout-components.mjs +657 -214
- package/fesm2022/acorex-platform-framework-client-layout-components.mjs.map +1 -1
- package/fesm2022/acorex-platform-framework-client-layout-designer.mjs +32 -16
- package/fesm2022/acorex-platform-framework-client-layout-designer.mjs.map +1 -1
- package/fesm2022/{acorex-platform-framework-client-layout-entity-acorex-platform-framework-client-layout-entity-DLW9GiJh.mjs → acorex-platform-framework-client-layout-entity-acorex-platform-framework-client-layout-entity-jeOx-Rb8.mjs} +277 -198
- package/fesm2022/acorex-platform-framework-client-layout-entity-acorex-platform-framework-client-layout-entity-jeOx-Rb8.mjs.map +1 -0
- package/fesm2022/{acorex-platform-framework-client-layout-entity-attachments-page.component-BY_hDj8A.mjs → acorex-platform-framework-client-layout-entity-attachments-page.component-B93uWhea.mjs} +2 -2
- package/fesm2022/{acorex-platform-framework-client-layout-entity-attachments-page.component-BY_hDj8A.mjs.map → acorex-platform-framework-client-layout-entity-attachments-page.component-B93uWhea.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-framework-client-layout-entity-bulk-entity.command-DccwhqkT.mjs → acorex-platform-framework-client-layout-entity-bulk-entity.command-DITHE0f6.mjs} +2 -2
- package/fesm2022/{acorex-platform-framework-client-layout-entity-bulk-entity.command-DccwhqkT.mjs.map → acorex-platform-framework-client-layout-entity-bulk-entity.command-DITHE0f6.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-framework-client-layout-entity-gallery-page.component-D7xxrauC.mjs → acorex-platform-framework-client-layout-entity-gallery-page.component-f5rLdctT.mjs} +2 -2
- package/fesm2022/{acorex-platform-framework-client-layout-entity-gallery-page.component-D7xxrauC.mjs.map → acorex-platform-framework-client-layout-entity-gallery-page.component-f5rLdctT.mjs.map} +1 -1
- package/fesm2022/acorex-platform-framework-client-layout-entity.mjs +1 -1
- package/fesm2022/acorex-platform-framework-client-layout-widget-core.mjs +59 -3
- package/fesm2022/acorex-platform-framework-client-layout-widget-core.mjs.map +1 -1
- package/fesm2022/{acorex-platform-framework-client-layout-widgets-acorex-platform-framework-client-layout-widgets-DuFZf8eb.mjs → acorex-platform-framework-client-layout-widgets-acorex-platform-framework-client-layout-widgets-D9WiRo-_.mjs} +330 -440
- package/fesm2022/acorex-platform-framework-client-layout-widgets-acorex-platform-framework-client-layout-widgets-D9WiRo-_.mjs.map +1 -0
- package/fesm2022/{acorex-platform-framework-client-layout-widgets-code-editor-widget-column.component-BL621E1r.mjs → acorex-platform-framework-client-layout-widgets-code-editor-widget-column.component-DqJIiEyu.mjs} +9 -3
- package/fesm2022/acorex-platform-framework-client-layout-widgets-code-editor-widget-column.component-DqJIiEyu.mjs.map +1 -0
- package/fesm2022/{acorex-platform-framework-client-layout-widgets-repeater-widget-column.component-BiGUrkFi.mjs → acorex-platform-framework-client-layout-widgets-repeater-widget-column.component-kwbJdTwe.mjs} +2 -2
- package/fesm2022/{acorex-platform-framework-client-layout-widgets-repeater-widget-column.component-BiGUrkFi.mjs.map → acorex-platform-framework-client-layout-widgets-repeater-widget-column.component-kwbJdTwe.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-framework-client-layout-widgets-text-block-widget-designer.component-BYg6-PoM.mjs → acorex-platform-framework-client-layout-widgets-text-block-widget-designer.component-Bke-Wy-4.mjs} +2 -2
- package/fesm2022/{acorex-platform-framework-client-layout-widgets-text-block-widget-designer.component-BYg6-PoM.mjs.map → acorex-platform-framework-client-layout-widgets-text-block-widget-designer.component-Bke-Wy-4.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-framework-client-layout-widgets-triggers-widget-editor.component-B_OM0T7i.mjs → acorex-platform-framework-client-layout-widgets-triggers-widget-editor.component-C-r7txgg.mjs} +21 -17
- package/fesm2022/acorex-platform-framework-client-layout-widgets-triggers-widget-editor.component-C-r7txgg.mjs.map +1 -0
- package/fesm2022/acorex-platform-framework-client-layout-widgets.mjs +1 -1
- package/fesm2022/acorex-platform-framework-client-themes-shared.mjs +48 -9
- package/fesm2022/acorex-platform-framework-client-themes-shared.mjs.map +1 -1
- package/fesm2022/acorex-platform-framework-client-workflow.mjs +47 -1
- package/fesm2022/acorex-platform-framework-client-workflow.mjs.map +1 -1
- package/i18n/en-AU/platform-layout-widgets.json +0 -1
- package/i18n/en-US/platform-layout-widgets.json +0 -1
- package/i18n/fa-IR/platform-layout-widgets.json +0 -1
- package/package.json +2 -2
- package/types/acorex-platform-framework-client-common.d.ts +2 -3
- package/types/acorex-platform-framework-client-core.d.ts +67 -2
- package/types/acorex-platform-framework-client-layout-builder.d.ts +4 -23
- package/types/acorex-platform-framework-client-layout-components.d.ts +121 -7
- package/types/acorex-platform-framework-client-layout-designer.d.ts +2 -0
- package/types/acorex-platform-framework-client-layout-entity.d.ts +25 -17
- package/types/acorex-platform-framework-client-layout-widget-core.d.ts +14 -1
- package/types/acorex-platform-framework-client-layout-widgets.d.ts +33 -69
- package/types/acorex-platform-framework-client-workflow.d.ts +29 -2
- package/fesm2022/acorex-platform-framework-client-layout-entity-acorex-platform-framework-client-layout-entity-DLW9GiJh.mjs.map +0 -1
- package/fesm2022/acorex-platform-framework-client-layout-widgets-acorex-platform-framework-client-layout-widgets-DuFZf8eb.mjs.map +0 -1
- package/fesm2022/acorex-platform-framework-client-layout-widgets-code-editor-widget-column.component-BL621E1r.mjs.map +0 -1
- package/fesm2022/acorex-platform-framework-client-layout-widgets-triggers-widget-editor.component-B_OM0T7i.mjs.map +0 -1
|
@@ -3,10 +3,10 @@ import { CommonModule, DOCUMENT } from '@angular/common';
|
|
|
3
3
|
import * as i0 from '@angular/core';
|
|
4
4
|
import { Injectable, inject, input, model, signal, computed, effect, output, viewChild, ChangeDetectionStrategy, Component, NgModule, EventEmitter, ElementRef, DestroyRef, untracked, Output } from '@angular/core';
|
|
5
5
|
import * as i4 from '@acorex-platform/framework-client/core';
|
|
6
|
-
import { AXPHookService, resolveDialogActionShortcuts, DEFAULT_CANCEL_DIALOG_ACTION_SHORTCUTS, DEFAULT_SUBMIT_DIALOG_ACTION_SHORTCUTS, AXP_SETTINGS_DEFINITION_DIALOG_OPENER, findOverlayContainerAncestor, getNestedVisibleOverlayPanes, getTopVisibleOverlayContainer, AXPExpressionEvaluatorService, AXPKeyboardShortcutRegistry,
|
|
7
|
-
export { DEFAULT_CANCEL_DIALOG_ACTION_SHORTCUTS, DEFAULT_SUBMIT_DIALOG_ACTION_SHORTCUTS } from '@acorex-platform/framework-client/core';
|
|
6
|
+
import { AXPHookService, AXPDialogOpener, resolveDialogActionShortcuts, DEFAULT_CANCEL_DIALOG_ACTION_SHORTCUTS, DEFAULT_SUBMIT_DIALOG_ACTION_SHORTCUTS, AXP_SETTINGS_DEFINITION_DIALOG_OPENER, findOverlayContainerAncestor, getNestedVisibleOverlayPanes, getTopVisibleOverlayContainer, AXPExpressionEvaluatorService, AXPKeyboardShortcutRegistry, normalizeDialogActionShortcutChord, isDialogShortcutAllowedInEditableField, resolveConfiguredFooterActionShortcuts, AXPComponentSlotModule, AXPContextStore } from '@acorex-platform/framework-client/core';
|
|
7
|
+
export { AXPDialogOpener, AXP_ROOT_LOADING, DEFAULT_CANCEL_DIALOG_ACTION_SHORTCUTS, DEFAULT_SUBMIT_DIALOG_ACTION_SHORTCUTS } from '@acorex-platform/framework-client/core';
|
|
8
8
|
import { provideCommandSetups, AXPCommandService } from '@acorex-platform/framework-client/runtime';
|
|
9
|
-
import { toAxpGridItemWidgetOptions, AXP_PREVIEW_WIDGET_FIELD_COMMAND_KEY,
|
|
9
|
+
import { toAxpGridItemWidgetOptions, AXP_PREVIEW_WIDGET_FIELD_COMMAND_KEY, createRandomUuid, AXPKeyboardShortcutPriority } from '@acorex-platform/framework-shared/core';
|
|
10
10
|
import { AXPopupService } from '@acorex/components/popup';
|
|
11
11
|
import * as i1 from '@acorex-platform/framework-client/layout/widget-core';
|
|
12
12
|
import { AXPWidgetSerializationHelper, AXPWidgetContainerComponent, AXPWidgetCoreModule, AXPWidgetRegistryService } from '@acorex-platform/framework-client/layout/widget-core';
|
|
@@ -22,7 +22,6 @@ import * as i2$1 from '@acorex/components/decorators';
|
|
|
22
22
|
import { AXDecoratorModule } from '@acorex/components/decorators';
|
|
23
23
|
import * as i3 from '@acorex/components/loading';
|
|
24
24
|
import { AXLoadingModule } from '@acorex/components/loading';
|
|
25
|
-
import { AXLoadingDialogService } from '@acorex/components/loading-dialog';
|
|
26
25
|
import { AXBasePageComponent } from '@acorex/components/page';
|
|
27
26
|
import { AXToastService } from '@acorex/components/toast';
|
|
28
27
|
import * as i6 from '@acorex/core/translation';
|
|
@@ -376,83 +375,6 @@ function normalizeDialogCloseConfirmation(value) {
|
|
|
376
375
|
}
|
|
377
376
|
//#endregion
|
|
378
377
|
|
|
379
|
-
/** Panel class on the popup overlay container; the reveal styles live in the host app. */
|
|
380
|
-
const AXP_DIALOG_REVEAL_PANEL_CLASS = 'axp-dialog-reveal';
|
|
381
|
-
/** Entry is owned by the panel class, so the overlay must not add its own fade-in. */
|
|
382
|
-
const AXP_DIALOG_REVEAL_POPUP_ANIMATION = { enter: [] };
|
|
383
|
-
/** Added once layout has settled so the reveal animation can play. */
|
|
384
|
-
const READY_CLASS = 'axp-dialog-reveal-ready';
|
|
385
|
-
/** The popup must keep the same size for this long before it counts as settled. */
|
|
386
|
-
const AXP_DIALOG_REVEAL_RESIZE_STABLE_MS = 320;
|
|
387
|
-
/** Upper bound for the stability wait (heavy entity step wizards). */
|
|
388
|
-
const RESIZE_WAIT_MAX_MS = 2800;
|
|
389
|
-
/** Resolves once the popup shell stops resizing (widgets, footer chrome, step content). */
|
|
390
|
-
function waitForDialogPopupLayoutStable(from) {
|
|
391
|
-
const popup = from.closest('.ax-popup');
|
|
392
|
-
if (!(popup instanceof HTMLElement)) {
|
|
393
|
-
return Promise.resolve();
|
|
394
|
-
}
|
|
395
|
-
return new Promise((resolve) => {
|
|
396
|
-
let quietTimer;
|
|
397
|
-
const finish = () => {
|
|
398
|
-
clearTimeout(quietTimer);
|
|
399
|
-
clearTimeout(maxWaitTimer);
|
|
400
|
-
observer.disconnect();
|
|
401
|
-
resolve();
|
|
402
|
-
};
|
|
403
|
-
const restartQuietWindow = () => {
|
|
404
|
-
clearTimeout(quietTimer);
|
|
405
|
-
quietTimer = setTimeout(finish, AXP_DIALOG_REVEAL_RESIZE_STABLE_MS);
|
|
406
|
-
};
|
|
407
|
-
const maxWaitTimer = setTimeout(finish, RESIZE_WAIT_MAX_MS);
|
|
408
|
-
const observer = new ResizeObserver(restartQuietWindow);
|
|
409
|
-
observer.observe(popup);
|
|
410
|
-
restartQuietWindow();
|
|
411
|
-
});
|
|
412
|
-
}
|
|
413
|
-
const FIRST_FOCUSABLE_FIELD_SELECTOR = [
|
|
414
|
-
'input:not([type="hidden"]):not([type="button"]):not([type="submit"]):not([type="reset"]):not([disabled]):not([tabindex="-1"])',
|
|
415
|
-
'textarea:not([disabled]):not([tabindex="-1"])',
|
|
416
|
-
'select:not([disabled]):not([tabindex="-1"])',
|
|
417
|
-
'[contenteditable="true"]:not([tabindex="-1"])',
|
|
418
|
-
].join(', ');
|
|
419
|
-
/** Reveals the popup shell that the panel class kept hidden, then focuses the first field. */
|
|
420
|
-
function markDialogRevealReady(from) {
|
|
421
|
-
from.closest(`.${AXP_DIALOG_REVEAL_PANEL_CLASS}`)?.classList.add(READY_CLASS);
|
|
422
|
-
focusFirstPopupField(from);
|
|
423
|
-
}
|
|
424
|
-
/**
|
|
425
|
-
* Focuses the first visible field in the popup body.
|
|
426
|
-
* Runs after reveal: the shell focus from the popup library happens while the overlay is still hidden.
|
|
427
|
-
*/
|
|
428
|
-
function focusFirstPopupField(from) {
|
|
429
|
-
const popup = from.closest('.ax-popup');
|
|
430
|
-
if (!(popup instanceof HTMLElement)) {
|
|
431
|
-
return;
|
|
432
|
-
}
|
|
433
|
-
setTimeout(() => {
|
|
434
|
-
const body = popup.querySelector('.ax-popup-body-container');
|
|
435
|
-
if (!(body instanceof HTMLElement)) {
|
|
436
|
-
return;
|
|
437
|
-
}
|
|
438
|
-
const field = Array.from(body.querySelectorAll(FIRST_FOCUSABLE_FIELD_SELECTOR)).find(isVisibleFocusTarget);
|
|
439
|
-
(field ?? popup).focus();
|
|
440
|
-
});
|
|
441
|
-
}
|
|
442
|
-
function isVisibleFocusTarget(element) {
|
|
443
|
-
if (element.closest('[hidden], [aria-hidden="true"]')) {
|
|
444
|
-
return false;
|
|
445
|
-
}
|
|
446
|
-
if (element.getAttribute('aria-disabled') === 'true') {
|
|
447
|
-
return false;
|
|
448
|
-
}
|
|
449
|
-
const style = getComputedStyle(element);
|
|
450
|
-
if (style.display === 'none' || style.visibility === 'hidden' || Number(style.opacity) === 0) {
|
|
451
|
-
return false;
|
|
452
|
-
}
|
|
453
|
-
return element.getClientRects().length > 0;
|
|
454
|
-
}
|
|
455
|
-
|
|
456
378
|
//#region ---- Inheritance Utilities ----
|
|
457
379
|
/**
|
|
458
380
|
* Resolves inherited properties from context and local values
|
|
@@ -557,12 +479,13 @@ class AXPLayoutBuilderService {
|
|
|
557
479
|
constructor() {
|
|
558
480
|
this.popupService = inject(AXPopupService);
|
|
559
481
|
this.hookService = inject(AXPHookService, { optional: true }) ?? undefined;
|
|
482
|
+
this.dialogOpener = inject(AXPDialogOpener);
|
|
560
483
|
}
|
|
561
484
|
/**
|
|
562
485
|
* Create a new layout builder
|
|
563
486
|
*/
|
|
564
487
|
create() {
|
|
565
|
-
return new LayoutBuilder(this.popupService, this.hookService);
|
|
488
|
+
return new LayoutBuilder(this.popupService, this.hookService, this.dialogOpener);
|
|
566
489
|
}
|
|
567
490
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPLayoutBuilderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
568
491
|
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPLayoutBuilderService, providedIn: 'root' }); }
|
|
@@ -580,9 +503,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
|
|
|
580
503
|
* Open/Closed: Extensible through container delegates
|
|
581
504
|
*/
|
|
582
505
|
class LayoutBuilder {
|
|
583
|
-
constructor(popupService, hookService) {
|
|
506
|
+
constructor(popupService, hookService, dialogOpener) {
|
|
584
507
|
this.popupService = popupService;
|
|
585
508
|
this.hookService = hookService;
|
|
509
|
+
this.dialogOpener = dialogOpener;
|
|
586
510
|
this.root = {
|
|
587
511
|
children: [],
|
|
588
512
|
mode: 'edit',
|
|
@@ -664,7 +588,7 @@ class LayoutBuilder {
|
|
|
664
588
|
if (!this.popupService) {
|
|
665
589
|
throw new Error('LayoutBuilder requires AXPopupService to create dialogs. Please inject it in the service constructor.');
|
|
666
590
|
}
|
|
667
|
-
const container = new DialogContainerBuilder(this.popupService, this.hookService);
|
|
591
|
+
const container = new DialogContainerBuilder(this.popupService, this.hookService, this.dialogOpener);
|
|
668
592
|
if (delegate) {
|
|
669
593
|
delegate(container);
|
|
670
594
|
}
|
|
@@ -1628,7 +1552,7 @@ class ListWidgetBuilder extends WidgetContainerMixin {
|
|
|
1628
1552
|
* Uses composition instead of inheritance for cleaner separation
|
|
1629
1553
|
*/
|
|
1630
1554
|
class DialogContainerBuilder {
|
|
1631
|
-
constructor(popupService, hookService) {
|
|
1555
|
+
constructor(popupService, hookService, dialogOpener) {
|
|
1632
1556
|
this.dialogState = {
|
|
1633
1557
|
type: 'flex-layout', // This will be overridden when content layout exists
|
|
1634
1558
|
children: [],
|
|
@@ -1652,6 +1576,7 @@ class DialogContainerBuilder {
|
|
|
1652
1576
|
this.popupService = inject(AXPopupService);
|
|
1653
1577
|
}
|
|
1654
1578
|
this.hookService = hookService ?? inject(AXPHookService, { optional: true }) ?? undefined;
|
|
1579
|
+
this.dialogOpener = dialogOpener ?? inject(AXPDialogOpener);
|
|
1655
1580
|
}
|
|
1656
1581
|
setOptions(options) {
|
|
1657
1582
|
this.dialogState.dialogOptions = { ...this.dialogState.dialogOptions, ...options };
|
|
@@ -1748,10 +1673,40 @@ class DialogContainerBuilder {
|
|
|
1748
1673
|
return result;
|
|
1749
1674
|
}
|
|
1750
1675
|
// Dialog-specific methods
|
|
1751
|
-
async show() {
|
|
1676
|
+
async show(options) {
|
|
1677
|
+
const outcome = await this.dialogOpener.run({
|
|
1678
|
+
load: () => Promise.resolve().then(function () { return dialogRenderer_component; }).then((module) => module.AXPDialogRendererComponent),
|
|
1679
|
+
data: this.dialogOpenKeyData(),
|
|
1680
|
+
present: (notifyShown, component) => this.presentDialog(component, notifyShown, options),
|
|
1681
|
+
});
|
|
1682
|
+
if (outcome.duplicate) {
|
|
1683
|
+
return createDismissedDialogRef();
|
|
1684
|
+
}
|
|
1685
|
+
return outcome.result;
|
|
1686
|
+
}
|
|
1687
|
+
dialogOpenKeyData() {
|
|
1688
|
+
const dialogOptions = this.dialogState.dialogOptions;
|
|
1689
|
+
return {
|
|
1690
|
+
title: dialogOptions?.title ?? '',
|
|
1691
|
+
size: String(dialogOptions?.size ?? 'md'),
|
|
1692
|
+
widget: this.contentWidgetName(this.contentLayout),
|
|
1693
|
+
};
|
|
1694
|
+
}
|
|
1695
|
+
contentWidgetName(node) {
|
|
1696
|
+
if (!node) {
|
|
1697
|
+
return '';
|
|
1698
|
+
}
|
|
1699
|
+
if (node.name) {
|
|
1700
|
+
return node.name;
|
|
1701
|
+
}
|
|
1702
|
+
const child = node.children?.[0];
|
|
1703
|
+
if (child) {
|
|
1704
|
+
return this.contentWidgetName(child);
|
|
1705
|
+
}
|
|
1706
|
+
return node.type ?? '';
|
|
1707
|
+
}
|
|
1708
|
+
async presentDialog(component, notifyShown, options) {
|
|
1752
1709
|
const dialogNode = this.build();
|
|
1753
|
-
// Import the dialog renderer component dynamically
|
|
1754
|
-
const { AXPDialogRendererComponent } = await Promise.resolve().then(function () { return dialogRenderer_component; });
|
|
1755
1710
|
this.dialogState.dialogOptions ??= {};
|
|
1756
1711
|
if (this.dialogState.dialogOptions.context == null || typeof this.dialogState.dialogOptions.context !== 'object') {
|
|
1757
1712
|
this.dialogState.dialogOptions.context = {};
|
|
@@ -1770,11 +1725,8 @@ class DialogContainerBuilder {
|
|
|
1770
1725
|
await hookService.runAsync(AXP_LAYOUT_BUILDER_DIALOG_BEFORE_OPEN_HOOK_KEY, beforePayload);
|
|
1771
1726
|
}
|
|
1772
1727
|
const confirmCloseWhenDirty = normalizeDialogCloseConfirmation(this.dialogState.dialogOptions?.confirmCloseWhenDirty);
|
|
1773
|
-
// Create dialog configuration
|
|
1774
1728
|
const dialogConfig = {
|
|
1775
1729
|
title: this.dialogState.dialogOptions?.title || '',
|
|
1776
|
-
//TODO: why we need message?
|
|
1777
|
-
//message: this.dialogState.dialogOptions?.message,
|
|
1778
1730
|
context: initialContext,
|
|
1779
1731
|
definition: dialogNode,
|
|
1780
1732
|
metadata: this.dialogState.dialogOptions.metadata,
|
|
@@ -1782,21 +1734,17 @@ class DialogContainerBuilder {
|
|
|
1782
1734
|
onAction: this.dialogState.dialogOptions?.onAction,
|
|
1783
1735
|
confirmCloseWhenDirty,
|
|
1784
1736
|
};
|
|
1785
|
-
//
|
|
1786
1737
|
if (hookService) {
|
|
1787
1738
|
await hookService.runAsync(AXP_LAYOUT_BUILDER_DIALOG_CONFIG_HOOK_KEY, dialogConfig);
|
|
1788
1739
|
}
|
|
1789
|
-
|
|
1790
|
-
return new Promise(async (resolve) => {
|
|
1740
|
+
return new Promise((resolve) => {
|
|
1791
1741
|
let flag = false;
|
|
1792
|
-
|
|
1742
|
+
const opened = this.popupService.open(component, {
|
|
1793
1743
|
title: dialogConfig.title,
|
|
1794
1744
|
size: this.dialogState.dialogOptions?.size || 'md',
|
|
1795
1745
|
closeButton: this.dialogState.dialogOptions?.closeButton || false,
|
|
1796
1746
|
closeOnBackdropClick: false,
|
|
1797
1747
|
draggable: this.dialogState.dialogOptions?.draggable ?? true,
|
|
1798
|
-
panelClass: [AXP_DIALOG_REVEAL_PANEL_CLASS],
|
|
1799
|
-
animation: AXP_DIALOG_REVEAL_POPUP_ANIMATION,
|
|
1800
1748
|
data: {
|
|
1801
1749
|
config: dialogConfig,
|
|
1802
1750
|
callBack: (result) => {
|
|
@@ -1805,9 +1753,17 @@ class DialogContainerBuilder {
|
|
|
1805
1753
|
},
|
|
1806
1754
|
},
|
|
1807
1755
|
});
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1756
|
+
notifyShown();
|
|
1757
|
+
options?.onShown?.();
|
|
1758
|
+
void Promise.resolve(opened).then(() => {
|
|
1759
|
+
if (!flag) {
|
|
1760
|
+
resolve(createDismissedDialogRef());
|
|
1761
|
+
}
|
|
1762
|
+
}, () => {
|
|
1763
|
+
if (!flag) {
|
|
1764
|
+
resolve(createDismissedDialogRef());
|
|
1765
|
+
}
|
|
1766
|
+
});
|
|
1811
1767
|
});
|
|
1812
1768
|
}
|
|
1813
1769
|
}
|
|
@@ -2669,121 +2625,8 @@ function shouldDeferDialogShortcutsToNestedOverlay(document, dialogHost, overlay
|
|
|
2669
2625
|
return shouldDeferEscapeToNestedOverlayFromDom(document, dialogHost);
|
|
2670
2626
|
}
|
|
2671
2627
|
|
|
2672
|
-
//#endregion
|
|
2673
|
-
//#region ----- Constants -----
|
|
2674
|
-
const SHELL_MS = 90;
|
|
2675
|
-
const MIN_MS = 80;
|
|
2676
|
-
const MAX_MS = 5000;
|
|
2677
|
-
const FIELD_MS = 7;
|
|
2678
|
-
const HEAVY_MS = 70;
|
|
2679
|
-
const CONTAINER_MS = 2;
|
|
2680
|
-
const TAB_MS = 3;
|
|
2681
|
-
const NETWORK_FIRST_MS = 200;
|
|
2682
|
-
const NETWORK_EACH_MS = 20;
|
|
2683
|
-
/** Widgets that fetch on init without a `dataSource` option. */
|
|
2684
|
-
const FETCH_WITHOUT_DATASOURCE = new Set([
|
|
2685
|
-
AXPWidgetsList.Editors.LookupBox,
|
|
2686
|
-
AXPWidgetsList.Editors.TagEditor,
|
|
2687
|
-
AXPWidgetsList.Editors.EntityCategory,
|
|
2688
|
-
AXPWidgetsList.Editors.MultiSourceSelector,
|
|
2689
|
-
AXPWidgetsList.Entity.EntityList,
|
|
2690
|
-
]);
|
|
2691
|
-
/** Widgets that are expensive to instantiate even with no network. */
|
|
2692
|
-
const HEAVY_RENDER = new Set([
|
|
2693
|
-
AXPWidgetsList.Editors.RichText,
|
|
2694
|
-
AXPWidgetsList.Advanced.CodeEditor,
|
|
2695
|
-
AXPWidgetsList.Editors.InlineDataTable,
|
|
2696
|
-
AXPWidgetsList.Editors.SignatureBox,
|
|
2697
|
-
AXPWidgetsList.Editors.MapBox,
|
|
2698
|
-
AXPWidgetsList.Editors.FileViewer,
|
|
2699
|
-
AXPWidgetsList.Editors.QueryBuilder,
|
|
2700
|
-
AXPWidgetsList.Editors.SkillMatrix,
|
|
2701
|
-
AXPWidgetsList.Editors.ResponsibilitiesMatrix,
|
|
2702
|
-
AXPWidgetsList.Editors.RiskMatrix,
|
|
2703
|
-
AXPWidgetsList.Editors.ControlHierarchyDiagram,
|
|
2704
|
-
]);
|
|
2705
|
-
//#endregion
|
|
2706
|
-
//#region ----- Estimate -----
|
|
2707
|
-
/**
|
|
2708
|
-
* Static walk of a dialog widget tree. Predicts how long the first paint will take
|
|
2709
|
-
* so the renderer can decide whether to show the loading dialog immediately.
|
|
2710
|
-
*/
|
|
2711
|
-
function estimateDialogRenderMs(node, kindOf) {
|
|
2712
|
-
const acc = { structural: 0, dataBound: 0 };
|
|
2713
|
-
walk(node, acc, kindOf);
|
|
2714
|
-
const network = acc.dataBound === 0 ? 0 : NETWORK_FIRST_MS + (acc.dataBound - 1) * NETWORK_EACH_MS;
|
|
2715
|
-
const raw = SHELL_MS + acc.structural + network;
|
|
2716
|
-
return Math.min(MAX_MS, Math.max(MIN_MS, raw));
|
|
2717
|
-
}
|
|
2718
|
-
//#endregion
|
|
2719
|
-
//#region ----- Walk -----
|
|
2720
|
-
function walk(node, acc, kindOf) {
|
|
2721
|
-
if (!node || node.visible === false) {
|
|
2722
|
-
return;
|
|
2723
|
-
}
|
|
2724
|
-
if (node.type === AXPWidgetsList.Layouts.TabsetLayout) {
|
|
2725
|
-
chargeNode(node, acc, kindOf);
|
|
2726
|
-
const children = node.children ?? [];
|
|
2727
|
-
if (children.length > 0) {
|
|
2728
|
-
walk(children[0], acc, kindOf);
|
|
2729
|
-
}
|
|
2730
|
-
for (let index = 1; index < children.length; index++) {
|
|
2731
|
-
acc.structural += TAB_MS;
|
|
2732
|
-
}
|
|
2733
|
-
return;
|
|
2734
|
-
}
|
|
2735
|
-
if (node.type === AXPWidgetsCatalog.stepWizard) {
|
|
2736
|
-
chargeNode(node, acc, kindOf);
|
|
2737
|
-
for (const content of wizardStepContents(node)) {
|
|
2738
|
-
walk(content, acc, kindOf);
|
|
2739
|
-
}
|
|
2740
|
-
for (const child of node.children ?? []) {
|
|
2741
|
-
walk(child, acc, kindOf);
|
|
2742
|
-
}
|
|
2743
|
-
return;
|
|
2744
|
-
}
|
|
2745
|
-
chargeNode(node, acc, kindOf);
|
|
2746
|
-
for (const child of node.children ?? []) {
|
|
2747
|
-
walk(child, acc, kindOf);
|
|
2748
|
-
}
|
|
2749
|
-
}
|
|
2750
|
-
function chargeNode(node, acc, kindOf) {
|
|
2751
|
-
if (HEAVY_RENDER.has(node.type)) {
|
|
2752
|
-
acc.structural += HEAVY_MS;
|
|
2753
|
-
}
|
|
2754
|
-
else if (kindOf(node.type) === 'container') {
|
|
2755
|
-
acc.structural += CONTAINER_MS;
|
|
2756
|
-
}
|
|
2757
|
-
else {
|
|
2758
|
-
acc.structural += FIELD_MS;
|
|
2759
|
-
}
|
|
2760
|
-
if (isNetworkBound(node)) {
|
|
2761
|
-
acc.dataBound += 1;
|
|
2762
|
-
}
|
|
2763
|
-
}
|
|
2764
|
-
function isNetworkBound(node) {
|
|
2765
|
-
if (FETCH_WITHOUT_DATASOURCE.has(node.type)) {
|
|
2766
|
-
return true;
|
|
2767
|
-
}
|
|
2768
|
-
const raw = node.options?.['dataSource'];
|
|
2769
|
-
return typeof raw === 'string' || (!!raw && typeof raw === 'object' && 'id' in raw);
|
|
2770
|
-
}
|
|
2771
|
-
function wizardStepContents(node) {
|
|
2772
|
-
const steps = node.options
|
|
2773
|
-
?.definition?.steps;
|
|
2774
|
-
if (!Array.isArray(steps)) {
|
|
2775
|
-
return [];
|
|
2776
|
-
}
|
|
2777
|
-
return steps.flatMap((step) => (step.content ? [step.content] : []));
|
|
2778
|
-
}
|
|
2779
|
-
//#endregion
|
|
2780
|
-
|
|
2781
2628
|
/** Debounce after widget count stabilizes before re-evaluating footer actions. */
|
|
2782
2629
|
const DIALOG_WIDGET_COUNT_STABLE_DELAY_MS = 350;
|
|
2783
|
-
/** Safety net so the dialog is never left hidden when the widget debounce does not fire. */
|
|
2784
|
-
const DIALOG_REVEAL_FALLBACK_MS = 3500;
|
|
2785
|
-
/** Fast dialogs skip the loading overlay unless they overrun this wait. */
|
|
2786
|
-
const LOADING_DIALOG_SHOW_THRESHOLD_MS = 400;
|
|
2787
2630
|
class AXPDialogRendererComponent extends AXBasePageComponent {
|
|
2788
2631
|
constructor() {
|
|
2789
2632
|
super(...arguments);
|
|
@@ -2798,21 +2641,12 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
|
|
|
2798
2641
|
this.document = inject(DOCUMENT);
|
|
2799
2642
|
this.host = inject((ElementRef));
|
|
2800
2643
|
this.shortcutRegistry = inject(AXPKeyboardShortcutRegistry);
|
|
2801
|
-
this.loadingDialog = inject(AXLoadingDialogService);
|
|
2802
|
-
this.widgetRegistry = inject(AXPWidgetRegistryService);
|
|
2803
|
-
this.debug = inject(AXPDebugService);
|
|
2804
2644
|
/** Ensures `show()` resolves once when the dialog closes (footer action or header close). */
|
|
2805
2645
|
this.callbackInvoked = false;
|
|
2806
2646
|
/** Skips dirty confirmation on the next {@link onClosing} (successful submit / programmatic close). */
|
|
2807
2647
|
this.skipNextOnClosingDirtyCheck = false;
|
|
2808
2648
|
/** Blocks re-entrant {@link onClosing} while the first close gate is still resolving. */
|
|
2809
2649
|
this.closeGateInProgress = false;
|
|
2810
|
-
/** Predicted first-paint cost from the widget tree, used for the loading decision and debug trace. */
|
|
2811
|
-
this.estimatedRenderMs = 0;
|
|
2812
|
-
/** `performance.now()` at `ngOnInit`, for estimated-vs-measured traces. */
|
|
2813
|
-
this.revealStartedAt = 0;
|
|
2814
|
-
/** Keeps the reveal pipeline to a single run (widget debounce and fallback timer both call it). */
|
|
2815
|
-
this.revealRequested = false;
|
|
2816
2650
|
this.destroyRef = inject(DestroyRef);
|
|
2817
2651
|
this.context = signal({}, /* @ts-ignore */
|
|
2818
2652
|
...(ngDevMode ? [{ debugName: "context" }] : /* istanbul ignore next */ []));
|
|
@@ -2860,9 +2694,8 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
|
|
|
2860
2694
|
if (this.debounceTimer) {
|
|
2861
2695
|
clearTimeout(this.debounceTimer);
|
|
2862
2696
|
}
|
|
2863
|
-
this.debounceTimer = setTimeout(
|
|
2864
|
-
|
|
2865
|
-
void this.revealWhenLayoutStable();
|
|
2697
|
+
this.debounceTimer = setTimeout(() => {
|
|
2698
|
+
void this.aggregateAndEvaluateActions();
|
|
2866
2699
|
}, DIALOG_WIDGET_COUNT_STABLE_DELAY_MS);
|
|
2867
2700
|
}, /* @ts-ignore */
|
|
2868
2701
|
...(ngDevMode ? [{ debugName: "#widgetActionsEffect" }] : /* istanbul ignore next */ []));
|
|
@@ -2877,24 +2710,9 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
|
|
|
2877
2710
|
//#endregion
|
|
2878
2711
|
//#region ---- Lifecycle ----
|
|
2879
2712
|
ngOnInit() {
|
|
2880
|
-
this.revealStartedAt = performance.now();
|
|
2881
|
-
this.estimatedRenderMs = estimateDialogRenderMs(this.config?.definition, (type) => this.widgetRegistry.getOptional(type)?.type);
|
|
2882
|
-
if (this.estimatedRenderMs >= LOADING_DIALOG_SHOW_THRESHOLD_MS) {
|
|
2883
|
-
this.revealLoading = this.openRevealLoading();
|
|
2884
|
-
}
|
|
2885
|
-
else {
|
|
2886
|
-
this.revealLoadingTimer = setTimeout(() => {
|
|
2887
|
-
this.revealLoading = this.openRevealLoading();
|
|
2888
|
-
}, LOADING_DIALOG_SHOW_THRESHOLD_MS);
|
|
2889
|
-
}
|
|
2890
2713
|
const initialContext = this.config?.context || {};
|
|
2891
2714
|
this.context.set(initialContext);
|
|
2892
2715
|
void this.invokeLayoutContextChangedHooks();
|
|
2893
|
-
const revealFallbackTimer = setTimeout(() => void this.revealWhenLayoutStable(), DIALOG_REVEAL_FALLBACK_MS);
|
|
2894
|
-
this.destroyRef.onDestroy(() => {
|
|
2895
|
-
clearTimeout(revealFallbackTimer);
|
|
2896
|
-
void this.closeRevealLoading();
|
|
2897
|
-
});
|
|
2898
2716
|
}
|
|
2899
2717
|
//#endregion
|
|
2900
2718
|
//#region ---- Keyboard Shortcuts ----
|
|
@@ -3394,40 +3212,6 @@ class AXPDialogRendererComponent extends AXBasePageComponent {
|
|
|
3394
3212
|
popFooter.append(footer);
|
|
3395
3213
|
});
|
|
3396
3214
|
}
|
|
3397
|
-
/**
|
|
3398
|
-
* The popup box is on screen before widgets and footer chrome are laid out, so it is held
|
|
3399
|
-
* hidden by the reveal panel class. Wait for the shell to stop resizing, then fade it in.
|
|
3400
|
-
*/
|
|
3401
|
-
async revealWhenLayoutStable() {
|
|
3402
|
-
if (this.revealRequested) {
|
|
3403
|
-
return;
|
|
3404
|
-
}
|
|
3405
|
-
this.revealRequested = true;
|
|
3406
|
-
await waitForDialogPopupLayoutStable(this.host.nativeElement);
|
|
3407
|
-
await this.closeRevealLoading();
|
|
3408
|
-
markDialogRevealReady(this.host.nativeElement);
|
|
3409
|
-
const elapsed = performance.now() - this.revealStartedAt;
|
|
3410
|
-
this.debug.trace('dialog-reveal', 'render-cost', 'estimated vs measured', () => ({
|
|
3411
|
-
estimatedMs: this.estimatedRenderMs,
|
|
3412
|
-
measuredMs: Math.round(elapsed - AXP_DIALOG_REVEAL_RESIZE_STABLE_MS),
|
|
3413
|
-
loadingShown: !!this.revealLoading,
|
|
3414
|
-
}));
|
|
3415
|
-
}
|
|
3416
|
-
async openRevealLoading() {
|
|
3417
|
-
const [title, text] = await Promise.all([
|
|
3418
|
-
this.translationService.translateAsync('@general:state-message.loading.title'),
|
|
3419
|
-
this.translationService.translateAsync('@general:state-message.loading.description'),
|
|
3420
|
-
]);
|
|
3421
|
-
return this.loadingDialog.show({ title, text, mode: 'indeterminate' });
|
|
3422
|
-
}
|
|
3423
|
-
async closeRevealLoading() {
|
|
3424
|
-
if (this.revealLoadingTimer) {
|
|
3425
|
-
clearTimeout(this.revealLoadingTimer);
|
|
3426
|
-
this.revealLoadingTimer = undefined;
|
|
3427
|
-
}
|
|
3428
|
-
const ref = await this.revealLoading?.catch(() => undefined);
|
|
3429
|
-
ref?.close();
|
|
3430
|
-
}
|
|
3431
3215
|
async collectWidgetActions() {
|
|
3432
3216
|
if (!this.widgetCoreService)
|
|
3433
3217
|
return [];
|