@acorex/platform 20.3.0-next.2 → 20.3.0-next.20

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 +403 -374
  2. package/core/index.d.ts +563 -46
  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.mjs +124 -224
  6. package/fesm2022/acorex-platform-common.mjs.map +1 -1
  7. package/fesm2022/acorex-platform-core.mjs +644 -110
  8. package/fesm2022/acorex-platform-core.mjs.map +1 -1
  9. package/fesm2022/acorex-platform-domain.mjs +16 -16
  10. package/fesm2022/acorex-platform-domain.mjs.map +1 -1
  11. package/fesm2022/acorex-platform-layout-builder.mjs +1927 -1951
  12. package/fesm2022/acorex-platform-layout-builder.mjs.map +1 -1
  13. package/fesm2022/acorex-platform-layout-components.mjs +3897 -435
  14. package/fesm2022/acorex-platform-layout-components.mjs.map +1 -1
  15. package/fesm2022/acorex-platform-layout-designer.mjs +96 -89
  16. package/fesm2022/acorex-platform-layout-designer.mjs.map +1 -1
  17. package/fesm2022/acorex-platform-layout-entity-create-entity.command-BXExgI3W.mjs +52 -0
  18. package/fesm2022/acorex-platform-layout-entity-create-entity.command-BXExgI3W.mjs.map +1 -0
  19. package/fesm2022/acorex-platform-layout-entity.mjs +1763 -1233
  20. package/fesm2022/acorex-platform-layout-entity.mjs.map +1 -1
  21. package/fesm2022/acorex-platform-layout-views.mjs +43 -33
  22. package/fesm2022/acorex-platform-layout-views.mjs.map +1 -1
  23. package/fesm2022/acorex-platform-layout-widget-core.mjs +2756 -0
  24. package/fesm2022/acorex-platform-layout-widget-core.mjs.map +1 -0
  25. package/fesm2022/{acorex-platform-widgets-button-widget-designer.component-C2Qn1YAW.mjs → acorex-platform-layout-widgets-button-widget-designer.component-BzsfTNs2.mjs} +6 -6
  26. package/fesm2022/acorex-platform-layout-widgets-button-widget-designer.component-BzsfTNs2.mjs.map +1 -0
  27. package/fesm2022/{acorex-platform-widgets-extra-properties-schema-widget-edit.component-D9mf08rU.mjs → acorex-platform-layout-widgets-extra-properties-schema-widget-edit.component-Dvk76-2W.mjs} +5 -5
  28. package/fesm2022/acorex-platform-layout-widgets-extra-properties-schema-widget-edit.component-Dvk76-2W.mjs.map +1 -0
  29. package/fesm2022/{acorex-platform-widgets-extra-properties-schema-widget-view.component-D6GQ-eyr.mjs → acorex-platform-layout-widgets-extra-properties-schema-widget-view.component-BYLaipWi.mjs} +5 -5
  30. package/fesm2022/acorex-platform-layout-widgets-extra-properties-schema-widget-view.component-BYLaipWi.mjs.map +1 -0
  31. package/fesm2022/{acorex-platform-widgets-extra-properties-values-widget-edit.component-DVbIdVZ6.mjs → acorex-platform-layout-widgets-extra-properties-values-widget-edit.component-DcSllNik.mjs} +5 -5
  32. package/fesm2022/acorex-platform-layout-widgets-extra-properties-values-widget-edit.component-DcSllNik.mjs.map +1 -0
  33. package/fesm2022/{acorex-platform-widgets-extra-properties-values-widget-view.component-D-aM64Hu.mjs → acorex-platform-layout-widgets-extra-properties-values-widget-view.component-BT-U4BiA.mjs} +5 -5
  34. package/fesm2022/acorex-platform-layout-widgets-extra-properties-values-widget-view.component-BT-U4BiA.mjs.map +1 -0
  35. package/fesm2022/{acorex-platform-widgets-extra-properties-widget-edit.component-em2-aU8E.mjs → acorex-platform-layout-widgets-extra-properties-widget-edit.component-Il7jnRBg.mjs} +5 -5
  36. package/fesm2022/acorex-platform-layout-widgets-extra-properties-widget-edit.component-Il7jnRBg.mjs.map +1 -0
  37. package/fesm2022/{acorex-platform-widgets-extra-properties-widget-view.component-BeuIofdr.mjs → acorex-platform-layout-widgets-extra-properties-widget-view.component-CBEPu7Fl.mjs} +5 -5
  38. package/fesm2022/acorex-platform-layout-widgets-extra-properties-widget-view.component-CBEPu7Fl.mjs.map +1 -0
  39. package/fesm2022/{acorex-platform-widgets-file-list-popup.component-rW2RD35f.mjs → acorex-platform-layout-widgets-file-list-popup.component-BPzn8lr3.mjs} +10 -10
  40. package/fesm2022/acorex-platform-layout-widgets-file-list-popup.component-BPzn8lr3.mjs.map +1 -0
  41. package/fesm2022/{acorex-platform-widgets-page-widget-designer.component-DNvnQ4Mc.mjs → acorex-platform-layout-widgets-page-widget-designer.component-C_JrGoXy.mjs} +8 -8
  42. package/fesm2022/acorex-platform-layout-widgets-page-widget-designer.component-C_JrGoXy.mjs.map +1 -0
  43. package/fesm2022/{acorex-platform-widgets-tabular-data-edit-popup.component-CPVRbE8B.mjs → acorex-platform-layout-widgets-tabular-data-edit-popup.component-C6DaBt_N.mjs} +14 -14
  44. package/fesm2022/acorex-platform-layout-widgets-tabular-data-edit-popup.component-C6DaBt_N.mjs.map +1 -0
  45. package/fesm2022/{acorex-platform-widgets-tabular-data-view-popup.component-Dmg5DdX8.mjs → acorex-platform-layout-widgets-tabular-data-view-popup.component-Bth3jI9T.mjs} +6 -5
  46. package/fesm2022/acorex-platform-layout-widgets-tabular-data-view-popup.component-Bth3jI9T.mjs.map +1 -0
  47. package/fesm2022/{acorex-platform-widgets-text-block-widget-designer.component-yADN3Xji.mjs → acorex-platform-layout-widgets-text-block-widget-designer.component-CUHptbP4.mjs} +6 -7
  48. package/fesm2022/acorex-platform-layout-widgets-text-block-widget-designer.component-CUHptbP4.mjs.map +1 -0
  49. package/fesm2022/{acorex-platform-widgets.mjs → acorex-platform-layout-widgets.mjs} +8362 -7479
  50. package/fesm2022/acorex-platform-layout-widgets.mjs.map +1 -0
  51. package/fesm2022/acorex-platform-native.mjs +7 -7
  52. package/fesm2022/acorex-platform-native.mjs.map +1 -1
  53. package/fesm2022/acorex-platform-runtime.mjs +40 -40
  54. package/fesm2022/acorex-platform-runtime.mjs.map +1 -1
  55. package/fesm2022/acorex-platform-themes-default-entity-master-create-view.component-eGzN6g2Y.mjs +115 -0
  56. package/fesm2022/acorex-platform-themes-default-entity-master-create-view.component-eGzN6g2Y.mjs.map +1 -0
  57. package/fesm2022/{acorex-platform-themes-default-entity-master-list-view.component-X0hLRZhX.mjs → acorex-platform-themes-default-entity-master-list-view.component-nDHfQQ3O.mjs} +34 -36
  58. package/fesm2022/acorex-platform-themes-default-entity-master-list-view.component-nDHfQQ3O.mjs.map +1 -0
  59. package/fesm2022/acorex-platform-themes-default-entity-master-modify-view.component-HJyalvcu.mjs +101 -0
  60. package/fesm2022/acorex-platform-themes-default-entity-master-modify-view.component-HJyalvcu.mjs.map +1 -0
  61. package/fesm2022/{acorex-platform-themes-default-entity-master-single-view.component-BExtm1JE.mjs → acorex-platform-themes-default-entity-master-single-view.component-e7m70Wls.mjs} +17 -17
  62. package/fesm2022/acorex-platform-themes-default-entity-master-single-view.component-e7m70Wls.mjs.map +1 -0
  63. package/fesm2022/{acorex-platform-themes-default-error-401.component-DrO1PEOH.mjs → acorex-platform-themes-default-error-401.component-CoBaQFTn.mjs} +4 -4
  64. package/fesm2022/{acorex-platform-themes-default-error-401.component-DrO1PEOH.mjs.map → acorex-platform-themes-default-error-401.component-CoBaQFTn.mjs.map} +1 -1
  65. package/fesm2022/{acorex-platform-themes-default-error-404.component-DqVq0oHX.mjs → acorex-platform-themes-default-error-404.component-BLlVOsS2.mjs} +4 -4
  66. package/fesm2022/{acorex-platform-themes-default-error-404.component-DqVq0oHX.mjs.map → acorex-platform-themes-default-error-404.component-BLlVOsS2.mjs.map} +1 -1
  67. package/fesm2022/{acorex-platform-themes-default-error-offline.component-Bt2PTL7_.mjs → acorex-platform-themes-default-error-offline.component-CybYQI9F.mjs} +4 -4
  68. package/fesm2022/{acorex-platform-themes-default-error-offline.component-Bt2PTL7_.mjs.map → acorex-platform-themes-default-error-offline.component-CybYQI9F.mjs.map} +1 -1
  69. package/fesm2022/acorex-platform-themes-default.mjs +45 -45
  70. package/fesm2022/acorex-platform-themes-default.mjs.map +1 -1
  71. package/fesm2022/{acorex-platform-themes-shared-icon-chooser-view.component-KpZWpnOJ.mjs → acorex-platform-themes-shared-icon-chooser-view.component-ReKSoVeN.mjs} +25 -15
  72. package/fesm2022/acorex-platform-themes-shared-icon-chooser-view.component-ReKSoVeN.mjs.map +1 -0
  73. package/fesm2022/{acorex-platform-themes-shared-settings.provider-CXiRmniv.mjs → acorex-platform-themes-shared-settings.provider-DY2xFnrv.mjs} +9 -9
  74. package/fesm2022/acorex-platform-themes-shared-settings.provider-DY2xFnrv.mjs.map +1 -0
  75. package/fesm2022/{acorex-platform-themes-shared-theme-color-chooser-column.component-BvOiVCgt.mjs → acorex-platform-themes-shared-theme-color-chooser-column.component-B2HDyY2z.mjs} +24 -9
  76. package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-column.component-B2HDyY2z.mjs.map +1 -0
  77. package/fesm2022/{acorex-platform-themes-shared-theme-color-chooser-view.component-BW0rfkjk.mjs → acorex-platform-themes-shared-theme-color-chooser-view.component-CeZxa49U.mjs} +24 -9
  78. package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-view.component-CeZxa49U.mjs.map +1 -0
  79. package/fesm2022/acorex-platform-themes-shared.mjs +264 -86
  80. package/fesm2022/acorex-platform-themes-shared.mjs.map +1 -1
  81. package/fesm2022/acorex-platform-workflow.mjs +27 -39
  82. package/fesm2022/acorex-platform-workflow.mjs.map +1 -1
  83. package/layout/builder/README.md +2 -3
  84. package/layout/builder/index.d.ts +683 -819
  85. package/layout/components/index.d.ts +1141 -115
  86. package/layout/designer/index.d.ts +8 -6
  87. package/layout/entity/index.d.ts +979 -282
  88. package/layout/views/index.d.ts +13 -13
  89. package/layout/widget-core/README.md +4 -0
  90. package/layout/widget-core/index.d.ts +957 -0
  91. package/layout/widgets/README.md +4 -0
  92. package/{widgets → layout/widgets}/index.d.ts +1933 -770
  93. package/package.json +14 -10
  94. package/themes/shared/index.d.ts +2 -2
  95. package/workflow/index.d.ts +3 -173
  96. package/fesm2022/acorex-platform-themes-default-entity-master-create-view.component-BXbkGGei.mjs +0 -115
  97. package/fesm2022/acorex-platform-themes-default-entity-master-create-view.component-BXbkGGei.mjs.map +0 -1
  98. package/fesm2022/acorex-platform-themes-default-entity-master-list-view.component-X0hLRZhX.mjs.map +0 -1
  99. package/fesm2022/acorex-platform-themes-default-entity-master-modify-view.component-Bp1JLsj1.mjs +0 -101
  100. package/fesm2022/acorex-platform-themes-default-entity-master-modify-view.component-Bp1JLsj1.mjs.map +0 -1
  101. package/fesm2022/acorex-platform-themes-default-entity-master-single-view.component-BExtm1JE.mjs.map +0 -1
  102. package/fesm2022/acorex-platform-themes-shared-icon-chooser-view.component-KpZWpnOJ.mjs.map +0 -1
  103. package/fesm2022/acorex-platform-themes-shared-settings.provider-CXiRmniv.mjs.map +0 -1
  104. package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-column.component-BvOiVCgt.mjs.map +0 -1
  105. package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-view.component-BW0rfkjk.mjs.map +0 -1
  106. package/fesm2022/acorex-platform-widgets-button-widget-designer.component-C2Qn1YAW.mjs.map +0 -1
  107. package/fesm2022/acorex-platform-widgets-checkbox-widget-column.component-CzEFmKWG.mjs +0 -84
  108. package/fesm2022/acorex-platform-widgets-checkbox-widget-column.component-CzEFmKWG.mjs.map +0 -1
  109. package/fesm2022/acorex-platform-widgets-checkbox-widget-designer.component-JC_nYunG.mjs +0 -55
  110. package/fesm2022/acorex-platform-widgets-checkbox-widget-designer.component-JC_nYunG.mjs.map +0 -1
  111. package/fesm2022/acorex-platform-widgets-checkbox-widget-view.component-C-4bWr9G.mjs +0 -76
  112. package/fesm2022/acorex-platform-widgets-checkbox-widget-view.component-C-4bWr9G.mjs.map +0 -1
  113. package/fesm2022/acorex-platform-widgets-color-box-widget-designer.component-CxgKO2VI.mjs +0 -55
  114. package/fesm2022/acorex-platform-widgets-color-box-widget-designer.component-CxgKO2VI.mjs.map +0 -1
  115. package/fesm2022/acorex-platform-widgets-extra-properties-schema-widget-edit.component-D9mf08rU.mjs.map +0 -1
  116. package/fesm2022/acorex-platform-widgets-extra-properties-schema-widget-view.component-D6GQ-eyr.mjs.map +0 -1
  117. package/fesm2022/acorex-platform-widgets-extra-properties-values-widget-edit.component-DVbIdVZ6.mjs.map +0 -1
  118. package/fesm2022/acorex-platform-widgets-extra-properties-values-widget-view.component-D-aM64Hu.mjs.map +0 -1
  119. package/fesm2022/acorex-platform-widgets-extra-properties-widget-edit.component-em2-aU8E.mjs.map +0 -1
  120. package/fesm2022/acorex-platform-widgets-extra-properties-widget-view.component-BeuIofdr.mjs.map +0 -1
  121. package/fesm2022/acorex-platform-widgets-file-list-popup.component-rW2RD35f.mjs.map +0 -1
  122. package/fesm2022/acorex-platform-widgets-file-rename-popup.component-DHFMnkls.mjs +0 -211
  123. package/fesm2022/acorex-platform-widgets-file-rename-popup.component-DHFMnkls.mjs.map +0 -1
  124. package/fesm2022/acorex-platform-widgets-page-widget-designer.component-DNvnQ4Mc.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-CPVRbE8B.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,2104 +1,2080 @@
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 = {}));
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 i1 from '@acorex/platform/layout/widget-core';
7
+ import { AXPWidgetContainerComponent, AXPPageStatus, AXPWidgetCoreModule } from '@acorex/platform/layout/widget-core';
8
+ import * as i2 from '@acorex/components/form';
9
+ import { AXFormComponent, AXFormModule } from '@acorex/components/form';
10
+ import { isEqual, get, cloneDeep } from 'lodash-es';
11
+ import { AXPExpressionEvaluatorService } from '@acorex/platform/core';
12
+ import { Subject, debounceTime, distinctUntilChanged, startWith } from 'rxjs';
13
+ import * as i2$1 from '@acorex/components/button';
14
+ import { AXButtonModule } from '@acorex/components/button';
15
+ import * as i3 from '@acorex/components/decorators';
16
+ import { AXDecoratorModule } from '@acorex/components/decorators';
17
+ import * as i4 from '@acorex/components/loading';
18
+ import { AXLoadingModule } from '@acorex/components/loading';
19
+ import { AXBasePageComponent } from '@acorex/components/page';
20
+ import * as i5 from '@acorex/core/translation';
21
+ import { AXTranslationModule } from '@acorex/core/translation';
46
22
 
