@acorex/platform 20.3.0-next.9 → 20.4.0

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 (131) hide show
  1. package/common/index.d.ts +643 -365
  2. package/core/index.d.ts +19 -4
  3. package/fesm2022/acorex-platform-auth.mjs +19 -19
  4. package/fesm2022/acorex-platform-auth.mjs.map +1 -1
  5. package/fesm2022/acorex-platform-common-common-settings.provider-9OHien_H.mjs +47 -0
  6. package/fesm2022/acorex-platform-common-common-settings.provider-9OHien_H.mjs.map +1 -0
  7. package/fesm2022/acorex-platform-common.mjs +673 -242
  8. package/fesm2022/acorex-platform-common.mjs.map +1 -1
  9. package/fesm2022/acorex-platform-core.mjs +58 -46
  10. package/fesm2022/acorex-platform-core.mjs.map +1 -1
  11. package/fesm2022/acorex-platform-domain.mjs +16 -16
  12. package/fesm2022/acorex-platform-domain.mjs.map +1 -1
  13. package/fesm2022/acorex-platform-layout-builder.mjs +1933 -2330
  14. package/fesm2022/acorex-platform-layout-builder.mjs.map +1 -1
  15. package/fesm2022/acorex-platform-layout-components.mjs +1511 -1626
  16. package/fesm2022/acorex-platform-layout-components.mjs.map +1 -1
  17. package/fesm2022/acorex-platform-layout-designer.mjs +82 -82
  18. package/fesm2022/acorex-platform-layout-designer.mjs.map +1 -1
  19. package/fesm2022/acorex-platform-layout-entity-create-entity.command-DyXF9zAh.mjs +52 -0
  20. package/fesm2022/acorex-platform-layout-entity-create-entity.command-DyXF9zAh.mjs.map +1 -0
  21. package/fesm2022/acorex-platform-layout-entity.mjs +1371 -917
  22. package/fesm2022/acorex-platform-layout-entity.mjs.map +1 -1
  23. package/fesm2022/acorex-platform-layout-views.mjs +63 -54
  24. package/fesm2022/acorex-platform-layout-views.mjs.map +1 -1
  25. package/fesm2022/acorex-platform-layout-widget-core.mjs +2758 -0
  26. package/fesm2022/acorex-platform-layout-widget-core.mjs.map +1 -0
  27. package/fesm2022/{acorex-platform-widgets-button-widget-designer.component-C2Qn1YAW.mjs → acorex-platform-layout-widgets-button-widget-designer.component-C_3IWNkj.mjs} +6 -6
  28. package/fesm2022/acorex-platform-layout-widgets-button-widget-designer.component-C_3IWNkj.mjs.map +1 -0
  29. package/fesm2022/{acorex-platform-widgets-extra-properties-schema-widget-edit.component-D9mf08rU.mjs → acorex-platform-layout-widgets-extra-properties-schema-widget-edit.component-CJltEgut.mjs} +5 -5
  30. package/fesm2022/acorex-platform-layout-widgets-extra-properties-schema-widget-edit.component-CJltEgut.mjs.map +1 -0
  31. package/fesm2022/{acorex-platform-widgets-extra-properties-schema-widget-view.component-D6GQ-eyr.mjs → acorex-platform-layout-widgets-extra-properties-schema-widget-view.component-pM-TIuk0.mjs} +5 -5
  32. package/fesm2022/acorex-platform-layout-widgets-extra-properties-schema-widget-view.component-pM-TIuk0.mjs.map +1 -0
  33. package/fesm2022/{acorex-platform-widgets-extra-properties-values-widget-edit.component-DVbIdVZ6.mjs → acorex-platform-layout-widgets-extra-properties-values-widget-edit.component-BqI96-fU.mjs} +5 -5
  34. package/fesm2022/acorex-platform-layout-widgets-extra-properties-values-widget-edit.component-BqI96-fU.mjs.map +1 -0
  35. package/fesm2022/{acorex-platform-widgets-extra-properties-values-widget-view.component-D-aM64Hu.mjs → acorex-platform-layout-widgets-extra-properties-values-widget-view.component-C-AhenaM.mjs} +5 -5
  36. package/fesm2022/acorex-platform-layout-widgets-extra-properties-values-widget-view.component-C-AhenaM.mjs.map +1 -0
  37. package/fesm2022/{acorex-platform-widgets-extra-properties-widget-edit.component-em2-aU8E.mjs → acorex-platform-layout-widgets-extra-properties-widget-edit.component-DCAya5ne.mjs} +5 -5
  38. package/fesm2022/acorex-platform-layout-widgets-extra-properties-widget-edit.component-DCAya5ne.mjs.map +1 -0
  39. package/fesm2022/{acorex-platform-widgets-extra-properties-widget-view.component-BeuIofdr.mjs → acorex-platform-layout-widgets-extra-properties-widget-view.component-D-PnBqLb.mjs} +5 -5
  40. package/fesm2022/acorex-platform-layout-widgets-extra-properties-widget-view.component-D-PnBqLb.mjs.map +1 -0
  41. package/fesm2022/{acorex-platform-widgets-file-list-popup.component-Cmtq2bBV.mjs → acorex-platform-layout-widgets-file-list-popup.component-DuuFHWvB.mjs} +9 -9
  42. package/fesm2022/acorex-platform-layout-widgets-file-list-popup.component-DuuFHWvB.mjs.map +1 -0
  43. package/fesm2022/{acorex-platform-widgets-page-widget-designer.component-D8ivmxzT.mjs → acorex-platform-layout-widgets-page-widget-designer.component-Bss0xUcu.mjs} +8 -8
  44. package/fesm2022/acorex-platform-layout-widgets-page-widget-designer.component-Bss0xUcu.mjs.map +1 -0
  45. package/fesm2022/{acorex-platform-widgets-tabular-data-edit-popup.component-CMqq_iOj.mjs → acorex-platform-layout-widgets-tabular-data-edit-popup.component-Cy9mHnNP.mjs} +8 -8
  46. package/fesm2022/acorex-platform-layout-widgets-tabular-data-edit-popup.component-Cy9mHnNP.mjs.map +1 -0
  47. package/fesm2022/{acorex-platform-widgets-tabular-data-view-popup.component-Dmg5DdX8.mjs → acorex-platform-layout-widgets-tabular-data-view-popup.component-DznLtuer.mjs} +6 -5
  48. package/fesm2022/acorex-platform-layout-widgets-tabular-data-view-popup.component-DznLtuer.mjs.map +1 -0
  49. package/fesm2022/{acorex-platform-widgets-text-block-widget-designer.component-yADN3Xji.mjs → acorex-platform-layout-widgets-text-block-widget-designer.component-ndOUSFi9.mjs} +6 -7
  50. package/fesm2022/acorex-platform-layout-widgets-text-block-widget-designer.component-ndOUSFi9.mjs.map +1 -0
  51. package/fesm2022/{acorex-platform-widgets.mjs → acorex-platform-layout-widgets.mjs} +4153 -3146
  52. package/fesm2022/acorex-platform-layout-widgets.mjs.map +1 -0
  53. package/fesm2022/acorex-platform-native.mjs +7 -7
  54. package/fesm2022/acorex-platform-native.mjs.map +1 -1
  55. package/fesm2022/acorex-platform-runtime.mjs +40 -40
  56. package/fesm2022/acorex-platform-runtime.mjs.map +1 -1
  57. package/fesm2022/acorex-platform-themes-default-entity-master-create-view.component-B1PT6FtZ.mjs +115 -0
  58. package/fesm2022/acorex-platform-themes-default-entity-master-create-view.component-B1PT6FtZ.mjs.map +1 -0
  59. package/fesm2022/{acorex-platform-themes-default-entity-master-list-view.component-7BB4LdjK.mjs → acorex-platform-themes-default-entity-master-list-view.component-rdKxuMC_.mjs} +69 -33
  60. package/fesm2022/acorex-platform-themes-default-entity-master-list-view.component-rdKxuMC_.mjs.map +1 -0
  61. package/fesm2022/acorex-platform-themes-default-entity-master-modify-view.component-4g19A3eI.mjs +101 -0
  62. package/fesm2022/acorex-platform-themes-default-entity-master-modify-view.component-4g19A3eI.mjs.map +1 -0
  63. package/fesm2022/{acorex-platform-themes-default-entity-master-single-view.component-BExtm1JE.mjs → acorex-platform-themes-default-entity-master-single-view.component-B8gx5cG7.mjs} +17 -17
  64. package/fesm2022/acorex-platform-themes-default-entity-master-single-view.component-B8gx5cG7.mjs.map +1 -0
  65. package/fesm2022/{acorex-platform-themes-default-error-401.component-DrO1PEOH.mjs → acorex-platform-themes-default-error-401.component-CcvGfdhu.mjs} +4 -4
  66. package/fesm2022/{acorex-platform-themes-default-error-401.component-DrO1PEOH.mjs.map → acorex-platform-themes-default-error-401.component-CcvGfdhu.mjs.map} +1 -1
  67. package/fesm2022/{acorex-platform-themes-default-error-404.component-DqVq0oHX.mjs → acorex-platform-themes-default-error-404.component-4-CaEsnV.mjs} +4 -4
  68. package/fesm2022/{acorex-platform-themes-default-error-404.component-DqVq0oHX.mjs.map → acorex-platform-themes-default-error-404.component-4-CaEsnV.mjs.map} +1 -1
  69. package/fesm2022/{acorex-platform-themes-default-error-offline.component-Bt2PTL7_.mjs → acorex-platform-themes-default-error-offline.component-BNecbFEj.mjs} +4 -4
  70. package/fesm2022/{acorex-platform-themes-default-error-offline.component-Bt2PTL7_.mjs.map → acorex-platform-themes-default-error-offline.component-BNecbFEj.mjs.map} +1 -1
  71. package/fesm2022/acorex-platform-themes-default.mjs +117 -51
  72. package/fesm2022/acorex-platform-themes-default.mjs.map +1 -1
  73. package/fesm2022/{acorex-platform-themes-shared-icon-chooser-view.component-BgEh06Tn.mjs → acorex-platform-themes-shared-icon-chooser-view.component-Dc_Txe32.mjs} +5 -5
  74. package/fesm2022/acorex-platform-themes-shared-icon-chooser-view.component-Dc_Txe32.mjs.map +1 -0
  75. package/fesm2022/{acorex-platform-themes-shared-settings.provider-CLUKU4y0.mjs → acorex-platform-themes-shared-settings.provider-DY2xFnrv.mjs} +8 -8
  76. package/fesm2022/acorex-platform-themes-shared-settings.provider-DY2xFnrv.mjs.map +1 -0
  77. package/fesm2022/{acorex-platform-themes-shared-theme-color-chooser-column.component-AeOQxjbS.mjs → acorex-platform-themes-shared-theme-color-chooser-column.component-hgWLhhle.mjs} +5 -5
  78. package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-column.component-hgWLhhle.mjs.map +1 -0
  79. package/fesm2022/{acorex-platform-themes-shared-theme-color-chooser-view.component-DEVzRd6-.mjs → acorex-platform-themes-shared-theme-color-chooser-view.component-CY3JZK_W.mjs} +5 -5
  80. package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-view.component-CY3JZK_W.mjs.map +1 -0
  81. package/fesm2022/acorex-platform-themes-shared.mjs +66 -55
  82. package/fesm2022/acorex-platform-themes-shared.mjs.map +1 -1
  83. package/fesm2022/acorex-platform-workflow.mjs +27 -39
  84. package/fesm2022/acorex-platform-workflow.mjs.map +1 -1
  85. package/layout/builder/README.md +1577 -3
  86. package/layout/builder/index.d.ts +735 -868
  87. package/layout/components/index.d.ts +218 -714
  88. package/layout/designer/index.d.ts +4 -4
  89. package/layout/entity/index.d.ts +954 -375
  90. package/layout/views/index.d.ts +13 -14
  91. package/layout/widget-core/README.md +4 -0
  92. package/layout/widget-core/index.d.ts +959 -0
  93. package/layout/widgets/README.md +4 -0
  94. package/{widgets → layout/widgets}/index.d.ts +426 -365
  95. package/package.json +18 -14
  96. package/themes/shared/index.d.ts +2 -2
  97. package/workflow/index.d.ts +3 -173
  98. package/fesm2022/acorex-platform-themes-default-entity-master-create-view.component-Ct-ri59W.mjs +0 -115
  99. package/fesm2022/acorex-platform-themes-default-entity-master-create-view.component-Ct-ri59W.mjs.map +0 -1
  100. package/fesm2022/acorex-platform-themes-default-entity-master-list-view.component-7BB4LdjK.mjs.map +0 -1
  101. package/fesm2022/acorex-platform-themes-default-entity-master-modify-view.component-BDJR088o.mjs +0 -101
  102. package/fesm2022/acorex-platform-themes-default-entity-master-modify-view.component-BDJR088o.mjs.map +0 -1
  103. package/fesm2022/acorex-platform-themes-default-entity-master-single-view.component-BExtm1JE.mjs.map +0 -1
  104. package/fesm2022/acorex-platform-themes-shared-icon-chooser-view.component-BgEh06Tn.mjs.map +0 -1
  105. package/fesm2022/acorex-platform-themes-shared-settings.provider-CLUKU4y0.mjs.map +0 -1
  106. package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-column.component-AeOQxjbS.mjs.map +0 -1
  107. package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-view.component-DEVzRd6-.mjs.map +0 -1
  108. package/fesm2022/acorex-platform-widgets-button-widget-designer.component-C2Qn1YAW.mjs.map +0 -1
  109. package/fesm2022/acorex-platform-widgets-checkbox-widget-column.component-CzEFmKWG.mjs +0 -84
  110. package/fesm2022/acorex-platform-widgets-checkbox-widget-column.component-CzEFmKWG.mjs.map +0 -1
  111. package/fesm2022/acorex-platform-widgets-checkbox-widget-designer.component-BXPrXy-h.mjs +0 -55
  112. package/fesm2022/acorex-platform-widgets-checkbox-widget-designer.component-BXPrXy-h.mjs.map +0 -1
  113. package/fesm2022/acorex-platform-widgets-checkbox-widget-view.component-KYCQ2qTJ.mjs +0 -92
  114. package/fesm2022/acorex-platform-widgets-checkbox-widget-view.component-KYCQ2qTJ.mjs.map +0 -1
  115. package/fesm2022/acorex-platform-widgets-color-box-widget-designer.component-BVZ7lWm9.mjs +0 -55
  116. package/fesm2022/acorex-platform-widgets-color-box-widget-designer.component-BVZ7lWm9.mjs.map +0 -1
  117. package/fesm2022/acorex-platform-widgets-extra-properties-schema-widget-edit.component-D9mf08rU.mjs.map +0 -1
  118. package/fesm2022/acorex-platform-widgets-extra-properties-schema-widget-view.component-D6GQ-eyr.mjs.map +0 -1
  119. package/fesm2022/acorex-platform-widgets-extra-properties-values-widget-edit.component-DVbIdVZ6.mjs.map +0 -1
  120. package/fesm2022/acorex-platform-widgets-extra-properties-values-widget-view.component-D-aM64Hu.mjs.map +0 -1
  121. package/fesm2022/acorex-platform-widgets-extra-properties-widget-edit.component-em2-aU8E.mjs.map +0 -1
  122. package/fesm2022/acorex-platform-widgets-extra-properties-widget-view.component-BeuIofdr.mjs.map +0 -1
  123. package/fesm2022/acorex-platform-widgets-file-list-popup.component-Cmtq2bBV.mjs.map +0 -1
  124. package/fesm2022/acorex-platform-widgets-page-widget-designer.component-D8ivmxzT.mjs.map +0 -1
  125. package/fesm2022/acorex-platform-widgets-rich-text-popup.component-Cydlpsat.mjs +0 -40
  126. package/fesm2022/acorex-platform-widgets-rich-text-popup.component-Cydlpsat.mjs.map +0 -1
  127. package/fesm2022/acorex-platform-widgets-tabular-data-edit-popup.component-CMqq_iOj.mjs.map +0 -1
  128. package/fesm2022/acorex-platform-widgets-tabular-data-view-popup.component-Dmg5DdX8.mjs.map +0 -1
  129. package/fesm2022/acorex-platform-widgets-text-block-widget-designer.component-yADN3Xji.mjs.map +0 -1
  130. package/fesm2022/acorex-platform-widgets.mjs.map +0 -1
  131. package/widgets/README.md +0 -4
@@ -1,2500 +1,2103 @@
1
- import * as i0 from '@angular/core';
2
- import { signal, computed, Injectable, InjectionToken, inject, ElementRef, effect, Injector, ChangeDetectorRef, ViewChild, Input, ChangeDetectionStrategy, Component, EventEmitter, Output, input, output, ViewContainerRef, Directive, Optional, Inject, NgModule } from '@angular/core';
3
- import { convertArrayToDataSource, AXDataSource } from '@acorex/cdk/common';
4
- import { setSmart, AXPDataSourceDefinitionProviderService, extractValue, getSmart, AXPExpressionEvaluatorService } from '@acorex/platform/core';
5
- import { set, cloneDeep, isEqual, get, merge, isNil, isUndefined, isObjectLike, sum, isEmpty, isString } from 'lodash-es';
6
- import { Subject, BehaviorSubject, filter } from 'rxjs';
7
- import { signalStore, withState, withComputed, withMethods, patchState } from '@ngrx/signals';
8
- import * as i1$1 from '@acorex/components/skeleton';
9
- import { AXSkeletonModule } from '@acorex/components/skeleton';
10
- import * as i2 from '@acorex/core/translation';
11
- import { AXTranslationService, AXTranslationModule } from '@acorex/core/translation';
12
- import { PortalModule } from '@angular/cdk/portal';
13
- import * as i1 from '@angular/common';
1
+ import * as i1$1 from '@angular/common';
14
2
  import { CommonModule } from '@angular/common';
