@acorex/platform 20.3.0-next.14 → 20.3.0-next.16
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 +1690 -1030
- 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 +160 -161
- package/fesm2022/acorex-platform-layout-entity.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-views.mjs +15 -15
- 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 +697 -664
- 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 +458 -237
- package/layout/components/index.d.ts +130 -42
- package/layout/widget-core/index.d.ts +8 -1
- package/layout/widgets/index.d.ts +80 -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,521 @@
|
|
|
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',
|
|
244
|
+
'large-text-editor',
|
|
245
|
+
'rich-text-editor',
|
|
246
|
+
'password-editor',
|
|
247
|
+
'number-editor',
|
|
248
|
+
'select-editor',
|
|
249
|
+
'lookup-editor',
|
|
250
|
+
'selection-list-editor',
|
|
251
|
+
'date-time-editor',
|
|
252
|
+
'toggle-editor',
|
|
253
|
+
'color-editor',
|
|
254
|
+
];
|
|
255
|
+
return valueWidgetTypes.includes(type);
|
|
272
256
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
257
|
+
build() {
|
|
258
|
+
const result = {
|
|
259
|
+
type: this.containerState.type,
|
|
260
|
+
children: this.containerState.children,
|
|
261
|
+
options: this.containerState.options,
|
|
262
|
+
mode: this.containerState.mode,
|
|
263
|
+
visible: this.containerState.visible,
|
|
264
|
+
};
|
|
265
|
+
// Add name with _form_field suffix for form fields
|
|
266
|
+
if (this.containerState.type === 'form-field') {
|
|
267
|
+
if (this.containerState.name) {
|
|
268
|
+
result.name = this.containerState.name;
|
|
269
|
+
}
|
|
270
|
+
else if (this.containerState.options?.['label']) {
|
|
271
|
+
result.name = labelToPath(this.containerState.options['label']) + '_form_field';
|
|
272
|
+
}
|
|
273
|
+
// Form fields don't have path
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
// Other containers can have name and path
|
|
277
|
+
if (this.containerState.name) {
|
|
278
|
+
result.name = this.containerState.name;
|
|
279
|
+
}
|
|
280
|
+
if (this.containerState.path) {
|
|
281
|
+
result.path = this.containerState.path;
|
|
282
|
+
}
|
|
283
|
+
if (this.containerState.defaultValue !== undefined) {
|
|
284
|
+
result.defaultValue = this.containerState.defaultValue;
|
|
285
|
+
}
|
|
282
286
|
}
|
|
283
|
-
|
|
284
|
-
return `<span style="color: #333;">${options.content}</span>`;
|
|
287
|
+
return result;
|
|
285
288
|
}
|
|
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
289
|
}
|
|
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());
|
|
290
|
+
/**
|
|
291
|
+
* Base container builder mixin - Interface Segregation Principle
|
|
292
|
+
* Provides common container operations
|
|
293
|
+
*/
|
|
294
|
+
class BaseContainerMixin extends BaseContainerBuilder {
|
|
295
|
+
name(name) {
|
|
296
|
+
this.containerState.name = name;
|
|
320
297
|
return this;
|
|
321
298
|
}
|
|
322
|
-
|
|
323
|
-
this.
|
|
299
|
+
path(path) {
|
|
300
|
+
this.containerState.path = path;
|
|
324
301
|
return this;
|
|
325
302
|
}
|
|
326
303
|
mode(mode) {
|
|
327
|
-
this.
|
|
304
|
+
this.containerState.mode = mode;
|
|
305
|
+
this.inheritanceContext.mode = mode;
|
|
306
|
+
return this;
|
|
307
|
+
}
|
|
308
|
+
visible(condition) {
|
|
309
|
+
if (!this.containerState.options)
|
|
310
|
+
this.containerState.options = {};
|
|
311
|
+
this.containerState.options['visible'] = condition;
|
|
312
|
+
this.inheritanceContext.visible = condition;
|
|
313
|
+
return this;
|
|
314
|
+
}
|
|
315
|
+
disabled(condition) {
|
|
316
|
+
if (!this.containerState.options)
|
|
317
|
+
this.containerState.options = {};
|
|
318
|
+
this.containerState.options['disabled'] = condition;
|
|
319
|
+
this.inheritanceContext.disabled = condition;
|
|
320
|
+
return this;
|
|
321
|
+
}
|
|
322
|
+
readonly(condition) {
|
|
323
|
+
if (!this.containerState.options)
|
|
324
|
+
this.containerState.options = {};
|
|
325
|
+
this.containerState.options['readonly'] = condition;
|
|
326
|
+
this.inheritanceContext.readonly = condition;
|
|
328
327
|
return this;
|
|
329
328
|
}
|
|
330
329
|
direction(direction) {
|
|
331
|
-
this.
|
|
330
|
+
if (!this.containerState.options)
|
|
331
|
+
this.containerState.options = {};
|
|
332
|
+
this.containerState.options['direction'] = direction;
|
|
333
|
+
this.inheritanceContext.direction = direction;
|
|
332
334
|
return this;
|
|
333
335
|
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
+
// Inheritance context methods
|
|
337
|
+
withInheritanceContext(context) {
|
|
338
|
+
this.inheritanceContext = mergeInheritanceContext(context);
|
|
336
339
|
return this;
|
|
337
340
|
}
|
|
338
|
-
|
|
341
|
+
getInheritanceContext() {
|
|
342
|
+
return { ...this.inheritanceContext };
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Layout container mixin - Interface Segregation Principle
|
|
347
|
+
* Provides layout-specific operations
|
|
348
|
+
*/
|
|
349
|
+
class LayoutContainerMixin extends BaseContainerMixin {
|
|
350
|
+
layout(value) {
|
|
351
|
+
// Layout handling can be added here if needed
|
|
352
|
+
return this;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Child container mixin - Interface Segregation Principle
|
|
357
|
+
* Provides child container management
|
|
358
|
+
*/
|
|
359
|
+
class ChildContainerMixin extends LayoutContainerMixin {
|
|
339
360
|
grid(delegate) {
|
|
340
361
|
const container = new GridContainerBuilder();
|
|
362
|
+
container.withInheritanceContext(this.inheritanceContext);
|
|
341
363
|
if (delegate) {
|
|
342
364
|
delegate(container);
|
|
343
365
|
}
|
|
344
|
-
this.
|
|
366
|
+
this.ensureChildren();
|
|
367
|
+
this.containerState.children.push(container.build());
|
|
345
368
|
return this;
|
|
346
369
|
}
|
|
347
370
|
flex(delegate) {
|
|
348
371
|
const container = new FlexContainerBuilder();
|
|
372
|
+
container.withInheritanceContext(this.inheritanceContext);
|
|
349
373
|
if (delegate) {
|
|
350
374
|
delegate(container);
|
|
351
375
|
}
|
|
352
|
-
this.
|
|
376
|
+
this.ensureChildren();
|
|
377
|
+
this.containerState.children.push(container.build());
|
|
353
378
|
return this;
|
|
354
379
|
}
|
|
355
380
|
panel(delegate) {
|
|
356
381
|
const container = new PanelContainerBuilder();
|
|
382
|
+
container.withInheritanceContext(this.inheritanceContext);
|
|
357
383
|
if (delegate) {
|
|
358
384
|
delegate(container);
|
|
359
385
|
}
|
|
360
|
-
this.
|
|
386
|
+
this.ensureChildren();
|
|
387
|
+
this.containerState.children.push(container.build());
|
|
361
388
|
return this;
|
|
362
389
|
}
|
|
363
390
|
page(delegate) {
|
|
364
391
|
const container = new PageContainerBuilder();
|
|
392
|
+
container.withInheritanceContext(this.inheritanceContext);
|
|
365
393
|
if (delegate) {
|
|
366
394
|
delegate(container);
|
|
367
395
|
}
|
|
368
|
-
this.
|
|
396
|
+
this.ensureChildren();
|
|
397
|
+
this.containerState.children.push(container.build());
|
|
369
398
|
return this;
|
|
370
399
|
}
|
|
371
400
|
tabset(delegate) {
|
|
372
401
|
const container = new TabsetContainerBuilder();
|
|
402
|
+
container.withInheritanceContext(this.inheritanceContext);
|
|
403
|
+
if (delegate) {
|
|
404
|
+
delegate(container);
|
|
405
|
+
}
|
|
406
|
+
this.ensureChildren();
|
|
407
|
+
this.containerState.children.push(container.build());
|
|
408
|
+
return this;
|
|
409
|
+
}
|
|
410
|
+
fieldset(delegate) {
|
|
411
|
+
const container = new FieldsetContainerBuilder();
|
|
412
|
+
container.withInheritanceContext(this.inheritanceContext);
|
|
413
|
+
if (delegate) {
|
|
414
|
+
delegate(container);
|
|
415
|
+
}
|
|
416
|
+
this.ensureChildren();
|
|
417
|
+
this.containerState.children.push(container.build());
|
|
418
|
+
return this;
|
|
419
|
+
}
|
|
420
|
+
dialog(delegate) {
|
|
421
|
+
const container = new DialogContainerBuilder(); // Will use inject() fallback
|
|
373
422
|
if (delegate) {
|
|
374
423
|
delegate(container);
|
|
375
424
|
}
|
|
376
|
-
|
|
425
|
+
return container;
|
|
426
|
+
}
|
|
427
|
+
formField(label, delegate) {
|
|
428
|
+
const field = new FormFieldBuilder(label);
|
|
429
|
+
field.withInheritanceContext(this.inheritanceContext);
|
|
430
|
+
if (delegate) {
|
|
431
|
+
delegate(field);
|
|
432
|
+
}
|
|
433
|
+
this.ensureChildren();
|
|
434
|
+
this.containerState.children.push(field.build());
|
|
377
435
|
return this;
|
|
378
436
|
}
|
|
379
|
-
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Widget container mixin - Interface Segregation Principle
|
|
440
|
+
* Provides widget creation operations
|
|
441
|
+
*/
|
|
442
|
+
class WidgetContainerMixin extends ChildContainerMixin {
|
|
380
443
|
textBox(options) {
|
|
381
|
-
|
|
382
|
-
this.state.nodes.push(widgetBuilder.build());
|
|
444
|
+
this.addWidget('text-editor', options);
|
|
383
445
|
return this;
|
|
384
446
|
}
|
|
385
447
|
largeTextBox(options) {
|
|
386
|
-
|
|
387
|
-
this.state.nodes.push(widgetBuilder.build());
|
|
448
|
+
this.addWidget('large-text-editor', options);
|
|
388
449
|
return this;
|
|
389
450
|
}
|
|
390
451
|
richText(options) {
|
|
391
|
-
|
|
392
|
-
this.state.nodes.push(widgetBuilder.build());
|
|
452
|
+
this.addWidget('rich-text-editor', options);
|
|
393
453
|
return this;
|
|
394
454
|
}
|
|
395
455
|
passwordBox(options) {
|
|
396
|
-
|
|
397
|
-
this.state.nodes.push(widgetBuilder.build());
|
|
456
|
+
this.addWidget('password-editor', options);
|
|
398
457
|
return this;
|
|
399
458
|
}
|
|
400
459
|
numberBox(options) {
|
|
401
|
-
|
|
402
|
-
this.state.nodes.push(widgetBuilder.build());
|
|
460
|
+
this.addWidget('number-editor', options);
|
|
403
461
|
return this;
|
|
404
462
|
}
|
|
405
463
|
selectBox(options) {
|
|
406
|
-
|
|
407
|
-
this.state.nodes.push(widgetBuilder.build());
|
|
464
|
+
this.addWidget('select-editor', options);
|
|
408
465
|
return this;
|
|
409
466
|
}
|
|
410
467
|
lookupBox(options) {
|
|
411
|
-
|
|
412
|
-
this.state.nodes.push(widgetBuilder.build());
|
|
468
|
+
this.addWidget('lookup-editor', options);
|
|
413
469
|
return this;
|
|
414
470
|
}
|
|
415
471
|
selectionList(options) {
|
|
416
|
-
|
|
417
|
-
this.state.nodes.push(widgetBuilder.build());
|
|
472
|
+
this.addWidget('selection-list-editor', options);
|
|
418
473
|
return this;
|
|
419
474
|
}
|
|
420
475
|
dateTimeBox(options) {
|
|
421
|
-
|
|
422
|
-
this.state.nodes.push(widgetBuilder.build());
|
|
476
|
+
this.addWidget('date-time-editor', options);
|
|
423
477
|
return this;
|
|
424
478
|
}
|
|
425
479
|
toggleSwitch(options) {
|
|
426
|
-
|
|
427
|
-
this.state.nodes.push(widgetBuilder.build());
|
|
480
|
+
this.addWidget('toggle-editor', options);
|
|
428
481
|
return this;
|
|
429
482
|
}
|
|
430
483
|
colorBox(options) {
|
|
431
|
-
|
|
432
|
-
this.state.nodes.push(widgetBuilder.build());
|
|
484
|
+
this.addWidget('color-editor', options);
|
|
433
485
|
return this;
|
|
434
486
|
}
|
|
435
|
-
|
|
436
|
-
const
|
|
437
|
-
|
|
487
|
+
list(delegate) {
|
|
488
|
+
const container = new ListWidgetBuilder();
|
|
489
|
+
container.withInheritanceContext(this.inheritanceContext);
|
|
490
|
+
if (delegate) {
|
|
491
|
+
delegate(container);
|
|
492
|
+
}
|
|
493
|
+
this.ensureChildren();
|
|
494
|
+
this.containerState.children.push(container.build());
|
|
438
495
|
return this;
|
|
439
496
|
}
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
mode: this.state.mode,
|
|
444
|
-
direction: this.state.direction,
|
|
445
|
-
look: this.state.look,
|
|
446
|
-
layoutConfig: this.state.layoutConfig,
|
|
447
|
-
};
|
|
497
|
+
customWidget(type, options) {
|
|
498
|
+
this.addWidget(type, options);
|
|
499
|
+
return this;
|
|
448
500
|
}
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
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
|
-
});
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* Flex Container Builder - Liskov Substitution Principle
|
|
504
|
+
* Extends WidgetContainerMixin to inherit all common functionality
|
|
505
|
+
*/
|
|
506
|
+
class FlexContainerBuilder extends WidgetContainerMixin {
|
|
507
|
+
constructor() {
|
|
508
|
+
super('flex-layout');
|
|
535
509
|
}
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
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
|
-
}
|
|
560
|
-
//#endregion
|
|
561
|
-
//#region ---- Container Builder Implementation ----
|
|
562
|
-
// Base Container Builder
|
|
563
|
-
class BaseContainerBuilder {
|
|
564
|
-
constructor(containerType) {
|
|
565
|
-
this.containerState = {
|
|
566
|
-
type: 'widget',
|
|
567
|
-
children: [],
|
|
568
|
-
};
|
|
569
|
-
this.containerState.widget = { type: containerType, options: {} };
|
|
570
|
-
}
|
|
571
|
-
baseId(id) {
|
|
572
|
-
this.containerState.id = id;
|
|
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 };
|
|
651
|
-
return this;
|
|
510
|
+
setOptions(options) {
|
|
511
|
+
this.containerState.options = { ...this.containerState.options, ...options };
|
|
512
|
+
return this;
|
|
652
513
|
}
|
|
653
514
|
// Individual fluent methods for Flex
|
|
654
|
-
|
|
515
|
+
setDirection(direction) {
|
|
655
516
|
return this.setOptions({ flexDirection: direction });
|
|
656
517
|
}
|
|
657
|
-
|
|
518
|
+
setWrap(wrap) {
|
|
658
519
|
return this.setOptions({ flexWrap: wrap });
|
|
659
520
|
}
|
|
660
521
|
setJustifyContent(justify) {
|
|
@@ -675,70 +536,17 @@ class FlexContainerBuilder extends BaseContainerBuilder {
|
|
|
675
536
|
setMargin(margin) {
|
|
676
537
|
return this.setOptions({ spacing: { margin } });
|
|
677
538
|
}
|
|
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
539
|
}
|
|
728
|
-
|
|
729
|
-
|
|
540
|
+
/**
|
|
541
|
+
* Grid Container Builder - Liskov Substitution Principle
|
|
542
|
+
* Extends WidgetContainerMixin to inherit all common functionality
|
|
543
|
+
*/
|
|
544
|
+
class GridContainerBuilder extends WidgetContainerMixin {
|
|
730
545
|
constructor() {
|
|
731
546
|
super('grid-layout');
|
|
732
547
|
}
|
|
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
548
|
setOptions(options) {
|
|
741
|
-
this.containerState.
|
|
549
|
+
this.containerState.options = { ...this.containerState.options, ...options };
|
|
742
550
|
return this;
|
|
743
551
|
}
|
|
744
552
|
// Individual fluent methods for Grid
|
|
@@ -769,42 +577,17 @@ class GridContainerBuilder extends BaseContainerBuilder {
|
|
|
769
577
|
setMargin(margin) {
|
|
770
578
|
return this.setOptions({ spacing: { margin } });
|
|
771
579
|
}
|
|
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
580
|
}
|
|
794
|
-
|
|
795
|
-
|
|
581
|
+
/**
|
|
582
|
+
* Panel Container Builder - Liskov Substitution Principle
|
|
583
|
+
* Extends WidgetContainerMixin to inherit all common functionality
|
|
584
|
+
*/
|
|
585
|
+
class PanelContainerBuilder extends WidgetContainerMixin {
|
|
796
586
|
constructor() {
|
|
797
587
|
super('panel-layout');
|
|
798
588
|
}
|
|
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
589
|
setOptions(options) {
|
|
807
|
-
this.containerState.
|
|
590
|
+
this.containerState.options = { ...this.containerState.options, ...options };
|
|
808
591
|
return this;
|
|
809
592
|
}
|
|
810
593
|
// Individual fluent methods for Panel
|
|
@@ -823,42 +606,17 @@ class PanelContainerBuilder extends BaseContainerBuilder {
|
|
|
823
606
|
setCollapsed(collapsed) {
|
|
824
607
|
return this.setOptions({ collapsed });
|
|
825
608
|
}
|
|
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
609
|
}
|
|
848
|
-
|
|
849
|
-
|
|
610
|
+
/**
|
|
611
|
+
* Page Container Builder - Liskov Substitution Principle
|
|
612
|
+
* Extends WidgetContainerMixin to inherit all common functionality
|
|
613
|
+
*/
|
|
614
|
+
class PageContainerBuilder extends WidgetContainerMixin {
|
|
850
615
|
constructor() {
|
|
851
616
|
super('page-layout');
|
|
852
617
|
}
|
|
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
618
|
setOptions(options) {
|
|
861
|
-
this.containerState.
|
|
619
|
+
this.containerState.options = { ...this.containerState.options, ...options };
|
|
862
620
|
return this;
|
|
863
621
|
}
|
|
864
622
|
// Individual fluent methods for Page
|
|
@@ -877,42 +635,17 @@ class PageContainerBuilder extends BaseContainerBuilder {
|
|
|
877
635
|
setDirection(direction) {
|
|
878
636
|
return this.setOptions({ direction });
|
|
879
637
|
}
|
|
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
638
|
}
|
|
902
|
-
|
|
903
|
-
|
|
639
|
+
/**
|
|
640
|
+
* Tabset Container Builder - Liskov Substitution Principle
|
|
641
|
+
* Extends WidgetContainerMixin to inherit all common functionality
|
|
642
|
+
*/
|
|
643
|
+
class TabsetContainerBuilder extends WidgetContainerMixin {
|
|
904
644
|
constructor() {
|
|
905
645
|
super('tabset-layout');
|
|
906
646
|
}
|
|
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
647
|
setOptions(options) {
|
|
915
|
-
this.containerState.
|
|
648
|
+
this.containerState.options = { ...this.containerState.options, ...options };
|
|
916
649
|
return this;
|
|
917
650
|
}
|
|
918
651
|
// Individual fluent methods for Tabset
|
|
@@ -925,396 +658,1113 @@ class TabsetContainerBuilder extends BaseContainerBuilder {
|
|
|
925
658
|
setActiveIndex(index) {
|
|
926
659
|
return this.setOptions({ activeIndex: index });
|
|
927
660
|
}
|
|
928
|
-
addContainer(name, delegate) { return this; }
|
|
929
|
-
addWidget(name, delegate) {
|
|
930
|
-
const childWidget = new WidgetBuilder(name);
|
|
931
|
-
if (delegate)
|
|
932
|
-
delegate(childWidget);
|
|
933
|
-
this.ensureChildren();
|
|
934
|
-
this.containerState.children.push(childWidget.build());
|
|
935
|
-
return this;
|
|
936
|
-
}
|
|
937
|
-
textBox(options) { return this.addDirectWidget('text-editor', options); }
|
|
938
|
-
largeTextBox(options) { return this.addDirectWidget('large-text-editor', options); }
|
|
939
|
-
richText(options) { return this.addDirectWidget('rich-text-editor', options); }
|
|
940
|
-
passwordBox(options) { return this.addDirectWidget('password-editor', options); }
|
|
941
|
-
numberBox(options) { return this.addDirectWidget('number-editor', options); }
|
|
942
|
-
selectBox(options) { return this.addDirectWidget('select-editor', options); }
|
|
943
|
-
lookupBox(options) { return this.addDirectWidget('lookup-editor', options); }
|
|
944
|
-
selectionList(options) { return this.addDirectWidget('selection-list', options); }
|
|
945
|
-
dateTimeBox(options) { return this.addDirectWidget('date-time-editor', options); }
|
|
946
|
-
toggleSwitch(options) { return this.addDirectWidget('toggle-editor', options); }
|
|
947
|
-
colorBox(options) { return this.addDirectWidget('color-editor', options); }
|
|
948
|
-
customWidget(type, options) { return this.addDirectWidget(type, options); }
|
|
949
661
|
}
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
this.widgetState.name = name;
|
|
960
|
-
}
|
|
961
|
-
}
|
|
962
|
-
// No utility methods needed - widget options are pure now
|
|
963
|
-
isContainerWidgetType(type) {
|
|
964
|
-
if (!type)
|
|
965
|
-
return false;
|
|
966
|
-
return ['grid-layout', 'flex-layout', 'panel-layout', 'page-layout', 'tabset-layout'].includes(type);
|
|
967
|
-
}
|
|
968
|
-
id(id) {
|
|
969
|
-
this.widgetState.id = id;
|
|
970
|
-
return this;
|
|
971
|
-
}
|
|
972
|
-
layout(value) {
|
|
973
|
-
if (typeof value === 'number') {
|
|
974
|
-
this.widgetState.layout = {
|
|
975
|
-
positions: {
|
|
976
|
-
sm: { colSpan: 12 },
|
|
977
|
-
md: { colSpan: 12 },
|
|
978
|
-
lg: { colSpan: value },
|
|
979
|
-
xl: { colSpan: value },
|
|
980
|
-
xxl: { colSpan: value },
|
|
981
|
-
}
|
|
982
|
-
};
|
|
983
|
-
}
|
|
984
|
-
else {
|
|
985
|
-
this.widgetState.layout = value;
|
|
986
|
-
}
|
|
987
|
-
return this;
|
|
988
|
-
}
|
|
989
|
-
mode(mode) {
|
|
990
|
-
this.widgetState.mode = mode;
|
|
991
|
-
return this;
|
|
992
|
-
}
|
|
993
|
-
visible(condition) {
|
|
994
|
-
this.widgetState.visible = condition;
|
|
995
|
-
return this;
|
|
662
|
+
/**
|
|
663
|
+
* Form Field Builder - Liskov Substitution Principle
|
|
664
|
+
* Can only contain ONE widget with automatic path generation
|
|
665
|
+
*/
|
|
666
|
+
class FormFieldBuilder extends LayoutContainerMixin {
|
|
667
|
+
constructor(label) {
|
|
668
|
+
super('form-field');
|
|
669
|
+
this.hasWidget = false;
|
|
670
|
+
this.containerState.options = { label, showLabel: true };
|
|
996
671
|
}
|
|
997
|
-
|
|
998
|
-
this.
|
|
672
|
+
setOptions(options) {
|
|
673
|
+
this.containerState.options = { ...this.containerState.options, ...options };
|
|
999
674
|
return this;
|
|
1000
675
|
}
|
|
1001
|
-
|
|
1002
|
-
this.
|
|
1003
|
-
return this;
|
|
676
|
+
setLabel(label) {
|
|
677
|
+
return this.setOptions({ label });
|
|
1004
678
|
}
|
|
1005
|
-
|
|
1006
|
-
this.
|
|
1007
|
-
return this;
|
|
679
|
+
setShowLabel(showLabel) {
|
|
680
|
+
return this.setOptions({ showLabel });
|
|
1008
681
|
}
|
|
1009
|
-
//
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
delegate(container);
|
|
682
|
+
// Single widget methods with automatic path generation
|
|
683
|
+
addSingleWidget(type, options) {
|
|
684
|
+
if (this.hasWidget) {
|
|
685
|
+
throw new Error('Form field can only contain one widget');
|
|
1014
686
|
}
|
|
1015
|
-
const
|
|
1016
|
-
this.
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
delegate(container);
|
|
687
|
+
const formFieldName = this.containerState.name;
|
|
688
|
+
const formFieldPath = this.containerState.path; // Get explicit path from form field
|
|
689
|
+
const formFieldLabel = this.containerState.options?.['label'];
|
|
690
|
+
const widgetName = options?.name;
|
|
691
|
+
// Generate widget path: explicit path -> widget name -> form field name -> label -> random
|
|
692
|
+
let widgetPath;
|
|
693
|
+
if (formFieldPath) {
|
|
694
|
+
widgetPath = formFieldPath; // Use explicit form field path first
|
|
1024
695
|
}
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
this.widgetState.children = built.children;
|
|
1028
|
-
return this;
|
|
1029
|
-
}
|
|
1030
|
-
panel(delegate) {
|
|
1031
|
-
const container = new PanelContainerBuilder();
|
|
1032
|
-
if (delegate) {
|
|
1033
|
-
delegate(container);
|
|
696
|
+
else if (widgetName) {
|
|
697
|
+
widgetPath = widgetName;
|
|
1034
698
|
}
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
this.widgetState.children = built.children;
|
|
1038
|
-
return this;
|
|
1039
|
-
}
|
|
1040
|
-
page(delegate) {
|
|
1041
|
-
const container = new PageContainerBuilder();
|
|
1042
|
-
if (delegate) {
|
|
1043
|
-
delegate(container);
|
|
699
|
+
else if (formFieldName) {
|
|
700
|
+
widgetPath = formFieldName; // Use form field name as default path
|
|
1044
701
|
}
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
this.widgetState.children = built.children;
|
|
1048
|
-
return this;
|
|
1049
|
-
}
|
|
1050
|
-
tabset(delegate) {
|
|
1051
|
-
const container = new TabsetContainerBuilder();
|
|
1052
|
-
if (delegate) {
|
|
1053
|
-
delegate(container);
|
|
702
|
+
else if (formFieldLabel) {
|
|
703
|
+
widgetPath = labelToPath(formFieldLabel);
|
|
1054
704
|
}
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
this.widgetState.children = built.children;
|
|
1058
|
-
return this;
|
|
1059
|
-
}
|
|
1060
|
-
widget(name, delegate) {
|
|
1061
|
-
const child = new WidgetBuilder(name);
|
|
1062
|
-
if (delegate) {
|
|
1063
|
-
delegate(child);
|
|
705
|
+
else {
|
|
706
|
+
widgetPath = generateRandomId();
|
|
1064
707
|
}
|
|
1065
|
-
|
|
1066
|
-
|
|
708
|
+
const finalName = widgetName || formFieldName || widgetPath;
|
|
709
|
+
const child = new WidgetBuilder();
|
|
710
|
+
child.type(type);
|
|
711
|
+
child.name(finalName);
|
|
712
|
+
child.path(widgetPath);
|
|
713
|
+
// Remove name from options since it's now in state
|
|
714
|
+
const { name: _, ...cleanOptions } = (options || {});
|
|
715
|
+
child.options(cleanOptions);
|
|
716
|
+
child.withInheritanceContext(this.inheritanceContext);
|
|
717
|
+
this.ensureChildren();
|
|
718
|
+
this.containerState.children.push(child.build());
|
|
719
|
+
this.hasWidget = true;
|
|
1067
720
|
return this;
|
|
1068
721
|
}
|
|
1069
|
-
// Widget type methods
|
|
1070
722
|
textBox(options) {
|
|
1071
|
-
|
|
1072
|
-
const child = new WidgetBuilder();
|
|
1073
|
-
child.textBox(options);
|
|
1074
|
-
this.widgetState.children.push(child.build());
|
|
1075
|
-
return this;
|
|
1076
|
-
}
|
|
1077
|
-
else {
|
|
1078
|
-
this.widgetState.widget = {
|
|
1079
|
-
type: 'text-editor',
|
|
1080
|
-
options,
|
|
1081
|
-
};
|
|
1082
|
-
return this;
|
|
1083
|
-
}
|
|
723
|
+
return this.addSingleWidget('text-editor', options);
|
|
1084
724
|
}
|
|
1085
725
|
largeTextBox(options) {
|
|
1086
|
-
|
|
1087
|
-
const child = new WidgetBuilder();
|
|
1088
|
-
child.largeTextBox(options);
|
|
1089
|
-
this.widgetState.children.push(child.build());
|
|
1090
|
-
return this;
|
|
1091
|
-
}
|
|
1092
|
-
else {
|
|
1093
|
-
this.widgetState.widget = {
|
|
1094
|
-
type: 'large-text-editor',
|
|
1095
|
-
options,
|
|
1096
|
-
};
|
|
1097
|
-
return this;
|
|
1098
|
-
}
|
|
726
|
+
return this.addSingleWidget('large-text-editor', options);
|
|
1099
727
|
}
|
|
1100
728
|
richText(options) {
|
|
1101
|
-
|
|
1102
|
-
const child = new WidgetBuilder();
|
|
1103
|
-
child.richText(options);
|
|
1104
|
-
this.widgetState.children.push(child.build());
|
|
1105
|
-
return this;
|
|
1106
|
-
}
|
|
1107
|
-
else {
|
|
1108
|
-
this.widgetState.widget = {
|
|
1109
|
-
type: 'rich-text-editor',
|
|
1110
|
-
options,
|
|
1111
|
-
};
|
|
1112
|
-
return this;
|
|
1113
|
-
}
|
|
729
|
+
return this.addSingleWidget('rich-text-editor', options);
|
|
1114
730
|
}
|
|
1115
731
|
passwordBox(options) {
|
|
1116
|
-
|
|
1117
|
-
const child = new WidgetBuilder();
|
|
1118
|
-
child.passwordBox(options);
|
|
1119
|
-
this.widgetState.children.push(child.build());
|
|
1120
|
-
return this;
|
|
1121
|
-
}
|
|
1122
|
-
else {
|
|
1123
|
-
this.widgetState.widget = {
|
|
1124
|
-
type: 'password-editor',
|
|
1125
|
-
options,
|
|
1126
|
-
};
|
|
1127
|
-
return this;
|
|
1128
|
-
}
|
|
732
|
+
return this.addSingleWidget('password-editor', options);
|
|
1129
733
|
}
|
|
1130
734
|
numberBox(options) {
|
|
1131
|
-
|
|
1132
|
-
const child = new WidgetBuilder();
|
|
1133
|
-
child.numberBox(options);
|
|
1134
|
-
this.widgetState.children.push(child.build());
|
|
1135
|
-
return this;
|
|
1136
|
-
}
|
|
1137
|
-
else {
|
|
1138
|
-
this.widgetState.widget = {
|
|
1139
|
-
type: 'number-editor',
|
|
1140
|
-
options,
|
|
1141
|
-
};
|
|
1142
|
-
return this;
|
|
1143
|
-
}
|
|
735
|
+
return this.addSingleWidget('number-editor', options);
|
|
1144
736
|
}
|
|
1145
737
|
selectBox(options) {
|
|
1146
|
-
|
|
1147
|
-
const child = new WidgetBuilder();
|
|
1148
|
-
child.selectBox(options);
|
|
1149
|
-
this.widgetState.children.push(child.build());
|
|
1150
|
-
return this;
|
|
1151
|
-
}
|
|
1152
|
-
else {
|
|
1153
|
-
this.widgetState.widget = {
|
|
1154
|
-
type: 'select-editor',
|
|
1155
|
-
options,
|
|
1156
|
-
};
|
|
1157
|
-
return this;
|
|
1158
|
-
}
|
|
738
|
+
return this.addSingleWidget('select-editor', options);
|
|
1159
739
|
}
|
|
1160
740
|
lookupBox(options) {
|
|
1161
|
-
|
|
1162
|
-
const child = new WidgetBuilder();
|
|
1163
|
-
child.lookupBox(options);
|
|
1164
|
-
this.widgetState.children.push(child.build());
|
|
1165
|
-
return this;
|
|
1166
|
-
}
|
|
1167
|
-
else {
|
|
1168
|
-
this.widgetState.widget = {
|
|
1169
|
-
type: 'lookup-editor',
|
|
1170
|
-
options,
|
|
1171
|
-
};
|
|
1172
|
-
return this;
|
|
1173
|
-
}
|
|
741
|
+
return this.addSingleWidget('lookup-editor', options);
|
|
1174
742
|
}
|
|
1175
743
|
selectionList(options) {
|
|
1176
|
-
|
|
1177
|
-
const child = new WidgetBuilder();
|
|
1178
|
-
child.selectionList(options);
|
|
1179
|
-
this.widgetState.children.push(child.build());
|
|
1180
|
-
return this;
|
|
1181
|
-
}
|
|
1182
|
-
else {
|
|
1183
|
-
this.widgetState.widget = {
|
|
1184
|
-
type: 'selection-list',
|
|
1185
|
-
options,
|
|
1186
|
-
};
|
|
1187
|
-
return this;
|
|
1188
|
-
}
|
|
744
|
+
return this.addSingleWidget('selection-list-editor', options);
|
|
1189
745
|
}
|
|
1190
746
|
dateTimeBox(options) {
|
|
1191
|
-
|
|
1192
|
-
const child = new WidgetBuilder();
|
|
1193
|
-
child.dateTimeBox(options);
|
|
1194
|
-
this.widgetState.children.push(child.build());
|
|
1195
|
-
return this;
|
|
1196
|
-
}
|
|
1197
|
-
else {
|
|
1198
|
-
this.widgetState.widget = {
|
|
1199
|
-
type: 'date-time-editor',
|
|
1200
|
-
options,
|
|
1201
|
-
};
|
|
1202
|
-
return this;
|
|
1203
|
-
}
|
|
747
|
+
return this.addSingleWidget('date-time-editor', options);
|
|
1204
748
|
}
|
|
1205
749
|
toggleSwitch(options) {
|
|
1206
|
-
|
|
1207
|
-
const child = new WidgetBuilder();
|
|
1208
|
-
child.toggleSwitch(options);
|
|
1209
|
-
this.widgetState.children.push(child.build());
|
|
1210
|
-
return this;
|
|
1211
|
-
}
|
|
1212
|
-
else {
|
|
1213
|
-
this.widgetState.widget = {
|
|
1214
|
-
type: 'toggle-editor',
|
|
1215
|
-
options,
|
|
1216
|
-
};
|
|
1217
|
-
return this;
|
|
1218
|
-
}
|
|
750
|
+
return this.addSingleWidget('toggle-editor', options);
|
|
1219
751
|
}
|
|
1220
752
|
colorBox(options) {
|
|
1221
|
-
|
|
1222
|
-
const child = new WidgetBuilder();
|
|
1223
|
-
child.colorBox(options);
|
|
1224
|
-
this.widgetState.children.push(child.build());
|
|
1225
|
-
return this;
|
|
1226
|
-
}
|
|
1227
|
-
else {
|
|
1228
|
-
this.widgetState.widget = {
|
|
1229
|
-
type: 'color-editor',
|
|
1230
|
-
options,
|
|
1231
|
-
};
|
|
1232
|
-
return this;
|
|
1233
|
-
}
|
|
753
|
+
return this.addSingleWidget('color-editor', options);
|
|
1234
754
|
}
|
|
1235
755
|
customWidget(type, options) {
|
|
1236
|
-
|
|
1237
|
-
const child = new WidgetBuilder();
|
|
1238
|
-
child.customWidget(type, options);
|
|
1239
|
-
this.widgetState.children.push(child.build());
|
|
1240
|
-
return this;
|
|
1241
|
-
}
|
|
1242
|
-
else {
|
|
1243
|
-
this.widgetState.widget = {
|
|
1244
|
-
type,
|
|
1245
|
-
options: options,
|
|
1246
|
-
};
|
|
1247
|
-
return this;
|
|
1248
|
-
}
|
|
1249
|
-
}
|
|
1250
|
-
build() {
|
|
1251
|
-
return {
|
|
1252
|
-
id: this.widgetState.id,
|
|
1253
|
-
name: this.widgetState.name,
|
|
1254
|
-
widget: this.widgetState.widget,
|
|
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,
|
|
1262
|
-
};
|
|
756
|
+
return this.addSingleWidget(type, options);
|
|
1263
757
|
}
|
|
1264
758
|
}
|
|
1265
|
-
|
|
1266
|
-
|
|
759
|
+
/**
|
|
760
|
+
* Fieldset Container Builder - Liskov Substitution Principle
|
|
761
|
+
* Extends LayoutContainerMixin to inherit layout functionality
|
|
762
|
+
* Specialized for form fields only
|
|
763
|
+
*/
|
|
764
|
+
class FieldsetContainerBuilder extends LayoutContainerMixin {
|
|
1267
765
|
constructor() {
|
|
1268
|
-
|
|
766
|
+
super('fieldset-layout');
|
|
767
|
+
this.containerState.options = {};
|
|
1269
768
|
}
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
type: 'block-layout',
|
|
1274
|
-
mode: this.builder.mode ?? 'view',
|
|
1275
|
-
children: (this.builder.nodes ?? []).map((node) => ({
|
|
1276
|
-
type: node.widget?.type ?? 'text-block-layout',
|
|
1277
|
-
mode: node.mode ?? 'view',
|
|
1278
|
-
children: (node.children ?? []).map((child) => ({
|
|
1279
|
-
type: child.widget?.type ?? 'text-block-layout',
|
|
1280
|
-
mode: child.mode ?? 'view',
|
|
1281
|
-
})),
|
|
1282
|
-
})),
|
|
1283
|
-
};
|
|
1284
|
-
}
|
|
769
|
+
setOptions(options) {
|
|
770
|
+
this.containerState.options = { ...this.containerState.options, ...options };
|
|
771
|
+
return this;
|
|
1285
772
|
}
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
773
|
+
// Individual fluent methods for Fieldset
|
|
774
|
+
setTitle(title) {
|
|
775
|
+
return this.setOptions({ title });
|
|
776
|
+
}
|
|
777
|
+
setDescription(description) {
|
|
778
|
+
return this.setOptions({ description });
|
|
779
|
+
}
|
|
780
|
+
setIcon(icon) {
|
|
781
|
+
return this.setOptions({ icon });
|
|
782
|
+
}
|
|
783
|
+
setCollapsible(collapsible) {
|
|
784
|
+
return this.setOptions({ collapsible });
|
|
785
|
+
}
|
|
786
|
+
setIsOpen(isOpen) {
|
|
787
|
+
return this.setOptions({ isOpen });
|
|
788
|
+
}
|
|
789
|
+
setLook(look) {
|
|
790
|
+
return this.setOptions({ look });
|
|
791
|
+
}
|
|
792
|
+
setShowHeader(showHeader) {
|
|
793
|
+
return this.setOptions({ showHeader });
|
|
794
|
+
}
|
|
795
|
+
setCols(cols) {
|
|
796
|
+
return this.setOptions({ cols });
|
|
797
|
+
}
|
|
798
|
+
// Only form fields are allowed in fieldset
|
|
799
|
+
formField(label, delegate) {
|
|
800
|
+
const field = new FormFieldBuilder(label);
|
|
801
|
+
field.withInheritanceContext(this.inheritanceContext);
|
|
802
|
+
if (delegate) {
|
|
803
|
+
delegate(field);
|
|
804
|
+
}
|
|
805
|
+
this.ensureChildren();
|
|
806
|
+
this.containerState.children.push(field.build());
|
|
807
|
+
return this;
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
/**
|
|
811
|
+
* List Widget Builder - Liskov Substitution Principle
|
|
812
|
+
* Extends WidgetContainerMixin to inherit all common functionality
|
|
813
|
+
*/
|
|
814
|
+
class ListWidgetBuilder extends WidgetContainerMixin {
|
|
815
|
+
constructor() {
|
|
816
|
+
super('list');
|
|
817
|
+
}
|
|
818
|
+
setOptions(options) {
|
|
819
|
+
this.containerState.options = { ...this.containerState.options, ...options };
|
|
820
|
+
return this;
|
|
821
|
+
}
|
|
822
|
+
// Individual fluent methods for List Widget
|
|
823
|
+
setDataSource(dataSource) {
|
|
824
|
+
return this.setOptions({ dataSource });
|
|
825
|
+
}
|
|
826
|
+
setColumns(columns) {
|
|
827
|
+
return this.setOptions({ columns });
|
|
828
|
+
}
|
|
829
|
+
// Event handlers
|
|
830
|
+
setOnRowClick(handler) {
|
|
831
|
+
return this.setOptions({ onRowClick: handler });
|
|
832
|
+
}
|
|
833
|
+
setOnRowDoubleClick(handler) {
|
|
834
|
+
return this.setOptions({ onRowDoubleClick: handler });
|
|
835
|
+
}
|
|
836
|
+
setOnSelectionChange(handler) {
|
|
837
|
+
return this.setOptions({ onSelectionChange: handler });
|
|
838
|
+
}
|
|
839
|
+
setOnRowCommand(handler) {
|
|
840
|
+
return this.setOptions({ onRowCommand: handler });
|
|
841
|
+
}
|
|
842
|
+
// Table features
|
|
843
|
+
setPaging(paging) {
|
|
844
|
+
return this.setOptions({ paging });
|
|
845
|
+
}
|
|
846
|
+
setShowHeader(show) {
|
|
847
|
+
return this.setOptions({ showHeader: show });
|
|
848
|
+
}
|
|
849
|
+
setShowFooter(show) {
|
|
850
|
+
return this.setOptions({ showFooter: show });
|
|
851
|
+
}
|
|
852
|
+
setFixHeader(fix) {
|
|
853
|
+
return this.setOptions({ fixHeader: fix });
|
|
854
|
+
}
|
|
855
|
+
setFixFooter(fix) {
|
|
856
|
+
return this.setOptions({ fixFooter: fix });
|
|
857
|
+
}
|
|
858
|
+
setFetchDataMode(mode) {
|
|
859
|
+
return this.setOptions({ fetchDataMode: mode });
|
|
860
|
+
}
|
|
861
|
+
setParentField(field) {
|
|
862
|
+
return this.setOptions({ parentField: field });
|
|
863
|
+
}
|
|
864
|
+
setMinHeight(height) {
|
|
865
|
+
return this.setOptions({ minHeight: height });
|
|
866
|
+
}
|
|
867
|
+
// Selection & Index
|
|
868
|
+
setShowIndex(show) {
|
|
869
|
+
return this.setOptions({ showIndex: show });
|
|
870
|
+
}
|
|
871
|
+
setAllowSelection(allow) {
|
|
872
|
+
return this.setOptions({ allowSelection: allow });
|
|
873
|
+
}
|
|
874
|
+
// Commands
|
|
875
|
+
setPrimaryCommands(commands) {
|
|
876
|
+
return this.setOptions({ primaryCommands: commands });
|
|
877
|
+
}
|
|
878
|
+
setSecondaryCommands(commands) {
|
|
879
|
+
return this.setOptions({ secondaryCommands: commands });
|
|
880
|
+
}
|
|
881
|
+
// Loading
|
|
882
|
+
setLoading(loading) {
|
|
883
|
+
return this.setOptions({ loading });
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
/**
|
|
887
|
+
* Dialog Container Builder - Specialized for dialog functionality
|
|
888
|
+
* Uses composition instead of inheritance for cleaner separation
|
|
889
|
+
*/
|
|
890
|
+
class DialogContainerBuilder {
|
|
891
|
+
constructor(popupService) {
|
|
892
|
+
this.dialogState = {
|
|
893
|
+
type: 'flex-layout', // This will be overridden when content layout exists
|
|
894
|
+
children: [],
|
|
895
|
+
mode: 'edit',
|
|
896
|
+
dialogOptions: {
|
|
897
|
+
title: '',
|
|
898
|
+
size: 'md',
|
|
899
|
+
closeButton: false,
|
|
900
|
+
},
|
|
901
|
+
actions: {
|
|
902
|
+
footer: {
|
|
903
|
+
prefix: [],
|
|
904
|
+
suffix: [],
|
|
905
|
+
},
|
|
906
|
+
},
|
|
907
|
+
};
|
|
908
|
+
if (popupService) {
|
|
909
|
+
this.popupService = popupService;
|
|
910
|
+
}
|
|
911
|
+
else {
|
|
912
|
+
this.popupService = inject(AXPopupService);
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
setOptions(options) {
|
|
916
|
+
this.dialogState.dialogOptions = { ...this.dialogState.dialogOptions, ...options };
|
|
917
|
+
return this;
|
|
918
|
+
}
|
|
919
|
+
// Individual fluent methods for Dialog
|
|
920
|
+
setTitle(title) {
|
|
921
|
+
return this.setOptions({ title });
|
|
922
|
+
}
|
|
923
|
+
setMessage(message) {
|
|
924
|
+
return this.setOptions({ message });
|
|
925
|
+
}
|
|
926
|
+
setSize(size) {
|
|
927
|
+
return this.setOptions({ size });
|
|
928
|
+
}
|
|
929
|
+
setCloseButton(closeButton) {
|
|
930
|
+
return this.setOptions({ closeButton });
|
|
931
|
+
}
|
|
932
|
+
setContext(context) {
|
|
933
|
+
return this.setOptions({ context });
|
|
934
|
+
}
|
|
935
|
+
content(delegate) {
|
|
936
|
+
if (delegate) {
|
|
937
|
+
// Create a flex container directly instead of through LayoutBuilder
|
|
938
|
+
const flexContainer = new FlexContainerBuilder();
|
|
939
|
+
flexContainer.setDirection('column');
|
|
940
|
+
flexContainer.setGap('10px');
|
|
941
|
+
delegate(flexContainer);
|
|
942
|
+
this.contentLayout = flexContainer.build();
|
|
943
|
+
}
|
|
944
|
+
return this;
|
|
945
|
+
}
|
|
946
|
+
setActions(delegate) {
|
|
947
|
+
if (delegate) {
|
|
948
|
+
const actionBuilder = new ActionBuilder(this);
|
|
949
|
+
delegate(actionBuilder);
|
|
950
|
+
}
|
|
951
|
+
return this;
|
|
952
|
+
}
|
|
953
|
+
// Build method to create dialog node
|
|
954
|
+
build() {
|
|
955
|
+
// If we have content layout, use it directly to avoid extra wrapper
|
|
956
|
+
if (this.contentLayout) {
|
|
957
|
+
return {
|
|
958
|
+
...this.contentLayout,
|
|
959
|
+
// Add dialog-specific properties
|
|
960
|
+
options: {
|
|
961
|
+
...this.contentLayout.options,
|
|
962
|
+
...this.dialogState.dialogOptions,
|
|
963
|
+
},
|
|
964
|
+
};
|
|
965
|
+
}
|
|
966
|
+
// Fallback to dialog state structure if no content
|
|
967
|
+
const result = {
|
|
968
|
+
...this.dialogState,
|
|
969
|
+
children: [],
|
|
970
|
+
};
|
|
971
|
+
// Add dialog-specific properties
|
|
972
|
+
if (this.dialogState.dialogOptions) {
|
|
973
|
+
result.options = { ...result.options, ...this.dialogState.dialogOptions };
|
|
974
|
+
}
|
|
975
|
+
return result;
|
|
976
|
+
}
|
|
977
|
+
// Dialog-specific methods
|
|
978
|
+
async show() {
|
|
979
|
+
const dialogNode = this.build();
|
|
980
|
+
// Import the dialog renderer component dynamically
|
|
981
|
+
const { AXPDialogRendererComponent } = await Promise.resolve().then(function () { return dialogRenderer_component; });
|
|
982
|
+
// Create dialog configuration
|
|
983
|
+
const dialogConfig = {
|
|
984
|
+
title: this.dialogState.dialogOptions?.title || '',
|
|
985
|
+
message: this.dialogState.dialogOptions?.message,
|
|
986
|
+
context: this.dialogState.dialogOptions?.context || {},
|
|
987
|
+
definition: dialogNode,
|
|
988
|
+
actions: this.dialogState.actions,
|
|
989
|
+
};
|
|
990
|
+
// The Promise resolves when user clicks an action button
|
|
991
|
+
return new Promise(async (resolve) => {
|
|
992
|
+
this.popupService.open(AXPDialogRendererComponent, {
|
|
993
|
+
title: dialogConfig.title,
|
|
994
|
+
size: this.dialogState.dialogOptions?.size || 'md',
|
|
995
|
+
closeButton: this.dialogState.dialogOptions?.closeButton || false,
|
|
996
|
+
closeOnBackdropClick: false,
|
|
997
|
+
draggable: false,
|
|
998
|
+
data: {
|
|
999
|
+
config: dialogConfig,
|
|
1000
|
+
callBack: (result) => {
|
|
1001
|
+
// Resolve with the dialog reference when user clicks an action
|
|
1002
|
+
resolve(result);
|
|
1003
|
+
},
|
|
1004
|
+
},
|
|
1005
|
+
});
|
|
1006
|
+
});
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
//#endregion
|
|
1010
|
+
//#region ---- Widget Builder Implementation ----
|
|
1011
|
+
/**
|
|
1012
|
+
* Widget Builder - Single Responsibility Principle
|
|
1013
|
+
* Handles individual widget configuration and building
|
|
1014
|
+
*/
|
|
1015
|
+
class WidgetBuilder {
|
|
1016
|
+
constructor(name) {
|
|
1017
|
+
this.widgetState = {
|
|
1018
|
+
type: 'widget',
|
|
1019
|
+
options: {},
|
|
1020
|
+
};
|
|
1021
|
+
this.inheritanceContext = {};
|
|
1022
|
+
if (name) {
|
|
1023
|
+
this.widgetState.name = name;
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
type(type) {
|
|
1027
|
+
this.widgetState.type = type;
|
|
1028
|
+
return this;
|
|
1029
|
+
}
|
|
1030
|
+
name(name) {
|
|
1031
|
+
this.widgetState.name = name;
|
|
1032
|
+
if (!this.widgetState.path) {
|
|
1033
|
+
this.widgetState.path = name;
|
|
1034
|
+
}
|
|
1035
|
+
return this;
|
|
1036
|
+
}
|
|
1037
|
+
path(path) {
|
|
1038
|
+
this.widgetState.path = path;
|
|
1039
|
+
return this;
|
|
1040
|
+
}
|
|
1041
|
+
options(options) {
|
|
1042
|
+
this.widgetState.options = options;
|
|
1043
|
+
return this;
|
|
1044
|
+
}
|
|
1045
|
+
layout(value) {
|
|
1046
|
+
if (typeof value === 'number') {
|
|
1047
|
+
this.widgetState.layout = {
|
|
1048
|
+
positions: {
|
|
1049
|
+
sm: { colSpan: 12 },
|
|
1050
|
+
md: { colSpan: 12 },
|
|
1051
|
+
lg: { colSpan: value },
|
|
1052
|
+
xl: { colSpan: value },
|
|
1053
|
+
xxl: { colSpan: value },
|
|
1054
|
+
},
|
|
1055
|
+
};
|
|
1056
|
+
}
|
|
1057
|
+
else {
|
|
1058
|
+
this.widgetState.layout = value;
|
|
1059
|
+
}
|
|
1060
|
+
return this;
|
|
1061
|
+
}
|
|
1062
|
+
mode(mode) {
|
|
1063
|
+
this.widgetState.mode = mode;
|
|
1064
|
+
this.inheritanceContext.mode = mode;
|
|
1065
|
+
return this;
|
|
1066
|
+
}
|
|
1067
|
+
visible(condition) {
|
|
1068
|
+
if (!this.widgetState.options) {
|
|
1069
|
+
this.widgetState.options = {};
|
|
1070
|
+
}
|
|
1071
|
+
this.widgetState.options['visible'] = condition;
|
|
1072
|
+
this.inheritanceContext.visible = condition;
|
|
1073
|
+
return this;
|
|
1074
|
+
}
|
|
1075
|
+
disabled(condition) {
|
|
1076
|
+
if (!this.widgetState.options) {
|
|
1077
|
+
this.widgetState.options = {};
|
|
1078
|
+
}
|
|
1079
|
+
this.widgetState.options['disabled'] = condition;
|
|
1080
|
+
this.inheritanceContext.disabled = condition;
|
|
1081
|
+
return this;
|
|
1082
|
+
}
|
|
1083
|
+
readonly(condition) {
|
|
1084
|
+
if (!this.widgetState.options) {
|
|
1085
|
+
this.widgetState.options = {};
|
|
1086
|
+
}
|
|
1087
|
+
this.widgetState.options['readonly'] = condition;
|
|
1088
|
+
this.inheritanceContext.readonly = condition;
|
|
1089
|
+
return this;
|
|
1090
|
+
}
|
|
1091
|
+
direction(direction) {
|
|
1092
|
+
if (!this.widgetState.options) {
|
|
1093
|
+
this.widgetState.options = {};
|
|
1094
|
+
}
|
|
1095
|
+
this.widgetState.options['direction'] = direction;
|
|
1096
|
+
this.inheritanceContext.direction = direction;
|
|
1097
|
+
return this;
|
|
1098
|
+
}
|
|
1099
|
+
// Inheritance context methods
|
|
1100
|
+
withInheritanceContext(context) {
|
|
1101
|
+
this.inheritanceContext = mergeInheritanceContext(context);
|
|
1102
|
+
// Apply inherited properties to widget state
|
|
1103
|
+
const resolved = resolveInheritedProperties(context, this.inheritanceContext);
|
|
1104
|
+
if (resolved.mode && !this.widgetState.mode) {
|
|
1105
|
+
this.widgetState.mode = resolved.mode;
|
|
1106
|
+
}
|
|
1107
|
+
if (!this.widgetState.options)
|
|
1108
|
+
this.widgetState.options = {};
|
|
1109
|
+
if (resolved.disabled !== undefined && !this.widgetState.options['disabled']) {
|
|
1110
|
+
this.widgetState.options['disabled'] = resolved.disabled;
|
|
1111
|
+
}
|
|
1112
|
+
if (resolved.readonly !== undefined && !this.widgetState.options['readonly']) {
|
|
1113
|
+
this.widgetState.options['readonly'] = resolved.readonly;
|
|
1114
|
+
}
|
|
1115
|
+
if (resolved.direction !== undefined && !this.widgetState.options['direction']) {
|
|
1116
|
+
this.widgetState.options['direction'] = resolved.direction;
|
|
1117
|
+
}
|
|
1118
|
+
if (resolved.visible !== undefined && !this.widgetState.options['visible']) {
|
|
1119
|
+
this.widgetState.options['visible'] = resolved.visible;
|
|
1120
|
+
}
|
|
1121
|
+
return this;
|
|
1122
|
+
}
|
|
1123
|
+
getInheritanceContext() {
|
|
1124
|
+
return { ...this.inheritanceContext };
|
|
1125
|
+
}
|
|
1126
|
+
build() {
|
|
1127
|
+
return {
|
|
1128
|
+
name: this.widgetState.name,
|
|
1129
|
+
type: this.widgetState.type,
|
|
1130
|
+
options: this.widgetState.options,
|
|
1131
|
+
mode: this.widgetState.mode,
|
|
1132
|
+
path: this.widgetState.path,
|
|
1133
|
+
defaultValue: this.widgetState.defaultValue,
|
|
1134
|
+
};
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
//#region ---- Action Builder Implementation ----
|
|
1138
|
+
class ActionBuilder {
|
|
1139
|
+
constructor(dialogBuilder) {
|
|
1140
|
+
this.dialogBuilder = dialogBuilder;
|
|
1141
|
+
}
|
|
1142
|
+
cancel(text) {
|
|
1143
|
+
if (!this.dialogBuilder['dialogState'].actions.footer.suffix) {
|
|
1144
|
+
this.dialogBuilder['dialogState'].actions.footer.suffix = [];
|
|
1145
|
+
}
|
|
1146
|
+
this.dialogBuilder['dialogState'].actions.footer.suffix.push({
|
|
1147
|
+
title: text || '@general:actions.cancel.title',
|
|
1148
|
+
icon: 'fa-times',
|
|
1149
|
+
color: 'default',
|
|
1150
|
+
command: { name: 'cancel' },
|
|
1151
|
+
});
|
|
1152
|
+
return this;
|
|
1153
|
+
}
|
|
1154
|
+
submit(text) {
|
|
1155
|
+
if (!this.dialogBuilder['dialogState'].actions.footer.suffix) {
|
|
1156
|
+
this.dialogBuilder['dialogState'].actions.footer.suffix = [];
|
|
1157
|
+
}
|
|
1158
|
+
this.dialogBuilder['dialogState'].actions.footer.suffix.push({
|
|
1159
|
+
title: text || '@general:actions.submit.title',
|
|
1160
|
+
icon: 'fa-check',
|
|
1161
|
+
color: 'primary',
|
|
1162
|
+
command: { name: 'submit', options: { validate: true } },
|
|
1163
|
+
});
|
|
1164
|
+
return this;
|
|
1165
|
+
}
|
|
1166
|
+
custom(action) {
|
|
1167
|
+
if (!this.dialogBuilder['dialogState'].actions.footer.suffix) {
|
|
1168
|
+
this.dialogBuilder['dialogState'].actions.footer.suffix = [];
|
|
1169
|
+
}
|
|
1170
|
+
this.dialogBuilder['dialogState'].actions.footer.suffix.push(action);
|
|
1171
|
+
return this;
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
class AXPLayoutConversionService {
|
|
1176
|
+
constructor() {
|
|
1177
|
+
//#region ---- Caching ----
|
|
1178
|
+
this.widgetTreeCache = new Map();
|
|
1179
|
+
this.formDefinitionCache = new Map();
|
|
1180
|
+
}
|
|
1181
|
+
//#endregion
|
|
1182
|
+
//#region ---- Public Methods ----
|
|
1183
|
+
/**
|
|
1184
|
+
* Convert AXPDynamicFormDefinition to AXPWidgetNode tree structure
|
|
1185
|
+
* Groups become Fieldset Layouts with Form Field widgets as children
|
|
1186
|
+
* Fields become Form Field widgets with Editor widgets as children
|
|
1187
|
+
*/
|
|
1188
|
+
convertFormDefinition(formDefinition) {
|
|
1189
|
+
// Create cache key based on form definition content
|
|
1190
|
+
const cacheKey = this.createFormDefinitionCacheKey(formDefinition);
|
|
1191
|
+
// Check cache first
|
|
1192
|
+
if (this.widgetTreeCache.has(cacheKey)) {
|
|
1193
|
+
return this.widgetTreeCache.get(cacheKey);
|
|
1194
|
+
}
|
|
1195
|
+
// Generate widget tree
|
|
1196
|
+
const widgetTree = {
|
|
1197
|
+
type: 'grid-layout',
|
|
1198
|
+
name: 'dynamic-form-container',
|
|
1199
|
+
options: {
|
|
1200
|
+
title: 'Dynamic Form',
|
|
1201
|
+
grid: {
|
|
1202
|
+
default: {
|
|
1203
|
+
columns: 1,
|
|
1204
|
+
gap: '1rem'
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
},
|
|
1208
|
+
children: formDefinition.groups.map(group => this.createGroupAsFieldsetWidget(group))
|
|
1209
|
+
};
|
|
1210
|
+
// Cache the result
|
|
1211
|
+
this.widgetTreeCache.set(cacheKey, widgetTree);
|
|
1212
|
+
return widgetTree;
|
|
1213
|
+
}
|
|
1214
|
+
/**
|
|
1215
|
+
* Convert AXPWidgetNode tree back to AXPDynamicFormDefinition
|
|
1216
|
+
* Parses Fieldset Layouts back to Groups
|
|
1217
|
+
* Parses Form Field widgets back to Fields
|
|
1218
|
+
*/
|
|
1219
|
+
convertWidgetTreeToFormDefinition(widgetTree) {
|
|
1220
|
+
// Create cache key based on widget tree content
|
|
1221
|
+
const cacheKey = this.createWidgetTreeCacheKey(widgetTree);
|
|
1222
|
+
// Check cache first
|
|
1223
|
+
if (this.formDefinitionCache.has(cacheKey)) {
|
|
1224
|
+
return this.formDefinitionCache.get(cacheKey);
|
|
1225
|
+
}
|
|
1226
|
+
// Parse widget tree
|
|
1227
|
+
const groups = [];
|
|
1228
|
+
if (widgetTree.children) {
|
|
1229
|
+
widgetTree.children.forEach(child => {
|
|
1230
|
+
if (child.type === 'fieldset-layout') {
|
|
1231
|
+
const group = this.extractGroupFromFieldset(child);
|
|
1232
|
+
groups.push(group);
|
|
1233
|
+
}
|
|
1234
|
+
});
|
|
1235
|
+
}
|
|
1236
|
+
const formDefinition = { groups };
|
|
1237
|
+
// Cache the result
|
|
1238
|
+
this.formDefinitionCache.set(cacheKey, formDefinition);
|
|
1239
|
+
return formDefinition;
|
|
1240
|
+
}
|
|
1241
|
+
/**
|
|
1242
|
+
* Validate that a widget tree represents a valid dynamic form structure
|
|
1243
|
+
*/
|
|
1244
|
+
validateFormWidgetTree(widgetTree) {
|
|
1245
|
+
if (!widgetTree || widgetTree.type !== 'grid-layout') {
|
|
1246
|
+
return false;
|
|
1247
|
+
}
|
|
1248
|
+
if (!widgetTree.children || widgetTree.children.length === 0) {
|
|
1249
|
+
return true; // Empty form is valid
|
|
1250
|
+
}
|
|
1251
|
+
// Check that all children are fieldset-layout widgets
|
|
1252
|
+
return widgetTree.children.every(child => child.type === 'fieldset-layout' &&
|
|
1253
|
+
child.children &&
|
|
1254
|
+
child.children.every(formField => formField.type === 'form-field' &&
|
|
1255
|
+
formField.children &&
|
|
1256
|
+
formField.children.length > 0));
|
|
1257
|
+
}
|
|
1258
|
+
/**
|
|
1259
|
+
* Clear all caches
|
|
1260
|
+
*/
|
|
1261
|
+
clearCaches() {
|
|
1262
|
+
this.widgetTreeCache.clear();
|
|
1263
|
+
this.formDefinitionCache.clear();
|
|
1264
|
+
}
|
|
1265
|
+
/**
|
|
1266
|
+
* Get cache statistics
|
|
1267
|
+
*/
|
|
1268
|
+
getCacheStats() {
|
|
1269
|
+
return {
|
|
1270
|
+
widgetTreeCacheSize: this.widgetTreeCache.size,
|
|
1271
|
+
formDefinitionCacheSize: this.formDefinitionCache.size
|
|
1272
|
+
};
|
|
1273
|
+
}
|
|
1274
|
+
//#endregion
|
|
1275
|
+
//#region ---- Private Methods ----
|
|
1276
|
+
/**
|
|
1277
|
+
* Convert a single group to Fieldset widget structure
|
|
1278
|
+
*/
|
|
1279
|
+
createGroupAsFieldsetWidget(group) {
|
|
1280
|
+
// Determine columns count from layout or default to 1
|
|
1281
|
+
const columnsCount = 1;
|
|
1282
|
+
return {
|
|
1283
|
+
type: 'fieldset-layout',
|
|
1284
|
+
name: group.name,
|
|
1285
|
+
options: {
|
|
1286
|
+
title: group.title,
|
|
1287
|
+
description: group.description,
|
|
1288
|
+
cols: columnsCount
|
|
1289
|
+
},
|
|
1290
|
+
children: this.createFieldWidgets(group.parameters, columnsCount)
|
|
1291
|
+
};
|
|
1292
|
+
}
|
|
1293
|
+
/**
|
|
1294
|
+
* Convert fields to Form Field widgets
|
|
1295
|
+
*/
|
|
1296
|
+
createFieldWidgets(fields, columnsCount) {
|
|
1297
|
+
return fields.map(field => this.createFormFieldWidget(field));
|
|
1298
|
+
}
|
|
1299
|
+
/**
|
|
1300
|
+
* Convert a single field to Form Field widget with editor as child
|
|
1301
|
+
*/
|
|
1302
|
+
createFormFieldWidget(field) {
|
|
1303
|
+
return {
|
|
1304
|
+
type: 'form-field',
|
|
1305
|
+
name: field.path,
|
|
1306
|
+
options: {
|
|
1307
|
+
label: field.title,
|
|
1308
|
+
description: field.description,
|
|
1309
|
+
showLabel: true
|
|
1310
|
+
},
|
|
1311
|
+
children: [field.widget] // The editor widget becomes a child of form-field
|
|
1312
|
+
};
|
|
1313
|
+
}
|
|
1314
|
+
/**
|
|
1315
|
+
* Extract group information from Fieldset Layout widget
|
|
1316
|
+
*/
|
|
1317
|
+
extractGroupFromFieldset(fieldsetNode) {
|
|
1318
|
+
const columnsCount = fieldsetNode.options?.['cols'] || 1;
|
|
1319
|
+
// Extract fields directly from fieldset children
|
|
1320
|
+
const fields = [];
|
|
1321
|
+
if (fieldsetNode.children) {
|
|
1322
|
+
fieldsetNode.children.forEach(formField => {
|
|
1323
|
+
if (formField.type === 'form-field' && formField.children && formField.children.length > 0) {
|
|
1324
|
+
const field = this.extractFieldFromFormWidget(formField);
|
|
1325
|
+
if (field) {
|
|
1326
|
+
fields.push(field);
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
});
|
|
1330
|
+
}
|
|
1331
|
+
return {
|
|
1332
|
+
name: fieldsetNode.name || `group-${Date.now()}`,
|
|
1333
|
+
title: fieldsetNode.options?.['title'],
|
|
1334
|
+
description: fieldsetNode.options?.['description'],
|
|
1335
|
+
parameters: fields,
|
|
1336
|
+
};
|
|
1337
|
+
}
|
|
1338
|
+
/**
|
|
1339
|
+
* Extract field information from Form Field widget
|
|
1340
|
+
*/
|
|
1341
|
+
extractFieldFromFormWidget(formFieldNode) {
|
|
1342
|
+
if (!formFieldNode.children || formFieldNode.children.length === 0) {
|
|
1343
|
+
return null;
|
|
1344
|
+
}
|
|
1345
|
+
const editorWidget = formFieldNode.children[0];
|
|
1346
|
+
return {
|
|
1347
|
+
path: formFieldNode.name || editorWidget.name || `field-${Date.now()}`,
|
|
1348
|
+
title: formFieldNode.options?.['label'],
|
|
1349
|
+
description: formFieldNode.options?.['description'],
|
|
1350
|
+
widget: editorWidget,
|
|
1351
|
+
mode: formFieldNode.mode
|
|
1352
|
+
};
|
|
1353
|
+
}
|
|
1354
|
+
/**
|
|
1355
|
+
* Create cache key for form definition
|
|
1356
|
+
*/
|
|
1357
|
+
createFormDefinitionCacheKey(formDefinition) {
|
|
1358
|
+
// Create a hash-like key instead of full JSON string
|
|
1359
|
+
const keyParts = [];
|
|
1360
|
+
keyParts.push(`groups:${formDefinition.groups.length}`);
|
|
1361
|
+
formDefinition.groups.forEach((group, groupIndex) => {
|
|
1362
|
+
keyParts.push(`g${groupIndex}:${group.name}:${group.parameters.length}`);
|
|
1363
|
+
group.parameters.forEach((param, paramIndex) => {
|
|
1364
|
+
keyParts.push(`p${groupIndex}.${paramIndex}:${param.path}:${param.widget.type}`);
|
|
1365
|
+
});
|
|
1366
|
+
});
|
|
1367
|
+
if (formDefinition.mode) {
|
|
1368
|
+
keyParts.push(`mode:${formDefinition.mode}`);
|
|
1369
|
+
}
|
|
1370
|
+
// Join with delimiter and create a shorter hash
|
|
1371
|
+
const keyString = keyParts.join('|');
|
|
1372
|
+
// If still too long, create a simple hash
|
|
1373
|
+
if (keyString.length > 100) {
|
|
1374
|
+
return this.createSimpleHash(keyString);
|
|
1375
|
+
}
|
|
1376
|
+
return keyString;
|
|
1377
|
+
}
|
|
1378
|
+
/**
|
|
1379
|
+
* Create cache key for widget tree
|
|
1380
|
+
*/
|
|
1381
|
+
createWidgetTreeCacheKey(widgetTree) {
|
|
1382
|
+
// Create a hash-like key instead of full JSON string
|
|
1383
|
+
const keyParts = [];
|
|
1384
|
+
keyParts.push(`type:${widgetTree.type}`);
|
|
1385
|
+
if (widgetTree.name) {
|
|
1386
|
+
keyParts.push(`name:${widgetTree.name}`);
|
|
1387
|
+
}
|
|
1388
|
+
if (widgetTree.children) {
|
|
1389
|
+
keyParts.push(`children:${widgetTree.children.length}`);
|
|
1390
|
+
widgetTree.children.forEach((child, index) => {
|
|
1391
|
+
keyParts.push(`c${index}:${child.type}`);
|
|
1392
|
+
if (child.children) {
|
|
1393
|
+
keyParts.push(`cc${index}:${child.children.length}`);
|
|
1394
|
+
child.children.forEach((grandChild, gIndex) => {
|
|
1395
|
+
keyParts.push(`gc${index}.${gIndex}:${grandChild.type}`);
|
|
1396
|
+
if (grandChild.children) {
|
|
1397
|
+
keyParts.push(`gcc${index}.${gIndex}:${grandChild.children.length}`);
|
|
1398
|
+
}
|
|
1399
|
+
});
|
|
1400
|
+
}
|
|
1401
|
+
});
|
|
1402
|
+
}
|
|
1403
|
+
// Join with delimiter and create a shorter hash
|
|
1404
|
+
const keyString = keyParts.join('|');
|
|
1405
|
+
// If still too long, create a simple hash
|
|
1406
|
+
if (keyString.length > 100) {
|
|
1407
|
+
return this.createSimpleHash(keyString);
|
|
1408
|
+
}
|
|
1409
|
+
return keyString;
|
|
1410
|
+
}
|
|
1411
|
+
/**
|
|
1412
|
+
* Create a simple hash from a string
|
|
1413
|
+
*/
|
|
1414
|
+
createSimpleHash(str) {
|
|
1415
|
+
let hash = 0;
|
|
1416
|
+
if (str.length === 0)
|
|
1417
|
+
return hash.toString();
|
|
1418
|
+
for (let i = 0; i < str.length; i++) {
|
|
1419
|
+
const char = str.charCodeAt(i);
|
|
1420
|
+
hash = ((hash << 5) - hash) + char;
|
|
1421
|
+
hash = hash & hash; // Convert to 32-bit integer
|
|
1422
|
+
}
|
|
1423
|
+
return Math.abs(hash).toString(36); // Convert to base36 for shorter string
|
|
1424
|
+
}
|
|
1425
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXPLayoutConversionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1426
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXPLayoutConversionService, providedIn: 'root' }); }
|
|
1427
|
+
}
|
|
1428
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXPLayoutConversionService, decorators: [{
|
|
1429
|
+
type: Injectable,
|
|
1430
|
+
args: [{
|
|
1431
|
+
providedIn: 'root'
|
|
1432
|
+
}]
|
|
1433
|
+
}] });
|
|
1434
|
+
|
|
1435
|
+
class AXPLayoutRendererComponent {
|
|
1436
|
+
constructor() {
|
|
1437
|
+
this.evaluatorService = inject(AXPExpressionEvaluatorService);
|
|
1438
|
+
this.conversionService = inject(AXPLayoutConversionService);
|
|
1439
|
+
/**
|
|
1440
|
+
* Tracks the latest scheduled evaluation to ensure last-write-wins for async evaluate
|
|
1441
|
+
*/
|
|
1442
|
+
this.evaluationRunId = 0;
|
|
1443
|
+
/**
|
|
1444
|
+
* RxJS subjects for context management
|
|
1445
|
+
*/
|
|
1446
|
+
this.contextUpdateSubject = new Subject();
|
|
1447
|
+
this.contextChangeSubject = new Subject();
|
|
1448
|
+
/**
|
|
1449
|
+
* Cache for expression evaluation results
|
|
1450
|
+
*/
|
|
1451
|
+
this.expressionCache = new Map();
|
|
1452
|
+
/**
|
|
1453
|
+
* Cache for widget tree comparisons
|
|
1454
|
+
*/
|
|
1455
|
+
this.widgetTreeCache = new Map();
|
|
1456
|
+
/**
|
|
1457
|
+
* Last layout hash for change detection
|
|
1458
|
+
*/
|
|
1459
|
+
this.lastLayoutHash = '';
|
|
1460
|
+
//#region ---- Inputs ----
|
|
1461
|
+
/**
|
|
1462
|
+
* Form definition containing groups and fields OR widget tree
|
|
1463
|
+
*/
|
|
1464
|
+
this.layout = input.required(...(ngDevMode ? [{ debugName: "layout" }] : []));
|
|
1465
|
+
/**
|
|
1466
|
+
* Form context/model data
|
|
1467
|
+
*/
|
|
1468
|
+
this.context = model({}, ...(ngDevMode ? [{ debugName: "context" }] : []));
|
|
1469
|
+
/**
|
|
1470
|
+
* Form appearance and density styling (normal, compact, spacious)
|
|
1471
|
+
*/
|
|
1472
|
+
this.look = input('fieldset', ...(ngDevMode ? [{ debugName: "look" }] : []));
|
|
1473
|
+
/**
|
|
1474
|
+
* Default form mode. Can be overridden by section/group and field.
|
|
1475
|
+
*/
|
|
1476
|
+
this.mode = input('edit', ...(ngDevMode ? [{ debugName: "mode" }] : []));
|
|
1477
|
+
//#endregion
|
|
1478
|
+
//#region ---- Widget Tree Conversion ----
|
|
1479
|
+
this.widgetTree = signal(null, ...(ngDevMode ? [{ debugName: "widgetTree" }] : []));
|
|
1480
|
+
/**
|
|
1481
|
+
* Convert and evaluate data when inputs change (optimized with RxJS)
|
|
1482
|
+
*/
|
|
1483
|
+
this.conversionEffect = effect(() => {
|
|
1484
|
+
const inputData = this.layout();
|
|
1485
|
+
const ctx = this.internalContext();
|
|
1486
|
+
const look = this.look();
|
|
1487
|
+
const runId = ++this.evaluationRunId;
|
|
1488
|
+
// Generate layout hash for change detection
|
|
1489
|
+
const layoutHash = this.generateLayoutHash(inputData, look);
|
|
1490
|
+
// Skip if layout hasn't changed
|
|
1491
|
+
if (layoutHash === this.lastLayoutHash && this.widgetTree()) {
|
|
1492
|
+
return;
|
|
1493
|
+
}
|
|
1494
|
+
this.lastLayoutHash = layoutHash;
|
|
1495
|
+
(async () => {
|
|
1496
|
+
// First evaluate expressions if needed
|
|
1497
|
+
const evaluated = await this.expressionEvaluator(inputData, ctx);
|
|
1498
|
+
// Ignore stale results
|
|
1499
|
+
if (runId !== this.evaluationRunId) {
|
|
1500
|
+
return;
|
|
1501
|
+
}
|
|
1502
|
+
// Convert to widget tree (this will also apply the layout look)
|
|
1503
|
+
const tree = evaluated;
|
|
1504
|
+
// Update widget tree
|
|
1505
|
+
const prev = this.widgetTree();
|
|
1506
|
+
if (!isEqual(prev, tree)) {
|
|
1507
|
+
tree.mode = this.mode();
|
|
1508
|
+
this.widgetTree.set(tree);
|
|
1509
|
+
}
|
|
1510
|
+
})();
|
|
1511
|
+
}, ...(ngDevMode ? [{ debugName: "conversionEffect" }] : []));
|
|
1512
|
+
//#endregion
|
|
1513
|
+
//#region ---- Outputs ----
|
|
1514
|
+
/**
|
|
1515
|
+
* Emitted when context change is initiated
|
|
1516
|
+
*/
|
|
1517
|
+
this.contextInitiated = output();
|
|
1518
|
+
/**
|
|
1519
|
+
* Emitted when form becomes valid/invalid
|
|
1520
|
+
*/
|
|
1521
|
+
this.validityChange = output();
|
|
1522
|
+
//#endregion
|
|
1523
|
+
//#region ---- Properties ----
|
|
1524
|
+
this.form = viewChild(AXFormComponent, ...(ngDevMode ? [{ debugName: "form" }] : []));
|
|
1525
|
+
this.container = viewChild(AXPWidgetContainerComponent, ...(ngDevMode ? [{ debugName: "container" }] : []));
|
|
1526
|
+
/**
|
|
1527
|
+
* Internal context signal for reactivity
|
|
1528
|
+
*/
|
|
1529
|
+
this.internalContext = signal({}, ...(ngDevMode ? [{ debugName: "internalContext" }] : []));
|
|
1530
|
+
//#endregion
|
|
1531
|
+
//#region ---- Effects ----
|
|
1532
|
+
/**
|
|
1533
|
+
* Effect to sync context changes from external to internal (optimized with RxJS)
|
|
1534
|
+
*/
|
|
1535
|
+
this.#contextSyncEffect = effect(() => {
|
|
1536
|
+
const ctx = this.context() ?? {};
|
|
1537
|
+
this.contextUpdateSubject.next(ctx);
|
|
1538
|
+
}, ...(ngDevMode ? [{ debugName: "#contextSyncEffect" }] : []));
|
|
1539
|
+
/**
|
|
1540
|
+
* Effect to handle widget tree status changes
|
|
1541
|
+
*/
|
|
1542
|
+
this.#widgetStatusEffect = effect(() => {
|
|
1543
|
+
const widgetTree = this.widgetTree();
|
|
1544
|
+
if (widgetTree) {
|
|
1545
|
+
this.container()?.builderService.setStatus(AXPPageStatus.Rendered);
|
|
1546
|
+
}
|
|
1547
|
+
}, ...(ngDevMode ? [{ debugName: "#widgetStatusEffect" }] : []));
|
|
1548
|
+
}
|
|
1549
|
+
async expressionEvaluator(expression, context) {
|
|
1550
|
+
// Check if it's a form definition that needs conversion
|
|
1551
|
+
if (this.isFormDefinition(expression)) {
|
|
1552
|
+
return this.conversionService.convertFormDefinition(expression);
|
|
1553
|
+
}
|
|
1554
|
+
// Generate cache key using a more efficient method
|
|
1555
|
+
const cacheKey = this.generateCacheKey(expression, context);
|
|
1556
|
+
// Check cache first
|
|
1557
|
+
if (this.expressionCache.has(cacheKey)) {
|
|
1558
|
+
return this.expressionCache.get(cacheKey);
|
|
1559
|
+
}
|
|
1560
|
+
const scope = {
|
|
1561
|
+
context: {
|
|
1562
|
+
eval: (path) => get(context, path),
|
|
1563
|
+
},
|
|
1564
|
+
};
|
|
1565
|
+
const result = await this.evaluatorService.evaluate(expression, scope);
|
|
1566
|
+
// Cache result with LRU-like behavior
|
|
1567
|
+
if (this.expressionCache.size > 50) {
|
|
1568
|
+
// Clear half the cache when it gets too large
|
|
1569
|
+
const keysToDelete = Array.from(this.expressionCache.keys()).slice(0, 25);
|
|
1570
|
+
keysToDelete.forEach(key => this.expressionCache.delete(key));
|
|
1571
|
+
}
|
|
1572
|
+
this.expressionCache.set(cacheKey, result);
|
|
1573
|
+
return result;
|
|
1574
|
+
}
|
|
1575
|
+
//#endregion
|
|
1576
|
+
//#region ---- Lifecycle Methods ----
|
|
1577
|
+
ngOnInit() {
|
|
1578
|
+
// Initialize internal context with input context
|
|
1579
|
+
this.internalContext.set(this.context() ?? {});
|
|
1580
|
+
// Setup RxJS streams for context management
|
|
1581
|
+
this.setupContextStreams();
|
|
1582
|
+
}
|
|
1583
|
+
//#endregion
|
|
1584
|
+
//#region ---- Effects ----
|
|
1585
|
+
/**
|
|
1586
|
+
* Effect to sync context changes from external to internal (optimized with RxJS)
|
|
1587
|
+
*/
|
|
1588
|
+
#contextSyncEffect;
|
|
1589
|
+
/**
|
|
1590
|
+
* Effect to handle widget tree status changes
|
|
1591
|
+
*/
|
|
1592
|
+
#widgetStatusEffect;
|
|
1593
|
+
//#endregion
|
|
1594
|
+
//#region ---- Event Handlers ----
|
|
1595
|
+
/**
|
|
1596
|
+
* Handle context change events from widget container (optimized with RxJS)
|
|
1597
|
+
*/
|
|
1598
|
+
handleContextChanged(event) {
|
|
1599
|
+
if (event.state === 'initiated') {
|
|
1600
|
+
this.contextInitiated.emit(event.data);
|
|
1601
|
+
}
|
|
1602
|
+
else {
|
|
1603
|
+
this.contextChangeSubject.next(event.data ?? {});
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
//#endregion
|
|
1607
|
+
//#region ---- Public Methods ----
|
|
1608
|
+
/**
|
|
1609
|
+
* Get the form component instance
|
|
1610
|
+
*/
|
|
1611
|
+
getForm() {
|
|
1612
|
+
return this.form();
|
|
1613
|
+
}
|
|
1614
|
+
/**
|
|
1615
|
+
* Get the widget container component instance
|
|
1616
|
+
*/
|
|
1617
|
+
getContainer() {
|
|
1618
|
+
return this.container();
|
|
1619
|
+
}
|
|
1620
|
+
/**
|
|
1621
|
+
* Get current form context
|
|
1622
|
+
*/
|
|
1623
|
+
getContext() {
|
|
1624
|
+
return this.internalContext();
|
|
1625
|
+
}
|
|
1626
|
+
/**
|
|
1627
|
+
* Update form context programmatically
|
|
1628
|
+
*/
|
|
1629
|
+
updateContext(context) {
|
|
1630
|
+
this.internalContext.set(context);
|
|
1631
|
+
}
|
|
1632
|
+
/**
|
|
1633
|
+
* Get the current widget tree
|
|
1634
|
+
*/
|
|
1635
|
+
getWidgetTree() {
|
|
1636
|
+
return this.widgetTree();
|
|
1637
|
+
}
|
|
1638
|
+
/**
|
|
1639
|
+
* Validate the form
|
|
1640
|
+
*/
|
|
1641
|
+
async validate() {
|
|
1642
|
+
const form = this.form();
|
|
1643
|
+
if (form) {
|
|
1644
|
+
const isValid = await form.validate();
|
|
1645
|
+
this.validityChange.emit(isValid.result);
|
|
1646
|
+
return isValid;
|
|
1647
|
+
}
|
|
1648
|
+
return {
|
|
1649
|
+
result: false,
|
|
1650
|
+
messages: [],
|
|
1651
|
+
rules: [],
|
|
1652
|
+
};
|
|
1653
|
+
}
|
|
1654
|
+
//#endregion
|
|
1655
|
+
//#region ---- RxJS Stream Setup ----
|
|
1656
|
+
/**
|
|
1657
|
+
* Setup RxJS streams for context management
|
|
1658
|
+
*/
|
|
1659
|
+
setupContextStreams() {
|
|
1660
|
+
// Debounced context updates from external source
|
|
1661
|
+
this.contextUpdateSubject
|
|
1662
|
+
.pipe(debounceTime(16), // ~60fps
|
|
1663
|
+
distinctUntilChanged((prev, curr) => isEqual(prev, curr)), startWith(this.context() ?? {}))
|
|
1664
|
+
.subscribe(ctx => {
|
|
1665
|
+
this.internalContext.set(ctx);
|
|
1666
|
+
});
|
|
1667
|
+
// Debounced context changes from widgets
|
|
1668
|
+
this.contextChangeSubject
|
|
1669
|
+
.pipe(debounceTime(16), // ~60fps
|
|
1670
|
+
distinctUntilChanged((prev, curr) => isEqual(prev, curr)))
|
|
1671
|
+
.subscribe(ctx => {
|
|
1672
|
+
this.internalContext.set(ctx);
|
|
1673
|
+
// Update the model signal directly - it will emit change events automatically
|
|
1674
|
+
this.context.set(this.internalContext());
|
|
1675
|
+
});
|
|
1676
|
+
}
|
|
1677
|
+
//#endregion
|
|
1678
|
+
//#region ---- Type Guards ----
|
|
1679
|
+
/**
|
|
1680
|
+
* Type guard to check if the input is a form definition
|
|
1681
|
+
*/
|
|
1682
|
+
isFormDefinition(data) {
|
|
1683
|
+
return data &&
|
|
1684
|
+
typeof data === 'object' &&
|
|
1685
|
+
'groups' in data &&
|
|
1686
|
+
Array.isArray(data.groups);
|
|
1687
|
+
}
|
|
1688
|
+
//#endregion
|
|
1689
|
+
//#region ---- Utility Methods ----
|
|
1690
|
+
/**
|
|
1691
|
+
* Generate layout hash for change detection (short hash)
|
|
1692
|
+
*/
|
|
1693
|
+
generateLayoutHash(layout, look) {
|
|
1694
|
+
if (!layout)
|
|
1695
|
+
return '';
|
|
1696
|
+
// Generate short hash for large layout strings
|
|
1697
|
+
const layoutStr = typeof layout === 'string' ? layout : JSON.stringify(layout);
|
|
1698
|
+
const layoutHash = this.simpleHash(layoutStr);
|
|
1699
|
+
return `${layoutHash}|${look}`;
|
|
1700
|
+
}
|
|
1701
|
+
/**
|
|
1702
|
+
* Generate cache key for expression evaluation (short hash)
|
|
1703
|
+
*/
|
|
1704
|
+
generateCacheKey(expression, context) {
|
|
1705
|
+
// Use short hash for better performance
|
|
1706
|
+
const exprStr = typeof expression === 'string' ? expression : JSON.stringify(expression);
|
|
1707
|
+
const exprHash = this.simpleHash(exprStr);
|
|
1708
|
+
const ctxHash = this.generateContextHash(context);
|
|
1709
|
+
return `${exprHash}|${ctxHash}`;
|
|
1710
|
+
}
|
|
1711
|
+
/**
|
|
1712
|
+
* Generate a simple hash for context change detection
|
|
1713
|
+
*/
|
|
1714
|
+
generateContextHash(context) {
|
|
1715
|
+
if (!context || typeof context !== 'object') {
|
|
1716
|
+
return String(context);
|
|
1717
|
+
}
|
|
1718
|
+
// Generate short hash for context
|
|
1719
|
+
const keys = Object.keys(context).sort();
|
|
1720
|
+
const contextStr = keys.map(key => `${key}:${context[key]}`).join('|');
|
|
1721
|
+
return this.simpleHash(contextStr);
|
|
1722
|
+
}
|
|
1723
|
+
/**
|
|
1724
|
+
* Simple hash function for generating short keys
|
|
1725
|
+
*/
|
|
1726
|
+
simpleHash(str) {
|
|
1727
|
+
let hash = 0;
|
|
1728
|
+
if (str.length === 0)
|
|
1729
|
+
return hash.toString();
|
|
1730
|
+
for (let i = 0; i < str.length; i++) {
|
|
1731
|
+
const char = str.charCodeAt(i);
|
|
1732
|
+
hash = ((hash << 5) - hash) + char;
|
|
1733
|
+
hash = hash & hash; // Convert to 32-bit integer
|
|
1734
|
+
}
|
|
1735
|
+
// Convert to positive hex string
|
|
1736
|
+
return Math.abs(hash).toString(16);
|
|
1737
|
+
}
|
|
1738
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXPLayoutRendererComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1739
|
+
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: `
|
|
1740
|
+
<ax-form>
|
|
1741
|
+
<axp-widgets-container [context]="internalContext()" (onContextChanged)="handleContextChanged($event)">
|
|
1742
|
+
@if (widgetTree()) {
|
|
1743
|
+
<ng-container axp-widget-renderer [node]="widgetTree()!" [mode]="mode()"></ng-container>
|
|
1744
|
+
}
|
|
1745
|
+
</axp-widgets-container>
|
|
1746
|
+
</ax-form>
|
|
1747
|
+
`, 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 }); }
|
|
1748
|
+
}
|
|
1749
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXPLayoutRendererComponent, decorators: [{
|
|
1750
|
+
type: Component,
|
|
1751
|
+
args: [{ selector: 'axp-layout-renderer', standalone: true, imports: [CommonModule, AXPWidgetCoreModule, AXFormModule], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
1752
|
+
<ax-form>
|
|
1753
|
+
<axp-widgets-container [context]="internalContext()" (onContextChanged)="handleContextChanged($event)">
|
|
1754
|
+
@if (widgetTree()) {
|
|
1755
|
+
<ng-container axp-widget-renderer [node]="widgetTree()!" [mode]="mode()"></ng-container>
|
|
1756
|
+
}
|
|
1757
|
+
</axp-widgets-container>
|
|
1758
|
+
</ax-form>
|
|
1307
1759
|
`, styles: [":host{display:block;width:100%}\n"] }]
|
|
1308
|
-
}]
|
|
1309
|
-
type: Input
|
|
1310
|
-
}] } });
|
|
1760
|
+
}] });
|
|
1311
1761
|
|
|
1312
1762
|
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.
|
|
1763
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: LayoutBuilderModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
|
|
1764
|
+
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.3", ngImport: i0, type: LayoutBuilderModule, imports: [CommonModule, AXPLayoutRendererComponent], exports: [AXPLayoutRendererComponent] }); }
|
|
1765
|
+
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: LayoutBuilderModule, providers: [AXPLayoutBuilderService], imports: [CommonModule, AXPLayoutRendererComponent] }); }
|
|
1316
1766
|
}
|
|
1317
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.
|
|
1767
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: LayoutBuilderModule, decorators: [{
|
|
1318
1768
|
type: NgModule,
|
|
1319
1769
|
args: [{
|
|
1320
1770
|
imports: [CommonModule, AXPLayoutRendererComponent],
|
|
@@ -1323,9 +1773,219 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImpor
|
|
|
1323
1773
|
}]
|
|
1324
1774
|
}] });
|
|
1325
1775
|
|
|
1776
|
+
//#endregion
|
|
1777
|
+
|
|
1778
|
+
class AXPDialogRendererComponent extends AXBasePageComponent {
|
|
1779
|
+
constructor() {
|
|
1780
|
+
super(...arguments);
|
|
1781
|
+
this.result = new EventEmitter();
|
|
1782
|
+
this.context = signal({}, ...(ngDevMode ? [{ debugName: "context" }] : []));
|
|
1783
|
+
// This will be set by the popup service automatically - same as dynamic-dialog
|
|
1784
|
+
this.callBack = () => { };
|
|
1785
|
+
this.isDialogLoading = signal(false, ...(ngDevMode ? [{ debugName: "isDialogLoading" }] : []));
|
|
1786
|
+
}
|
|
1787
|
+
ngOnInit() {
|
|
1788
|
+
// Initialize context with provided context
|
|
1789
|
+
this.context.set(this.config?.context || {});
|
|
1790
|
+
}
|
|
1791
|
+
handleContextChanged(event) {
|
|
1792
|
+
this.context.set(event);
|
|
1793
|
+
}
|
|
1794
|
+
handleContextInitiated(event) {
|
|
1795
|
+
this.context.set(event);
|
|
1796
|
+
}
|
|
1797
|
+
visibleFooterPrefixActions() {
|
|
1798
|
+
return this.config?.actions?.footer?.prefix || [];
|
|
1799
|
+
}
|
|
1800
|
+
visibleFooterSuffixActions() {
|
|
1801
|
+
return this.config?.actions?.footer?.suffix || [
|
|
1802
|
+
{ title: 'Cancel', color: 'secondary', command: { name: 'cancel' } },
|
|
1803
|
+
{ title: 'Confirm', color: 'primary', command: { name: 'confirm' } }
|
|
1804
|
+
];
|
|
1805
|
+
}
|
|
1806
|
+
isFormLoading() {
|
|
1807
|
+
return this.isDialogLoading();
|
|
1808
|
+
}
|
|
1809
|
+
isSubmitting() {
|
|
1810
|
+
return this.isDialogLoading();
|
|
1811
|
+
}
|
|
1812
|
+
async executeAction(action) {
|
|
1813
|
+
const actionName = action.command?.name || action.title?.toLowerCase();
|
|
1814
|
+
// Store the action and context - same pattern as dynamic-dialog
|
|
1815
|
+
const result = {
|
|
1816
|
+
context: this.context(),
|
|
1817
|
+
action: actionName
|
|
1818
|
+
};
|
|
1819
|
+
// Store result in component property
|
|
1820
|
+
this.dialogResult = result;
|
|
1821
|
+
// Store in popup data for DialogRef access
|
|
1822
|
+
if (this.data) {
|
|
1823
|
+
this.data.context = result.context;
|
|
1824
|
+
this.data.action = result.action;
|
|
1825
|
+
}
|
|
1826
|
+
// Call the callback with DialogRef - same pattern as dynamic-dialog
|
|
1827
|
+
this.callBack({
|
|
1828
|
+
close: (result) => {
|
|
1829
|
+
this.close(result);
|
|
1830
|
+
},
|
|
1831
|
+
context: () => {
|
|
1832
|
+
return this.context();
|
|
1833
|
+
},
|
|
1834
|
+
action: () => {
|
|
1835
|
+
return result.action;
|
|
1836
|
+
},
|
|
1837
|
+
setLoading: (loading) => {
|
|
1838
|
+
this.isDialogLoading.set(loading);
|
|
1839
|
+
}
|
|
1840
|
+
});
|
|
1841
|
+
}
|
|
1842
|
+
close(result) {
|
|
1843
|
+
if (result) {
|
|
1844
|
+
this.result.emit(result);
|
|
1845
|
+
}
|
|
1846
|
+
super.close(result);
|
|
1847
|
+
}
|
|
1848
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXPDialogRendererComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
1849
|
+
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: `
|
|
1850
|
+
<div class="ax-p-4">
|
|
1851
|
+
<axp-layout-renderer
|
|
1852
|
+
[layout]="config.definition"
|
|
1853
|
+
[context]="context()"
|
|
1854
|
+
(contextChange)="handleContextChanged($event)"
|
|
1855
|
+
(contextInitiated)="handleContextInitiated($event)"
|
|
1856
|
+
>
|
|
1857
|
+
</axp-layout-renderer>
|
|
1858
|
+
</div>
|
|
1859
|
+
|
|
1860
|
+
<ax-footer>
|
|
1861
|
+
<ax-prefix>
|
|
1862
|
+
<ng-container *ngTemplateOutlet="footerPrefixActions"></ng-container>
|
|
1863
|
+
</ax-prefix>
|
|
1864
|
+
<ax-suffix>
|
|
1865
|
+
<ng-container *ngTemplateOutlet="footerSuffixActions"></ng-container>
|
|
1866
|
+
</ax-suffix>
|
|
1867
|
+
</ax-footer>
|
|
1868
|
+
|
|
1869
|
+
<!-- Footer Prefix Actions -->
|
|
1870
|
+
<ng-template #footerPrefixActions>
|
|
1871
|
+
@for (action of visibleFooterPrefixActions(); track $index) {
|
|
1872
|
+
<ax-button
|
|
1873
|
+
[disabled]="action.disabled || isFormLoading()"
|
|
1874
|
+
[text]="(action.title | translate | async)!"
|
|
1875
|
+
[look]="'outline'"
|
|
1876
|
+
[color]="action.color"
|
|
1877
|
+
(onClick)="executeAction(action)"
|
|
1878
|
+
>
|
|
1879
|
+
@if (isFormLoading() && action.command?.name != 'cancel') {
|
|
1880
|
+
<ax-loading></ax-loading>
|
|
1881
|
+
}
|
|
1882
|
+
<ax-prefix>
|
|
1883
|
+
<i class="{{ action.icon }}"></i>
|
|
1884
|
+
</ax-prefix>
|
|
1885
|
+
</ax-button>
|
|
1886
|
+
}
|
|
1887
|
+
</ng-template>
|
|
1888
|
+
|
|
1889
|
+
<!-- Footer Suffix Actions -->
|
|
1890
|
+
<ng-template #footerSuffixActions>
|
|
1891
|
+
@for (action of visibleFooterSuffixActions(); track $index) {
|
|
1892
|
+
<ax-button
|
|
1893
|
+
[disabled]="action.disabled || isSubmitting()"
|
|
1894
|
+
[text]="(action.title | translate | async)!"
|
|
1895
|
+
[look]="'solid'"
|
|
1896
|
+
[color]="action.color"
|
|
1897
|
+
(onClick)="executeAction(action)"
|
|
1898
|
+
>
|
|
1899
|
+
@if (action.icon) {
|
|
1900
|
+
<ax-prefix>
|
|
1901
|
+
<ax-icon icon="{{ action.icon }}"></ax-icon>
|
|
1902
|
+
</ax-prefix>
|
|
1903
|
+
}
|
|
1904
|
+
</ax-button>
|
|
1905
|
+
}
|
|
1906
|
+
</ng-template>
|
|
1907
|
+
`, 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" }] }); }
|
|
1908
|
+
}
|
|
1909
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXPDialogRendererComponent, decorators: [{
|
|
1910
|
+
type: Component,
|
|
1911
|
+
args: [{
|
|
1912
|
+
selector: 'axp-dialog-renderer',
|
|
1913
|
+
standalone: true,
|
|
1914
|
+
imports: [CommonModule, AXPLayoutRendererComponent, AXButtonModule, AXDecoratorModule, AXLoadingModule, AXTranslationModule],
|
|
1915
|
+
template: `
|
|
1916
|
+
<div class="ax-p-4">
|
|
1917
|
+
<axp-layout-renderer
|
|
1918
|
+
[layout]="config.definition"
|
|
1919
|
+
[context]="context()"
|
|
1920
|
+
(contextChange)="handleContextChanged($event)"
|
|
1921
|
+
(contextInitiated)="handleContextInitiated($event)"
|
|
1922
|
+
>
|
|
1923
|
+
</axp-layout-renderer>
|
|
1924
|
+
</div>
|
|
1925
|
+
|
|
1926
|
+
<ax-footer>
|
|
1927
|
+
<ax-prefix>
|
|
1928
|
+
<ng-container *ngTemplateOutlet="footerPrefixActions"></ng-container>
|
|
1929
|
+
</ax-prefix>
|
|
1930
|
+
<ax-suffix>
|
|
1931
|
+
<ng-container *ngTemplateOutlet="footerSuffixActions"></ng-container>
|
|
1932
|
+
</ax-suffix>
|
|
1933
|
+
</ax-footer>
|
|
1934
|
+
|
|
1935
|
+
<!-- Footer Prefix Actions -->
|
|
1936
|
+
<ng-template #footerPrefixActions>
|
|
1937
|
+
@for (action of visibleFooterPrefixActions(); track $index) {
|
|
1938
|
+
<ax-button
|
|
1939
|
+
[disabled]="action.disabled || isFormLoading()"
|
|
1940
|
+
[text]="(action.title | translate | async)!"
|
|
1941
|
+
[look]="'outline'"
|
|
1942
|
+
[color]="action.color"
|
|
1943
|
+
(onClick)="executeAction(action)"
|
|
1944
|
+
>
|
|
1945
|
+
@if (isFormLoading() && action.command?.name != 'cancel') {
|
|
1946
|
+
<ax-loading></ax-loading>
|
|
1947
|
+
}
|
|
1948
|
+
<ax-prefix>
|
|
1949
|
+
<i class="{{ action.icon }}"></i>
|
|
1950
|
+
</ax-prefix>
|
|
1951
|
+
</ax-button>
|
|
1952
|
+
}
|
|
1953
|
+
</ng-template>
|
|
1954
|
+
|
|
1955
|
+
<!-- Footer Suffix Actions -->
|
|
1956
|
+
<ng-template #footerSuffixActions>
|
|
1957
|
+
@for (action of visibleFooterSuffixActions(); track $index) {
|
|
1958
|
+
<ax-button
|
|
1959
|
+
[disabled]="action.disabled || isSubmitting()"
|
|
1960
|
+
[text]="(action.title | translate | async)!"
|
|
1961
|
+
[look]="'solid'"
|
|
1962
|
+
[color]="action.color"
|
|
1963
|
+
(onClick)="executeAction(action)"
|
|
1964
|
+
>
|
|
1965
|
+
@if (action.icon) {
|
|
1966
|
+
<ax-prefix>
|
|
1967
|
+
<ax-icon icon="{{ action.icon }}"></ax-icon>
|
|
1968
|
+
</ax-prefix>
|
|
1969
|
+
}
|
|
1970
|
+
</ax-button>
|
|
1971
|
+
}
|
|
1972
|
+
</ng-template>
|
|
1973
|
+
`
|
|
1974
|
+
}]
|
|
1975
|
+
}], propDecorators: { config: [{
|
|
1976
|
+
type: Input
|
|
1977
|
+
}], result: [{
|
|
1978
|
+
type: Output
|
|
1979
|
+
}] } });
|
|
1980
|
+
|
|
1981
|
+
var dialogRenderer_component = /*#__PURE__*/Object.freeze({
|
|
1982
|
+
__proto__: null,
|
|
1983
|
+
AXPDialogRendererComponent: AXPDialogRendererComponent
|
|
1984
|
+
});
|
|
1985
|
+
|
|
1326
1986
|
/**
|
|
1327
1987
|
* Generated bundle index. Do not edit.
|
|
1328
1988
|
*/
|
|
1329
1989
|
|
|
1330
|
-
export { AXPLayoutBuilderService, AXPLayoutRendererComponent, LayoutBuilderModule };
|
|
1990
|
+
export { AXPDialogRendererComponent, AXPLayoutBuilderService, AXPLayoutConversionService, AXPLayoutRendererComponent, LayoutBuilderModule };
|
|
1331
1991
|
//# sourceMappingURL=acorex-platform-layout-builder.mjs.map
|