47
- class AXPLayoutElement {
48
- api() {
49
- return {};
23
+ //#region ---- Inheritance Utilities ----
24
+ /**
25
+ * Resolves inherited properties from context and local values
26
+ */
27
+ function resolveInheritedProperties(context, localValues = {}) {
28
+ return {
29
+ mode: localValues.mode ?? context.mode ?? 'edit',
30
+ disabled: localValues.disabled ?? context.disabled,
31
+ readonly: localValues.readonly ?? context.readonly,
32
+ direction: localValues.direction ?? context.direction,
33
+ visible: localValues.visible ?? context.visible,
34
+ };
35
+ }
36
+ /**
37
+ * Merges inheritance context with local overrides
38
+ */
39
+ function mergeInheritanceContext(parentContext, localOverrides = {}) {
40
+ return {
41
+ ...parentContext,
42
+ ...localOverrides,
43
+ };
44
+ }
45
+ /**
46
+ * Generates a random string for path/name generation
47
+ */
48
+ function generateRandomId() {
49
+ return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
50
+ }
51
+ /**
52
+ * Converts label to a valid path/name
53
+ */
54
+ function labelToPath(label) {
55
+ return label
56
+ .toLowerCase()
57
+ .replace(/[^a-z0-9\s]/g, '') // Remove special characters
58
+ .replace(/\s+/g, '_') // Replace spaces with underscores
59
+ .substring(0, 50); // Limit length
60
+ }
61
+ /**
62
+ * Generates path for value widgets based on hierarchy
63
+ */
64
+ function generateValueWidgetPath(widgetName, formFieldName, formFieldLabel) {
65
+ // Priority: widget name -> form field name -> generated from label -> random
66
+ if (widgetName) {
67
+ return widgetName;
68
+ }
69
+ if (formFieldName) {
70
+ return formFieldName;
50
71
  }
72
+ if (formFieldLabel) {
73
+ return labelToPath(formFieldLabel);
74
+ }
75
+ return generateRandomId();
51
76
  }
77
+ //#endregion
78
+ //#region ---- Service Implementation ----
52
79
  class AXPLayoutBuilderService {
53
80
  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);
81
+ this.popupService = inject(AXPopupService);
125
82
  }
126
83
  /**
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.
84
+ * Create a new layout builder
130
85
  */
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
- });
86
+ create() {
87
+ return new LayoutBuilder(this.popupService);
159
88
  }
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 }); }
89
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXPLayoutBuilderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
90
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXPLayoutBuilderService, providedIn: 'root' }); }
163
91
  }
164
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPLayoutBuilderService, decorators: [{
165
- type: Injectable
92
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXPLayoutBuilderService, decorators: [{
93
+ type: Injectable,
94
+ args: [{
95
+ providedIn: 'root',
96
+ }]
166
97
  }] });
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,
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',
245
111
  };
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,
112
+ this.inheritanceContext = {
113
+ mode: 'edit',
265
114
  };
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
- email: 'email-editor',
288
- largeText: 'large-text-editor',
289
- link: 'link-editor',
290
- number: 'number-editor',
291
- numberUnit: 'number-unit-editor',
292
- password: 'password-editor',
293
- phone: 'phone-editor',
294
- richText: 'rich-text-editor',
295
- select: 'select-editor',
296
- selectionList: 'selection-list-editor',
297
- text: 'text-editor',
298
- table: 'table-editor',
299
- toggle: 'toggle-editor',
300
- blockLayout: 'block-layout',
301
- pageLayout: 'page-layout',
302
- repeaterLayout: 'repeater-layout',
303
- textBlockLayout: 'text-block-layout',
304
- fileUploader: 'file-uploader',
305
- fileTypeExtension: 'file-type-extension',
306
- map: 'map',
307
- imageMarker: 'image-marker',
308
- image: 'image',
309
- gallery: 'gallery',
310
- signature: 'signature',
311
- buttonAction: 'button-action',
312
- document: 'document-layout',
313
- lookup: 'lookup-editor',
314
- formField: 'form-field',
315
- qrcode: 'qrcode',
316
- advancedGrid: 'advanced-grid-layout',
317
- advancedGridItem: 'advanced-grid-item-layout',
318
- grid: 'grid-layout',
319
- gridItem: 'grid-item-layout',
320
- gridRow: 'grid-row-layout',
321
- widgetSelector: 'widget-selector',
322
- template: 'template',
323
- templateDesigner: 'template-designer',
324
- cronJob: 'cron-job',
325
- spacing: 'spacing',
326
- direction: 'direction',
327
- border: 'border',
328
- flexLayout: 'flex-layout',
329
- flexItem: 'flex-item-layout',
330
- avatar: 'avatar',
331
- themePaletteChooser: 'theme-palette-chooser',
332
- themeModeChooser: 'theme-mode-chooser',
333
- menuOrientationChooser: 'menu-orientation-chooser',
334
- fontStyleChooser: 'font-style-chooser',
335
- fontSizeChooser: 'font-size-chooser',
336
- iconChooser: 'icon-chooser',
337
- themeColorChooser: 'theme-color-chooser',
338
- gridOptions: 'grid-options',
339
- gridItemOptions: 'grid-item-options',
340
- advancedGridOptions: 'advanced-grid-options',
341
- stringFilter: 'string-filter',
342
- numberFilter: 'number-filter',
343
- dateTimeFilter: 'datetime-filter',
344
- booleanFilter: 'boolean-filter',
345
- lookupFilter: 'lookup-filter',
346
- flexOptions: 'flex-options',
347
- flexItemOptions: 'flex-item-options',
348
- selectFilter: 'select-filter',
349
- requiredValidation: 'required-validation',
350
- regularExpressionValidation: 'regular-expression-validation',
351
- minLengthValidation: 'min-length-validation',
352
- maxLengthValidation: 'max-length-validation',
353
- lessThanValidation: 'less-than-validation',
354
- greaterThanValidation: 'greater-than-validation',
355
- betweenValidation: 'between-validation',
356
- equalValidation: 'equal-validation',
357
- callbackValidation: 'callback-validation',
358
- donutChart: 'donut-chart',
359
- lineChart: 'line-chart',
360
- barChart: 'bar-chart',
361
- gaugeChart: 'gauge-chart',
362
- stickyNote: 'sticky-note',
363
- clockCalendar: 'clock-calendar',
364
- analogClock: 'analog-clock',
365
- weather: 'weather',
366
- minimalWeather: 'minimal-weather',
367
- advancedWeather: 'advanced-weather',
368
- metaData: 'meta-data-editor',
369
- templateEditor: 'template-box-editor',
370
- panel: 'panel',
371
- notification: 'notification',
372
- taskBoard: 'task-board',
373
- comment: 'comment',
374
- list: 'list',
375
- listToolbar: 'list-toolbar',
376
- entityList: 'entity-list',
377
- documentUploader: 'document-uploader',
378
- };
379
-
380
- function cloneProperty(property, values) {
381
- return merge(cloneDeep(property), values);
382
- }
383
- function createStringProperty(ctor) {
384
- return {
385
- name: ctor.name,
386
- title: ctor.title,
387
- group: ctor.group,
388
- schema: {
389
- dataType: 'string',
390
- defaultValue: ctor.defaultValue,
391
- interface: {
392
- name: ctor.name,
393
- path: ctor.path ?? ctor.name,
394
- type: AXPWidgetsCatalog.text,
395
- },
396
- },
397
- visible: !isNil(ctor.visible) ? ctor.visible : true,
398
- };
399
- }
400
- function createNumberProperty(ctor) {
401
- return {
402
- name: ctor.name,
403
- title: ctor.title,
404
- group: ctor.group,
405
- schema: {
406
- dataType: 'number',
407
- defaultValue: ctor.defaultValue,
408
- interface: {
409
- name: ctor.name,
410
- path: ctor.path ?? ctor.name,
411
- type: AXPWidgetsCatalog.number,
412
- options: ctor.options,
413
- },
414
- },
415
- visible: !isNil(ctor.visible) ? ctor.visible : true,
416
- };
417
- }
418
- function createBooleanProperty(ctor) {
419
- return {
420
- name: ctor.name,
421
- title: ctor.title,
422
- group: ctor.group,
423
- schema: {
424
- dataType: 'boolean',
425
- defaultValue: ctor.defaultValue ?? false,
426
- interface: {
427
- name: ctor.name,
428
- path: ctor.path ?? ctor.name,
429
- type: AXPWidgetsCatalog.toggle,
430
- },
431
- },
432
- visible: ctor.visible ?? true,
433
- };
434
- }
435
- function createSelectProperty(ctor) {
436
- return {
437
- name: ctor.name,
438
- title: ctor.title,
439
- group: ctor.group,
440
- schema: {
441
- dataType: 'string',
442
- defaultValue: Array.isArray(ctor.defaultValue)
443
- ? ctor.defaultValue.map((item) => (typeof item === 'string' ? { id: item } : item))
444
- : typeof ctor.defaultValue === 'string'
445
- ? { id: ctor.defaultValue, title: ctor.defaultValue }
446
- : ctor.defaultValue,
447
- interface: {
448
- name: ctor.name,
449
- path: ctor.path ?? ctor.name,
450
- type: AXPWidgetsCatalog.select,
451
- options: {
452
- dataSource: ctor.dataSource.map((item) => (typeof item === 'string' ? { id: item, title: item } : item)),
453
- },
454
- },
455
- },
456
- visible: ctor.visible ?? true,
457
- };
458
- }
459
- const AXP_WIDGET_TOKEN = new InjectionToken('AXP_WIDGET_TOKEN');
460
- const AXP_WIDGET_COLUMN_TOKEN = new InjectionToken('AXP_WIDGET_COLUMN_TOKEN');
461
-
462
- class AXPBaseWidgetComponent extends AXPLayoutElement {
463
- constructor() {
464
- super(...arguments);
465
- this.token = inject(AXP_WIDGET_TOKEN);
466
- this.host = inject(ElementRef).nativeElement;
467
- this.layoutService = inject(AXPLayoutBuilderService);
468
- this.contextService = inject(AXPLayoutBuilderContextStore);
469
- this.config = this.token.config;
470
- this.node = this.token.node;
471
- this.name = this.token.node.name;
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 [];
491
115
  }
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;
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);
516
159
  }
517
- return null;
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;
518
171
  }
519
- call(name, ...args) {
520
- const fn = get(this, name);
521
- if (fn && typeof fn == 'function') {
522
- fn.bind(this)(...args);
172
+ dialog(delegate) {
173
+ const container = new DialogContainerBuilder(this.popupService);
174
+ if (delegate) {
175
+ delegate(container);
523
176
  }
177
+ return container;
524
178
  }
525
- setChildren(children) {
526
- this._children.set([...children]);
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
+ };
527
194
  }
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 }); }
531
- }
532
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPBaseWidgetComponent, decorators: [{
533
- type: Injectable
534
- }] });
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 }); }
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
- }
712
- }
713
- }
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" }] : []));
348
+ /**
349
+ * Layout container mixin - Interface Segregation Principle
350
+ * Provides layout-specific operations
351
+ */
352
+ class LayoutContainerMixin extends BaseContainerMixin {
353
+ layout(value) {
354
+ // Layout handling can be added here if needed
355
+ return this;
730
356
  }
731
- async extractItem(item) {
732
- if (isNil(item)) {
733
- return null;
357
+ }
358
+ /**
359
+ * Child container mixin - Interface Segregation Principle
360
+ * Provides child container management
361
+ */
362
+ class ChildContainerMixin extends LayoutContainerMixin {
363
+ grid(delegate) {
364
+ const container = new GridContainerBuilder();
365
+ container.withInheritanceContext(this.inheritanceContext);
366
+ if (delegate) {
367
+ delegate(container);
734
368
  }
735
- if (isObjectLike(item) && get(item, this.textField()) != null) {
736
- return item;
369
+ this.ensureChildren();
370
+ this.containerState.children.push(container.build());
371
+ return this;
372
+ }
373
+ flex(delegate) {
374
+ const container = new FlexContainerBuilder();
375
+ container.withInheritanceContext(this.inheritanceContext);
376
+ if (delegate) {
377
+ delegate(container);
737
378
  }
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
- }
379
+ this.ensureChildren();
380
+ this.containerState.children.push(container.build());
381
+ return this;
382
+ }
383
+ panel(delegate) {
384
+ const container = new PanelContainerBuilder();
385
+ container.withInheritanceContext(this.inheritanceContext);
386
+ if (delegate) {
387
+ delegate(container);
745
388
  }
746
- return isObjectLike(item)
747
- ? item
748
- : {
749
- [this.valueField()]: item,
750
- [this.textField()]: item,
751
- };
389
+ this.ensureChildren();
390
+ this.containerState.children.push(container.build());
391
+ return this;
392
+ }
393
+ page(delegate) {
394
+ const container = new PageContainerBuilder();
395
+ container.withInheritanceContext(this.inheritanceContext);
396
+ if (delegate) {
397
+ delegate(container);
398
+ }
399
+ this.ensureChildren();
400
+ this.containerState.children.push(container.build());
401
+ return this;
402
+ }
403
+ tabset(delegate) {
404
+ const container = new TabsetContainerBuilder();
405
+ container.withInheritanceContext(this.inheritanceContext);
406
+ if (delegate) {
407
+ delegate(container);
408
+ }
409
+ this.ensureChildren();
410
+ this.containerState.children.push(container.build());
411
+ return this;
412
+ }
413
+ fieldset(delegate) {
414
+ const container = new FieldsetContainerBuilder();
415
+ container.withInheritanceContext(this.inheritanceContext);
416
+ if (delegate) {
417
+ delegate(container);
418
+ }
419
+ this.ensureChildren();
420
+ this.containerState.children.push(container.build());
421
+ return this;
422
+ }
423
+ dialog(delegate) {
424
+ const container = new DialogContainerBuilder(); // Will use inject() fallback
425
+ if (delegate) {
426
+ delegate(container);
427
+ }
428
+ return container;
429
+ }
430
+ formField(label, delegate) {
431
+ const field = new FormFieldBuilder(label);
432
+ field.withInheritanceContext(this.inheritanceContext);
433
+ if (delegate) {
434
+ delegate(field);
435
+ }
436
+ this.ensureChildren();
437
+ this.containerState.children.push(field.build());
438
+ return this;
752
439
  }
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
440
  }
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" }] : []));
441
+ /**
442
+ * Widget container mixin - Interface Segregation Principle
443
+ * Provides widget creation operations
444
+ */
445
+ class WidgetContainerMixin extends ChildContainerMixin {
446
+ textBox(options) {
447
+ this.addWidget('text-editor', options);
448
+ return this;
449
+ }
450
+ largeTextBox(options) {
451
+ this.addWidget('large-text-editor', options);
452
+ return this;
453
+ }
454
+ richText(options) {
455
+ this.addWidget('rich-text-editor', options);
456
+ return this;
457
+ }
458
+ passwordBox(options) {
459
+ this.addWidget('password-editor', options);
460
+ return this;
461
+ }
462
+ numberBox(options) {
463
+ this.addWidget('number-editor', options);
464
+ return this;
465
+ }
466
+ selectBox(options) {
467
+ this.addWidget('select-editor', options);
468
+ return this;
469
+ }
470
+ lookupBox(options) {
471
+ this.addWidget('lookup-editor', options);
472
+ return this;
473
+ }
474
+ selectionList(options) {
475
+ this.addWidget('selection-list-editor', options);
476
+ return this;
477
+ }
478
+ dateTimeBox(options) {
479
+ this.addWidget('date-time-editor', options);
480
+ return this;
481
+ }
482
+ toggleSwitch(options) {
483
+ this.addWidget('toggle-editor', options);
484
+ return this;
485
+ }
486
+ colorBox(options) {
487
+ this.addWidget('color-editor', options);
488
+ return this;
489
+ }
490
+ list(delegate) {
491
+ const container = new ListWidgetBuilder();
492
+ container.withInheritanceContext(this.inheritanceContext);
493
+ if (delegate) {
494
+ delegate(container);
495
+ }
496
+ this.ensureChildren();
497
+ this.containerState.children.push(container.build());
498
+ return this;
499
+ }
500
+ customWidget(type, options) {
501
+ this.addWidget(type, options);
502
+ return this;
773
503
  }
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
504
  }
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 {
505
+ /**
506
+ * Flex Container Builder - Liskov Substitution Principle
507
+ * Extends WidgetContainerMixin to inherit all common functionality
508
+ */
509
+ class FlexContainerBuilder extends WidgetContainerMixin {
782
510
  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" }] : []));
511
+ super('flex-layout');
512
+ }
513
+ setOptions(options) {
514
+ this.containerState.options = { ...this.containerState.options, ...options };
515
+ return this;
516
+ }
517
+ // Individual fluent methods for Flex
518
+ setDirection(direction) {
519
+ return this.setOptions({ flexDirection: direction });
520
+ }
521
+ setWrap(wrap) {
522
+ return this.setOptions({ flexWrap: wrap });
523
+ }
524
+ setJustifyContent(justify) {
525
+ return this.setOptions({ justifyContent: justify });
526
+ }
527
+ setAlignItems(align) {
528
+ return this.setOptions({ alignItems: align });
529
+ }
530
+ setGap(gap) {
531
+ return this.setOptions({ gap });
532
+ }
533
+ setBackgroundColor(color) {
534
+ return this.setOptions({ backgroundColor: color });
535
+ }
536
+ setPadding(padding) {
537
+ return this.setOptions({ spacing: { padding } });
538
+ }
539
+ setMargin(margin) {
540
+ return this.setOptions({ spacing: { margin } });
839
541
  }
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
542
  }
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 {
543
+ /**
544
+ * Grid Container Builder - Liskov Substitution Principle
545
+ * Extends WidgetContainerMixin to inherit all common functionality
546
+ */
547
+ class GridContainerBuilder extends WidgetContainerMixin {
848
548
  constructor() {
849
- super(...arguments);
850
- this.hostClass = computed(() => this.blockClass(), ...(ngDevMode ? [{ debugName: "hostClass" }] : []));
851
- this.hostStyle = computed(() => this.blockStyle(), ...(ngDevMode ? [{ debugName: "hostStyle" }] : []));
549
+ super('grid-layout');
550
+ }
551
+ setOptions(options) {
552
+ this.containerState.options = { ...this.containerState.options, ...options };
553
+ return this;
554
+ }
555
+ // Individual fluent methods for Grid
556
+ setColumns(columns) {
557
+ return this.setOptions({ grid: { default: { columns } } });
558
+ }
559
+ setRows(rows) {
560
+ return this.setOptions({ grid: { default: { rows } } });
561
+ }
562
+ setGap(gap) {
563
+ return this.setOptions({ grid: { default: { gap } } });
564
+ }
565
+ setJustifyItems(justify) {
566
+ return this.setOptions({ grid: { default: { justifyItems: justify } } });
567
+ }
568
+ setAlignItems(align) {
569
+ return this.setOptions({ grid: { default: { alignItems: align } } });
570
+ }
571
+ setAutoFlow(flow) {
572
+ return this.setOptions({ grid: { default: { autoFlow: flow } } });
573
+ }
574
+ setBackgroundColor(color) {
575
+ return this.setOptions({ backgroundColor: color });
576
+ }
577
+ setPadding(padding) {
578
+ return this.setOptions({ spacing: { padding } });
579
+ }
580
+ setMargin(margin) {
581
+ return this.setOptions({ spacing: { margin } });
852
582
  }
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
583
  }
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 {
584
+ /**
585
+ * Panel Container Builder - Liskov Substitution Principle
586
+ * Extends WidgetContainerMixin to inherit all common functionality
587
+ */
588
+ class PanelContainerBuilder extends WidgetContainerMixin {
861
589
  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 }); }
590
+ super('panel-layout');
591
+ }
592
+ setOptions(options) {
593
+ this.containerState.options = { ...this.containerState.options, ...options };
594
+ return this;
595
+ }
596
+ // Individual fluent methods for Panel
597
+ setCaption(caption) {
598
+ return this.setOptions({ caption });
599
+ }
600
+ setIcon(icon) {
601
+ return this.setOptions({ icon });
602
+ }
603
+ setLook(look) {
604
+ return this.setOptions({ look });
605
+ }
606
+ setShowHeader(show) {
607
+ return this.setOptions({ showHeader: show });
608
+ }
609
+ setCollapsed(collapsed) {
610
+ return this.setOptions({ collapsed });
611
+ }
919
612
  }
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 {
613
+ /**
614
+ * Page Container Builder - Liskov Substitution Principle
615
+ * Extends WidgetContainerMixin to inherit all common functionality
616
+ */
617
+ class PageContainerBuilder extends WidgetContainerMixin {
925
618
  constructor() {
926
- super(...arguments);
927
- this.hostClass = computed(() => this.inlineClass(), ...(ngDevMode ? [{ debugName: "hostClass" }] : []));
928
- this.hostStyle = computed(() => this.inlineStyle(), ...(ngDevMode ? [{ debugName: "hostStyle" }] : []));
619
+ super('page-layout');
620
+ }
621
+ setOptions(options) {
622
+ this.containerState.options = { ...this.containerState.options, ...options };
623
+ return this;
624
+ }
625
+ // Individual fluent methods for Page
626
+ setBackgroundColor(color) {
627
+ return this.setOptions({ backgroundColor: color });
628
+ }
629
+ setTheme(theme) {
630
+ return this.setOptions({ theme });
631
+ }
632
+ setHasHeader(hasHeader) {
633
+ return this.setOptions({ hasHeader });
634
+ }
635
+ setHasFooter(hasFooter) {
636
+ return this.setOptions({ hasFooter });
637
+ }
638
+ setDirection(direction) {
639
+ return this.setOptions({ direction });
929
640
  }
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
641
  }
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 {
642
+ /**
643
+ * Tabset Container Builder - Liskov Substitution Principle
644
+ * Extends WidgetContainerMixin to inherit all common functionality
645
+ */
646
+ class TabsetContainerBuilder extends WidgetContainerMixin {
938
647
  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 }); }
