@acorex/platform 20.3.0-next.13 → 20.3.0-next.15
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 +19 -19
- package/fesm2022/acorex-platform-auth.mjs.map +1 -1
- package/fesm2022/acorex-platform-common.mjs +99 -99
- package/fesm2022/acorex-platform-common.mjs.map +1 -1
- package/fesm2022/acorex-platform-core.mjs +42 -42
- package/fesm2022/acorex-platform-core.mjs.map +1 -1
- package/fesm2022/acorex-platform-domain.mjs +16 -16
- package/fesm2022/acorex-platform-domain.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-builder.mjs +1535 -969
- package/fesm2022/acorex-platform-layout-builder.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-components.mjs +2189 -1567
- package/fesm2022/acorex-platform-layout-components.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-designer.mjs +73 -73
- package/fesm2022/acorex-platform-layout-designer.mjs.map +1 -1
- package/fesm2022/{acorex-platform-layout-entity-create-entity.command-CuueLekJ.mjs → acorex-platform-layout-entity-create-entity.command-CFBqiwfy.mjs} +4 -4
- package/fesm2022/{acorex-platform-layout-entity-create-entity.command-CuueLekJ.mjs.map → acorex-platform-layout-entity-create-entity.command-CFBqiwfy.mjs.map} +1 -1
- package/fesm2022/acorex-platform-layout-entity.mjs +178 -163
- package/fesm2022/acorex-platform-layout-entity.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-views.mjs +19 -17
- package/fesm2022/acorex-platform-layout-views.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-widget-core.mjs +163 -74
- package/fesm2022/acorex-platform-layout-widget-core.mjs.map +1 -1
- package/fesm2022/{acorex-platform-layout-widgets-button-widget-designer.component-C82aG5Rf.mjs → acorex-platform-layout-widgets-button-widget-designer.component-BzsfTNs2.mjs} +4 -4
- package/fesm2022/{acorex-platform-layout-widgets-button-widget-designer.component-C82aG5Rf.mjs.map → acorex-platform-layout-widgets-button-widget-designer.component-BzsfTNs2.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-layout-widgets-extra-properties-schema-widget-edit.component-CMmz70I8.mjs → acorex-platform-layout-widgets-extra-properties-schema-widget-edit.component-Dvk76-2W.mjs} +4 -4
- package/fesm2022/{acorex-platform-layout-widgets-extra-properties-schema-widget-edit.component-CMmz70I8.mjs.map → acorex-platform-layout-widgets-extra-properties-schema-widget-edit.component-Dvk76-2W.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-layout-widgets-extra-properties-schema-widget-view.component-DDOyf7NG.mjs → acorex-platform-layout-widgets-extra-properties-schema-widget-view.component-BYLaipWi.mjs} +4 -4
- package/fesm2022/{acorex-platform-layout-widgets-extra-properties-schema-widget-view.component-DDOyf7NG.mjs.map → acorex-platform-layout-widgets-extra-properties-schema-widget-view.component-BYLaipWi.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-layout-widgets-extra-properties-values-widget-edit.component-flsveRJc.mjs → acorex-platform-layout-widgets-extra-properties-values-widget-edit.component-DcSllNik.mjs} +4 -4
- package/fesm2022/{acorex-platform-layout-widgets-extra-properties-values-widget-edit.component-flsveRJc.mjs.map → acorex-platform-layout-widgets-extra-properties-values-widget-edit.component-DcSllNik.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-layout-widgets-extra-properties-values-widget-view.component-CFXLM9Ls.mjs → acorex-platform-layout-widgets-extra-properties-values-widget-view.component-BT-U4BiA.mjs} +4 -4
- package/fesm2022/{acorex-platform-layout-widgets-extra-properties-values-widget-view.component-CFXLM9Ls.mjs.map → acorex-platform-layout-widgets-extra-properties-values-widget-view.component-BT-U4BiA.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-layout-widgets-extra-properties-widget-edit.component-1Wd5IUpo.mjs → acorex-platform-layout-widgets-extra-properties-widget-edit.component-Il7jnRBg.mjs} +4 -4
- package/fesm2022/{acorex-platform-layout-widgets-extra-properties-widget-edit.component-1Wd5IUpo.mjs.map → acorex-platform-layout-widgets-extra-properties-widget-edit.component-Il7jnRBg.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-layout-widgets-extra-properties-widget-view.component-WLyXXg19.mjs → acorex-platform-layout-widgets-extra-properties-widget-view.component-CBEPu7Fl.mjs} +4 -4
- package/fesm2022/{acorex-platform-layout-widgets-extra-properties-widget-view.component-WLyXXg19.mjs.map → acorex-platform-layout-widgets-extra-properties-widget-view.component-CBEPu7Fl.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-layout-widgets-file-list-popup.component-DxTXIO_k.mjs → acorex-platform-layout-widgets-file-list-popup.component-BPzn8lr3.mjs} +5 -5
- package/fesm2022/acorex-platform-layout-widgets-file-list-popup.component-BPzn8lr3.mjs.map +1 -0
- package/fesm2022/{acorex-platform-layout-widgets-page-widget-designer.component-44YSAqDc.mjs → acorex-platform-layout-widgets-page-widget-designer.component-C_JrGoXy.mjs} +4 -4
- package/fesm2022/{acorex-platform-layout-widgets-page-widget-designer.component-44YSAqDc.mjs.map → acorex-platform-layout-widgets-page-widget-designer.component-C_JrGoXy.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-layout-widgets-tabular-data-edit-popup.component-Dat0cqWe.mjs → acorex-platform-layout-widgets-tabular-data-edit-popup.component-C6DaBt_N.mjs} +4 -4
- package/fesm2022/{acorex-platform-layout-widgets-tabular-data-edit-popup.component-Dat0cqWe.mjs.map → acorex-platform-layout-widgets-tabular-data-edit-popup.component-C6DaBt_N.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-layout-widgets-tabular-data-view-popup.component-CtJBzqq_.mjs → acorex-platform-layout-widgets-tabular-data-view-popup.component-Bth3jI9T.mjs} +4 -4
- package/fesm2022/{acorex-platform-layout-widgets-tabular-data-view-popup.component-CtJBzqq_.mjs.map → acorex-platform-layout-widgets-tabular-data-view-popup.component-Bth3jI9T.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-layout-widgets-text-block-widget-designer.component-Djpj1fNO.mjs → acorex-platform-layout-widgets-text-block-widget-designer.component-CUHptbP4.mjs} +4 -4
- package/fesm2022/{acorex-platform-layout-widgets-text-block-widget-designer.component-Djpj1fNO.mjs.map → acorex-platform-layout-widgets-text-block-widget-designer.component-CUHptbP4.mjs.map} +1 -1
- package/fesm2022/acorex-platform-layout-widgets.mjs +729 -676
- package/fesm2022/acorex-platform-layout-widgets.mjs.map +1 -1
- package/fesm2022/acorex-platform-native.mjs +7 -7
- package/fesm2022/acorex-platform-native.mjs.map +1 -1
- package/fesm2022/acorex-platform-runtime.mjs +40 -40
- package/fesm2022/acorex-platform-runtime.mjs.map +1 -1
- package/fesm2022/{acorex-platform-themes-default-entity-master-create-view.component-oOA674Jd.mjs → acorex-platform-themes-default-entity-master-create-view.component-eGzN6g2Y.mjs} +4 -4
- package/fesm2022/{acorex-platform-themes-default-entity-master-create-view.component-oOA674Jd.mjs.map → acorex-platform-themes-default-entity-master-create-view.component-eGzN6g2Y.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-themes-default-entity-master-list-view.component-Bk4p9oHD.mjs → acorex-platform-themes-default-entity-master-list-view.component-DjNvH3OA.mjs} +10 -10
- package/fesm2022/{acorex-platform-themes-default-entity-master-list-view.component-Bk4p9oHD.mjs.map → acorex-platform-themes-default-entity-master-list-view.component-DjNvH3OA.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-themes-default-entity-master-modify-view.component-CrDQOCpB.mjs → acorex-platform-themes-default-entity-master-modify-view.component-HJyalvcu.mjs} +4 -4
- package/fesm2022/{acorex-platform-themes-default-entity-master-modify-view.component-CrDQOCpB.mjs.map → acorex-platform-themes-default-entity-master-modify-view.component-HJyalvcu.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-themes-default-entity-master-single-view.component-CT-1gX4H.mjs → acorex-platform-themes-default-entity-master-single-view.component-e7m70Wls.mjs} +7 -7
- package/fesm2022/{acorex-platform-themes-default-entity-master-single-view.component-CT-1gX4H.mjs.map → acorex-platform-themes-default-entity-master-single-view.component-e7m70Wls.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-themes-default-error-401.component-CHJFmJ2W.mjs → acorex-platform-themes-default-error-401.component-CoBaQFTn.mjs} +4 -4
- package/fesm2022/{acorex-platform-themes-default-error-401.component-CHJFmJ2W.mjs.map → acorex-platform-themes-default-error-401.component-CoBaQFTn.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-themes-default-error-404.component-Db8KkVIF.mjs → acorex-platform-themes-default-error-404.component-BLlVOsS2.mjs} +4 -4
- package/fesm2022/{acorex-platform-themes-default-error-404.component-Db8KkVIF.mjs.map → acorex-platform-themes-default-error-404.component-BLlVOsS2.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-themes-default-error-offline.component-DH39Viy-.mjs → acorex-platform-themes-default-error-offline.component-CybYQI9F.mjs} +4 -4
- package/fesm2022/{acorex-platform-themes-default-error-offline.component-DH39Viy-.mjs.map → acorex-platform-themes-default-error-offline.component-CybYQI9F.mjs.map} +1 -1
- package/fesm2022/acorex-platform-themes-default.mjs +39 -39
- package/fesm2022/acorex-platform-themes-default.mjs.map +1 -1
- package/fesm2022/{acorex-platform-themes-shared-icon-chooser-view.component-mFBYGE0_.mjs → acorex-platform-themes-shared-icon-chooser-view.component-ReKSoVeN.mjs} +4 -4
- package/fesm2022/{acorex-platform-themes-shared-icon-chooser-view.component-mFBYGE0_.mjs.map → acorex-platform-themes-shared-icon-chooser-view.component-ReKSoVeN.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-themes-shared-theme-color-chooser-column.component-viyyova6.mjs → acorex-platform-themes-shared-theme-color-chooser-column.component-B2HDyY2z.mjs} +4 -4
- package/fesm2022/{acorex-platform-themes-shared-theme-color-chooser-column.component-viyyova6.mjs.map → acorex-platform-themes-shared-theme-color-chooser-column.component-B2HDyY2z.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-themes-shared-theme-color-chooser-view.component-BrUtKTXd.mjs → acorex-platform-themes-shared-theme-color-chooser-view.component-CeZxa49U.mjs} +4 -4
- package/fesm2022/{acorex-platform-themes-shared-theme-color-chooser-view.component-BrUtKTXd.mjs.map → acorex-platform-themes-shared-theme-color-chooser-view.component-CeZxa49U.mjs.map} +1 -1
- package/fesm2022/acorex-platform-themes-shared.mjs +42 -42
- package/fesm2022/acorex-platform-themes-shared.mjs.map +1 -1
- package/fesm2022/acorex-platform-workflow.mjs +25 -25
- package/fesm2022/acorex-platform-workflow.mjs.map +1 -1
- package/layout/builder/index.d.ts +389 -237
- package/layout/components/index.d.ts +130 -42
- package/layout/widget-core/index.d.ts +8 -1
- package/layout/widgets/index.d.ts +83 -2
- package/package.json +6 -6
- package/fesm2022/acorex-platform-layout-widgets-file-list-popup.component-DxTXIO_k.mjs.map +0 -1
|
@@ -1,660 +1,503 @@
|
|
|
1
|
+
import * as i1$1 from '@angular/common';
|
|
1
2
|
import { CommonModule } from '@angular/common';
|
|
2
3
|
import * as i0 from '@angular/core';
|
|
3
|
-
import { Injectable,
|
|
4
|
+
import { inject, Injectable, input, model, signal, effect, output, viewChild, ChangeDetectionStrategy, Component, NgModule, EventEmitter, Output, Input } from '@angular/core';
|
|
5
|
+
import { AXPopupService } from '@acorex/components/popup';
|
|
4
6
|
import * as i1 from '@acorex/platform/layout/widget-core';
|
|
5
|
-
import { AXPWidgetCoreModule } from '@acorex/platform/layout/widget-core';
|
|
7
|
+
import { AXPWidgetContainerComponent, AXPPageStatus, AXPWidgetCoreModule } from '@acorex/platform/layout/widget-core';
|
|
8
|
+
import * as i2 from '@acorex/components/form';
|
|
9
|
+
import { AXFormComponent, AXFormModule } from '@acorex/components/form';
|
|
10
|
+
import { isEqual, get } from 'lodash-es';
|
|
11
|
+
import { AXPExpressionEvaluatorService } from '@acorex/platform/core';
|
|
12
|
+
import { Subject, debounceTime, distinctUntilChanged, startWith } from 'rxjs';
|
|
13
|
+
import * as i2$1 from '@acorex/components/button';
|
|
14
|
+
import { AXButtonModule } from '@acorex/components/button';
|
|
15
|
+
import * as i3 from '@acorex/components/decorators';
|
|
16
|
+
import { AXDecoratorModule } from '@acorex/components/decorators';
|
|
17
|
+
import * as i4 from '@acorex/components/loading';
|
|
18
|
+
import { AXLoadingModule } from '@acorex/components/loading';
|
|
19
|
+
import { AXBasePageComponent } from '@acorex/components/page';
|
|
20
|
+
import * as i5 from '@acorex/core/translation';
|
|
21
|
+
import { AXTranslationModule } from '@acorex/core/translation';
|
|
6
22
|
|
|
23
|
+
//#region ---- Inheritance Utilities ----
|
|
24
|
+
/**
|
|
25
|
+
* Resolves inherited properties from context and local values
|
|
26
|
+
*/
|
|
27
|
+
function resolveInheritedProperties(context, localValues = {}) {
|
|
28
|
+
return {
|
|
29
|
+
mode: localValues.mode ?? context.mode ?? 'edit',
|
|
30
|
+
disabled: localValues.disabled ?? context.disabled,
|
|
31
|
+
readonly: localValues.readonly ?? context.readonly,
|
|
32
|
+
direction: localValues.direction ?? context.direction,
|
|
33
|
+
visible: localValues.visible ?? context.visible,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Merges inheritance context with local overrides
|
|
38
|
+
*/
|
|
39
|
+
function mergeInheritanceContext(parentContext, localOverrides = {}) {
|
|
40
|
+
return {
|
|
41
|
+
...parentContext,
|
|
42
|
+
...localOverrides,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Generates a random string for path/name generation
|
|
47
|
+
*/
|
|
48
|
+
function generateRandomId() {
|
|
49
|
+
return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Converts label to a valid path/name
|
|
53
|
+
*/
|
|
54
|
+
function labelToPath(label) {
|
|
55
|
+
return label
|
|
56
|
+
.toLowerCase()
|
|
57
|
+
.replace(/[^a-z0-9\s]/g, '') // Remove special characters
|
|
58
|
+
.replace(/\s+/g, '_') // Replace spaces with underscores
|
|
59
|
+
.substring(0, 50); // Limit length
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Generates path for value widgets based on hierarchy
|
|
63
|
+
*/
|
|
64
|
+
function generateValueWidgetPath(widgetName, formFieldName, formFieldLabel) {
|
|
65
|
+
// Priority: widget name -> form field name -> generated from label -> random
|
|
66
|
+
if (widgetName) {
|
|
67
|
+
return widgetName;
|
|
68
|
+
}
|
|
69
|
+
if (formFieldName) {
|
|
70
|
+
return formFieldName;
|
|
71
|
+
}
|
|
72
|
+
if (formFieldLabel) {
|
|
73
|
+
return labelToPath(formFieldLabel);
|
|
74
|
+
}
|
|
75
|
+
return generateRandomId();
|
|
76
|
+
}
|
|
77
|
+
//#endregion
|
|
78
|
+
//#region ---- Service Implementation ----
|
|
7
79
|
class AXPLayoutBuilderService {
|
|
80
|
+
constructor() {
|
|
81
|
+
this.popupService = inject(AXPopupService);
|
|
82
|
+
}
|
|
8
83
|
/**
|
|
9
84
|
* Create a new layout builder
|
|
10
85
|
*/
|
|
11
86
|
create() {
|
|
12
|
-
return new LayoutBuilder();
|
|
87
|
+
return new LayoutBuilder(this.popupService);
|
|
13
88
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (!node.widget)
|
|
37
|
-
return;
|
|
38
|
-
// Create layout node for widget
|
|
39
|
-
const nodeId = node.id || `widget-${nodeCounter++}`;
|
|
40
|
-
const layoutNode = {
|
|
41
|
-
id: nodeId,
|
|
42
|
-
type: node.widget.type,
|
|
43
|
-
zone: parentZone,
|
|
44
|
-
mode: node.mode || 'edit',
|
|
45
|
-
options: node.widget.options || {}
|
|
46
|
-
};
|
|
47
|
-
// Convert layout config to node layout
|
|
48
|
-
if (node.layout) {
|
|
49
|
-
layoutNode.layout = this.convertToNodeLayout(node.layout);
|
|
50
|
-
}
|
|
51
|
-
else if (hasMultipleNodes && definition.layoutConfig) {
|
|
52
|
-
// Apply default layout for multi-node structures when node has no explicit layout
|
|
53
|
-
layoutNode.layout = this.convertToNodeLayout(definition.layoutConfig);
|
|
54
|
-
}
|
|
55
|
-
// Add order if specified
|
|
56
|
-
if (node.order !== undefined) {
|
|
57
|
-
if (!layoutNode.layout)
|
|
58
|
-
layoutNode.layout = {};
|
|
59
|
-
layoutNode.layout.order = typeof node.order === 'number' ? node.order : parseInt(node.order.toString());
|
|
60
|
-
}
|
|
61
|
-
// Process widget options and content
|
|
62
|
-
this.processWidgetOptions(layoutNode, node);
|
|
63
|
-
layoutNodes.push(layoutNode);
|
|
64
|
-
// Recursively add children into the same zone (flat)
|
|
65
|
-
if (node.children && node.children.length > 0) {
|
|
66
|
-
processNodes(node.children, parentZone);
|
|
67
|
-
}
|
|
68
|
-
});
|
|
89
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXPLayoutBuilderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
90
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXPLayoutBuilderService, providedIn: 'root' }); }
|
|
91
|
+
}
|
|
92
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXPLayoutBuilderService, decorators: [{
|
|
93
|
+
type: Injectable,
|
|
94
|
+
args: [{
|
|
95
|
+
providedIn: 'root'
|
|
96
|
+
}]
|
|
97
|
+
}] });
|
|
98
|
+
//#endregion
|
|
99
|
+
//#region ---- Layout Builder Implementation ----
|
|
100
|
+
/**
|
|
101
|
+
* Main layout builder - Single Responsibility: Layout orchestration
|
|
102
|
+
* Open/Closed: Extensible through container delegates
|
|
103
|
+
*/
|
|
104
|
+
class LayoutBuilder {
|
|
105
|
+
constructor(popupService) {
|
|
106
|
+
this.popupService = popupService;
|
|
107
|
+
this.root = {
|
|
108
|
+
children: [],
|
|
109
|
+
mode: 'edit',
|
|
110
|
+
type: 'flex-layout',
|
|
69
111
|
};
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const assignOrder = (nodes) => {
|
|
73
|
-
if (!nodes)
|
|
74
|
-
return;
|
|
75
|
-
nodes.forEach(n => {
|
|
76
|
-
if (n.order === undefined || n.order === null) {
|
|
77
|
-
n.order = nodeOrder++;
|
|
78
|
-
}
|
|
79
|
-
if (n.children && n.children.length > 0)
|
|
80
|
-
assignOrder(n.children);
|
|
81
|
-
});
|
|
112
|
+
this.inheritanceContext = {
|
|
113
|
+
mode: 'edit',
|
|
82
114
|
};
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
gap: '16px'
|
|
91
|
-
},
|
|
92
|
-
order: 0 // Main zone gets order 0 (first)
|
|
93
|
-
};
|
|
115
|
+
}
|
|
116
|
+
// Predefined layout containers at root level - delegate pattern required
|
|
117
|
+
grid(delegate) {
|
|
118
|
+
const container = new GridContainerBuilder();
|
|
119
|
+
container.withInheritanceContext(this.inheritanceContext);
|
|
120
|
+
if (delegate) {
|
|
121
|
+
delegate(container);
|
|
94
122
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
layoutNodes
|
|
99
|
-
};
|
|
123
|
+
//this.state.children!.push(container.build());
|
|
124
|
+
this.root = container.build();
|
|
125
|
+
return this;
|
|
100
126
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
const styles = {};
|
|
107
|
-
// Handle responsive positions
|
|
108
|
-
if (layout.positions) {
|
|
109
|
-
// Use largest breakpoint for base styles
|
|
110
|
-
const position = layout.positions.xxl || layout.positions.xl || layout.positions.lg ||
|
|
111
|
-
layout.positions.md || layout.positions.sm;
|
|
112
|
-
if (position) {
|
|
113
|
-
if (position.colSpan && position.colSpan < 12) {
|
|
114
|
-
styles['width'] = `${(position.colSpan / 12) * 100}%`;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
127
|
+
flex(delegate) {
|
|
128
|
+
const container = new FlexContainerBuilder();
|
|
129
|
+
container.withInheritanceContext(this.inheritanceContext);
|
|
130
|
+
if (delegate) {
|
|
131
|
+
delegate(container);
|
|
117
132
|
}
|
|
118
|
-
|
|
133
|
+
this.root.children.push(container.build());
|
|
134
|
+
return this;
|
|
119
135
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
if (layout.positions) {
|
|
126
|
-
const position = layout.positions.xxl || layout.positions.xl || layout.positions.lg ||
|
|
127
|
-
layout.positions.md || layout.positions.sm;
|
|
128
|
-
if (position) {
|
|
129
|
-
if (position.colSpan && position.colSpan < 12) {
|
|
130
|
-
nodeLayout.flex = `0 0 ${(position.colSpan / 12) * 100}%`;
|
|
131
|
-
}
|
|
132
|
-
if (position.rowSpan && position.rowSpan > 1) {
|
|
133
|
-
nodeLayout.gridArea = `span ${position.rowSpan}`;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
+
panel(delegate) {
|
|
137
|
+
const container = new PanelContainerBuilder();
|
|
138
|
+
container.withInheritanceContext(this.inheritanceContext);
|
|
139
|
+
if (delegate) {
|
|
140
|
+
delegate(container);
|
|
136
141
|
}
|
|
137
|
-
|
|
142
|
+
this.root.children.push(container.build());
|
|
143
|
+
return this;
|
|
138
144
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
return;
|
|
145
|
-
const options = sourceNode.widget.options;
|
|
146
|
-
const processedOptions = { ...options };
|
|
147
|
-
// Process based on actual widget type
|
|
148
|
-
switch (sourceNode.widget.type) {
|
|
149
|
-
case 'text-block-layout':
|
|
150
|
-
// Text Block Widget - فقط content
|
|
151
|
-
processedOptions.content = this.processTextBlockContent(options);
|
|
152
|
-
break;
|
|
153
|
-
case 'grid-layout':
|
|
154
|
-
// Grid Layout Widget - complete grid system
|
|
155
|
-
processedOptions.grid = this.processGridOptions(options);
|
|
156
|
-
processedOptions.spacing = this.processSpacingOptions(options);
|
|
157
|
-
processedOptions.border = this.processBorderOptions(options);
|
|
158
|
-
if (options.backgroundColor)
|
|
159
|
-
processedOptions.backgroundColor = options.backgroundColor;
|
|
160
|
-
break;
|
|
161
|
-
case 'flex-layout':
|
|
162
|
-
// Flex Layout Widget
|
|
163
|
-
processedOptions.flex = this.processFlexOptions(options);
|
|
164
|
-
break;
|
|
165
|
-
case 'panel-layout':
|
|
166
|
-
// Panel Layout Widget
|
|
167
|
-
processedOptions.panel = this.processPanelOptions(options);
|
|
168
|
-
break;
|
|
169
|
-
case 'tag':
|
|
170
|
-
// Tag Widget (custom)
|
|
171
|
-
processedOptions.label = options.label || options.content || 'Tag';
|
|
172
|
-
processedOptions.tone = options.tone || 'primary';
|
|
173
|
-
break;
|
|
174
|
-
default:
|
|
175
|
-
// Generic processing برای custom widgets
|
|
176
|
-
if (options.content) {
|
|
177
|
-
processedOptions.content = this.processGenericContent(options);
|
|
178
|
-
}
|
|
179
|
-
break;
|
|
145
|
+
page(delegate) {
|
|
146
|
+
const container = new PageContainerBuilder();
|
|
147
|
+
container.withInheritanceContext(this.inheritanceContext);
|
|
148
|
+
if (delegate) {
|
|
149
|
+
delegate(container);
|
|
180
150
|
}
|
|
181
|
-
|
|
151
|
+
this.root.children.push(container.build());
|
|
152
|
+
return this;
|
|
182
153
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
return '';
|
|
189
|
-
let content = options.content;
|
|
190
|
-
// اگر قبلاً HTML tag داره، استفاده کن
|
|
191
|
-
if (content.includes('<')) {
|
|
192
|
-
return content;
|
|
154
|
+
tabset(delegate) {
|
|
155
|
+
const container = new TabsetContainerBuilder();
|
|
156
|
+
container.withInheritanceContext(this.inheritanceContext);
|
|
157
|
+
if (delegate) {
|
|
158
|
+
delegate(container);
|
|
193
159
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
160
|
+
this.root.children.push(container.build());
|
|
161
|
+
return this;
|
|
162
|
+
}
|
|
163
|
+
fieldset(delegate) {
|
|
164
|
+
const container = new FieldsetContainerBuilder();
|
|
165
|
+
container.withInheritanceContext(this.inheritanceContext);
|
|
166
|
+
if (delegate) {
|
|
167
|
+
delegate(container);
|
|
198
168
|
}
|
|
199
|
-
|
|
200
|
-
|
|
169
|
+
this.root.children.push(container.build());
|
|
170
|
+
return this;
|
|
171
|
+
}
|
|
172
|
+
dialog(delegate) {
|
|
173
|
+
const container = new DialogContainerBuilder(this.popupService);
|
|
174
|
+
if (delegate) {
|
|
175
|
+
delegate(container);
|
|
201
176
|
}
|
|
202
|
-
|
|
203
|
-
return `<span style="color: #333; line-height: 1.5;">${content}</span>`;
|
|
177
|
+
return container;
|
|
204
178
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
gap: options.gap || '16px',
|
|
214
|
-
justifyItems: options.justifyItems || 'stretch',
|
|
215
|
-
alignItems: options.alignItems || 'stretch',
|
|
216
|
-
autoFlow: options.autoFlow || 'row'
|
|
217
|
-
}
|
|
218
|
-
};
|
|
179
|
+
formField(label, delegate) {
|
|
180
|
+
const field = new FormFieldBuilder(label);
|
|
181
|
+
field.withInheritanceContext(this.inheritanceContext);
|
|
182
|
+
if (delegate) {
|
|
183
|
+
delegate(field);
|
|
184
|
+
}
|
|
185
|
+
this.root.children.push(field.build());
|
|
186
|
+
return this;
|
|
219
187
|
}
|
|
220
|
-
|
|
221
|
-
* Process spacing options
|
|
222
|
-
*/
|
|
223
|
-
processSpacingOptions(options) {
|
|
224
|
-
if (!options.spacing && !options.padding && !options.margin)
|
|
225
|
-
return undefined;
|
|
188
|
+
build() {
|
|
226
189
|
return {
|
|
227
|
-
|
|
228
|
-
|
|
190
|
+
type: this.root.type,
|
|
191
|
+
children: this.root.children,
|
|
192
|
+
mode: this.root.mode,
|
|
229
193
|
};
|
|
230
194
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
195
|
+
}
|
|
196
|
+
//#endregion
|
|
197
|
+
//#region ---- Container Builder Implementation ----
|
|
198
|
+
/**
|
|
199
|
+
* Abstract base container builder - Open/Closed Principle
|
|
200
|
+
* Provides common functionality for all container types
|
|
201
|
+
*/
|
|
202
|
+
class BaseContainerBuilder {
|
|
203
|
+
constructor(containerType) {
|
|
204
|
+
this.containerState = {
|
|
205
|
+
mode: 'edit',
|
|
206
|
+
type: 'flex-layout',
|
|
207
|
+
children: [],
|
|
242
208
|
};
|
|
209
|
+
this.inheritanceContext = {};
|
|
210
|
+
this.containerState.type = containerType;
|
|
243
211
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
processFlexOptions(options) {
|
|
248
|
-
return {
|
|
249
|
-
flexDirection: options.flexDirection || 'row',
|
|
250
|
-
justifyContent: options.justifyContent || 'flex-start',
|
|
251
|
-
alignItems: options.alignItems || 'stretch',
|
|
252
|
-
gap: options.gap || '16px',
|
|
253
|
-
flexWrap: options.flexWrap || 'nowrap'
|
|
254
|
-
};
|
|
212
|
+
// Base methods shared by all containers
|
|
213
|
+
ensureChildren() {
|
|
214
|
+
this.containerState.children = this.containerState.children || [];
|
|
255
215
|
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
}
|
|
216
|
+
addWidget(type, options) {
|
|
217
|
+
const child = new WidgetBuilder();
|
|
218
|
+
child.type(type);
|
|
219
|
+
// For value widgets, ensure path is provided
|
|
220
|
+
const widgetOptions = options ?? {};
|
|
221
|
+
const path = widgetOptions.path;
|
|
222
|
+
const name = widgetOptions.name;
|
|
223
|
+
if (this.isValueWidget(type) && !path) {
|
|
224
|
+
throw new Error(`Value widget '${type}' requires a 'path' property`);
|
|
225
|
+
}
|
|
226
|
+
// Set name and path in widget state, not options
|
|
227
|
+
if (name) {
|
|
228
|
+
child.name(name);
|
|
229
|
+
}
|
|
230
|
+
if (path) {
|
|
231
|
+
child.path(path);
|
|
232
|
+
}
|
|
233
|
+
// Remove name and path from options
|
|
234
|
+
const { name: _, path: __, ...cleanOptions } = widgetOptions;
|
|
235
|
+
child.options(cleanOptions);
|
|
236
|
+
child.withInheritanceContext(this.inheritanceContext);
|
|
237
|
+
this.ensureChildren();
|
|
238
|
+
this.containerState.children.push(child.build());
|
|
239
|
+
return this;
|
|
266
240
|
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
241
|
+
isValueWidget(type) {
|
|
242
|
+
const valueWidgetTypes = [
|
|
243
|
+
'text-editor', 'large-text-editor', 'rich-text-editor', 'password-editor',
|
|
244
|
+
'number-editor', 'select-editor', 'lookup-editor', 'selection-list-editor',
|
|
245
|
+
'date-time-editor', 'toggle-editor', 'color-editor'
|
|
246
|
+
];
|
|
247
|
+
return valueWidgetTypes.includes(type);
|
|
272
248
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
249
|
+
build() {
|
|
250
|
+
const result = {
|
|
251
|
+
type: this.containerState.type,
|
|
252
|
+
children: this.containerState.children,
|
|
253
|
+
options: this.containerState.options,
|
|
254
|
+
mode: this.containerState.mode,
|
|
255
|
+
visible: this.containerState.visible,
|
|
256
|
+
};
|
|
257
|
+
// Add name with _form_field suffix for form fields
|
|
258
|
+
if (this.containerState.type === 'form-field') {
|
|
259
|
+
if (this.containerState.name) {
|
|
260
|
+
result.name = this.containerState.name;
|
|
261
|
+
}
|
|
262
|
+
else if (this.containerState.options?.['label']) {
|
|
263
|
+
result.name = labelToPath(this.containerState.options['label']) + '_form_field';
|
|
264
|
+
}
|
|
265
|
+
// Form fields don't have path
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
// Other containers can have name and path
|
|
269
|
+
if (this.containerState.name) {
|
|
270
|
+
result.name = this.containerState.name;
|
|
271
|
+
}
|
|
272
|
+
if (this.containerState.path) {
|
|
273
|
+
result.path = this.containerState.path;
|
|
274
|
+
}
|
|
275
|
+
if (this.containerState.defaultValue !== undefined) {
|
|
276
|
+
result.defaultValue = this.containerState.defaultValue;
|
|
277
|
+
}
|
|
282
278
|
}
|
|
283
|
-
|
|
284
|
-
return `<span style="color: #333;">${options.content}</span>`;
|
|
279
|
+
return result;
|
|
285
280
|
}
|
|
286
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: AXPLayoutBuilderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
287
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: AXPLayoutBuilderService, providedIn: 'root' }); }
|
|
288
281
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
class LayoutBuilder {
|
|
297
|
-
constructor() {
|
|
298
|
-
this.state = {
|
|
299
|
-
nodes: [],
|
|
300
|
-
mode: 'edit',
|
|
301
|
-
direction: 'vertical',
|
|
302
|
-
look: 'normal',
|
|
303
|
-
layoutConfig: {
|
|
304
|
-
positions: {
|
|
305
|
-
sm: { colSpan: 12 },
|
|
306
|
-
md: { colSpan: 8 },
|
|
307
|
-
lg: { colSpan: 6 },
|
|
308
|
-
xl: { colSpan: 5 },
|
|
309
|
-
xxl: { colSpan: 4 },
|
|
310
|
-
},
|
|
311
|
-
},
|
|
312
|
-
};
|
|
313
|
-
}
|
|
314
|
-
widget(name, delegate) {
|
|
315
|
-
const widgetBuilder = new WidgetBuilder(name);
|
|
316
|
-
if (delegate) {
|
|
317
|
-
delegate(widgetBuilder);
|
|
318
|
-
}
|
|
319
|
-
this.state.nodes.push(widgetBuilder.build());
|
|
282
|
+
/**
|
|
283
|
+
* Base container builder mixin - Interface Segregation Principle
|
|
284
|
+
* Provides common container operations
|
|
285
|
+
*/
|
|
286
|
+
class BaseContainerMixin extends BaseContainerBuilder {
|
|
287
|
+
name(name) {
|
|
288
|
+
this.containerState.name = name;
|
|
320
289
|
return this;
|
|
321
290
|
}
|
|
322
|
-
|
|
323
|
-
this.
|
|
291
|
+
path(path) {
|
|
292
|
+
this.containerState.path = path;
|
|
324
293
|
return this;
|
|
325
294
|
}
|
|
326
295
|
mode(mode) {
|
|
327
|
-
this.
|
|
296
|
+
this.containerState.mode = mode;
|
|
297
|
+
this.inheritanceContext.mode = mode;
|
|
298
|
+
return this;
|
|
299
|
+
}
|
|
300
|
+
visible(condition) {
|
|
301
|
+
if (!this.containerState.options)
|
|
302
|
+
this.containerState.options = {};
|
|
303
|
+
this.containerState.options['visible'] = condition;
|
|
304
|
+
this.inheritanceContext.visible = condition;
|
|
305
|
+
return this;
|
|
306
|
+
}
|
|
307
|
+
disabled(condition) {
|
|
308
|
+
if (!this.containerState.options)
|
|
309
|
+
this.containerState.options = {};
|
|
310
|
+
this.containerState.options['disabled'] = condition;
|
|
311
|
+
this.inheritanceContext.disabled = condition;
|
|
312
|
+
return this;
|
|
313
|
+
}
|
|
314
|
+
readonly(condition) {
|
|
315
|
+
if (!this.containerState.options)
|
|
316
|
+
this.containerState.options = {};
|
|
317
|
+
this.containerState.options['readonly'] = condition;
|
|
318
|
+
this.inheritanceContext.readonly = condition;
|
|
328
319
|
return this;
|
|
329
320
|
}
|
|
330
321
|
direction(direction) {
|
|
331
|
-
this.
|
|
322
|
+
if (!this.containerState.options)
|
|
323
|
+
this.containerState.options = {};
|
|
324
|
+
this.containerState.options['direction'] = direction;
|
|
325
|
+
this.inheritanceContext.direction = direction;
|
|
332
326
|
return this;
|
|
333
327
|
}
|
|
334
|
-
|
|
335
|
-
|
|
328
|
+
// Inheritance context methods
|
|
329
|
+
withInheritanceContext(context) {
|
|
330
|
+
this.inheritanceContext = mergeInheritanceContext(context);
|
|
336
331
|
return this;
|
|
337
332
|
}
|
|
338
|
-
|
|
333
|
+
getInheritanceContext() {
|
|
334
|
+
return { ...this.inheritanceContext };
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Layout container mixin - Interface Segregation Principle
|
|
339
|
+
* Provides layout-specific operations
|
|
340
|
+
*/
|
|
341
|
+
class LayoutContainerMixin extends BaseContainerMixin {
|
|
342
|
+
layout(value) {
|
|
343
|
+
// Layout handling can be added here if needed
|
|
344
|
+
return this;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Child container mixin - Interface Segregation Principle
|
|
349
|
+
* Provides child container management
|
|
350
|
+
*/
|
|
351
|
+
class ChildContainerMixin extends LayoutContainerMixin {
|
|
339
352
|
grid(delegate) {
|
|
340
353
|
const container = new GridContainerBuilder();
|
|
354
|
+
container.withInheritanceContext(this.inheritanceContext);
|
|
341
355
|
if (delegate) {
|
|
342
356
|
delegate(container);
|
|
343
357
|
}
|
|
344
|
-
this.
|
|
358
|
+
this.ensureChildren();
|
|
359
|
+
this.containerState.children.push(container.build());
|
|
345
360
|
return this;
|
|
346
361
|
}
|
|
347
362
|
flex(delegate) {
|
|
348
363
|
const container = new FlexContainerBuilder();
|
|
364
|
+
container.withInheritanceContext(this.inheritanceContext);
|
|
349
365
|
if (delegate) {
|
|
350
366
|
delegate(container);
|
|
351
367
|
}
|
|
352
|
-
this.
|
|
368
|
+
this.ensureChildren();
|
|
369
|
+
this.containerState.children.push(container.build());
|
|
353
370
|
return this;
|
|
354
371
|
}
|
|
355
372
|
panel(delegate) {
|
|
356
373
|
const container = new PanelContainerBuilder();
|
|
374
|
+
container.withInheritanceContext(this.inheritanceContext);
|
|
357
375
|
if (delegate) {
|
|
358
376
|
delegate(container);
|
|
359
377
|
}
|
|
360
|
-
this.
|
|
378
|
+
this.ensureChildren();
|
|
379
|
+
this.containerState.children.push(container.build());
|
|
361
380
|
return this;
|
|
362
381
|
}
|
|
363
382
|
page(delegate) {
|
|
364
383
|
const container = new PageContainerBuilder();
|
|
384
|
+
container.withInheritanceContext(this.inheritanceContext);
|
|
365
385
|
if (delegate) {
|
|
366
386
|
delegate(container);
|
|
367
387
|
}
|
|
368
|
-
this.
|
|
388
|
+
this.ensureChildren();
|
|
389
|
+
this.containerState.children.push(container.build());
|
|
369
390
|
return this;
|
|
370
391
|
}
|
|
371
392
|
tabset(delegate) {
|
|
372
393
|
const container = new TabsetContainerBuilder();
|
|
394
|
+
container.withInheritanceContext(this.inheritanceContext);
|
|
395
|
+
if (delegate) {
|
|
396
|
+
delegate(container);
|
|
397
|
+
}
|
|
398
|
+
this.ensureChildren();
|
|
399
|
+
this.containerState.children.push(container.build());
|
|
400
|
+
return this;
|
|
401
|
+
}
|
|
402
|
+
fieldset(delegate) {
|
|
403
|
+
const container = new FieldsetContainerBuilder();
|
|
404
|
+
container.withInheritanceContext(this.inheritanceContext);
|
|
405
|
+
if (delegate) {
|
|
406
|
+
delegate(container);
|
|
407
|
+
}
|
|
408
|
+
this.ensureChildren();
|
|
409
|
+
this.containerState.children.push(container.build());
|
|
410
|
+
return this;
|
|
411
|
+
}
|
|
412
|
+
dialog(delegate) {
|
|
413
|
+
const container = new DialogContainerBuilder(); // Will use inject() fallback
|
|
373
414
|
if (delegate) {
|
|
374
415
|
delegate(container);
|
|
375
416
|
}
|
|
376
|
-
|
|
417
|
+
return container;
|
|
418
|
+
}
|
|
419
|
+
formField(label, delegate) {
|
|
420
|
+
const field = new FormFieldBuilder(label);
|
|
421
|
+
field.withInheritanceContext(this.inheritanceContext);
|
|
422
|
+
if (delegate) {
|
|
423
|
+
delegate(field);
|
|
424
|
+
}
|
|
425
|
+
this.ensureChildren();
|
|
426
|
+
this.containerState.children.push(field.build());
|
|
377
427
|
return this;
|
|
378
428
|
}
|
|
379
|
-
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Widget container mixin - Interface Segregation Principle
|
|
432
|
+
* Provides widget creation operations
|
|
433
|
+
*/
|
|
434
|
+
class WidgetContainerMixin extends ChildContainerMixin {
|
|
380
435
|
textBox(options) {
|
|
381
|
-
|
|
382
|
-
this.state.nodes.push(widgetBuilder.build());
|
|
436
|
+
this.addWidget('text-editor', options);
|
|
383
437
|
return this;
|
|
384
438
|
}
|
|
385
439
|
largeTextBox(options) {
|
|
386
|
-
|
|
387
|
-
this.state.nodes.push(widgetBuilder.build());
|
|
440
|
+
this.addWidget('large-text-editor', options);
|
|
388
441
|
return this;
|
|
389
442
|
}
|
|
390
443
|
richText(options) {
|
|
391
|
-
|
|
392
|
-
this.state.nodes.push(widgetBuilder.build());
|
|
444
|
+
this.addWidget('rich-text-editor', options);
|
|
393
445
|
return this;
|
|
394
446
|
}
|
|
395
447
|
passwordBox(options) {
|
|
396
|
-
|
|
397
|
-
this.state.nodes.push(widgetBuilder.build());
|
|
448
|
+
this.addWidget('password-editor', options);
|
|
398
449
|
return this;
|
|
399
450
|
}
|
|
400
451
|
numberBox(options) {
|
|
401
|
-
|
|
402
|
-
this.state.nodes.push(widgetBuilder.build());
|
|
452
|
+
this.addWidget('number-editor', options);
|
|
403
453
|
return this;
|
|
404
454
|
}
|
|
405
455
|
selectBox(options) {
|
|
406
|
-
|
|
407
|
-
this.state.nodes.push(widgetBuilder.build());
|
|
456
|
+
this.addWidget('select-editor', options);
|
|
408
457
|
return this;
|
|
409
458
|
}
|
|
410
459
|
lookupBox(options) {
|
|
411
|
-
|
|
412
|
-
this.state.nodes.push(widgetBuilder.build());
|
|
460
|
+
this.addWidget('lookup-editor', options);
|
|
413
461
|
return this;
|
|
414
462
|
}
|
|
415
463
|
selectionList(options) {
|
|
416
|
-
|
|
417
|
-
this.state.nodes.push(widgetBuilder.build());
|
|
464
|
+
this.addWidget('selection-list-editor', options);
|
|
418
465
|
return this;
|
|
419
466
|
}
|
|
420
467
|
dateTimeBox(options) {
|
|
421
|
-
|
|
422
|
-
this.state.nodes.push(widgetBuilder.build());
|
|
468
|
+
this.addWidget('date-time-editor', options);
|
|
423
469
|
return this;
|
|
424
470
|
}
|
|
425
471
|
toggleSwitch(options) {
|
|
426
|
-
|
|
427
|
-
this.state.nodes.push(widgetBuilder.build());
|
|
472
|
+
this.addWidget('toggle-editor', options);
|
|
428
473
|
return this;
|
|
429
474
|
}
|
|
430
475
|
colorBox(options) {
|
|
431
|
-
|
|
432
|
-
this.state.nodes.push(widgetBuilder.build());
|
|
476
|
+
this.addWidget('color-editor', options);
|
|
433
477
|
return this;
|
|
434
478
|
}
|
|
435
479
|
customWidget(type, options) {
|
|
436
|
-
|
|
437
|
-
this.state.nodes.push(widgetBuilder.build());
|
|
480
|
+
this.addWidget(type, options);
|
|
438
481
|
return this;
|
|
439
482
|
}
|
|
440
|
-
build() {
|
|
441
|
-
return {
|
|
442
|
-
nodes: this.state.nodes,
|
|
443
|
-
mode: this.state.mode,
|
|
444
|
-
direction: this.state.direction,
|
|
445
|
-
look: this.state.look,
|
|
446
|
-
layoutConfig: this.state.layoutConfig,
|
|
447
|
-
};
|
|
448
|
-
}
|
|
449
|
-
render() {
|
|
450
|
-
console.log('render() called, state:', JSON.parse(JSON.stringify(this.state)));
|
|
451
|
-
if (!this.state.nodes || !Array.isArray(this.state.nodes)) {
|
|
452
|
-
console.error('Invalid nodes in state:', this.state.nodes);
|
|
453
|
-
return {};
|
|
454
|
-
}
|
|
455
|
-
try {
|
|
456
|
-
const nodes = this.transformToWidgetNodes(this.state.nodes);
|
|
457
|
-
console.log('render() transformed nodes:', JSON.parse(JSON.stringify(nodes)));
|
|
458
|
-
// No nodes
|
|
459
|
-
if (!nodes || nodes.length === 0) {
|
|
460
|
-
return {};
|
|
461
|
-
}
|
|
462
|
-
// Single node → return directly
|
|
463
|
-
if (nodes.length === 1) {
|
|
464
|
-
return nodes[0];
|
|
465
|
-
}
|
|
466
|
-
// Multiple nodes → wrap in a container node with layout config
|
|
467
|
-
const flexDirection = this.state.direction === 'horizontal' ? 'row' : 'column';
|
|
468
|
-
const container = {
|
|
469
|
-
type: 'flex-layout',
|
|
470
|
-
name: 'root',
|
|
471
|
-
options: {
|
|
472
|
-
flexDirection,
|
|
473
|
-
gap: '16px',
|
|
474
|
-
},
|
|
475
|
-
children: nodes,
|
|
476
|
-
};
|
|
477
|
-
return container;
|
|
478
|
-
}
|
|
479
|
-
catch (error) {
|
|
480
|
-
console.error('Error in render():', error);
|
|
481
|
-
return {};
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
transformToWidgetNodes(nodes) {
|
|
485
|
-
console.log('transformToWidgetNodes called with nodes:', nodes);
|
|
486
|
-
const sorted = this.sortNodes(nodes);
|
|
487
|
-
// Helper to compute flex-item options from grid layout positions
|
|
488
|
-
const toFlexItemOptions = (layout, order) => {
|
|
489
|
-
let colSpan;
|
|
490
|
-
const positions = layout?.positions ?? this.state.layoutConfig?.positions;
|
|
491
|
-
if (positions) {
|
|
492
|
-
colSpan = positions.xxl?.colSpan || positions.xl?.colSpan || positions.lg?.colSpan || positions.md?.colSpan || positions.sm?.colSpan;
|
|
493
|
-
}
|
|
494
|
-
const basis = colSpan && colSpan > 0 && colSpan <= 12 ? `${(colSpan / 12) * 100}%` : undefined;
|
|
495
|
-
const opt = {};
|
|
496
|
-
if (basis)
|
|
497
|
-
opt.basis = basis;
|
|
498
|
-
if (order !== undefined)
|
|
499
|
-
opt.order = typeof order === 'number' ? order : parseInt(order.toString());
|
|
500
|
-
return opt;
|
|
501
|
-
};
|
|
502
|
-
const multipleSiblings = sorted.length > 1;
|
|
503
|
-
return sorted.map((node) => {
|
|
504
|
-
console.log('Processing node:', node);
|
|
505
|
-
// Build the actual widget node
|
|
506
|
-
const widget = {
|
|
507
|
-
type: node.widget?.type || 'unknown',
|
|
508
|
-
name: node.id,
|
|
509
|
-
options: {
|
|
510
|
-
...node.widget?.options,
|
|
511
|
-
mode: node.mode,
|
|
512
|
-
visible: node.visible,
|
|
513
|
-
disabled: node.disabled,
|
|
514
|
-
readonly: node.readonly,
|
|
515
|
-
},
|
|
516
|
-
};
|
|
517
|
-
if (node.children && node.children.length > 0) {
|
|
518
|
-
widget.children = this.transformToWidgetNodes(this.sortNodes(node.children));
|
|
519
|
-
}
|
|
520
|
-
// If we have multiple siblings at this level, wrap each item in a flex-item container
|
|
521
|
-
if (multipleSiblings) {
|
|
522
|
-
const flexItemOptions = toFlexItemOptions(node.layout, node.order);
|
|
523
|
-
const wrapped = {
|
|
524
|
-
type: 'flex-item-layout',
|
|
525
|
-
name: `${node.id || 'item'}-container`,
|
|
526
|
-
options: {
|
|
527
|
-
...flexItemOptions,
|
|
528
|
-
},
|
|
529
|
-
children: [widget],
|
|
530
|
-
};
|
|
531
|
-
return wrapped;
|
|
532
|
-
}
|
|
533
|
-
return widget;
|
|
534
|
-
});
|
|
535
|
-
}
|
|
536
|
-
sortNodes(nodes) {
|
|
537
|
-
if (!Array.isArray(nodes))
|
|
538
|
-
return [];
|
|
539
|
-
return nodes
|
|
540
|
-
.map((n, idx) => ({ n, idx }))
|
|
541
|
-
.sort((a, b) => {
|
|
542
|
-
const ao = a.n.order;
|
|
543
|
-
const bo = b.n.order;
|
|
544
|
-
const aHas = ao !== undefined && ao !== null;
|
|
545
|
-
const bHas = bo !== undefined && bo !== null;
|
|
546
|
-
if (aHas && bHas) {
|
|
547
|
-
if (typeof ao === 'number' && typeof bo === 'number')
|
|
548
|
-
return ao - bo;
|
|
549
|
-
return String(ao).localeCompare(String(bo));
|
|
550
|
-
}
|
|
551
|
-
if (aHas && !bHas)
|
|
552
|
-
return -1;
|
|
553
|
-
if (!aHas && bHas)
|
|
554
|
-
return 1;
|
|
555
|
-
return a.idx - b.idx;
|
|
556
|
-
})
|
|
557
|
-
.map(x => x.n);
|
|
558
|
-
}
|
|
559
483
|
}
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
children: [],
|
|
568
|
-
};
|
|
569
|
-
this.containerState.widget = { type: containerType, options: {} };
|
|
484
|
+
/**
|
|
485
|
+
* Flex Container Builder - Liskov Substitution Principle
|
|
486
|
+
* Extends WidgetContainerMixin to inherit all common functionality
|
|
487
|
+
*/
|
|
488
|
+
class FlexContainerBuilder extends WidgetContainerMixin {
|
|
489
|
+
constructor() {
|
|
490
|
+
super('flex-layout');
|
|
570
491
|
}
|
|
571
|
-
|
|
572
|
-
this.containerState.
|
|
573
|
-
return this;
|
|
574
|
-
}
|
|
575
|
-
baseLayout(value) {
|
|
576
|
-
if (typeof value === 'number') {
|
|
577
|
-
this.containerState.layout = {
|
|
578
|
-
positions: {
|
|
579
|
-
sm: { colSpan: 12 },
|
|
580
|
-
md: { colSpan: 12 },
|
|
581
|
-
lg: { colSpan: value },
|
|
582
|
-
xl: { colSpan: value },
|
|
583
|
-
xxl: { colSpan: value },
|
|
584
|
-
},
|
|
585
|
-
};
|
|
586
|
-
}
|
|
587
|
-
else {
|
|
588
|
-
this.containerState.layout = value;
|
|
589
|
-
}
|
|
590
|
-
return this;
|
|
591
|
-
}
|
|
592
|
-
// Base methods shared by all containers
|
|
593
|
-
ensureChildren() {
|
|
594
|
-
this.containerState.children = this.containerState.children || [];
|
|
595
|
-
}
|
|
596
|
-
addDirectWidget(type, options) {
|
|
597
|
-
const child = new WidgetBuilder();
|
|
598
|
-
child.customWidget(type, options);
|
|
599
|
-
this.ensureChildren();
|
|
600
|
-
this.containerState.children.push(child.build());
|
|
601
|
-
return this;
|
|
602
|
-
}
|
|
603
|
-
build() {
|
|
604
|
-
return {
|
|
605
|
-
id: this.containerState.id,
|
|
606
|
-
name: this.containerState.name,
|
|
607
|
-
widget: this.containerState.widget,
|
|
608
|
-
children: this.containerState.children,
|
|
609
|
-
layout: this.containerState.layout,
|
|
610
|
-
mode: this.containerState.mode,
|
|
611
|
-
order: this.containerState.order,
|
|
612
|
-
visible: this.containerState.visible,
|
|
613
|
-
disabled: this.containerState.disabled,
|
|
614
|
-
readonly: this.containerState.readonly,
|
|
615
|
-
};
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
// Flex Container Builder
|
|
619
|
-
class FlexContainerBuilder extends BaseContainerBuilder {
|
|
620
|
-
constructor() {
|
|
621
|
-
super('flex-layout');
|
|
622
|
-
}
|
|
623
|
-
id(id) {
|
|
624
|
-
return this.baseId(id);
|
|
625
|
-
}
|
|
626
|
-
layout(value) {
|
|
627
|
-
return this.baseLayout(value);
|
|
628
|
-
}
|
|
629
|
-
mode(mode) {
|
|
630
|
-
this.containerState.mode = mode;
|
|
631
|
-
return this;
|
|
632
|
-
}
|
|
633
|
-
order(value) {
|
|
634
|
-
this.containerState.order = value;
|
|
635
|
-
return this;
|
|
636
|
-
}
|
|
637
|
-
visible(condition) {
|
|
638
|
-
this.containerState.visible = condition;
|
|
639
|
-
return this;
|
|
640
|
-
}
|
|
641
|
-
disabled(condition) {
|
|
642
|
-
this.containerState.disabled = condition;
|
|
643
|
-
return this;
|
|
644
|
-
}
|
|
645
|
-
readonly(condition) {
|
|
646
|
-
this.containerState.readonly = condition;
|
|
647
|
-
return this;
|
|
648
|
-
}
|
|
649
|
-
setOptions(options) {
|
|
650
|
-
this.containerState.widget.options = { ...this.containerState.widget.options, ...options };
|
|
492
|
+
setOptions(options) {
|
|
493
|
+
this.containerState.options = { ...this.containerState.options, ...options };
|
|
651
494
|
return this;
|
|
652
495
|
}
|
|
653
496
|
// Individual fluent methods for Flex
|
|
654
|
-
|
|
497
|
+
setDirection(direction) {
|
|
655
498
|
return this.setOptions({ flexDirection: direction });
|
|
656
499
|
}
|
|
657
|
-
|
|
500
|
+
setWrap(wrap) {
|
|
658
501
|
return this.setOptions({ flexWrap: wrap });
|
|
659
502
|
}
|
|
660
503
|
setJustifyContent(justify) {
|
|
@@ -675,70 +518,17 @@ class FlexContainerBuilder extends BaseContainerBuilder {
|
|
|
675
518
|
setMargin(margin) {
|
|
676
519
|
return this.setOptions({ spacing: { margin } });
|
|
677
520
|
}
|
|
678
|
-
addContainer(name, delegate) {
|
|
679
|
-
// Implementation will be simplified for now
|
|
680
|
-
return this;
|
|
681
|
-
}
|
|
682
|
-
addWidget(name, delegate) {
|
|
683
|
-
const childWidget = new WidgetBuilder(name);
|
|
684
|
-
if (delegate) {
|
|
685
|
-
delegate(childWidget);
|
|
686
|
-
}
|
|
687
|
-
this.ensureChildren();
|
|
688
|
-
this.containerState.children.push(childWidget.build());
|
|
689
|
-
return this;
|
|
690
|
-
}
|
|
691
|
-
textBox(options) {
|
|
692
|
-
return this.addDirectWidget('text-editor', options);
|
|
693
|
-
}
|
|
694
|
-
largeTextBox(options) {
|
|
695
|
-
return this.addDirectWidget('large-text-editor', options);
|
|
696
|
-
}
|
|
697
|
-
richText(options) {
|
|
698
|
-
return this.addDirectWidget('rich-text-editor', options);
|
|
699
|
-
}
|
|
700
|
-
passwordBox(options) {
|
|
701
|
-
return this.addDirectWidget('password-editor', options);
|
|
702
|
-
}
|
|
703
|
-
numberBox(options) {
|
|
704
|
-
return this.addDirectWidget('number-editor', options);
|
|
705
|
-
}
|
|
706
|
-
selectBox(options) {
|
|
707
|
-
return this.addDirectWidget('select-editor', options);
|
|
708
|
-
}
|
|
709
|
-
lookupBox(options) {
|
|
710
|
-
return this.addDirectWidget('lookup-editor', options);
|
|
711
|
-
}
|
|
712
|
-
selectionList(options) {
|
|
713
|
-
return this.addDirectWidget('selection-list', options);
|
|
714
|
-
}
|
|
715
|
-
dateTimeBox(options) {
|
|
716
|
-
return this.addDirectWidget('date-time-editor', options);
|
|
717
|
-
}
|
|
718
|
-
toggleSwitch(options) {
|
|
719
|
-
return this.addDirectWidget('toggle-editor', options);
|
|
720
|
-
}
|
|
721
|
-
colorBox(options) {
|
|
722
|
-
return this.addDirectWidget('color-editor', options);
|
|
723
|
-
}
|
|
724
|
-
customWidget(type, options) {
|
|
725
|
-
return this.addDirectWidget(type, options);
|
|
726
|
-
}
|
|
727
521
|
}
|
|
728
|
-
|
|
729
|
-
|
|
522
|
+
/**
|
|
523
|
+
* Grid Container Builder - Liskov Substitution Principle
|
|
524
|
+
* Extends WidgetContainerMixin to inherit all common functionality
|
|
525
|
+
*/
|
|
526
|
+
class GridContainerBuilder extends WidgetContainerMixin {
|
|
730
527
|
constructor() {
|
|
731
528
|
super('grid-layout');
|
|
732
529
|
}
|
|
733
|
-
id(id) { return this.baseId(id); }
|
|
734
|
-
layout(value) { return this.baseLayout(value); }
|
|
735
|
-
mode(mode) { this.containerState.mode = mode; return this; }
|
|
736
|
-
order(value) { this.containerState.order = value; return this; }
|
|
737
|
-
visible(condition) { this.containerState.visible = condition; return this; }
|
|
738
|
-
disabled(condition) { this.containerState.disabled = condition; return this; }
|
|
739
|
-
readonly(condition) { this.containerState.readonly = condition; return this; }
|
|
740
530
|
setOptions(options) {
|
|
741
|
-
this.containerState.
|
|
531
|
+
this.containerState.options = { ...this.containerState.options, ...options };
|
|
742
532
|
return this;
|
|
743
533
|
}
|
|
744
534
|
// Individual fluent methods for Grid
|
|
@@ -769,42 +559,17 @@ class GridContainerBuilder extends BaseContainerBuilder {
|
|
|
769
559
|
setMargin(margin) {
|
|
770
560
|
return this.setOptions({ spacing: { margin } });
|
|
771
561
|
}
|
|
772
|
-
addContainer(name, delegate) { return this; }
|
|
773
|
-
addWidget(name, delegate) {
|
|
774
|
-
const childWidget = new WidgetBuilder(name);
|
|
775
|
-
if (delegate)
|
|
776
|
-
delegate(childWidget);
|
|
777
|
-
this.ensureChildren();
|
|
778
|
-
this.containerState.children.push(childWidget.build());
|
|
779
|
-
return this;
|
|
780
|
-
}
|
|
781
|
-
textBox(options) { return this.addDirectWidget('text-editor', options); }
|
|
782
|
-
largeTextBox(options) { return this.addDirectWidget('large-text-editor', options); }
|
|
783
|
-
richText(options) { return this.addDirectWidget('rich-text-editor', options); }
|
|
784
|
-
passwordBox(options) { return this.addDirectWidget('password-editor', options); }
|
|
785
|
-
numberBox(options) { return this.addDirectWidget('number-editor', options); }
|
|
786
|
-
selectBox(options) { return this.addDirectWidget('select-editor', options); }
|
|
787
|
-
lookupBox(options) { return this.addDirectWidget('lookup-editor', options); }
|
|
788
|
-
selectionList(options) { return this.addDirectWidget('selection-list', options); }
|
|
789
|
-
dateTimeBox(options) { return this.addDirectWidget('date-time-editor', options); }
|
|
790
|
-
toggleSwitch(options) { return this.addDirectWidget('toggle-editor', options); }
|
|
791
|
-
colorBox(options) { return this.addDirectWidget('color-editor', options); }
|
|
792
|
-
customWidget(type, options) { return this.addDirectWidget(type, options); }
|
|
793
562
|
}
|
|
794
|
-
|
|
795
|
-
|
|
563
|
+
/**
|
|
564
|
+
* Panel Container Builder - Liskov Substitution Principle
|
|
565
|
+
* Extends WidgetContainerMixin to inherit all common functionality
|
|
566
|
+
*/
|
|
567
|
+
class PanelContainerBuilder extends WidgetContainerMixin {
|
|
796
568
|
constructor() {
|
|
797
569
|
super('panel-layout');
|
|
798
570
|
}
|
|
799
|
-
id(id) { return this.baseId(id); }
|
|
800
|
-
layout(value) { return this.baseLayout(value); }
|
|
801
|
-
mode(mode) { this.containerState.mode = mode; return this; }
|
|
802
|
-
order(value) { this.containerState.order = value; return this; }
|
|
803
|
-
visible(condition) { this.containerState.visible = condition; return this; }
|
|
804
|
-
disabled(condition) { this.containerState.disabled = condition; return this; }
|
|
805
|
-
readonly(condition) { this.containerState.readonly = condition; return this; }
|
|
806
571
|
setOptions(options) {
|
|
807
|
-
this.containerState.
|
|
572
|
+
this.containerState.options = { ...this.containerState.options, ...options };
|
|
808
573
|
return this;
|
|
809
574
|
}
|
|
810
575
|
// Individual fluent methods for Panel
|
|
@@ -823,42 +588,17 @@ class PanelContainerBuilder extends BaseContainerBuilder {
|
|
|
823
588
|
setCollapsed(collapsed) {
|
|
824
589
|
return this.setOptions({ collapsed });
|
|
825
590
|
}
|
|
826
|
-
addContainer(name, delegate) { return this; }
|
|
827
|
-
addWidget(name, delegate) {
|
|
828
|
-
const childWidget = new WidgetBuilder(name);
|
|
829
|
-
if (delegate)
|
|
830
|
-
delegate(childWidget);
|
|
831
|
-
this.ensureChildren();
|
|
832
|
-
this.containerState.children.push(childWidget.build());
|
|
833
|
-
return this;
|
|
834
|
-
}
|
|
835
|
-
textBox(options) { return this.addDirectWidget('text-editor', options); }
|
|
836
|
-
largeTextBox(options) { return this.addDirectWidget('large-text-editor', options); }
|
|
837
|
-
richText(options) { return this.addDirectWidget('rich-text-editor', options); }
|
|
838
|
-
passwordBox(options) { return this.addDirectWidget('password-editor', options); }
|
|
839
|
-
numberBox(options) { return this.addDirectWidget('number-editor', options); }
|
|
840
|
-
selectBox(options) { return this.addDirectWidget('select-editor', options); }
|
|
841
|
-
lookupBox(options) { return this.addDirectWidget('lookup-editor', options); }
|
|
842
|
-
selectionList(options) { return this.addDirectWidget('selection-list', options); }
|
|
843
|
-
dateTimeBox(options) { return this.addDirectWidget('date-time-editor', options); }
|
|
844
|
-
toggleSwitch(options) { return this.addDirectWidget('toggle-editor', options); }
|
|
845
|
-
colorBox(options) { return this.addDirectWidget('color-editor', options); }
|
|
846
|
-
customWidget(type, options) { return this.addDirectWidget(type, options); }
|
|
847
591
|
}
|
|
848
|
-
|
|
849
|
-
|
|
592
|
+
/**
|
|
593
|
+
* Page Container Builder - Liskov Substitution Principle
|
|
594
|
+
* Extends WidgetContainerMixin to inherit all common functionality
|
|
595
|
+
*/
|
|
596
|
+
class PageContainerBuilder extends WidgetContainerMixin {
|
|
850
597
|
constructor() {
|
|
851
598
|
super('page-layout');
|
|
852
599
|
}
|
|
853
|
-
id(id) { return this.baseId(id); }
|
|
854
|
-
layout(value) { return this.baseLayout(value); }
|
|
855
|
-
mode(mode) { this.containerState.mode = mode; return this; }
|
|
856
|
-
order(value) { this.containerState.order = value; return this; }
|
|
857
|
-
visible(condition) { this.containerState.visible = condition; return this; }
|
|
858
|
-
disabled(condition) { this.containerState.disabled = condition; return this; }
|
|
859
|
-
readonly(condition) { this.containerState.readonly = condition; return this; }
|
|
860
600
|
setOptions(options) {
|
|
861
|
-
this.containerState.
|
|
601
|
+
this.containerState.options = { ...this.containerState.options, ...options };
|
|
862
602
|
return this;
|
|
863
603
|
}
|
|
864
604
|
// Individual fluent methods for Page
|
|
@@ -877,42 +617,17 @@ class PageContainerBuilder extends BaseContainerBuilder {
|
|
|
877
617
|
setDirection(direction) {
|
|
878
618
|
return this.setOptions({ direction });
|
|
879
619
|
}
|
|
880
|
-
addContainer(name, delegate) { return this; }
|
|
881
|
-
addWidget(name, delegate) {
|
|
882
|
-
const childWidget = new WidgetBuilder(name);
|
|
883
|
-
if (delegate)
|
|
884
|
-
delegate(childWidget);
|
|
885
|
-
this.ensureChildren();
|
|
886
|
-
this.containerState.children.push(childWidget.build());
|
|
887
|
-
return this;
|
|
888
|
-
}
|
|
889
|
-
textBox(options) { return this.addDirectWidget('text-editor', options); }
|
|
890
|
-
largeTextBox(options) { return this.addDirectWidget('large-text-editor', options); }
|
|
891
|
-
richText(options) { return this.addDirectWidget('rich-text-editor', options); }
|
|
892
|
-
passwordBox(options) { return this.addDirectWidget('password-editor', options); }
|
|
893
|
-
numberBox(options) { return this.addDirectWidget('number-editor', options); }
|
|
894
|
-
selectBox(options) { return this.addDirectWidget('select-editor', options); }
|
|
895
|
-
lookupBox(options) { return this.addDirectWidget('lookup-editor', options); }
|
|
896
|
-
selectionList(options) { return this.addDirectWidget('selection-list', options); }
|
|
897
|
-
dateTimeBox(options) { return this.addDirectWidget('date-time-editor', options); }
|
|
898
|
-
toggleSwitch(options) { return this.addDirectWidget('toggle-editor', options); }
|
|
899
|
-
colorBox(options) { return this.addDirectWidget('color-editor', options); }
|
|
900
|
-
customWidget(type, options) { return this.addDirectWidget(type, options); }
|
|
901
620
|
}
|
|
902
|
-
|
|
903
|
-
|
|
621
|
+
/**
|
|
622
|
+
* Tabset Container Builder - Liskov Substitution Principle
|
|
623
|
+
* Extends WidgetContainerMixin to inherit all common functionality
|
|
624
|
+
*/
|
|
625
|
+
class TabsetContainerBuilder extends WidgetContainerMixin {
|
|
904
626
|
constructor() {
|
|
905
627
|
super('tabset-layout');
|
|
906
628
|
}
|
|
907
|
-
id(id) { return this.baseId(id); }
|
|
908
|
-
layout(value) { return this.baseLayout(value); }
|
|
909
|
-
mode(mode) { this.containerState.mode = mode; return this; }
|
|
910
|
-
order(value) { this.containerState.order = value; return this; }
|
|
911
|
-
visible(condition) { this.containerState.visible = condition; return this; }
|
|
912
|
-
disabled(condition) { this.containerState.disabled = condition; return this; }
|
|
913
|
-
readonly(condition) { this.containerState.readonly = condition; return this; }
|
|
914
629
|
setOptions(options) {
|
|
915
|
-
this.containerState.
|
|
630
|
+
this.containerState.options = { ...this.containerState.options, ...options };
|
|
916
631
|
return this;
|
|
917
632
|
}
|
|
918
633
|
// Individual fluent methods for Tabset
|
|
@@ -925,48 +640,312 @@ class TabsetContainerBuilder extends BaseContainerBuilder {
|
|
|
925
640
|
setActiveIndex(index) {
|
|
926
641
|
return this.setOptions({ activeIndex: index });
|
|
927
642
|
}
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* Form Field Builder - Liskov Substitution Principle
|
|
646
|
+
* Can only contain ONE widget with automatic path generation
|
|
647
|
+
*/
|
|
648
|
+
class FormFieldBuilder extends LayoutContainerMixin {
|
|
649
|
+
constructor(label) {
|
|
650
|
+
super('form-field');
|
|
651
|
+
this.hasWidget = false;
|
|
652
|
+
this.containerState.options = { label, showLabel: true };
|
|
653
|
+
}
|
|
654
|
+
setOptions(options) {
|
|
655
|
+
this.containerState.options = { ...this.containerState.options, ...options };
|
|
656
|
+
return this;
|
|
657
|
+
}
|
|
658
|
+
setLabel(label) {
|
|
659
|
+
return this.setOptions({ label });
|
|
660
|
+
}
|
|
661
|
+
setShowLabel(showLabel) {
|
|
662
|
+
return this.setOptions({ showLabel });
|
|
663
|
+
}
|
|
664
|
+
// Single widget methods with automatic path generation
|
|
665
|
+
addSingleWidget(type, options) {
|
|
666
|
+
if (this.hasWidget) {
|
|
667
|
+
throw new Error('Form field can only contain one widget');
|
|
668
|
+
}
|
|
669
|
+
const formFieldName = this.containerState.name;
|
|
670
|
+
const formFieldPath = this.containerState.path; // Get explicit path from form field
|
|
671
|
+
const formFieldLabel = this.containerState.options?.['label'];
|
|
672
|
+
const widgetName = options?.name;
|
|
673
|
+
// Generate widget path: explicit path -> widget name -> form field name -> label -> random
|
|
674
|
+
let widgetPath;
|
|
675
|
+
if (formFieldPath) {
|
|
676
|
+
widgetPath = formFieldPath; // Use explicit form field path first
|
|
677
|
+
}
|
|
678
|
+
else if (widgetName) {
|
|
679
|
+
widgetPath = widgetName;
|
|
680
|
+
}
|
|
681
|
+
else if (formFieldName) {
|
|
682
|
+
widgetPath = formFieldName; // Use form field name as default path
|
|
683
|
+
}
|
|
684
|
+
else if (formFieldLabel) {
|
|
685
|
+
widgetPath = labelToPath(formFieldLabel);
|
|
686
|
+
}
|
|
687
|
+
else {
|
|
688
|
+
widgetPath = generateRandomId();
|
|
689
|
+
}
|
|
690
|
+
const finalName = widgetName || formFieldName || widgetPath;
|
|
691
|
+
const child = new WidgetBuilder();
|
|
692
|
+
child.type(type);
|
|
693
|
+
child.name(finalName);
|
|
694
|
+
child.path(widgetPath);
|
|
695
|
+
// Remove name from options since it's now in state
|
|
696
|
+
const { name: _, ...cleanOptions } = (options || {});
|
|
697
|
+
child.options(cleanOptions);
|
|
698
|
+
child.withInheritanceContext(this.inheritanceContext);
|
|
933
699
|
this.ensureChildren();
|
|
934
|
-
this.containerState.children.push(
|
|
700
|
+
this.containerState.children.push(child.build());
|
|
701
|
+
this.hasWidget = true;
|
|
935
702
|
return this;
|
|
936
703
|
}
|
|
937
|
-
textBox(options) {
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
704
|
+
textBox(options) {
|
|
705
|
+
return this.addSingleWidget('text-editor', options);
|
|
706
|
+
}
|
|
707
|
+
largeTextBox(options) {
|
|
708
|
+
return this.addSingleWidget('large-text-editor', options);
|
|
709
|
+
}
|
|
710
|
+
richText(options) {
|
|
711
|
+
return this.addSingleWidget('rich-text-editor', options);
|
|
712
|
+
}
|
|
713
|
+
passwordBox(options) {
|
|
714
|
+
return this.addSingleWidget('password-editor', options);
|
|
715
|
+
}
|
|
716
|
+
numberBox(options) {
|
|
717
|
+
return this.addSingleWidget('number-editor', options);
|
|
718
|
+
}
|
|
719
|
+
selectBox(options) {
|
|
720
|
+
return this.addSingleWidget('select-editor', options);
|
|
721
|
+
}
|
|
722
|
+
lookupBox(options) {
|
|
723
|
+
return this.addSingleWidget('lookup-editor', options);
|
|
724
|
+
}
|
|
725
|
+
selectionList(options) {
|
|
726
|
+
return this.addSingleWidget('selection-list-editor', options);
|
|
727
|
+
}
|
|
728
|
+
dateTimeBox(options) {
|
|
729
|
+
return this.addSingleWidget('date-time-editor', options);
|
|
730
|
+
}
|
|
731
|
+
toggleSwitch(options) {
|
|
732
|
+
return this.addSingleWidget('toggle-editor', options);
|
|
733
|
+
}
|
|
734
|
+
colorBox(options) {
|
|
735
|
+
return this.addSingleWidget('color-editor', options);
|
|
736
|
+
}
|
|
737
|
+
customWidget(type, options) {
|
|
738
|
+
return this.addSingleWidget(type, options);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
/**
|
|
742
|
+
* Fieldset Container Builder - Liskov Substitution Principle
|
|
743
|
+
* Extends LayoutContainerMixin to inherit layout functionality
|
|
744
|
+
* Specialized for form fields only
|
|
745
|
+
*/
|
|
746
|
+
class FieldsetContainerBuilder extends LayoutContainerMixin {
|
|
747
|
+
constructor() {
|
|
748
|
+
super('fieldset-layout');
|
|
749
|
+
this.containerState.options = {};
|
|
750
|
+
}
|
|
751
|
+
setOptions(options) {
|
|
752
|
+
this.containerState.options = { ...this.containerState.options, ...options };
|
|
753
|
+
return this;
|
|
754
|
+
}
|
|
755
|
+
// Individual fluent methods for Fieldset
|
|
756
|
+
setTitle(title) {
|
|
757
|
+
return this.setOptions({ title });
|
|
758
|
+
}
|
|
759
|
+
setDescription(description) {
|
|
760
|
+
return this.setOptions({ description });
|
|
761
|
+
}
|
|
762
|
+
setIcon(icon) {
|
|
763
|
+
return this.setOptions({ icon });
|
|
764
|
+
}
|
|
765
|
+
setCollapsible(collapsible) {
|
|
766
|
+
return this.setOptions({ collapsible });
|
|
767
|
+
}
|
|
768
|
+
setIsOpen(isOpen) {
|
|
769
|
+
return this.setOptions({ isOpen });
|
|
770
|
+
}
|
|
771
|
+
setLook(look) {
|
|
772
|
+
return this.setOptions({ look });
|
|
773
|
+
}
|
|
774
|
+
setShowHeader(showHeader) {
|
|
775
|
+
return this.setOptions({ showHeader });
|
|
776
|
+
}
|
|
777
|
+
setCols(cols) {
|
|
778
|
+
return this.setOptions({ cols });
|
|
779
|
+
}
|
|
780
|
+
// Only form fields are allowed in fieldset
|
|
781
|
+
formField(label, delegate) {
|
|
782
|
+
const field = new FormFieldBuilder(label);
|
|
783
|
+
field.withInheritanceContext(this.inheritanceContext);
|
|
784
|
+
if (delegate) {
|
|
785
|
+
delegate(field);
|
|
786
|
+
}
|
|
787
|
+
this.ensureChildren();
|
|
788
|
+
this.containerState.children.push(field.build());
|
|
789
|
+
return this;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
/**
|
|
793
|
+
* Dialog Container Builder - Specialized for dialog functionality
|
|
794
|
+
* Uses composition instead of inheritance for cleaner separation
|
|
795
|
+
*/
|
|
796
|
+
class DialogContainerBuilder {
|
|
797
|
+
constructor(popupService) {
|
|
798
|
+
this.dialogState = {
|
|
799
|
+
type: 'flex-layout', // This will be overridden when content layout exists
|
|
800
|
+
children: [],
|
|
801
|
+
mode: 'edit',
|
|
802
|
+
dialogOptions: {
|
|
803
|
+
title: '',
|
|
804
|
+
size: 'md',
|
|
805
|
+
closeButton: false,
|
|
806
|
+
},
|
|
807
|
+
actions: {
|
|
808
|
+
footer: {
|
|
809
|
+
prefix: [],
|
|
810
|
+
suffix: []
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
};
|
|
814
|
+
if (popupService) {
|
|
815
|
+
this.popupService = popupService;
|
|
816
|
+
}
|
|
817
|
+
else {
|
|
818
|
+
this.popupService = inject(AXPopupService);
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
setOptions(options) {
|
|
822
|
+
this.dialogState.dialogOptions = { ...this.dialogState.dialogOptions, ...options };
|
|
823
|
+
return this;
|
|
824
|
+
}
|
|
825
|
+
// Individual fluent methods for Dialog
|
|
826
|
+
setTitle(title) {
|
|
827
|
+
return this.setOptions({ title });
|
|
828
|
+
}
|
|
829
|
+
setMessage(message) {
|
|
830
|
+
return this.setOptions({ message });
|
|
831
|
+
}
|
|
832
|
+
setSize(size) {
|
|
833
|
+
return this.setOptions({ size });
|
|
834
|
+
}
|
|
835
|
+
setCloseButton(closeButton) {
|
|
836
|
+
return this.setOptions({ closeButton });
|
|
837
|
+
}
|
|
838
|
+
setContext(context) {
|
|
839
|
+
return this.setOptions({ context });
|
|
840
|
+
}
|
|
841
|
+
content(delegate) {
|
|
842
|
+
if (delegate) {
|
|
843
|
+
// Create a flex container directly instead of through LayoutBuilder
|
|
844
|
+
const flexContainer = new FlexContainerBuilder();
|
|
845
|
+
flexContainer.setDirection('column');
|
|
846
|
+
flexContainer.setGap('10px');
|
|
847
|
+
delegate(flexContainer);
|
|
848
|
+
this.contentLayout = flexContainer.build();
|
|
849
|
+
}
|
|
850
|
+
return this;
|
|
851
|
+
}
|
|
852
|
+
setActions(delegate) {
|
|
853
|
+
if (delegate) {
|
|
854
|
+
const actionBuilder = new ActionBuilder(this);
|
|
855
|
+
delegate(actionBuilder);
|
|
856
|
+
}
|
|
857
|
+
return this;
|
|
858
|
+
}
|
|
859
|
+
// Build method to create dialog node
|
|
860
|
+
build() {
|
|
861
|
+
// If we have content layout, use it directly to avoid extra wrapper
|
|
862
|
+
if (this.contentLayout) {
|
|
863
|
+
return {
|
|
864
|
+
...this.contentLayout,
|
|
865
|
+
// Add dialog-specific properties
|
|
866
|
+
options: {
|
|
867
|
+
...this.contentLayout.options,
|
|
868
|
+
...this.dialogState.dialogOptions
|
|
869
|
+
}
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
// Fallback to dialog state structure if no content
|
|
873
|
+
const result = {
|
|
874
|
+
...this.dialogState,
|
|
875
|
+
children: []
|
|
876
|
+
};
|
|
877
|
+
// Add dialog-specific properties
|
|
878
|
+
if (this.dialogState.dialogOptions) {
|
|
879
|
+
result.options = { ...result.options, ...this.dialogState.dialogOptions };
|
|
880
|
+
}
|
|
881
|
+
return result;
|
|
882
|
+
}
|
|
883
|
+
// Dialog-specific methods
|
|
884
|
+
async show() {
|
|
885
|
+
const dialogNode = this.build();
|
|
886
|
+
// Import the dialog renderer component dynamically
|
|
887
|
+
const { AXPDialogRendererComponent } = await Promise.resolve().then(function () { return dialogRenderer_component; });
|
|
888
|
+
// Create dialog configuration
|
|
889
|
+
const dialogConfig = {
|
|
890
|
+
title: this.dialogState.dialogOptions?.title || '',
|
|
891
|
+
message: this.dialogState.dialogOptions?.message,
|
|
892
|
+
context: this.dialogState.dialogOptions?.context || {},
|
|
893
|
+
definition: dialogNode,
|
|
894
|
+
actions: this.dialogState.actions,
|
|
895
|
+
};
|
|
896
|
+
// The Promise resolves when user clicks an action button
|
|
897
|
+
return new Promise(async (resolve) => {
|
|
898
|
+
this.popupService.open(AXPDialogRendererComponent, {
|
|
899
|
+
title: dialogConfig.title,
|
|
900
|
+
size: this.dialogState.dialogOptions?.size || 'md',
|
|
901
|
+
closeButton: this.dialogState.dialogOptions?.closeButton || false,
|
|
902
|
+
closeOnBackdropClick: false,
|
|
903
|
+
draggable: false,
|
|
904
|
+
data: {
|
|
905
|
+
config: dialogConfig,
|
|
906
|
+
callBack: (result) => {
|
|
907
|
+
// Resolve with the dialog reference when user clicks an action
|
|
908
|
+
resolve(result);
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
});
|
|
912
|
+
});
|
|
913
|
+
}
|
|
949
914
|
}
|
|
950
915
|
//#endregion
|
|
951
916
|
//#region ---- Widget Builder Implementation ----
|
|
917
|
+
/**
|
|
918
|
+
* Widget Builder - Single Responsibility Principle
|
|
919
|
+
* Handles individual widget configuration and building
|
|
920
|
+
*/
|
|
952
921
|
class WidgetBuilder {
|
|
953
922
|
constructor(name) {
|
|
954
923
|
this.widgetState = {
|
|
955
924
|
type: 'widget',
|
|
956
|
-
|
|
925
|
+
options: {},
|
|
957
926
|
};
|
|
927
|
+
this.inheritanceContext = {};
|
|
958
928
|
if (name) {
|
|
959
929
|
this.widgetState.name = name;
|
|
960
930
|
}
|
|
961
931
|
}
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
932
|
+
type(type) {
|
|
933
|
+
this.widgetState.type = type;
|
|
934
|
+
return this;
|
|
935
|
+
}
|
|
936
|
+
name(name) {
|
|
937
|
+
this.widgetState.name = name;
|
|
938
|
+
if (!this.widgetState.path) {
|
|
939
|
+
this.widgetState.path = name;
|
|
940
|
+
}
|
|
941
|
+
return this;
|
|
967
942
|
}
|
|
968
|
-
|
|
969
|
-
this.widgetState.
|
|
943
|
+
path(path) {
|
|
944
|
+
this.widgetState.path = path;
|
|
945
|
+
return this;
|
|
946
|
+
}
|
|
947
|
+
options(options) {
|
|
948
|
+
this.widgetState.options = options;
|
|
970
949
|
return this;
|
|
971
950
|
}
|
|
972
951
|
layout(value) {
|
|
@@ -988,333 +967,710 @@ class WidgetBuilder {
|
|
|
988
967
|
}
|
|
989
968
|
mode(mode) {
|
|
990
969
|
this.widgetState.mode = mode;
|
|
970
|
+
this.inheritanceContext.mode = mode;
|
|
991
971
|
return this;
|
|
992
972
|
}
|
|
993
973
|
visible(condition) {
|
|
994
|
-
this.widgetState.
|
|
974
|
+
if (!this.widgetState.options) {
|
|
975
|
+
this.widgetState.options = {};
|
|
976
|
+
}
|
|
977
|
+
this.widgetState.options['visible'] = condition;
|
|
978
|
+
this.inheritanceContext.visible = condition;
|
|
995
979
|
return this;
|
|
996
980
|
}
|
|
997
981
|
disabled(condition) {
|
|
998
|
-
this.widgetState.
|
|
982
|
+
if (!this.widgetState.options) {
|
|
983
|
+
this.widgetState.options = {};
|
|
984
|
+
}
|
|
985
|
+
this.widgetState.options['disabled'] = condition;
|
|
986
|
+
this.inheritanceContext.disabled = condition;
|
|
999
987
|
return this;
|
|
1000
988
|
}
|
|
1001
989
|
readonly(condition) {
|
|
1002
|
-
this.widgetState.
|
|
1003
|
-
|
|
1004
|
-
}
|
|
1005
|
-
order(value) {
|
|
1006
|
-
this.widgetState.order = value;
|
|
1007
|
-
return this;
|
|
1008
|
-
}
|
|
1009
|
-
// Container helpers - delegate pattern required
|
|
1010
|
-
grid(delegate) {
|
|
1011
|
-
const container = new GridContainerBuilder();
|
|
1012
|
-
if (delegate) {
|
|
1013
|
-
delegate(container);
|
|
990
|
+
if (!this.widgetState.options) {
|
|
991
|
+
this.widgetState.options = {};
|
|
1014
992
|
}
|
|
1015
|
-
|
|
1016
|
-
this.
|
|
1017
|
-
this.widgetState.children = built.children;
|
|
993
|
+
this.widgetState.options['readonly'] = condition;
|
|
994
|
+
this.inheritanceContext.readonly = condition;
|
|
1018
995
|
return this;
|
|
1019
996
|
}
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
delegate(container);
|
|
997
|
+
direction(direction) {
|
|
998
|
+
if (!this.widgetState.options) {
|
|
999
|
+
this.widgetState.options = {};
|
|
1024
1000
|
}
|
|
1025
|
-
|
|
1026
|
-
this.
|
|
1027
|
-
this.widgetState.children = built.children;
|
|
1001
|
+
this.widgetState.options['direction'] = direction;
|
|
1002
|
+
this.inheritanceContext.direction = direction;
|
|
1028
1003
|
return this;
|
|
1029
1004
|
}
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1005
|
+
// Inheritance context methods
|
|
1006
|
+
withInheritanceContext(context) {
|
|
1007
|
+
this.inheritanceContext = mergeInheritanceContext(context);
|
|
1008
|
+
// Apply inherited properties to widget state
|
|
1009
|
+
const resolved = resolveInheritedProperties(context, this.inheritanceContext);
|
|
1010
|
+
if (resolved.mode && !this.widgetState.mode) {
|
|
1011
|
+
this.widgetState.mode = resolved.mode;
|
|
1012
|
+
}
|
|
1013
|
+
if (!this.widgetState.options)
|
|
1014
|
+
this.widgetState.options = {};
|
|
1015
|
+
if (resolved.disabled !== undefined && !this.widgetState.options['disabled']) {
|
|
1016
|
+
this.widgetState.options['disabled'] = resolved.disabled;
|
|
1017
|
+
}
|
|
1018
|
+
if (resolved.readonly !== undefined && !this.widgetState.options['readonly']) {
|
|
1019
|
+
this.widgetState.options['readonly'] = resolved.readonly;
|
|
1020
|
+
}
|
|
1021
|
+
if (resolved.direction !== undefined && !this.widgetState.options['direction']) {
|
|
1022
|
+
this.widgetState.options['direction'] = resolved.direction;
|
|
1023
|
+
}
|
|
1024
|
+
if (resolved.visible !== undefined && !this.widgetState.options['visible']) {
|
|
1025
|
+
this.widgetState.options['visible'] = resolved.visible;
|
|
1034
1026
|
}
|
|
1035
|
-
const built = container.build();
|
|
1036
|
-
this.widgetState.widget = built.widget;
|
|
1037
|
-
this.widgetState.children = built.children;
|
|
1038
1027
|
return this;
|
|
1039
1028
|
}
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1029
|
+
getInheritanceContext() {
|
|
1030
|
+
return { ...this.inheritanceContext };
|
|
1031
|
+
}
|
|
1032
|
+
build() {
|
|
1033
|
+
return {
|
|
1034
|
+
name: this.widgetState.name,
|
|
1035
|
+
type: this.widgetState.type,
|
|
1036
|
+
options: this.widgetState.options,
|
|
1037
|
+
mode: this.widgetState.mode,
|
|
1038
|
+
path: this.widgetState.path,
|
|
1039
|
+
defaultValue: this.widgetState.defaultValue,
|
|
1040
|
+
};
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
//#region ---- Action Builder Implementation ----
|
|
1044
|
+
class ActionBuilder {
|
|
1045
|
+
constructor(dialogBuilder) {
|
|
1046
|
+
this.dialogBuilder = dialogBuilder;
|
|
1047
|
+
}
|
|
1048
|
+
cancel(text) {
|
|
1049
|
+
if (!this.dialogBuilder['dialogState'].actions.footer.suffix) {
|
|
1050
|
+
this.dialogBuilder['dialogState'].actions.footer.suffix = [];
|
|
1044
1051
|
}
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1052
|
+
this.dialogBuilder['dialogState'].actions.footer.suffix.push({
|
|
1053
|
+
title: text || '@general:actions.cancel.title',
|
|
1054
|
+
icon: 'fa-times',
|
|
1055
|
+
color: 'default',
|
|
1056
|
+
command: { name: 'cancel' },
|
|
1057
|
+
});
|
|
1048
1058
|
return this;
|
|
1049
1059
|
}
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
delegate(container);
|
|
1060
|
+
submit(text) {
|
|
1061
|
+
if (!this.dialogBuilder['dialogState'].actions.footer.suffix) {
|
|
1062
|
+
this.dialogBuilder['dialogState'].actions.footer.suffix = [];
|
|
1054
1063
|
}
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1064
|
+
this.dialogBuilder['dialogState'].actions.footer.suffix.push({
|
|
1065
|
+
title: text || '@general:actions.submit.title',
|
|
1066
|
+
icon: 'fa-check',
|
|
1067
|
+
color: 'primary',
|
|
1068
|
+
command: { name: 'submit', options: { validate: true } },
|
|
1069
|
+
});
|
|
1058
1070
|
return this;
|
|
1059
1071
|
}
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
delegate(child);
|
|
1072
|
+
custom(action) {
|
|
1073
|
+
if (!this.dialogBuilder['dialogState'].actions.footer.suffix) {
|
|
1074
|
+
this.dialogBuilder['dialogState'].actions.footer.suffix = [];
|
|
1064
1075
|
}
|
|
1065
|
-
this.
|
|
1066
|
-
this.widgetState.children.push(child.build());
|
|
1076
|
+
this.dialogBuilder['dialogState'].actions.footer.suffix.push(action);
|
|
1067
1077
|
return this;
|
|
1068
1078
|
}
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
}
|
|
1077
|
-
else {
|
|
1078
|
-
this.widgetState.widget = {
|
|
1079
|
-
type: 'text-editor',
|
|
1080
|
-
options,
|
|
1081
|
-
};
|
|
1082
|
-
return this;
|
|
1083
|
-
}
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
class AXPLayoutConversionService {
|
|
1082
|
+
constructor() {
|
|
1083
|
+
//#region ---- Caching ----
|
|
1084
|
+
this.widgetTreeCache = new Map();
|
|
1085
|
+
this.formDefinitionCache = new Map();
|
|
1084
1086
|
}
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
return this;
|
|
1087
|
+
//#endregion
|
|
1088
|
+
//#region ---- Public Methods ----
|
|
1089
|
+
/**
|
|
1090
|
+
* Convert AXPDynamicFormDefinition to AXPWidgetNode tree structure
|
|
1091
|
+
* Groups become Fieldset Layouts with Form Field widgets as children
|
|
1092
|
+
* Fields become Form Field widgets with Editor widgets as children
|
|
1093
|
+
*/
|
|
1094
|
+
convertFormDefinition(formDefinition) {
|
|
1095
|
+
// Create cache key based on form definition content
|
|
1096
|
+
const cacheKey = this.createFormDefinitionCacheKey(formDefinition);
|
|
1097
|
+
// Check cache first
|
|
1098
|
+
if (this.widgetTreeCache.has(cacheKey)) {
|
|
1099
|
+
return this.widgetTreeCache.get(cacheKey);
|
|
1098
1100
|
}
|
|
1101
|
+
// Generate widget tree
|
|
1102
|
+
const widgetTree = {
|
|
1103
|
+
type: 'grid-layout',
|
|
1104
|
+
name: 'dynamic-form-container',
|
|
1105
|
+
options: {
|
|
1106
|
+
title: 'Dynamic Form',
|
|
1107
|
+
grid: {
|
|
1108
|
+
default: {
|
|
1109
|
+
columns: 1,
|
|
1110
|
+
gap: '1rem'
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
},
|
|
1114
|
+
children: formDefinition.groups.map(group => this.createGroupAsFieldsetWidget(group))
|
|
1115
|
+
};
|
|
1116
|
+
// Cache the result
|
|
1117
|
+
this.widgetTreeCache.set(cacheKey, widgetTree);
|
|
1118
|
+
return widgetTree;
|
|
1099
1119
|
}
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1120
|
+
/**
|
|
1121
|
+
* Convert AXPWidgetNode tree back to AXPDynamicFormDefinition
|
|
1122
|
+
* Parses Fieldset Layouts back to Groups
|
|
1123
|
+
* Parses Form Field widgets back to Fields
|
|
1124
|
+
*/
|
|
1125
|
+
convertWidgetTreeToFormDefinition(widgetTree) {
|
|
1126
|
+
// Create cache key based on widget tree content
|
|
1127
|
+
const cacheKey = this.createWidgetTreeCacheKey(widgetTree);
|
|
1128
|
+
// Check cache first
|
|
1129
|
+
if (this.formDefinitionCache.has(cacheKey)) {
|
|
1130
|
+
return this.formDefinitionCache.get(cacheKey);
|
|
1106
1131
|
}
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1132
|
+
// Parse widget tree
|
|
1133
|
+
const groups = [];
|
|
1134
|
+
if (widgetTree.children) {
|
|
1135
|
+
widgetTree.children.forEach(child => {
|
|
1136
|
+
if (child.type === 'fieldset-layout') {
|
|
1137
|
+
const group = this.extractGroupFromFieldset(child);
|
|
1138
|
+
groups.push(group);
|
|
1139
|
+
}
|
|
1140
|
+
});
|
|
1113
1141
|
}
|
|
1142
|
+
const formDefinition = { groups };
|
|
1143
|
+
// Cache the result
|
|
1144
|
+
this.formDefinitionCache.set(cacheKey, formDefinition);
|
|
1145
|
+
return formDefinition;
|
|
1114
1146
|
}
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
return
|
|
1147
|
+
/**
|
|
1148
|
+
* Validate that a widget tree represents a valid dynamic form structure
|
|
1149
|
+
*/
|
|
1150
|
+
validateFormWidgetTree(widgetTree) {
|
|
1151
|
+
if (!widgetTree || widgetTree.type !== 'grid-layout') {
|
|
1152
|
+
return false;
|
|
1121
1153
|
}
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
type: 'password-editor',
|
|
1125
|
-
options,
|
|
1126
|
-
};
|
|
1127
|
-
return this;
|
|
1154
|
+
if (!widgetTree.children || widgetTree.children.length === 0) {
|
|
1155
|
+
return true; // Empty form is valid
|
|
1128
1156
|
}
|
|
1157
|
+
// Check that all children are fieldset-layout widgets
|
|
1158
|
+
return widgetTree.children.every(child => child.type === 'fieldset-layout' &&
|
|
1159
|
+
child.children &&
|
|
1160
|
+
child.children.every(formField => formField.type === 'form-field' &&
|
|
1161
|
+
formField.children &&
|
|
1162
|
+
formField.children.length > 0));
|
|
1129
1163
|
}
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
}
|
|
1137
|
-
else {
|
|
1138
|
-
this.widgetState.widget = {
|
|
1139
|
-
type: 'number-editor',
|
|
1140
|
-
options,
|
|
1141
|
-
};
|
|
1142
|
-
return this;
|
|
1143
|
-
}
|
|
1164
|
+
/**
|
|
1165
|
+
* Clear all caches
|
|
1166
|
+
*/
|
|
1167
|
+
clearCaches() {
|
|
1168
|
+
this.widgetTreeCache.clear();
|
|
1169
|
+
this.formDefinitionCache.clear();
|
|
1144
1170
|
}
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
this.widgetState.widget = {
|
|
1154
|
-
type: 'select-editor',
|
|
1155
|
-
options,
|
|
1156
|
-
};
|
|
1157
|
-
return this;
|
|
1158
|
-
}
|
|
1171
|
+
/**
|
|
1172
|
+
* Get cache statistics
|
|
1173
|
+
*/
|
|
1174
|
+
getCacheStats() {
|
|
1175
|
+
return {
|
|
1176
|
+
widgetTreeCacheSize: this.widgetTreeCache.size,
|
|
1177
|
+
formDefinitionCacheSize: this.formDefinitionCache.size
|
|
1178
|
+
};
|
|
1159
1179
|
}
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1180
|
+
//#endregion
|
|
1181
|
+
//#region ---- Private Methods ----
|
|
1182
|
+
/**
|
|
1183
|
+
* Convert a single group to Fieldset widget structure
|
|
1184
|
+
*/
|
|
1185
|
+
createGroupAsFieldsetWidget(group) {
|
|
1186
|
+
// Determine columns count from layout or default to 1
|
|
1187
|
+
const columnsCount = 1;
|
|
1188
|
+
return {
|
|
1189
|
+
type: 'fieldset-layout',
|
|
1190
|
+
name: group.name,
|
|
1191
|
+
options: {
|
|
1192
|
+
title: group.title,
|
|
1193
|
+
description: group.description,
|
|
1194
|
+
cols: columnsCount
|
|
1195
|
+
},
|
|
1196
|
+
children: this.createFieldWidgets(group.parameters, columnsCount)
|
|
1197
|
+
};
|
|
1198
|
+
}
|
|
1199
|
+
/**
|
|
1200
|
+
* Convert fields to Form Field widgets
|
|
1201
|
+
*/
|
|
1202
|
+
createFieldWidgets(fields, columnsCount) {
|
|
1203
|
+
return fields.map(field => this.createFormFieldWidget(field));
|
|
1204
|
+
}
|
|
1205
|
+
/**
|
|
1206
|
+
* Convert a single field to Form Field widget with editor as child
|
|
1207
|
+
*/
|
|
1208
|
+
createFormFieldWidget(field) {
|
|
1209
|
+
return {
|
|
1210
|
+
type: 'form-field',
|
|
1211
|
+
name: field.path,
|
|
1212
|
+
options: {
|
|
1213
|
+
label: field.title,
|
|
1214
|
+
description: field.description,
|
|
1215
|
+
showLabel: true
|
|
1216
|
+
},
|
|
1217
|
+
children: [field.widget] // The editor widget becomes a child of form-field
|
|
1218
|
+
};
|
|
1219
|
+
}
|
|
1220
|
+
/**
|
|
1221
|
+
* Extract group information from Fieldset Layout widget
|
|
1222
|
+
*/
|
|
1223
|
+
extractGroupFromFieldset(fieldsetNode) {
|
|
1224
|
+
const columnsCount = fieldsetNode.options?.['cols'] || 1;
|
|
1225
|
+
// Extract fields directly from fieldset children
|
|
1226
|
+
const fields = [];
|
|
1227
|
+
if (fieldsetNode.children) {
|
|
1228
|
+
fieldsetNode.children.forEach(formField => {
|
|
1229
|
+
if (formField.type === 'form-field' && formField.children && formField.children.length > 0) {
|
|
1230
|
+
const field = this.extractFieldFromFormWidget(formField);
|
|
1231
|
+
if (field) {
|
|
1232
|
+
fields.push(field);
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
});
|
|
1166
1236
|
}
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1237
|
+
return {
|
|
1238
|
+
name: fieldsetNode.name || `group-${Date.now()}`,
|
|
1239
|
+
title: fieldsetNode.options?.['title'],
|
|
1240
|
+
description: fieldsetNode.options?.['description'],
|
|
1241
|
+
parameters: fields,
|
|
1242
|
+
};
|
|
1243
|
+
}
|
|
1244
|
+
/**
|
|
1245
|
+
* Extract field information from Form Field widget
|
|
1246
|
+
*/
|
|
1247
|
+
extractFieldFromFormWidget(formFieldNode) {
|
|
1248
|
+
if (!formFieldNode.children || formFieldNode.children.length === 0) {
|
|
1249
|
+
return null;
|
|
1173
1250
|
}
|
|
1251
|
+
const editorWidget = formFieldNode.children[0];
|
|
1252
|
+
return {
|
|
1253
|
+
path: formFieldNode.name || editorWidget.name || `field-${Date.now()}`,
|
|
1254
|
+
title: formFieldNode.options?.['label'],
|
|
1255
|
+
description: formFieldNode.options?.['description'],
|
|
1256
|
+
widget: editorWidget,
|
|
1257
|
+
mode: formFieldNode.mode
|
|
1258
|
+
};
|
|
1174
1259
|
}
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1260
|
+
/**
|
|
1261
|
+
* Create cache key for form definition
|
|
1262
|
+
*/
|
|
1263
|
+
createFormDefinitionCacheKey(formDefinition) {
|
|
1264
|
+
// Create a hash-like key instead of full JSON string
|
|
1265
|
+
const keyParts = [];
|
|
1266
|
+
keyParts.push(`groups:${formDefinition.groups.length}`);
|
|
1267
|
+
formDefinition.groups.forEach((group, groupIndex) => {
|
|
1268
|
+
keyParts.push(`g${groupIndex}:${group.name}:${group.parameters.length}`);
|
|
1269
|
+
group.parameters.forEach((param, paramIndex) => {
|
|
1270
|
+
keyParts.push(`p${groupIndex}.${paramIndex}:${param.path}:${param.widget.type}`);
|
|
1271
|
+
});
|
|
1272
|
+
});
|
|
1273
|
+
if (formDefinition.mode) {
|
|
1274
|
+
keyParts.push(`mode:${formDefinition.mode}`);
|
|
1181
1275
|
}
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
return this;
|
|
1276
|
+
// Join with delimiter and create a shorter hash
|
|
1277
|
+
const keyString = keyParts.join('|');
|
|
1278
|
+
// If still too long, create a simple hash
|
|
1279
|
+
if (keyString.length > 100) {
|
|
1280
|
+
return this.createSimpleHash(keyString);
|
|
1188
1281
|
}
|
|
1282
|
+
return keyString;
|
|
1189
1283
|
}
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1284
|
+
/**
|
|
1285
|
+
* Create cache key for widget tree
|
|
1286
|
+
*/
|
|
1287
|
+
createWidgetTreeCacheKey(widgetTree) {
|
|
1288
|
+
// Create a hash-like key instead of full JSON string
|
|
1289
|
+
const keyParts = [];
|
|
1290
|
+
keyParts.push(`type:${widgetTree.type}`);
|
|
1291
|
+
if (widgetTree.name) {
|
|
1292
|
+
keyParts.push(`name:${widgetTree.name}`);
|
|
1196
1293
|
}
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1294
|
+
if (widgetTree.children) {
|
|
1295
|
+
keyParts.push(`children:${widgetTree.children.length}`);
|
|
1296
|
+
widgetTree.children.forEach((child, index) => {
|
|
1297
|
+
keyParts.push(`c${index}:${child.type}`);
|
|
1298
|
+
if (child.children) {
|
|
1299
|
+
keyParts.push(`cc${index}:${child.children.length}`);
|
|
1300
|
+
child.children.forEach((grandChild, gIndex) => {
|
|
1301
|
+
keyParts.push(`gc${index}.${gIndex}:${grandChild.type}`);
|
|
1302
|
+
if (grandChild.children) {
|
|
1303
|
+
keyParts.push(`gcc${index}.${gIndex}:${grandChild.children.length}`);
|
|
1304
|
+
}
|
|
1305
|
+
});
|
|
1306
|
+
}
|
|
1307
|
+
});
|
|
1203
1308
|
}
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
this.widgetState.children.push(child.build());
|
|
1210
|
-
return this;
|
|
1309
|
+
// Join with delimiter and create a shorter hash
|
|
1310
|
+
const keyString = keyParts.join('|');
|
|
1311
|
+
// If still too long, create a simple hash
|
|
1312
|
+
if (keyString.length > 100) {
|
|
1313
|
+
return this.createSimpleHash(keyString);
|
|
1211
1314
|
}
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1315
|
+
return keyString;
|
|
1316
|
+
}
|
|
1317
|
+
/**
|
|
1318
|
+
* Create a simple hash from a string
|
|
1319
|
+
*/
|
|
1320
|
+
createSimpleHash(str) {
|
|
1321
|
+
let hash = 0;
|
|
1322
|
+
if (str.length === 0)
|
|
1323
|
+
return hash.toString();
|
|
1324
|
+
for (let i = 0; i < str.length; i++) {
|
|
1325
|
+
const char = str.charCodeAt(i);
|
|
1326
|
+
hash = ((hash << 5) - hash) + char;
|
|
1327
|
+
hash = hash & hash; // Convert to 32-bit integer
|
|
1218
1328
|
}
|
|
1329
|
+
return Math.abs(hash).toString(36); // Convert to base36 for shorter string
|
|
1219
1330
|
}
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1331
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXPLayoutConversionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1332
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXPLayoutConversionService, providedIn: 'root' }); }
|
|
1333
|
+
}
|
|
1334
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXPLayoutConversionService, decorators: [{
|
|
1335
|
+
type: Injectable,
|
|
1336
|
+
args: [{
|
|
1337
|
+
providedIn: 'root'
|
|
1338
|
+
}]
|
|
1339
|
+
}] });
|
|
1340
|
+
|
|
1341
|
+
class AXPLayoutRendererComponent {
|
|
1342
|
+
constructor() {
|
|
1343
|
+
this.evaluatorService = inject(AXPExpressionEvaluatorService);
|
|
1344
|
+
this.conversionService = inject(AXPLayoutConversionService);
|
|
1345
|
+
/**
|
|
1346
|
+
* Tracks the latest scheduled evaluation to ensure last-write-wins for async evaluate
|
|
1347
|
+
*/
|
|
1348
|
+
this.evaluationRunId = 0;
|
|
1349
|
+
/**
|
|
1350
|
+
* RxJS subjects for context management
|
|
1351
|
+
*/
|
|
1352
|
+
this.contextUpdateSubject = new Subject();
|
|
1353
|
+
this.contextChangeSubject = new Subject();
|
|
1354
|
+
/**
|
|
1355
|
+
* Cache for expression evaluation results
|
|
1356
|
+
*/
|
|
1357
|
+
this.expressionCache = new Map();
|
|
1358
|
+
/**
|
|
1359
|
+
* Cache for widget tree comparisons
|
|
1360
|
+
*/
|
|
1361
|
+
this.widgetTreeCache = new Map();
|
|
1362
|
+
/**
|
|
1363
|
+
* Last layout hash for change detection
|
|
1364
|
+
*/
|
|
1365
|
+
this.lastLayoutHash = '';
|
|
1366
|
+
//#region ---- Inputs ----
|
|
1367
|
+
/**
|
|
1368
|
+
* Form definition containing groups and fields OR widget tree
|
|
1369
|
+
*/
|
|
1370
|
+
this.layout = input.required(...(ngDevMode ? [{ debugName: "layout" }] : []));
|
|
1371
|
+
/**
|
|
1372
|
+
* Form context/model data
|
|
1373
|
+
*/
|
|
1374
|
+
this.context = model({}, ...(ngDevMode ? [{ debugName: "context" }] : []));
|
|
1375
|
+
/**
|
|
1376
|
+
* Form appearance and density styling (normal, compact, spacious)
|
|
1377
|
+
*/
|
|
1378
|
+
this.look = input('fieldset', ...(ngDevMode ? [{ debugName: "look" }] : []));
|
|
1379
|
+
/**
|
|
1380
|
+
* Default form mode. Can be overridden by section/group and field.
|
|
1381
|
+
*/
|
|
1382
|
+
this.mode = input('edit', ...(ngDevMode ? [{ debugName: "mode" }] : []));
|
|
1383
|
+
//#endregion
|
|
1384
|
+
//#region ---- Widget Tree Conversion ----
|
|
1385
|
+
this.widgetTree = signal(null, ...(ngDevMode ? [{ debugName: "widgetTree" }] : []));
|
|
1386
|
+
/**
|
|
1387
|
+
* Convert and evaluate data when inputs change (optimized with RxJS)
|
|
1388
|
+
*/
|
|
1389
|
+
this.conversionEffect = effect(() => {
|
|
1390
|
+
const inputData = this.layout();
|
|
1391
|
+
const ctx = this.internalContext();
|
|
1392
|
+
const look = this.look();
|
|
1393
|
+
const runId = ++this.evaluationRunId;
|
|
1394
|
+
// Generate layout hash for change detection
|
|
1395
|
+
const layoutHash = this.generateLayoutHash(inputData, look);
|
|
1396
|
+
// Skip if layout hasn't changed
|
|
1397
|
+
if (layoutHash === this.lastLayoutHash && this.widgetTree()) {
|
|
1398
|
+
return;
|
|
1399
|
+
}
|
|
1400
|
+
this.lastLayoutHash = layoutHash;
|
|
1401
|
+
(async () => {
|
|
1402
|
+
// First evaluate expressions if needed
|
|
1403
|
+
const evaluated = await this.expressionEvaluator(inputData, ctx);
|
|
1404
|
+
// Ignore stale results
|
|
1405
|
+
if (runId !== this.evaluationRunId) {
|
|
1406
|
+
return;
|
|
1407
|
+
}
|
|
1408
|
+
// Convert to widget tree (this will also apply the layout look)
|
|
1409
|
+
const tree = evaluated;
|
|
1410
|
+
// Update widget tree
|
|
1411
|
+
const prev = this.widgetTree();
|
|
1412
|
+
if (!isEqual(prev, tree)) {
|
|
1413
|
+
tree.mode = this.mode();
|
|
1414
|
+
this.widgetTree.set(tree);
|
|
1415
|
+
}
|
|
1416
|
+
})();
|
|
1417
|
+
}, ...(ngDevMode ? [{ debugName: "conversionEffect" }] : []));
|
|
1418
|
+
//#endregion
|
|
1419
|
+
//#region ---- Outputs ----
|
|
1420
|
+
/**
|
|
1421
|
+
* Emitted when context change is initiated
|
|
1422
|
+
*/
|
|
1423
|
+
this.contextInitiated = output();
|
|
1424
|
+
/**
|
|
1425
|
+
* Emitted when form becomes valid/invalid
|
|
1426
|
+
*/
|
|
1427
|
+
this.validityChange = output();
|
|
1428
|
+
//#endregion
|
|
1429
|
+
//#region ---- Properties ----
|
|
1430
|
+
this.form = viewChild(AXFormComponent, ...(ngDevMode ? [{ debugName: "form" }] : []));
|
|
1431
|
+
this.container = viewChild(AXPWidgetContainerComponent, ...(ngDevMode ? [{ debugName: "container" }] : []));
|
|
1432
|
+
/**
|
|
1433
|
+
* Internal context signal for reactivity
|
|
1434
|
+
*/
|
|
1435
|
+
this.internalContext = signal({}, ...(ngDevMode ? [{ debugName: "internalContext" }] : []));
|
|
1436
|
+
//#endregion
|
|
1437
|
+
//#region ---- Effects ----
|
|
1438
|
+
/**
|
|
1439
|
+
* Effect to sync context changes from external to internal (optimized with RxJS)
|
|
1440
|
+
*/
|
|
1441
|
+
this.#contextSyncEffect = effect(() => {
|
|
1442
|
+
const ctx = this.context() ?? {};
|
|
1443
|
+
this.contextUpdateSubject.next(ctx);
|
|
1444
|
+
}, ...(ngDevMode ? [{ debugName: "#contextSyncEffect" }] : []));
|
|
1445
|
+
/**
|
|
1446
|
+
* Effect to handle widget tree status changes
|
|
1447
|
+
*/
|
|
1448
|
+
this.#widgetStatusEffect = effect(() => {
|
|
1449
|
+
const widgetTree = this.widgetTree();
|
|
1450
|
+
if (widgetTree) {
|
|
1451
|
+
this.container()?.builderService.setStatus(AXPPageStatus.Rendered);
|
|
1452
|
+
}
|
|
1453
|
+
}, ...(ngDevMode ? [{ debugName: "#widgetStatusEffect" }] : []));
|
|
1454
|
+
}
|
|
1455
|
+
async expressionEvaluator(expression, context) {
|
|
1456
|
+
// Check if it's a form definition that needs conversion
|
|
1457
|
+
if (this.isFormDefinition(expression)) {
|
|
1458
|
+
return this.conversionService.convertFormDefinition(expression);
|
|
1226
1459
|
}
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1460
|
+
// Generate cache key using a more efficient method
|
|
1461
|
+
const cacheKey = this.generateCacheKey(expression, context);
|
|
1462
|
+
// Check cache first
|
|
1463
|
+
if (this.expressionCache.has(cacheKey)) {
|
|
1464
|
+
return this.expressionCache.get(cacheKey);
|
|
1465
|
+
}
|
|
1466
|
+
const scope = {
|
|
1467
|
+
context: {
|
|
1468
|
+
eval: (path) => get(context, path),
|
|
1469
|
+
},
|
|
1470
|
+
};
|
|
1471
|
+
const result = await this.evaluatorService.evaluate(expression, scope);
|
|
1472
|
+
// Cache result with LRU-like behavior
|
|
1473
|
+
if (this.expressionCache.size > 50) {
|
|
1474
|
+
// Clear half the cache when it gets too large
|
|
1475
|
+
const keysToDelete = Array.from(this.expressionCache.keys()).slice(0, 25);
|
|
1476
|
+
keysToDelete.forEach(key => this.expressionCache.delete(key));
|
|
1233
1477
|
}
|
|
1478
|
+
this.expressionCache.set(cacheKey, result);
|
|
1479
|
+
return result;
|
|
1234
1480
|
}
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1481
|
+
//#endregion
|
|
1482
|
+
//#region ---- Lifecycle Methods ----
|
|
1483
|
+
ngOnInit() {
|
|
1484
|
+
// Initialize internal context with input context
|
|
1485
|
+
this.internalContext.set(this.context() ?? {});
|
|
1486
|
+
// Setup RxJS streams for context management
|
|
1487
|
+
this.setupContextStreams();
|
|
1488
|
+
}
|
|
1489
|
+
//#endregion
|
|
1490
|
+
//#region ---- Effects ----
|
|
1491
|
+
/**
|
|
1492
|
+
* Effect to sync context changes from external to internal (optimized with RxJS)
|
|
1493
|
+
*/
|
|
1494
|
+
#contextSyncEffect;
|
|
1495
|
+
/**
|
|
1496
|
+
* Effect to handle widget tree status changes
|
|
1497
|
+
*/
|
|
1498
|
+
#widgetStatusEffect;
|
|
1499
|
+
//#endregion
|
|
1500
|
+
//#region ---- Event Handlers ----
|
|
1501
|
+
/**
|
|
1502
|
+
* Handle context change events from widget container (optimized with RxJS)
|
|
1503
|
+
*/
|
|
1504
|
+
handleContextChanged(event) {
|
|
1505
|
+
if (event.state === 'initiated') {
|
|
1506
|
+
this.contextInitiated.emit(event.data);
|
|
1241
1507
|
}
|
|
1242
1508
|
else {
|
|
1243
|
-
this.
|
|
1244
|
-
type,
|
|
1245
|
-
options: options,
|
|
1246
|
-
};
|
|
1247
|
-
return this;
|
|
1509
|
+
this.contextChangeSubject.next(event.data ?? {});
|
|
1248
1510
|
}
|
|
1249
1511
|
}
|
|
1250
|
-
|
|
1512
|
+
//#endregion
|
|
1513
|
+
//#region ---- Public Methods ----
|
|
1514
|
+
/**
|
|
1515
|
+
* Get the form component instance
|
|
1516
|
+
*/
|
|
1517
|
+
getForm() {
|
|
1518
|
+
return this.form();
|
|
1519
|
+
}
|
|
1520
|
+
/**
|
|
1521
|
+
* Get the widget container component instance
|
|
1522
|
+
*/
|
|
1523
|
+
getContainer() {
|
|
1524
|
+
return this.container();
|
|
1525
|
+
}
|
|
1526
|
+
/**
|
|
1527
|
+
* Get current form context
|
|
1528
|
+
*/
|
|
1529
|
+
getContext() {
|
|
1530
|
+
return this.internalContext();
|
|
1531
|
+
}
|
|
1532
|
+
/**
|
|
1533
|
+
* Update form context programmatically
|
|
1534
|
+
*/
|
|
1535
|
+
updateContext(context) {
|
|
1536
|
+
this.internalContext.set(context);
|
|
1537
|
+
}
|
|
1538
|
+
/**
|
|
1539
|
+
* Get the current widget tree
|
|
1540
|
+
*/
|
|
1541
|
+
getWidgetTree() {
|
|
1542
|
+
return this.widgetTree();
|
|
1543
|
+
}
|
|
1544
|
+
/**
|
|
1545
|
+
* Validate the form
|
|
1546
|
+
*/
|
|
1547
|
+
async validate() {
|
|
1548
|
+
const form = this.form();
|
|
1549
|
+
if (form) {
|
|
1550
|
+
const isValid = await form.validate();
|
|
1551
|
+
this.validityChange.emit(isValid.result);
|
|
1552
|
+
return isValid;
|
|
1553
|
+
}
|
|
1251
1554
|
return {
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
children: this.widgetState.children,
|
|
1256
|
-
layout: this.widgetState.layout,
|
|
1257
|
-
mode: this.widgetState.mode,
|
|
1258
|
-
order: this.widgetState.order,
|
|
1259
|
-
visible: this.widgetState.visible,
|
|
1260
|
-
disabled: this.widgetState.disabled,
|
|
1261
|
-
readonly: this.widgetState.readonly,
|
|
1555
|
+
result: false,
|
|
1556
|
+
messages: [],
|
|
1557
|
+
rules: [],
|
|
1262
1558
|
};
|
|
1263
1559
|
}
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1560
|
+
//#endregion
|
|
1561
|
+
//#region ---- RxJS Stream Setup ----
|
|
1562
|
+
/**
|
|
1563
|
+
* Setup RxJS streams for context management
|
|
1564
|
+
*/
|
|
1565
|
+
setupContextStreams() {
|
|
1566
|
+
// Debounced context updates from external source
|
|
1567
|
+
this.contextUpdateSubject
|
|
1568
|
+
.pipe(debounceTime(16), // ~60fps
|
|
1569
|
+
distinctUntilChanged((prev, curr) => isEqual(prev, curr)), startWith(this.context() ?? {}))
|
|
1570
|
+
.subscribe(ctx => {
|
|
1571
|
+
this.internalContext.set(ctx);
|
|
1572
|
+
});
|
|
1573
|
+
// Debounced context changes from widgets
|
|
1574
|
+
this.contextChangeSubject
|
|
1575
|
+
.pipe(debounceTime(16), // ~60fps
|
|
1576
|
+
distinctUntilChanged((prev, curr) => isEqual(prev, curr)))
|
|
1577
|
+
.subscribe(ctx => {
|
|
1578
|
+
this.internalContext.set(ctx);
|
|
1579
|
+
// Update the model signal directly - it will emit change events automatically
|
|
1580
|
+
this.context.set(this.internalContext());
|
|
1581
|
+
});
|
|
1269
1582
|
}
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1583
|
+
//#endregion
|
|
1584
|
+
//#region ---- Type Guards ----
|
|
1585
|
+
/**
|
|
1586
|
+
* Type guard to check if the input is a form definition
|
|
1587
|
+
*/
|
|
1588
|
+
isFormDefinition(data) {
|
|
1589
|
+
return data &&
|
|
1590
|
+
typeof data === 'object' &&
|
|
1591
|
+
'groups' in data &&
|
|
1592
|
+
Array.isArray(data.groups);
|
|
1593
|
+
}
|
|
1594
|
+
//#endregion
|
|
1595
|
+
//#region ---- Utility Methods ----
|
|
1596
|
+
/**
|
|
1597
|
+
* Generate layout hash for change detection (short hash)
|
|
1598
|
+
*/
|
|
1599
|
+
generateLayoutHash(layout, look) {
|
|
1600
|
+
if (!layout)
|
|
1601
|
+
return '';
|
|
1602
|
+
// Generate short hash for large layout strings
|
|
1603
|
+
const layoutStr = typeof layout === 'string' ? layout : JSON.stringify(layout);
|
|
1604
|
+
const layoutHash = this.simpleHash(layoutStr);
|
|
1605
|
+
return `${layoutHash}|${look}`;
|
|
1606
|
+
}
|
|
1607
|
+
/**
|
|
1608
|
+
* Generate cache key for expression evaluation (short hash)
|
|
1609
|
+
*/
|
|
1610
|
+
generateCacheKey(expression, context) {
|
|
1611
|
+
// Use short hash for better performance
|
|
1612
|
+
const exprStr = typeof expression === 'string' ? expression : JSON.stringify(expression);
|
|
1613
|
+
const exprHash = this.simpleHash(exprStr);
|
|
1614
|
+
const ctxHash = this.generateContextHash(context);
|
|
1615
|
+
return `${exprHash}|${ctxHash}`;
|
|
1616
|
+
}
|
|
1617
|
+
/**
|
|
1618
|
+
* Generate a simple hash for context change detection
|
|
1619
|
+
*/
|
|
1620
|
+
generateContextHash(context) {
|
|
1621
|
+
if (!context || typeof context !== 'object') {
|
|
1622
|
+
return String(context);
|
|
1284
1623
|
}
|
|
1624
|
+
// Generate short hash for context
|
|
1625
|
+
const keys = Object.keys(context).sort();
|
|
1626
|
+
const contextStr = keys.map(key => `${key}:${context[key]}`).join('|');
|
|
1627
|
+
return this.simpleHash(contextStr);
|
|
1285
1628
|
}
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1629
|
+
/**
|
|
1630
|
+
* Simple hash function for generating short keys
|
|
1631
|
+
*/
|
|
1632
|
+
simpleHash(str) {
|
|
1633
|
+
let hash = 0;
|
|
1634
|
+
if (str.length === 0)
|
|
1635
|
+
return hash.toString();
|
|
1636
|
+
for (let i = 0; i < str.length; i++) {
|
|
1637
|
+
const char = str.charCodeAt(i);
|
|
1638
|
+
hash = ((hash << 5) - hash) + char;
|
|
1639
|
+
hash = hash & hash; // Convert to 32-bit integer
|
|
1640
|
+
}
|
|
1641
|
+
// Convert to positive hex string
|
|
1642
|
+
return Math.abs(hash).toString(16);
|
|
1643
|
+
}
|
|
1644
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXPLayoutRendererComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1645
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.3", 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: `
|
|
1646
|
+
<ax-form>
|
|
1647
|
+
<axp-widgets-container [context]="internalContext()" (onContextChanged)="handleContextChanged($event)">
|
|
1648
|
+
@if (widgetTree()) {
|
|
1649
|
+
<ng-container axp-widget-renderer [node]="widgetTree()!" [mode]="mode()"></ng-container>
|
|
1650
|
+
}
|
|
1651
|
+
</axp-widgets-container>
|
|
1652
|
+
</ax-form>
|
|
1653
|
+
`, isInline: true, styles: [":host{display:block;width:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { 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"], exportAs: ["widgetRenderer"] }, { kind: "ngmodule", type: AXFormModule }, { kind: "component", type: i2.AXFormComponent, selector: "ax-form", inputs: ["labelMode", "look", "messageStyle", "updateOn"], outputs: ["onValidate", "updateOnChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
1296
1654
|
}
|
|
1297
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.
|
|
1655
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXPLayoutRendererComponent, decorators: [{
|
|
1298
1656
|
type: Component,
|
|
1299
|
-
args: [{ selector: 'axp-layout-renderer', standalone: true, imports: [CommonModule, AXPWidgetCoreModule], template: `
|
|
1300
|
-
<
|
|
1301
|
-
<
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
</
|
|
1306
|
-
</
|
|
1657
|
+
args: [{ selector: 'axp-layout-renderer', standalone: true, imports: [CommonModule, AXPWidgetCoreModule, AXFormModule], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
1658
|
+
<ax-form>
|
|
1659
|
+
<axp-widgets-container [context]="internalContext()" (onContextChanged)="handleContextChanged($event)">
|
|
1660
|
+
@if (widgetTree()) {
|
|
1661
|
+
<ng-container axp-widget-renderer [node]="widgetTree()!" [mode]="mode()"></ng-container>
|
|
1662
|
+
}
|
|
1663
|
+
</axp-widgets-container>
|
|
1664
|
+
</ax-form>
|
|
1307
1665
|
`, styles: [":host{display:block;width:100%}\n"] }]
|
|
1308
|
-
}]
|
|
1309
|
-
type: Input
|
|
1310
|
-
}] } });
|
|
1666
|
+
}] });
|
|
1311
1667
|
|
|
1312
1668
|
class LayoutBuilderModule {
|
|
1313
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.
|
|
1314
|
-
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.
|
|
1315
|
-
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.
|
|
1669
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: LayoutBuilderModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
|
|
1670
|
+
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.3", ngImport: i0, type: LayoutBuilderModule, imports: [CommonModule, AXPLayoutRendererComponent], exports: [AXPLayoutRendererComponent] }); }
|
|
1671
|
+
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: LayoutBuilderModule, providers: [AXPLayoutBuilderService], imports: [CommonModule, AXPLayoutRendererComponent] }); }
|
|
1316
1672
|
}
|
|
1317
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.
|
|
1673
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: LayoutBuilderModule, decorators: [{
|
|
1318
1674
|
type: NgModule,
|
|
1319
1675
|
args: [{
|
|
1320
1676
|
imports: [CommonModule, AXPLayoutRendererComponent],
|
|
@@ -1323,9 +1679,219 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImpor
|
|
|
1323
1679
|
}]
|
|
1324
1680
|
}] });
|
|
1325
1681
|
|
|
1682
|
+
//#endregion
|
|
1683
|
+
|
|
1684
|
+
class AXPDialogRendererComponent extends AXBasePageComponent {
|
|
1685
|
+
constructor() {
|
|
1686
|
+
super(...arguments);
|
|
1687
|
+
this.result = new EventEmitter();
|
|
1688
|
+
this.context = signal({}, ...(ngDevMode ? [{ debugName: "context" }] : []));
|
|
1689
|
+
// This will be set by the popup service automatically - same as dynamic-dialog
|
|
1690
|
+
this.callBack = () => { };
|
|
1691
|
+
this.isDialogLoading = signal(false, ...(ngDevMode ? [{ debugName: "isDialogLoading" }] : []));
|
|
1692
|
+
}
|
|
1693
|
+
ngOnInit() {
|
|
1694
|
+
// Initialize context with provided context
|
|
1695
|
+
this.context.set(this.config?.context || {});
|
|
1696
|
+
}
|
|
1697
|
+
handleContextChanged(event) {
|
|
1698
|
+
this.context.set(event);
|
|
1699
|
+
}
|
|
1700
|
+
handleContextInitiated(event) {
|
|
1701
|
+
this.context.set(event);
|
|
1702
|
+
}
|
|
1703
|
+
visibleFooterPrefixActions() {
|
|
1704
|
+
return this.config?.actions?.footer?.prefix || [];
|
|
1705
|
+
}
|
|
1706
|
+
visibleFooterSuffixActions() {
|
|
1707
|
+
return this.config?.actions?.footer?.suffix || [
|
|
1708
|
+
{ title: 'Cancel', color: 'secondary', command: { name: 'cancel' } },
|
|
1709
|
+
{ title: 'Confirm', color: 'primary', command: { name: 'confirm' } }
|
|
1710
|
+
];
|
|
1711
|
+
}
|
|
1712
|
+
isFormLoading() {
|
|
1713
|
+
return this.isDialogLoading();
|
|
1714
|
+
}
|
|
1715
|
+
isSubmitting() {
|
|
1716
|
+
return this.isDialogLoading();
|
|
1717
|
+
}
|
|
1718
|
+
async executeAction(action) {
|
|
1719
|
+
const actionName = action.command?.name || action.title?.toLowerCase();
|
|
1720
|
+
// Store the action and context - same pattern as dynamic-dialog
|
|
1721
|
+
const result = {
|
|
1722
|
+
context: this.context(),
|
|
1723
|
+
action: actionName
|
|
1724
|
+
};
|
|
1725
|
+
// Store result in component property
|
|
1726
|
+
this.dialogResult = result;
|
|
1727
|
+
// Store in popup data for DialogRef access
|
|
1728
|
+
if (this.data) {
|
|
1729
|
+
this.data.context = result.context;
|
|
1730
|
+
this.data.action = result.action;
|
|
1731
|
+
}
|
|
1732
|
+
// Call the callback with DialogRef - same pattern as dynamic-dialog
|
|
1733
|
+
this.callBack({
|
|
1734
|
+
close: (result) => {
|
|
1735
|
+
this.close(result);
|
|
1736
|
+
},
|
|
1737
|
+
context: () => {
|
|
1738
|
+
return this.context();
|
|
1739
|
+
},
|
|
1740
|
+
action: () => {
|
|
1741
|
+
return result.action;
|
|
1742
|
+
},
|
|
1743
|
+
setLoading: (loading) => {
|
|
1744
|
+
this.isDialogLoading.set(loading);
|
|
1745
|
+
}
|
|
1746
|
+
});
|
|
1747
|
+
}
|
|
1748
|
+
close(result) {
|
|
1749
|
+
if (result) {
|
|
1750
|
+
this.result.emit(result);
|
|
1751
|
+
}
|
|
1752
|
+
super.close(result);
|
|
1753
|
+
}
|
|
1754
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXPDialogRendererComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
1755
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.3", type: AXPDialogRendererComponent, isStandalone: true, selector: "axp-dialog-renderer", inputs: { config: "config" }, outputs: { result: "result" }, usesInheritance: true, ngImport: i0, template: `
|
|
1756
|
+
<div class="ax-p-4">
|
|
1757
|
+
<axp-layout-renderer
|
|
1758
|
+
[layout]="config.definition"
|
|
1759
|
+
[context]="context()"
|
|
1760
|
+
(contextChange)="handleContextChanged($event)"
|
|
1761
|
+
(contextInitiated)="handleContextInitiated($event)"
|
|
1762
|
+
>
|
|
1763
|
+
</axp-layout-renderer>
|
|
1764
|
+
</div>
|
|
1765
|
+
|
|
1766
|
+
<ax-footer>
|
|
1767
|
+
<ax-prefix>
|
|
1768
|
+
<ng-container *ngTemplateOutlet="footerPrefixActions"></ng-container>
|
|
1769
|
+
</ax-prefix>
|
|
1770
|
+
<ax-suffix>
|
|
1771
|
+
<ng-container *ngTemplateOutlet="footerSuffixActions"></ng-container>
|
|
1772
|
+
</ax-suffix>
|
|
1773
|
+
</ax-footer>
|
|
1774
|
+
|
|
1775
|
+
<!-- Footer Prefix Actions -->
|
|
1776
|
+
<ng-template #footerPrefixActions>
|
|
1777
|
+
@for (action of visibleFooterPrefixActions(); track $index) {
|
|
1778
|
+
<ax-button
|
|
1779
|
+
[disabled]="action.disabled || isFormLoading()"
|
|
1780
|
+
[text]="(action.title | translate | async)!"
|
|
1781
|
+
[look]="'outline'"
|
|
1782
|
+
[color]="action.color"
|
|
1783
|
+
(onClick)="executeAction(action)"
|
|
1784
|
+
>
|
|
1785
|
+
@if (isFormLoading() && action.command?.name != 'cancel') {
|
|
1786
|
+
<ax-loading></ax-loading>
|
|
1787
|
+
}
|
|
1788
|
+
<ax-prefix>
|
|
1789
|
+
<i class="{{ action.icon }}"></i>
|
|
1790
|
+
</ax-prefix>
|
|
1791
|
+
</ax-button>
|
|
1792
|
+
}
|
|
1793
|
+
</ng-template>
|
|
1794
|
+
|
|
1795
|
+
<!-- Footer Suffix Actions -->
|
|
1796
|
+
<ng-template #footerSuffixActions>
|
|
1797
|
+
@for (action of visibleFooterSuffixActions(); track $index) {
|
|
1798
|
+
<ax-button
|
|
1799
|
+
[disabled]="action.disabled || isSubmitting()"
|
|
1800
|
+
[text]="(action.title | translate | async)!"
|
|
1801
|
+
[look]="'solid'"
|
|
1802
|
+
[color]="action.color"
|
|
1803
|
+
(onClick)="executeAction(action)"
|
|
1804
|
+
>
|
|
1805
|
+
@if (action.icon) {
|
|
1806
|
+
<ax-prefix>
|
|
1807
|
+
<ax-icon icon="{{ action.icon }}"></ax-icon>
|
|
1808
|
+
</ax-prefix>
|
|
1809
|
+
}
|
|
1810
|
+
</ax-button>
|
|
1811
|
+
}
|
|
1812
|
+
</ng-template>
|
|
1813
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: AXPLayoutRendererComponent, selector: "axp-layout-renderer", inputs: ["layout", "context", "look", "mode"], outputs: ["contextChange", "contextInitiated", "validityChange"] }, { kind: "ngmodule", type: AXButtonModule }, { kind: "component", type: i2$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: i3.AXDecoratorIconComponent, selector: "ax-icon", inputs: ["icon"] }, { kind: "component", type: i3.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: i4.AXLoadingComponent, selector: "ax-loading", inputs: ["visible", "type", "context"], outputs: ["visibleChange"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i5.AXTranslatorPipe, name: "translate" }] }); }
|
|
1814
|
+
}
|
|
1815
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXPDialogRendererComponent, decorators: [{
|
|
1816
|
+
type: Component,
|
|
1817
|
+
args: [{
|
|
1818
|
+
selector: 'axp-dialog-renderer',
|
|
1819
|
+
standalone: true,
|
|
1820
|
+
imports: [CommonModule, AXPLayoutRendererComponent, AXButtonModule, AXDecoratorModule, AXLoadingModule, AXTranslationModule],
|
|
1821
|
+
template: `
|
|
1822
|
+
<div class="ax-p-4">
|
|
1823
|
+
<axp-layout-renderer
|
|
1824
|
+
[layout]="config.definition"
|
|
1825
|
+
[context]="context()"
|
|
1826
|
+
(contextChange)="handleContextChanged($event)"
|
|
1827
|
+
(contextInitiated)="handleContextInitiated($event)"
|
|
1828
|
+
>
|
|
1829
|
+
</axp-layout-renderer>
|
|
1830
|
+
</div>
|
|
1831
|
+
|
|
1832
|
+
<ax-footer>
|
|
1833
|
+
<ax-prefix>
|
|
1834
|
+
<ng-container *ngTemplateOutlet="footerPrefixActions"></ng-container>
|
|
1835
|
+
</ax-prefix>
|
|
1836
|
+
<ax-suffix>
|
|
1837
|
+
<ng-container *ngTemplateOutlet="footerSuffixActions"></ng-container>
|
|
1838
|
+
</ax-suffix>
|
|
1839
|
+
</ax-footer>
|
|
1840
|
+
|
|
1841
|
+
<!-- Footer Prefix Actions -->
|
|
1842
|
+
<ng-template #footerPrefixActions>
|
|
1843
|
+
@for (action of visibleFooterPrefixActions(); track $index) {
|
|
1844
|
+
<ax-button
|
|
1845
|
+
[disabled]="action.disabled || isFormLoading()"
|
|
1846
|
+
[text]="(action.title | translate | async)!"
|
|
1847
|
+
[look]="'outline'"
|
|
1848
|
+
[color]="action.color"
|
|
1849
|
+
(onClick)="executeAction(action)"
|
|
1850
|
+
>
|
|
1851
|
+
@if (isFormLoading() && action.command?.name != 'cancel') {
|
|
1852
|
+
<ax-loading></ax-loading>
|
|
1853
|
+
}
|
|
1854
|
+
<ax-prefix>
|
|
1855
|
+
<i class="{{ action.icon }}"></i>
|
|
1856
|
+
</ax-prefix>
|
|
1857
|
+
</ax-button>
|
|
1858
|
+
}
|
|
1859
|
+
</ng-template>
|
|
1860
|
+
|
|
1861
|
+
<!-- Footer Suffix Actions -->
|
|
1862
|
+
<ng-template #footerSuffixActions>
|
|
1863
|
+
@for (action of visibleFooterSuffixActions(); track $index) {
|
|
1864
|
+
<ax-button
|
|
1865
|
+
[disabled]="action.disabled || isSubmitting()"
|
|
1866
|
+
[text]="(action.title | translate | async)!"
|
|
1867
|
+
[look]="'solid'"
|
|
1868
|
+
[color]="action.color"
|
|
1869
|
+
(onClick)="executeAction(action)"
|
|
1870
|
+
>
|
|
1871
|
+
@if (action.icon) {
|
|
1872
|
+
<ax-prefix>
|
|
1873
|
+
<ax-icon icon="{{ action.icon }}"></ax-icon>
|
|
1874
|
+
</ax-prefix>
|
|
1875
|
+
}
|
|
1876
|
+
</ax-button>
|
|
1877
|
+
}
|
|
1878
|
+
</ng-template>
|
|
1879
|
+
`
|
|
1880
|
+
}]
|
|
1881
|
+
}], propDecorators: { config: [{
|
|
1882
|
+
type: Input
|
|
1883
|
+
}], result: [{
|
|
1884
|
+
type: Output
|
|
1885
|
+
}] } });
|
|
1886
|
+
|
|
1887
|
+
var dialogRenderer_component = /*#__PURE__*/Object.freeze({
|
|
1888
|
+
__proto__: null,
|
|
1889
|
+
AXPDialogRendererComponent: AXPDialogRendererComponent
|
|
1890
|
+
});
|
|
1891
|
+
|
|
1326
1892
|
/**
|
|
1327
1893
|
* Generated bundle index. Do not edit.
|
|
1328
1894
|
*/
|
|
1329
1895
|
|
|
1330
|
-
export { AXPLayoutBuilderService, AXPLayoutRendererComponent, LayoutBuilderModule };
|
|
1896
|
+
export { AXPDialogRendererComponent, AXPLayoutBuilderService, AXPLayoutConversionService, AXPLayoutRendererComponent, LayoutBuilderModule };
|
|
1331
1897
|
//# sourceMappingURL=acorex-platform-layout-builder.mjs.map
|