15
- import { AXDataTableColumnComponent, AXBaseDataTable } from '@acorex/components/data-table';
16
- import { AXUnsubscriber } from '@acorex/core/utils';
17
-
18
- var AXPPageStatus;
19
- (function (AXPPageStatus) {
20
- // Idle statuses
21
- AXPPageStatus["Idle"] = "idle";
22
- // Rendering statuses
23
- AXPPageStatus["Rendering"] = "rendering";
24
- AXPPageStatus["Rendered"] = "rendered";
25
- // Processing statuses
26
- AXPPageStatus["Processing"] = "processing";
27
- // Submission statuses
28
- AXPPageStatus["Submitting"] = "submitting";
29
- AXPPageStatus["Submitted"] = "submitted";
30
- // Validation statuses
31
- AXPPageStatus["Validating"] = "validating";
32
- AXPPageStatus["Validated"] = "validated";
33
- // Error handling
34
- AXPPageStatus["Error"] = "error";
35
- })(AXPPageStatus || (AXPPageStatus = {}));
36
- var AXPWidgetStatus;
37
- (function (AXPWidgetStatus) {
38
- // Rendering statuses
39
- AXPWidgetStatus["Rendering"] = "rendering";
40
- AXPWidgetStatus["Rendered"] = "rendered";
41
- // Processing statuses
42
- AXPWidgetStatus["Processing"] = "processing";
43
- // Error handling
44
- AXPWidgetStatus["Error"] = "error";
45
- })(AXPWidgetStatus || (AXPWidgetStatus = {}));
46
-
47
- class AXPLayoutElement {
48
- api() {
49
- return {};
50
- }
51
- }
52
- class AXPLayoutBuilderService {
53
- constructor() {
54
- this.variables$ = signal({}, ...(ngDevMode ? [{ debugName: "variables$" }] : []));
55
- this.functions$ = signal({}, ...(ngDevMode ? [{ debugName: "functions$" }] : []));
56
- this.onRefresh = new Subject();
57
- this.widgets = new Map();
58
- this.onWidgetRegistered = new Subject();
59
- this.status$ = signal(AXPPageStatus.Rendering, ...(ngDevMode ? [{ debugName: "status$" }] : []));
60
- this.status = this.status$.asReadonly();
61
- this.isBusy = computed(() => {
62
- return [AXPPageStatus.Processing, AXPPageStatus.Submitting, AXPPageStatus.Rendering].includes(this.status());
63
- }, ...(ngDevMode ? [{ debugName: "isBusy" }] : []));
64
- }
65
- get variables() {
66
- return this.variables$();
67
- }
68
- get functions() {
69
- return this.functions$();
70
- }
71
- updateStatus() {
72
- this.status$.update(() => this.detectStatus());
73
- }
74
- detectStatus() {
75
- const statuses = Array.from(this.widgets.values()).map((c) => c.status());
76
- // Rendering statuses
77
- if (statuses.some((status) => status === AXPWidgetStatus.Rendering)) {
78
- return AXPPageStatus.Rendering;
79
- }
80
- if (statuses.every((status) => status === AXPWidgetStatus.Rendered)) {
81
- return AXPPageStatus.Rendered;
82
- }
83
- // Processing statuses
84
- if (statuses.some((status) => status === AXPWidgetStatus.Processing)) {
85
- return AXPPageStatus.Processing;
86
- }
87
- // Error handling
88
- if (statuses.some((status) => status === AXPWidgetStatus.Error)) {
89
- return AXPPageStatus.Error;
90
- }
91
- return AXPPageStatus.Rendered; // Default to Loaded when all widgets are in a completed state
92
- }
93
- refresh() {
94
- setTimeout(() => {
95
- this.onRefresh.next();
96
- }, 0);
97
- }
98
- setStatus(status) {
99
- this.status$.set(status);
100
- }
101
- setVariables(...args) {
102
- if (args.length == 0)
103
- return;
104
- else if (args.length == 1)
105
- this.variables$.set(args[0]);
106
- else if (args.length == 2) {
107
- this.variables$.update((v) => set(v, args[0], args[1]));
108
- }
109
- }
110
- setFunctions(...args) {
111
- if (args.length == 0)
112
- return;
113
- else if (args.length == 1)
114
- this.functions$.set(args[0]);
115
- else if (args.length == 2) {
116
- this.functions$.update((v) => set(v, args[0], args[1]));
117
- }
118
- }
119
- registerWidget(id, widget) {
120
- this.widgets.set(id, widget);
121
- this.onWidgetRegistered.next({ id, widget });
122
- }
123
- getWidget(id) {
124
- return this.widgets.get(id);
125
- }
126
- /**
127
- * Waits until a widget with the given id is registered, then resolves with it.
128
- * If the widget is already registered, resolves immediately.
129
- * Optionally accepts a timeout (in ms) after which it resolves with undefined.
130
- */
131
- async waitForWidget(id, timeoutMs) {
132
- const existing = this.widgets.get(id);
133
- if (existing) {
134
- return existing;
135
- }
136
- return new Promise((resolve) => {
137
- let resolved = false;
138
- let timer = null;
139
- const sub = this.onWidgetRegistered.subscribe(({ id: registeredId, widget }) => {
140
- if (registeredId === id && !resolved) {
141
- resolved = true;
142
- sub.unsubscribe();
143
- if (timer) {
144
- clearTimeout(timer);
145
- }
146
- resolve(widget);
147
- }
148
- });
149
- if (timeoutMs != null && timeoutMs > 0) {
150
- timer = setTimeout(() => {
151
- if (!resolved) {
152
- resolved = true;
153
- sub.unsubscribe();
154
- resolve(undefined);
155
- }
156
- }, timeoutMs);
157
- }
158
- });
159
- }
160
- ngOnDestroy() { }
161
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPLayoutBuilderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
162
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPLayoutBuilderService }); }
163
- }
164
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPLayoutBuilderService, decorators: [{
165
- type: Injectable
166
- }] });
167
-
168
- class AXPLayoutContextChangeEvent {
169
- }
170
- const AXPLayoutBuilderContextStore = signalStore(
171
- // Initial State
172
- withState(() => ({
173
- data: {}, // Shared context data
174
- state: 'initiated', // Current state
175
- initialSnapshot: {}, // Snapshot of the first initialized state
176
- previousSnapshot: {}, // Snapshot of the previous state
177
- lastChange: {
178
- state: 'initiated',
179
- }, // Last change event
180
- })),
181
- // Computed Signals
182
- withComputed(({ data, state, lastChange, initialSnapshot, previousSnapshot }) => ({
183
- isChanged: computed(() => state() === 'changed'),
184
- isReset: computed(() => state() === 'restored'),
185
- isInitiated: computed(() => state() === 'initiated'),
186
- isEmpty: computed(() => Object.keys(data()).length === 0),
187
- isDirty: computed(() => !isEqual(data(), previousSnapshot())),
188
- snapshot: computed(() => cloneDeep(data())), // Current data snapshot
189
- initial: computed(() => cloneDeep(initialSnapshot())), // Initial snapshot
190
- previous: computed(() => cloneDeep(previousSnapshot())), // Previous snapshot
191
- changeEvent: computed(() => lastChange()), // Reactive last change event
192
- })),
193
- // Methods for State Management
194
- withMethods((store) => ({
195
- // Update a specific value
196
- update(path, value) {
197
- const currentData = cloneDeep(store.data());
198
- const oldValue = get(currentData, path);
199
- // Skip if the value hasn't changed
200
- if (isEqual(oldValue, value)) {
201
- return;
202
- }
203
- // Update the value and prepare the change event
204
- const updatedData = setSmart(currentData, path, value);
205
- const changeEvent = {
206
- oldValue,
207
- newValue: value,
208
- path,
209
- state: 'changed',
210
- data: updatedData,
211
- };
212
- // Patch the state
213
- patchState(store, {
214
- previousSnapshot: store.snapshot(), // Save the previous state
215
- data: updatedData,
216
- state: 'changed',
217
- lastChange: changeEvent,
218
- });
219
- },
220
- patch(context) {
221
- const currentData = cloneDeep(store.data());
222
- // Update the value and prepare the change event
223
- const updatedData = { ...currentData, ...context };
224
- const changeEvent = {
225
- state: 'patch',
226
- data: updatedData,
227
- };
228
- // Patch the state
229
- patchState(store, {
230
- previousSnapshot: store.snapshot(), // Save the previous state
231
- data: updatedData,
232
- state: 'changed',
233
- lastChange: changeEvent,
234
- });
235
- },
236
- // Reset to the initial state
237
- reset() {
238
- const initialData = store.initial();
239
- const changeEvent = {
240
- oldValue: cloneDeep(store.data()), // Current data becomes old value
241
- newValue: cloneDeep(initialData), // Reset to the initial state
242
- path: '',
243
- state: 'restored',
244
- data: initialData,
245
- };
246
- patchState(store, {
247
- previousSnapshot: store.snapshot(), // Save the previous state
248
- data: initialData,
249
- state: 'restored',
250
- lastChange: changeEvent,
251
- });
252
- },
253
- // Initialize the state
254
- set(initialData) {
255
- const currentData = store.data();
256
- if (isEqual(currentData, initialData)) {
257
- return; // Skip if the current state matches the initial state
258
- }
259
- const changeEvent = {
260
- oldValue: null,
261
- newValue: cloneDeep(initialData),
262
- path: '',
263
- state: 'initiated',
264
- data: initialData,
265
- };
266
- patchState(store, {
267
- initialSnapshot: cloneDeep(initialData), // Save the initial state
268
- previousSnapshot: store.snapshot(), // Save the current state as the previous
269
- data: initialData,
270
- state: 'initiated',
271
- lastChange: changeEvent,
272
- });
273
- },
274
- // Get a specific value
275
- getValue(path) {
276
- return get(store.data(), path);
277
- },
278
- })));
279
-
280
- const AXPWidgetsCatalog = {
281
- timeDuration: 'time-duration',
282
- tagable: 'tagable-editor',
283
- checkbox: 'checkbox-editor',
284
- color: 'color-editor',
285
- contact: 'contact-editor',
286
- dateTime: 'date-time-editor',
287
- largeText: 'large-text-editor',
288
- number: 'number-editor',
289
- numberUnit: 'number-unit-editor',
290
- password: 'password-editor',
291
- richText: 'rich-text-editor',
292
- select: 'select-editor',
293
- selectionList: 'selection-list-editor',
294
- text: 'text-editor',
295
- table: 'table-editor',
296
- toggle: 'toggle-editor',
297
- blockLayout: 'block-layout',
298
- pageLayout: 'page-layout',
299
- repeaterLayout: 'repeater-layout',
300
- textBlockLayout: 'text-block-layout',
301
- fileUploader: 'file-uploader',
302
- fileTypeExtension: 'file-type-extension',
303
- map: 'map',
304
- imageMarker: 'image-marker',
305
- image: 'image',
306
- gallery: 'gallery',
307
- signature: 'signature',
308
- buttonAction: 'button-action',
309
- document: 'document-layout',
310
- lookup: 'lookup-editor',
311
- formField: 'form-field',
312
- qrcode: 'qrcode',
313
- advancedGrid: 'advanced-grid-layout',
314
- advancedGridItem: 'advanced-grid-item-layout',
315
- grid: 'grid-layout',
316
- gridItem: 'grid-item-layout',
317
- // gridRow: 'grid-row-layout',
318
- widgetSelector: 'widget-selector',
319
- template: 'template',
320
- templateDesigner: 'template-designer',
321
- cronJob: 'cron-job',
322
- spacing: 'spacing',
323
- direction: 'direction',
324
- border: 'border',
325
- flexLayout: 'flex-layout',
326
- flexItem: 'flex-item-layout',
327
- tableLayout: 'table-layout',
328
- tableItem: 'table-item-layout',
329
- avatar: 'avatar',
330
- themePaletteChooser: 'theme-palette-chooser',
331
- themeModeChooser: 'theme-mode-chooser',
332
- menuOrientationChooser: 'menu-orientation-chooser',
333
- fontStyleChooser: 'font-style-chooser',
334
- fontSizeChooser: 'font-size-chooser',
335
- iconChooser: 'icon-chooser',
336
- themeColorChooser: 'theme-color-chooser',
337
- gridOptions: 'grid-options',
338
- gridItemOptions: 'grid-item-options',
339
- advancedGridOptions: 'advanced-grid-options',
340
- stringFilter: 'string-filter',
341
- numberFilter: 'number-filter',
342
- dateTimeFilter: 'datetime-filter',
343
- booleanFilter: 'boolean-filter',
344
- lookupFilter: 'lookup-filter',
345
- flexOptions: 'flex-options',
346
- flexItemOptions: 'flex-item-options',
347
- selectFilter: 'select-filter',
348
- requiredValidation: 'required-validation',
349
- regularExpressionValidation: 'regular-expression-validation',
350
- minLengthValidation: 'min-length-validation',
351
- maxLengthValidation: 'max-length-validation',
352
- lessThanValidation: 'less-than-validation',
353
- greaterThanValidation: 'greater-than-validation',
354
- betweenValidation: 'between-validation',
355
- equalValidation: 'equal-validation',
356
- callbackValidation: 'callback-validation',
357
- donutChart: 'donut-chart',
358
- lineChart: 'line-chart',
359
- barChart: 'bar-chart',
360
- gaugeChart: 'gauge-chart',
361
- stickyNote: 'sticky-note',
362
- clockCalendar: 'clock-calendar',
363
- analogClock: 'analog-clock',
364
- weather: 'weather',
365
- minimalWeather: 'minimal-weather',
366
- advancedWeather: 'advanced-weather',
367
- metaData: 'meta-data-editor',
368
- templateEditor: 'template-box-editor',
369
- panel: 'panel',
370
- notification: 'notification',
371
- taskBoard: 'task-board',
372
- comment: 'comment',
373
- list: 'list',
374
- listToolbar: 'list-toolbar',
375
- entityList: 'entity-list',
376
- documentUploader: 'document-uploader',
377
- };
3
+ import * as i0 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';
6
+ import * as i2 from '@acorex/components/form';
7
+ import { AXFormComponent, AXFormModule } from '@acorex/components/form';
8
+ import { AXPExpressionEvaluatorService } from '@acorex/platform/core';
9
+ import * as i1 from '@acorex/platform/layout/widget-core';
10
+ import { AXPWidgetContainerComponent, AXPPageStatus, AXPWidgetCoreModule } from '@acorex/platform/layout/widget-core';
11
+ import { isEqual, get, cloneDeep } from 'lodash-es';
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';
378
22
 
379
- function cloneProperty(property, values) {
380
- return merge(cloneDeep(property), values);
381
- }
382
- function createStringProperty(ctor) {
23
+ //#region ---- Inheritance Utilities ----
24
+ /**
25
+ * Resolves inherited properties from context and local values
26
+ */
27
+ function resolveInheritedProperties(context, localValues = {}) {
383
28
  return {
384
- name: ctor.name,
385
- title: ctor.title,
386
- group: ctor.group,
387
- schema: {
388
- dataType: 'string',
389
- defaultValue: ctor.defaultValue,
390
- interface: {
391
- name: ctor.name,
392
- path: ctor.path ?? ctor.name,
393
- type: AXPWidgetsCatalog.text,
394
- },
395
- },
396
- visible: !isNil(ctor.visible) ? ctor.visible : true,
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,
397
34
  };
398
35
  }
399
- function createNumberProperty(ctor) {
36
+ /**
37
+ * Merges inheritance context with local overrides
38
+ */
39
+ function mergeInheritanceContext(parentContext, localOverrides = {}) {
400
40
  return {
401
- name: ctor.name,
402
- title: ctor.title,
403
- group: ctor.group,
404
- schema: {
405
- dataType: 'number',
406
- defaultValue: ctor.defaultValue,
407
- interface: {
408
- name: ctor.name,
409
- path: ctor.path ?? ctor.name,
410
- type: AXPWidgetsCatalog.number,
411
- options: ctor.options,
412
- },
413
- },
414
- visible: !isNil(ctor.visible) ? ctor.visible : true,
41
+ ...parentContext,
42
+ ...localOverrides,
415
43
  };
416
44
  }
417
- function createBooleanProperty(ctor) {
418
- return {
419
- name: ctor.name,
420
- title: ctor.title,
421
- group: ctor.group,
422
- schema: {
423
- dataType: 'boolean',
424
- defaultValue: ctor.defaultValue ?? false,
425
- interface: {
426
- name: ctor.name,
427
- path: ctor.path ?? ctor.name,
428
- type: AXPWidgetsCatalog.toggle,
429
- },
430
- },
431
- visible: ctor.visible ?? true,
432
- };
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);
433
50
  }
434
- function createSelectProperty(ctor) {
435
- return {
436
- name: ctor.name,
437
- title: ctor.title,
438
- group: ctor.group,
439
- schema: {
440
- dataType: 'string',
441
- defaultValue: Array.isArray(ctor.defaultValue)
442
- ? ctor.defaultValue.map((item) => (typeof item === 'string' ? { id: item } : item))
443
- : typeof ctor.defaultValue === 'string'
444
- ? { id: ctor.defaultValue, title: ctor.defaultValue }
445
- : ctor.defaultValue,
446
- interface: {
447
- name: ctor.name,
448
- path: ctor.path ?? ctor.name,
449
- type: AXPWidgetsCatalog.select,
450
- options: {
451
- dataSource: ctor.dataSource.map((item) => (typeof item === 'string' ? { id: item, title: item } : item)),
452
- },
453
- },
454
- },
455
- visible: ctor.visible ?? true,
456
- };
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
457
60
  }
458
- const AXP_WIDGET_TOKEN = new InjectionToken('AXP_WIDGET_TOKEN');
459
- const AXP_WIDGET_COLUMN_TOKEN = new InjectionToken('AXP_WIDGET_COLUMN_TOKEN');
460
-
461
- class AXPBaseWidgetComponent extends AXPLayoutElement {
462
- constructor() {
463
- super(...arguments);
464
- this.token = inject(AXP_WIDGET_TOKEN);
465
- this.host = inject(ElementRef).nativeElement;
466
- this.layoutService = inject(AXPLayoutBuilderService);
467
- this.contextService = inject(AXPLayoutBuilderContextStore);
468
- this.config = this.token.config;
469
- this.node = this.token.node;
470
- this.name = this.token.node.name;
471
- this.component = this;
472
- this._options = signal(this.token.options ?? {}, ...(ngDevMode ? [{ debugName: "_options" }] : []));
473
- this.options = this._options.asReadonly();
474
- this.onOptionsChanged = new Subject();
475
- this._status = signal(AXPWidgetStatus.Rendering, ...(ngDevMode ? [{ debugName: "_status" }] : []));
476
- this.status = this._status.asReadonly();
477
- this.onStatusChanged = new BehaviorSubject(this._status());
478
- this.#statusEffect = effect(() => {
479
- this.onStatusChanged.next(this.status());
480
- }, ...(ngDevMode ? [{ debugName: "#statusEffect" }] : []));
481
- this.isBusy = computed(() => [AXPWidgetStatus.Rendering, AXPWidgetStatus.Processing].includes(this.status()), ...(ngDevMode ? [{ debugName: "isBusy" }] : []));
482
- this._children = signal(this.token.node.children ?? [], ...(ngDevMode ? [{ debugName: "_children" }] : []));
483
- this.children = this._children.asReadonly();
484
- }
485
- get id() {
486
- return this._id;
487
- }
488
- #statusEffect;
489
- outputs() {
490
- return [];
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;
491
68
  }
492
- ngOnInit() {
493
- if (get(this.node, '__meta__.added')) {
494
- this.onAdded();
495
- }
496
- this.setStatus(AXPWidgetStatus.Rendered);
497
- }
498
- setStatus(status) {
499
- this._status.set(status);
500
- this.layoutService.updateStatus();
501
- }
502
- setOptions(values) {
503
- const oldValue = this.options();
504
- const value = cloneDeep(values);
505
- this._options.set({ ...oldValue, ...value });
506
- this.onOptionsChanged.next({ sender: this });
507
- }
508
- output(name) {
509
- const outputs = this.outputs().map((c) => (typeof c == 'string' ? { name: c, value: c } : c));
510
- if (outputs.some((c) => c.name == name)) {
511
- const opt = get(this, name);
512
- if (typeof opt == 'function') {
513
- return opt();
514
- }
515
- return opt;
516
- }
517
- return null;
69
+ if (formFieldName) {
70
+ return formFieldName;
518
71
  }
519
- call(name, ...args) {
520
- const fn = get(this, name);
521
- if (fn && typeof fn == 'function') {
522
- fn.bind(this)(...args);
523
- }
72
+ if (formFieldLabel) {
73
+ return labelToPath(formFieldLabel);
74
+ }
75
+ return generateRandomId();
76
+ }
77
+ //#endregion
78
+ //#region ---- Service Implementation ----
79
+ class AXPLayoutBuilderService {
80
+ constructor() {
81
+ this.popupService = inject(AXPopupService);
524
82
  }
525
- setChildren(children) {
526
- this._children.set([...children]);
83
+ /**
84
+ * Create a new layout builder
85
+ */
86
+ create() {
87
+ return new LayoutBuilder(this.popupService);
527
88
  }
528
- onAdded() { }
529
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPBaseWidgetComponent, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
530
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPBaseWidgetComponent }); }
89
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: AXPLayoutBuilderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
90
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: AXPLayoutBuilderService, providedIn: 'root' }); }
531
91
  }
532
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPBaseWidgetComponent, decorators: [{
533
- type: Injectable
92
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: AXPLayoutBuilderService, decorators: [{
93
+ type: Injectable,
94
+ args: [{
95
+ providedIn: 'root',
96
+ }]
534
97
  }] });
535
- class AXPLayoutBaseWidgetComponent extends AXPBaseWidgetComponent {
536
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPLayoutBaseWidgetComponent, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
537
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPLayoutBaseWidgetComponent }); }
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',
111
+ };
112
+ this.inheritanceContext = {
113
+ mode: 'edit',
114
+ };
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);
122
+ }
123
+ //this.state.children!.push(container.build());
124
+ this.root = container.build();
125
+ return this;
126
+ }
127
+ flex(delegate) {
128
+ const container = new FlexContainerBuilder();
129
+ container.withInheritanceContext(this.inheritanceContext);
130
+ if (delegate) {
131
+ delegate(container);
132
+ }
133
+ this.root.children.push(container.build());
134
+ return this;
135
+ }
136
+ panel(delegate) {
137
+ const container = new PanelContainerBuilder();
138
+ container.withInheritanceContext(this.inheritanceContext);
139
+ if (delegate) {
140
+ delegate(container);
141
+ }
142
+ this.root.children.push(container.build());
143
+ return this;
144
+ }
145
+ page(delegate) {
146
+ const container = new PageContainerBuilder();
147
+ container.withInheritanceContext(this.inheritanceContext);
148
+ if (delegate) {
149
+ delegate(container);
150
+ }
151
+ this.root.children.push(container.build());
152
+ return this;
153
+ }
154
+ tabset(delegate) {
155
+ const container = new TabsetContainerBuilder();
156
+ container.withInheritanceContext(this.inheritanceContext);
157
+ if (delegate) {
158
+ delegate(container);
159
+ }
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);
168
+ }
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);
176
+ }
177
+ return container;
178
+ }
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;
187
+ }
188
+ build() {
189
+ return {
190
+ type: this.root.type,
191
+ children: this.root.children,
192
+ mode: this.root.mode,
193
+ };
194
+ }
538
195
  }
539
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPLayoutBaseWidgetComponent, decorators: [{
540
- type: Injectable
541
- }] });
542
- class AXPValueWidgetComponent extends AXPLayoutBaseWidgetComponent {
543
- constructor() {
544
- super(...arguments);
545
- this.path = this.token.node.path;
546
- this.defaultValue = this.token.defaultValue ?? this.token.node.defaultValue;
547
- this._isValueWidget = false;
548
- this.isValueWidget = () => this._isValueWidget;
549
- this.onValueChanged = new Subject();
550
- this.fullPath = signal(null, ...(ngDevMode ? [{ debugName: "fullPath" }] : []));
551
- this.parentPath = signal(null, ...(ngDevMode ? [{ debugName: "parentPath" }] : []));
552
- this.getValue = computed(() => {
553
- return this.fullPath() ? this.extractValue(this.fullPath()) : null;
554
- }, ...(ngDevMode ? [{ debugName: "getValue", equal: isEqual }] : [{ equal: isEqual }]));
555
- this.validationRules = computed(() => {
556
- const validationsRaw = this.options()['validations'];
557
- if (validationsRaw == null) {
558
- return [];
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: [],
208
+ };
209
+ this.inheritanceContext = {};
210
+ this.containerState.type = containerType;
211
+ }
212
+ // Base methods shared by all containers
213
+ ensureChildren() {
214
+ this.containerState.children = this.containerState.children || [];
215
+ }
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
+ // IMPORTANT: Apply inheritance context BEFORE setting options
236
+ // This ensures that inherited properties (readonly, disabled, etc.) are applied first
237
+ // Then user-provided options will override them if explicitly set
238
+ child.withInheritanceContext(this.inheritanceContext);
239
+ child.options(cleanOptions);
240
+ this.ensureChildren();
241
+ this.containerState.children.push(child.build());
242
+ return this;
243
+ }
244
+ isValueWidget(type) {
245
+ const valueWidgetTypes = [
246
+ 'text-editor',
247
+ 'large-text-editor',
248
+ 'rich-text-editor',
249
+ 'password-editor',
250
+ 'number-editor',
251
+ 'select-editor',
252
+ 'lookup-editor',
253
+ 'selection-list-editor',
254
+ 'date-time-editor',
255
+ 'toggle-editor',
256
+ 'color-editor',
257
+ ];
258
+ return valueWidgetTypes.includes(type);
259
+ }
260
+ build() {
261
+ const result = {
262
+ type: this.containerState.type,
263
+ children: this.containerState.children,
264
+ options: this.containerState.options,
265
+ mode: this.containerState.mode,
266
+ visible: this.containerState.visible,
267
+ };
268
+ // Add name with _form_field suffix for form fields
269
+ if (this.containerState.type === 'form-field') {
270
+ if (this.containerState.name) {
271
+ result.name = this.containerState.name;
559
272
  }
560
- return Object.values(this.options()['validations'])
561
- .filter((c) => c != null)
562
- .map((c) => ({
563
- rule: c.rule,
564
- message: c.message,
565
- options: c.options,
566
- }));
567
- }, ...(ngDevMode ? [{ debugName: "validationRules" }] : []));
568
- }
569
- ngOnInit() {
570
- this._isValueWidget = this.config.properties?.some((c) => c.name == 'path') ?? false;
571
- if (this.isValueWidget()) {
572
- this.detectFullPath();
573
- if (!isNil(this.defaultValue) && isNil(this.getValue())) {
574
- this.setValue(this.defaultValue);
273
+ else if (this.containerState.options?.['label']) {
274
+ result.name = labelToPath(this.containerState.options['label']) + '_form_field';
575
275
  }
276
+ // Form fields don't have path
576
277
  }
577
- //
578
- super.ngOnInit();
579
- }
580
- extractValue(path) {
581
- const rawValue = this.contextService.getValue(path);
582
- if (this.node.valueTransforms?.getter) {
583
- return this.node.valueTransforms?.getter(rawValue);
584
- }
585
- return rawValue;
586
- }
587
- setValue(value) {
588
- if (this.node.valueTransforms?.setter) {
589
- value = this.node.valueTransforms?.setter(value);
590
- }
591
- const oldValue = this.getValue();
592
- value = isUndefined(value) ? null : value;
593
- if (isNil(value) && isNil(oldValue)) {
594
- return;
595
- }
596
- if (isEqual(oldValue, value)) {
597
- return;
598
- }
599
- if (this.fullPath()) {
600
- this.contextService.update(this.fullPath(), value);
601
- this.onValueChanged.next({ sender: this });
602
- }
603
- }
604
- detectFullPath() {
605
- const sections = [];
606
- const ids = [];
607
- //
608
- let parent = this;
609
- //
610
- while (parent) {
611
- const isValueWidget = parent instanceof AXPValueWidgetComponent && parent.isValueWidget();
612
- const valueParent = parent;
613
- const path = valueParent.path ?? (isValueWidget ? valueParent.name : null);
614
- const id = valueParent.name;
615
- //
616
- if (path) {
617
- sections.push(path);
278
+ else {
279
+ // Other containers can have name and path
280
+ if (this.containerState.name) {
281
+ result.name = this.containerState.name;
618
282
  }
619
- if (parent.index != null && isValueWidget) {
620
- sections.push(`[${parent.index}]`);
283
+ if (this.containerState.path) {
284
+ result.path = this.containerState.path;
621
285
  }
622
- if (id) {
623
- ids.push(id);
624
- if (parent.index != null) {
625
- ids.push(`${parent.index}`);
626
- }
286
+ if (this.containerState.defaultValue !== undefined) {
287
+ result.defaultValue = this.containerState.defaultValue;
627
288
  }
628
- parent = parent.parent;
629
- }
630
- //
631
- this.fullPath.set(sections.reverse().join('.'));
632
- this.parentPath.set(sections.slice(0, sections.length - 1).join('.'));
633
- this._id = this.name || this.parent ? ids.reverse().join('_') : null;
634
- if (this._id) {
635
- this.layoutService.registerWidget(this._id, this);
636
289
  }
290
+ return result;
637
291
  }
638
- handleValueChanged(e) {
639
- if (e.isUserInteraction) {
640
- this.setValue(e.value);
641
- }
292
+ }
293
+ /**
294
+ * Base container builder mixin - Interface Segregation Principle
295
+ * Provides common container operations
296
+ */
297
+ class BaseContainerMixin extends BaseContainerBuilder {
298
+ name(name) {
299
+ this.containerState.name = name;
300
+ return this;
301
+ }
302
+ path(path) {
303
+ this.containerState.path = path;
304
+ return this;
305
+ }
306
+ mode(mode) {
307
+ this.containerState.mode = mode;
308
+ this.inheritanceContext.mode = mode;
309
+ return this;
310
+ }
311
+ visible(condition) {
312
+ if (!this.containerState.options)
313
+ this.containerState.options = {};
314
+ this.containerState.options['visible'] = condition;
315
+ this.inheritanceContext.visible = condition;
316
+ return this;
317
+ }
318
+ disabled(condition) {
319
+ if (!this.containerState.options)
320
+ this.containerState.options = {};
321
+ this.containerState.options['disabled'] = condition;
322
+ this.inheritanceContext.disabled = condition;
323
+ return this;
324
+ }
325
+ readonly(condition) {
326
+ if (!this.containerState.options)
327
+ this.containerState.options = {};
328
+ this.containerState.options['readonly'] = condition;
329
+ this.inheritanceContext.readonly = condition;
330
+ return this;
331
+ }
332
+ direction(direction) {
333
+ if (!this.containerState.options)
334
+ this.containerState.options = {};
335
+ this.containerState.options['direction'] = direction;
336
+ this.inheritanceContext.direction = direction;
337
+ return this;
338
+ }
339
+ // Inheritance context methods
340
+ withInheritanceContext(context) {
341
+ this.inheritanceContext = mergeInheritanceContext(context);
342
+ return this;
343
+ }
344
+ getInheritanceContext() {
345
+ return { ...this.inheritanceContext };
642
346
  }
643
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPValueWidgetComponent, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
644
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPValueWidgetComponent }); }
645
347
  }
646
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPValueWidgetComponent, decorators: [{
647
- type: Injectable
648
- }] });
649
- class AXPDataListWidgetComponent extends AXPValueWidgetComponent {
650
- constructor() {
651
- super(...arguments);
652
- this.dataService = inject(AXPDataSourceDefinitionProviderService);
653
- this.textField = computed(() => this.options()['textField'] ?? 'title', ...(ngDevMode ? [{ debugName: "textField" }] : []));
654
- this.valueField = computed(() => this.options()['valueField'] ?? 'id', ...(ngDevMode ? [{ debugName: "valueField" }] : []));
655
- this.dataSource = signal(convertArrayToDataSource([]), ...(ngDevMode ? [{ debugName: "dataSource" }] : []));
656
- this.isReady = computed(() => {
657
- const key = this.dataSource().config?.key;
658
- const valueField = this.valueField();
659
- const result = key == valueField;
660
- return result;
661
- }, ...(ngDevMode ? [{ debugName: "isReady" }] : []));
662
- this.selectedItems = signal([], ...(ngDevMode ? [{ debugName: "selectedItems" }] : []));
663
- this.rf = effect(async () => {
664
- const rawValue = this.options()['dataSource'];
665
- // static datasource class
666
- if (rawValue instanceof AXDataSource) {
667
- this.dataSource.set(rawValue);
668
- }
669
- // static array datasource
670
- else if (Array.isArray(rawValue)) {
671
- const ds = new AXDataSource({
672
- key: this.valueField(),
673
- pageSize: 10,
674
- load: async (e) => {
675
- const raw = this.options()['dataSource'];
676
- return {
677
- items: raw,
678
- total: raw.length,
679
- };
680
- },
681
- byKey: (key) => {
682
- const raw = this.options()['dataSource'];
683
- const item = raw.filter((c) => c[this.valueField()] == key);
684
- return Promise.resolve(item[0]);
685
- },
686
- });
687
- this.dataSource.set(ds);
688
- }
689
- // resolve data source by name
690
- else if (rawValue && (typeof rawValue == 'string' || typeof rawValue == 'object')) {
691
- const id = typeof rawValue == 'object' ? rawValue['id'] : rawValue;
692
- const c = await this.dataService.get(id);
693
- if (this.mode == 'designer' && c?.samples?.length) {
694
- this.dataSource.set(convertArrayToDataSource(c.samples, {
695
- key: this.valueField(),
696
- pageSize: 500,
697
- }));
698
- }
699
- else {
700
- const ds = c?.source();
701
- if (ds && ds instanceof Promise) {
702
- const d = await ds;
703
- this.dataSource.set(d);
704
- }
705
- else if (ds) {
706
- this.dataSource.set(ds);
707
- }
708
- // empty datasource
709
- else {
710
- this.dataSource.set(convertArrayToDataSource([]));
711
- }
348
+ /**
349
+ * Layout container mixin - Interface Segregation Principle
350
+ * Provides layout-specific operations
351
+ */
352
+ class LayoutContainerMixin extends BaseContainerMixin {
353
+ layout(value) {
354
+ // Map layout intent to grid item sizing so containers like `form-field`
355
+ // can span multiple columns inside grid/fieldset layouts.
356
+ if (!this.containerState.options)
357
+ this.containerState.options = {};
358
+ if (typeof value === 'number') {
359
+ // Direct numeric shorthand → colSpan
360
+ this.containerState.options.colSpan = value;
361
+ }
362
+ else if (value) {
363
+ // Try to extract a reasonable colSpan from breakpoint positions
364
+ const positions = value.positions;
365
+ if (positions) {
366
+ const colSpan = positions?.lg?.colSpan ??
367
+ positions?.xl?.colSpan ??
368
+ positions?.xxl?.colSpan ??
369
+ positions?.md?.colSpan ??
370
+ positions?.sm?.colSpan;
371
+ if (colSpan != null) {
372
+ this.containerState.options.colSpan = colSpan;
712
373
  }
713
374
  }
714
- // empty datasource
715
- else {
716
- this.dataSource.set(convertArrayToDataSource([]));
717
- }
718
- }, ...(ngDevMode ? [{ debugName: "rf" }] : []));
719
- this.effect2 = effect(async () => {
720
- const value = this.getValue();
721
- const items = [];
722
- if (Array.isArray(value)) {
723
- items.push(...(await Promise.all(value.map((item) => this.extractItem(item)))));
724
- }
725
- else {
726
- items.push(await this.extractItem(value));
727
- }
728
- this.selectedItems.set(items.filter((c) => c != null));
729
- }, ...(ngDevMode ? [{ debugName: "effect2" }] : []));
730
- }
731
- async extractItem(item) {
732
- if (isNil(item)) {
733
- return null;
734
- }
735
- if (isObjectLike(item) && get(item, this.textField()) != null) {
736
- return item;
737
- }
738
- const key = extractValue(item, this.valueField());
739
- const ds = this.dataSource();
740
- if (ds.config?.byKey) {
741
- const found = await ds.config?.byKey(key);
742
- if (found) {
743
- return found;
744
- }
745
375
  }
746
- return isObjectLike(item)
747
- ? item
748
- : {
749
- [this.valueField()]: item,
750
- [this.textField()]: item,
751
- };
376
+ return this;
752
377
  }
753
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPDataListWidgetComponent, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
754
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPDataListWidgetComponent }); }
755
378
  }
756
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPDataListWidgetComponent, decorators: [{
757
- type: Injectable
758
- }] });
759
- class AXPColumnWidgetComponent {
760
- constructor() {
761
- this.token = inject(AXP_WIDGET_COLUMN_TOKEN);
762
- this.path = this.token.path;
763
- this.options = this.token.options ?? {};
764
- this.rawValue = null;
765
- this.nullText = this.options['nullText'];
766
- this.nullValue = this.options['nullValue'];
767
- this.value = computed(() => {
768
- if (isNil(this.rawValue) && !isNil(this.nullValue)) {
769
- return this.nullValue;
770
- }
771
- return this.rawValue;
772
- }, ...(ngDevMode ? [{ debugName: "value" }] : []));
379
+ /**
380
+ * Child container mixin - Interface Segregation Principle
381
+ * Provides child container management
382
+ */
383
+ class ChildContainerMixin extends LayoutContainerMixin {
384
+ grid(delegate) {
385
+ const container = new GridContainerBuilder();
386
+ container.withInheritanceContext(this.inheritanceContext);
387
+ if (delegate) {
388
+ delegate(container);
389
+ }
390
+ this.ensureChildren();
391
+ this.containerState.children.push(container.build());
392
+ return this;
393
+ }
394
+ flex(delegate) {
395
+ const container = new FlexContainerBuilder();
396
+ container.withInheritanceContext(this.inheritanceContext);
397
+ if (delegate) {
398
+ delegate(container);
399
+ }
400
+ this.ensureChildren();
401
+ this.containerState.children.push(container.build());
402
+ return this;
403
+ }
404
+ panel(delegate) {
405
+ const container = new PanelContainerBuilder();
406
+ container.withInheritanceContext(this.inheritanceContext);
407
+ if (delegate) {
408
+ delegate(container);
409
+ }
410
+ this.ensureChildren();
411
+ this.containerState.children.push(container.build());
412
+ return this;
413
+ }
414
+ page(delegate) {
415
+ const container = new PageContainerBuilder();
416
+ container.withInheritanceContext(this.inheritanceContext);
417
+ if (delegate) {
418
+ delegate(container);
419
+ }
420
+ this.ensureChildren();
421
+ this.containerState.children.push(container.build());
422
+ return this;
423
+ }
424
+ tabset(delegate) {
425
+ const container = new TabsetContainerBuilder();
426
+ container.withInheritanceContext(this.inheritanceContext);
427
+ if (delegate) {
428
+ delegate(container);
429
+ }
430
+ this.ensureChildren();
431
+ this.containerState.children.push(container.build());
432
+ return this;
433
+ }
434
+ fieldset(delegate) {
435
+ const container = new FieldsetContainerBuilder();
436
+ container.withInheritanceContext(this.inheritanceContext);
437
+ if (delegate) {
438
+ delegate(container);
439
+ }
440
+ this.ensureChildren();
441
+ this.containerState.children.push(container.build());
442
+ return this;
443
+ }
444
+ dialog(delegate) {
445
+ const container = new DialogContainerBuilder(); // Will use inject() fallback
446
+ if (delegate) {
447
+ delegate(container);
448
+ }
449
+ return container;
450
+ }
451
+ formField(label, delegate) {
452
+ const field = new FormFieldBuilder(label);
453
+ field.withInheritanceContext(this.inheritanceContext);
454
+ if (delegate) {
455
+ delegate(field);
456
+ }
457
+ this.ensureChildren();
458
+ this.containerState.children.push(field.build());
459
+ return this;
773
460
  }
774
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPColumnWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
775
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPColumnWidgetComponent }); }
776
461
  }