648
+ super('tabset-layout');
649
+ }
650
+ setOptions(options) {
651
+ this.containerState.options = { ...this.containerState.options, ...options };
652
+ return this;
653
+ }
654
+ // Individual fluent methods for Tabset
655
+ setLook(look) {
656
+ return this.setOptions({ look });
657
+ }
658
+ setOrientation(orientation) {
659
+ return this.setOptions({ orientation });
660
+ }
661
+ setActiveIndex(index) {
662
+ return this.setOptions({ activeIndex: index });
663
+ }
991
664
  }
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 {
997
- 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 }); }
665
+ /**
666
+ * Form Field Builder - Liskov Substitution Principle
667
+ * Can only contain ONE widget with automatic path generation
668
+ */
669
+ class FormFieldBuilder extends LayoutContainerMixin {
670
+ constructor(label) {
671
+ super('form-field');
672
+ this.hasWidget = false;
673
+ this.containerState.options = { label, showLabel: true };
674
+ }
675
+ setOptions(options) {
676
+ this.containerState.options = { ...this.containerState.options, ...options };
677
+ return this;
678
+ }
679
+ setLabel(label) {
680
+ return this.setOptions({ label });
681
+ }
682
+ setShowLabel(showLabel) {
683
+ return this.setOptions({ showLabel });
684
+ }
685
+ // Single widget methods with automatic path generation
686
+ addSingleWidget(type, options) {
687
+ if (this.hasWidget) {
688
+ throw new Error('Form field can only contain one widget');
689
+ }
690
+ const formFieldName = this.containerState.name;
691
+ const formFieldPath = this.containerState.path; // Get explicit path from form field
692
+ const formFieldLabel = this.containerState.options?.['label'];
693
+ const widgetName = options?.name;
694
+ // Generate widget path: explicit path -> widget name -> form field name -> label -> random
695
+ let widgetPath;
696
+ if (formFieldPath) {
697
+ widgetPath = formFieldPath; // Use explicit form field path first
698
+ }
699
+ else if (widgetName) {
700
+ widgetPath = widgetName;
701
+ }
702
+ else if (formFieldName) {
703
+ widgetPath = formFieldName; // Use form field name as default path
704
+ }
705
+ else if (formFieldLabel) {
706
+ widgetPath = labelToPath(formFieldLabel);
707
+ }
708
+ else {
709
+ widgetPath = generateRandomId();
710
+ }
711
+ const finalName = widgetName || formFieldName || widgetPath;
712
+ const child = new WidgetBuilder();
713
+ child.type(type);
714
+ child.name(finalName);
715
+ child.path(widgetPath);
716
+ // Remove name from options since it's now in state
717
+ const { name: _, ...cleanOptions } = (options || {});
718
+ child.withInheritanceContext(this.inheritanceContext);
719
+ child.options(cleanOptions);
720
+ // IMPORTANT: Store the widget builder, don't build it yet!
721
+ // This allows properties set after this method (like disabled, readonly) to be applied
722
+ this.childWidget = child;
723
+ this.hasWidget = true;
724
+ return this;
725
+ }
726
+ textBox(options) {
727
+ return this.addSingleWidget('text-editor', options);
728
+ }
729
+ largeTextBox(options) {
730
+ return this.addSingleWidget('large-text-editor', options);
731
+ }
732
+ richText(options) {
733
+ return this.addSingleWidget('rich-text-editor', options);
734
+ }
735
+ passwordBox(options) {
736
+ return this.addSingleWidget('password-editor', options);
737
+ }
738
+ numberBox(options) {
739
+ return this.addSingleWidget('number-editor', options);
740
+ }
741
+ selectBox(options) {
742
+ return this.addSingleWidget('select-editor', options);
743
+ }
744
+ lookupBox(options) {
745
+ return this.addSingleWidget('lookup-editor', options);
746
+ }
747
+ selectionList(options) {
748
+ return this.addSingleWidget('selection-list-editor', options);
749
+ }
750
+ dateTimeBox(options) {
751
+ return this.addSingleWidget('date-time-editor', options);
752
+ }
753
+ toggleSwitch(options) {
754
+ return this.addSingleWidget('toggle-editor', options);
755
+ }
756
+ colorBox(options) {
757
+ return this.addSingleWidget('color-editor', options);
758
+ }
759
+ customWidget(type, options) {
760
+ return this.addSingleWidget(type, options);
761
+ }
762
+ // Override property setters to propagate changes to child widget
763
+ disabled(condition) {
764
+ super.disabled(condition);
765
+ if (this.childWidget) {
766
+ this.childWidget.withInheritanceContext(this.inheritanceContext);
767
+ }
768
+ return this;
769
+ }
770
+ readonly(condition) {
771
+ super.readonly(condition);
772
+ if (this.childWidget) {
773
+ this.childWidget.withInheritanceContext(this.inheritanceContext);
774
+ }
775
+ return this;
776
+ }
777
+ visible(condition) {
778
+ super.visible(condition);
779
+ if (this.childWidget) {
780
+ this.childWidget.withInheritanceContext(this.inheritanceContext);
781
+ }
782
+ return this;
783
+ }
784
+ direction(direction) {
785
+ super.direction(direction);
786
+ if (this.childWidget) {
787
+ this.childWidget.withInheritanceContext(this.inheritanceContext);
788
+ }
789
+ return this;
790
+ }
791
+ mode(mode) {
792
+ super.mode(mode);
793
+ if (this.childWidget) {
794
+ this.childWidget.withInheritanceContext(this.inheritanceContext);
795
+ }
796
+ return this;
797
+ }
798
+ // Override withInheritanceContext to pass it to the child widget if it exists
799
+ withInheritanceContext(context) {
800
+ // Call parent implementation first
801
+ super.withInheritanceContext(context);
802
+ // If we have a child widget, update its inheritance context too
803
+ if (this.childWidget) {
804
+ this.childWidget.withInheritanceContext(this.inheritanceContext);
805
+ }
806
+ return this;
807
+ }
808
+ // Override build() to build the child widget at the last moment
809
+ build() {
810
+ // Build the child widget and add it to children before building the form field
811
+ if (this.childWidget) {
812
+ this.ensureChildren();
813
+ this.containerState.children.push(this.childWidget.build());
814
+ }
815
+ // Call parent build
816
+ return super.build();
817
+ }
1030
818
  }
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 {
819
+ /**
820
+ * Fieldset Container Builder - Liskov Substitution Principle
821
+ * Extends LayoutContainerMixin to inherit layout functionality
822
+ * Specialized for form fields only
823
+ */
824
+ class FieldsetContainerBuilder extends LayoutContainerMixin {
1036
825
  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 }); }
826
+ super('fieldset-layout');
827
+ this.containerState.options = {};
828
+ }
829
+ setOptions(options) {
830
+ this.containerState.options = { ...this.containerState.options, ...options };
831
+ return this;
832
+ }
833
+ // Individual fluent methods for Fieldset
834
+ setTitle(title) {
835
+ return this.setOptions({ title });
836
+ }
837
+ setDescription(description) {
838
+ return this.setOptions({ description });
839
+ }
840
+ setIcon(icon) {
841
+ return this.setOptions({ icon });
842
+ }
843
+ setCollapsible(collapsible) {
844
+ return this.setOptions({ collapsible });
845
+ }
846
+ setIsOpen(isOpen) {
847
+ return this.setOptions({ isOpen });
848
+ }
849
+ setLook(look) {
850
+ return this.setOptions({ look });
851
+ }
852
+ setShowHeader(showHeader) {
853
+ return this.setOptions({ showHeader });
854
+ }
855
+ setCols(cols) {
856
+ return this.setOptions({ cols });
857
+ }
858
+ // Only form fields are allowed in fieldset
859
+ formField(label, delegate) {
860
+ const field = new FormFieldBuilder(label);
861
+ field.withInheritanceContext(this.inheritanceContext);
862
+ if (delegate) {
863
+ delegate(field);
864
+ }
865
+ this.ensureChildren();
866
+ this.containerState.children.push(field.build());
867
+ return this;
868
+ }
1070
869
  }
