@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.
Files changed (84) hide show
  1. package/fesm2022/acorex-platform-auth.mjs +19 -19
  2. package/fesm2022/acorex-platform-auth.mjs.map +1 -1
  3. package/fesm2022/acorex-platform-common.mjs +99 -99
  4. package/fesm2022/acorex-platform-common.mjs.map +1 -1
  5. package/fesm2022/acorex-platform-core.mjs +42 -42
  6. package/fesm2022/acorex-platform-core.mjs.map +1 -1
  7. package/fesm2022/acorex-platform-domain.mjs +16 -16
  8. package/fesm2022/acorex-platform-domain.mjs.map +1 -1
  9. package/fesm2022/acorex-platform-layout-builder.mjs +1690 -1030
  10. package/fesm2022/acorex-platform-layout-builder.mjs.map +1 -1
  11. package/fesm2022/acorex-platform-layout-components.mjs +2189 -1567
  12. package/fesm2022/acorex-platform-layout-components.mjs.map +1 -1
  13. package/fesm2022/acorex-platform-layout-designer.mjs +73 -73
  14. package/fesm2022/acorex-platform-layout-designer.mjs.map +1 -1
  15. package/fesm2022/{acorex-platform-layout-entity-create-entity.command-CuueLekJ.mjs → acorex-platform-layout-entity-create-entity.command-CFBqiwfy.mjs} +4 -4
  16. package/fesm2022/{acorex-platform-layout-entity-create-entity.command-CuueLekJ.mjs.map → acorex-platform-layout-entity-create-entity.command-CFBqiwfy.mjs.map} +1 -1
  17. package/fesm2022/acorex-platform-layout-entity.mjs +160 -161
  18. package/fesm2022/acorex-platform-layout-entity.mjs.map +1 -1
  19. package/fesm2022/acorex-platform-layout-views.mjs +15 -15
  20. package/fesm2022/acorex-platform-layout-views.mjs.map +1 -1
  21. package/fesm2022/acorex-platform-layout-widget-core.mjs +163 -74
  22. package/fesm2022/acorex-platform-layout-widget-core.mjs.map +1 -1
  23. package/fesm2022/{acorex-platform-layout-widgets-button-widget-designer.component-C82aG5Rf.mjs → acorex-platform-layout-widgets-button-widget-designer.component-BzsfTNs2.mjs} +4 -4
  24. 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
  25. 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
  26. 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
  27. 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
  28. 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
  29. 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
  30. 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
  31. 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
  32. 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
  33. 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
  34. 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
  35. 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
  36. 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
  37. 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
  38. package/fesm2022/acorex-platform-layout-widgets-file-list-popup.component-BPzn8lr3.mjs.map +1 -0
  39. 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
  40. 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
  41. 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
  42. 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
  43. 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
  44. 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
  45. 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
  46. 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
  47. package/fesm2022/acorex-platform-layout-widgets.mjs +697 -664
  48. package/fesm2022/acorex-platform-layout-widgets.mjs.map +1 -1
  49. package/fesm2022/acorex-platform-native.mjs +7 -7
  50. package/fesm2022/acorex-platform-native.mjs.map +1 -1
  51. package/fesm2022/acorex-platform-runtime.mjs +40 -40
  52. package/fesm2022/acorex-platform-runtime.mjs.map +1 -1
  53. 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
  54. 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
  55. 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
  56. 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
  57. 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
  58. 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
  59. 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
  60. 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
  61. package/fesm2022/{acorex-platform-themes-default-error-401.component-CHJFmJ2W.mjs → acorex-platform-themes-default-error-401.component-CoBaQFTn.mjs} +4 -4
  62. package/fesm2022/{acorex-platform-themes-default-error-401.component-CHJFmJ2W.mjs.map → acorex-platform-themes-default-error-401.component-CoBaQFTn.mjs.map} +1 -1
  63. package/fesm2022/{acorex-platform-themes-default-error-404.component-Db8KkVIF.mjs → acorex-platform-themes-default-error-404.component-BLlVOsS2.mjs} +4 -4
  64. package/fesm2022/{acorex-platform-themes-default-error-404.component-Db8KkVIF.mjs.map → acorex-platform-themes-default-error-404.component-BLlVOsS2.mjs.map} +1 -1
  65. package/fesm2022/{acorex-platform-themes-default-error-offline.component-DH39Viy-.mjs → acorex-platform-themes-default-error-offline.component-CybYQI9F.mjs} +4 -4
  66. package/fesm2022/{acorex-platform-themes-default-error-offline.component-DH39Viy-.mjs.map → acorex-platform-themes-default-error-offline.component-CybYQI9F.mjs.map} +1 -1
  67. package/fesm2022/acorex-platform-themes-default.mjs +39 -39
  68. package/fesm2022/acorex-platform-themes-default.mjs.map +1 -1
  69. package/fesm2022/{acorex-platform-themes-shared-icon-chooser-view.component-mFBYGE0_.mjs → acorex-platform-themes-shared-icon-chooser-view.component-ReKSoVeN.mjs} +4 -4
  70. 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
  71. 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
  72. 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
  73. 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
  74. 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
  75. package/fesm2022/acorex-platform-themes-shared.mjs +42 -42
  76. package/fesm2022/acorex-platform-themes-shared.mjs.map +1 -1
  77. package/fesm2022/acorex-platform-workflow.mjs +25 -25
  78. package/fesm2022/acorex-platform-workflow.mjs.map +1 -1
  79. package/layout/builder/index.d.ts +458 -237
  80. package/layout/components/index.d.ts +130 -42
  81. package/layout/widget-core/index.d.ts +8 -1
  82. package/layout/widgets/index.d.ts +80 -2
  83. package/package.json +6 -6
  84. 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, Input, Component, NgModule } from '@angular/core';
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
- * Convert Layout Builder definition to Layout Renderer format
16
- * با processing و cleaning options
17
- */
18
- convertToLayoutRenderer(definition) {
19
- const layoutDefinition = {
20
- zones: {},
21
- styles: definition.layoutConfig ? this.convertGridLayoutToStyles(definition.layoutConfig) : {}
22
- };
23
- const layoutNodes = [];
24
- let nodeCounter = 0;
25
- // Helper: count all nodes recursively
26
- const countNodes = (nodes) => {
27
- if (!nodes || nodes.length === 0)
28
- return 0;
29
- return nodes.reduce((acc, n) => acc + 1 + countNodes(n.children), 0);
30
- };
31
- const totalNodeCount = countNodes(definition.nodes);
32
- const hasMultipleNodes = totalNodeCount > 1;
33
- // Process nodes recursively - flatten everything into the same zone
34
- const processNodes = (nodes, parentZone = 'main') => {
35
- nodes.forEach((node) => {
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
- // Create zones in order and assign auto-order to nodes (recursively)
71
- let nodeOrder = 1;
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
- assignOrder(definition.nodes);
84
- // Always create a default main zone when there are nodes
85
- if (totalNodeCount > 0) {
86
- layoutDefinition.zones['main'] = {
87
- layout: {
88
- type: 'flex',
89
- flexDirection: definition.direction === 'horizontal' ? 'row' : 'column',
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
- processNodes(definition.nodes);
96
- return {
97
- layoutDefinition,
98
- layoutNodes
99
- };
123
+ //this.state.children!.push(container.build());
124
+ this.root = container.build();
125
+ return this;
100
126
  }
101
- // Removed container mapping helpers - all nodes are treated uniformly
102
- /**
103
- * Convert grid layout options to CSS styles
104
- */
105
- convertGridLayoutToStyles(layout) {
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
- return styles;
133
+ this.root.children.push(container.build());
134
+ return this;
119
135
  }
120
- /**
121
- * Convert grid layout to node layout configuration
122
- */
123
- convertToNodeLayout(layout) {
124
- const nodeLayout = {};
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
- return nodeLayout;
142
+ this.root.children.push(container.build());
143
+ return this;
138
144
  }
139
- /**
140
- * Process widget options based on actual widget configurations
141
- */
142
- processWidgetOptions(layoutNode, sourceNode) {
143
- if (!sourceNode.widget?.options)
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
- layoutNode.options = processedOptions;
151
+ this.root.children.push(container.build());
152
+ return this;
182
153
  }
183
- /**
184
- * Process text-block-layout content
185
- */
186
- processTextBlockContent(options) {
187
- if (!options.content)
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
- // تبدیل plain text به HTML با styling مناسب
195
- if (options.type === 'heading' || options.level === 'h1' || options.level === 'h2') {
196
- const level = options.level || 'h2';
197
- return `<${level} style="margin: 0 0 16px 0; color: #333; font-weight: 600;">${content}</${level}>`;
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
- if (options.weight === 'bold' || options.bold) {
200
- return `<strong style="color: #333;">${content}</strong>`;
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
- // Default text styling
203
- return `<span style="color: #333; line-height: 1.5;">${content}</span>`;
177
+ return container;
204
178
  }
205
- /**
206
- * Process grid-layout options
207
- */
208
- processGridOptions(options) {
209
- return {
210
- default: {
211
- columns: options.columns || 12,
212
- rows: options.rows || 'auto',
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
- padding: options.spacing?.padding || options.padding || '0px',
228
- margin: options.spacing?.margin || options.margin || '0px'
190
+ type: this.root.type,
191
+ children: this.root.children,
192
+ mode: this.root.mode,
229
193
  };
230
194
  }
231
- /**
232
- * Process border options
233
- */
234
- processBorderOptions(options) {
235
- if (!options.border && !options.borderRadius && !options.borderWidth)
236
- return undefined;
237
- return {
238
- radius: options.border?.radius || options.borderRadius || '0px',
239
- width: options.border?.width || options.borderWidth || '0px',
240
- color: options.border?.color || options.borderColor || '#ddd',
241
- style: options.border?.style || options.borderStyle || 'solid'
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
- * Process flex-layout options
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
- * Process panel-layout options
258
- */
259
- processPanelOptions(options) {
260
- return {
261
- title: options.title || '',
262
- collapsible: options.collapsible || false,
263
- collapsed: options.collapsed || false,
264
- padding: options.padding || '16px'
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
- * Process tag content
269
- */
270
- processTagContent(options) {
271
- return options.label || options.content || 'Tag';
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
- * Process generic content
275
- */
276
- processGenericContent(options) {
277
- if (!options.content)
278
- return '';
279
- // اگر HTML tags داره، استفاده کن
280
- if (options.content.includes('<')) {
281
- return options.content;
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
- // Plain text با basic styling
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
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: AXPLayoutBuilderService, decorators: [{
290
- type: Injectable,
291
- args: [{
292
- providedIn: 'root'
293
- }]
294
- }] });
295
- //#region ---- Layout Builder Implementation ----
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
- layout(config) {
323
- this.state.layoutConfig = { ...this.state.layoutConfig, ...config };
299
+ path(path) {
300
+ this.containerState.path = path;
324
301
  return this;
325
302
  }
326
303
  mode(mode) {
327
- this.state.mode = mode;
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.state.direction = direction;
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
- look(look) {
335
- this.state.look = look;
336
+ // Inheritance context methods
337
+ withInheritanceContext(context) {
338
+ this.inheritanceContext = mergeInheritanceContext(context);
336
339
  return this;
337
340
  }
338
- // Predefined layout containers at root level - delegate pattern required
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.state.nodes.push(container.build());
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.state.nodes.push(container.build());
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.state.nodes.push(container.build());
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.state.nodes.push(container.build());
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
- this.state.nodes.push(container.build());
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
- // Direct widget methods at root level
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
- const widgetBuilder = new WidgetBuilder().textBox(options);
382
- this.state.nodes.push(widgetBuilder.build());
444
+ this.addWidget('text-editor', options);
383
445
  return this;
384
446
  }
385
447
  largeTextBox(options) {
386
- const widgetBuilder = new WidgetBuilder().largeTextBox(options);
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
- const widgetBuilder = new WidgetBuilder().richText(options);
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
- const widgetBuilder = new WidgetBuilder().passwordBox(options);
397
- this.state.nodes.push(widgetBuilder.build());
456
+ this.addWidget('password-editor', options);
398
457
  return this;
399
458
  }
400
459
  numberBox(options) {
401
- const widgetBuilder = new WidgetBuilder().numberBox(options);
402
- this.state.nodes.push(widgetBuilder.build());
460
+ this.addWidget('number-editor', options);
403
461
  return this;
404
462
  }
405
463
  selectBox(options) {
406
- const widgetBuilder = new WidgetBuilder().selectBox(options);
407
- this.state.nodes.push(widgetBuilder.build());
464
+ this.addWidget('select-editor', options);
408
465
  return this;
409
466
  }
410
467
  lookupBox(options) {
411
- const widgetBuilder = new WidgetBuilder().lookupBox(options);
412
- this.state.nodes.push(widgetBuilder.build());
468
+ this.addWidget('lookup-editor', options);
413
469
  return this;
414
470
  }
415
471
  selectionList(options) {
416
- const widgetBuilder = new WidgetBuilder().selectionList(options);
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
- const widgetBuilder = new WidgetBuilder().dateTimeBox(options);
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
- const widgetBuilder = new WidgetBuilder().toggleSwitch(options);
427
- this.state.nodes.push(widgetBuilder.build());
480
+ this.addWidget('toggle-editor', options);
428
481
  return this;
429
482
  }
430
483
  colorBox(options) {
431
- const widgetBuilder = new WidgetBuilder().colorBox(options);
432
- this.state.nodes.push(widgetBuilder.build());
484
+ this.addWidget('color-editor', options);
433
485
  return this;
434
486
  }
435
- customWidget(type, options) {
436
- const widgetBuilder = new WidgetBuilder().customWidget(type, options);
437
- this.state.nodes.push(widgetBuilder.build());
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
- build() {
441
- return {
442
- nodes: this.state.nodes,
443
- mode: this.state.mode,
444
- direction: this.state.direction,
445
- look: this.state.look,
446
- layoutConfig: this.state.layoutConfig,
447
- };
497
+ customWidget(type, options) {
498
+ this.addWidget(type, options);
499
+ return this;
448
500
  }
449
- render() {
450
- console.log('render() called, state:', JSON.parse(JSON.stringify(this.state)));
451
- if (!this.state.nodes || !Array.isArray(this.state.nodes)) {
452
- console.error('Invalid nodes in state:', this.state.nodes);
453
- return {};
454
- }
455
- try {
456
- const nodes = this.transformToWidgetNodes(this.state.nodes);
457
- console.log('render() transformed nodes:', JSON.parse(JSON.stringify(nodes)));
458
- // No nodes
459
- if (!nodes || nodes.length === 0) {
460
- return {};
461
- }
462
- // Single node → return directly
463
- if (nodes.length === 1) {
464
- return nodes[0];
465
- }
466
- // Multiple nodes → wrap in a container node with layout config
467
- const flexDirection = this.state.direction === 'horizontal' ? 'row' : 'column';
468
- const container = {
469
- type: 'flex-layout',
470
- name: 'root',
471
- options: {
472
- flexDirection,
473
- gap: '16px',
474
- },
475
- children: nodes,
476
- };
477
- return container;
478
- }
479
- catch (error) {
480
- console.error('Error in render():', error);
481
- return {};
482
- }
483
- }
484
- transformToWidgetNodes(nodes) {
485
- console.log('transformToWidgetNodes called with nodes:', nodes);
486
- const sorted = this.sortNodes(nodes);
487
- // Helper to compute flex-item options from grid layout positions
488
- const toFlexItemOptions = (layout, order) => {
489
- let colSpan;
490
- const positions = layout?.positions ?? this.state.layoutConfig?.positions;
491
- if (positions) {
492
- colSpan = positions.xxl?.colSpan || positions.xl?.colSpan || positions.lg?.colSpan || positions.md?.colSpan || positions.sm?.colSpan;
493
- }
494
- const basis = colSpan && colSpan > 0 && colSpan <= 12 ? `${(colSpan / 12) * 100}%` : undefined;
495
- const opt = {};
496
- if (basis)
497
- opt.basis = basis;
498
- if (order !== undefined)
499
- opt.order = typeof order === 'number' ? order : parseInt(order.toString());
500
- return opt;
501
- };
502
- const multipleSiblings = sorted.length > 1;
503
- return sorted.map((node) => {
504
- console.log('Processing node:', node);
505
- // Build the actual widget node
506
- const widget = {
507
- type: node.widget?.type || 'unknown',
508
- name: node.id,
509
- options: {
510
- ...node.widget?.options,
511
- mode: node.mode,
512
- visible: node.visible,
513
- disabled: node.disabled,
514
- readonly: node.readonly,
515
- },
516
- };
517
- if (node.children && node.children.length > 0) {
518
- widget.children = this.transformToWidgetNodes(this.sortNodes(node.children));
519
- }
520
- // If we have multiple siblings at this level, wrap each item in a flex-item container
521
- if (multipleSiblings) {
522
- const flexItemOptions = toFlexItemOptions(node.layout, node.order);
523
- const wrapped = {
524
- type: 'flex-item-layout',
525
- name: `${node.id || 'item'}-container`,
526
- options: {
527
- ...flexItemOptions,
528
- },
529
- children: [widget],
530
- };
531
- return wrapped;
532
- }
533
- return widget;
534
- });
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
- sortNodes(nodes) {
537
- if (!Array.isArray(nodes))
538
- return [];
539
- return nodes
540
- .map((n, idx) => ({ n, idx }))
541
- .sort((a, b) => {
542
- const ao = a.n.order;
543
- const bo = b.n.order;
544
- const aHas = ao !== undefined && ao !== null;
545
- const bHas = bo !== undefined && bo !== null;
546
- if (aHas && bHas) {
547
- if (typeof ao === 'number' && typeof bo === 'number')
548
- return ao - bo;
549
- return String(ao).localeCompare(String(bo));
550
- }
551
- if (aHas && !bHas)
552
- return -1;
553
- if (!aHas && bHas)
554
- return 1;
555
- return a.idx - b.idx;
556
- })
557
- .map(x => x.n);
558
- }
559
- }
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
- setFlexDirection(direction) {
515
+ setDirection(direction) {
655
516
  return this.setOptions({ flexDirection: direction });
656
517
  }
657
- setFlexWrap(wrap) {
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
- // Grid Container Builder
729
- class GridContainerBuilder extends BaseContainerBuilder {
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.widget.options = { ...this.containerState.widget.options, ...options };
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
- // Panel Container Builder
795
- class PanelContainerBuilder extends BaseContainerBuilder {
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.widget.options = { ...this.containerState.widget.options, ...options };
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
- // Page Container Builder
849
- class PageContainerBuilder extends BaseContainerBuilder {
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.widget.options = { ...this.containerState.widget.options, ...options };
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
- // Tabset Container Builder
903
- class TabsetContainerBuilder extends BaseContainerBuilder {
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.widget.options = { ...this.containerState.widget.options, ...options };
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
- //#endregion
951
- //#region ---- Widget Builder Implementation ----
952
- class WidgetBuilder {
953
- constructor(name) {
954
- this.widgetState = {
955
- type: 'widget',
956
- children: [],
957
- };
958
- if (name) {
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
- disabled(condition) {
998
- this.widgetState.disabled = condition;
672
+ setOptions(options) {
673
+ this.containerState.options = { ...this.containerState.options, ...options };
999
674
  return this;
1000
675
  }
1001
- readonly(condition) {
1002
- this.widgetState.readonly = condition;
1003
- return this;
676
+ setLabel(label) {
677
+ return this.setOptions({ label });
1004
678
  }
1005
- order(value) {
1006
- this.widgetState.order = value;
1007
- return this;
679
+ setShowLabel(showLabel) {
680
+ return this.setOptions({ showLabel });
1008
681
  }
1009
- // Container helpers - delegate pattern required
1010
- grid(delegate) {
1011
- const container = new GridContainerBuilder();
1012
- if (delegate) {
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 built = container.build();
1016
- this.widgetState.widget = built.widget;
1017
- this.widgetState.children = built.children;
1018
- return this;
1019
- }
1020
- flex(delegate) {
1021
- const container = new FlexContainerBuilder();
1022
- if (delegate) {
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
- const built = container.build();
1026
- this.widgetState.widget = built.widget;
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
- const built = container.build();
1036
- this.widgetState.widget = built.widget;
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
- const built = container.build();
1046
- this.widgetState.widget = built.widget;
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
- const built = container.build();
1056
- this.widgetState.widget = built.widget;
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
- this.widgetState.children = this.widgetState.children || [];
1066
- this.widgetState.children.push(child.build());
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
- if (this.isContainerWidgetType(this.widgetState.widget?.type)) {
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
- if (this.isContainerWidgetType(this.widgetState.widget?.type)) {
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
- if (this.isContainerWidgetType(this.widgetState.widget?.type)) {
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
- if (this.isContainerWidgetType(this.widgetState.widget?.type)) {
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
- if (this.isContainerWidgetType(this.widgetState.widget?.type)) {
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
- if (this.isContainerWidgetType(this.widgetState.widget?.type)) {
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
- if (this.isContainerWidgetType(this.widgetState.widget?.type)) {
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
- if (this.isContainerWidgetType(this.widgetState.widget?.type)) {
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
- if (this.isContainerWidgetType(this.widgetState.widget?.type)) {
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
- if (this.isContainerWidgetType(this.widgetState.widget?.type)) {
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
- if (this.isContainerWidgetType(this.widgetState.widget?.type)) {
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
- if (this.isContainerWidgetType(this.widgetState.widget?.type)) {
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
- class AXPLayoutRendererComponent {
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
- this.renderedNode = {};
766
+ super('fieldset-layout');
767
+ this.containerState.options = {};
1269
768
  }
1270
- ngOnInit() {
1271
- if (this.builder) {
1272
- this.renderedNode = {
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
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: AXPLayoutRendererComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1287
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.2.4", type: AXPLayoutRendererComponent, isStandalone: true, selector: "axp-layout-renderer", inputs: { builder: "builder" }, ngImport: i0, template: `
1288
- <axp-widgets-container>
1289
- <ng-container
1290
- axp-widget-renderer
1291
- [node]="renderedNode"
1292
- [mode]="builder?.mode ?? 'view'">
1293
- </ng-container>
1294
- </axp-widgets-container>
1295
- `, 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"] }] }); }
1296
- }
1297
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: AXPLayoutRendererComponent, decorators: [{
1298
- type: Component,
1299
- args: [{ selector: 'axp-layout-renderer', standalone: true, imports: [CommonModule, AXPWidgetCoreModule], template: `
1300
- <axp-widgets-container>
1301
- <ng-container
1302
- axp-widget-renderer
1303
- [node]="renderedNode"
1304
- [mode]="builder?.mode ?? 'view'">
1305
- </ng-container>
1306
- </axp-widgets-container>
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
- }], propDecorators: { builder: [{
1309
- type: Input
1310
- }] } });
1760
+ }] });
1311
1761
 
1312
1762
  class LayoutBuilderModule {
1313
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: LayoutBuilderModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
1314
- static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.2.4", ngImport: i0, type: LayoutBuilderModule, imports: [CommonModule, AXPLayoutRendererComponent], exports: [AXPLayoutRendererComponent] }); }
1315
- static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.2.4", ngImport: i0, type: LayoutBuilderModule, providers: [AXPLayoutBuilderService], imports: [CommonModule, AXPLayoutRendererComponent] }); }
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.2.4", ngImport: i0, type: LayoutBuilderModule, decorators: [{
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