777
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPColumnWidgetComponent, decorators: [{
778
- type: Injectable
779
- }] });
780
-
781
- class AXPBoxModelLayoutWidgetComponent extends AXPLayoutBaseWidgetComponent {
782
- constructor() {
783
- super(...arguments);
784
- this.hostBoxStyle = computed(() => {
785
- const options = this.options();
786
- const style = {};
787
- const spacing = options?.['spacing'];
788
- const border = options?.['border'];
789
- const backgroundColor = options?.['backgroundColor'];
790
- const direction = options?.['direction'];
791
- const overflow = options?.['overflow'];
792
- const overflowX = options?.['overflowX'];
793
- const overflowY = options?.['overflowY'];
794
- style['background-color'] = backgroundColor ?? '';
795
- style['padding'] = spacing?.padding ?? '';
796
- style['margin'] = spacing?.margin ?? '';
797
- style['border-radius'] = border?.radius ?? '';
798
- style['border-width'] = border?.width ?? '';
799
- style['border-color'] = border?.color ?? '';
800
- style['border-style'] = border?.style ?? '';
801
- style['overflow'] = overflow ?? '';
802
- style['overflow-x'] = overflowX ?? '';
803
- style['overflow-y'] = overflowY ?? '';
804
- style['direction'] = direction ?? '';
805
- return style;
806
- }, ...(ngDevMode ? [{ debugName: "hostBoxStyle" }] : []));
807
- this.blockStyle = computed(() => {
808
- const options = this.options();
809
- const style = { ...this.hostBoxStyle() };
810
- const width = options?.['width'];
811
- const minWidth = options?.['minWidth'];
812
- const maxWidth = options?.['maxWidth'];
813
- const height = options?.['height'];
814
- const minHeight = options?.['minHeight'];
815
- const maxHeight = options?.['maxHeight'];
816
- style['min-width'] = minWidth ?? '';
817
- style['width'] = width ?? '';
818
- style['max-width'] = maxWidth ?? '';
819
- style['min-height'] = minHeight ?? '';
820
- style['height'] = height ?? '';
821
- style['max-height'] = maxHeight ?? '';
822
- return style;
823
- }, ...(ngDevMode ? [{ debugName: "blockStyle" }] : []));
824
- this.inlineStyle = computed(() => {
825
- return { ...this.hostBoxStyle() };
826
- }, ...(ngDevMode ? [{ debugName: "inlineStyle" }] : []));
827
- this.blockClass = computed(() => {
828
- return {
829
- 'ax-block': true,
830
- 'ax-w-full': true,
831
- // 'ax-widget-outline': true,
832
- };
833
- }, ...(ngDevMode ? [{ debugName: "blockClass" }] : []));
834
- this.inlineClass = computed(() => {
835
- return {
836
- 'ax-inline-block': true,
837
- };
838
- }, ...(ngDevMode ? [{ debugName: "inlineClass" }] : []));
462
+ /**
463
+ * Widget container mixin - Interface Segregation Principle
464
+ * Provides widget creation operations
465
+ */
466
+ class WidgetContainerMixin extends ChildContainerMixin {
467
+ textBox(options) {
468
+ this.addWidget('text-editor', options);
469
+ return this;
839
470
  }
840
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPBoxModelLayoutWidgetComponent, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
841
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPBoxModelLayoutWidgetComponent }); }
842
- }
843
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPBoxModelLayoutWidgetComponent, decorators: [{
844
- type: Injectable
845
- }] });
846
-
847
- class AXPBlockBaseLayoutWidgetComponent extends AXPBoxModelLayoutWidgetComponent {
848
- constructor() {
849
- super(...arguments);
850
- this.hostClass = computed(() => this.blockClass(), ...(ngDevMode ? [{ debugName: "hostClass" }] : []));
851
- this.hostStyle = computed(() => this.blockStyle(), ...(ngDevMode ? [{ debugName: "hostStyle" }] : []));
471
+ largeTextBox(options) {
472
+ this.addWidget('large-text-editor', options);
473
+ return this;
852
474
  }
853
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPBlockBaseLayoutWidgetComponent, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
854
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPBlockBaseLayoutWidgetComponent }); }
855
- }
856
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPBlockBaseLayoutWidgetComponent, decorators: [{
857
- type: Injectable
858
- }] });
859
-
860
- class AXPFlexBaseLayoutWidgetComponent extends AXPBlockBaseLayoutWidgetComponent {
861
- constructor() {
862
- super(...arguments);
863
- this.flex = computed(() => this.options(), ...(ngDevMode ? [{ debugName: "flex" }] : []));
864
- this.hostFlexStyle = computed(() => {
865
- const blockStyle = this.blockStyle();
866
- const style = { ...blockStyle };
867
- const flex = this.flex();
868
- if (isNil(flex?.flexDirection)) {
869
- style['flex-direction'] = '';
870
- }
871
- else {
872
- style['flex-direction'] = flex.flexDirection;
873
- }
874
- if (isNil(flex?.flexWrap)) {
875
- style['flex-wrap'] = '';
876
- }
877
- else {
878
- style['flex-wrap'] = flex.flexWrap;
879
- }
880
- //TODO NEED TO FIX LATER
881
- style['overflow'] = flex?.flexWrap === 'nowrap' ? 'auto' : '';
882
- //END
883
- if (isNil(flex?.justifyContent)) {
884
- style['justify-content'] = '';
885
- }
886
- else {
887
- style['justify-content'] = flex.justifyContent;
888
- }
889
- if (isNil(flex?.alignItems)) {
890
- style['align-items'] = '';
891
- }
892
- else {
893
- style['align-items'] = flex.alignItems;
894
- }
895
- if (isNil(flex?.gap)) {
896
- style['gap'] = '';
897
- }
898
- else {
899
- style['gap'] = flex.gap;
900
- }
901
- return style;
902
- }, ...(ngDevMode ? [{ debugName: "hostFlexStyle" }] : []));
903
- this.hostFlexClass = computed(() => {
904
- return {
905
- ...this.blockClass(),
906
- 'ax-flex': true,
907
- 'ax-h-full': true,
908
- };
909
- }, ...(ngDevMode ? [{ debugName: "hostFlexClass" }] : []));
910
- this.hostClass = computed(() => {
911
- return this.hostFlexClass();
912
- }, ...(ngDevMode ? [{ debugName: "hostClass" }] : []));
913
- this.hostStyle = computed(() => {
914
- return this.hostFlexStyle();
915
- }, ...(ngDevMode ? [{ debugName: "hostStyle" }] : []));
916
- }
917
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPFlexBaseLayoutWidgetComponent, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
918
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPFlexBaseLayoutWidgetComponent }); }
919
- }
920
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPFlexBaseLayoutWidgetComponent, decorators: [{
921
- type: Injectable
922
- }] });
923
-
924
- class AXPInlineBaseLayoutWidgetComponent extends AXPBoxModelLayoutWidgetComponent {
925
- constructor() {
926
- super(...arguments);
927
- this.hostClass = computed(() => this.inlineClass(), ...(ngDevMode ? [{ debugName: "hostClass" }] : []));
928
- this.hostStyle = computed(() => this.inlineStyle(), ...(ngDevMode ? [{ debugName: "hostStyle" }] : []));
475
+ richText(options) {
476
+ this.addWidget('rich-text-editor', options);
477
+ return this;
478
+ }
479
+ passwordBox(options) {
480
+ this.addWidget('password-editor', options);
481
+ return this;
482
+ }
483
+ numberBox(options) {
484
+ this.addWidget('number-editor', options);
485
+ return this;
486
+ }
487
+ selectBox(options) {
488
+ this.addWidget('select-editor', options);
489
+ return this;
490
+ }
491
+ lookupBox(options) {
492
+ this.addWidget('lookup-editor', options);
493
+ return this;
494
+ }
495
+ selectionList(options) {
496
+ this.addWidget('selection-list-editor', options);
497
+ return this;
498
+ }
499
+ dateTimeBox(options) {
500
+ this.addWidget('date-time-editor', options);
501
+ return this;
502
+ }
503
+ toggleSwitch(options) {
504
+ this.addWidget('toggle-editor', options);
505
+ return this;
506
+ }
507
+ colorBox(options) {
508
+ this.addWidget('color-editor', options);
509
+ return this;
510
+ }
511
+ list(delegate) {
512
+ const container = new ListWidgetBuilder();
513
+ container.withInheritanceContext(this.inheritanceContext);
514
+ if (delegate) {
515
+ delegate(container);
516
+ }
517
+ this.ensureChildren();
518
+ this.containerState.children.push(container.build());
519
+ return this;
520
+ }
521
+ customWidget(type, options) {
522
+ this.addWidget(type, options);
523
+ return this;
929
524
  }
930
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPInlineBaseLayoutWidgetComponent, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
931
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPInlineBaseLayoutWidgetComponent }); }
932
- }
933
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPInlineBaseLayoutWidgetComponent, decorators: [{
934
- type: Injectable
935
- }] });
936
-
937
- class AXPFlexItemBaseLayoutWidgetComponent extends AXPInlineBaseLayoutWidgetComponent {
938
- constructor() {
939
- super(...arguments);
940
- this.flexItem = computed(() => this.options(), ...(ngDevMode ? [{ debugName: "flexItem" }] : []));
941
- this.hostFlexItemStyle = computed(() => {
942
- const inlineStyle = this.blockStyle();
943
- const style = { ...inlineStyle };
944
- const fi = this.flexItem();
945
- if (isNil(fi?.order)) {
946
- style['order'] = '';
947
- }
948
- else {
949
- style['order'] = fi.order;
950
- }
951
- if (isNil(fi?.grow)) {
952
- style['flex-grow'] = '';
953
- }
954
- else {
955
- style['flex-grow'] = fi.grow;
956
- }
957
- if (isNil(fi?.shrink)) {
958
- style['flex-shrink'] = '';
959
- }
960
- else {
961
- style['flex-shrink'] = fi.shrink;
962
- }
963
- if (isNil(fi?.basis)) {
964
- style['flex-basis'] = '';
965
- }
966
- else {
967
- style['flex-basis'] = fi.basis;
968
- }
969
- if (isNil(fi?.alignSelf)) {
970
- style['align-self'] = '';
971
- }
972
- else {
973
- style['align-self'] = fi.alignSelf;
974
- }
975
- return style;
976
- }, ...(ngDevMode ? [{ debugName: "hostFlexItemStyle" }] : []));
977
- this.hostFlexItemClass = computed(() => {
978
- return {
979
- ...this.blockClass(),
980
- };
981
- }, ...(ngDevMode ? [{ debugName: "hostFlexItemClass" }] : []));
982
- this.hostClass = computed(() => {
983
- return this.hostFlexItemClass();
984
- }, ...(ngDevMode ? [{ debugName: "hostClass" }] : []));
985
- this.hostStyle = computed(() => {
986
- return this.hostFlexItemStyle();
987
- }, ...(ngDevMode ? [{ debugName: "hostStyle" }] : []));
988
- }
989
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPFlexItemBaseLayoutWidgetComponent, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
990
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPFlexItemBaseLayoutWidgetComponent }); }
991
525
  }
992
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPFlexItemBaseLayoutWidgetComponent, decorators: [{
993
- type: Injectable
994
- }] });
995
-
996
- class AXPGridBaseLayoutWidgetComponent extends AXPBlockBaseLayoutWidgetComponent {
526
+ /**
527
+ * Flex Container Builder - Liskov Substitution Principle
528
+ * Extends WidgetContainerMixin to inherit all common functionality
529
+ */
530
+ class FlexContainerBuilder extends WidgetContainerMixin {
997
531
  constructor() {
998
- super(...arguments);
999
- this.grid = computed(() => this.options()?.['grid'], ...(ngDevMode ? [{ debugName: "grid" }] : []));
1000
- this.hostGridStyle = computed(() => {
1001
- const style = { ...this.inlineStyle() };
1002
- const g = this.grid()?.default;
1003
- if (g?.gap)
1004
- style['gap'] = g.gap;
1005
- return style;
1006
- }, ...(ngDevMode ? [{ debugName: "hostGridStyle" }] : []));
1007
- this.hostGridClass = computed(() => {
1008
- const cls = {
1009
- ...this.inlineClass(),
1010
- 'ax-grid': true,
1011
- };
1012
- const g = this.grid()?.default;
1013
- if (g?.columns)
1014
- cls[`lg:ax-grid-cols-${g.columns}`] = true;
1015
- if (g?.rows)
1016
- cls[`lg:ax-grid-rows-${g.rows}`] = true;
1017
- if (g?.justifyItems)
1018
- cls[`lg:ax-justify-items-${g.justifyItems}`] = true;
1019
- if (g?.alignItems)
1020
- cls[`lg:ax-align-items-${g.alignItems}`] = true;
1021
- if (g?.autoFlow)
1022
- cls[`lg:ax-grid-flow-${g.autoFlow}`] = true;
1023
- return cls;
1024
- }, ...(ngDevMode ? [{ debugName: "hostGridClass" }] : []));
1025
- this.hostClass = computed(() => this.hostGridClass(), ...(ngDevMode ? [{ debugName: "hostClass" }] : []));
1026
- this.hostStyle = computed(() => this.hostGridStyle(), ...(ngDevMode ? [{ debugName: "hostStyle" }] : []));
1027
- }
1028
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPGridBaseLayoutWidgetComponent, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
1029
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPGridBaseLayoutWidgetComponent }); }
532
+ super('flex-layout');
533
+ }
534
+ setOptions(options) {
535
+ this.containerState.options = { ...this.containerState.options, ...options };
536
+ return this;
537
+ }
538
+ // Individual fluent methods for Flex
539
+ setDirection(direction) {
540
+ return this.setOptions({ flexDirection: direction });
541
+ }
542
+ setWrap(wrap) {
543
+ return this.setOptions({ flexWrap: wrap });
544
+ }
545
+ setJustifyContent(justify) {
546
+ return this.setOptions({ justifyContent: justify });
547
+ }
548
+ setAlignItems(align) {
549
+ return this.setOptions({ alignItems: align });
550
+ }
551
+ setGap(gap) {
552
+ return this.setOptions({ gap });
553
+ }
554
+ setBackgroundColor(color) {
555
+ return this.setOptions({ backgroundColor: color });
556
+ }
557
+ setPadding(padding) {
558
+ return this.setOptions({ spacing: { padding } });
559
+ }
560
+ setMargin(margin) {
561
+ return this.setOptions({ spacing: { margin } });
562
+ }
1030
563
  }
1031
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPGridBaseLayoutWidgetComponent, decorators: [{
1032
- type: Injectable
1033
- }] });
1034
-
1035
- class AXPGridItemBaseLayoutWidgetComponent extends AXPFlexBaseLayoutWidgetComponent {
564
+ /**
565
+ * Grid Container Builder - Liskov Substitution Principle
566
+ * Extends WidgetContainerMixin to inherit all common functionality
567
+ */
568
+ class GridContainerBuilder extends WidgetContainerMixin {
1036
569
  constructor() {
1037
- super(...arguments);
1038
- this.gridItem = computed(() => this.options(), ...(ngDevMode ? [{ debugName: "gridItem" }] : []));
1039
- this.hostGridItemStyle = computed(() => {
1040
- const style = { ...this.hostFlexStyle() };
1041
- const g = this.gridItem();
1042
- if (g?.alignSelf)
1043
- style['align-self'] = g.alignSelf;
1044
- if (g?.justifySelf)
1045
- style['justify-self'] = g.justifySelf;
1046
- return style;
1047
- }, ...(ngDevMode ? [{ debugName: "hostGridItemStyle" }] : []));
1048
- this.hostGridItemClass = computed(() => {
1049
- const cls = { ...this.hostFlexClass() };
1050
- const g = this.gridItem();
1051
- if (g?.colSpan)
1052
- cls[`lg:ax-col-span-${g.colSpan}`] = true;
1053
- if (g?.colStart)
1054
- cls[`lg:ax-col-start-${g.colStart}`] = true;
1055
- if (g?.colEnd)
1056
- cls[`lg:ax-col-end-${g.colEnd}`] = true;
1057
- if (g?.rowSpan)
1058
- cls[`lg:ax-row-span-${g.rowSpan}`] = true;
1059
- if (g?.rowStart)
1060
- cls[`lg:ax-row-start-${g.rowStart}`] = true;
1061
- if (g?.rowEnd)
1062
- cls[`lg:ax-row-end-${g.rowEnd}`] = true;
1063
- return cls;
1064
- }, ...(ngDevMode ? [{ debugName: "hostGridItemClass" }] : []));
1065
- this.hostClass = computed(() => this.hostGridItemClass(), ...(ngDevMode ? [{ debugName: "hostClass" }] : []));
1066
- this.hostStyle = computed(() => this.hostGridItemStyle(), ...(ngDevMode ? [{ debugName: "hostStyle" }] : []));
1067
- }
1068
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPGridItemBaseLayoutWidgetComponent, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
1069
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPGridItemBaseLayoutWidgetComponent }); }
570
+ super('grid-layout');
571
+ }
572
+ setOptions(options) {
573
+ this.containerState.options = { ...this.containerState.options, ...options };
574
+ return this;
575
+ }
576
+ // Individual fluent methods for Grid
577
+ setColumns(columns) {
578
+ return this.setOptions({ grid: { default: { columns } } });
579
+ }
580
+ setRows(rows) {
581
+ return this.setOptions({ grid: { default: { rows } } });
582
+ }
583
+ setGap(gap) {
584
+ return this.setOptions({ grid: { default: { gap } } });
585
+ }
586
+ setJustifyItems(justify) {
587
+ return this.setOptions({ grid: { default: { justifyItems: justify } } });
588
+ }
589
+ setAlignItems(align) {
590
+ return this.setOptions({ grid: { default: { alignItems: align } } });
591
+ }
592
+ setAutoFlow(flow) {
593
+ return this.setOptions({ grid: { default: { autoFlow: flow } } });
594
+ }
595
+ setBackgroundColor(color) {
596
+ return this.setOptions({ backgroundColor: color });
597
+ }
598
+ setPadding(padding) {
599
+ return this.setOptions({ spacing: { padding } });
600
+ }
601
+ setMargin(margin) {
602
+ return this.setOptions({ spacing: { margin } });
603
+ }
1070
604
  }