1071
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPGridItemBaseLayoutWidgetComponent, decorators: [{
1072
- type: Injectable
1073
- }] });
1074
-
1075
- class AXPWidgetRegistryService {
1076
- /**
1077
- *
1078
- */
870
+ /**
871
+ * List Widget Builder - Liskov Substitution Principle
872
+ * Extends WidgetContainerMixin to inherit all common functionality
873
+ */
874
+ class ListWidgetBuilder extends WidgetContainerMixin {
1079
875
  constructor() {
1080
- this.types = new Map();
1081
- AXPWidgetRegistryService.instance = this;
876
+ super('list');
1082
877
  }
1083
- register(widget) {
1084
- this.types.set(widget.name, widget);
878
+ setOptions(options) {
879
+ this.containerState.options = { ...this.containerState.options, ...options };
880
+ return this;
1085
881
  }
1086
- extend(parentName, widget) {
1087
- const parentWidget = this.resolve(parentName);
1088
- const newWidget = merge({}, parentWidget, widget);
1089
- newWidget.name = widget.name;
1090
- this.register(newWidget);
882
+ // Individual fluent methods for List Widget
883
+ setDataSource(dataSource) {
884
+ return this.setOptions({ dataSource });
1091
885
  }
1092
- resolve(name) {
1093
- const widget = this.types.get(name);
1094
- if (!widget) {
1095
- throw new Error(`Widget with name "${name}" does not exist.`);
1096
- }
1097
- return widget;
886
+ setColumns(columns) {
887
+ return this.setOptions({ columns });
888
+ }
889
+ // Event handlers
890
+ setOnRowClick(handler) {
891
+ return this.setOptions({ onRowClick: handler });
892
+ }
893
+ setOnRowDoubleClick(handler) {
894
+ return this.setOptions({ onRowDoubleClick: handler });
895
+ }
896
+ setOnSelectionChange(handler) {
897
+ return this.setOptions({ onSelectionChange: handler });
898
+ }
899
+ setOnRowCommand(handler) {
900
+ return this.setOptions({ onRowCommand: handler });
901
+ }
902
+ // Table features
903
+ setPaging(paging) {
904
+ return this.setOptions({ paging });
905
+ }
906
+ setShowHeader(show) {
907
+ return this.setOptions({ showHeader: show });
908
+ }
909
+ setShowFooter(show) {
910
+ return this.setOptions({ showFooter: show });
911
+ }
912
+ setFixHeader(fix) {
913
+ return this.setOptions({ fixHeader: fix });
914
+ }
915
+ setFixFooter(fix) {
916
+ return this.setOptions({ fixFooter: fix });
917
+ }
918
+ setFetchDataMode(mode) {
919
+ return this.setOptions({ fetchDataMode: mode });
920
+ }
921
+ setParentField(field) {
922
+ return this.setOptions({ parentField: field });
923
+ }
924
+ setMinHeight(height) {
925
+ return this.setOptions({ minHeight: height });
926
+ }
927
+ // Selection & Index
928
+ setShowIndex(show) {
929
+ return this.setOptions({ showIndex: show });
930
+ }
931
+ setAllowSelection(allow) {
932
+ return this.setOptions({ allowSelection: allow });
933
+ }
934
+ // Commands
935
+ setPrimaryCommands(commands) {
936
+ return this.setOptions({ primaryCommands: commands });
937
+ }
938
+ setSecondaryCommands(commands) {
939
+ return this.setOptions({ secondaryCommands: commands });
1098
940
  }
1099
- all() {
1100
- return Array.from(this.types.values());
941
+ // Loading
942
+ setLoading(loading) {
943
+ return this.setOptions({ loading });
1101
944
  }
1102
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPWidgetRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1103
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPWidgetRegistryService, providedIn: 'root' }); }
1104
945
  }
1105
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPWidgetRegistryService, decorators: [{
1106
- type: Injectable,
1107
- args: [{
1108
- providedIn: 'root',
1109
- }]
1110
- }], ctorParameters: () => [] });
1111
-
1112
- class AXPWidgetColumnRendererComponent extends AXDataTableColumnComponent {
1113
- constructor() {
1114
- super(...arguments);
1115
- this.widgetRegistery = inject(AXPWidgetRegistryService);
1116
- this.grid = inject(AXBaseDataTable);
1117
- this.mergedOptions = signal({}, ...(ngDevMode ? [{ debugName: "mergedOptions" }] : []));
1118
- this.loadingRow = signal(null, ...(ngDevMode ? [{ debugName: "loadingRow" }] : []));
1119
- this.injector = inject(Injector);
1120
- this.cdr = inject(ChangeDetectorRef);
1121
- }
1122
- get node() {
1123
- return this._node;
1124
- }
1125
- set node(v) {
1126
- this._node = v;
1127
- }
1128
- get renderFooterTemplate() {
1129
- return this.footerTemplate ?? this._contentFooterTemplate;
1130
- }
1131
- get renderCellTemplate() {
1132
- return this.cellTemplate ?? this._contentCellTemplate;
1133
- }
1134
- async handleExpandRow(row) {
1135
- this.loadingRow.set(row);
1136
- await this.grid.expandRow(row);
1137
- this.loadingRow.set(null);
1138
- // if (row.data?.__meta__?.expanded === undefined) {
1139
- // this.width = `${parseInt(this.width as string) + 24}px`;
1140
- // }
1141
- }
1142
- get renderHeaderTemplate() {
1143
- return this.headerTemplate ?? this._contentHeaderTemplate;
1144
- }
1145
- get loadingEnabled() {
1146
- return true;
1147
- }
1148
- get name() {
1149
- return `col-${this.node.path}`;
1150
- }
1151
- async ngOnInit() {
1152
- const widget = this.widgetRegistery.resolve(this.node.type);
1153
- const mode = 'column';
1154
- this.component = await widget?.components[mode]?.component();
1155
- //
1156
- const props = widget?.components[mode]?.properties
1157
- ?.filter((c) => c.schema.defaultValue)
1158
- .map((c) => ({ [c.name]: c.schema.defaultValue }))
1159
- .reduce((acc, curr) => {
1160
- return { ...acc, ...curr };
1161
- }, {});
1162
- //
1163
- this.mergedOptions.set(merge(props, this.node.options) || {});
1164
- const tokenValue = {
1165
- path: this.node.path,
1166
- options: this.mergedOptions(),
1167
- };
1168
- this.widgetInjector = Injector.create({
1169
- parent: this.injector,
1170
- providers: [
1171
- {
1172
- provide: AXP_WIDGET_COLUMN_TOKEN,
1173
- useValue: tokenValue,
946
+ /**
947
+ * Dialog Container Builder - Specialized for dialog functionality
948
+ * Uses composition instead of inheritance for cleaner separation
949
+ */
950
+ class DialogContainerBuilder {
951
+ constructor(popupService) {
952
+ this.dialogState = {
953
+ type: 'flex-layout', // This will be overridden when content layout exists
954
+ children: [],
955
+ mode: 'edit',
956
+ dialogOptions: {
957
+ title: '',
958
+ size: 'md',
959
+ closeButton: false,
960
+ },
961
+ actions: {
962
+ footer: {
963
+ prefix: [],
964
+ suffix: [],
1174
965
  },
1175
- ],
1176
- });
1177
- this.width = this.customWidth ? this.customWidth : (this.mergedOptions().width ?? '200px');
1178
- this.allowResizing = this.mergedOptions().allowResizing || true;
1179
- this.cdr.detectChanges();
1180
- }
1181
- getInputs(data) {
1182
- return {
1183
- rawValue: getSmart(data, this.node.path),
1184
- rowData: data,
966
+ },
1185
967
  };
968
+ if (popupService) {
969
+ this.popupService = popupService;
970
+ }
971
+ else {
972
+ this.popupService = inject(AXPopupService);
973
+ }
1186
974
  }
1187
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPWidgetColumnRendererComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
1188
- 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: [
1189
- AXPLayoutBuilderService,
1190
- { provide: AXDataTableColumnComponent, useExisting: AXPWidgetColumnRendererComponent },
1191
- ], 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: `
1192
- <ng-template #header>{{ caption | translate | async }}</ng-template>
1193
- <ng-template #cell let-row>
1194
- <div class="ax-flex ax-gap-2 ax-items-center">
1195
- @if (expandHandler) {
1196
- <div
1197
- (click)="handleExpandRow(row)"
1198
- class="ax-expand-handler"
1199
- [class.ax-invisible]="row.data.hasChild === false"
1200
- id="ax-expand-handler-container"
1201
- [style.padding-inline-start.rem]="row.data?.__meta__?.level * 2"
1202
- >
1203
- @if (loadingRow() === row) {
1204
- <i class="fas fa-spinner-third ax-animate-twSpin ax-animate-infinite"></i>
1205
- } @else {
1206
- @if (row.data?.__meta__?.expanded) {
1207
- <i [class]="customCollapseIcon || 'far fa-minus-square ax-text-md ax-opacity-75'"></i>
1208
- } @else {
1209
- <i [class]="customExpandIcon || 'far fa-plus-square ax-text-md ax-opacity-75'"></i>
1210
- }
1211
- }
1212
- </div>
975
+ setOptions(options) {
976
+ this.dialogState.dialogOptions = { ...this.dialogState.dialogOptions, ...options };
977
+ return this;
978
+ }
979
+ // Individual fluent methods for Dialog
980
+ setTitle(title) {
981
+ return this.setOptions({ title });
982
+ }
983
+ setMessage(message) {
984
+ return this.setOptions({ message });
985
+ }
986
+ setSize(size) {
987
+ return this.setOptions({ size });
988
+ }
989
+ setCloseButton(closeButton) {
990
+ return this.setOptions({ closeButton });
991
+ }
992
+ setContext(context) {
993
+ return this.setOptions({ context });
994
+ }
995
+ content(delegate) {
996
+ if (delegate) {
997
+ // Create a flex container directly instead of through LayoutBuilder
998
+ const flexContainer = new FlexContainerBuilder();
999
+ flexContainer.setDirection('column');
1000
+ flexContainer.setGap('10px');
1001
+ delegate(flexContainer);
1002
+ this.contentLayout = flexContainer.build();
1213
1003
  }
1214
- @if (component && widgetInjector && row?.data) {
1215
- <ng-container
1216
- *ngComponentOutlet="component; injector: widgetInjector; inputs: getInputs(row.data)"
1217
- ></ng-container>
1004
+ return this;
1005
+ }
1006
+ setActions(delegate) {
1007
+ if (delegate) {
1008
+ const actionBuilder = new ActionBuilder(this);
1009
+ delegate(actionBuilder);
1010
+ }
1011
+ return this;
1012
+ }
1013
+ // Build method to create dialog node
1014
+ build() {
1015
+ // If we have content layout, use it directly to avoid extra wrapper
1016
+ if (this.contentLayout) {
1017
+ return {
1018
+ ...this.contentLayout,
1019
+ // Add dialog-specific properties
1020
+ options: {
1021
+ ...this.contentLayout.options,
1022
+ ...this.dialogState.dialogOptions,
1023
+ },
1024
+ };
1025
+ }
1026
+ // Fallback to dialog state structure if no content
1027
+ const result = {
1028
+ ...this.dialogState,
1029
+ children: [],
1030
+ };
1031
+ // Add dialog-specific properties
1032
+ if (this.dialogState.dialogOptions) {
1033
+ result.options = { ...result.options, ...this.dialogState.dialogOptions };
1218
1034
  }
1219
- </div>
1220
- </ng-template>
1221
- <ng-template #footer></ng-template>
1222
- `, 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 }); }
1035
+ return result;
1036
+ }
1037
+ // Dialog-specific methods
1038
+ async show() {
1039
+ const dialogNode = this.build();
1040
+ // Import the dialog renderer component dynamically
1041
+ const { AXPDialogRendererComponent } = await Promise.resolve().then(function () { return dialogRenderer_component; });
1042
+ // Create dialog configuration
1043
+ const dialogConfig = {
1044
+ title: this.dialogState.dialogOptions?.title || '',
1045
+ message: this.dialogState.dialogOptions?.message,
1046
+ context: this.dialogState.dialogOptions?.context || {},
1047
+ definition: dialogNode,
1048
+ actions: this.dialogState.actions,
1049
+ };
1050
+ // The Promise resolves when user clicks an action button
1051
+ return new Promise(async (resolve) => {
1052
+ this.popupService.open(AXPDialogRendererComponent, {
1053
+ title: dialogConfig.title,
1054
+ size: this.dialogState.dialogOptions?.size || 'md',
1055
+ closeButton: this.dialogState.dialogOptions?.closeButton || false,
1056
+ closeOnBackdropClick: false,
1057
+ draggable: false,
1058
+ data: {
1059
+ config: dialogConfig,
1060
+ callBack: (result) => {
1061
+ // Resolve with the dialog reference when user clicks an action
1062
+ resolve(result);
1063
+ },
1064
+ },
1065
+ });
1066
+ });
1067
+ }
1223
1068
  }
1224
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPWidgetColumnRendererComponent, decorators: [{
1225
- type: Component,
1226
- args: [{
1227
- selector: 'axp-widget-column-renderer',
1228
- template: `
1229
- <ng-template #header>{{ caption | translate | async }}</ng-template>
1230
- <ng-template #cell let-row>
1231
- <div class="ax-flex ax-gap-2 ax-items-center">
1232
- @if (expandHandler) {
1233
- <div
1234
- (click)="handleExpandRow(row)"
1235
- class="ax-expand-handler"
1236
- [class.ax-invisible]="row.data.hasChild === false"
1237
- id="ax-expand-handler-container"
1238
- [style.padding-inline-start.rem]="row.data?.__meta__?.level * 2"
1239
- >
1240
- @if (loadingRow() === row) {
1241
- <i class="fas fa-spinner-third ax-animate-twSpin ax-animate-infinite"></i>
1242
- } @else {
1243
- @if (row.data?.__meta__?.expanded) {
1244
- <i [class]="customCollapseIcon || 'far fa-minus-square ax-text-md ax-opacity-75'"></i>
1245
- } @else {
1246
- <i [class]="customExpandIcon || 'far fa-plus-square ax-text-md ax-opacity-75'"></i>
1247
- }
1248
- }
1249
- </div>
1250
- }
1251
- @if (component && widgetInjector && row?.data) {
1252
- <ng-container
1253
- *ngComponentOutlet="component; injector: widgetInjector; inputs: getInputs(row.data)"
1254
- ></ng-container>
1255
- }
1256
- </div>
1257
- </ng-template>
1258
- <ng-template #footer></ng-template>
1259
- `,
1260
- providers: [
1261
- AXPLayoutBuilderService,
1262
- { provide: AXDataTableColumnComponent, useExisting: AXPWidgetColumnRendererComponent },
1263
- ],
1264
- changeDetection: ChangeDetectionStrategy.OnPush,
1265
- inputs: ['caption'],
1266
- standalone: false,
1267
- }]
1268
- }], propDecorators: { customExpandIcon: [{
1269
- type: Input
1270
- }], customCollapseIcon: [{
1271
- type: Input
1272
- }], customWidth: [{
1273
- type: Input
1274
- }], node: [{
1275
- type: Input,
1276
- args: [{ required: true }]
1277
- }], footerTemplate: [{
1278
- type: Input
1279
- }], _contentFooterTemplate: [{
1280
- type: ViewChild,
1281
- args: ['footer']
1282
- }], expandHandler: [{
1283
- type: Input
1284
- }], cellTemplate: [{
1285
- type: Input
1286
- }], _contentCellTemplate: [{
1287
- type: ViewChild,
1288
- args: ['cell']
1289
- }], headerTemplate: [{
1290
- type: Input
1291
- }], _contentHeaderTemplate: [{
1292
- type: ViewChild,
1293
- args: ['header']
1294
- }] } });
1295
-
1296
- class AXPWidgetContainerComponent {
1297
- set context(value) {
1298
- this.contextService.set(value);
1069
+ //#endregion
1070
+ //#region ---- Widget Builder Implementation ----
1071
+ /**
1072
+ * Widget Builder - Single Responsibility Principle
1073
+ * Handles individual widget configuration and building
1074
+ */
1075
+ class WidgetBuilder {
1076
+ constructor(name) {
1077
+ this.widgetState = {
1078
+ type: 'widget',
1079
+ options: {},
1080
+ };
1081
+ this.inheritanceContext = {};
1082
+ if (name) {
1083
+ this.widgetState.name = name;
1084
+ }
1299
1085
  }
1300
- set functions(v) {
1301
- this.builderService.setFunctions(v);
1086
+ type(type) {
1087
+ this.widgetState.type = type;
1088
+ return this;
1302
1089
  }
1303
- constructor() {
1304
- this.contextService = inject(AXPLayoutBuilderContextStore);
1305
- this.builderService = inject(AXPLayoutBuilderService);
1306
- this.onContextChanged = new EventEmitter();
1307
- this.status = computed(() => {
1308
- return this.builderService.status();
1309
- }, ...(ngDevMode ? [{ debugName: "status" }] : []));
1310
- this.isBusy = computed(() => {
1311
- return this.builderService.isBusy();
1312
- }, ...(ngDevMode ? [{ debugName: "isBusy" }] : []));
1313
- effect(() => {
1314
- if (this.contextService.isChanged()) {
1315
- this.onContextChanged.emit(this.contextService.changeEvent());
1316
- }
1317
- });
1090
+ name(name) {
1091
+ this.widgetState.name = name;
1092
+ if (!this.widgetState.path) {
1093
+ this.widgetState.path = name;
1094
+ }
1095
+ return this;
1096
+ }
1097
+ path(path) {
1098
+ this.widgetState.path = path;
1099
+ return this;
1100
+ }
1101
+ options(options) {
1102
+ // Merge options instead of replacing to preserve inherited properties
1103
+ this.widgetState.options = { ...this.widgetState.options, ...options };
1104
+ return this;
1105
+ }
1106
+ layout(value) {
1107
+ if (typeof value === 'number') {
1108
+ this.widgetState.layout = {
1109
+ positions: {
1110
+ sm: { colSpan: 12 },
1111
+ md: { colSpan: 12 },
1112
+ lg: { colSpan: value },
1113
+ xl: { colSpan: value },
1114
+ xxl: { colSpan: value },
1115
+ },
1116
+ };
1117
+ }
1118
+ else {
1119
+ this.widgetState.layout = value;
1120
+ }
1121
+ return this;
1318
1122
  }
1319
- refresh() {
1320
- this.builderService.refresh();
1123
+ mode(mode) {
1124
+ this.widgetState.mode = mode;
1125
+ this.inheritanceContext.mode = mode;
1126
+ return this;
1321
1127
  }
1322
- find(name) {
1323
- return this.builderService.waitForWidget(name);
1128
+ visible(condition) {
1129
+ if (!this.widgetState.options) {
1130
+ this.widgetState.options = {};
1131
+ }
1132
+ this.widgetState.options['visible'] = condition;
1133
+ this.inheritanceContext.visible = condition;
1134
+ return this;
1324
1135
  }
1325
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPWidgetContainerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1326
- 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 }); }
1327
- }
1328
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPWidgetContainerComponent, decorators: [{
1329
- type: Component,
1330
- args: [{
1331
- selector: 'axp-widgets-container',
1332
- template: `<ng-content></ng-content>`,
1333
- changeDetection: ChangeDetectionStrategy.OnPush,
1334
- host: { style: 'display: contents;' },
1335
- providers: [AXPLayoutBuilderService, AXPLayoutBuilderContextStore],
1336
- standalone: false,
1337
- }]
1338
- }], ctorParameters: () => [], propDecorators: { onContextChanged: [{
1339
- type: Output
1340
- }], context: [{
1341
- type: Input
1342
- }], functions: [{
1343
- type: Input
1344
- }] } });
1345
-
1346
- class AXPWidgetPlaceholderComponent {
1347
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPWidgetPlaceholderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1348
- 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>
1349
- <ax-skeleton class="ax-w-full ax-h-10 ax-rounded-md"></ax-skeleton>
1350
- </div>`, isInline: true, dependencies: [{ kind: "ngmodule", type: AXSkeletonModule }, { kind: "component", type: i1$1.AXSkeletonComponent, selector: "ax-skeleton", inputs: ["animated"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1351
- }
1352
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPWidgetPlaceholderComponent, decorators: [{
1353
- type: Component,
1354
- args: [{
1355
- selector: 'axp-widget-placeholder',
1356
- template: `<div>
1357
- <ax-skeleton class="ax-w-full ax-h-10 ax-rounded-md"></ax-skeleton>
1358
- </div>`,
1359
- changeDetection: ChangeDetectionStrategy.OnPush,
1360
- imports: [AXSkeletonModule],
1361
- standalone: true,
1362
- }]
1363
- }] });
1364
-
1365
- class AXPWidgetRendererDirective {
1366
- //#endregion
1367
- constructor() {
1368
- this.parentNode = input(...(ngDevMode ? [undefined, { debugName: "parentNode" }] : []));
1369
- this.index = input(...(ngDevMode ? [undefined, { debugName: "index" }] : []));
1370
- this.mode = input.required(...(ngDevMode ? [{ debugName: "mode" }] : []));
1371
- this.node = input.required(...(ngDevMode ? [{ debugName: "node" }] : []));
1372
- this._options = signal({}, ...(ngDevMode ? [{ debugName: "_options" }] : []));
1373
- this.options = this._options.asReadonly();
1374
- this.onOptionsChanged = output();
1375
- this.onValueChanged = output();
1376
- //#region ---- Properties ----
1377
- this.mergedOptions = signal({}, ...(ngDevMode ? [{ debugName: "mergedOptions" }] : []));
1378
- this.injector = inject(Injector);
1379
- this.builderService = inject(AXPLayoutBuilderService);
1380
- this.contextService = inject(AXPLayoutBuilderContextStore);
1381
- this.widgetRegistery = inject(AXPWidgetRegistryService);
1382
- this.unsubscriber = inject(AXUnsubscriber);
1383
- this.translateService = inject(AXTranslationService);
1384
- this.widgetService = inject(AXPWidgetRegistryService);
1385
- this.expressionEvaluator = inject(AXPExpressionEvaluatorService);
1386
- this.viewContainerRef = inject(ViewContainerRef);
1387
- this.isLoading = signal(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
1388
- this.expressionEvaluators = new Map();
1389
- this.renderTimeoutId = null;
1390
- this.hasInitialRender = false;
1391
- this.onContextChanged = new Subject();
1392
- effect(async () => {
1393
- const changed = this.contextService.changeEvent();
1394
- // Don't trigger re-render during initial setup
1395
- if (!this.hasInitialRender) {
1396
- return;
1397
- }
1398
- if ((await this.updateOptionsBasedOnContext()) > 0) {
1399
- this.applyOptions();
1400
- }
1401
- if (this.checkFormulaForUpdate(this.node().formula, changed.path)) {
1402
- await this.updateValueBasedOnFormula();
1403
- }
1404
- //
1405
- if (changed.path) {
1406
- this.onContextChanged.next({ path: changed.path });
1407
- }
1408
- });
1409
- this.builderService.onRefresh.pipe(this.unsubscriber.takeUntilDestroy).subscribe(async () => {
1410
- if ((await this.updateOptionsBasedOnContext()) > 0) {
1411
- this.applyOptions();
1412
- }
1413
- });
1136
+ disabled(condition) {
1137
+ if (!this.widgetState.options) {
1138
+ this.widgetState.options = {};
1139
+ }
1140
+ this.widgetState.options['disabled'] = condition;
1141
+ this.inheritanceContext.disabled = condition;
1142
+ return this;
1414
1143
  }
1415
- // Detect input changes
1416
- ngOnChanges(changes) {
1417
- if (changes['mode'] || changes['node']) {
1418
- // Check if either 'mode' or 'node' has changed
1419
- this.rerenderComponent();
1420
- }
1421
- }
1422
- rerenderComponent() {
1423
- // Clear any pending render operation to prevent double rendering
1424
- if (this.renderTimeoutId) {
1425
- clearTimeout(this.renderTimeoutId);
1426
- this.renderTimeoutId = null;
1427
- }
1428
- // Reset loading state to allow re-rendering
1429
- this.isLoading.set(false);
1430
- // Schedule the component loading
1431
- this.renderTimeoutId = setTimeout(async () => {
1432
- await this.loadComponent();
1433
- this.renderTimeoutId = null;
1434
- });
1144
+ readonly(condition) {
1145
+ if (!this.widgetState.options) {
1146
+ this.widgetState.options = {};
1147
+ }
1148
+ this.widgetState.options['readonly'] = condition;
1149
+ this.inheritanceContext.readonly = condition;
1150
+ return this;
1435
1151
  }
1436
- ngOnDestroy() {
1437
- if (this.renderTimeoutId) {
1438
- clearTimeout(this.renderTimeoutId);
1152
+ direction(direction) {
1153
+ if (!this.widgetState.options) {
1154
+ this.widgetState.options = {};
1439
1155
  }
1440
- if (this.componentRef) {
1441
- this.componentRef.destroy();
1156
+ this.widgetState.options['direction'] = direction;
1157
+ this.inheritanceContext.direction = direction;
1158
+ return this;
1159
+ }
1160
+ // Inheritance context methods
1161
+ withInheritanceContext(context) {
1162
+ this.inheritanceContext = mergeInheritanceContext(context);
1163
+ // Apply inherited properties to widget state
1164
+ const resolved = resolveInheritedProperties(context, this.inheritanceContext);
1165
+ // Always apply inherited properties (remove the conditions that check if already set)
1166
+ // This allows properties to be updated when inheritance context changes
1167
+ if (resolved.mode) {
1168
+ this.widgetState.mode = resolved.mode;
1442
1169
  }
1443
- }
1444
- async loadComponent() {
1445
- if (this.isLoading()) {
1446
- return;
1170
+ if (!this.widgetState.options)
1171
+ this.widgetState.options = {};
1172
+ if (resolved.disabled !== undefined) {
1173
+ this.widgetState.options['disabled'] = resolved.disabled;
1447
1174
  }
1448
- this.isLoading.set(true);
1449
- try {
1450
- // Destroy the existing component if it exists
1451
- if (this.componentRef) {
1452
- this.componentRef.destroy();
1453
- }
1454
- this.viewContainerRef.clear();
1455
- //
1456
- const widget = this.widgetRegistery.resolve(this.node().type);
1457
- //
1458
- const propertiesToProcess = [
1459
- ...(widget?.properties ?? []),
1460
- ...(widget?.components[this.mode()]?.properties ?? []),
1461
- ]?.filter((c) => c.schema.defaultValue != null);
1462
- // Process default values (evaluate expressions if needed)
1463
- const props = {};
1464
- for (const property of propertiesToProcess) {
1465
- const defaultValue = property.schema.defaultValue;
1466
- if (typeof defaultValue === 'string' && this.expressionEvaluator.isExpression(defaultValue)) {
1467
- // Evaluate expression for default value
1468
- try {
1469
- const evaluatedValue = await this.evaluateExpression(defaultValue);
1470
- props[property.name] = evaluatedValue;
1471
- }
1472
- catch (error) {
1473
- console.error(`Error evaluating default value expression for property ${property.name}:`, error);
1474
- props[property.name] = cloneDeep(defaultValue); // Fallback to original value
1475
- }
1476
- }
1477
- else {
1478
- // Use static default value
1479
- props[property.name] = cloneDeep(defaultValue);
1480
- }
1481
- }
1482
- //
1483
- this.mergedOptions.set(merge(props, widget?.options, this.node().options) || {});
1484
- this.preprocessAndInitialOptions(cloneDeep(this.node().options));
1485
- await this.updateOptionsBasedOnContext();
1486
- //
1487
- this._options.update((val) => ({ ...val, ...this.mergedOptions() }));
1488
- // Evaluate default value
1489
- let defaultValue = this.node().defaultValue;
1490
- if (defaultValue && this.expressionEvaluator.isExpression(defaultValue)) {
1491
- defaultValue = await this.evaluateExpression(defaultValue);
1492
- }
1493
- //
1494
- const tokenValue = {
1495
- node: this.node(),
1496
- defaultValue: defaultValue,
1497
- options: this.mergedOptions(),
1498
- config: widget,
1499
- };
1500
- const token = Injector.create({
1501
- parent: this.injector,
1502
- providers: [
1503
- {
1504
- provide: AXP_WIDGET_TOKEN,
1505
- useValue: tokenValue,
1506
- },
1507
- ],
1508
- });
1509
- //
1510
- const loadingRef = this.viewContainerRef.createComponent(AXPWidgetPlaceholderComponent);
1511
- //
1512
- const com = await widget?.components[this.mode()]?.component();
1513
- if (!com) {
1514
- console.error(`${this.node().type} widget component not found with mode: ${this.mode()}`);
1515
- return;
1516
- }
1517
- this.componentRef = this.viewContainerRef.createComponent(com, { injector: token });
1518
- this.instance = this.componentRef.instance;
1519
- this.instance.setStatus(AXPWidgetStatus.Rendering);
1520
- this.instance.parent = this.parentNode();
1521
- this.instance.index = this.index();
1522
- this.instance.mode = this.mode();
1523
- this.instance.setStatus(AXPWidgetStatus.Rendered);
1524
- this.instance?.onOptionsChanged?.pipe(this.unsubscriber.takeUntilDestroy).subscribe((c) => {
1525
- this.onOptionsChanged.emit({ sender: this, widget: c.sender });
1526
- });
1527
- this.instance?.onValueChanged?.pipe(this.unsubscriber.takeUntilDestroy).subscribe((c) => {
1528
- this.onValueChanged.emit({ sender: this, widget: c.sender });
1529
- });
1530
- await this.updateValueBasedOnFormula();
1531
- await this.assignTriggers();
1532
- //
1533
- loadingRef.destroy();
1534
- // Mark that initial render is complete
1535
- this.hasInitialRender = true;
1536
- }
1537
- catch (error) {
1538
- console.error('Error loading component:', error);
1539
- }
1540
- finally {
1541
- this.isLoading.set(false);
1542
- }
1543
- }
1544
- applyOptions() {
1545
- if (!this.instance)
1546
- return;
1547
- this._options.update((val) => ({ ...val, ...this.mergedOptions() }));
1548
- this.instance.setOptions(this.mergedOptions());
1549
- }
1550
- checkFormulaForUpdate(formula, path) {
1551
- if (formula) {
1552
- const regex = /context\.eval\('([^']+)'\)/g;
1553
- const matches = formula.match(regex);
1554
- const nodes = matches ? matches.map((match) => match.match(/'([^']+)'/)[1]) : [];
1555
- return nodes.includes(path);
1556
- }
1557
- else
1558
- return false;
1175
+ if (resolved.readonly !== undefined) {
1176
+ this.widgetState.options['readonly'] = resolved.readonly;
1177
+ }
1178
+ if (resolved.direction !== undefined) {
1179
+ this.widgetState.options['direction'] = resolved.direction;
1180
+ }
1181
+ if (resolved.visible !== undefined) {
1182
+ this.widgetState.options['visible'] = resolved.visible;
1183
+ }
1184
+ return this;
1559
1185
  }
1560
- preprocessAndInitialOptions(obj, pathPrefix = '') {
1561
- if (!obj) {
1562
- return;
1563
- }
1564
- Object.entries(obj).forEach(([key, value]) => {
1565
- const currentPath = pathPrefix ? `${pathPrefix}.${key}` : key;
1566
- // CRITICAL FIX: Skip trigger actions during options processing
1567
- //
1568
- // PROBLEM: Trigger actions were being evaluated immediately during widget setup/options processing,
1569
- // causing them to execute before the actual trigger event occurred. This meant triggers would fire
1570
- // during initialization instead of when the specified context path actually changed.
1571
- //
1572
- // ROOT CAUSE: The expression evaluator was processing trigger action expressions (like console.log
1573
- // or widget.setValue calls) as part of the normal options preprocessing, treating them as dynamic
1574
- // expressions that needed immediate evaluation.
1575
- //
1576
- // SOLUTION: Detect when we're processing trigger actions and store them as static values without
1577
- // expression evaluation. This ensures trigger actions are only evaluated when the trigger's
1578
- // subscription callback actually fires (when the event condition is met).
1579
- //
1580
- // RESULT: Triggers now only execute when their event filters pass (e.g., when the specified
1581
- // context path changes), not during widget initialization.
1582
- if (currentPath.includes('triggers') && (key === 'action' || currentPath.endsWith('.action'))) {
1583
- // Apply static values directly without expression evaluation
1584
- this.mergedOptions.update((currentOptions) => {
1585
- return set(currentOptions, currentPath, value);
1586
- });
1587
- return;
1588
- }
1589
- if (typeof value === 'string' && this.expressionEvaluator.isExpression(value)) {
1590
- // Cache dynamic expression for later evaluation
1591
- this.expressionEvaluators.set(currentPath, () => this.evaluateExpression(value));
1592
- }
1593
- else if (typeof value === 'object' &&
1594
- value !== null &&
1595
- (value.constructor === Object || Array.isArray(value))) {
1596
- // Recursively handle nested objects
1597
- this.preprocessAndInitialOptions(value, currentPath);
1598
- }
1599
- else {
1600
- // Apply static values directly
1601
- this.mergedOptions.update((currentOptions) => {
1602
- return set(currentOptions, currentPath, value);
1603
- });
1604
- }
1186
+ getInheritanceContext() {
1187
+ return { ...this.inheritanceContext };
1188
+ }
1189
+ build() {
1190
+ return {
1191
+ name: this.widgetState.name,
1192
+ type: this.widgetState.type,
1193
+ options: this.widgetState.options,
1194
+ mode: this.widgetState.mode,
1195
+ path: this.widgetState.path,
1196
+ defaultValue: this.widgetState.defaultValue,
1197
+ };
1198
+ }
1199
+ }
1200
+ //#region ---- Action Builder Implementation ----
1201
+ class ActionBuilder {
1202
+ constructor(dialogBuilder) {
1203
+ this.dialogBuilder = dialogBuilder;
1204
+ }
1205
+ cancel(text) {
1206
+ if (!this.dialogBuilder['dialogState'].actions.footer.suffix) {
1207
+ this.dialogBuilder['dialogState'].actions.footer.suffix = [];
1208
+ }
1209
+ this.dialogBuilder['dialogState'].actions.footer.suffix.push({
1210
+ title: text || '@general:actions.cancel.title',
1211
+ icon: 'fa-times',
1212
+ color: 'default',
1213
+ command: { name: 'cancel' },
1605
1214
  });
1215
+ return this;
1606
1216
  }
1607
- async updateOptionsBasedOnContext() {
1608
- const updatePromises = Array.from(this.expressionEvaluators).map(async ([path, evaluator]) => {
1609
- const newValue = await evaluator();
1610
- return { path, newValue };
1217
+ submit(text) {
1218
+ if (!this.dialogBuilder['dialogState'].actions.footer.suffix) {
1219
+ this.dialogBuilder['dialogState'].actions.footer.suffix = [];
1220
+ }
1221
+ this.dialogBuilder['dialogState'].actions.footer.suffix.push({
1222
+ title: text || '@general:actions.submit.title',
1223
+ icon: 'fa-check',
1224
+ color: 'primary',
1225
+ command: { name: 'submit', options: { validate: true } },
1611
1226
  });
1612
- // Wait for all evaluators to complete
1613
- const updates = await Promise.all(updatePromises);
1614
- // Apply updates to mergedOptions
1615
- if (updates.length > 0) {
1616
- this.mergedOptions.update((o) => {
1617
- const updatedOptions = { ...o };
1618
- updates.forEach(({ path, newValue }) => {
1619
- // Set the new value in the updatedOptions object by path
1620
- set(updatedOptions, path, newValue); // Assuming 'set' can handle paths like 'property.subproperty'
1621
- });
1622
- return updatedOptions;
1623
- });
1227
+ return this;
1228
+ }
1229
+ custom(action) {
1230
+ if (!this.dialogBuilder['dialogState'].actions.footer.suffix) {
1231
+ this.dialogBuilder['dialogState'].actions.footer.suffix = [];
1624
1232
  }
1625
- return updates.length;
1233
+ this.dialogBuilder['dialogState'].actions.footer.suffix.push(action);
1234
+ return this;
1235
+ }
1236
+ }
1237
+
1238
+ class AXPLayoutConversionService {
1239
+ constructor() {
1240
+ //#region ---- Caching ----
1241
+ this.widgetTreeCache = new Map();
1242
+ this.formDefinitionCache = new Map();
1626
1243
  }
1627
- async updateValueBasedOnFormula() {
1628
- if (this.node().formula) {
1629
- const value = await this.evaluateExpression(this.node().formula);
1630
- this.instance.setValue(value);
1244
+ //#endregion
1245
+ //#region ---- Public Methods ----
1246
+ /**
1247
+ * Convert AXPDynamicFormDefinition to AXPWidgetNode tree structure
1248
+ * Groups become Fieldset Layouts with Form Field widgets as children
1249
+ * Fields become Form Field widgets with Editor widgets as children
1250
+ */
1251
+ convertFormDefinition(formDefinition) {
1252
+ // Create cache key based on form definition content
1253
+ const cacheKey = this.createFormDefinitionCacheKey(formDefinition);
1254
+ // Check cache first
1255
+ if (this.widgetTreeCache.has(cacheKey)) {
1256
+ return this.widgetTreeCache.get(cacheKey);
1631
1257
  }
1258
+ // Generate widget tree
1259
+ const widgetTree = {
1260
+ type: 'grid-layout',
1261
+ name: 'dynamic-form-container',
1262
+ options: {
1263
+ title: 'Dynamic Form',
1264
+ grid: {
1265
+ default: {
1266
+ columns: 1,
1267
+ gap: '1rem'
1268
+ }
1269
+ }
1270
+ },
1271
+ children: formDefinition.groups.map(group => this.createGroupAsFieldsetWidget(group))
1272
+ };
1273
+ // Cache the result
1274
+ this.widgetTreeCache.set(cacheKey, widgetTree);
1275
+ return widgetTree;
1632
1276
  }
1633
- async evaluateExpression(templateExpression) {
1634
- try {
1635
- const scope = this.buildExpressionScope();
1636
- return await this.expressionEvaluator.evaluate(templateExpression, scope);
1277
+ /**
1278
+ * Convert AXPWidgetNode tree back to AXPDynamicFormDefinition
1279
+ * Parses Fieldset Layouts back to Groups
1280
+ * Parses Form Field widgets back to Fields
1281
+ */
1282
+ convertWidgetTreeToFormDefinition(widgetTree) {
1283
+ // Create cache key based on widget tree content
1284
+ const cacheKey = this.createWidgetTreeCacheKey(widgetTree);
1285
+ // Check cache first
1286
+ if (this.formDefinitionCache.has(cacheKey)) {
1287
+ return this.formDefinitionCache.get(cacheKey);
1288
+ }
1289
+ // Parse widget tree
1290
+ const groups = [];
1291
+ if (widgetTree.children) {
1292
+ widgetTree.children.forEach(child => {
1293
+ if (child.type === 'fieldset-layout') {
1294
+ const group = this.extractGroupFromFieldset(child);
1295
+ groups.push(group);
1296
+ }
1297
+ });
1637
1298
  }
1638
- catch (error) {
1639
- console.error('Error evaluating expression:', error);
1299
+ const formDefinition = { groups };
1300
+ // Cache the result
1301
+ this.formDefinitionCache.set(cacheKey, formDefinition);
1302
+ return formDefinition;
1303
+ }
1304
+ /**
1305
+ * Validate that a widget tree represents a valid dynamic form structure
1306
+ */
1307
+ validateFormWidgetTree(widgetTree) {
1308
+ if (!widgetTree || widgetTree.type !== 'grid-layout') {
1640
1309
  return false;
1641
1310
  }
1311
+ if (!widgetTree.children || widgetTree.children.length === 0) {
1312
+ return true; // Empty form is valid
1313
+ }
1314
+ // Check that all children are fieldset-layout widgets
1315
+ return widgetTree.children.every(child => child.type === 'fieldset-layout' &&
1316
+ child.children &&
1317
+ child.children.every(formField => formField.type === 'form-field' &&
1318
+ formField.children &&
1319
+ formField.children.length > 0));
1642
1320
  }
1643
- buildExpressionScope() {
1644
- return {
1645
- context: this.getContextScope(),
1646
- events: this.getEventScope(),
1647
- widget: this.getWidgetScope(),
1648
- methods: this.getFunctionScope(),
1649
- vars: this.getVariablesScope(),
1650
- };
1321
+ /**
1322
+ * Clear all caches
1323
+ */
1324
+ clearCaches() {
1325
+ this.widgetTreeCache.clear();
1326
+ this.formDefinitionCache.clear();
1651
1327
  }
1652
- getContextScope() {
1328
+ /**
1329
+ * Get cache statistics
1330
+ */
1331
+ getCacheStats() {
1653
1332
  return {
1654
- eval: (path) => {
1655
- //TODO: Handle array index
1656
- const fullPath = path.startsWith('>') ? `${this.instance?.parentPath()}.${path.substring(1)}` : path;
1657
- const value = this.contextService.getValue(fullPath);
1658
- return value;
1659
- },
1660
- set: (path, value) => {
1661
- this.contextService.update(path, value);
1662
- },
1663
- data: () => {
1664
- return this.contextService.data();
1665
- },
1666
- isDirty: () => {
1667
- return this.contextService.isDirty();
1668
- },
1333
+ widgetTreeCacheSize: this.widgetTreeCache.size,
1334
+ formDefinitionCacheSize: this.formDefinitionCache.size
1669
1335
  };
1670
1336
  }
1671
- getEventScope() {
1337
+ //#endregion
1338
+ //#region ---- Private Methods ----
1339
+ /**
1340
+ * Convert a single group to Fieldset widget structure
1341
+ */
1342
+ createGroupAsFieldsetWidget(group) {
1343
+ // Determine columns count from layout or default to 1
1344
+ const columnsCount = 1;
1672
1345
  return {
1673
- context: (path) => {
1674
- return this.onContextChanged.pipe(filter((c) => {
1675
- // If no path filter specified, pass all events
1676
- if (path == null || path === '') {
1677
- return true;
1678
- }
1679
- // Ensure c.path exists
1680
- if (!c.path) {
1681
- return false;
1682
- }
1683
- // Pattern: "prefix*" - matches paths that start with prefix
1684
- if (path.endsWith('*')) {
1685
- const prefix = path.substring(0, path.length - 1);
1686
- return c.path.startsWith(prefix);
1687
- }
1688
- // Pattern: "*suffix" - matches paths that end with suffix
1689
- else if (path.startsWith('*')) {
1690
- const suffix = path.substring(1);
1691
- return c.path.endsWith(suffix);
1692
- }
1693
- // Exact match
1694
- else {
1695
- return c.path === path;
1696
- }
1697
- }));
1346
+ type: 'fieldset-layout',
1347
+ name: group.name,
1348
+ options: {
1349
+ title: group.title,
1350
+ description: group.description,
1351
+ cols: columnsCount
1698
1352
  },
1699
- from: (event) => get(this.instance.api(), event),
1353
+ children: this.createFieldWidgets(group.parameters, columnsCount)
1700
1354
  };
1701
1355
  }
1702
- getWidgetScope() {
1356
+ /**
1357
+ * Convert fields to Form Field widgets
1358
+ */
1359
+ createFieldWidgets(fields, columnsCount) {
1360
+ return fields.map(field => this.createFormFieldWidget(field));
1361
+ }
1362
+ /**
1363
+ * Convert a single field to Form Field widget with editor as child
1364
+ */
1365
+ createFormFieldWidget(field) {
1703
1366
  return {
1704
- call: (name, ...args) => {
1705
- this.instance.call(name, ...args);
1706
- },
1707
- setValue: (value) => {
1708
- this.instance.setValue(value);
1709
- },
1710
- clear: () => {
1711
- this.instance.setValue(undefined);
1712
- },
1713
- refresh: () => {
1714
- const refresh = this.instance?.['refresh'];
1715
- if (refresh && typeof refresh === 'function') {
1716
- refresh.bind(this.instance)();
1717
- }
1718
- },
1719
- output: (name) => {
1720
- this.instance.output(name);
1721
- },
1722
- find: (id) => {
1723
- return this.builderService.getWidget(id);
1367
+ type: 'form-field',
1368
+ name: field.path,
1369
+ options: {
1370
+ label: field.title,
1371
+ description: field.description,
1372
+ showLabel: true
1724
1373
  },
1374
+ children: [field.widget] // The editor widget becomes a child of form-field
1725
1375
  };
1726
1376
  }
1727
- getFunctionScope() {
1728
- const scope = {
1729
- sum: (values) => {
1730
- return sum(values);
1731
- },
1732
- translate: (text, options) => {
1733
- return this.translateService.translateAsync(text, options);
1734
- },
1735
- widgetInfo: (name) => {
1736
- return this.widgetService.resolve(name);
1737
- },
1377
+ /**
1378
+ * Extract group information from Fieldset Layout widget
1379
+ */
1380
+ extractGroupFromFieldset(fieldsetNode) {
1381
+ const columnsCount = fieldsetNode.options?.['cols'] || 1;
1382
+ // Extract fields directly from fieldset children
1383
+ const fields = [];
1384
+ if (fieldsetNode.children) {
1385
+ fieldsetNode.children.forEach(formField => {
1386
+ if (formField.type === 'form-field' && formField.children && formField.children.length > 0) {
1387
+ const field = this.extractFieldFromFormWidget(formField);
1388
+ if (field) {
1389
+ fields.push(field);
1390
+ }
1391
+ }
1392
+ });
1393
+ }
1394
+ return {
1395
+ name: fieldsetNode.name || `group-${Date.now()}`,
1396
+ title: fieldsetNode.options?.['title'],
1397
+ description: fieldsetNode.options?.['description'],
1398
+ parameters: fields,
1738
1399
  };
1739
- // Add custom functions from builder service
1740
- Object.entries(this.builderService.functions).forEach(([key, fn]) => {
1741
- scope[key] = (...args) => {
1742
- return fn(...args);
1743
- };
1744
- });
1745
- return scope;
1746
1400
  }
1747
- getVariablesScope() {
1401
+ /**
1402
+ * Extract field information from Form Field widget
1403
+ */
1404
+ extractFieldFromFormWidget(formFieldNode) {
1405
+ if (!formFieldNode.children || formFieldNode.children.length === 0) {
1406
+ return null;
1407
+ }
1408
+ const editorWidget = formFieldNode.children[0];
1748
1409
  return {
1749
- eval: (path) => get(this.builderService.variables, path),
1410
+ path: formFieldNode.name || editorWidget.name || `field-${Date.now()}`,
1411
+ title: formFieldNode.options?.['label'],
1412
+ description: formFieldNode.options?.['description'],
1413
+ widget: editorWidget,
1414
+ mode: formFieldNode.mode
1750
1415
  };
1751
1416
  }
1752
- async assignTriggers() {
1753
- const node = this.node();
1754
- // Check multiple possible locations for triggers
1755
- const triggers = node.triggers || node.options?.['triggers'] || this.mergedOptions()?.triggers;
1756
- if (!triggers) {
1757
- return;
1758
- }
1759
- for (const trigger of triggers) {
1760
- try {
1761
- const event = await this.evaluateTrigger(trigger.event);
1762
- if (event) {
1763
- event.pipe(this.unsubscriber.takeUntilDestroy).subscribe(() => {
1764
- const exec = async (action) => {
1765
- if (!isEmpty(action) && isString(action)) {
1766
- await this.evaluateAction(action);
1767
- }
1768
- };
1769
- const actions = Array.isArray(trigger.action) ? trigger.action : [trigger.action];
1770
- actions.forEach(async (a) => await exec(a));
1771
- });
1772
- }
1773
- }
1774
- catch (error) {
1775
- console.error('Error assigning trigger:', error);
1776
- }
1417
+ /**
1418
+ * Create cache key for form definition
1419
+ */
1420
+ createFormDefinitionCacheKey(formDefinition) {
1421
+ // Create a hash-like key instead of full JSON string
1422
+ const keyParts = [];
1423
+ keyParts.push(`groups:${formDefinition.groups.length}`);
1424
+ formDefinition.groups.forEach((group, groupIndex) => {
1425
+ keyParts.push(`g${groupIndex}:${group.name}:${group.parameters.length}`);
1426
+ group.parameters.forEach((param, paramIndex) => {
1427
+ keyParts.push(`p${groupIndex}.${paramIndex}:${param.path}:${param.widget.type}`);
1428
+ });
1429
+ });
1430
+ if (formDefinition.mode) {
1431
+ keyParts.push(`mode:${formDefinition.mode}`);
1777
1432
  }
1433
+ // Join with delimiter and create a shorter hash
1434
+ const keyString = keyParts.join('|');
1435
+ // If still too long, create a simple hash
1436
+ if (keyString.length > 100) {
1437
+ return this.createSimpleHash(keyString);
1438
+ }
1439
+ return keyString;
1778
1440
  }
1779
- async evaluateTrigger(templateExpression) {
1780
- try {
1781
- const scope = this.buildExpressionScope();
1782
- const result = await this.expressionEvaluator.evaluate(templateExpression, scope);
1783
- // Check if result is an Observable
1784
- if (result && typeof result.subscribe === 'function') {
1785
- return result;
1786
- }
1787
- else {
1788
- return null;
1789
- }
1441
+ /**
1442
+ * Create cache key for widget tree
1443
+ */
1444
+ createWidgetTreeCacheKey(widgetTree) {
1445
+ // Create a hash-like key instead of full JSON string
1446
+ const keyParts = [];
1447
+ keyParts.push(`type:${widgetTree.type}`);
1448
+ if (widgetTree.name) {
1449
+ keyParts.push(`name:${widgetTree.name}`);
1790
1450
  }
1791
- catch (error) {
1792
- console.error('Error evaluating trigger expression:', error);
1793
- return null;
1451
+ if (widgetTree.children) {
1452
+ keyParts.push(`children:${widgetTree.children.length}`);
1453
+ widgetTree.children.forEach((child, index) => {
1454
+ keyParts.push(`c${index}:${child.type}`);
1455
+ if (child.children) {
1456
+ keyParts.push(`cc${index}:${child.children.length}`);
1457
+ child.children.forEach((grandChild, gIndex) => {
1458
+ keyParts.push(`gc${index}.${gIndex}:${grandChild.type}`);
1459
+ if (grandChild.children) {
1460
+ keyParts.push(`gcc${index}.${gIndex}:${grandChild.children.length}`);
1461
+ }
1462
+ });
1463
+ }
1464
+ });
1794
1465
  }
1795
- }
1796
- async evaluateAction(templateExpression) {
1797
- try {
1798
- const scope = this.buildExpressionScope();
1799
- await this.expressionEvaluator.evaluate(templateExpression, scope);
1466
+ // Join with delimiter and create a shorter hash
1467
+ const keyString = keyParts.join('|');
1468
+ // If still too long, create a simple hash
1469
+ if (keyString.length > 100) {
1470
+ return this.createSimpleHash(keyString);
1800
1471
  }
1801
- catch (error) {
1802
- console.error('Error evaluating action expression:', templateExpression, error);
1472
+ return keyString;
1473
+ }
1474
+ /**
1475
+ * Create a simple hash from a string
1476
+ */
1477
+ createSimpleHash(str) {
1478
+ let hash = 0;
1479
+ if (str.length === 0)
1480
+ return hash.toString();
1481
+ for (let i = 0; i < str.length; i++) {
1482
+ const char = str.charCodeAt(i);
1483
+ hash = ((hash << 5) - hash) + char;
1484
+ hash = hash & hash; // Convert to 32-bit integer
1803
1485
  }
1486
+ return Math.abs(hash).toString(36); // Convert to base36 for shorter string
1804
1487
  }
1805
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPWidgetRendererDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
1806
- 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: [
1807
- {
1808
- provide: AXUnsubscriber,
1809
- },
1810
- ], exportAs: ["widgetRenderer"], usesOnChanges: true, ngImport: i0 }); }
1488
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXPLayoutConversionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1489
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXPLayoutConversionService, providedIn: 'root' }); }
1811
1490
  }
1812
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPWidgetRendererDirective, decorators: [{
1813
- type: Directive,
1491
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXPLayoutConversionService, decorators: [{
1492
+ type: Injectable,
1814
1493
  args: [{
1815
- selector: '[axp-widget-renderer]',
1816
- exportAs: 'widgetRenderer',
1817
- providers: [
1818
- {
1819
- provide: AXUnsubscriber,
1820
- },
1821
- ],
1822
- standalone: false,
1494
+ providedIn: 'root'
1823
1495
  }]
1824
- }], ctorParameters: () => [] });
1496
+ }] });
1825
1497
 
1826
- const COMPONENTS = [AXPWidgetContainerComponent, AXPWidgetColumnRendererComponent, AXPWidgetRendererDirective];
1827
- class AXPLayoutBuilderModule {
1828
- static forRoot(config) {
1829
- return {
1830
- ngModule: AXPLayoutBuilderModule,
1831
- providers: [
1832
- {
1833
- provide: 'AXPLayoutBuilderModuleFactory',
1834
- useFactory: (registry) => async () => {
1835
- await Promise.all(config?.widgets?.map((w) => Promise.resolve(registry.register(w))) || []);
1836
- await Promise.all(config?.extendedWidgets?.map((ew) => Promise.resolve(registry.extend(ew.parentName, ew.widget))) || []);
1837
- },
1838
- deps: [AXPWidgetRegistryService],
1839
- multi: true,
1840
- },
1841
- ],
1498
+ class AXPLayoutRendererComponent {
1499
+ constructor() {
1500
+ this.evaluatorService = inject(AXPExpressionEvaluatorService);
1501
+ this.conversionService = inject(AXPLayoutConversionService);
1502
+ /**
1503
+ * Tracks the latest scheduled evaluation to ensure last-write-wins for async evaluate
1504
+ */
1505
+ this.evaluationRunId = 0;
1506
+ /**
1507
+ * RxJS subjects for context management
1508
+ */
1509
+ this.contextUpdateSubject = new Subject();
1510
+ this.contextChangeSubject = new Subject();
1511
+ /**
1512
+ * Cache for expression evaluation results
1513
+ */
1514
+ this.expressionCache = new Map();
1515
+ /**
1516
+ * Cache for widget tree comparisons
1517
+ */
1518
+ this.widgetTreeCache = new Map();
1519
+ /**
1520
+ * Last layout hash for change detection
1521
+ */
1522
+ this.lastLayoutHash = '';
1523
+ //#region ---- Inputs ----
1524
+ /**
1525
+ * Form definition containing groups and fields OR widget tree
1526
+ */
1527
+ this.layout = input.required(...(ngDevMode ? [{ debugName: "layout" }] : []));
1528
+ /**
1529
+ * Form context/model data
1530
+ */
1531
+ this.context = model({}, ...(ngDevMode ? [{ debugName: "context" }] : []));
1532
+ /**
1533
+ * Form appearance and density styling (normal, compact, spacious)
1534
+ */
1535
+ this.look = input('fieldset', ...(ngDevMode ? [{ debugName: "look" }] : []));
1536
+ /**
1537
+ * Default form mode. Can be overridden by section/group and field.
1538
+ */
1539
+ this.mode = input('edit', ...(ngDevMode ? [{ debugName: "mode" }] : []));
1540
+ //#endregion
1541
+ //#region ---- Widget Tree Conversion ----
1542
+ this.widgetTree = signal(null, ...(ngDevMode ? [{ debugName: "widgetTree" }] : []));
1543
+ /**
1544
+ * Convert and evaluate data when inputs change (optimized with RxJS)
1545
+ */
1546
+ this.conversionEffect = effect(() => {
1547
+ const inputData = this.layout();
1548
+ const ctx = this.internalContext();
1549
+ const look = this.look();
1550
+ const runId = ++this.evaluationRunId;
1551
+ // Generate layout hash for change detection
1552
+ const layoutHash = this.generateLayoutHash(inputData, look);
1553
+ // Skip if layout hasn't changed
1554
+ if (layoutHash === this.lastLayoutHash && this.widgetTree()) {
1555
+ return;
1556
+ }
1557
+ this.lastLayoutHash = layoutHash;
1558
+ (async () => {
1559
+ // First evaluate expressions if needed
1560
+ const evaluated = await this.expressionEvaluator(inputData, ctx);
1561
+ // Ignore stale results
1562
+ if (runId !== this.evaluationRunId) {
1563
+ return;
1564
+ }
1565
+ // Convert to widget tree (this will also apply the layout look)
1566
+ const tree = evaluated;
1567
+ // Update widget tree
1568
+ const prev = this.widgetTree();
1569
+ if (!isEqual(prev, tree)) {
1570
+ tree.mode = this.mode();
1571
+ this.widgetTree.set(tree);
1572
+ }
1573
+ })();
1574
+ }, ...(ngDevMode ? [{ debugName: "conversionEffect" }] : []));
1575
+ //#endregion
1576
+ //#region ---- Outputs ----
1577
+ /**
1578
+ * Emitted when context change is initiated
1579
+ */
1580
+ this.contextInitiated = output();
1581
+ /**
1582
+ * Emitted when form becomes valid/invalid
1583
+ */
1584
+ this.validityChange = output();
1585
+ //#endregion
1586
+ //#region ---- Properties ----
1587
+ this.form = viewChild(AXFormComponent, ...(ngDevMode ? [{ debugName: "form" }] : []));
1588
+ this.container = viewChild(AXPWidgetContainerComponent, ...(ngDevMode ? [{ debugName: "container" }] : []));
1589
+ /**
1590
+ * Internal context signal for reactivity
1591
+ */
1592
+ this.internalContext = signal({}, ...(ngDevMode ? [{ debugName: "internalContext" }] : []));
1593
+ /**
1594
+ * Initial context for reset functionality
1595
+ */
1596
+ this.initialContext = {};
1597
+ //#endregion
1598
+ //#region ---- Effects ----
1599
+ /**
1600
+ * Effect to sync context changes from external to internal (optimized with RxJS)
1601
+ */
1602
+ this.#contextSyncEffect = effect(() => {
1603
+ const ctx = this.context() ?? {};
1604
+ this.contextUpdateSubject.next(ctx);
1605
+ }, ...(ngDevMode ? [{ debugName: "#contextSyncEffect" }] : []));
1606
+ /**
1607
+ * Effect to handle widget tree status changes
1608
+ */
1609
+ this.#widgetStatusEffect = effect(() => {
1610
+ const widgetTree = this.widgetTree();
1611
+ if (widgetTree) {
1612
+ this.container()?.builderService.setStatus(AXPPageStatus.Rendered);
1613
+ }
1614
+ }, ...(ngDevMode ? [{ debugName: "#widgetStatusEffect" }] : []));
1615
+ }
1616
+ async expressionEvaluator(expression, context) {
1617
+ // Check if it's a form definition that needs conversion
1618
+ if (this.isFormDefinition(expression)) {
1619
+ return this.conversionService.convertFormDefinition(expression);
1620
+ }
1621
+ // Generate cache key using a more efficient method
1622
+ const cacheKey = this.generateCacheKey(expression, context);
1623
+ // Check cache first
1624
+ if (this.expressionCache.has(cacheKey)) {
1625
+ return this.expressionCache.get(cacheKey);
1626
+ }
1627
+ const scope = {
1628
+ context: {
1629
+ eval: (path) => get(context, path),
1630
+ },
1842
1631
  };
1632
+ const result = await this.evaluatorService.evaluate(expression, scope);
1633
+ // Cache result with LRU-like behavior
1634
+ if (this.expressionCache.size > 50) {
1635
+ // Clear half the cache when it gets too large
1636
+ const keysToDelete = Array.from(this.expressionCache.keys()).slice(0, 25);
1637
+ keysToDelete.forEach(key => this.expressionCache.delete(key));
1638
+ }
1639
+ this.expressionCache.set(cacheKey, result);
1640
+ return result;
1641
+ }
1642
+ //#endregion
1643
+ //#region ---- Lifecycle Methods ----
1644
+ ngOnInit() {
1645
+ // Initialize internal context with input context
1646
+ const ctx = this.context() ?? {};
1647
+ this.internalContext.set(ctx);
1648
+ // Store initial context for reset functionality
1649
+ this.initialContext = cloneDeep(ctx);
1650
+ // Setup RxJS streams for context management
1651
+ this.setupContextStreams();
1652
+ }
1653
+ //#endregion
1654
+ //#region ---- Effects ----
1655
+ /**
1656
+ * Effect to sync context changes from external to internal (optimized with RxJS)
1657
+ */
1658
+ #contextSyncEffect;
1659
+ /**
1660
+ * Effect to handle widget tree status changes
1661
+ */
1662
+ #widgetStatusEffect;
1663
+ //#endregion
1664
+ //#region ---- Event Handlers ----
1665
+ /**
1666
+ * Handle context change events from widget container (optimized with RxJS)
1667
+ */
1668
+ handleContextChanged(event) {
1669
+ if (event.state === 'initiated') {
1670
+ this.contextInitiated.emit(event.data);
1671
+ }
1672
+ else {
1673
+ this.contextChangeSubject.next(event.data ?? {});
1674
+ }
1675
+ }
1676
+ //#endregion
1677
+ //#region ---- Public Methods ----
1678
+ /**
1679
+ * Get the form component instance
1680
+ */
1681
+ getForm() {
1682
+ return this.form();
1683
+ }
1684
+ /**
1685
+ * Get the widget container component instance
1686
+ */
1687
+ getContainer() {
1688
+ return this.container();
1689
+ }
1690
+ /**
1691
+ * Get current form context
1692
+ */
1693
+ getContext() {
1694
+ return this.internalContext();
1695
+ }
1696
+ /**
1697
+ * Update form context programmatically
1698
+ */
1699
+ updateContext(context) {
1700
+ this.internalContext.set(context);
1843
1701
  }
1844
- static forChild(config) {
1702
+ /**
1703
+ * Get the current widget tree
1704
+ */
1705
+ getWidgetTree() {
1706
+ return this.widgetTree();
1707
+ }
1708
+ /**
1709
+ * Validate the form
1710
+ */
1711
+ async validate() {
1712
+ const form = this.form();
1713
+ if (form) {
1714
+ const isValid = await form.validate();
1715
+ this.validityChange.emit(isValid.result);
1716
+ return isValid;
1717
+ }
1845
1718
  return {
1846
- ngModule: AXPLayoutBuilderModule,
1847
- providers: [
1848
- {
1849
- provide: 'AXPLayoutBuilderModuleFactory',
1850
- useFactory: (registry) => async () => {
1851
- await Promise.all(config?.widgets?.map((w) => Promise.resolve(registry.register(w))) || []);
1852
- await Promise.all(config?.extendedWidgets?.map((ew) => Promise.resolve(registry.extend(ew.parentName, ew.widget))) || []);
1853
- },
1854
- deps: [AXPWidgetRegistryService],
1855
- multi: true,
1856
- },
1857
- ],
1719
+ result: false,
1720
+ messages: [],
1721
+ rules: [],
1858
1722
  };
1859
1723
  }
1860
1724
  /**
1861
- * @ignore
1725
+ * Clear the form context
1862
1726
  */
1863
- constructor(instances) {
1864
- instances?.forEach((f) => {
1865
- f();
1727
+ clear() {
1728
+ // Clear internal context
1729
+ this.internalContext.set({});
1730
+ // Update the model signal
1731
+ this.context.set({});
1732
+ }
1733
+ /**
1734
+ * Reset the form to its initial state
1735
+ */
1736
+ reset() {
1737
+ // Reset to initial context
1738
+ const resetContext = cloneDeep(this.initialContext);
1739
+ this.internalContext.set(resetContext);
1740
+ // Update the model signal
1741
+ this.context.set(resetContext);
1742
+ }
1743
+ //#endregion
1744
+ //#region ---- RxJS Stream Setup ----
1745
+ /**
1746
+ * Setup RxJS streams for context management
1747
+ */
1748
+ setupContextStreams() {
1749
+ // Debounced context updates from external source
1750
+ this.contextUpdateSubject
1751
+ .pipe(debounceTime(16), // ~60fps
1752
+ distinctUntilChanged((prev, curr) => isEqual(prev, curr)), startWith(this.context() ?? {}))
1753
+ .subscribe(ctx => {
1754
+ this.internalContext.set(ctx);
1866
1755
  });
1756
+ // Debounced context changes from widgets
1757
+ this.contextChangeSubject
1758
+ .pipe(debounceTime(16), // ~60fps
1759
+ distinctUntilChanged((prev, curr) => isEqual(prev, curr)))
1760
+ .subscribe(ctx => {
1761
+ this.internalContext.set(ctx);
1762
+ // Update the model signal directly - it will emit change events automatically
1763
+ this.context.set(this.internalContext());
1764
+ });
1765
+ }
1766
+ //#endregion
1767
+ //#region ---- Type Guards ----
1768
+ /**
1769
+ * Type guard to check if the input is a form definition
1770
+ */
1771
+ isFormDefinition(data) {
1772
+ return data &&
1773
+ typeof data === 'object' &&
1774
+ 'groups' in data &&
1775
+ Array.isArray(data.groups);
1776
+ }
1777
+ //#endregion
1778
+ //#region ---- Utility Methods ----
1779
+ /**
1780
+ * Generate layout hash for change detection (short hash)
1781
+ */
1782
+ generateLayoutHash(layout, look) {
1783
+ if (!layout)
1784
+ return '';
1785
+ // Generate short hash for large layout strings
1786
+ const layoutStr = typeof layout === 'string' ? layout : JSON.stringify(layout);
1787
+ const layoutHash = this.simpleHash(layoutStr);
1788
+ return `${layoutHash}|${look}`;
1789
+ }
1790
+ /**
1791
+ * Generate cache key for expression evaluation (short hash)
1792
+ */
1793
+ generateCacheKey(expression, context) {
1794
+ // Use short hash for better performance
1795
+ const exprStr = typeof expression === 'string' ? expression : JSON.stringify(expression);
1796
+ const exprHash = this.simpleHash(exprStr);
1797
+ const ctxHash = this.generateContextHash(context);
1798
+ return `${exprHash}|${ctxHash}`;
1799
+ }
1800
+ /**
1801
+ * Generate a simple hash for context change detection
1802
+ */
1803
+ generateContextHash(context) {
1804
+ if (!context || typeof context !== 'object') {
1805
+ return String(context);
1806
+ }
1807
+ // Generate short hash for context
1808
+ const keys = Object.keys(context).sort();
1809
+ const contextStr = keys.map(key => `${key}:${context[key]}`).join('|');
1810
+ return this.simpleHash(contextStr);
1867
1811
  }
1868
- 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 }); }
1869
- 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] }); }
1870
- static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPLayoutBuilderModule, imports: [CommonModule, PortalModule, AXSkeletonModule, CommonModule, AXTranslationModule] }); }
1812
+ /**
1813
+ * Simple hash function for generating short keys
1814
+ */
1815
+ simpleHash(str) {
1816
+ let hash = 0;
1817
+ if (str.length === 0)
1818
+ return hash.toString();
1819
+ for (let i = 0; i < str.length; i++) {
1820
+ const char = str.charCodeAt(i);
1821
+ hash = ((hash << 5) - hash) + char;
1822
+ hash = hash & hash; // Convert to 32-bit integer
1823
+ }
1824
+ // Convert to positive hex string
1825
+ return Math.abs(hash).toString(16);
1826
+ }
1827
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXPLayoutRendererComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1828
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.3", type: AXPLayoutRendererComponent, isStandalone: true, selector: "axp-layout-renderer", inputs: { layout: { classPropertyName: "layout", publicName: "layout", isSignal: true, isRequired: true, transformFunction: null }, context: { classPropertyName: "context", publicName: "context", isSignal: true, isRequired: false, transformFunction: null }, look: { classPropertyName: "look", publicName: "look", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { context: "contextChange", contextInitiated: "contextInitiated", validityChange: "validityChange" }, viewQueries: [{ propertyName: "form", first: true, predicate: AXFormComponent, descendants: true, isSignal: true }, { propertyName: "container", first: true, predicate: AXPWidgetContainerComponent, descendants: true, isSignal: true }], ngImport: i0, template: `
1829
+ <ax-form>
1830
+ <axp-widgets-container [context]="internalContext()" (onContextChanged)="handleContextChanged($event)">
1831
+ @if (widgetTree()) {
1832
+ <ng-container axp-widget-renderer [node]="widgetTree()!" [mode]="mode()"></ng-container>
1833
+ }
1834
+ </axp-widgets-container>
1835
+ </ax-form>
1836
+ `, 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 }); }
1837
+ }
1838
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXPLayoutRendererComponent, decorators: [{
1839
+ type: Component,
1840
+ args: [{ selector: 'axp-layout-renderer', standalone: true, imports: [CommonModule, AXPWidgetCoreModule, AXFormModule], changeDetection: ChangeDetectionStrategy.OnPush, template: `
1841
+ <ax-form>
1842
+ <axp-widgets-container [context]="internalContext()" (onContextChanged)="handleContextChanged($event)">
1843
+ @if (widgetTree()) {
1844
+ <ng-container axp-widget-renderer [node]="widgetTree()!" [mode]="mode()"></ng-container>
1845
+ }
1846
+ </axp-widgets-container>
1847
+ </ax-form>
1848
+ `, styles: [":host{display:block;width:100%}\n"] }]
1849
+ }] });
1850
+
1851
+ class LayoutBuilderModule {
1852
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: LayoutBuilderModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
1853
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.3", ngImport: i0, type: LayoutBuilderModule, imports: [CommonModule, AXPLayoutRendererComponent], exports: [AXPLayoutRendererComponent] }); }
1854
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: LayoutBuilderModule, providers: [AXPLayoutBuilderService], imports: [CommonModule, AXPLayoutRendererComponent] }); }
1871
1855
  }
1872
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: AXPLayoutBuilderModule, decorators: [{
1856
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: LayoutBuilderModule, decorators: [{
1873
1857
  type: NgModule,
1874
1858
  args: [{
1875
- imports: [CommonModule, PortalModule, AXSkeletonModule, CommonModule, AXTranslationModule],
1876
- exports: [...COMPONENTS],
1877
- declarations: [...COMPONENTS],
1859
+ imports: [CommonModule, AXPLayoutRendererComponent],
1860
+ providers: [AXPLayoutBuilderService],
1861
+ exports: [AXPLayoutRendererComponent],
1878
1862
  }]
1879
- }], ctorParameters: () => [{ type: undefined, decorators: [{
1880
- type: Optional
1881
- }, {
1882
- type: Inject,
1883
- args: ['AXPLayoutBuilderModuleFactory']
1884
- }] }] });
1863
+ }] });
1885
1864
 
1886
- class AXPPropertyEditorHelper {
1887
- static expandShorthand(values) {
1888
- switch (values.length) {
1889
- case 1:
1890
- return [values[0], values[0], values[0], values[0]];
1891
- case 2:
1892
- return [values[0], values[1], values[0], values[1]];
1893
- case 3:
1894
- return [values[0], values[1], values[2], values[1]];
1895
- case 4:
1896
- return values;
1897
- default:
1898
- throw new Error(`Invalid shorthand value count. Input: ${values}`);
1899
- }
1900
- }
1901
- static condenseShorthand(values) {
1902
- if (values.length !== 4) {
1903
- throw new Error('Expected 4 values for condensation.');
1904
- }
1905
- if (values[0] === values[1] && values[1] === values[2] && values[2] === values[3]) {
1906
- return `${values[0]}`;
1907
- }
1908
- else if (values[0] === values[2] && values[1] === values[3]) {
1909
- return `${values[0]} ${values[1]}`;
1910
- }
1911
- else if (values[1] === values[3]) {
1912
- return `${values[0]} ${values[1]} ${values[2]}`;
1913
- }
1914
- else {
1915
- return `${values[0]} ${values[1]} ${values[2]} ${values[3]}`;
1916
- }
1865
+ //#endregion
1866
+
1867
+ class AXPDialogRendererComponent extends AXBasePageComponent {
1868
+ constructor() {
1869
+ super(...arguments);
1870
+ this.result = new EventEmitter();
1871
+ this.context = signal({}, ...(ngDevMode ? [{ debugName: "context" }] : []));
1872
+ // This will be set by the popup service automatically - same as dynamic-dialog
1873
+ this.callBack = () => { };
1874
+ this.isDialogLoading = signal(false, ...(ngDevMode ? [{ debugName: "isDialogLoading" }] : []));
1917
1875
  }
1918
- static parseSides(input) {
1919
- const values = this.expandShorthand(input.match(/(?:rgb\([^)]+\)|[^ ]+)/g)?.map((value) => value.trim()) || []);
1920
- return { top: values[0], right: values[1], bottom: values[2], left: values[3] };
1876
+ ngOnInit() {
1877
+ // Initialize context with provided context
1878
+ this.context.set(this.config?.context || {});
1921
1879
  }
1922
- static parseSidesWithUnits(input) {
1923
- const values = this.expandShorthand(input.match(/(?:rgb\([^)]+\)|[^ ]+)/g)?.map((value) => value.trim()) || []);
1924
- return {
1925
- top: AXPPropertyEditorHelper.getValueWithUnit(values[0]).value,
1926
- right: AXPPropertyEditorHelper.getValueWithUnit(values[1]).value,
1927
- bottom: AXPPropertyEditorHelper.getValueWithUnit(values[2]).value,
1928
- left: AXPPropertyEditorHelper.getValueWithUnit(values[3]).value,
1929
- };
1880
+ handleContextChanged(event) {
1881
+ this.context.set(event);
1930
1882
  }
1931
- static parseCorners(input) {
1932
- const values = this.expandShorthand(input.split(' ').map((value) => value.trim()));
1933
- return {
1934
- 'top-left': AXPPropertyEditorHelper.getValueWithUnit(values[0]).value,
1935
- 'top-right': AXPPropertyEditorHelper.getValueWithUnit(values[1]).value,
1936
- 'bottom-left': AXPPropertyEditorHelper.getValueWithUnit(values[2]).value,
1937
- 'bottom-right': AXPPropertyEditorHelper.getValueWithUnit(values[3]).value,
1938
- };
1883
+ handleContextInitiated(event) {
1884
+ this.context.set(event);
1939
1885
  }
1940
- static parseSpacingBox(input) {
1941
- return {
1942
- margin: this.parseSidesWithUnits(input.margin),
1943
- padding: this.parseSidesWithUnits(input.padding),
1944
- };
1886
+ visibleFooterPrefixActions() {
1887
+ return this.config?.actions?.footer?.prefix || [];
1945
1888
  }
1946
- static parseBorderBox(input) {
1947
- return {
1948
- width: this.parseSidesWithUnits(input.width),
1949
- radius: this.parseCorners(input.radius),
1950
- color: this.parseSides(input.color),
1951
- style: this.parseSides(input.style),
1952
- };
1889
+ visibleFooterSuffixActions() {
1890
+ return this.config?.actions?.footer?.suffix || [
1891
+ { title: 'Cancel', color: 'secondary', command: { name: 'cancel' } },
1892
+ { title: 'Confirm', color: 'primary', command: { name: 'confirm' } }
1893
+ ];
1953
1894
  }
1954
- static parseSpacingBoxReverse(input, units) {
1955
- const format = (value, unit) => `${value}${unit}`;
1956
- return {
1957
- margin: AXPPropertyEditorHelper.condenseShorthand([
1958
- format(input.margin.top, units.margin.top),
1959
- format(input.margin.right, units.margin.right),
1960
- format(input.margin.bottom, units.margin.bottom),
1961
- format(input.margin.left, units.margin.left),
1962
- ]),
1963
- padding: AXPPropertyEditorHelper.condenseShorthand([
1964
- format(input.padding.top, units.padding.top),
1965
- format(input.padding.right, units.padding.right),
1966
- format(input.padding.bottom, units.padding.bottom),
1967
- format(input.padding.left, units.padding.left),
1968
- ]),
1969
- };
1895
+ isFormLoading() {
1896
+ return this.isDialogLoading();
1970
1897
  }
1971
- static parseBorderBoxReverse(input, units) {
1972
- const format = (value, unit) => `${value}${unit}`;
1973
- return {
1974
- width: AXPPropertyEditorHelper.condenseShorthand([
1975
- format(input.width.top, units.width.top),
1976
- format(input.width.right, units.width.right),
1977
- format(input.width.bottom, units.width.bottom),
1978
- format(input.width.left, units.width.left),
1979
- ]),
1980
- radius: AXPPropertyEditorHelper.condenseShorthand([
1981
- format(input.radius['top-left'], units.radius['top-left']),
1982
- format(input.radius['top-right'], units.radius['top-right']),
1983
- format(input.radius['bottom-right'], units.radius['bottom-right']),
1984
- format(input.radius['bottom-left'], units.radius['bottom-left']),
1985
- ]),
1986
- color: AXPPropertyEditorHelper.condenseShorthand([
1987
- `${input.color.top}${units.color.top}`,
1988
- `${input.color.right}${units.color.right}`,
1989
- `${input.color.bottom}${units.color.bottom}`,
1990
- `${input.color.left}${units.color.left}`,
1991
- ]),
1992
- style: AXPPropertyEditorHelper.condenseShorthand([
1993
- `${input.style.top}${units.style.top}`,
1994
- `${input.style.right}${units.style.right}`,
1995
- `${input.style.bottom}${units.style.bottom}`,
1996
- `${input.style.left}${units.style.left}`,
1997
- ]),
1998
- };
1898
+ isSubmitting() {
1899
+ return this.isDialogLoading();
1999
1900
  }
2000
- static getValueWithUnit(input) {
2001
- if (typeof input === 'number')
2002
- return { value: input, unit: 'px' };
2003
- if (input === 'auto')
2004
- return { value: 0, unit: 'px' };
2005
- const match = input.match(/^([0-9.]+)([a-z%]*)$/i);
2006
- if (!match)
2007
- throw new Error(`Invalid unit format: ${input}`);
2008
- return { value: parseFloat(match[1]), unit: match[2] || '' };
2009
- }
2010
- static getValueFromUnit(value, unit) {
2011
- return unit ? `${value}${unit}` : `${value}`;
2012
- }
2013
- static parseGap(gap) {
2014
- const parts = gap.split(/\s+/);
2015
- const match = parts[0].match(/^(\d+\.?\d*)([a-z%]+)$/);
2016
- if (!match) {
2017
- throw new Error('Invalid gap format');
2018
- }
2019
- const [, xValue, unit] = match;
2020
- let yValue = parseFloat(xValue);
2021
- if (parts.length === 2) {
2022
- const secondMatch = parts[1].match(/^(\d+\.?\d*)[a-z%]+$/);
2023
- if (!secondMatch) {
2024
- throw new Error('Invalid gap format');
2025
- }
2026
- yValue = parseFloat(secondMatch[1]);
2027
- }
2028
- return {
2029
- values: {
2030
- x: parseFloat(xValue),
2031
- y: yValue,
2032
- },
2033
- unit,
1901
+ async executeAction(action) {
1902
+ const actionName = action.command?.name || action.title?.toLowerCase();
1903
+ // Store the action and context - same pattern as dynamic-dialog
1904
+ const result = {
1905
+ context: this.context(),
1906
+ action: actionName
2034
1907
  };
2035
- }
2036
- static parseGridTemplate(gridTemplate) {
2037
- const match = gridTemplate.match(/^repeat\((\d+),\s*(?:1fr|auto|minmax\([^)]*\))\)$/);
2038
- if (!match) {
2039
- throw new Error("Invalid grid template format. Expected 'repeat(N, 1fr|auto|minmax(...))'.");
2040
- }
2041
- return parseInt(match[1], 10);
2042
- }
2043
- static createGridTemplate(repetitionCount) {
2044
- if (repetitionCount <= 0) {
2045
- throw new Error('Repetition count must be a positive integer.');
1908
+ // Store result in component property
1909
+ this.dialogResult = result;
1910
+ // Store in popup data for DialogRef access
1911
+ if (this.data) {
1912
+ this.data.context = result.context;
1913
+ this.data.action = result.action;
2046
1914
  }
2047
- return `repeat(${repetitionCount}, 1fr)`;
1915
+ // Call the callback with DialogRef - same pattern as dynamic-dialog
1916
+ this.callBack({
1917
+ close: (result) => {
1918
+ this.close(result);
1919
+ },
1920
+ context: () => {
1921
+ return this.context();
1922
+ },
1923
+ action: () => {
1924
+ return result.action;
1925
+ },
1926
+ setLoading: (loading) => {
1927
+ this.isDialogLoading.set(loading);
1928
+ }
1929
+ });
2048
1930
  }
2049
- }
2050
- function findNonEmptyBreakpoints(values) {
2051
- const breakpoints = ['default', 'sm', 'md', 'lg', 'xl', '2xl', '3xl'];
2052
- const nonEmptyBreakpoints = [];
2053
- for (const breakpoint of breakpoints) {
2054
- if (values[breakpoint] !== undefined) {
2055
- nonEmptyBreakpoints.push(breakpoint);
1931
+ close(result) {
1932
+ if (result) {
1933
+ this.result.emit(result);
2056
1934
  }
2057
- }
2058
- return nonEmptyBreakpoints;
1935
+ super.close(result);
1936
+ }
1937
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXPDialogRendererComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
1938
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.3", type: AXPDialogRendererComponent, isStandalone: true, selector: "axp-dialog-renderer", inputs: { config: "config" }, outputs: { result: "result" }, usesInheritance: true, ngImport: i0, template: `
1939
+ <div class="ax-p-4">
1940
+ <axp-layout-renderer
1941
+ [layout]="config.definition"
1942
+ [context]="context()"
1943
+ (contextChange)="handleContextChanged($event)"
1944
+ (contextInitiated)="handleContextInitiated($event)"
1945
+ >
1946
+ </axp-layout-renderer>
1947
+ </div>
1948
+
1949
+ <ax-footer>
1950
+ <ax-prefix>
1951
+ <ng-container *ngTemplateOutlet="footerPrefixActions"></ng-container>
1952
+ </ax-prefix>
1953
+ <ax-suffix>
1954
+ <ng-container *ngTemplateOutlet="footerSuffixActions"></ng-container>
1955
+ </ax-suffix>
1956
+ </ax-footer>
1957
+
1958
+ <!-- Footer Prefix Actions -->
1959
+ <ng-template #footerPrefixActions>
1960
+ @for (action of visibleFooterPrefixActions(); track $index) {
1961
+ <ax-button
1962
+ [disabled]="action.disabled || isFormLoading()"
1963
+ [text]="(action.title | translate | async)!"
1964
+ [look]="'outline'"
1965
+ [color]="action.color"
1966
+ (onClick)="executeAction(action)"
1967
+ >
1968
+ @if (isFormLoading() && action.command?.name != 'cancel') {
1969
+ <ax-loading></ax-loading>
1970
+ }
1971
+ <ax-prefix>
1972
+ <i class="{{ action.icon }}"></i>
1973
+ </ax-prefix>
1974
+ </ax-button>
1975
+ }
1976
+ </ng-template>
1977
+
1978
+ <!-- Footer Suffix Actions -->
1979
+ <ng-template #footerSuffixActions>
1980
+ @for (action of visibleFooterSuffixActions(); track $index) {
1981
+ <ax-button
1982
+ [disabled]="action.disabled || isSubmitting()"
1983
+ [text]="(action.title | translate | async)!"
1984
+ [look]="'solid'"
1985
+ [color]="action.color"
1986
+ (onClick)="executeAction(action)"
1987
+ >
1988
+ @if (action.icon) {
1989
+ <ax-prefix>
1990
+ <ax-icon icon="{{ action.icon }}"></ax-icon>
1991
+ </ax-prefix>
1992
+ }
1993
+ </ax-button>
1994
+ }
1995
+ </ng-template>
1996
+ `, 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" }] }); }
2059
1997
  }
1998
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXPDialogRendererComponent, decorators: [{
1999
+ type: Component,
2000
+ args: [{
2001
+ selector: 'axp-dialog-renderer',
2002
+ standalone: true,
2003
+ imports: [CommonModule, AXPLayoutRendererComponent, AXButtonModule, AXDecoratorModule, AXLoadingModule, AXTranslationModule],
2004
+ template: `
2005
+ <div class="ax-p-4">
2006
+ <axp-layout-renderer
2007
+ [layout]="config.definition"
2008
+ [context]="context()"
2009
+ (contextChange)="handleContextChanged($event)"
2010
+ (contextInitiated)="handleContextInitiated($event)"
2011
+ >
2012
+ </axp-layout-renderer>
2013
+ </div>
2014
+
2015
+ <ax-footer>
2016
+ <ax-prefix>
2017
+ <ng-container *ngTemplateOutlet="footerPrefixActions"></ng-container>
2018
+ </ax-prefix>
2019
+ <ax-suffix>
2020
+ <ng-container *ngTemplateOutlet="footerSuffixActions"></ng-container>
2021
+ </ax-suffix>
2022
+ </ax-footer>
2023
+
2024
+ <!-- Footer Prefix Actions -->
2025
+ <ng-template #footerPrefixActions>
2026
+ @for (action of visibleFooterPrefixActions(); track $index) {
2027
+ <ax-button
2028
+ [disabled]="action.disabled || isFormLoading()"
2029
+ [text]="(action.title | translate | async)!"
2030
+ [look]="'outline'"
2031
+ [color]="action.color"
2032
+ (onClick)="executeAction(action)"
2033
+ >
2034
+ @if (isFormLoading() && action.command?.name != 'cancel') {
2035
+ <ax-loading></ax-loading>
2036
+ }
2037
+ <ax-prefix>
2038
+ <i class="{{ action.icon }}"></i>
2039
+ </ax-prefix>
2040
+ </ax-button>
2041
+ }
2042
+ </ng-template>
2060
2043
 
2061
- const AXP_WIDGETS_LAYOUT_CATEGORY = {
2062
- name: 'layout',
2063
- order: 1,
2064
- title: 'Layout',
2065
- };
2066
- const AXP_WIDGETS_EDITOR_CATEGORY = {
2067
- name: 'editor',
2068
- order: 2,
2069
- title: 'Editors',
2070
- };
2071
- const AXP_WIDGETS_ACTION_CATEGORY = {
2072
- name: 'action',
2073
- order: 3,
2074
- title: 'Action',
2075
- };
2076
- const AXP_WIDGETS_ADVANCE_CATEGORY = {
2077
- name: 'advance',
2078
- order: 4,
2079
- title: 'Advance',
2080
- };
2081
- const AXP_WIDGETS_CATEGORIES = [
2082
- AXP_WIDGETS_LAYOUT_CATEGORY,
2083
- AXP_WIDGETS_EDITOR_CATEGORY,
2084
- AXP_WIDGETS_ACTION_CATEGORY,
2085
- AXP_WIDGETS_ADVANCE_CATEGORY,
2086
- ];
2044
+ <!-- Footer Suffix Actions -->
2045
+ <ng-template #footerSuffixActions>
2046
+ @for (action of visibleFooterSuffixActions(); track $index) {
2047
+ <ax-button
2048
+ [disabled]="action.disabled || isSubmitting()"
2049
+ [text]="(action.title | translate | async)!"
2050
+ [look]="'solid'"
2051
+ [color]="action.color"
2052
+ (onClick)="executeAction(action)"
2053
+ >
2054
+ @if (action.icon) {
2055
+ <ax-prefix>
2056
+ <ax-icon icon="{{ action.icon }}"></ax-icon>
2057
+ </ax-prefix>
2058
+ }
2059
+ </ax-button>
2060
+ }
2061
+ </ng-template>
2062
+ `
2063
+ }]
2064
+ }], propDecorators: { config: [{
2065
+ type: Input
2066
+ }], result: [{
2067
+ type: Output
2068
+ }] } });
2087
2069
 
2088
- var AXPWidgetGroupEnum;
2089
- (function (AXPWidgetGroupEnum) {
2090
- AXPWidgetGroupEnum["FormElement"] = "form-element";
2091
- AXPWidgetGroupEnum["DashboardWidget"] = "dashboard-widget";
2092
- AXPWidgetGroupEnum["FormTemplate"] = "form-template";
2093
- AXPWidgetGroupEnum["PropertyEditor"] = "property-editor";
2094
- AXPWidgetGroupEnum["MetaData"] = "meta-data";
2095
- AXPWidgetGroupEnum["SettingWidget"] = "setting-widget";
2096
- AXPWidgetGroupEnum["EntityWidget"] = "entity-widget";
2097
- })(AXPWidgetGroupEnum || (AXPWidgetGroupEnum = {}));
2070
+ var dialogRenderer_component = /*#__PURE__*/Object.freeze({
2071
+ __proto__: null,
2072
+ AXPDialogRendererComponent: AXPDialogRendererComponent
2073
+ });
2098
2074
 
2099
2075
  /**
2100
2076
  * Generated bundle index. Do not edit.
2101
2077
  */
2102
2078
 
2103
- export { AXPBaseWidgetComponent, AXPBlockBaseLayoutWidgetComponent, AXPBoxModelLayoutWidgetComponent, AXPColumnWidgetComponent, AXPDataListWidgetComponent, AXPFlexBaseLayoutWidgetComponent, AXPFlexItemBaseLayoutWidgetComponent, AXPGridBaseLayoutWidgetComponent, AXPGridItemBaseLayoutWidgetComponent, AXPInlineBaseLayoutWidgetComponent, AXPLayoutBaseWidgetComponent, AXPLayoutBuilderContextStore, AXPLayoutBuilderModule, AXPLayoutBuilderService, AXPLayoutContextChangeEvent, AXPLayoutElement, AXPPageStatus, AXPPropertyEditorHelper, 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 };
2079
+ export { AXPDialogRendererComponent, AXPLayoutBuilderService, AXPLayoutConversionService, AXPLayoutRendererComponent, LayoutBuilderModule };
2104
2080
  //# sourceMappingURL=acorex-platform-layout-builder.mjs.map