1071
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPGridItemBaseLayoutWidgetComponent, decorators: [{
1072
- type: Injectable
1073
- }] });
1074
-
1075
- class AXPTableBaseLayoutWidgetComponent extends AXPBlockBaseLayoutWidgetComponent {
605
+ /**
606
+ * Panel Container Builder - Liskov Substitution Principle
607
+ * Extends WidgetContainerMixin to inherit all common functionality
608
+ */
609
+ class PanelContainerBuilder extends WidgetContainerMixin {
1076
610
  constructor() {
1077
- super(...arguments);
1078
- this.hostTableClass = computed(() => ({
1079
- ...this.blockClass(),
1080
- }), ...(ngDevMode ? [{ debugName: "hostTableClass" }] : []));
1081
- this.hostTableStyle = computed(() => {
1082
- const style = { ...this.blockStyle() };
1083
- style['overflow-x'] = 'auto';
1084
- return style;
1085
- }, ...(ngDevMode ? [{ debugName: "hostTableStyle" }] : []));
1086
- this.hostClass = computed(() => this.hostTableClass(), ...(ngDevMode ? [{ debugName: "hostClass" }] : []));
1087
- this.hostStyle = computed(() => this.hostTableStyle(), ...(ngDevMode ? [{ debugName: "hostStyle" }] : []));
1088
- }
1089
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPTableBaseLayoutWidgetComponent, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
1090
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPTableBaseLayoutWidgetComponent }); }
1091
- }
1092
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPTableBaseLayoutWidgetComponent, decorators: [{
1093
- type: Injectable
1094
- }] });
1095
-
1096
- class AXPTableItemOpsBaseLayoutWidgetComponent extends AXPBlockBaseLayoutWidgetComponent {
1097
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPTableItemOpsBaseLayoutWidgetComponent, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
1098
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPTableItemOpsBaseLayoutWidgetComponent }); }
611
+ super('panel-layout');
612
+ }
613
+ setOptions(options) {
614
+ this.containerState.options = { ...this.containerState.options, ...options };
615
+ return this;
616
+ }
617
+ // Individual fluent methods for Panel
618
+ setCaption(caption) {
619
+ return this.setOptions({ caption });
620
+ }
621
+ setIcon(icon) {
622
+ return this.setOptions({ icon });
623
+ }
624
+ setLook(look) {
625
+ return this.setOptions({ look });
626
+ }
627
+ setShowHeader(show) {
628
+ return this.setOptions({ showHeader: show });
629
+ }
630
+ setCollapsed(collapsed) {
631
+ return this.setOptions({ collapsed });
632
+ }
1099
633
  }
1100
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPTableItemOpsBaseLayoutWidgetComponent, decorators: [{
1101
- type: Injectable
1102
- }] });
1103
-
1104
- class AXPTableItemBaseLayoutWidgetComponent extends AXPTableItemOpsBaseLayoutWidgetComponent {
634
+ /**
635
+ * Page Container Builder - Liskov Substitution Principle
636
+ * Extends WidgetContainerMixin to inherit all common functionality
637
+ */
638
+ class PageContainerBuilder extends WidgetContainerMixin {
1105
639
  constructor() {
1106
- super(...arguments);
1107
- this.colSpan = computed(() => {
1108
- const v = this.options()?.colSpan;
1109
- return v ? Math.max(1, v) : 1;
1110
- }, ...(ngDevMode ? [{ debugName: "colSpan" }] : []));
1111
- this.rowSpan = computed(() => {
1112
- const v = this.options()?.rowSpan;
1113
- return v ? Math.max(1, v) : 1;
1114
- }, ...(ngDevMode ? [{ debugName: "rowSpan" }] : []));
1115
- this.hostClass = computed(() => this.blockClass(), ...(ngDevMode ? [{ debugName: "hostClass" }] : []));
1116
- this.hostStyle = computed(() => this.blockStyle(), ...(ngDevMode ? [{ debugName: "hostStyle" }] : []));
1117
- }
1118
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPTableItemBaseLayoutWidgetComponent, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
1119
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPTableItemBaseLayoutWidgetComponent }); }
640
+ super('page-layout');
641
+ }
642
+ setOptions(options) {
643
+ this.containerState.options = { ...this.containerState.options, ...options };
644
+ return this;
645
+ }
646
+ // Individual fluent methods for Page
647
+ setBackgroundColor(color) {
648
+ return this.setOptions({ backgroundColor: color });
649
+ }
650
+ setTheme(theme) {
651
+ return this.setOptions({ theme });
652
+ }
653
+ setHasHeader(hasHeader) {
654
+ return this.setOptions({ hasHeader });
655
+ }
656
+ setHasFooter(hasFooter) {
657
+ return this.setOptions({ hasFooter });
658
+ }
659
+ setDirection(direction) {
660
+ return this.setOptions({ direction });
661
+ }
1120
662
  }
1121
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPTableItemBaseLayoutWidgetComponent, decorators: [{
1122
- type: Injectable
1123
- }] });
1124
-
1125
- class AXPWidgetRegistryService {
1126
- /**
1127
- *
1128
- */
663
+ /**
664
+ * Tabset Container Builder - Liskov Substitution Principle
665
+ * Extends WidgetContainerMixin to inherit all common functionality
666
+ */
667
+ class TabsetContainerBuilder extends WidgetContainerMixin {
1129
668
  constructor() {
1130
- this.types = new Map();
1131
- AXPWidgetRegistryService.instance = this;
669
+ super('tabset-layout');
1132
670
  }
1133
- register(widget) {
1134
- this.types.set(widget.name, widget);
671
+ setOptions(options) {
672
+ this.containerState.options = { ...this.containerState.options, ...options };
673
+ return this;
1135
674
  }
1136
- extend(parentName, widget) {
1137
- const parentWidget = this.resolve(parentName);
1138
- const newWidget = merge({}, parentWidget, widget);
1139
- newWidget.name = widget.name;
1140
- this.register(newWidget);
675
+ // Individual fluent methods for Tabset
676
+ setLook(look) {
677
+ return this.setOptions({ look });
1141
678
  }
1142
- resolve(name) {
1143
- const widget = this.types.get(name);
1144
- if (!widget) {
1145
- throw new Error(`Widget with name "${name}" does not exist.`);
1146
- }
1147
- return widget;
679
+ setOrientation(orientation) {
680
+ return this.setOptions({ orientation });
1148
681
  }
1149
- all() {
1150
- return Array.from(this.types.values());
682
+ setActiveIndex(index) {
683
+ return this.setOptions({ activeIndex: index });
1151
684
  }
1152
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPWidgetRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1153
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPWidgetRegistryService, providedIn: 'root' }); }
1154
685
  }
1155
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPWidgetRegistryService, decorators: [{
1156
- type: Injectable,
1157
- args: [{
1158
- providedIn: 'root',
1159
- }]
1160
- }], ctorParameters: () => [] });
1161
-
1162
- class AXPWidgetColumnRendererComponent extends AXDataTableColumnComponent {
1163
- constructor() {
1164
- super(...arguments);
1165
- this.widgetRegistery = inject(AXPWidgetRegistryService);
1166
- this.grid = inject(AXBaseDataTable);
1167
- this.mergedOptions = signal({}, ...(ngDevMode ? [{ debugName: "mergedOptions" }] : []));
1168
- this.loadingRow = signal(null, ...(ngDevMode ? [{ debugName: "loadingRow" }] : []));
1169
- this.injector = inject(Injector);
1170
- this.cdr = inject(ChangeDetectorRef);
1171
- }
1172
- get node() {
1173
- return this._node;
1174
- }
1175
- set node(v) {
1176
- this._node = v;
1177
- }
1178
- get renderFooterTemplate() {
1179
- return this.footerTemplate ?? this._contentFooterTemplate;
1180
- }
1181
- get renderCellTemplate() {
1182
- return this.cellTemplate ?? this._contentCellTemplate;
1183
- }
1184
- async handleExpandRow(row) {
1185
- this.loadingRow.set(row);
1186
- await this.grid.expandRow(row);
1187
- this.loadingRow.set(null);
1188
- // if (row.data?.__meta__?.expanded === undefined) {
1189
- // this.width = `${parseInt(this.width as string) + 24}px`;
1190
- // }
1191
- }
1192
- get renderHeaderTemplate() {
1193
- return this.headerTemplate ?? this._contentHeaderTemplate;
1194
- }
1195
- get loadingEnabled() {
1196
- return true;
1197
- }
1198
- get name() {
1199
- return `col-${this.node.path}`;
1200
- }
1201
- async ngOnInit() {
1202
- const widget = this.widgetRegistery.resolve(this.node.type);
1203
- const mode = 'column';
1204
- this.component = await widget?.components[mode]?.component();
1205
- //
1206
- const props = widget?.components[mode]?.properties
1207
- ?.filter((c) => c.schema.defaultValue)
1208
- .map((c) => ({ [c.name]: c.schema.defaultValue }))
1209
- .reduce((acc, curr) => {
1210
- return { ...acc, ...curr };
1211
- }, {});
1212
- //
1213
- this.mergedOptions.set(merge(props, this.node.options) || {});
1214
- const tokenValue = {
1215
- path: this.node.path,
1216
- options: this.mergedOptions(),
1217
- };
1218
- this.widgetInjector = Injector.create({
1219
- parent: this.injector,
1220
- providers: [
1221
- {
1222
- provide: AXP_WIDGET_COLUMN_TOKEN,
1223
- useValue: tokenValue,
1224
- },
1225
- ],
1226
- });
1227
- this.width = this.customWidth ? this.customWidth : (this.mergedOptions().width ?? '200px');
1228
- this.allowResizing = this.mergedOptions().allowResizing || true;
1229
- this.cdr.detectChanges();
686
+ /**
687
+ * Form Field Builder - Liskov Substitution Principle
688
+ * Can only contain ONE widget with automatic path generation
689
+ */
690
+ class FormFieldBuilder extends LayoutContainerMixin {
691
+ constructor(label) {
692
+ super('form-field');
693
+ this.hasWidget = false;
694
+ this.containerState.options = { label, showLabel: true };
1230
695
  }
1231
- getInputs(data) {
1232
- return {
1233
- rawValue: getSmart(data, this.node.path),
1234
- rowData: data,
1235
- };
696
+ setOptions(options) {
697
+ this.containerState.options = { ...this.containerState.options, ...options };
698
+ return this;
1236
699
  }
1237
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPWidgetColumnRendererComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
1238
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.8", type: AXPWidgetColumnRendererComponent, isStandalone: false, selector: "axp-widget-column-renderer", inputs: { caption: "caption", customExpandIcon: "customExpandIcon", customCollapseIcon: "customCollapseIcon", customWidth: "customWidth", node: "node", footerTemplate: "footerTemplate", expandHandler: "expandHandler", cellTemplate: "cellTemplate", headerTemplate: "headerTemplate" }, providers: [
1239
- AXPLayoutBuilderService,
1240
- { provide: AXDataTableColumnComponent, useExisting: AXPWidgetColumnRendererComponent },
1241
- ], viewQueries: [{ propertyName: "_contentFooterTemplate", first: true, predicate: ["footer"], descendants: true }, { propertyName: "_contentCellTemplate", first: true, predicate: ["cell"], descendants: true }, { propertyName: "_contentHeaderTemplate", first: true, predicate: ["header"], descendants: true }], usesInheritance: true, ngImport: i0, template: `
1242
- <ng-template #header>{{ caption | translate | async }}</ng-template>
1243
- <ng-template #cell let-row>
1244
- <div class="ax-flex ax-gap-2 ax-items-center">
1245
- @if (expandHandler) {
1246
- <div
1247
- (click)="handleExpandRow(row)"
1248
- class="ax-expand-handler"
1249
- [class.ax-invisible]="row.data.hasChild === false"
1250
- id="ax-expand-handler-container"
1251
- [style.padding-inline-start.rem]="row.data?.__meta__?.level * 2"
1252
- >
1253
- @if (loadingRow() === row) {
1254
- <i class="fas fa-spinner-third ax-animate-twSpin ax-animate-infinite"></i>
1255
- } @else {
1256
- @if (row.data?.__meta__?.expanded) {
1257
- <i [class]="customCollapseIcon || 'far fa-minus-square ax-text-md ax-opacity-75'"></i>
1258
- } @else {
1259
- <i [class]="customExpandIcon || 'far fa-plus-square ax-text-md ax-opacity-75'"></i>
1260
- }
1261
- }
1262
- </div>
700
+ setLabel(label) {
701
+ return this.setOptions({ label });
702
+ }
703
+ setShowLabel(showLabel) {
704
+ return this.setOptions({ showLabel });
705
+ }
706
+ // Single widget methods with automatic path generation
707
+ addSingleWidget(type, options) {
708
+ if (this.hasWidget) {
709
+ throw new Error('Form field can only contain one widget');
1263
710
  }
1264
- @if (component && widgetInjector && row?.data) {
1265
- <ng-container
1266
- *ngComponentOutlet="component; injector: widgetInjector; inputs: getInputs(row.data)"
1267
- ></ng-container>
711
+ const formFieldName = this.containerState.name;
712
+ const formFieldPath = this.containerState.path; // Get explicit path from form field
713
+ const formFieldLabel = this.containerState.options?.['label'];
714
+ const widgetName = options?.name;
715
+ // Generate widget path: explicit path -> widget name -> form field name -> label -> random
716
+ let widgetPath;
717
+ if (formFieldPath) {
718
+ widgetPath = formFieldPath; // Use explicit form field path first
1268
719
  }
1269
- </div>
1270
- </ng-template>
1271
- <ng-template #footer></ng-template>
1272
- `, isInline: true, dependencies: [{ kind: "directive", type: i1.NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule", "ngComponentOutletNgModuleFactory"], exportAs: ["ngComponentOutlet"] }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }, { kind: "pipe", type: i2.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1273
- }
1274
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPWidgetColumnRendererComponent, decorators: [{
1275
- type: Component,
1276
- args: [{
1277
- selector: 'axp-widget-column-renderer',
1278
- template: `
1279
- <ng-template #header>{{ caption | translate | async }}</ng-template>
1280
- <ng-template #cell let-row>
1281
- <div class="ax-flex ax-gap-2 ax-items-center">
1282
- @if (expandHandler) {
1283
- <div
1284
- (click)="handleExpandRow(row)"
1285
- class="ax-expand-handler"
1286
- [class.ax-invisible]="row.data.hasChild === false"
1287
- id="ax-expand-handler-container"
1288
- [style.padding-inline-start.rem]="row.data?.__meta__?.level * 2"
1289
- >
1290
- @if (loadingRow() === row) {
1291
- <i class="fas fa-spinner-third ax-animate-twSpin ax-animate-infinite"></i>
1292
- } @else {
1293
- @if (row.data?.__meta__?.expanded) {
1294
- <i [class]="customCollapseIcon || 'far fa-minus-square ax-text-md ax-opacity-75'"></i>
1295
- } @else {
1296
- <i [class]="customExpandIcon || 'far fa-plus-square ax-text-md ax-opacity-75'"></i>
1297
- }
1298
- }
1299
- </div>
720
+ else if (widgetName) {
721
+ widgetPath = widgetName;
1300
722
  }
1301
- @if (component && widgetInjector && row?.data) {
1302
- <ng-container
1303
- *ngComponentOutlet="component; injector: widgetInjector; inputs: getInputs(row.data)"
1304
- ></ng-container>
723
+ else if (formFieldName) {
724
+ widgetPath = formFieldName; // Use form field name as default path
1305
725
  }
1306
- </div>
1307
- </ng-template>
1308
- <ng-template #footer></ng-template>
1309
- `,
1310
- providers: [
1311
- AXPLayoutBuilderService,
1312
- { provide: AXDataTableColumnComponent, useExisting: AXPWidgetColumnRendererComponent },
1313
- ],
1314
- changeDetection: ChangeDetectionStrategy.OnPush,
1315
- inputs: ['caption'],
1316
- standalone: false,
1317
- }]
1318
- }], propDecorators: { customExpandIcon: [{
1319
- type: Input
1320
- }], customCollapseIcon: [{
1321
- type: Input
1322
- }], customWidth: [{
1323
- type: Input
1324
- }], node: [{
1325
- type: Input,
1326
- args: [{ required: true }]
1327
- }], footerTemplate: [{
1328
- type: Input
1329
- }], _contentFooterTemplate: [{
1330
- type: ViewChild,
1331
- args: ['footer']
1332
- }], expandHandler: [{
1333
- type: Input
1334
- }], cellTemplate: [{
1335
- type: Input
1336
- }], _contentCellTemplate: [{
1337
- type: ViewChild,
1338
- args: ['cell']
1339
- }], headerTemplate: [{
1340
- type: Input
1341
- }], _contentHeaderTemplate: [{
1342
- type: ViewChild,
1343
- args: ['header']
1344
- }] } });
1345
-
1346
- class AXPWidgetContainerComponent {
1347
- set context(value) {
1348
- this.contextService.set(value);
726
+ else if (formFieldLabel) {
727
+ widgetPath = labelToPath(formFieldLabel);
728
+ }
729
+ else {
730
+ widgetPath = generateRandomId();
731
+ }
732
+ const finalName = widgetName || formFieldName || widgetPath;
733
+ const child = new WidgetBuilder();
734
+ child.type(type);
735
+ child.name(finalName);
736
+ child.path(widgetPath);
737
+ // Remove name from options since it's now in state
738
+ const { name: _, ...cleanOptions } = (options || {});
739
+ child.withInheritanceContext(this.inheritanceContext);
740
+ child.options(cleanOptions);
741
+ // IMPORTANT: Store the widget builder, don't build it yet!
742
+ // This allows properties set after this method (like disabled, readonly) to be applied
743
+ this.childWidget = child;
744
+ this.hasWidget = true;
745
+ return this;
1349
746
  }
1350
- set functions(v) {
1351
- this.builderService.setFunctions(v);
747
+ textBox(options) {
748
+ return this.addSingleWidget('text-editor', options);
1352
749
  }
1353
- constructor() {
1354
- this.contextService = inject(AXPLayoutBuilderContextStore);
1355
- this.builderService = inject(AXPLayoutBuilderService);
1356
- this.onContextChanged = new EventEmitter();
1357
- this.status = computed(() => {
1358
- return this.builderService.status();
1359
- }, ...(ngDevMode ? [{ debugName: "status" }] : []));
1360
- this.isBusy = computed(() => {
1361
- return this.builderService.isBusy();
1362
- }, ...(ngDevMode ? [{ debugName: "isBusy" }] : []));
1363
- effect(() => {
1364
- if (this.contextService.isChanged()) {
1365
- this.onContextChanged.emit(this.contextService.changeEvent());
1366
- }
1367
- });
750
+ largeTextBox(options) {
751
+ return this.addSingleWidget('large-text-editor', options);
1368
752
  }
1369
- refresh() {
1370
- this.builderService.refresh();
753
+ richText(options) {
754
+ return this.addSingleWidget('rich-text-editor', options);
1371
755
  }
1372
- find(name) {
1373
- return this.builderService.waitForWidget(name);
756
+ passwordBox(options) {
757
+ return this.addSingleWidget('password-editor', options);
1374
758
  }
1375
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPWidgetContainerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1376
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.8", type: AXPWidgetContainerComponent, isStandalone: false, selector: "axp-widgets-container", inputs: { context: "context", functions: "functions" }, outputs: { onContextChanged: "onContextChanged" }, host: { styleAttribute: "display: contents;" }, providers: [AXPLayoutBuilderService, AXPLayoutBuilderContextStore], ngImport: i0, template: `<ng-content></ng-content>`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1377
- }
1378
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPWidgetContainerComponent, decorators: [{
1379
- type: Component,
1380
- args: [{
1381
- selector: 'axp-widgets-container',
1382
- template: `<ng-content></ng-content>`,
1383
- changeDetection: ChangeDetectionStrategy.OnPush,
1384
- host: { style: 'display: contents;' },
1385
- providers: [AXPLayoutBuilderService, AXPLayoutBuilderContextStore],
1386
- standalone: false,
1387
- }]
1388
- }], ctorParameters: () => [], propDecorators: { onContextChanged: [{
1389
- type: Output
1390
- }], context: [{
1391
- type: Input
1392
- }], functions: [{
1393
- type: Input
1394
- }] } });
1395
-
1396
- class AXPWidgetPlaceholderComponent {
1397
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPWidgetPlaceholderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1398
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.8", type: AXPWidgetPlaceholderComponent, isStandalone: true, selector: "axp-widget-placeholder", ngImport: i0, template: `<div>
1399
- <ax-skeleton class="ax-w-full ax-h-10 ax-rounded-md"></ax-skeleton>
1400
- </div>`, isInline: true, dependencies: [{ kind: "ngmodule", type: AXSkeletonModule }, { kind: "component", type: i1$1.AXSkeletonComponent, selector: "ax-skeleton", inputs: ["animated"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1401
- }
1402
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPWidgetPlaceholderComponent, decorators: [{
1403
- type: Component,
1404
- args: [{
1405
- selector: 'axp-widget-placeholder',
1406
- template: `<div>
1407
- <ax-skeleton class="ax-w-full ax-h-10 ax-rounded-md"></ax-skeleton>
1408
- </div>`,
1409
- changeDetection: ChangeDetectionStrategy.OnPush,
1410
- imports: [AXSkeletonModule],
1411
- standalone: true,
1412
- }]
1413
- }] });
1414
-
1415
- class AXPWidgetRendererDirective {
1416
- //#endregion
1417
- //#endregion
1418
- constructor() {
1419
- this.parentNode = input(...(ngDevMode ? [undefined, { debugName: "parentNode" }] : []));
1420
- this.index = input(...(ngDevMode ? [undefined, { debugName: "index" }] : []));
1421
- this.mode = input.required(...(ngDevMode ? [{ debugName: "mode" }] : []));
1422
- this.node = input.required(...(ngDevMode ? [{ debugName: "node" }] : []));
1423
- this._options = signal({}, ...(ngDevMode ? [{ debugName: "_options" }] : []));
1424
- this.options = this._options.asReadonly();
1425
- this.onOptionsChanged = output();
1426
- this.onValueChanged = output();
1427
- //#region ---- Properties ----
1428
- this.mergedOptions = signal({}, ...(ngDevMode ? [{ debugName: "mergedOptions" }] : []));
1429
- this.injector = inject(Injector);
1430
- this.builderService = inject(AXPLayoutBuilderService);
1431
- this.contextService = inject(AXPLayoutBuilderContextStore);
1432
- this.widgetRegistery = inject(AXPWidgetRegistryService);
1433
- this.unsubscriber = inject(AXUnsubscriber);
1434
- this.translateService = inject(AXTranslationService);
1435
- this.widgetService = inject(AXPWidgetRegistryService);
1436
- this.expressionEvaluator = inject(AXPExpressionEvaluatorService);
1437
- this.viewContainerRef = inject(ViewContainerRef);
1438
- this.isLoading = signal(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
1439
- this.expressionEvaluators = new Map();
1440
- this.renderTimeoutId = null;
1441
- this.hasInitialRender = false;
1442
- this.onContextChanged = new Subject();
1443
- //#region ---- Performance Optimization Properties ----
1444
- this.contextUpdateQueue = new Set();
1445
- this.contextUpdateTimeout = null;
1446
- this.CONTEXT_UPDATE_DEBOUNCE_MS = 4; // ~250fps for large forms
1447
- // Removed visibility detection - not needed for current implementation
1448
- // Expression result caching
1449
- this.expressionCache = new Map();
1450
- this.EXPRESSION_CACHE_TTL = 500; // Cache for 500ms (increased for better hit rate)
1451
- // Options change tracking
1452
- this.lastAppliedOptions = null;
1453
- // Expression result tracking
1454
- this.lastExpressionResults = new Map();
1455
- // Buffer for context changes that happen before initial render completes
1456
- this.preRenderContextQueue = new Set();
1457
- effect(async () => {
1458
- const changed = this.contextService.changeEvent();
1459
- // Don't trigger re-render during initial setup
1460
- if (!this.hasInitialRender) {
1461
- if (changed.path) {
1462
- this.preRenderContextQueue.add(changed.path);
1463
- console.log(`📝 [${this.node().type}] Buffered pre-render context change: ${changed.path}`);
1464
- }
1465
- return;
1466
- }
1467
- // CRITICAL PERFORMANCE FIX: Only respond to relevant context changes
1468
- if (changed.path && this.isRelevantContextChange(changed.path)) {
1469
- console.log(`🎯 [${this.node().type}] Context change detected: ${changed.path}`);
1470
- this.queueContextUpdate(changed.path);
1471
- }
1472
- });
1473
- this.builderService.onRefresh.pipe(this.unsubscriber.takeUntilDestroy).subscribe(async () => {
1474
- await this.processBatchedUpdates();
1475
- });
759
+ numberBox(options) {
760
+ return this.addSingleWidget('number-editor', options);
1476
761
  }
1477
- //#region ---- Expression Caching Methods ----
1478
- getCachedExpressionResult(expressionKey) {
1479
- const cached = this.expressionCache.get(expressionKey);
1480
- if (cached && performance.now() - cached.timestamp < this.EXPRESSION_CACHE_TTL) {
1481
- console.log(`💾 [${this.node().type}] Using cached expression result for '${expressionKey}'`);
1482
- return cached.value;
1483
- }
1484
- return null;
762
+ selectBox(options) {
763
+ return this.addSingleWidget('select-editor', options);
1485
764
  }
1486
- setCachedExpressionResult(expressionKey, value) {
1487
- this.expressionCache.set(expressionKey, {
1488
- value,
1489
- timestamp: performance.now(),
1490
- });
765
+ lookupBox(options) {
766
+ return this.addSingleWidget('lookup-editor', options);
1491
767
  }
1492
- clearExpressionCache() {
1493
- this.expressionCache.clear();
768
+ selectionList(options) {
769
+ return this.addSingleWidget('selection-list-editor', options);
1494
770
  }
1495
- isPathAffectingExpressions(changedPath) {
1496
- console.log(`🔍 [${this.node().type}] Checking if path '${changedPath}' affects expressions. Expression count: ${this.expressionEvaluators.size}`);
1497
- // If widget has no expressions, no need to clear cache
1498
- if (this.expressionEvaluators.size === 0) {
1499
- console.log(`🔍 [${this.node().type}] Path '${changedPath}' - no expressions, keeping cache`);
1500
- return false;
1501
- }
1502
- // Use the same logic as hasExpressionDependency to check for actual dependencies
1503
- if (this.hasExpressionDependency(changedPath)) {
1504
- console.log(`🔍 [${this.node().type}] Path '${changedPath}' affects expressions - clearing cache`);
1505
- return true;
1506
- }
1507
- console.log(`🔍 [${this.node().type}] Path '${changedPath}' does not affect expressions - keeping cache`);
1508
- return false;
771
+ dateTimeBox(options) {
772
+ return this.addSingleWidget('date-time-editor', options);
1509
773
  }
1510
- //#endregion
1511
- //#region ---- Context Batching Methods ----
1512
- queueContextUpdate(path) {
1513
- if (path) {
1514
- console.log(`🔄 [${this.node().type}] Queueing context update for path: ${path}`);
1515
- this.contextUpdateQueue.add(path);
1516
- // Clear existing timeout
1517
- if (this.contextUpdateTimeout) {
1518
- clearTimeout(this.contextUpdateTimeout);
1519
- }
1520
- // Debounce updates
1521
- this.contextUpdateTimeout = setTimeout(() => {
1522
- console.log(`⚡ [${this.node().type}] Processing batched updates for ${this.contextUpdateQueue.size} paths`);
1523
- this.processBatchedUpdates();
1524
- }, this.CONTEXT_UPDATE_DEBOUNCE_MS);
1525
- }
1526
- }
1527
- async processBatchedUpdates() {
1528
- if (this.contextUpdateQueue.size === 0)
1529
- return;
1530
- const startTime = performance.now();
1531
- const paths = Array.from(this.contextUpdateQueue);
1532
- this.contextUpdateQueue.clear();
1533
- console.log(`📊 [${this.node().type}] Processing ${paths.length} paths:`, paths);
1534
- // Clear expression cache only if changed paths affect this widget's expressions
1535
- const shouldClearCache = paths.some((path) => this.isPathAffectingExpressions(path));
1536
- if (shouldClearCache) {
1537
- console.log(`🗑️ [${this.node().type}] Clearing expression cache due to expression-affecting path change`);
1538
- this.clearExpressionCache();
1539
- }
1540
- // Process updates in batches
1541
- const optionsStartTime = performance.now();
1542
- const hasOptionsUpdate = await this.updateOptionsBasedOnContext();
1543
- const optionsTime = performance.now() - optionsStartTime;
1544
- if (typeof hasOptionsUpdate === 'number' && hasOptionsUpdate > 0) {
1545
- console.log(`🔧 [${this.node().type}] Options updated (${optionsTime.toFixed(2)}ms)`);
1546
- this.applyOptions();
1547
- }
1548
- // Check formulas for any of the changed paths
1549
- const formulaStartTime = performance.now();
1550
- const formulaNeedsUpdate = paths.some((path) => this.checkFormulaForUpdate(this.node().formula, path));
1551
- if (formulaNeedsUpdate) {
1552
- console.log(`🧮 [${this.node().type}] Formula needs update`);
1553
- await this.updateValueBasedOnFormula();
1554
- }
1555
- const formulaTime = performance.now() - formulaStartTime;
1556
- // Emit context changes
1557
- paths.forEach((path) => {
1558
- this.onContextChanged.next({ path });
1559
- });
1560
- const totalTime = performance.now() - startTime;
1561
- console.log(`✅ [${this.node().type}] Batch processing completed in ${totalTime.toFixed(2)}ms (options: ${optionsTime.toFixed(2)}ms, formula: ${formulaTime.toFixed(2)}ms)`);
774
+ toggleSwitch(options) {
775
+ return this.addSingleWidget('toggle-editor', options);
1562
776
  }
1563
- //#endregion
1564
- //#region ---- Context Relevance Filtering ----
1565
- isRelevantContextChange(changedPath) {
1566
- const node = this.node();
1567
- // 1. Direct path match - widget's own field
1568
- if (node.path === changedPath) {
1569
- return true;
1570
- }
1571
- // 2. Parent path match - widget is inside the changed container
1572
- if (node.path && changedPath.startsWith(node.path + '.')) {
1573
- return true;
1574
- }
1575
- // 3. Child path match - changed field is inside this widget's container
1576
- if (node.path && node.path.startsWith(changedPath + '.')) {
1577
- return true;
1578
- }
1579
- // 4. Expression dependency check - if widget has expressions that depend on this path
1580
- if (this.hasExpressionDependency(changedPath)) {
1581
- return true;
1582
- }
1583
- // 5. Formula dependency check - if widget's formula depends on this path
1584
- if (node.formula && this.checkFormulaForUpdate(node.formula, changedPath)) {
1585
- return true;
1586
- }
1587
- // 6. Trigger dependency check - if widget has triggers that depend on this path
1588
- if (this.hasTriggerDependency(changedPath)) {
1589
- return true;
1590
- }
1591
- return false;
1592
- }
1593
- hasExpressionDependency(changedPath) {
1594
- // Check if any cached expressions depend on the changed path
1595
- for (const [path, evaluator] of this.expressionEvaluators) {
1596
- // Check if the expression path itself contains the changed path
1597
- if (path.includes(changedPath)) {
1598
- return true;
1599
- }
1600
- // Parse the actual expression content to check for context.eval() calls
1601
- // We need to get the original expression string to analyze it
1602
- const node = this.node();
1603
- const expressionValue = this.getExpressionValueFromNode(node, path);
1604
- if (expressionValue && typeof expressionValue === 'string') {
1605
- // Look for context.eval() calls that reference the changed path
1606
- const contextEvalRegex = /context\.eval\(['"]([^'\"]+)['"]\)/g;
1607
- let match;
1608
- while ((match = contextEvalRegex.exec(expressionValue)) !== null) {
1609
- const evalPath = match[1];
1610
- // Normalize Id-suffixed segments to dot-id form (e.g., 'typeId' -> 'type.id', 'party.typeId' -> 'party.type.id')
1611
- const normalizePath = (p) => {
1612
- if (!p)
1613
- return p;
1614
- const parts = p.split('.');
1615
- const last = parts[parts.length - 1];
1616
- if (last && last.endsWith('Id')) {
1617
- parts.splice(parts.length - 1, 1, last.slice(0, -2), 'id');
1618
- }
1619
- return parts.join('.');
1620
- };
1621
- const isSegmentSuffix = (a, b) => {
1622
- if (!a || !b)
1623
- return false;
1624
- const pa = a.split('.');
1625
- const pb = b.split('.');
1626
- if (pb.length > pa.length)
1627
- return false;
1628
- for (let i = 1; i <= pb.length; i++) {
1629
- if (pa[pa.length - i] !== pb[pb.length - i])
1630
- return false;
1631
- }
1632
- return true;
1633
- };
1634
- const evalNorm = normalizePath(evalPath);
1635
- const changedNorm = normalizePath(changedPath);
1636
- // Debug log for dependency check
1637
- console.log(`🧭 [${this.node().type}] dep-check expr='${path}', changed='${changedPath}', eval='${evalPath}', evalNorm='${evalNorm}', changedNorm='${changedNorm}'`);
1638
- // Generic direct and hierarchical dependency checks (raw and normalized)
1639
- const rawMatch = evalPath === changedPath ||
1640
- evalPath.startsWith(changedPath + '.') ||
1641
- changedPath.startsWith(evalPath + '.') ||
1642
- isSegmentSuffix(evalPath, changedPath) ||
1643
- isSegmentSuffix(changedPath, evalPath);
1644
- const normMatch = evalNorm === changedNorm ||
1645
- evalNorm.startsWith(changedNorm + '.') ||
1646
- changedNorm.startsWith(evalNorm + '.') ||
1647
- isSegmentSuffix(evalNorm, changedNorm) ||
1648
- isSegmentSuffix(changedNorm, evalNorm);
1649
- if (rawMatch || normMatch) {
1650
- console.log(`🔍 [${this.node().type}] Expression '${path}' depends on '${changedPath}' via context.eval('${evalPath}') (generic match)`);
1651
- return true;
1652
- }
1653
- // Check for path aliases/mappings (e.g., typeId.id <-> type.id)
1654
- if (this.isPathAlias(evalPath, changedPath)) {
1655
- console.log(`🔍 [${this.node().type}] Expression '${path}' depends on '${changedPath}' via context.eval('${evalPath}') (path alias)`);
1656
- return true;
1657
- }
1658
- }
1659
- }
777
+ colorBox(options) {
778
+ return this.addSingleWidget('color-editor', options);
779
+ }
780
+ customWidget(type, options) {
781
+ return this.addSingleWidget(type, options);
782
+ }
783
+ // Override property setters to propagate changes to child widget
784
+ disabled(condition) {
785
+ super.disabled(condition);
786
+ if (this.childWidget) {
787
+ this.childWidget.withInheritanceContext(this.inheritanceContext);
1660
788
  }
1661
- return false;
1662
- }
1663
- isPathAlias(evalPath, changedPath) {
1664
- // Dynamic path alias detection based on 'Id' suffix pattern
1665
- // Examples: typeId.id <-> type.id, categoryId.name <-> category.name
1666
- // Check if evalPath ends with 'Id' and has a property
1667
- if (evalPath.endsWith('Id') && evalPath.includes('.')) {
1668
- const basePath = evalPath.substring(0, evalPath.lastIndexOf('Id'));
1669
- const property = evalPath.substring(evalPath.lastIndexOf('.') + 1);
1670
- const mappedPath = `${basePath}.${property}`;
1671
- if (changedPath === mappedPath) {
1672
- console.log(`🔍 [${this.node().type}] Path alias detected: '${evalPath}' <-> '${changedPath}'`);
1673
- return true;
1674
- }
789
+ return this;
790
+ }
791
+ readonly(condition) {
792
+ super.readonly(condition);
793
+ if (this.childWidget) {
794
+ this.childWidget.withInheritanceContext(this.inheritanceContext);
1675
795
  }
1676
- // Check if changedPath ends with 'Id' and has a property
1677
- if (changedPath.endsWith('Id') && changedPath.includes('.')) {
1678
- const basePath = changedPath.substring(0, changedPath.lastIndexOf('Id'));
1679
- const property = changedPath.substring(changedPath.lastIndexOf('.') + 1);
1680
- const mappedPath = `${basePath}.${property}`;
1681
- if (evalPath === mappedPath) {
1682
- console.log(`🔍 [${this.node().type}] Path alias detected: '${evalPath}' <-> '${changedPath}'`);
1683
- return true;
1684
- }
796
+ return this;
797
+ }
798
+ visible(condition) {
799
+ super.visible(condition);
800
+ if (this.childWidget) {
801
+ this.childWidget.withInheritanceContext(this.inheritanceContext);
1685
802
  }
1686
- // Check for direct 'Id' suffix mapping (e.g., typeId <-> type.id)
1687
- if (evalPath.endsWith('Id') && !evalPath.includes('.')) {
1688
- const basePath = evalPath.substring(0, evalPath.lastIndexOf('Id'));
1689
- const mappedPath = `${basePath}.id`;
1690
- if (changedPath === mappedPath) {
1691
- console.log(`🔍 [${this.node().type}] Path alias detected: '${evalPath}' <-> '${changedPath}'`);
1692
- return true;
1693
- }
803
+ return this;
804
+ }
805
+ direction(direction) {
806
+ super.direction(direction);
807
+ if (this.childWidget) {
808
+ this.childWidget.withInheritanceContext(this.inheritanceContext);
1694
809
  }
1695
- if (changedPath.endsWith('Id') && !changedPath.includes('.')) {
1696
- const basePath = changedPath.substring(0, changedPath.lastIndexOf('Id'));
1697
- const mappedPath = `${basePath}.id`;
1698
- if (evalPath === mappedPath) {
1699
- console.log(`🔍 [${this.node().type}] Path alias detected: '${evalPath}' <-> '${changedPath}'`);
1700
- return true;
1701
- }
810
+ return this;
811
+ }
812
+ mode(mode) {
813
+ super.mode(mode);
814
+ if (this.childWidget) {
815
+ this.childWidget.withInheritanceContext(this.inheritanceContext);
1702
816
  }
1703
- return false;
1704
- }
1705
- getExpressionValueFromNode(node, path) {
1706
- try {
1707
- // Navigate through the node structure to find the expression value
1708
- const pathParts = path.split('.');
1709
- let current = node.options || {};
1710
- // Navigate through the path parts
1711
- for (const part of pathParts) {
1712
- if (current && typeof current === 'object' && part in current) {
1713
- current = current[part];
1714
- }
1715
- else {
1716
- return null;
1717
- }
1718
- }
1719
- return typeof current === 'string' ? current : null;
817
+ return this;
818
+ }
819
+ // Override withInheritanceContext to pass it to the child widget if it exists
820
+ withInheritanceContext(context) {
821
+ // Call parent implementation first
822
+ super.withInheritanceContext(context);
823
+ // If we have a child widget, update its inheritance context too
824
+ if (this.childWidget) {
825
+ this.childWidget.withInheritanceContext(this.inheritanceContext);
1720
826
  }
1721
- catch (error) {
1722
- console.error('Error extracting expression value from node:', error);
1723
- return null;
827
+ return this;
828
+ }
829
+ // Override build() to build the child widget at the last moment
830
+ build() {
831
+ // Build the child widget and add it to children before building the form field
832
+ if (this.childWidget) {
833
+ this.ensureChildren();
834
+ this.containerState.children.push(this.childWidget.build());
1724
835
  }
836
+ // Call parent build
837
+ return super.build();
1725
838
  }
1726
- hasTriggerDependency(changedPath) {
1727
- const node = this.node();
1728
- const triggers = node.triggers || node.options?.['triggers'] || this.mergedOptions()?.triggers;
1729
- if (!triggers)
1730
- return false;
1731
- // Check if any trigger event depends on the changed path
1732
- for (const trigger of triggers) {
1733
- if (trigger.event && trigger.event.includes(changedPath)) {
1734
- return true;
1735
- }
839
+ }
840
+ /**
841
+ * Fieldset Container Builder - Liskov Substitution Principle
842
+ * Extends LayoutContainerMixin to inherit layout functionality
843
+ * Specialized for form fields only
844
+ */
845
+ class FieldsetContainerBuilder extends LayoutContainerMixin {
846
+ constructor() {
847
+ super('fieldset-layout');
848
+ this.containerState.options = {};
849
+ }
850
+ setOptions(options) {
851
+ this.containerState.options = { ...this.containerState.options, ...options };
852
+ return this;
853
+ }
854
+ // Individual fluent methods for Fieldset
855
+ setTitle(title) {
856
+ return this.setOptions({ title });
857
+ }
858
+ setDescription(description) {
859
+ return this.setOptions({ description });
860
+ }
861
+ setIcon(icon) {
862
+ return this.setOptions({ icon });
863
+ }
864
+ setCollapsible(collapsible) {
865
+ return this.setOptions({ collapsible });
866
+ }
867
+ setIsOpen(isOpen) {
868
+ return this.setOptions({ isOpen });
869
+ }
870
+ setLook(look) {
871
+ return this.setOptions({ look });
872
+ }
873
+ setShowHeader(showHeader) {
874
+ return this.setOptions({ showHeader });
875
+ }
876
+ setCols(cols) {
877
+ return this.setOptions({ cols });
878
+ }
879
+ // Only form fields are allowed in fieldset
880
+ formField(label, delegate) {
881
+ const field = new FormFieldBuilder(label);
882
+ field.withInheritanceContext(this.inheritanceContext);
883
+ if (delegate) {
884
+ delegate(field);
1736
885
  }
1737
- return false;
886
+ this.ensureChildren();
887
+ this.containerState.children.push(field.build());
888
+ return this;
1738
889
  }
1739
- //#endregion
1740
- // Removed visibility detection methods - not needed for current implementation
1741
- // Detect input changes
1742
- ngOnChanges(changes) {
1743
- if (changes['mode'] || changes['node']) {
1744
- // Check if either 'mode' or 'node' has changed
1745
- this.rerenderComponent();
1746
- }
1747
- }
1748
- rerenderComponent() {
1749
- // Clear any pending render operation to prevent double rendering
1750
- if (this.renderTimeoutId) {
1751
- clearTimeout(this.renderTimeoutId);
1752
- this.renderTimeoutId = null;
1753
- }
1754
- // Reset loading state to allow re-rendering
1755
- this.isLoading.set(false);
1756
- // Schedule the component loading
1757
- this.renderTimeoutId = setTimeout(async () => {
1758
- await this.loadComponent();
1759
- this.renderTimeoutId = null;
1760
- });
890
+ }
891
+ /**
892
+ * List Widget Builder - Liskov Substitution Principle
893
+ * Extends WidgetContainerMixin to inherit all common functionality
894
+ */
895
+ class ListWidgetBuilder extends WidgetContainerMixin {
896
+ constructor() {
897
+ super('list');
898
+ }
899
+ setOptions(options) {
900
+ this.containerState.options = { ...this.containerState.options, ...options };
901
+ return this;
902
+ }
903
+ // Individual fluent methods for List Widget
904
+ setDataSource(dataSource) {
905
+ return this.setOptions({ dataSource });
906
+ }
907
+ setColumns(columns) {
908
+ return this.setOptions({ columns });
909
+ }
910
+ // Event handlers
911
+ setOnRowClick(handler) {
912
+ return this.setOptions({ onRowClick: handler });
913
+ }
914
+ setOnRowDoubleClick(handler) {
915
+ return this.setOptions({ onRowDoubleClick: handler });
916
+ }
917
+ setOnSelectionChange(handler) {
918
+ return this.setOptions({ onSelectionChange: handler });
919
+ }
920
+ setOnRowCommand(handler) {
921
+ return this.setOptions({ onRowCommand: handler });
1761
922
  }
1762
- ngOnDestroy() {
1763
- if (this.renderTimeoutId) {
1764
- clearTimeout(this.renderTimeoutId);
923
+ // Table features
924
+ setPaging(paging) {
925
+ return this.setOptions({ paging });
926
+ }
927
+ setShowHeader(show) {
928
+ return this.setOptions({ showHeader: show });
929
+ }
930
+ setShowFooter(show) {
931
+ return this.setOptions({ showFooter: show });
932
+ }
933
+ setFixHeader(fix) {
934
+ return this.setOptions({ fixHeader: fix });
935
+ }
936
+ setFixFooter(fix) {
937
+ return this.setOptions({ fixFooter: fix });
938
+ }
939
+ setFetchDataMode(mode) {
940
+ return this.setOptions({ fetchDataMode: mode });
941
+ }
942
+ setParentField(field) {
943
+ return this.setOptions({ parentField: field });
944
+ }
945
+ setMinHeight(height) {
946
+ return this.setOptions({ minHeight: height });
947
+ }
948
+ // Selection & Index
949
+ setShowIndex(show) {
950
+ return this.setOptions({ showIndex: show });
951
+ }
952
+ setAllowSelection(allow) {
953
+ return this.setOptions({ allowSelection: allow });
954
+ }
955
+ // Commands
956
+ setPrimaryCommands(commands) {
957
+ return this.setOptions({ primaryCommands: commands });
958
+ }
959
+ setSecondaryCommands(commands) {
960
+ return this.setOptions({ secondaryCommands: commands });
961
+ }
962
+ // Loading
963
+ setLoading(loading) {
964
+ return this.setOptions({ loading });
965
+ }
966
+ }
967
+ /**
968
+ * Dialog Container Builder - Specialized for dialog functionality
969
+ * Uses composition instead of inheritance for cleaner separation
970
+ */
971
+ class DialogContainerBuilder {
972
+ constructor(popupService) {
973
+ this.dialogState = {
974
+ type: 'flex-layout', // This will be overridden when content layout exists
975
+ children: [],
976
+ mode: 'edit',
977
+ dialogOptions: {
978
+ title: '',
979
+ size: 'md',
980
+ closeButton: false,
981
+ },
982
+ actions: {
983
+ footer: {
984
+ prefix: [],
985
+ suffix: [],
986
+ },
987
+ },
988
+ };
989
+ if (popupService) {
990
+ this.popupService = popupService;
1765
991
  }
1766
- if (this.contextUpdateTimeout) {
1767
- clearTimeout(this.contextUpdateTimeout);
992
+ else {
993
+ this.popupService = inject(AXPopupService);
1768
994
  }
1769
- if (this.componentRef) {
1770
- this.componentRef.destroy();
995
+ }
996
+ setOptions(options) {
997
+ this.dialogState.dialogOptions = { ...this.dialogState.dialogOptions, ...options };
998
+ return this;
999
+ }
1000
+ // Individual fluent methods for Dialog
1001
+ setTitle(title) {
1002
+ return this.setOptions({ title });
1003
+ }
1004
+ setMessage(message) {
1005
+ return this.setOptions({ message });
1006
+ }
1007
+ setSize(size) {
1008
+ return this.setOptions({ size });
1009
+ }
1010
+ setCloseButton(closeButton) {
1011
+ return this.setOptions({ closeButton });
1012
+ }
1013
+ setContext(context) {
1014
+ return this.setOptions({ context });
1015
+ }
1016
+ content(delegate) {
1017
+ if (delegate) {
1018
+ // Create a flex container directly instead of through LayoutBuilder
1019
+ const flexContainer = new FlexContainerBuilder();
1020
+ flexContainer.setDirection('column');
1021
+ flexContainer.setGap('10px');
1022
+ delegate(flexContainer);
1023
+ this.contentLayout = flexContainer.build();
1771
1024
  }
1025
+ return this;
1772
1026
  }
1773
- async loadComponent() {
1774
- if (this.isLoading()) {
1775
- return;
1027
+ setActions(delegate) {
1028
+ if (delegate) {
1029
+ const actionBuilder = new ActionBuilder(this);
1030
+ delegate(actionBuilder);
1776
1031
  }
1777
- this.isLoading.set(true);
1778
- try {
1779
- // Destroy the existing component if it exists
1780
- if (this.componentRef) {
1781
- this.componentRef.destroy();
1782
- }
1783
- this.viewContainerRef.clear();
1784
- //
1785
- const widget = this.widgetRegistery.resolve(this.node().type);
1786
- //
1787
- const propertiesToProcess = [
1788
- ...(widget?.properties ?? []),
1789
- ...(widget?.components[this.mode()]?.properties ?? []),
1790
- ]?.filter((c) => c.schema.defaultValue != null);
1791
- // Process default values (evaluate expressions if needed)
1792
- const props = {};
1793
- for (const property of propertiesToProcess) {
1794
- const defaultValue = property.schema.defaultValue;
1795
- if (typeof defaultValue === 'string' && this.expressionEvaluator.isExpression(defaultValue)) {
1796
- // Evaluate expression for default value
1797
- try {
1798
- const evaluatedValue = await this.evaluateExpression(defaultValue);
1799
- props[property.name] = evaluatedValue;
1800
- }
1801
- catch (error) {
1802
- console.error(`Error evaluating default value expression for property ${property.name}:`, error);
1803
- props[property.name] = cloneDeep(defaultValue); // Fallback to original value
1804
- }
1805
- }
1806
- else {
1807
- // Use static default value
1808
- props[property.name] = cloneDeep(defaultValue);
1809
- }
1810
- }
1811
- //
1812
- this.mergedOptions.set(merge(props, widget?.options, this.node().options) || {});
1813
- this.expressionEvaluators.clear();
1814
- // Register expressions from widget defaults and node options to cover related-entity cases
1815
- this.preprocessAndInitialOptions(cloneDeep(widget?.options));
1816
- this.preprocessAndInitialOptions(cloneDeep(this.node().options));
1817
- await this.updateOptionsBasedOnContext();
1818
- //
1819
- this._options.update((val) => ({ ...val, ...this.mergedOptions() }));
1820
- // Evaluate default value
1821
- let defaultValue = this.node().defaultValue;
1822
- if (defaultValue && this.expressionEvaluator.isExpression(defaultValue)) {
1823
- defaultValue = await this.evaluateExpression(defaultValue);
1824
- }
1825
- //
1826
- const tokenValue = {
1827
- node: this.node(),
1828
- defaultValue: defaultValue,
1829
- options: this.mergedOptions(),
1830
- config: widget,
1032
+ return this;
1033
+ }
1034
+ // Build method to create dialog node
1035
+ build() {
1036
+ // If we have content layout, use it directly to avoid extra wrapper
1037
+ if (this.contentLayout) {
1038
+ return {
1039
+ ...this.contentLayout,
1040
+ // Add dialog-specific properties
1041
+ options: {
1042
+ ...this.contentLayout.options,
1043
+ ...this.dialogState.dialogOptions,
1044
+ },
1831
1045
  };
1832
- const token = Injector.create({
1833
- parent: this.injector,
1834
- providers: [
1835
- {
1836
- provide: AXP_WIDGET_TOKEN,
1837
- useValue: tokenValue,
1046
+ }
1047
+ // Fallback to dialog state structure if no content
1048
+ const result = {
1049
+ ...this.dialogState,
1050
+ children: [],
1051
+ };
1052
+ // Add dialog-specific properties
1053
+ if (this.dialogState.dialogOptions) {
1054
+ result.options = { ...result.options, ...this.dialogState.dialogOptions };
1055
+ }
1056
+ return result;
1057
+ }
1058
+ // Dialog-specific methods
1059
+ async show() {
1060
+ const dialogNode = this.build();
1061
+ // Import the dialog renderer component dynamically
1062
+ const { AXPDialogRendererComponent } = await Promise.resolve().then(function () { return dialogRenderer_component; });
1063
+ // Create dialog configuration
1064
+ const dialogConfig = {
1065
+ title: this.dialogState.dialogOptions?.title || '',
1066
+ message: this.dialogState.dialogOptions?.message,
1067
+ context: this.dialogState.dialogOptions?.context || {},
1068
+ definition: dialogNode,
1069
+ actions: this.dialogState.actions,
1070
+ };
1071
+ // The Promise resolves when user clicks an action button
1072
+ return new Promise(async (resolve) => {
1073
+ this.popupService.open(AXPDialogRendererComponent, {
1074
+ title: dialogConfig.title,
1075
+ size: this.dialogState.dialogOptions?.size || 'md',
1076
+ closeButton: this.dialogState.dialogOptions?.closeButton || false,
1077
+ closeOnBackdropClick: false,
1078
+ draggable: false,
1079
+ data: {
1080
+ config: dialogConfig,
1081
+ callBack: (result) => {
1082
+ // Resolve with the dialog reference when user clicks an action
1083
+ resolve(result);
1838
1084
  },
1839
- ],
1840
- });
1841
- //
1842
- const loadingRef = this.viewContainerRef.createComponent(AXPWidgetPlaceholderComponent);
1843
- //
1844
- const com = await widget?.components[this.mode()]?.component();
1845
- if (!com) {
1846
- console.error(`${this.node().type} widget component not found with mode: ${this.mode()}`);
1847
- return;
1848
- }
1849
- this.componentRef = this.viewContainerRef.createComponent(com, { injector: token });
1850
- this.instance = this.componentRef.instance;
1851
- this.instance.setStatus(AXPWidgetStatus.Rendering);
1852
- this.instance.parent = this.parentNode();
1853
- this.instance.index = this.index();
1854
- this.instance.mode = this.mode();
1855
- this.instance.setStatus(AXPWidgetStatus.Rendered);
1856
- this.instance?.onOptionsChanged?.pipe(this.unsubscriber.takeUntilDestroy).subscribe((c) => {
1857
- this.onOptionsChanged.emit({ sender: this, widget: c.sender });
1858
- });
1859
- this.instance?.onValueChanged?.pipe(this.unsubscriber.takeUntilDestroy).subscribe((c) => {
1860
- this.onValueChanged.emit({ sender: this, widget: c.sender });
1085
+ },
1861
1086
  });
1862
- await this.updateValueBasedOnFormula();
1863
- await this.assignTriggers();
1864
- //
1865
- loadingRef.destroy();
1866
- // Mark that initial render is complete
1867
- this.hasInitialRender = true;
1868
- // Re-evaluate expressions after initial render to catch late-arriving related context
1869
- if (this.expressionEvaluators.size > 0) {
1870
- console.log(`🔄 [${this.node().type}] Re-evaluating expressions after initial render`);
1871
- await this.updateOptionsBasedOnContext();
1872
- this.applyOptions();
1873
- }
1874
- // Process any buffered pre-render context changes now that the component is ready
1875
- if (this.preRenderContextQueue.size > 0) {
1876
- console.log(`🚀 [${this.node().type}] Processing ${this.preRenderContextQueue.size} buffered pre-render changes`);
1877
- this.preRenderContextQueue.forEach((p) => this.contextUpdateQueue.add(p));
1878
- this.preRenderContextQueue.clear();
1879
- await this.processBatchedUpdates();
1880
- }
1087
+ });
1088
+ }
1089
+ }
1090
+ //#endregion
1091
+ //#region ---- Widget Builder Implementation ----
1092
+ /**
1093
+ * Widget Builder - Single Responsibility Principle
1094
+ * Handles individual widget configuration and building
1095
+ */
1096
+ class WidgetBuilder {
1097
+ constructor(name) {
1098
+ this.widgetState = {
1099
+ type: 'widget',
1100
+ options: {},
1101
+ };
1102
+ this.inheritanceContext = {};
1103
+ if (name) {
1104
+ this.widgetState.name = name;
1105
+ }
1106
+ }
1107
+ type(type) {
1108
+ this.widgetState.type = type;
1109
+ return this;
1110
+ }
1111
+ name(name) {
1112
+ this.widgetState.name = name;
1113
+ if (!this.widgetState.path) {
1114
+ this.widgetState.path = name;
1115
+ }
1116
+ return this;
1117
+ }
1118
+ path(path) {
1119
+ this.widgetState.path = path;
1120
+ return this;
1121
+ }
1122
+ options(options) {
1123
+ // Merge options instead of replacing to preserve inherited properties
1124
+ this.widgetState.options = { ...this.widgetState.options, ...options };
1125
+ return this;
1126
+ }
1127
+ layout(value) {
1128
+ if (typeof value === 'number') {
1129
+ this.widgetState.layout = {
1130
+ positions: {
1131
+ sm: { colSpan: 12 },
1132
+ md: { colSpan: 12 },
1133
+ lg: { colSpan: value },
1134
+ xl: { colSpan: value },
1135
+ xxl: { colSpan: value },
1136
+ },
1137
+ };
1881
1138
  }
1882
- catch (error) {
1883
- console.error('Error loading component:', error);
1139
+ else {
1140
+ this.widgetState.layout = value;
1884
1141
  }
1885
- finally {
1886
- this.isLoading.set(false);
1142
+ return this;
1143
+ }
1144
+ mode(mode) {
1145
+ this.widgetState.mode = mode;
1146
+ this.inheritanceContext.mode = mode;
1147
+ return this;
1148
+ }
1149
+ visible(condition) {
1150
+ if (!this.widgetState.options) {
1151
+ this.widgetState.options = {};
1887
1152
  }
1153
+ this.widgetState.options['visible'] = condition;
1154
+ this.inheritanceContext.visible = condition;
1155
+ return this;
1888
1156
  }
1889
- applyOptions() {
1890
- if (!this.instance)
1891
- return;
1892
- const currentOptions = this.mergedOptions();
1893
- // Check if options have actually changed
1894
- if (this.hasOptionsChanged(currentOptions)) {
1895
- console.log('applyOptions', this.node().path, '- options changed');
1896
- this._options.update((val) => ({ ...val, ...currentOptions }));
1897
- this.instance.setOptions(currentOptions);
1898
- this.lastAppliedOptions = cloneDeep(currentOptions); // Deep clone using Lodash
1157
+ disabled(condition) {
1158
+ if (!this.widgetState.options) {
1159
+ this.widgetState.options = {};
1899
1160
  }
1900
- else {
1901
- console.log('applyOptions', this.node().path, '- no changes, skipping');
1161
+ this.widgetState.options['disabled'] = condition;
1162
+ this.inheritanceContext.disabled = condition;
1163
+ return this;
1164
+ }
1165
+ readonly(condition) {
1166
+ if (!this.widgetState.options) {
1167
+ this.widgetState.options = {};
1902
1168
  }
1169
+ this.widgetState.options['readonly'] = condition;
1170
+ this.inheritanceContext.readonly = condition;
1171
+ return this;
1903
1172
  }
1904
- hasOptionsChanged(newOptions) {
1905
- if (!this.lastAppliedOptions) {
1906
- return true; // First time, always apply
1173
+ direction(direction) {
1174
+ if (!this.widgetState.options) {
1175
+ this.widgetState.options = {};
1907
1176
  }
1908
- // Deep comparison of options using Lodash isEqual
1909
- return !isEqual(newOptions, this.lastAppliedOptions);
1177
+ this.widgetState.options['direction'] = direction;
1178
+ this.inheritanceContext.direction = direction;
1179
+ return this;
1910
1180
  }
1911
- // Removed deepCloneValue method - now using Lodash cloneDeep
1912
- checkFormulaForUpdate(formula, path) {
1913
- if (formula) {
1914
- const regex = /context\.eval\('([^']+)'\)/g;
1915
- const matches = formula.match(regex);
1916
- const nodes = matches ? matches.map((match) => match.match(/'([^']+)'/)[1]) : [];
1917
- return nodes.includes(path);
1181
+ // Inheritance context methods
1182
+ withInheritanceContext(context) {
1183
+ this.inheritanceContext = mergeInheritanceContext(context);
1184
+ // Apply inherited properties to widget state
1185
+ const resolved = resolveInheritedProperties(context, this.inheritanceContext);
1186
+ // Always apply inherited properties (remove the conditions that check if already set)
1187
+ // This allows properties to be updated when inheritance context changes
1188
+ if (resolved.mode) {
1189
+ this.widgetState.mode = resolved.mode;
1918
1190
  }
1919
- else
1920
- return false;
1191
+ if (!this.widgetState.options)
1192
+ this.widgetState.options = {};
1193
+ if (resolved.disabled !== undefined) {
1194
+ this.widgetState.options['disabled'] = resolved.disabled;
1195
+ }
1196
+ if (resolved.readonly !== undefined) {
1197
+ this.widgetState.options['readonly'] = resolved.readonly;
1198
+ }
1199
+ if (resolved.direction !== undefined) {
1200
+ this.widgetState.options['direction'] = resolved.direction;
1201
+ }
1202
+ if (resolved.visible !== undefined) {
1203
+ this.widgetState.options['visible'] = resolved.visible;
1204
+ }
1205
+ return this;
1921
1206
  }
1922
- preprocessAndInitialOptions(obj, pathPrefix = '') {
1923
- if (!obj) {
1924
- return;
1925
- }
1926
- Object.entries(obj).forEach(([key, value]) => {
1927
- const currentPath = pathPrefix ? `${pathPrefix}.${key}` : key;
1928
- // CRITICAL FIX: Skip trigger actions during options processing
1929
- //
1930
- // PROBLEM: Trigger actions were being evaluated immediately during widget setup/options processing,
1931
- // causing them to execute before the actual trigger event occurred. This meant triggers would fire
1932
- // during initialization instead of when the specified context path actually changed.
1933
- //
1934
- // ROOT CAUSE: The expression evaluator was processing trigger action expressions (like console.log
1935
- // or widget.setValue calls) as part of the normal options preprocessing, treating them as dynamic
1936
- // expressions that needed immediate evaluation.
1937
- //
1938
- // SOLUTION: Detect when we're processing trigger actions and store them as static values without
1939
- // expression evaluation. This ensures trigger actions are only evaluated when the trigger's
1940
- // subscription callback actually fires (when the event condition is met).
1941
- //
1942
- // RESULT: Triggers now only execute when their event filters pass (e.g., when the specified
1943
- // context path changes), not during widget initialization.
1944
- if (currentPath.includes('triggers') && (key === 'action' || currentPath.endsWith('.action'))) {
1945
- // Apply static values directly without expression evaluation
1946
- this.mergedOptions.update((currentOptions) => {
1947
- return set(currentOptions, currentPath, value);
1948
- });
1949
- return;
1950
- }
1951
- if (typeof value === 'string' && this.expressionEvaluator.isExpression(value)) {
1952
- // Cache dynamic expression for later evaluation
1953
- this.expressionEvaluators.set(currentPath, () => this.evaluateExpression(value));
1954
- }
1955
- else if (typeof value === 'object' &&
1956
- value !== null &&
1957
- (value.constructor === Object || Array.isArray(value))) {
1958
- // Recursively handle nested objects
1959
- this.preprocessAndInitialOptions(value, currentPath);
1960
- }
1961
- else {
1962
- // Apply static values directly
1963
- this.mergedOptions.update((currentOptions) => {
1964
- return set(currentOptions, currentPath, value);
1965
- });
1966
- }
1967
- });
1207
+ getInheritanceContext() {
1208
+ return { ...this.inheritanceContext };
1968
1209
  }
1969
- async updateOptionsBasedOnContext() {
1970
- // Early return if no expressions to evaluate
1971
- if (this.expressionEvaluators.size === 0) {
1972
- return 0;
1973
- }
1974
- console.log(`🔍 [${this.node().type}] Evaluating ${this.expressionEvaluators.size} expressions`);
1975
- const updatePromises = Array.from(this.expressionEvaluators).map(async ([path, evaluator]) => {
1976
- // Check cache first
1977
- const cachedValue = this.getCachedExpressionResult(path);
1978
- if (cachedValue !== null) {
1979
- console.log(`💾 [${this.node().type}] Using cached expression result for '${path}'`);
1980
- return { path, newValue: cachedValue, fromCache: true };
1981
- }
1982
- // Evaluate expression if not cached
1983
- const evalStartTime = performance.now();
1984
- const newValue = await evaluator();
1985
- const evalTime = performance.now() - evalStartTime;
1986
- // Check if result has actually changed using Lodash isEqual
1987
- const lastValue = this.lastExpressionResults.get(path);
1988
- const hasChanged = !isEqual(newValue, lastValue);
1989
- if (hasChanged) {
1990
- console.log(`📝 [${this.node().type}] Expression '${path}' evaluated in ${evalTime.toFixed(2)}ms - value changed`);
1991
- // Cache the result and track it
1992
- this.setCachedExpressionResult(path, newValue);
1993
- this.lastExpressionResults.set(path, cloneDeep(newValue));
1994
- return { path, newValue, fromCache: false, hasChanged: true };
1995
- }
1996
- else {
1997
- console.log(`📝 [${this.node().type}] Expression '${path}' evaluated in ${evalTime.toFixed(2)}ms - value unchanged, skipping update`);
1998
- // Cache the result but don't update
1999
- this.setCachedExpressionResult(path, newValue);
2000
- this.lastExpressionResults.set(path, cloneDeep(newValue));
2001
- return { path, newValue, fromCache: false, hasChanged: false };
2002
- }
1210
+ build() {
1211
+ return {
1212
+ name: this.widgetState.name,
1213
+ type: this.widgetState.type,
1214
+ options: this.widgetState.options,
1215
+ mode: this.widgetState.mode,
1216
+ path: this.widgetState.path,
1217
+ defaultValue: this.widgetState.defaultValue,
1218
+ };
1219
+ }
1220
+ }
1221
+ //#region ---- Action Builder Implementation ----
1222
+ class ActionBuilder {
1223
+ constructor(dialogBuilder) {
1224
+ this.dialogBuilder = dialogBuilder;
1225
+ }
1226
+ cancel(text) {
1227
+ if (!this.dialogBuilder['dialogState'].actions.footer.suffix) {
1228
+ this.dialogBuilder['dialogState'].actions.footer.suffix = [];
1229
+ }
1230
+ this.dialogBuilder['dialogState'].actions.footer.suffix.push({
1231
+ title: text || '@general:actions.cancel.title',
1232
+ icon: 'fa-times',
1233
+ color: 'default',
1234
+ command: { name: 'cancel' },
2003
1235
  });
2004
- // Wait for all evaluators to complete
2005
- const updates = await Promise.all(updatePromises);
2006
- // Filter updates to only include those that have actually changed
2007
- const changedUpdates = updates.filter((update) => update.hasChanged !== false);
2008
- // Apply updates to mergedOptions only for changed values
2009
- if (changedUpdates.length > 0) {
2010
- this.mergedOptions.update((o) => {
2011
- const updatedOptions = { ...o };
2012
- changedUpdates.forEach(({ path, newValue }) => {
2013
- set(updatedOptions, path, newValue);
2014
- });
2015
- return updatedOptions;
2016
- });
1236
+ return this;
1237
+ }
1238
+ submit(text) {
1239
+ if (!this.dialogBuilder['dialogState'].actions.footer.suffix) {
1240
+ this.dialogBuilder['dialogState'].actions.footer.suffix = [];
2017
1241
  }
2018
- const cacheHits = updates.filter((update) => update.fromCache).length;
2019
- const skippedUpdates = updates.length - changedUpdates.length;
2020
- console.log(`📋 [${this.node().type}] Applied ${changedUpdates.length} expression updates (${cacheHits} cache hits, ${skippedUpdates} skipped)`);
2021
- return changedUpdates.length;
1242
+ this.dialogBuilder['dialogState'].actions.footer.suffix.push({
1243
+ title: text || '@general:actions.submit.title',
1244
+ icon: 'fa-check',
1245
+ color: 'primary',
1246
+ command: { name: 'submit', options: { validate: true } },
1247
+ });
1248
+ return this;
2022
1249
  }
2023
- async updateValueBasedOnFormula() {
2024
- if (this.node().formula) {
2025
- const value = await this.evaluateExpression(this.node().formula);
2026
- this.instance.setValue(value);
1250
+ custom(action) {
1251
+ if (!this.dialogBuilder['dialogState'].actions.footer.suffix) {
1252
+ this.dialogBuilder['dialogState'].actions.footer.suffix = [];
2027
1253
  }
1254
+ this.dialogBuilder['dialogState'].actions.footer.suffix.push(action);
1255
+ return this;
1256
+ }
1257
+ }
1258
+
1259
+ class AXPLayoutConversionService {
1260
+ constructor() {
1261
+ //#region ---- Caching ----
1262
+ this.widgetTreeCache = new Map();
1263
+ this.formDefinitionCache = new Map();
1264
+ }
1265
+ //#endregion
1266
+ //#region ---- Public Methods ----
1267
+ /**
1268
+ * Convert AXPDynamicFormDefinition to AXPWidgetNode tree structure
1269
+ * Groups become Fieldset Layouts with Form Field widgets as children
1270
+ * Fields become Form Field widgets with Editor widgets as children
1271
+ */
1272
+ convertFormDefinition(formDefinition) {
1273
+ // Create cache key based on form definition content
1274
+ const cacheKey = this.createFormDefinitionCacheKey(formDefinition);
1275
+ // Check cache first
1276
+ if (this.widgetTreeCache.has(cacheKey)) {
1277
+ return this.widgetTreeCache.get(cacheKey);
1278
+ }
1279
+ // Generate widget tree
1280
+ const widgetTree = {
1281
+ type: 'grid-layout',
1282
+ name: 'dynamic-form-container',
1283
+ options: {
1284
+ title: 'Dynamic Form',
1285
+ grid: {
1286
+ default: {
1287
+ columns: 1,
1288
+ gap: '1rem',
1289
+ },
1290
+ },
1291
+ },
1292
+ children: formDefinition.groups.map((group) => this.createGroupAsFieldsetWidget(group)),
1293
+ };
1294
+ // Cache the result
1295
+ this.widgetTreeCache.set(cacheKey, widgetTree);
1296
+ return widgetTree;
2028
1297
  }
2029
- async evaluateExpression(templateExpression) {
2030
- try {
2031
- const scope = this.buildExpressionScope();
2032
- return await this.expressionEvaluator.evaluate(templateExpression, scope);
1298
+ /**
1299
+ * Convert AXPWidgetNode tree back to AXPDynamicFormDefinition
1300
+ * Parses Fieldset Layouts back to Groups
1301
+ * Parses Form Field widgets back to Fields
1302
+ */
1303
+ convertWidgetTreeToFormDefinition(widgetTree) {
1304
+ // Create cache key based on widget tree content
1305
+ const cacheKey = this.createWidgetTreeCacheKey(widgetTree);
1306
+ // Check cache first
1307
+ if (this.formDefinitionCache.has(cacheKey)) {
1308
+ return this.formDefinitionCache.get(cacheKey);
1309
+ }
1310
+ // Parse widget tree
1311
+ const groups = [];
1312
+ if (widgetTree.children) {
1313
+ widgetTree.children.forEach((child) => {
1314
+ if (child.type === 'fieldset-layout') {
1315
+ const group = this.extractGroupFromFieldset(child);
1316
+ groups.push(group);
1317
+ }
1318
+ });
2033
1319
  }
2034
- catch (error) {
2035
- console.error('Error evaluating expression:', error);
1320
+ const formDefinition = { groups };
1321
+ // Cache the result
1322
+ this.formDefinitionCache.set(cacheKey, formDefinition);
1323
+ return formDefinition;
1324
+ }
1325
+ /**
1326
+ * Validate that a widget tree represents a valid dynamic form structure
1327
+ */
1328
+ validateFormWidgetTree(widgetTree) {
1329
+ if (!widgetTree || widgetTree.type !== 'grid-layout') {
2036
1330
  return false;
2037
1331
  }
1332
+ if (!widgetTree.children || widgetTree.children.length === 0) {
1333
+ return true; // Empty form is valid
1334
+ }
1335
+ // Check that all children are fieldset-layout widgets
1336
+ return widgetTree.children.every((child) => child.type === 'fieldset-layout' &&
1337
+ child.children &&
1338
+ child.children.every((formField) => formField.type === 'form-field' && formField.children && formField.children.length > 0));
2038
1339
  }
2039
- buildExpressionScope() {
2040
- return {
2041
- context: this.getContextScope(),
2042
- events: this.getEventScope(),
2043
- widget: this.getWidgetScope(),
2044
- methods: this.getFunctionScope(),
2045
- vars: this.getVariablesScope(),
2046
- };
1340
+ /**
1341
+ * Clear all caches
1342
+ */
1343
+ clearCaches() {
1344
+ this.widgetTreeCache.clear();
1345
+ this.formDefinitionCache.clear();
2047
1346
  }
2048
- getContextScope() {
1347
+ /**
1348
+ * Get cache statistics
1349
+ */
1350
+ getCacheStats() {
2049
1351
  return {
2050
- eval: (path) => {
2051
- //TODO: Handle array index
2052
- const fullPath = path.startsWith('>') ? `${this.instance?.parentPath()}.${path.substring(1)}` : path;
2053
- const value = this.contextService.getValue(fullPath);
2054
- return value;
2055
- },
2056
- set: (path, value) => {
2057
- this.contextService.update(path, value);
2058
- },
2059
- data: () => {
2060
- return this.contextService.data();
2061
- },
2062
- isDirty: () => {
2063
- return this.contextService.isDirty();
2064
- },
1352
+ widgetTreeCacheSize: this.widgetTreeCache.size,
1353
+ formDefinitionCacheSize: this.formDefinitionCache.size,
2065
1354
  };
2066
1355
  }
2067
- getEventScope() {
1356
+ //#endregion
1357
+ //#region ---- Private Methods ----
1358
+ /**
1359
+ * Convert a single group to Fieldset widget structure
1360
+ */
1361
+ createGroupAsFieldsetWidget(group) {
1362
+ // Determine columns count from layout or default to 1
1363
+ const columnsCount = 1;
2068
1364
  return {
2069
- context: (path) => {
2070
- return this.onContextChanged.pipe(filter((c) => {
2071
- // If no path filter specified, pass all events
2072
- if (path == null || path === '') {
2073
- return true;
2074
- }
2075
- // Ensure c.path exists
2076
- if (!c.path) {
2077
- return false;
2078
- }
2079
- // Pattern: "prefix*" - matches paths that start with prefix
2080
- if (path.endsWith('*')) {
2081
- const prefix = path.substring(0, path.length - 1);
2082
- return c.path.startsWith(prefix);
2083
- }
2084
- // Pattern: "*suffix" - matches paths that end with suffix
2085
- else if (path.startsWith('*')) {
2086
- const suffix = path.substring(1);
2087
- return c.path.endsWith(suffix);
2088
- }
2089
- // Exact match
2090
- else {
2091
- return c.path === path;
2092
- }
2093
- }));
1365
+ type: 'fieldset-layout',
1366
+ name: group.name,
1367
+ options: {
1368
+ title: group.title,
1369
+ description: group.description,
1370
+ cols: columnsCount,
2094
1371
  },
2095
- from: (event) => get(this.instance.api(), event),
1372
+ children: this.createFieldWidgets(group.parameters, columnsCount),
2096
1373
  };
2097
1374
  }
2098
- getWidgetScope() {
2099
- return {
2100
- call: (name, ...args) => {
2101
- this.instance.call(name, ...args);
2102
- },
2103
- setValue: (value) => {
2104
- this.instance.setValue(value);
2105
- },
2106
- clear: () => {
2107
- this.instance.setValue(undefined);
2108
- },
2109
- refresh: () => {
2110
- const refresh = this.instance?.['refresh'];
2111
- if (refresh && typeof refresh === 'function') {
2112
- refresh.bind(this.instance)();
2113
- }
2114
- },
2115
- output: (name) => {
2116
- this.instance.output(name);
2117
- },
2118
- find: (id) => {
2119
- return this.builderService.getWidget(id);
2120
- },
2121
- };
1375
+ /**
1376
+ * Convert fields to Form Field widgets
1377
+ */
1378
+ createFieldWidgets(fields, columnsCount) {
1379
+ return fields.map((field) => this.createFormFieldWidget(field));
2122
1380
  }
2123
- getFunctionScope() {
2124
- const scope = {
2125
- sum: (values) => {
2126
- return sum(values);
2127
- },
2128
- translate: (text, options) => {
2129
- return this.translateService.translateAsync(text, options);
2130
- },
2131
- widgetInfo: (name) => {
2132
- return this.widgetService.resolve(name);
1381
+ /**
1382
+ * Convert a single field to Form Field widget with editor as child
1383
+ */
1384
+ createFormFieldWidget(field) {
1385
+ return {
1386
+ type: 'form-field',
1387
+ name: field.path,
1388
+ options: {
1389
+ label: field.title,
1390
+ description: field.description,
1391
+ showLabel: true,
2133
1392
  },
1393
+ children: [field.widget], // The editor widget becomes a child of form-field
2134
1394
  };
2135
- // Add custom functions from builder service
2136
- Object.entries(this.builderService.functions).forEach(([key, fn]) => {
2137
- scope[key] = (...args) => {
2138
- return fn(...args);
2139
- };
2140
- });
2141
- return scope;
2142
1395
  }
2143
- getVariablesScope() {
1396
+ /**
1397
+ * Extract group information from Fieldset Layout widget
1398
+ */
1399
+ extractGroupFromFieldset(fieldsetNode) {
1400
+ const columnsCount = fieldsetNode.options?.['cols'] || 1;
1401
+ // Extract fields directly from fieldset children
1402
+ const fields = [];
1403
+ if (fieldsetNode.children) {
1404
+ fieldsetNode.children.forEach((formField) => {
1405
+ if (formField.type === 'form-field' && formField.children && formField.children.length > 0) {
1406
+ const field = this.extractFieldFromFormWidget(formField);
1407
+ if (field) {
1408
+ fields.push(field);
1409
+ }
1410
+ }
1411
+ });
1412
+ }
2144
1413
  return {
2145
- eval: (path) => get(this.builderService.variables, path),
1414
+ name: fieldsetNode.name || `group-${Date.now()}`,
1415
+ title: fieldsetNode.options?.['title'],
1416
+ description: fieldsetNode.options?.['description'],
1417
+ parameters: fields,
2146
1418
  };
2147
1419
  }
2148
- async assignTriggers() {
2149
- const node = this.node();
2150
- // Check multiple possible locations for triggers
2151
- const triggers = node.triggers || node.options?.['triggers'] || this.mergedOptions()?.triggers;
2152
- if (!triggers) {
2153
- return;
2154
- }
2155
- for (const trigger of triggers) {
2156
- try {
2157
- const event = await this.evaluateTrigger(trigger.event);
2158
- if (event) {
2159
- event.pipe(this.unsubscriber.takeUntilDestroy).subscribe(() => {
2160
- const exec = async (action) => {
2161
- if (!isEmpty(action) && isString(action)) {
2162
- await this.evaluateAction(action);
2163
- }
2164
- };
2165
- const actions = Array.isArray(trigger.action) ? trigger.action : [trigger.action];
2166
- actions.forEach(async (a) => await exec(a));
2167
- });
2168
- }
2169
- }
2170
- catch (error) {
2171
- console.error('Error assigning trigger:', error);
2172
- }
1420
+ /**
1421
+ * Extract field information from Form Field widget
1422
+ */
1423
+ extractFieldFromFormWidget(formFieldNode) {
1424
+ if (!formFieldNode.children || formFieldNode.children.length === 0) {
1425
+ return null;
2173
1426
  }
1427
+ const editorWidget = formFieldNode.children[0];
1428
+ return {
1429
+ path: formFieldNode.name || editorWidget.name || `field-${Date.now()}`,
1430
+ title: formFieldNode.options?.['label'],
1431
+ description: formFieldNode.options?.['description'],
1432
+ widget: editorWidget,
1433
+ mode: formFieldNode.mode,
1434
+ };
2174
1435
  }
2175
- async evaluateTrigger(templateExpression) {
2176
- try {
2177
- const scope = this.buildExpressionScope();
2178
- const result = await this.expressionEvaluator.evaluate(templateExpression, scope);
2179
- // Check if result is an Observable
2180
- if (result && typeof result.subscribe === 'function') {
2181
- return result;
2182
- }
2183
- else {
2184
- return null;
2185
- }
1436
+ /**
1437
+ * Create cache key for form definition
1438
+ */
1439
+ createFormDefinitionCacheKey(formDefinition) {
1440
+ // Create a hash-like key instead of full JSON string
1441
+ const keyParts = [];
1442
+ keyParts.push(`groups:${formDefinition.groups.length}`);
1443
+ formDefinition.groups.forEach((group, groupIndex) => {
1444
+ keyParts.push(`g${groupIndex}:${group.name}:${group.parameters.length}`);
1445
+ group.parameters.forEach((param, paramIndex) => {
1446
+ keyParts.push(`p${groupIndex}.${paramIndex}:${param.path}:${param.widget.type}`);
1447
+ });
1448
+ });
1449
+ if (formDefinition.mode) {
1450
+ keyParts.push(`mode:${formDefinition.mode}`);
2186
1451
  }
2187
- catch (error) {
2188
- console.error('Error evaluating trigger expression:', error);
2189
- return null;
1452
+ // Join with delimiter and create a shorter hash
1453
+ const keyString = keyParts.join('|');
1454
+ // If still too long, create a simple hash
1455
+ if (keyString.length > 100) {
1456
+ return this.createSimpleHash(keyString);
2190
1457
  }
1458
+ return keyString;
2191
1459
  }
2192
- async evaluateAction(templateExpression) {
2193
- try {
2194
- const scope = this.buildExpressionScope();
2195
- await this.expressionEvaluator.evaluate(templateExpression, scope);
1460
+ /**
1461
+ * Create cache key for widget tree
1462
+ */
1463
+ createWidgetTreeCacheKey(widgetTree) {
1464
+ // Create a hash-like key instead of full JSON string
1465
+ const keyParts = [];
1466
+ keyParts.push(`type:${widgetTree.type}`);
1467
+ if (widgetTree.name) {
1468
+ keyParts.push(`name:${widgetTree.name}`);
1469
+ }
1470
+ if (widgetTree.children) {
1471
+ keyParts.push(`children:${widgetTree.children.length}`);
1472
+ widgetTree.children.forEach((child, index) => {
1473
+ keyParts.push(`c${index}:${child.type}`);
1474
+ if (child.children) {
1475
+ keyParts.push(`cc${index}:${child.children.length}`);
1476
+ child.children.forEach((grandChild, gIndex) => {
1477
+ keyParts.push(`gc${index}.${gIndex}:${grandChild.type}`);
1478
+ if (grandChild.children) {
1479
+ keyParts.push(`gcc${index}.${gIndex}:${grandChild.children.length}`);
1480
+ }
1481
+ });
1482
+ }
1483
+ });
2196
1484
  }
2197
- catch (error) {
2198
- console.error('Error evaluating action expression:', templateExpression, error);
1485
+ // Join with delimiter and create a shorter hash
1486
+ const keyString = keyParts.join('|');
1487
+ // If still too long, create a simple hash
1488
+ if (keyString.length > 100) {
1489
+ return this.createSimpleHash(keyString);
2199
1490
  }
1491
+ return keyString;
2200
1492
  }
2201
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPWidgetRendererDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
2202
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.1.8", type: AXPWidgetRendererDirective, isStandalone: false, selector: "[axp-widget-renderer]", inputs: { parentNode: { classPropertyName: "parentNode", publicName: "parentNode", isSignal: true, isRequired: false, transformFunction: null }, index: { classPropertyName: "index", publicName: "index", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: true, transformFunction: null }, node: { classPropertyName: "node", publicName: "node", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { onOptionsChanged: "onOptionsChanged", onValueChanged: "onValueChanged" }, providers: [
2203
- {
2204
- provide: AXUnsubscriber,
2205
- },
2206
- ], exportAs: ["widgetRenderer"], usesOnChanges: true, ngImport: i0 }); }
1493
+ /**
1494
+ * Create a simple hash from a string
1495
+ */
1496
+ createSimpleHash(str) {
1497
+ let hash = 0;
1498
+ if (str.length === 0)
1499
+ return hash.toString();
1500
+ for (let i = 0; i < str.length; i++) {
1501
+ const char = str.charCodeAt(i);
1502
+ hash = (hash << 5) - hash + char;
1503
+ hash = hash & hash; // Convert to 32-bit integer
1504
+ }
1505
+ return Math.abs(hash).toString(36); // Convert to base36 for shorter string
1506
+ }
1507
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: AXPLayoutConversionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1508
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: AXPLayoutConversionService, providedIn: 'root' }); }
2207
1509
  }
2208
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPWidgetRendererDirective, decorators: [{
2209
- type: Directive,
1510
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: AXPLayoutConversionService, decorators: [{
1511
+ type: Injectable,
2210
1512
  args: [{
2211
- selector: '[axp-widget-renderer]',
2212
- exportAs: 'widgetRenderer',
2213
- providers: [
2214
- {
2215
- provide: AXUnsubscriber,
2216
- },
2217
- ],
2218
- standalone: false,
1513
+ providedIn: 'root',
2219
1514
  }]
2220
- }], ctorParameters: () => [] });
1515
+ }] });
2221
1516
 
2222
- const COMPONENTS = [AXPWidgetContainerComponent, AXPWidgetColumnRendererComponent, AXPWidgetRendererDirective];
2223
- class AXPLayoutBuilderModule {
2224
- static forRoot(config) {
2225
- return {
2226
- ngModule: AXPLayoutBuilderModule,
2227
- providers: [
2228
- {
2229
- provide: 'AXPLayoutBuilderModuleFactory',
2230
- useFactory: (registry) => async () => {
2231
- await Promise.all(config?.widgets?.map((w) => Promise.resolve(registry.register(w))) || []);
2232
- await Promise.all(config?.extendedWidgets?.map((ew) => Promise.resolve(registry.extend(ew.parentName, ew.widget))) || []);
2233
- },
2234
- deps: [AXPWidgetRegistryService],
2235
- multi: true,
2236
- },
2237
- ],
1517
+ class AXPLayoutRendererComponent {
1518
+ constructor() {
1519
+ this.evaluatorService = inject(AXPExpressionEvaluatorService);
1520
+ this.conversionService = inject(AXPLayoutConversionService);
1521
+ /**
1522
+ * Tracks the latest scheduled evaluation to ensure last-write-wins for async evaluate
1523
+ */
1524
+ this.evaluationRunId = 0;
1525
+ /**
1526
+ * RxJS subjects for context management
1527
+ */
1528
+ this.contextUpdateSubject = new Subject();
1529
+ this.contextChangeSubject = new Subject();
1530
+ /**
1531
+ * Cache for expression evaluation results
1532
+ */
1533
+ this.expressionCache = new Map();
1534
+ /**
1535
+ * Cache for widget tree comparisons
1536
+ */
1537
+ this.widgetTreeCache = new Map();
1538
+ /**
1539
+ * Last layout hash for change detection
1540
+ */
1541
+ this.lastLayoutHash = '';
1542
+ //#region ---- Inputs ----
1543
+ /**
1544
+ * Form definition containing groups and fields OR widget tree
1545
+ */
1546
+ this.layout = input.required(...(ngDevMode ? [{ debugName: "layout" }] : []));
1547
+ /**
1548
+ * Form context/model data
1549
+ */
1550
+ this.context = model({}, ...(ngDevMode ? [{ debugName: "context" }] : []));
1551
+ /**
1552
+ * Form appearance and density styling (normal, compact, spacious)
1553
+ */
1554
+ this.look = input('fieldset', ...(ngDevMode ? [{ debugName: "look" }] : []));
1555
+ /**
1556
+ * Default form mode. Can be overridden by section/group and field.
1557
+ */
1558
+ this.mode = input('edit', ...(ngDevMode ? [{ debugName: "mode" }] : []));
1559
+ //#endregion
1560
+ //#region ---- Widget Tree Conversion ----
1561
+ this.widgetTree = signal(null, ...(ngDevMode ? [{ debugName: "widgetTree" }] : []));
1562
+ /**
1563
+ * Convert and evaluate data when inputs change (optimized with RxJS)
1564
+ */
1565
+ this.conversionEffect = effect(() => {
1566
+ const inputData = this.layout();
1567
+ const ctx = this.internalContext();
1568
+ const look = this.look();
1569
+ const runId = ++this.evaluationRunId;
1570
+ // Generate layout hash for change detection
1571
+ const layoutHash = this.generateLayoutHash(inputData, look);
1572
+ // Skip if layout hasn't changed
1573
+ if (layoutHash === this.lastLayoutHash && this.widgetTree()) {
1574
+ return;
1575
+ }
1576
+ this.lastLayoutHash = layoutHash;
1577
+ (async () => {
1578
+ // First evaluate expressions if needed
1579
+ const evaluated = await this.expressionEvaluator(inputData, ctx);
1580
+ // Ignore stale results
1581
+ if (runId !== this.evaluationRunId) {
1582
+ return;
1583
+ }
1584
+ // Convert to widget tree (this will also apply the layout look)
1585
+ const tree = evaluated;
1586
+ // Update widget tree
1587
+ const prev = this.widgetTree();
1588
+ if (!isEqual(prev, tree)) {
1589
+ tree.mode = this.mode();
1590
+ this.widgetTree.set(tree);
1591
+ }
1592
+ })();
1593
+ }, ...(ngDevMode ? [{ debugName: "conversionEffect" }] : []));
1594
+ //#endregion
1595
+ //#region ---- Outputs ----
1596
+ /**
1597
+ * Emitted when context change is initiated
1598
+ */
1599
+ this.contextInitiated = output();
1600
+ /**
1601
+ * Emitted when form becomes valid/invalid
1602
+ */
1603
+ this.validityChange = output();
1604
+ //#endregion
1605
+ //#region ---- Properties ----
1606
+ this.form = viewChild(AXFormComponent, ...(ngDevMode ? [{ debugName: "form" }] : []));
1607
+ this.container = viewChild(AXPWidgetContainerComponent, ...(ngDevMode ? [{ debugName: "container" }] : []));
1608
+ /**
1609
+ * Internal context signal for reactivity
1610
+ */
1611
+ this.internalContext = signal({}, ...(ngDevMode ? [{ debugName: "internalContext" }] : []));
1612
+ /**
1613
+ * Initial context for reset functionality
1614
+ */
1615
+ this.initialContext = {};
1616
+ //#endregion
1617
+ //#region ---- Effects ----
1618
+ /**
1619
+ * Effect to sync context changes from external to internal (optimized with RxJS)
1620
+ */
1621
+ this.#contextSyncEffect = effect(() => {
1622
+ const ctx = this.context() ?? {};
1623
+ this.contextUpdateSubject.next(ctx);
1624
+ }, ...(ngDevMode ? [{ debugName: "#contextSyncEffect" }] : []));
1625
+ /**
1626
+ * Effect to handle widget tree status changes
1627
+ */
1628
+ this.#widgetStatusEffect = effect(() => {
1629
+ const widgetTree = this.widgetTree();
1630
+ if (widgetTree) {
1631
+ this.container()?.builderService.setStatus(AXPPageStatus.Rendered);
1632
+ }
1633
+ }, ...(ngDevMode ? [{ debugName: "#widgetStatusEffect" }] : []));
1634
+ }
1635
+ async expressionEvaluator(expression, context) {
1636
+ // Check if it's a form definition that needs conversion
1637
+ if (this.isFormDefinition(expression)) {
1638
+ return this.conversionService.convertFormDefinition(expression);
1639
+ }
1640
+ // Generate cache key using a more efficient method
1641
+ const cacheKey = this.generateCacheKey(expression, context);
1642
+ // Check cache first
1643
+ if (this.expressionCache.has(cacheKey)) {
1644
+ return this.expressionCache.get(cacheKey);
1645
+ }
1646
+ const scope = {
1647
+ context: {
1648
+ eval: (path) => get(context, path),
1649
+ },
2238
1650
  };
1651
+ const result = await this.evaluatorService.evaluate(expression, scope);
1652
+ // Cache result with LRU-like behavior
1653
+ if (this.expressionCache.size > 50) {
1654
+ // Clear half the cache when it gets too large
1655
+ const keysToDelete = Array.from(this.expressionCache.keys()).slice(0, 25);
1656
+ keysToDelete.forEach((key) => this.expressionCache.delete(key));
1657
+ }
1658
+ this.expressionCache.set(cacheKey, result);
1659
+ return result;
2239
1660
  }
2240
- static forChild(config) {
2241
- return {
2242
- ngModule: AXPLayoutBuilderModule,
2243
- providers: [
2244
- {
2245
- provide: 'AXPLayoutBuilderModuleFactory',
2246
- useFactory: (registry) => async () => {
2247
- await Promise.all(config?.widgets?.map((w) => Promise.resolve(registry.register(w))) || []);
2248
- await Promise.all(config?.extendedWidgets?.map((ew) => Promise.resolve(registry.extend(ew.parentName, ew.widget))) || []);
2249
- },
2250
- deps: [AXPWidgetRegistryService],
2251
- multi: true,
2252
- },
2253
- ],
2254
- };
1661
+ //#endregion
1662
+ //#region ---- Lifecycle Methods ----
1663
+ ngOnInit() {
1664
+ // Initialize internal context with input context
1665
+ const ctx = this.context() ?? {};
1666
+ this.internalContext.set(ctx);
1667
+ // Store initial context for reset functionality
1668
+ this.initialContext = cloneDeep(ctx);
1669
+ // Setup RxJS streams for context management
1670
+ this.setupContextStreams();
2255
1671
  }
1672
+ //#endregion
1673
+ //#region ---- Effects ----
2256
1674
  /**
2257
- * @ignore
1675
+ * Effect to sync context changes from external to internal (optimized with RxJS)
2258
1676
  */
2259
- constructor(instances) {
2260
- instances?.forEach((f) => {
2261
- f();
2262
- });
2263
- }
2264
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPLayoutBuilderModule, deps: [{ token: 'AXPLayoutBuilderModuleFactory', optional: true }], target: i0.ɵɵFactoryTarget.NgModule }); }
2265
- static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.1.8", ngImport: i0, type: AXPLayoutBuilderModule, declarations: [AXPWidgetContainerComponent, AXPWidgetColumnRendererComponent, AXPWidgetRendererDirective], imports: [CommonModule, PortalModule, AXSkeletonModule, CommonModule, AXTranslationModule], exports: [AXPWidgetContainerComponent, AXPWidgetColumnRendererComponent, AXPWidgetRendererDirective] }); }
2266
- static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPLayoutBuilderModule, imports: [CommonModule, PortalModule, AXSkeletonModule, CommonModule, AXTranslationModule] }); }
2267
- }
2268
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPLayoutBuilderModule, decorators: [{
2269
- type: NgModule,
2270
- args: [{
2271
- imports: [CommonModule, PortalModule, AXSkeletonModule, CommonModule, AXTranslationModule],
2272
- exports: [...COMPONENTS],
2273
- declarations: [...COMPONENTS],
2274
- }]
2275
- }], ctorParameters: () => [{ type: undefined, decorators: [{
2276
- type: Optional
2277
- }, {
2278
- type: Inject,
2279
- args: ['AXPLayoutBuilderModuleFactory']
2280
- }] }] });
2281
-
2282
- class AXPPropertyEditorHelper {
2283
- static expandShorthand(values) {
2284
- switch (values.length) {
2285
- case 1:
2286
- return [values[0], values[0], values[0], values[0]];
2287
- case 2:
2288
- return [values[0], values[1], values[0], values[1]];
2289
- case 3:
2290
- return [values[0], values[1], values[2], values[1]];
2291
- case 4:
2292
- return values;
2293
- default:
2294
- throw new Error(`Invalid shorthand value count. Input: ${values}`);
2295
- }
2296
- }
2297
- static condenseShorthand(values) {
2298
- if (values.length !== 4) {
2299
- throw new Error('Expected 4 values for condensation.');
2300
- }
2301
- if (values[0] === values[1] && values[1] === values[2] && values[2] === values[3]) {
2302
- return `${values[0]}`;
2303
- }
2304
- else if (values[0] === values[2] && values[1] === values[3]) {
2305
- return `${values[0]} ${values[1]}`;
2306
- }
2307
- else if (values[1] === values[3]) {
2308
- return `${values[0]} ${values[1]} ${values[2]}`;
1677
+ #contextSyncEffect;
1678
+ /**
1679
+ * Effect to handle widget tree status changes
1680
+ */
1681
+ #widgetStatusEffect;
1682
+ //#endregion
1683
+ //#region ---- Event Handlers ----
1684
+ /**
1685
+ * Handle context change events from widget container (optimized with RxJS)
1686
+ */
1687
+ handleContextChanged(event) {
1688
+ if (event.state === 'initiated') {
1689
+ this.contextInitiated.emit(event.data);
2309
1690
  }
2310
1691
  else {
2311
- return `${values[0]} ${values[1]} ${values[2]} ${values[3]}`;
1692
+ this.contextChangeSubject.next(event.data ?? {});
2312
1693
  }
2313
1694
  }
2314
- static parseSides(input) {
2315
- const values = this.expandShorthand(input.match(/(?:rgb\([^)]+\)|[^ ]+)/g)?.map((value) => value.trim()) || []);
2316
- return { top: values[0], right: values[1], bottom: values[2], left: values[3] };
1695
+ //#endregion
1696
+ //#region ---- Public Methods ----
1697
+ /**
1698
+ * Get the form component instance
1699
+ */
1700
+ getForm() {
1701
+ return this.form();
2317
1702
  }
2318
- static parseSidesWithUnits(input) {
2319
- const values = this.expandShorthand(input.match(/(?:rgb\([^)]+\)|[^ ]+)/g)?.map((value) => value.trim()) || []);
2320
- return {
2321
- top: AXPPropertyEditorHelper.getValueWithUnit(values[0]).value,
2322
- right: AXPPropertyEditorHelper.getValueWithUnit(values[1]).value,
2323
- bottom: AXPPropertyEditorHelper.getValueWithUnit(values[2]).value,
2324
- left: AXPPropertyEditorHelper.getValueWithUnit(values[3]).value,
2325
- };
1703
+ /**
1704
+ * Get the widget container component instance
1705
+ */
1706
+ getContainer() {
1707
+ return this.container();
2326
1708
  }
2327
- static parseCorners(input) {
2328
- const values = this.expandShorthand(input.split(' ').map((value) => value.trim()));
2329
- return {
2330
- 'top-left': AXPPropertyEditorHelper.getValueWithUnit(values[0]).value,
2331
- 'top-right': AXPPropertyEditorHelper.getValueWithUnit(values[1]).value,
2332
- 'bottom-left': AXPPropertyEditorHelper.getValueWithUnit(values[2]).value,
2333
- 'bottom-right': AXPPropertyEditorHelper.getValueWithUnit(values[3]).value,
2334
- };
1709
+ /**
1710
+ * Get current form context
1711
+ */
1712
+ getContext() {
1713
+ return this.internalContext();
2335
1714
  }
2336
- static parseSpacingBox(input) {
2337
- return {
2338
- margin: this.parseSidesWithUnits(input.margin),
2339
- padding: this.parseSidesWithUnits(input.padding),
2340
- };
1715
+ /**
1716
+ * Update form context programmatically
1717
+ */
1718
+ updateContext(context) {
1719
+ this.internalContext.set(context);
2341
1720
  }
2342
- static parseBorderBox(input) {
2343
- return {
2344
- width: this.parseSidesWithUnits(input.width),
2345
- radius: this.parseCorners(input.radius),
2346
- color: this.parseSides(input.color),
2347
- style: this.parseSides(input.style),
2348
- };
1721
+ /**
1722
+ * Get the current widget tree
1723
+ */
1724
+ getWidgetTree() {
1725
+ return this.widgetTree();
2349
1726
  }
2350
- static parseSpacingBoxReverse(input, units) {
2351
- const format = (value, unit) => `${value}${unit}`;
1727
+ /**
1728
+ * Validate the form
1729
+ */
1730
+ async validate() {
1731
+ const form = this.form();
1732
+ if (form) {
1733
+ const isValid = await form.validate();
1734
+ this.validityChange.emit(isValid.result);
1735
+ return isValid;
1736
+ }
2352
1737
  return {
2353
- margin: AXPPropertyEditorHelper.condenseShorthand([
2354
- format(input.margin.top, units.margin.top),
2355
- format(input.margin.right, units.margin.right),
2356
- format(input.margin.bottom, units.margin.bottom),
2357
- format(input.margin.left, units.margin.left),
2358
- ]),
2359
- padding: AXPPropertyEditorHelper.condenseShorthand([
2360
- format(input.padding.top, units.padding.top),
2361
- format(input.padding.right, units.padding.right),
2362
- format(input.padding.bottom, units.padding.bottom),
2363
- format(input.padding.left, units.padding.left),
2364
- ]),
1738
+ result: false,
1739
+ messages: [],
1740
+ rules: [],
2365
1741
  };
2366
1742
  }
2367
- static parseBorderBoxReverse(input, units) {
2368
- const format = (value, unit) => `${value}${unit}`;
2369
- return {
2370
- width: AXPPropertyEditorHelper.condenseShorthand([
2371
- format(input.width.top, units.width.top),
2372
- format(input.width.right, units.width.right),
2373
- format(input.width.bottom, units.width.bottom),
2374
- format(input.width.left, units.width.left),
2375
- ]),
2376
- radius: AXPPropertyEditorHelper.condenseShorthand([
2377
- format(input.radius['top-left'], units.radius['top-left']),
2378
- format(input.radius['top-right'], units.radius['top-right']),
2379
- format(input.radius['bottom-right'], units.radius['bottom-right']),
2380
- format(input.radius['bottom-left'], units.radius['bottom-left']),
2381
- ]),
2382
- color: AXPPropertyEditorHelper.condenseShorthand([
2383
- `${input.color.top}${units.color.top}`,
2384
- `${input.color.right}${units.color.right}`,
2385
- `${input.color.bottom}${units.color.bottom}`,
2386
- `${input.color.left}${units.color.left}`,
2387
- ]),
2388
- style: AXPPropertyEditorHelper.condenseShorthand([
2389
- `${input.style.top}${units.style.top}`,
2390
- `${input.style.right}${units.style.right}`,
2391
- `${input.style.bottom}${units.style.bottom}`,
2392
- `${input.style.left}${units.style.left}`,
2393
- ]),
2394
- };
1743
+ /**
1744
+ * Clear the form context
1745
+ */
1746
+ clear() {
1747
+ // Clear internal context
1748
+ this.internalContext.set({});
1749
+ // Update the model signal
1750
+ this.context.set({});
2395
1751
  }
2396
- static getValueWithUnit(input) {
2397
- if (typeof input === 'number')
2398
- return { value: input, unit: 'px' };
2399
- if (input === 'auto')
2400
- return { value: 0, unit: 'px' };
2401
- const match = input.match(/^([0-9.]+)([a-z%]*)$/i);
2402
- if (!match)
2403
- throw new Error(`Invalid unit format: ${input}`);
2404
- return { value: parseFloat(match[1]), unit: match[2] || '' };
2405
- }
2406
- static getValueFromUnit(value, unit) {
2407
- return unit ? `${value}${unit}` : `${value}`;
2408
- }
2409
- static parseGap(gap) {
2410
- const parts = gap.split(/\s+/);
2411
- const match = parts[0].match(/^(\d+\.?\d*)([a-z%]+)$/);
2412
- if (!match) {
2413
- throw new Error('Invalid gap format');
2414
- }
2415
- const [, xValue, unit] = match;
2416
- let yValue = parseFloat(xValue);
2417
- if (parts.length === 2) {
2418
- const secondMatch = parts[1].match(/^(\d+\.?\d*)[a-z%]+$/);
2419
- if (!secondMatch) {
2420
- throw new Error('Invalid gap format');
2421
- }
2422
- yValue = parseFloat(secondMatch[1]);
2423
- }
2424
- return {
2425
- values: {
2426
- x: parseFloat(xValue),
2427
- y: yValue,
2428
- },
2429
- unit,
2430
- };
1752
+ /**
1753
+ * Reset the form to its initial state
1754
+ */
1755
+ reset() {
1756
+ // Reset to initial context
1757
+ const resetContext = cloneDeep(this.initialContext);
1758
+ this.internalContext.set(resetContext);
1759
+ // Update the model signal
1760
+ this.context.set(resetContext);
2431
1761
  }
2432
- static parseGridTemplate(gridTemplate) {
2433
- const match = gridTemplate.match(/^repeat\((\d+),\s*(?:1fr|auto|minmax\([^)]*\))\)$/);
2434
- if (!match) {
2435
- throw new Error("Invalid grid template format. Expected 'repeat(N, 1fr|auto|minmax(...))'.");
2436
- }
2437
- return parseInt(match[1], 10);
1762
+ //#endregion
1763
+ //#region ---- RxJS Stream Setup ----
1764
+ /**
1765
+ * Setup RxJS streams for context management
1766
+ */
1767
+ setupContextStreams() {
1768
+ // Debounced context updates from external source
1769
+ this.contextUpdateSubject
1770
+ .pipe(debounceTime(16), // ~60fps
1771
+ distinctUntilChanged((prev, curr) => isEqual(prev, curr)), startWith(this.context() ?? {}))
1772
+ .subscribe((ctx) => {
1773
+ this.internalContext.set(ctx);
1774
+ });
1775
+ // Debounced context changes from widgets
1776
+ this.contextChangeSubject
1777
+ .pipe(debounceTime(16), // ~60fps
1778
+ distinctUntilChanged((prev, curr) => isEqual(prev, curr)))
1779
+ .subscribe((ctx) => {
1780
+ this.internalContext.set(ctx);
1781
+ // Update the model signal directly - it will emit change events automatically
1782
+ this.context.set(this.internalContext());
1783
+ });
1784
+ }
1785
+ //#endregion
1786
+ //#region ---- Type Guards ----
1787
+ /**
1788
+ * Type guard to check if the input is a form definition
1789
+ */
1790
+ isFormDefinition(data) {
1791
+ return data && typeof data === 'object' && 'groups' in data && Array.isArray(data.groups);
1792
+ }
1793
+ //#endregion
1794
+ //#region ---- Utility Methods ----
1795
+ /**
1796
+ * Generate layout hash for change detection (short hash)
1797
+ */
1798
+ generateLayoutHash(layout, look) {
1799
+ if (!layout)
1800
+ return '';
1801
+ // Generate short hash for large layout strings
1802
+ const layoutStr = typeof layout === 'string' ? layout : JSON.stringify(layout);
1803
+ const layoutHash = this.simpleHash(layoutStr);
1804
+ return `${layoutHash}|${look}`;
1805
+ }
1806
+ /**
1807
+ * Generate cache key for expression evaluation (short hash)
1808
+ */
1809
+ generateCacheKey(expression, context) {
1810
+ // Use short hash for better performance
1811
+ const exprStr = typeof expression === 'string' ? expression : JSON.stringify(expression);
1812
+ const exprHash = this.simpleHash(exprStr);
1813
+ const ctxHash = this.generateContextHash(context);
1814
+ return `${exprHash}|${ctxHash}`;
2438
1815
  }
2439
- static createGridTemplate(repetitionCount) {
2440
- if (repetitionCount <= 0) {
2441
- throw new Error('Repetition count must be a positive integer.');
1816
+ /**
1817
+ * Generate a simple hash for context change detection
1818
+ */
1819
+ generateContextHash(context) {
1820
+ if (!context || typeof context !== 'object') {
1821
+ return String(context);
2442
1822
  }
2443
- return `repeat(${repetitionCount}, 1fr)`;
1823
+ // Generate short hash for context
1824
+ const keys = Object.keys(context).sort();
1825
+ const contextStr = keys.map((key) => `${key}:${context[key]}`).join('|');
1826
+ return this.simpleHash(contextStr);
2444
1827
  }
1828
+ /**
1829
+ * Simple hash function for generating short keys
1830
+ */
1831
+ simpleHash(str) {
1832
+ let hash = 0;
1833
+ if (str.length === 0)
1834
+ return hash.toString();
1835
+ for (let i = 0; i < str.length; i++) {
1836
+ const char = str.charCodeAt(i);
1837
+ hash = (hash << 5) - hash + char;
1838
+ hash = hash & hash; // Convert to 32-bit integer
1839
+ }
1840
+ // Convert to positive hex string
1841
+ return Math.abs(hash).toString(16);
1842
+ }
1843
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: AXPLayoutRendererComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1844
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.4", 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: `
1845
+ <ax-form>
1846
+ <axp-widgets-container [context]="internalContext()" (onContextChanged)="handleContextChanged($event)">
1847
+ @if (widgetTree()) {
1848
+ <ng-container axp-widget-renderer [node]="widgetTree()!" [mode]="mode()"></ng-container>
1849
+ }
1850
+ </axp-widgets-container>
1851
+ </ax-form>
1852
+ `, 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 }); }
2445
1853
  }
2446
- function findNonEmptyBreakpoints(values) {
2447
- const breakpoints = ['default', 'sm', 'md', 'lg', 'xl', '2xl', '3xl'];
2448
- const nonEmptyBreakpoints = [];
2449
- for (const breakpoint of breakpoints) {
2450
- if (values[breakpoint] !== undefined) {
2451
- nonEmptyBreakpoints.push(breakpoint);
2452
- }
1854
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: AXPLayoutRendererComponent, decorators: [{
1855
+ type: Component,
1856
+ args: [{ selector: 'axp-layout-renderer', standalone: true, imports: [CommonModule, AXPWidgetCoreModule, AXFormModule], changeDetection: ChangeDetectionStrategy.OnPush, template: `
1857
+ <ax-form>
1858
+ <axp-widgets-container [context]="internalContext()" (onContextChanged)="handleContextChanged($event)">
1859
+ @if (widgetTree()) {
1860
+ <ng-container axp-widget-renderer [node]="widgetTree()!" [mode]="mode()"></ng-container>
1861
+ }
1862
+ </axp-widgets-container>
1863
+ </ax-form>
1864
+ `, styles: [":host{display:block;width:100%}\n"] }]
1865
+ }] });
1866
+
1867
+ class LayoutBuilderModule {
1868
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: LayoutBuilderModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
1869
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.4", ngImport: i0, type: LayoutBuilderModule, imports: [CommonModule, AXPLayoutRendererComponent], exports: [AXPLayoutRendererComponent] }); }
1870
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: LayoutBuilderModule, providers: [AXPLayoutBuilderService], imports: [CommonModule, AXPLayoutRendererComponent] }); }
1871
+ }
1872
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: LayoutBuilderModule, decorators: [{
1873
+ type: NgModule,
1874
+ args: [{
1875
+ imports: [CommonModule, AXPLayoutRendererComponent],
1876
+ providers: [AXPLayoutBuilderService],
1877
+ exports: [AXPLayoutRendererComponent],
1878
+ }]
1879
+ }] });
1880
+
1881
+ //#endregion
1882
+
1883
+ class AXPDialogRendererComponent extends AXBasePageComponent {
1884
+ constructor() {
1885
+ super(...arguments);
1886
+ this.result = new EventEmitter();
1887
+ this.context = signal({}, ...(ngDevMode ? [{ debugName: "context" }] : []));
1888
+ // This will be set by the popup service automatically - same as dynamic-dialog
1889
+ this.callBack = () => { };
1890
+ this.isDialogLoading = signal(false, ...(ngDevMode ? [{ debugName: "isDialogLoading" }] : []));
1891
+ }
1892
+ ngOnInit() {
1893
+ // Initialize context with provided context
1894
+ this.context.set(this.config?.context || {});
2453
1895
  }
2454
- return nonEmptyBreakpoints;
1896
+ handleContextChanged(event) {
1897
+ this.context.set(event);
1898
+ }
1899
+ handleContextInitiated(event) {
1900
+ this.context.set(event);
1901
+ }
1902
+ visibleFooterPrefixActions() {
1903
+ return this.config?.actions?.footer?.prefix || [];
1904
+ }
1905
+ visibleFooterSuffixActions() {
1906
+ return (this.config?.actions?.footer?.suffix || [
1907
+ { title: 'Cancel', color: 'secondary', command: { name: 'cancel' } },
1908
+ { title: 'Confirm', color: 'primary', command: { name: 'confirm' } },
1909
+ ]);
1910
+ }
1911
+ isFormLoading() {
1912
+ return this.isDialogLoading();
1913
+ }
1914
+ isSubmitting() {
1915
+ return this.isDialogLoading();
1916
+ }
1917
+ async executeAction(action) {
1918
+ const actionName = action.command?.name || action.title?.toLowerCase();
1919
+ // Store the action and context - same pattern as dynamic-dialog
1920
+ const result = {
1921
+ context: this.context(),
1922
+ action: actionName,
1923
+ };
1924
+ // Store result in component property
1925
+ this.dialogResult = result;
1926
+ // Store in popup data for DialogRef access
1927
+ if (this.data) {
1928
+ this.data.context = result.context;
1929
+ this.data.action = result.action;
1930
+ }
1931
+ // Call the callback with DialogRef - same pattern as dynamic-dialog
1932
+ this.callBack({
1933
+ close: (result) => {
1934
+ this.close(result);
1935
+ },
1936
+ context: () => {
1937
+ return this.context();
1938
+ },
1939
+ action: () => {
1940
+ return result.action;
1941
+ },
1942
+ setLoading: (loading) => {
1943
+ this.isDialogLoading.set(loading);
1944
+ },
1945
+ });
1946
+ }
1947
+ close(result) {
1948
+ if (result) {
1949
+ this.result.emit(result);
1950
+ }
1951
+ super.close(result);
1952
+ }
1953
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: AXPDialogRendererComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
1954
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.4", type: AXPDialogRendererComponent, isStandalone: true, selector: "axp-dialog-renderer", inputs: { config: "config" }, outputs: { result: "result" }, usesInheritance: true, ngImport: i0, template: `
1955
+ <div class="ax-p-4">
1956
+ <axp-layout-renderer
1957
+ [layout]="config.definition"
1958
+ [context]="context()"
1959
+ (contextChange)="handleContextChanged($event)"
1960
+ (contextInitiated)="handleContextInitiated($event)"
1961
+ >
1962
+ </axp-layout-renderer>
1963
+ </div>
1964
+
1965
+ <ax-footer>
1966
+ <ax-prefix>
1967
+ <ng-container *ngTemplateOutlet="footerPrefixActions"></ng-container>
1968
+ </ax-prefix>
1969
+ <ax-suffix>
1970
+ <ng-container *ngTemplateOutlet="footerSuffixActions"></ng-container>
1971
+ </ax-suffix>
1972
+ </ax-footer>
1973
+
1974
+ <!-- Footer Prefix Actions -->
1975
+ <ng-template #footerPrefixActions>
1976
+ @for (action of visibleFooterPrefixActions(); track $index) {
1977
+ <ax-button
1978
+ [disabled]="action.disabled || isFormLoading()"
1979
+ [text]="(action.title | translate | async)!"
1980
+ [look]="'outline'"
1981
+ [color]="action.color"
1982
+ (onClick)="executeAction(action)"
1983
+ >
1984
+ @if (isFormLoading() && action.command?.name != 'cancel') {
1985
+ <ax-loading></ax-loading>
1986
+ }
1987
+ <ax-prefix>
1988
+ <i class="{{ action.icon }}"></i>
1989
+ </ax-prefix>
1990
+ </ax-button>
1991
+ }
1992
+ </ng-template>
1993
+
1994
+ <!-- Footer Suffix Actions -->
1995
+ <ng-template #footerSuffixActions>
1996
+ @for (action of visibleFooterSuffixActions(); track $index) {
1997
+ <ax-button
1998
+ [disabled]="action.disabled || isSubmitting()"
1999
+ [text]="(action.title | translate | async)!"
2000
+ [look]="'solid'"
2001
+ [color]="action.color"
2002
+ (onClick)="executeAction(action)"
2003
+ >
2004
+ @if (action.icon) {
2005
+ <ax-prefix>
2006
+ <ax-icon icon="{{ action.icon }}"></ax-icon>
2007
+ </ax-prefix>
2008
+ }
2009
+ </ax-button>
2010
+ }
2011
+ </ng-template>
2012
+ `, 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" }] }); }
2455
2013
  }
2014
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: AXPDialogRendererComponent, decorators: [{
2015
+ type: Component,
2016
+ args: [{
2017
+ selector: 'axp-dialog-renderer',
2018
+ standalone: true,
2019
+ imports: [
2020
+ CommonModule,
2021
+ AXPLayoutRendererComponent,
2022
+ AXButtonModule,
2023
+ AXDecoratorModule,
2024
+ AXLoadingModule,
2025
+ AXTranslationModule,
2026
+ ],
2027
+ template: `
2028
+ <div class="ax-p-4">
2029
+ <axp-layout-renderer
2030
+ [layout]="config.definition"
2031
+ [context]="context()"
2032
+ (contextChange)="handleContextChanged($event)"
2033
+ (contextInitiated)="handleContextInitiated($event)"
2034
+ >
2035
+ </axp-layout-renderer>
2036
+ </div>
2037
+
2038
+ <ax-footer>
2039
+ <ax-prefix>
2040
+ <ng-container *ngTemplateOutlet="footerPrefixActions"></ng-container>
2041
+ </ax-prefix>
2042
+ <ax-suffix>
2043
+ <ng-container *ngTemplateOutlet="footerSuffixActions"></ng-container>
2044
+ </ax-suffix>
2045
+ </ax-footer>
2456
2046
 
2457
- const AXP_WIDGETS_LAYOUT_CATEGORY = {
2458
- name: 'layout',
2459
- order: 1,
2460
- title: 'Layout',
2461
- };
2462
- const AXP_WIDGETS_EDITOR_CATEGORY = {
2463
- name: 'editor',
2464
- order: 2,
2465
- title: 'Editors',
2466
- };
2467
- const AXP_WIDGETS_ACTION_CATEGORY = {
2468
- name: 'action',
2469
- order: 3,
2470
- title: 'Action',
2471
- };
2472
- const AXP_WIDGETS_ADVANCE_CATEGORY = {
2473
- name: 'advance',
2474
- order: 4,
2475
- title: 'Advance',
2476
- };
2477
- const AXP_WIDGETS_CATEGORIES = [
2478
- AXP_WIDGETS_LAYOUT_CATEGORY,
2479
- AXP_WIDGETS_EDITOR_CATEGORY,
2480
- AXP_WIDGETS_ACTION_CATEGORY,
2481
- AXP_WIDGETS_ADVANCE_CATEGORY,
2482
- ];
2047
+ <!-- Footer Prefix Actions -->
2048
+ <ng-template #footerPrefixActions>
2049
+ @for (action of visibleFooterPrefixActions(); track $index) {
2050
+ <ax-button
2051
+ [disabled]="action.disabled || isFormLoading()"
2052
+ [text]="(action.title | translate | async)!"
2053
+ [look]="'outline'"
2054
+ [color]="action.color"
2055
+ (onClick)="executeAction(action)"
2056
+ >
2057
+ @if (isFormLoading() && action.command?.name != 'cancel') {
2058
+ <ax-loading></ax-loading>
2059
+ }
2060
+ <ax-prefix>
2061
+ <i class="{{ action.icon }}"></i>
2062
+ </ax-prefix>
2063
+ </ax-button>
2064
+ }
2065
+ </ng-template>
2066
+
2067
+ <!-- Footer Suffix Actions -->
2068
+ <ng-template #footerSuffixActions>
2069
+ @for (action of visibleFooterSuffixActions(); track $index) {
2070
+ <ax-button
2071
+ [disabled]="action.disabled || isSubmitting()"
2072
+ [text]="(action.title | translate | async)!"
2073
+ [look]="'solid'"
2074
+ [color]="action.color"
2075
+ (onClick)="executeAction(action)"
2076
+ >
2077
+ @if (action.icon) {
2078
+ <ax-prefix>
2079
+ <ax-icon icon="{{ action.icon }}"></ax-icon>
2080
+ </ax-prefix>
2081
+ }
2082
+ </ax-button>
2083
+ }
2084
+ </ng-template>
2085
+ `,
2086
+ }]
2087
+ }], propDecorators: { config: [{
2088
+ type: Input
2089
+ }], result: [{
2090
+ type: Output
2091
+ }] } });
2483
2092
 
2484
- var AXPWidgetGroupEnum;
2485
- (function (AXPWidgetGroupEnum) {
2486
- AXPWidgetGroupEnum["FormElement"] = "form-element";
2487
- AXPWidgetGroupEnum["DashboardWidget"] = "dashboard-widget";
2488
- AXPWidgetGroupEnum["FormTemplate"] = "form-template";
2489
- AXPWidgetGroupEnum["PropertyEditor"] = "property-editor";
2490
- AXPWidgetGroupEnum["MetaData"] = "meta-data";
2491
- AXPWidgetGroupEnum["SettingWidget"] = "setting-widget";
2492
- AXPWidgetGroupEnum["EntityWidget"] = "entity-widget";
2493
- })(AXPWidgetGroupEnum || (AXPWidgetGroupEnum = {}));
2093
+ var dialogRenderer_component = /*#__PURE__*/Object.freeze({
2094
+ __proto__: null,
2095
+ AXPDialogRendererComponent: AXPDialogRendererComponent
2096
+ });
2494
2097
 
2495
2098
  /**
2496
2099
  * Generated bundle index. Do not edit.
2497
2100
  */
2498
2101
 
2499
- export { AXPBaseWidgetComponent, AXPBlockBaseLayoutWidgetComponent, AXPBoxModelLayoutWidgetComponent, AXPColumnWidgetComponent, AXPDataListWidgetComponent, AXPFlexBaseLayoutWidgetComponent, AXPFlexItemBaseLayoutWidgetComponent, AXPGridBaseLayoutWidgetComponent, AXPGridItemBaseLayoutWidgetComponent, AXPInlineBaseLayoutWidgetComponent, AXPLayoutBaseWidgetComponent, AXPLayoutBuilderContextStore, AXPLayoutBuilderModule, AXPLayoutBuilderService, AXPLayoutContextChangeEvent, AXPLayoutElement, AXPPageStatus, AXPPropertyEditorHelper, AXPTableBaseLayoutWidgetComponent, AXPTableItemBaseLayoutWidgetComponent, AXPTableItemOpsBaseLayoutWidgetComponent, AXPValueWidgetComponent, AXPWidgetColumnRendererComponent, AXPWidgetContainerComponent, AXPWidgetGroupEnum, AXPWidgetRegistryService, AXPWidgetRendererDirective, AXPWidgetStatus, AXPWidgetsCatalog, AXP_WIDGETS_ACTION_CATEGORY, AXP_WIDGETS_ADVANCE_CATEGORY, AXP_WIDGETS_CATEGORIES, AXP_WIDGETS_EDITOR_CATEGORY, AXP_WIDGETS_LAYOUT_CATEGORY, AXP_WIDGET_COLUMN_TOKEN, AXP_WIDGET_TOKEN, cloneProperty, createBooleanProperty, createNumberProperty, createSelectProperty, createStringProperty, findNonEmptyBreakpoints };
2102
+ export { AXPDialogRendererComponent, AXPLayoutBuilderService, AXPLayoutConversionService, AXPLayoutRendererComponent, LayoutBuilderModule };
2500
2103
  //# sourceMappingURL=acorex-platform-layout-builder.mjs.map