@acorex/platform 21.0.0-next.8 → 21.0.0-next.83

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 (133) hide show
  1. package/contracts/README.md +3 -0
  2. package/fesm2022/acorex-platform-auth.mjs +201 -81
  3. package/fesm2022/acorex-platform-auth.mjs.map +1 -1
  4. package/fesm2022/acorex-platform-common-common-settings.provider-Ytey9uhY.mjs +177 -0
  5. package/fesm2022/acorex-platform-common-common-settings.provider-Ytey9uhY.mjs.map +1 -0
  6. package/fesm2022/acorex-platform-common.mjs +4490 -880
  7. package/fesm2022/acorex-platform-common.mjs.map +1 -1
  8. package/fesm2022/acorex-platform-contracts.mjs +38 -0
  9. package/fesm2022/acorex-platform-contracts.mjs.map +1 -0
  10. package/fesm2022/acorex-platform-core.mjs +2612 -667
  11. package/fesm2022/acorex-platform-core.mjs.map +1 -1
  12. package/fesm2022/acorex-platform-domain.mjs +557 -826
  13. package/fesm2022/acorex-platform-domain.mjs.map +1 -1
  14. package/fesm2022/acorex-platform-layout-builder.mjs +1255 -209
  15. package/fesm2022/acorex-platform-layout-builder.mjs.map +1 -1
  16. package/fesm2022/acorex-platform-layout-components-binding-expression-editor-popup.component-CXEdvDTf.mjs +121 -0
  17. package/fesm2022/acorex-platform-layout-components-binding-expression-editor-popup.component-CXEdvDTf.mjs.map +1 -0
  18. package/fesm2022/acorex-platform-layout-components.mjs +6486 -1915
  19. package/fesm2022/acorex-platform-layout-components.mjs.map +1 -1
  20. package/fesm2022/acorex-platform-layout-designer.mjs +612 -287
  21. package/fesm2022/acorex-platform-layout-designer.mjs.map +1 -1
  22. package/fesm2022/acorex-platform-layout-entity-attachments-page.component-CwG6n_Yb.mjs +376 -0
  23. package/fesm2022/acorex-platform-layout-entity-attachments-page.component-CwG6n_Yb.mjs.map +1 -0
  24. package/fesm2022/acorex-platform-layout-entity-file-list-popup.component--IeMZLIr.mjs +100 -0
  25. package/fesm2022/acorex-platform-layout-entity-file-list-popup.component--IeMZLIr.mjs.map +1 -0
  26. package/fesm2022/acorex-platform-layout-entity.mjs +22502 -9964
  27. package/fesm2022/acorex-platform-layout-entity.mjs.map +1 -1
  28. package/fesm2022/acorex-platform-layout-views.mjs +1360 -338
  29. package/fesm2022/acorex-platform-layout-views.mjs.map +1 -1
  30. package/fesm2022/acorex-platform-layout-widget-core.mjs +2226 -524
  31. package/fesm2022/acorex-platform-layout-widget-core.mjs.map +1 -1
  32. package/fesm2022/{acorex-platform-layout-widgets-button-widget-designer.component-C3VoBb_b.mjs → acorex-platform-layout-widgets-button-widget-designer.component-Dy7jF-oD.mjs} +10 -10
  33. package/fesm2022/acorex-platform-layout-widgets-button-widget-designer.component-Dy7jF-oD.mjs.map +1 -0
  34. package/fesm2022/{acorex-platform-layout-widgets-image-preview.popup-V31OpYah.mjs → acorex-platform-layout-widgets-image-preview.popup-C_EPAvCU.mjs} +6 -7
  35. package/fesm2022/acorex-platform-layout-widgets-image-preview.popup-C_EPAvCU.mjs.map +1 -0
  36. package/fesm2022/{acorex-platform-layout-widgets-page-widget-designer.component-BtZMBxYp.mjs → acorex-platform-layout-widgets-page-widget-designer.component-D10yO28c.mjs} +12 -12
  37. package/fesm2022/acorex-platform-layout-widgets-page-widget-designer.component-D10yO28c.mjs.map +1 -0
  38. package/fesm2022/acorex-platform-layout-widgets-repeater-widget-column.component-J0zcGKBX.mjs +116 -0
  39. package/fesm2022/acorex-platform-layout-widgets-repeater-widget-column.component-J0zcGKBX.mjs.map +1 -0
  40. package/fesm2022/{acorex-platform-layout-widgets-tabular-data-edit-popup.component-Ck7-wpT2.mjs → acorex-platform-layout-widgets-tabular-data-edit-popup.component-DjpZU6gz.mjs} +6 -6
  41. package/fesm2022/acorex-platform-layout-widgets-tabular-data-edit-popup.component-DjpZU6gz.mjs.map +1 -0
  42. package/fesm2022/{acorex-platform-layout-widgets-tabular-data-view-popup.component-y8vjUiVs.mjs → acorex-platform-layout-widgets-tabular-data-view-popup.component-gX-3Kx9I.mjs} +5 -5
  43. package/fesm2022/acorex-platform-layout-widgets-tabular-data-view-popup.component-gX-3Kx9I.mjs.map +1 -0
  44. package/fesm2022/{acorex-platform-layout-widgets-text-block-widget-designer.component-Df1BFkSa.mjs → acorex-platform-layout-widgets-text-block-widget-designer.component-Vo4fWHtX.mjs} +6 -6
  45. package/fesm2022/acorex-platform-layout-widgets-text-block-widget-designer.component-Vo4fWHtX.mjs.map +1 -0
  46. package/fesm2022/acorex-platform-layout-widgets.mjs +18742 -16715
  47. package/fesm2022/acorex-platform-layout-widgets.mjs.map +1 -1
  48. package/fesm2022/acorex-platform-native.mjs +8 -7
  49. package/fesm2022/acorex-platform-native.mjs.map +1 -1
  50. package/fesm2022/acorex-platform-runtime.mjs +391 -166
  51. package/fesm2022/acorex-platform-runtime.mjs.map +1 -1
  52. package/fesm2022/acorex-platform-themes-default-entity-master-create-view.component-CWLfNqV0.mjs +160 -0
  53. package/fesm2022/acorex-platform-themes-default-entity-master-create-view.component-CWLfNqV0.mjs.map +1 -0
  54. package/fesm2022/acorex-platform-themes-default-entity-master-modify-view.component-C7cT82K2.mjs +120 -0
  55. package/fesm2022/acorex-platform-themes-default-entity-master-modify-view.component-C7cT82K2.mjs.map +1 -0
  56. package/fesm2022/{acorex-platform-themes-default-entity-master-single-view.component-eMBby9k4.mjs → acorex-platform-themes-default-entity-master-single-view.component-Br9p5aXT.mjs} +21 -28
  57. package/fesm2022/acorex-platform-themes-default-entity-master-single-view.component-Br9p5aXT.mjs.map +1 -0
  58. package/fesm2022/acorex-platform-themes-default-error-401.component-B1nsdpTY.mjs +48 -0
  59. package/fesm2022/acorex-platform-themes-default-error-401.component-B1nsdpTY.mjs.map +1 -0
  60. package/fesm2022/acorex-platform-themes-default-error-404.component-D4UvRe8u.mjs +42 -0
  61. package/fesm2022/acorex-platform-themes-default-error-404.component-D4UvRe8u.mjs.map +1 -0
  62. package/fesm2022/acorex-platform-themes-default-error-offline.component-DR6G8gPC.mjs +19 -0
  63. package/fesm2022/acorex-platform-themes-default-error-offline.component-DR6G8gPC.mjs.map +1 -0
  64. package/fesm2022/acorex-platform-themes-default.mjs +2337 -94
  65. package/fesm2022/acorex-platform-themes-default.mjs.map +1 -1
  66. package/fesm2022/{acorex-platform-themes-shared-icon-chooser-column.component-C0EpfU2k.mjs → acorex-platform-themes-shared-icon-chooser-column.component-CqkWJYdv.mjs} +6 -6
  67. package/fesm2022/acorex-platform-themes-shared-icon-chooser-column.component-CqkWJYdv.mjs.map +1 -0
  68. package/fesm2022/{acorex-platform-themes-shared-icon-chooser-view.component-9W52W6Nu.mjs → acorex-platform-themes-shared-icon-chooser-view.component-BOTuLdWN.mjs} +6 -6
  69. package/fesm2022/acorex-platform-themes-shared-icon-chooser-view.component-BOTuLdWN.mjs.map +1 -0
  70. package/fesm2022/{acorex-platform-themes-shared-settings.provider-DSs1o1M6.mjs → acorex-platform-themes-shared-settings.provider-BjuzSe0T.mjs} +52 -33
  71. package/fesm2022/acorex-platform-themes-shared-settings.provider-BjuzSe0T.mjs.map +1 -0
  72. package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-column.component-D566Kdvy.mjs +94 -0
  73. package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-column.component-D566Kdvy.mjs.map +1 -0
  74. package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-view.component-D7-rCGl7.mjs +86 -0
  75. package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-view.component-D7-rCGl7.mjs.map +1 -0
  76. package/fesm2022/acorex-platform-themes-shared.mjs +793 -612
  77. package/fesm2022/acorex-platform-themes-shared.mjs.map +1 -1
  78. package/fesm2022/acorex-platform-workflow.mjs +997 -274
  79. package/fesm2022/acorex-platform-workflow.mjs.map +1 -1
  80. package/fesm2022/acorex-platform.mjs.map +1 -1
  81. package/package.json +41 -35
  82. package/{auth/index.d.ts → types/acorex-platform-auth.d.ts} +40 -19
  83. package/types/acorex-platform-common.d.ts +3673 -0
  84. package/types/acorex-platform-contracts.d.ts +940 -0
  85. package/{core/index.d.ts → types/acorex-platform-core.d.ts} +1029 -170
  86. package/{domain/index.d.ts → types/acorex-platform-domain.d.ts} +744 -412
  87. package/{layout/builder/index.d.ts → types/acorex-platform-layout-builder.d.ts} +262 -55
  88. package/types/acorex-platform-layout-components.d.ts +3329 -0
  89. package/{layout/designer/index.d.ts → types/acorex-platform-layout-designer.d.ts} +96 -18
  90. package/types/acorex-platform-layout-entity.d.ts +4512 -0
  91. package/{layout/views/index.d.ts → types/acorex-platform-layout-views.d.ts} +334 -91
  92. package/{layout/widget-core/index.d.ts → types/acorex-platform-layout-widget-core.d.ts} +438 -162
  93. package/{layout/widgets/index.d.ts → types/acorex-platform-layout-widgets.d.ts} +1152 -507
  94. package/{native/index.d.ts → types/acorex-platform-native.d.ts} +0 -7
  95. package/types/acorex-platform-runtime.d.ts +571 -0
  96. package/{themes/default/index.d.ts → types/acorex-platform-themes-default.d.ts} +268 -11
  97. package/{themes/shared/index.d.ts → types/acorex-platform-themes-shared.d.ts} +30 -2
  98. package/{workflow/index.d.ts → types/acorex-platform-workflow.d.ts} +436 -748
  99. package/common/index.d.ts +0 -2470
  100. package/fesm2022/acorex-platform-common-common-settings.provider-zhqNP3xb.mjs +0 -71
  101. package/fesm2022/acorex-platform-common-common-settings.provider-zhqNP3xb.mjs.map +0 -1
  102. package/fesm2022/acorex-platform-layout-widgets-button-widget-designer.component-C3VoBb_b.mjs.map +0 -1
  103. package/fesm2022/acorex-platform-layout-widgets-file-list-popup.component-CxrsI6Hn.mjs +0 -135
  104. package/fesm2022/acorex-platform-layout-widgets-file-list-popup.component-CxrsI6Hn.mjs.map +0 -1
  105. package/fesm2022/acorex-platform-layout-widgets-image-preview.popup-V31OpYah.mjs.map +0 -1
  106. package/fesm2022/acorex-platform-layout-widgets-page-widget-designer.component-BtZMBxYp.mjs.map +0 -1
  107. package/fesm2022/acorex-platform-layout-widgets-tabular-data-edit-popup.component-Ck7-wpT2.mjs.map +0 -1
  108. package/fesm2022/acorex-platform-layout-widgets-tabular-data-view-popup.component-y8vjUiVs.mjs.map +0 -1
  109. package/fesm2022/acorex-platform-layout-widgets-text-block-widget-designer.component-Df1BFkSa.mjs.map +0 -1
  110. package/fesm2022/acorex-platform-themes-default-entity-master-create-view.component-VIGuU5M4.mjs +0 -157
  111. package/fesm2022/acorex-platform-themes-default-entity-master-create-view.component-VIGuU5M4.mjs.map +0 -1
  112. package/fesm2022/acorex-platform-themes-default-entity-master-list-view.component-D3qZa5fM.mjs +0 -1542
  113. package/fesm2022/acorex-platform-themes-default-entity-master-list-view.component-D3qZa5fM.mjs.map +0 -1
  114. package/fesm2022/acorex-platform-themes-default-entity-master-modify-view.component-Ua3ZA5hk.mjs +0 -101
  115. package/fesm2022/acorex-platform-themes-default-entity-master-modify-view.component-Ua3ZA5hk.mjs.map +0 -1
  116. package/fesm2022/acorex-platform-themes-default-entity-master-single-view.component-eMBby9k4.mjs.map +0 -1
  117. package/fesm2022/acorex-platform-themes-default-error-401.component-cfREo88K.mjs +0 -31
  118. package/fesm2022/acorex-platform-themes-default-error-401.component-cfREo88K.mjs.map +0 -1
  119. package/fesm2022/acorex-platform-themes-default-error-404.component-CdCV5ZoA.mjs +0 -25
  120. package/fesm2022/acorex-platform-themes-default-error-404.component-CdCV5ZoA.mjs.map +0 -1
  121. package/fesm2022/acorex-platform-themes-default-error-offline.component-E7SzBcAt.mjs +0 -19
  122. package/fesm2022/acorex-platform-themes-default-error-offline.component-E7SzBcAt.mjs.map +0 -1
  123. package/fesm2022/acorex-platform-themes-shared-icon-chooser-column.component-C0EpfU2k.mjs.map +0 -1
  124. package/fesm2022/acorex-platform-themes-shared-icon-chooser-view.component-9W52W6Nu.mjs.map +0 -1
  125. package/fesm2022/acorex-platform-themes-shared-settings.provider-DSs1o1M6.mjs.map +0 -1
  126. package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-column.component-Dz0cylyQ.mjs +0 -65
  127. package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-column.component-Dz0cylyQ.mjs.map +0 -1
  128. package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-view.component-DY0JtT1v.mjs +0 -64
  129. package/fesm2022/acorex-platform-themes-shared-theme-color-chooser-view.component-DY0JtT1v.mjs.map +0 -1
  130. package/layout/components/index.d.ts +0 -1670
  131. package/layout/entity/index.d.ts +0 -2294
  132. package/runtime/index.d.ts +0 -307
  133. /package/{index.d.ts → types/acorex-platform.d.ts} +0 -0
@@ -1,11 +1,29 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, inject, Injectable, computed, input, effect, Directive, EventEmitter, HostListener, Output, ViewContainerRef, ElementRef, signal, output, DestroyRef, Renderer2, untracked, Injector, runInInjectionContext, provideAppInitializer, NgModule, Pipe } from '@angular/core';
2
+ import { InjectionToken, inject, Injectable, Directive, computed, Injector, ChangeDetectionStrategy, Component, input, ElementRef, ViewContainerRef, signal, effect, runInInjectionContext, Optional, Inject, NgModule, EventEmitter, HostListener, Output, provideAppInitializer, Pipe } from '@angular/core';
3
+ import { AXTranslationService, resolveMultiLanguageString } from '@acorex/core/translation';
4
+ import { AXDataSource } from '@acorex/cdk/common';
5
+ import { isPlainObject, get, set, isNil, isEmpty, isArray, merge, isObjectLike, transform, isEqual, differenceWith, union, cloneDeep, has, sortBy, isUndefined, endsWith, startsWith, includes, lte, gte, lt, gt, orderBy } from 'lodash-es';
3
6
  import { signalStore, withState, withComputed, withMethods, patchState } from '@ngrx/signals';
4
- import { get, isPlainObject, set, isArray, merge, isNil, isObjectLike, transform, isEmpty, isEqual, differenceWith, union, cloneDeep, isUndefined, endsWith, startsWith, includes, lte, gte, lt, gt, orderBy } from 'lodash-es';
7
+ import * as i1 from '@acorex/components/skeleton';
8
+ import { AXSkeletonModule } from '@acorex/components/skeleton';
5
9
  import { Subject, interval, fromEvent } from 'rxjs';
10
+ import { toSignal } from '@angular/core/rxjs-interop';
11
+ import { DOCUMENT } from '@angular/common';
12
+ import { AXLocaleService } from '@acorex/core/locale';
6
13
  import { AXCalendarService } from '@acorex/core/date-time';
7
14
  import { startWith, map, debounceTime } from 'rxjs/operators';
8
15
 
16
+ //#region ---- Datasource keys ----
17
+ /** Platform core datasource registry names. */
18
+ const AXPPlatformCoreDataSourceKeys = {
19
+ ModuleManifests: 'platform-module-manifests',
20
+ CatalogScopes: 'platform-catalog-scopes',
21
+ };
22
+ //#endregion
23
+
24
+ //#region ---- Contracts barrel ----
25
+ //#endregion
26
+
9
27
  const AXP_ACTIVITY_LOG_PROVIDER = new InjectionToken('AXP_ACTIVITY_LOGS_PROVIDER');
10
28
  class AXPActivityLogService {
11
29
  constructor() {
@@ -17,10 +35,10 @@ class AXPActivityLogService {
17
35
  async getHistoryByIds(refId, refType, ids) {
18
36
  return (await Promise.all(this.providers?.map((p) => p.getHistoryByIds(refId, refType, ids)) ?? [])).flat();
19
37
  }
20
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPActivityLogService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
21
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPActivityLogService, providedIn: 'root' }); }
38
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPActivityLogService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
39
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPActivityLogService, providedIn: 'root' }); }
22
40
  }
23
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPActivityLogService, decorators: [{
41
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPActivityLogService, decorators: [{
24
42
  type: Injectable,
25
43
  args: [{ providedIn: 'root' }]
26
44
  }] });
@@ -28,6 +46,180 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImpo
28
46
  class AXPActivityLogProvider {
29
47
  }
30
48
 
49
+ //#region ---- Catalog scope definition ----
50
+ //#endregion
51
+
52
+ //#region ---- Provider contract ----
53
+ const AXP_CATALOG_SCOPE_DEFINITION_PROVIDER = new InjectionToken('AXP_CATALOG_SCOPE_DEFINITION_PROVIDER');
54
+ //#endregion
55
+
56
+ //#region ---- Imports ----
57
+ //#endregion
58
+ //#region ---- Aggregator service ----
59
+ /**
60
+ * Collects {@link AXPCatalogScopeDefinition} entries from all registered providers.
61
+ */
62
+ class AXPCatalogScopeDefinitionProviderService {
63
+ constructor() {
64
+ //#region ---- Services & Dependencies ----
65
+ this.providers = inject(AXP_CATALOG_SCOPE_DEFINITION_PROVIDER, { optional: true });
66
+ this.translation = inject(AXTranslationService);
67
+ }
68
+ //#endregion
69
+ //#region ---- Public API ----
70
+ async items() {
71
+ const merged = [];
72
+ const seen = new Set();
73
+ if (!Array.isArray(this.providers)) {
74
+ return merged;
75
+ }
76
+ for (const raw of this.providers) {
77
+ const provider = await Promise.resolve(raw);
78
+ if (!provider || typeof provider.items !== 'function') {
79
+ continue;
80
+ }
81
+ for (const item of await provider.items()) {
82
+ const name = item.name?.trim();
83
+ if (!name) {
84
+ continue;
85
+ }
86
+ if (seen.has(name)) {
87
+ throw new Error(`Duplicate catalog scope definition: "${name}"`);
88
+ }
89
+ seen.add(name);
90
+ merged.push(item);
91
+ }
92
+ }
93
+ return merged.sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
94
+ }
95
+ async get(name) {
96
+ const key = name?.trim();
97
+ if (!key) {
98
+ return undefined;
99
+ }
100
+ return (await this.items()).find((d) => d.name === key);
101
+ }
102
+ /**
103
+ * Scopes applicable to the given entity ref (`Module.Entity`).
104
+ */
105
+ async byTarget(entityRef) {
106
+ const target = entityRef?.trim();
107
+ if (!target) {
108
+ return [];
109
+ }
110
+ return (await this.items()).filter((d) => d.targets?.some((t) => t === target));
111
+ }
112
+ /**
113
+ * Resolves display title for a scope key (active locale).
114
+ */
115
+ resolveTitle(definition) {
116
+ const locale = this.translation.getActiveLang() ?? 'en-US';
117
+ if (typeof definition.title === 'string') {
118
+ const title = definition.title.trim();
119
+ if (title.startsWith('@')) {
120
+ return this.translation.translateSync(title) || title;
121
+ }
122
+ return title;
123
+ }
124
+ return resolveMultiLanguageString(definition.title, locale) || definition.name;
125
+ }
126
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPCatalogScopeDefinitionProviderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
127
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPCatalogScopeDefinitionProviderService, providedIn: 'root' }); }
128
+ }
129
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPCatalogScopeDefinitionProviderService, decorators: [{
130
+ type: Injectable,
131
+ args: [{ providedIn: 'root' }]
132
+ }] });
133
+
134
+ //#region ---- Imports ----
135
+ //#endregion
136
+ //#region ---- Data source definition ----
137
+ /**
138
+ * Registered catalog scopes for select / selection-list widgets via {@link AXPPlatformCoreDataSourceKeys.CatalogScopes}.
139
+ */
140
+ class AXPCatalogScopeDefinitionsDataSourceDefinition {
141
+ constructor() {
142
+ //#region ---- Services & Dependencies ----
143
+ this.catalogScopeRegistry = inject(AXPCatalogScopeDefinitionProviderService);
144
+ //#endregion
145
+ }
146
+ //#endregion
147
+ //#region ---- Public API ----
148
+ async items() {
149
+ return [
150
+ {
151
+ name: AXPPlatformCoreDataSourceKeys.CatalogScopes,
152
+ title: 'Catalog scopes',
153
+ source: () => new AXDataSource({
154
+ key: 'name',
155
+ pageSize: 500,
156
+ load: async (e) => {
157
+ const all = await this.catalogScopeRegistry.items();
158
+ const localeFilter = this.readTargetFilter(e?.filter);
159
+ const items = all
160
+ .filter((scope) => !localeFilter || scope.targets?.includes(localeFilter))
161
+ .map((scope) => ({
162
+ name: scope.name,
163
+ title: this.catalogScopeRegistry.resolveTitle(scope),
164
+ description: scope.description,
165
+ targets: scope.targets,
166
+ icon: scope.icon,
167
+ cssClass: scope.cssClass,
168
+ }));
169
+ return { items, total: items.length };
170
+ },
171
+ byKey: async (key) => {
172
+ const name = String(key ?? '').trim();
173
+ if (!name) {
174
+ return undefined;
175
+ }
176
+ const scope = await this.catalogScopeRegistry.get(name);
177
+ if (!scope) {
178
+ return undefined;
179
+ }
180
+ return {
181
+ name: scope.name,
182
+ title: this.catalogScopeRegistry.resolveTitle(scope),
183
+ description: scope.description,
184
+ targets: scope.targets,
185
+ icon: scope.icon,
186
+ cssClass: scope.cssClass,
187
+ };
188
+ },
189
+ }),
190
+ columns: [
191
+ { name: 'name', title: 'Name', datatype: 'string', type: 'text-editor' },
192
+ { name: 'title', title: 'Title', datatype: 'string', type: 'text-editor' },
193
+ ],
194
+ filters: [
195
+ {
196
+ field: 'targets',
197
+ title: 'Target entity',
198
+ operator: { type: 'contains' },
199
+ widget: { type: 'text-editor' },
200
+ filterType: { advance: false, inline: false },
201
+ },
202
+ ],
203
+ textField: { name: 'title', title: 'Title' },
204
+ valueField: { name: 'name', title: 'Name' },
205
+ },
206
+ ];
207
+ }
208
+ //#endregion
209
+ //#region ---- Helpers ----
210
+ readTargetFilter(filter) {
211
+ if (!filter || typeof filter !== 'object') {
212
+ return undefined;
213
+ }
214
+ const f = filter;
215
+ if (f.field === 'targets' && typeof f.value === 'string' && f.value.trim()) {
216
+ return f.value.trim();
217
+ }
218
+ return undefined;
219
+ }
220
+ }
221
+ //#endregion
222
+
31
223
  //#region ---- Color Palette Provider ----
32
224
  /**
33
225
  * Abstract class for color palette providers
@@ -170,10 +362,10 @@ class AXPColorPaletteService {
170
362
  const uniquePalettes = allPalettes.filter((palette, index, self) => index === self.findIndex((p) => p.name === palette.name));
171
363
  this.cache = uniquePalettes;
172
364
  }
173
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPColorPaletteService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
174
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPColorPaletteService, providedIn: 'root' }); }
365
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPColorPaletteService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
366
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPColorPaletteService, providedIn: 'root' }); }
175
367
  }
176
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPColorPaletteService, decorators: [{
368
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPColorPaletteService, decorators: [{
177
369
  type: Injectable,
178
370
  args: [{
179
371
  providedIn: 'root',
@@ -288,13 +480,277 @@ class AXPDefaultColorPalettesProvider extends AXPColorPaletteProvider {
288
480
  },
289
481
  ];
290
482
  }
291
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPDefaultColorPalettesProvider, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
292
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPDefaultColorPalettesProvider }); }
483
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPDefaultColorPalettesProvider, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
484
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPDefaultColorPalettesProvider }); }
293
485
  }
294
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPDefaultColorPalettesProvider, decorators: [{
486
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPDefaultColorPalettesProvider, decorators: [{
295
487
  type: Injectable
296
488
  }] });
297
489
 
490
+ //#region ---- Column Width Provider ----
491
+ /**
492
+ * Injection token for column width providers
493
+ * Use this to register multiple column width providers with multi: true
494
+ *
495
+ * @example
496
+ * ```typescript
497
+ * providers: [
498
+ * {
499
+ * provide: AXP_COLUMN_WIDTH_PROVIDER,
500
+ * useValue: {
501
+ * matchers: [
502
+ * { name: 'title', width: '300px', priority: 10 },
503
+ * { widgetType: /text-box/i, width: '200px' }
504
+ * ],
505
+ * priority: 150
506
+ * },
507
+ * multi: true
508
+ * }
509
+ * ]
510
+ * ```
511
+ */
512
+ const AXP_COLUMN_WIDTH_PROVIDER = new InjectionToken('AXP_COLUMN_WIDTH_PROVIDER');
513
+ //#endregion
514
+
515
+ //#region ---- Column Width Service ----
516
+ /**
517
+ * Service for resolving column widths based on registered providers
518
+ * Supports matching by column name (priority) and widget type (fallback)
519
+ * Uses multi-provider pattern for extensibility
520
+ */
521
+ class AXPColumnWidthService {
522
+ constructor() {
523
+ //#region ---- Services & Dependencies ----
524
+ this.providers = inject(AXP_COLUMN_WIDTH_PROVIDER, {
525
+ optional: true,
526
+ }) ?? [];
527
+ }
528
+ //#region ---- Public Methods ----
529
+ /**
530
+ * Resolve width for a column based on registered providers
531
+ * Only returns a width if column doesn't already have one set
532
+ *
533
+ * @param column Column to resolve width for
534
+ * @returns Resolved width string (normalized) or undefined if no match or column already has width
535
+ */
536
+ resolveWidth(column) {
537
+ // If column already has a width set, don't override
538
+ const existingWidth = column.width || column.options?.width;
539
+ if (existingWidth) {
540
+ return undefined;
541
+ }
542
+ // Get widget type from column (support both widget.type and showAs.type)
543
+ const widgetType = column.widget?.type || column.showAs?.type;
544
+ // Collect all matchers from all providers
545
+ const allMatchers = this.collectMatchers();
546
+ // Try name-based matches first (higher priority)
547
+ const nameMatch = this.findNameMatch(column.name, allMatchers);
548
+ if (nameMatch) {
549
+ return this.normalizeWidth(nameMatch.width);
550
+ }
551
+ // Fallback to widget-type matches (case-insensitive)
552
+ if (widgetType) {
553
+ const widgetMatch = this.findWidgetTypeMatch(widgetType, allMatchers);
554
+ if (widgetMatch) {
555
+ return this.normalizeWidth(widgetMatch.width);
556
+ }
557
+ }
558
+ return undefined;
559
+ }
560
+ /**
561
+ * Apply resolved width to a column (mutates the column)
562
+ * Only applies if column doesn't already have a width
563
+ *
564
+ * @param column Column to apply width to
565
+ * @returns true if width was applied, false if column already had width or no match found
566
+ */
567
+ applyWidth(column) {
568
+ const width = this.resolveWidth(column);
569
+ if (!width) {
570
+ return false;
571
+ }
572
+ // Apply to the appropriate property
573
+ if (column.options) {
574
+ column.options.width = width;
575
+ }
576
+ else {
577
+ column.width = width;
578
+ }
579
+ return true;
580
+ }
581
+ //#endregion
582
+ //#region ---- Private Methods ----
583
+ /**
584
+ * Collect all matchers from all providers, sorted by provider priority and matcher priority
585
+ */
586
+ collectMatchers() {
587
+ const allMatchers = [];
588
+ for (const provider of this.providers) {
589
+ const providerPriority = provider.priority ?? 100;
590
+ for (const matcher of provider.matchers) {
591
+ allMatchers.push({
592
+ matcher,
593
+ providerPriority,
594
+ });
595
+ }
596
+ }
597
+ // Sort by provider priority (descending), then by matcher priority (descending)
598
+ allMatchers.sort((a, b) => {
599
+ if (a.providerPriority !== b.providerPriority) {
600
+ return b.providerPriority - a.providerPriority; // Higher priority first
601
+ }
602
+ const priorityA = a.matcher.priority ?? 0;
603
+ const priorityB = b.matcher.priority ?? 0;
604
+ return priorityB - priorityA; // Higher priority first
605
+ });
606
+ return allMatchers;
607
+ }
608
+ /**
609
+ * Find first matching matcher by column name
610
+ * Name matches take precedence over widget type matches
611
+ */
612
+ findNameMatch(columnName, allMatchers) {
613
+ for (const { matcher } of allMatchers) {
614
+ if (!matcher.name) {
615
+ continue; // Skip if no name matcher
616
+ }
617
+ if (typeof matcher.name === 'string') {
618
+ // Exact string match (case-sensitive for names)
619
+ if (matcher.name === columnName) {
620
+ return matcher;
621
+ }
622
+ }
623
+ else if (matcher.name instanceof RegExp) {
624
+ // RegExp match
625
+ if (matcher.name.test(columnName)) {
626
+ return matcher;
627
+ }
628
+ }
629
+ }
630
+ return undefined;
631
+ }
632
+ /**
633
+ * Find first matching matcher by widget type (case-insensitive)
634
+ */
635
+ findWidgetTypeMatch(widgetType, allMatchers) {
636
+ const widgetTypeLower = widgetType.toLowerCase();
637
+ for (const { matcher } of allMatchers) {
638
+ if (!matcher.widgetType) {
639
+ continue; // Skip if no widget type matcher
640
+ }
641
+ if (typeof matcher.widgetType === 'string') {
642
+ // Case-insensitive string match
643
+ if (matcher.widgetType.toLowerCase() === widgetTypeLower) {
644
+ return matcher;
645
+ }
646
+ }
647
+ else if (matcher.widgetType instanceof RegExp) {
648
+ // RegExp match (should already be case-insensitive if needed, but we'll test case-insensitively)
649
+ // Create a new regex with case-insensitive flag if not already present
650
+ let regex = matcher.widgetType;
651
+ if (!regex.flags.includes('i')) {
652
+ regex = new RegExp(regex.source, regex.flags + 'i');
653
+ }
654
+ if (regex.test(widgetType)) {
655
+ return matcher;
656
+ }
657
+ }
658
+ }
659
+ return undefined;
660
+ }
661
+ /**
662
+ * Normalize width value to string format
663
+ * Numbers are converted to pixels (e.g., 150 -> '150px')
664
+ * Strings are preserved as-is (supports any CSS unit)
665
+ */
666
+ normalizeWidth(width) {
667
+ if (typeof width === 'number') {
668
+ return `${width}px`;
669
+ }
670
+ return width;
671
+ }
672
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPColumnWidthService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
673
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPColumnWidthService, providedIn: 'root' }); }
674
+ }
675
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPColumnWidthService, decorators: [{
676
+ type: Injectable,
677
+ args: [{
678
+ providedIn: 'root',
679
+ }]
680
+ }] });
681
+
682
+ //#region ---- Column Width Types ----
683
+ //#endregion
684
+
685
+ //#region ---- Default Column Width Provider ----
686
+ /**
687
+ * Default column width configuration
688
+ * Migrated from DEFAULT_COLUMN_WIDTHS in column-width.middleware.ts
689
+ * Provides sensible defaults for common column names
690
+ */
691
+ const defaultColumnWidthProvider = {
692
+ priority: 100,
693
+ matchers: [
694
+ // Priority: name-based matches (higher priority = more specific)
695
+ { name: 'code', width: '150px', priority: 10 },
696
+ { name: 'name', width: '250px', priority: 10 },
697
+ { name: 'title', width: '250px', priority: 10 },
698
+ { name: 'description', width: '350px', priority: 10 },
699
+ { name: 'note', width: '350px', priority: 10 },
700
+ { name: 'notes', width: '350px', priority: 10 },
701
+ { name: 'content', width: '500px', priority: 10 },
702
+ { name: 'level', width: '100px', priority: 10 },
703
+ { name: 'order', width: '100px', priority: 10 },
704
+ { name: 'phones', width: '300px', priority: 10 },
705
+ { name: 'emails', width: '300px', priority: 10 },
706
+ ],
707
+ };
708
+ //#endregion
709
+
710
+ class AXPComponentSlot {
711
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPComponentSlot, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
712
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: AXPComponentSlot, isStandalone: true, ngImport: i0 }); }
713
+ }
714
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPComponentSlot, decorators: [{
715
+ type: Directive
716
+ }] });
717
+
718
+ //#region ---- Locale map shape ----
719
+ /** Per-locale string map (`{ 'en-US': '...', 'fa-IR': '...' }`). */
720
+ function isLocaleStringMap(value) {
721
+ if (!isPlainObject(value)) {
722
+ return false;
723
+ }
724
+ const entries = Object.values(value);
725
+ if (entries.length === 0) {
726
+ return false;
727
+ }
728
+ return entries.every((entry) => entry === null || entry === undefined || typeof entry === 'string');
729
+ }
730
+ /**
731
+ * Use locale-map read/write when the option is on or live/saved data is already a map.
732
+ * Auto-enables when API data is a map but the editor option was left off.
733
+ */
734
+ function shouldUseLocaleMapShape(localeMapOptionEnabled, current, saved) {
735
+ return localeMapOptionEnabled || isLocaleStringMap(current) || isLocaleStringMap(saved);
736
+ }
737
+ /** Updates one locale entry; preserves sibling locales from current or saved map. */
738
+ function buildLocaleTextMapValue(current, saved, lang, input) {
739
+ const seed = isLocaleStringMap(current)
740
+ ? { ...current }
741
+ : isLocaleStringMap(saved)
742
+ ? { ...saved }
743
+ : {};
744
+ seed[lang] = input ?? '';
745
+ for (const key of Object.keys(seed)) {
746
+ if ((seed[key] ?? '').trim() === '') {
747
+ delete seed[key];
748
+ }
749
+ }
750
+ return Object.keys(seed).length === 0 ? null : seed;
751
+ }
752
+ //#endregion
753
+
298
754
  function extractNestedFieldsWildcard(obj, basePath, fields) {
299
755
  const result = {};
300
756
  if (fields.length === 1 && fields[0] === '*') {
@@ -313,7 +769,41 @@ function setSmart(obj, smartPath, value) {
313
769
  const lastPart = pathParts[pathParts.length - 1];
314
770
  const fieldMatch = lastPart.match(/^\{(.+)\}$/);
315
771
  if (!fieldMatch) {
772
+ // Fix: If value is undefined and we're setting a nested property,
773
+ // check if the parent object exists and if setting this would result in
774
+ // all properties being undefined. If so, set the parent to null instead.
775
+ if (isNil(value) && pathParts.length > 1) {
776
+ const parentPath = pathParts.slice(0, -1).join('.');
777
+ const parentObj = get(obj, parentPath);
778
+ // If parent is already null/undefined, don't recreate the object structure
779
+ if (isNil(parentObj)) {
780
+ return obj;
781
+ }
782
+ // Check if parent is a plain object (not array, not null)
783
+ if (isPlainObject(parentObj)) {
784
+ const existingKeys = Object.keys(parentObj);
785
+ // Check if all existing properties are null/undefined using lodash
786
+ const allNil = isEmpty(existingKeys) || existingKeys.every((key) => isNil(parentObj[key]));
787
+ if (allNil) {
788
+ // Set parent to null instead of creating/keeping an object with all undefined properties
789
+ set(obj, parentPath, null);
790
+ return obj;
791
+ }
792
+ }
793
+ }
316
794
  set(obj, smartPath, value);
795
+ // After setting, check again if parent should be null using lodash
796
+ if (isNil(value) && pathParts.length > 1) {
797
+ const parentPath = pathParts.slice(0, -1).join('.');
798
+ const parentObj = get(obj, parentPath);
799
+ if (isPlainObject(parentObj)) {
800
+ // Use lodash to check if all properties are null/undefined
801
+ const allNil = Object.keys(parentObj).every((key) => isNil(parentObj[key]));
802
+ if (allNil) {
803
+ set(obj, parentPath, null);
804
+ }
805
+ }
806
+ }
317
807
  return obj;
318
808
  }
319
809
  const fields = fieldMatch[1].split(',').map((f) => f.trim());
@@ -522,152 +1012,972 @@ function getDetailedChanges(obj1, obj2) {
522
1012
  function getEnumValues(enumType) {
523
1013
  return Object.entries(enumType).map(([key, value]) => ({ id: value, title: key }));
524
1014
  }
525
-
526
- class AXPContextChangeEvent {
1015
+ //#region ---- Form value equality (dirty / baseline) ----
1016
+ /** Whether a value uses the multi-language text widget shape (string or locale map). */
1017
+ function isMultiLanguageFormShape(value) {
1018
+ if (value === null || value === undefined) {
1019
+ return true;
1020
+ }
1021
+ if (typeof value === 'string') {
1022
+ return true;
1023
+ }
1024
+ return isLocaleStringMap(value);
527
1025
  }
528
- const AXPContextStore = signalStore({
529
- providedIn: 'root',
530
- },
531
- // Initial State
532
- withState(() => ({
533
- data: {}, // Shared context data
534
- state: 'initiated', // Current state
535
- initialSnapshot: {}, // Snapshot of the first initialized state
536
- previousSnapshot: {}, // Snapshot of the previous state
537
- lastChange: {
538
- state: 'initiated',
539
- }, // Last change event
540
- })),
541
- // Computed Signals
542
- withComputed(({ data, state, lastChange, initialSnapshot, previousSnapshot }) => ({
543
- isChanged: computed(() => state() === 'changed'),
544
- isReset: computed(() => state() === 'restored'),
545
- isInitiated: computed(() => state() === 'initiated'),
546
- isEmpty: computed(() => Object.keys(data()).length === 0),
547
- isDirty: computed(() => !isEqual(data(), previousSnapshot())),
548
- snapshot: computed(() => cloneDeep(data())), // Current data snapshot
549
- initial: computed(() => cloneDeep(initialSnapshot())), // Initial snapshot
550
- previous: computed(() => cloneDeep(previousSnapshot())), // Previous snapshot
551
- changeEvent: computed(() => lastChange()), // Reactive last change event
552
- })),
553
- // Methods for State Management
554
- withMethods((store) => ({
555
- // Update a specific value
556
- update(path, value) {
557
- const currentData = cloneDeep(store.data());
558
- const oldValue = getSmart(currentData, path);
559
- // Skip if the value hasn't changed
560
- if (isEqual(oldValue, value)) {
561
- return;
1026
+ /** Drops empty/whitespace locale entries from a multi-language map. */
1027
+ function normalizeLocaleStringMap(value) {
1028
+ const normalized = {};
1029
+ for (const [key, entry] of Object.entries(value)) {
1030
+ if (typeof entry !== 'string') {
1031
+ continue;
562
1032
  }
563
- // Update the value and prepare the change event
564
- const updatedData = setSmart(currentData, path, value);
565
- const changeEvent = {
566
- oldValue,
567
- newValue: value,
568
- path,
569
- state: 'changed',
570
- data: updatedData,
571
- };
572
- // Patch the state
573
- patchState(store, {
574
- previousSnapshot: store.snapshot(), // Save the previous state
575
- data: updatedData,
576
- state: 'changed',
577
- lastChange: changeEvent,
578
- });
579
- },
580
- patch(context) {
581
- const currentData = cloneDeep(store.data());
582
- // Update the value and prepare the change event
583
- const updatedData = { ...currentData, ...context };
584
- const changeEvent = {
585
- state: 'patch',
586
- data: updatedData,
587
- };
588
- // Patch the state
589
- patchState(store, {
590
- previousSnapshot: store.snapshot(), // Save the previous state
591
- data: updatedData,
592
- state: 'changed',
593
- lastChange: changeEvent,
594
- });
595
- },
596
- // Reset to the initial state
597
- reset() {
598
- const initialData = store.initial();
599
- const changeEvent = {
600
- oldValue: cloneDeep(store.data()), // Current data becomes old value
601
- newValue: cloneDeep(initialData), // Reset to the initial state
602
- path: '',
603
- state: 'restored',
604
- data: initialData,
605
- };
606
- patchState(store, {
607
- previousSnapshot: store.snapshot(), // Save the previous state
608
- data: initialData,
609
- state: 'restored',
610
- lastChange: changeEvent,
611
- });
612
- },
613
- // Initialize the state
614
- set(initialData) {
615
- const currentData = store.data();
616
- if (isEqual(currentData, initialData)) {
617
- return; // Skip if the current state matches the initial state
1033
+ const trimmed = entry.trim();
1034
+ if (trimmed !== '') {
1035
+ normalized[key] = trimmed;
618
1036
  }
619
- const changeEvent = {
620
- oldValue: null,
621
- newValue: cloneDeep(initialData),
622
- path: '',
623
- state: 'initiated',
624
- data: initialData,
625
- };
626
- patchState(store, {
627
- initialSnapshot: cloneDeep(initialData), // Save the initial state
628
- previousSnapshot: store.snapshot(), // Save the current state as the previous
629
- data: initialData,
630
- state: 'initiated',
631
- lastChange: changeEvent,
632
- });
633
- },
634
- // Get a specific value
635
- getValue(path) {
636
- return getSmart(store.data(), path);
637
- },
638
- })));
639
-
640
- class AXPDataGenerator {
641
- static uuid() {
642
- return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (char) => {
643
- const random = (Math.random() * 16) | 0;
644
- const value = char === 'x' ? random : (random & 0x3) | 0x8;
645
- return value.toString(16);
646
- });
647
1037
  }
648
- static number(...args) {
649
- let min = 0;
650
- let max = 100;
651
- if (args.length == 1)
652
- max = args[0];
653
- if (args.length == 2) {
654
- min = args[0];
655
- max = args[1];
656
- }
657
- return Math.floor(Math.random() * (max - min + 1)) + min;
1038
+ return Object.keys(normalized).length === 0 ? null : normalized;
1039
+ }
1040
+ /**
1041
+ * Compares multi-language field values (plain string vs locale map) for dirty/baseline checks.
1042
+ * Returns `null` when either side is not a multi-language shape (caller uses generic normalization).
1043
+ */
1044
+ function compareMultiLanguageFormValues(a, b) {
1045
+ if (!isMultiLanguageFormShape(a) || !isMultiLanguageFormShape(b)) {
1046
+ return null;
658
1047
  }
659
- static date(...args) {
660
- let start = new Date(2000, 0, 1);
661
- let end = new Date();
662
- if (args.length === 1) {
663
- start = args[0];
1048
+ const normalizeScalar = (value) => {
1049
+ if (value === null || value === undefined) {
1050
+ return null;
664
1051
  }
665
- else if (args.length === 2) {
666
- start = args[0];
667
- end = args[1];
1052
+ if (typeof value === 'string') {
1053
+ const trimmed = value.trim();
1054
+ return trimmed === '' ? null : trimmed;
668
1055
  }
669
- const startTime = start.getTime();
670
- const endTime = end.getTime();
1056
+ if (isLocaleStringMap(value)) {
1057
+ return normalizeLocaleStringMap(value);
1058
+ }
1059
+ return null;
1060
+ };
1061
+ const left = normalizeScalar(a);
1062
+ const right = normalizeScalar(b);
1063
+ if (left === null && right === null) {
1064
+ return true;
1065
+ }
1066
+ if (left === null || right === null) {
1067
+ return false;
1068
+ }
1069
+ if (typeof left === 'string' && typeof right === 'string') {
1070
+ return left === right;
1071
+ }
1072
+ if (typeof left === 'string' && typeof right === 'object') {
1073
+ const values = Object.values(right);
1074
+ return values.length > 0 && values.every((text) => text === left);
1075
+ }
1076
+ if (typeof right === 'string' && typeof left === 'object') {
1077
+ const values = Object.values(left);
1078
+ return values.length > 0 && values.every((text) => text === right);
1079
+ }
1080
+ if (typeof left !== 'object' || typeof right !== 'object') {
1081
+ return false;
1082
+ }
1083
+ const leftMap = left;
1084
+ const rightMap = right;
1085
+ const leftKeys = Object.keys(leftMap).sort();
1086
+ const rightKeys = Object.keys(rightMap).sort();
1087
+ if (leftKeys.length !== rightKeys.length) {
1088
+ return false;
1089
+ }
1090
+ return leftKeys.every((key) => leftMap[key] === rightMap[key]);
1091
+ }
1092
+ /** Lookup/select filter-mode payload: `{ value, displayText?, operation }`. */
1093
+ function isFilterFormShape(value) {
1094
+ if (!isPlainObject(value)) {
1095
+ return false;
1096
+ }
1097
+ const record = value;
1098
+ return 'value' in record && 'operation' in record;
1099
+ }
1100
+ /**
1101
+ * Compares filter-mode widget values by selection identity; ignores derived `displayText`.
1102
+ */
1103
+ function compareFilterFormValues(a, b) {
1104
+ if (!isFilterFormShape(a) || !isFilterFormShape(b)) {
1105
+ return null;
1106
+ }
1107
+ const left = a;
1108
+ const right = b;
1109
+ if (!isFormValueEqual(left['value'], right['value'])) {
1110
+ return false;
1111
+ }
1112
+ const opLeft = get(left, 'operation.type');
1113
+ const opRight = get(right, 'operation.type');
1114
+ if (opLeft !== undefined || opRight !== undefined) {
1115
+ return opLeft === opRight;
1116
+ }
1117
+ return true;
1118
+ }
1119
+ function isReferenceScalar(value) {
1120
+ return typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean';
1121
+ }
1122
+ /** Scalar id or `{ id }` identity used by lookup/select widgets. */
1123
+ function getReferenceId(value) {
1124
+ if (isNil(value)) {
1125
+ return undefined;
1126
+ }
1127
+ if (isReferenceScalar(value)) {
1128
+ return value;
1129
+ }
1130
+ if (isPlainObject(value)) {
1131
+ const id = get(value, 'id');
1132
+ if (!isNil(id)) {
1133
+ return id;
1134
+ }
1135
+ }
1136
+ return undefined;
1137
+ }
1138
+ /**
1139
+ * Compares scalar selection ids to full lookup/select items (`5` vs `{ id: 5, ... }`).
1140
+ * Does not apply when both sides are objects — those use structural compare so field edits stay dirty.
1141
+ */
1142
+ function compareReferenceFormValues(a, b) {
1143
+ const refA = getReferenceId(a);
1144
+ const refB = getReferenceId(b);
1145
+ if (refA === undefined || refB === undefined) {
1146
+ return null;
1147
+ }
1148
+ const aIsScalar = isReferenceScalar(a);
1149
+ const bIsScalar = isReferenceScalar(b);
1150
+ if (!aIsScalar && !bIsScalar) {
1151
+ return null;
1152
+ }
1153
+ return isEqual(normalizeFormValue(refA), normalizeFormValue(refB));
1154
+ }
1155
+ /**
1156
+ * Semantic equality for lookup/select values when restoring saved baseline shape.
1157
+ * Compares by selection identity (id), including two full items with the same id.
1158
+ */
1159
+ function isSelectionValueEqual(a, b) {
1160
+ const filterEqual = compareFilterFormValues(a, b);
1161
+ if (filterEqual !== null) {
1162
+ return filterEqual;
1163
+ }
1164
+ const refA = getReferenceId(a);
1165
+ const refB = getReferenceId(b);
1166
+ if (refA !== undefined && refB !== undefined) {
1167
+ return isEqual(normalizeFormValue(refA), normalizeFormValue(refB));
1168
+ }
1169
+ return isFormValueEqual(a, b);
1170
+ }
1171
+ /**
1172
+ * Stable sort key for order-insensitive array comparison (after normalization).
1173
+ */
1174
+ function formValueSortKey(value) {
1175
+ if (value === null) {
1176
+ return '\0';
1177
+ }
1178
+ if (typeof value === 'number') {
1179
+ return `n:${value}`;
1180
+ }
1181
+ if (typeof value === 'boolean') {
1182
+ return `b:${value}`;
1183
+ }
1184
+ if (typeof value === 'string') {
1185
+ return `s:${value}`;
1186
+ }
1187
+ try {
1188
+ return `j:${JSON.stringify(value)}`;
1189
+ }
1190
+ catch {
1191
+ return `u:${String(value)}`;
1192
+ }
1193
+ }
1194
+ /**
1195
+ * Sorts normalized array items so order does not affect dirty checks.
1196
+ */
1197
+ function sortNormalizedArrayItems(items) {
1198
+ return [...items].sort((a, b) => formValueSortKey(a).localeCompare(formValueSortKey(b)));
1199
+ }
1200
+ /**
1201
+ * Normalizes a form/context value for semantic baseline comparison.
1202
+ *
1203
+ * Rules:
1204
+ * - `null`, `undefined`, whitespace-only strings, and empty arrays → `null` (empty)
1205
+ * - Numeric strings → numbers (`"5"` and `5` match)
1206
+ * - Arrays → elements normalized; empty/all-empty → `null`; otherwise sorted (order ignored)
1207
+ * - Plain objects → keys sorted; `null`/empty nested values omitted
1208
+ * - `Date` → epoch milliseconds
1209
+ */
1210
+ function normalizeFormValue(value) {
1211
+ if (value === undefined || value === null) {
1212
+ return null;
1213
+ }
1214
+ if (typeof value === 'string') {
1215
+ const trimmed = value.trim();
1216
+ if (trimmed === '') {
1217
+ return null;
1218
+ }
1219
+ if (/^-?\d+(\.\d+)?$/.test(trimmed)) {
1220
+ const num = Number(trimmed);
1221
+ if (!Number.isNaN(num)) {
1222
+ return num;
1223
+ }
1224
+ }
1225
+ return trimmed;
1226
+ }
1227
+ if (typeof value === 'number') {
1228
+ return Number.isNaN(value) ? null : value;
1229
+ }
1230
+ if (typeof value === 'boolean') {
1231
+ return value;
1232
+ }
1233
+ if (value instanceof Date) {
1234
+ return value.getTime();
1235
+ }
1236
+ if (isArray(value)) {
1237
+ const normalizedItems = value
1238
+ .map((item) => normalizeFormValue(item))
1239
+ .filter((item) => item !== null);
1240
+ if (normalizedItems.length === 0) {
1241
+ return null;
1242
+ }
1243
+ return sortNormalizedArrayItems(normalizedItems);
1244
+ }
1245
+ if (isPlainObject(value)) {
1246
+ const record = value;
1247
+ if (isLocaleStringMap(record)) {
1248
+ return normalizeLocaleStringMap(record);
1249
+ }
1250
+ const normalized = {};
1251
+ for (const key of Object.keys(record).sort()) {
1252
+ const next = normalizeFormValue(record[key]);
1253
+ if (next !== null) {
1254
+ normalized[key] = next;
1255
+ }
1256
+ }
1257
+ return normalized;
1258
+ }
1259
+ return value;
1260
+ }
1261
+ /**
1262
+ * Semantic equality for dirty-state and baseline checks.
1263
+ * Prefer over lodash `isEqual` when comparing user-edited form/context data to a saved baseline.
1264
+ *
1265
+ * Arrays: `[]`, `null`, and all-empty lists are equivalent; non-empty arrays compare as multisets (order ignored).
1266
+ */
1267
+ function isFormValueEqual(a, b) {
1268
+ const multiLanguageEqual = compareMultiLanguageFormValues(a, b);
1269
+ if (multiLanguageEqual !== null) {
1270
+ return multiLanguageEqual;
1271
+ }
1272
+ const filterEqual = compareFilterFormValues(a, b);
1273
+ if (filterEqual !== null) {
1274
+ return filterEqual;
1275
+ }
1276
+ const referenceEqual = compareReferenceFormValues(a, b);
1277
+ if (referenceEqual !== null) {
1278
+ return referenceEqual;
1279
+ }
1280
+ if (isArray(a) && isArray(b)) {
1281
+ // Empty repeater rows normalize away; raw length must differ before multiset compare.
1282
+ if (a.length !== b.length) {
1283
+ return false;
1284
+ }
1285
+ return isEqual(normalizeFormValue(a), normalizeFormValue(b));
1286
+ }
1287
+ if (isPlainObject(a) && isPlainObject(b)) {
1288
+ const left = a;
1289
+ const right = b;
1290
+ const keys = new Set([...Object.keys(left), ...Object.keys(right)]);
1291
+ for (const key of keys) {
1292
+ if (!isFormValueEqual(left[key], right[key])) {
1293
+ return false;
1294
+ }
1295
+ }
1296
+ return true;
1297
+ }
1298
+ return isEqual(normalizeFormValue(a), normalizeFormValue(b));
1299
+ }
1300
+ /**
1301
+ * Returns whether `current` differs from the saved/clean `baseline` snapshot.
1302
+ */
1303
+ function isFormContextDirty(current, baseline) {
1304
+ return !isFormValueEqual(current, baseline);
1305
+ }
1306
+ /**
1307
+ * Clones a context object for use as a dirty-tracking baseline.
1308
+ */
1309
+ function captureFormContextBaseline(context) {
1310
+ return cloneDeep(context);
1311
+ }
1312
+ //#endregion
1313
+
1314
+ class AXPContextChangeEvent {
1315
+ }
1316
+ //#endregion
1317
+ //#region ---- Context signal store ----
1318
+ const AXPContextStore = signalStore(withState(() => ({
1319
+ data: {},
1320
+ state: 'initiated',
1321
+ /** Last committed / saved baseline — discard reverts to this snapshot. */
1322
+ savedSnapshot: {},
1323
+ previousSnapshot: {},
1324
+ lastChange: {
1325
+ state: 'initiated',
1326
+ },
1327
+ })), withComputed(({ data, state, lastChange, savedSnapshot, previousSnapshot }) => ({
1328
+ isChanged: computed(() => state() === 'changed'),
1329
+ isReset: computed(() => state() === 'restored'),
1330
+ isInitiated: computed(() => state() === 'initiated'),
1331
+ isEmpty: computed(() => Object.keys(data()).length === 0),
1332
+ isSavedCommitted: computed(() => !isEmpty(savedSnapshot())),
1333
+ isDirty: computed(() => {
1334
+ const saved = savedSnapshot();
1335
+ if (isEmpty(saved)) {
1336
+ return false;
1337
+ }
1338
+ return isFormContextDirty(data(), saved);
1339
+ }),
1340
+ snapshot: computed(() => cloneDeep(data())),
1341
+ saved: computed(() => cloneDeep(savedSnapshot())),
1342
+ /** @deprecated Use {@link saved} — only saved baseline is tracked. */
1343
+ initial: computed(() => cloneDeep(savedSnapshot())),
1344
+ previous: computed(() => cloneDeep(previousSnapshot())),
1345
+ changeEvent: computed(() => lastChange()),
1346
+ })), withMethods((store) => {
1347
+ const updateValue = (path, value, options) => {
1348
+ const currentData = cloneDeep(store.data());
1349
+ const oldValue = getSmart(currentData, path);
1350
+ const hasSaved = !isEmpty(store.savedSnapshot());
1351
+ const savedAtPath = hasSaved ? getSmart(store.savedSnapshot(), path) : undefined;
1352
+ if (hasSaved && isSelectionValueEqual(value, savedAtPath) && !isEqual(value, savedAtPath)) {
1353
+ value = cloneDeep(savedAtPath);
1354
+ }
1355
+ const isArrayLengthChange = Array.isArray(value) && (!Array.isArray(oldValue) || value.length !== oldValue.length);
1356
+ if (!isArrayLengthChange && isEqual(oldValue, value)) {
1357
+ return;
1358
+ }
1359
+ const shouldNormalizeSavedShape = hasSaved && isSelectionValueEqual(value, savedAtPath) && !isEqual(oldValue, value);
1360
+ if (!isArrayLengthChange && isFormValueEqual(oldValue, value) && !shouldNormalizeSavedShape) {
1361
+ return;
1362
+ }
1363
+ const origin = options?.origin ?? 'system';
1364
+ const updatedData = setSmart(currentData, path, value);
1365
+ const changeEvent = {
1366
+ oldValue,
1367
+ newValue: value,
1368
+ path,
1369
+ state: 'changed',
1370
+ data: updatedData,
1371
+ origin,
1372
+ };
1373
+ patchState(store, {
1374
+ previousSnapshot: store.snapshot(),
1375
+ data: updatedData,
1376
+ state: 'changed',
1377
+ lastChange: changeEvent,
1378
+ });
1379
+ };
1380
+ const applyObjectPaths = (obj, options, prefix = '') => {
1381
+ for (const [key, value] of Object.entries(obj ?? {})) {
1382
+ const path = prefix ? `${prefix}.${key}` : key;
1383
+ if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
1384
+ applyObjectPaths(value, options, path);
1385
+ }
1386
+ else {
1387
+ updateValue(path, value, options);
1388
+ }
1389
+ }
1390
+ };
1391
+ const revertToSaved = () => {
1392
+ const savedData = store.saved();
1393
+ const changeEvent = {
1394
+ oldValue: cloneDeep(store.data()),
1395
+ newValue: cloneDeep(savedData),
1396
+ path: '',
1397
+ state: 'restored',
1398
+ data: savedData,
1399
+ };
1400
+ patchState(store, {
1401
+ previousSnapshot: store.snapshot(),
1402
+ data: savedData,
1403
+ state: 'restored',
1404
+ lastChange: changeEvent,
1405
+ });
1406
+ };
1407
+ return {
1408
+ update(path, value, options) {
1409
+ updateValue(path, value, options);
1410
+ },
1411
+ applyObjectPaths,
1412
+ patch(context, options) {
1413
+ const currentData = cloneDeep(store.data());
1414
+ const updatedData = { ...currentData, ...context };
1415
+ const changeEvent = {
1416
+ state: 'patch',
1417
+ data: updatedData,
1418
+ };
1419
+ const syncedSnapshot = cloneDeep(updatedData);
1420
+ patchState(store, {
1421
+ ...(options?.updateSaved
1422
+ ? { savedSnapshot: syncedSnapshot, previousSnapshot: syncedSnapshot }
1423
+ : { previousSnapshot: store.snapshot() }),
1424
+ data: updatedData,
1425
+ state: 'changed',
1426
+ lastChange: changeEvent,
1427
+ });
1428
+ },
1429
+ /** Reverts live data to the last saved snapshot. */
1430
+ revertToSaved,
1431
+ reset() {
1432
+ revertToSaved();
1433
+ },
1434
+ /** Loads live data; saved baseline is committed separately via {@link commitSaved}. */
1435
+ set(initialData) {
1436
+ const currentData = store.data();
1437
+ if (isFormValueEqual(currentData, initialData)) {
1438
+ return;
1439
+ }
1440
+ const changeEvent = {
1441
+ oldValue: null,
1442
+ newValue: cloneDeep(initialData),
1443
+ path: '',
1444
+ state: 'initiated',
1445
+ data: initialData,
1446
+ };
1447
+ patchState(store, {
1448
+ previousSnapshot: store.snapshot(),
1449
+ data: initialData,
1450
+ state: 'initiated',
1451
+ lastChange: changeEvent,
1452
+ });
1453
+ },
1454
+ getValue(path) {
1455
+ return getSmart(store.data(), path);
1456
+ },
1457
+ getSavedValue(path) {
1458
+ return getSmart(store.savedSnapshot(), path);
1459
+ },
1460
+ hasValue(path) {
1461
+ return has(store.data(), path);
1462
+ },
1463
+ /** Marks the current data as the saved baseline. */
1464
+ commitSaved() {
1465
+ const snapshot = cloneDeep(store.data());
1466
+ patchState(store, {
1467
+ savedSnapshot: snapshot,
1468
+ previousSnapshot: snapshot,
1469
+ state: 'initiated',
1470
+ lastChange: {
1471
+ state: 'initiated',
1472
+ data: snapshot,
1473
+ },
1474
+ });
1475
+ },
1476
+ /** Merges parent-bound entity fields without resetting the saved baseline. */
1477
+ applyParentBind(merged) {
1478
+ if (isFormValueEqual(store.data(), merged)) {
1479
+ return;
1480
+ }
1481
+ patchState(store, {
1482
+ data: merged,
1483
+ });
1484
+ },
1485
+ };
1486
+ }));
1487
+ //#endregion
1488
+
1489
+ class AXPExpressionEvaluatorScopeProviderContext {
1490
+ constructor() {
1491
+ this.scopes = {};
1492
+ }
1493
+ addScope(namespace, functions) {
1494
+ this.scopes[namespace] = { ...this.scopes[namespace], ...functions };
1495
+ }
1496
+ getScopes() {
1497
+ return this.scopes;
1498
+ }
1499
+ }
1500
+ const AXP_EXPRESSION_EVALUATOR_SCOPE_PROVIDER = new InjectionToken('AXP_EXPRESSION_EVALUATOR_SCOPE_PROVIDER');
1501
+ class AXPExpressionEvaluatorScopeProviderService {
1502
+ constructor() {
1503
+ this.injector = inject(Injector);
1504
+ this.cache = null;
1505
+ }
1506
+ async load() {
1507
+ if (this.cache)
1508
+ return;
1509
+ const raw = this.injector.get(AXP_EXPRESSION_EVALUATOR_SCOPE_PROVIDER, [], { optional: true });
1510
+ const providers = Array.isArray(raw)
1511
+ ? raw
1512
+ : typeof raw === 'function'
1513
+ ? raw()
1514
+ : [];
1515
+ const context = new AXPExpressionEvaluatorScopeProviderContext();
1516
+ for (const provider of providers) {
1517
+ // Resolve lazy-loaded providers (provideLazyProvider useFactory returns Promise<Provider>)
1518
+ const resolved = await Promise.resolve(provider);
1519
+ if (!resolved || typeof resolved.provide !== 'function') {
1520
+ continue;
1521
+ }
1522
+ await resolved.provide(context);
1523
+ }
1524
+ this.cache = context.getScopes();
1525
+ }
1526
+ async getScopesAsync() {
1527
+ await this.load();
1528
+ return this.cache || {};
1529
+ }
1530
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPExpressionEvaluatorScopeProviderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1531
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPExpressionEvaluatorScopeProviderService, providedIn: 'root' }); }
1532
+ }
1533
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPExpressionEvaluatorScopeProviderService, decorators: [{
1534
+ type: Injectable,
1535
+ args: [{ providedIn: 'root' }]
1536
+ }] });
1537
+
1538
+ class AXPExpressionEvaluatorService {
1539
+ constructor() {
1540
+ // Memoization cache for compiled expressions
1541
+ this.expressionCache = new Map();
1542
+ this.providerService = inject(AXPExpressionEvaluatorScopeProviderService);
1543
+ }
1544
+ getOrCompileFunction(expression) {
1545
+ if (!this.expressionCache.has(expression)) {
1546
+ // Check if expression contains multiple statements (has semicolons or newlines)
1547
+ const hasMultipleStatements = expression.includes(';') || expression.includes('\n');
1548
+ let fn;
1549
+ if (hasMultipleStatements) {
1550
+ // For multiple statements, execute them in sequence and return the last expression
1551
+ fn = new Function('scope', `with (scope) { return (async function() { ${expression} })(); }`);
1552
+ }
1553
+ else {
1554
+ // For single expressions, use return
1555
+ fn = new Function('scope', `with (scope) { return (async function() { return ${expression}; })(); }`);
1556
+ }
1557
+ this.expressionCache.set(expression, fn);
1558
+ }
1559
+ return this.expressionCache.get(expression);
1560
+ }
1561
+ async getMergedScope(userScope) {
1562
+ const pluginScopes = await this.providerService.getScopesAsync();
1563
+ // Merge pluginScopes and userScope (userScope takes precedence)
1564
+ return { ...pluginScopes, ...userScope };
1565
+ }
1566
+ async evaluate(source, scope = {}) {
1567
+ try {
1568
+ const mergedScope = await this.getMergedScope(scope);
1569
+ if (typeof source === 'string' && source.includes('{{')) {
1570
+ return await this.evaluateStringExpression(source, mergedScope);
1571
+ }
1572
+ else if (Array.isArray(source)) {
1573
+ const evaluatedArray = [];
1574
+ for (const item of source) {
1575
+ evaluatedArray.push(await this.evaluate(item, mergedScope));
1576
+ }
1577
+ return evaluatedArray;
1578
+ }
1579
+ else if (typeof source === 'object' && source !== null) {
1580
+ const evaluatedObject = {};
1581
+ for (const key in source) {
1582
+ if (source.hasOwnProperty(key)) {
1583
+ evaluatedObject[key] = await this.evaluate(source[key], mergedScope);
1584
+ }
1585
+ }
1586
+ return evaluatedObject;
1587
+ }
1588
+ else {
1589
+ return source;
1590
+ }
1591
+ }
1592
+ catch (error) {
1593
+ console.error('Expression evaluator - Error evaluating expression:', source, error);
1594
+ return false;
1595
+ }
1596
+ }
1597
+ async evaluateStringExpression(templateExpression, scope) {
1598
+ // Check if the input is exactly a single {{ ... }} expression (handle multiline)
1599
+ const exactMatch = templateExpression.match(/^\s*\{\{\s*([\s\S]*?)\s*\}\}\s*$/);
1600
+ if (exactMatch) {
1601
+ const expression = exactMatch[1];
1602
+ const sandbox = this.getOrCompileFunction(expression);
1603
+ const result = await sandbox(scope);
1604
+ return result;
1605
+ }
1606
+ // Otherwise, interpolate all {{ ... }} expressions in the string
1607
+ const regex = /\{\{\s*([\s\S]*?)\s*\}\}/g;
1608
+ // Collect all matches and their positions
1609
+ const matches = [];
1610
+ let match;
1611
+ while ((match = regex.exec(templateExpression)) !== null) {
1612
+ matches.push({
1613
+ expression: match[1],
1614
+ start: match.index,
1615
+ end: regex.lastIndex,
1616
+ raw: match[0],
1617
+ });
1618
+ }
1619
+ // Evaluate all expressions in parallel
1620
+ const values = await Promise.all(matches.map((m) => {
1621
+ const sandbox = this.getOrCompileFunction(m.expression);
1622
+ return sandbox(scope);
1623
+ }));
1624
+ // Reconstruct the string with evaluated values
1625
+ let result = '';
1626
+ let lastIndex = 0;
1627
+ matches.forEach((m, i) => {
1628
+ result += templateExpression.slice(lastIndex, m.start);
1629
+ const value = values[i];
1630
+ result += value !== undefined && value !== null ? value : '';
1631
+ lastIndex = m.end;
1632
+ });
1633
+ result += templateExpression.slice(lastIndex);
1634
+ return result;
1635
+ }
1636
+ isExpression(expression) {
1637
+ if (typeof expression === 'string') {
1638
+ return expression.trim().startsWith('{{') && expression.trim().endsWith('}}');
1639
+ }
1640
+ return false;
1641
+ }
1642
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPExpressionEvaluatorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1643
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPExpressionEvaluatorService, providedIn: 'root' }); }
1644
+ }
1645
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPExpressionEvaluatorService, decorators: [{
1646
+ type: Injectable,
1647
+ args: [{ providedIn: 'root' }]
1648
+ }] });
1649
+
1650
+ class AXPComponentSlotPlaceholderComponent {
1651
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPComponentSlotPlaceholderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1652
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.9", type: AXPComponentSlotPlaceholderComponent, isStandalone: true, selector: "axp-component-slot-placeholder", ngImport: i0, template: `<div>
1653
+ <ax-skeleton class="ax-w-full ax-h-10 ax-rounded-md"></ax-skeleton>
1654
+ </div>`, isInline: true, dependencies: [{ kind: "ngmodule", type: AXSkeletonModule }, { kind: "component", type: i1.AXSkeletonComponent, selector: "ax-skeleton", inputs: ["animated"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1655
+ }
1656
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPComponentSlotPlaceholderComponent, decorators: [{
1657
+ type: Component,
1658
+ args: [{
1659
+ selector: 'axp-component-slot-placeholder',
1660
+ template: `<div>
1661
+ <ax-skeleton class="ax-w-full ax-h-10 ax-rounded-md"></ax-skeleton>
1662
+ </div>`,
1663
+ changeDetection: ChangeDetectionStrategy.OnPush,
1664
+ imports: [AXSkeletonModule],
1665
+ standalone: true,
1666
+ }]
1667
+ }] });
1668
+
1669
+ class AXPComponentSlotRegistryService {
1670
+ constructor() {
1671
+ this.registry = new Map();
1672
+ }
1673
+ register(slotName, config) {
1674
+ let configs = this.registry.get(slotName) || [];
1675
+ // Check if the component is already registered in this slot
1676
+ const isDuplicate = configs.some(existingConfig => existingConfig.name === config.name);
1677
+ if (!isDuplicate) {
1678
+ configs = [...configs, config]; // Add the new configuration
1679
+ this.registry.set(slotName, configs);
1680
+ }
1681
+ }
1682
+ get(slotName) {
1683
+ return this.registry.get(slotName) || [];
1684
+ }
1685
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPComponentSlotRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1686
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPComponentSlotRegistryService, providedIn: 'root' }); }
1687
+ }
1688
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPComponentSlotRegistryService, decorators: [{
1689
+ type: Injectable,
1690
+ args: [{
1691
+ providedIn: 'root'
1692
+ }]
1693
+ }] });
1694
+
1695
+ class AXPComponentSlotDirective {
1696
+ constructor() {
1697
+ this.name = input.required(...(ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ []));
1698
+ this.host = input(...(ngDevMode ? [undefined, { debugName: "host" }] : /* istanbul ignore next */ []));
1699
+ this.context = input(...(ngDevMode ? [undefined, { debugName: "context" }] : /* istanbul ignore next */ []));
1700
+ this.elementRef = inject(ElementRef);
1701
+ this.registryService = inject(AXPComponentSlotRegistryService);
1702
+ this.injector = inject(Injector);
1703
+ this.evaluator = inject(AXPExpressionEvaluatorService);
1704
+ this.viewContainerRef = inject(ViewContainerRef);
1705
+ this.contextStore = inject(AXPContextStore);
1706
+ this.isEmpty = computed(() => this._viewCount() === 0, ...(ngDevMode ? [{ debugName: "isEmpty" }] : /* istanbul ignore next */ []));
1707
+ // Create a signal to store the count of children
1708
+ this._viewCount = signal(0, ...(ngDevMode ? [{ debugName: "_viewCount" }] : /* istanbul ignore next */ []));
1709
+ // Track component references for updates
1710
+ this.componentRefs = [];
1711
+ // Track placeholder references for replacement
1712
+ this.placeholderRefs = new Map();
1713
+ // Watch for context changes and update component instances
1714
+ effect(() => {
1715
+ const currentContext = this.context();
1716
+ const currentHost = this.host();
1717
+ // Update all component references with new context and host
1718
+ this.componentRefs.forEach((componentRef) => {
1719
+ if (componentRef.instance) {
1720
+ Object.assign(componentRef.instance, {
1721
+ host: currentHost,
1722
+ context: currentContext,
1723
+ });
1724
+ }
1725
+ });
1726
+ });
1727
+ }
1728
+ async ngOnInit() {
1729
+ await this.loadComponents();
1730
+ // Update the signal after loading
1731
+ this._viewCount.set(this.viewContainerRef.length);
1732
+ }
1733
+ async loadComponents() {
1734
+ this.viewContainerRef.clear();
1735
+ // Clear previous component references
1736
+ this.componentRefs = [];
1737
+ this.placeholderRefs.clear();
1738
+ const configs = sortBy(this.registryService.get(this.name()), (c) => c.priority ?? 0);
1739
+ if (!configs) {
1740
+ console.error(`No component found for slot ${this.name()}`);
1741
+ return;
1742
+ }
1743
+ // Evaluate conditions and check features in parallel for performance
1744
+ const results = await Promise.all(configs.map(async (c) => {
1745
+ // Check condition if provided
1746
+ const conditionPassed = c.condition ? await this.evaluateCondition(c.condition) : true;
1747
+ // Check features if provided
1748
+ // const featuresPassed =
1749
+ // c.features && c.features.length > 0 ? this.sessionService.isFeatureEnabled(...c.features) : true;
1750
+ return {
1751
+ config: c,
1752
+ // visible: conditionPassed && featuresPassed,
1753
+ visible: conditionPassed,
1754
+ };
1755
+ }));
1756
+ // Filter visible components while preserving priority order
1757
+ const slots = results.filter((r) => r.visible).map((r) => r.config);
1758
+ // Create skeleton placeholders immediately in priority order
1759
+ slots.forEach((config, index) => {
1760
+ const placeholderRef = this.viewContainerRef.createComponent(AXPComponentSlotPlaceholderComponent);
1761
+ this.placeholderRefs.set(index, placeholderRef);
1762
+ });
1763
+ // Load all components in parallel and replace placeholders as they become ready
1764
+ const loadPromises = slots.map(async (config, index) => {
1765
+ let component;
1766
+ let options = {};
1767
+ // load component
1768
+ if (typeof config.loadComponent === 'function') {
1769
+ await runInInjectionContext(this.injector, async () => {
1770
+ component = await config.loadComponent?.();
1771
+ });
1772
+ }
1773
+ else if (config.component) {
1774
+ component = config.component;
1775
+ }
1776
+ // load options
1777
+ if (typeof config.options === 'function') {
1778
+ await runInInjectionContext(this.injector, async () => {
1779
+ const fun = config.options;
1780
+ options = await fun();
1781
+ });
1782
+ }
1783
+ else if (config.options) {
1784
+ options = await this.evaluator.evaluate(config.options, {});
1785
+ }
1786
+ // Replace placeholder with actual component as soon as it's ready
1787
+ if (!component) {
1788
+ console.warn(`Component failed to load for slot ${this.name()} at index ${index}`);
1789
+ // Remove placeholder if component failed to load
1790
+ const placeholderRef = this.placeholderRefs.get(index);
1791
+ if (placeholderRef) {
1792
+ placeholderRef.destroy();
1793
+ this.placeholderRefs.delete(index);
1794
+ }
1795
+ return;
1796
+ }
1797
+ // Get the placeholder reference at this index
1798
+ const placeholderRef = this.placeholderRefs.get(index);
1799
+ if (!placeholderRef) {
1800
+ console.warn(`Placeholder not found for index ${index}`);
1801
+ return;
1802
+ }
1803
+ // Get the index of the placeholder in the view container
1804
+ const placeholderIndex = this.viewContainerRef.indexOf(placeholderRef.hostView);
1805
+ // Remove the placeholder
1806
+ placeholderRef.destroy();
1807
+ this.placeholderRefs.delete(index);
1808
+ // Create the actual component at the same position
1809
+ const componentRef = this.viewContainerRef.createComponent(component, { index: placeholderIndex });
1810
+ // Store the component reference for future updates
1811
+ this.componentRefs.push(componentRef);
1812
+ Object.assign(componentRef.instance, {
1813
+ host: this.host(),
1814
+ context: this.context(),
1815
+ ...options,
1816
+ });
1817
+ });
1818
+ // Wait for all components to finish loading (they render as they become ready)
1819
+ await Promise.all(loadPromises);
1820
+ }
1821
+ async evaluateCondition(condition) {
1822
+ if (typeof condition === 'string') {
1823
+ const result = await this.evaluator.evaluate(condition, {});
1824
+ return result;
1825
+ }
1826
+ return condition(this.contextStore.data());
1827
+ //return condition(this.context());
1828
+ }
1829
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPComponentSlotDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
1830
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: AXPComponentSlotDirective, isStandalone: false, selector: "axp-component-slot", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: true, transformFunction: null }, host: { classPropertyName: "host", publicName: "host", isSignal: true, isRequired: false, transformFunction: null }, context: { classPropertyName: "context", publicName: "context", isSignal: true, isRequired: false, transformFunction: null } }, exportAs: ["slot"], ngImport: i0 }); }
1831
+ }
1832
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPComponentSlotDirective, decorators: [{
1833
+ type: Directive,
1834
+ args: [{
1835
+ selector: 'axp-component-slot',
1836
+ standalone: false,
1837
+ exportAs: 'slot',
1838
+ }]
1839
+ }], ctorParameters: () => [], propDecorators: { name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: true }] }], host: [{ type: i0.Input, args: [{ isSignal: true, alias: "host", required: false }] }], context: [{ type: i0.Input, args: [{ isSignal: true, alias: "context", required: false }] }] } });
1840
+
1841
+ class AXPComponentSlotModule {
1842
+ static forRoot(configs) {
1843
+ return {
1844
+ ngModule: AXPComponentSlotModule,
1845
+ providers: [
1846
+ {
1847
+ provide: 'AXPComponentSlotModuleFactory',
1848
+ useFactory: (registry) => () => {
1849
+ if (configs) {
1850
+ for (const [key, value] of Object.entries(configs)) {
1851
+ value.forEach(v => {
1852
+ registry.register(key, v);
1853
+ });
1854
+ }
1855
+ }
1856
+ },
1857
+ deps: [AXPComponentSlotRegistryService],
1858
+ multi: true
1859
+ }
1860
+ ]
1861
+ };
1862
+ }
1863
+ static forChild(configs) {
1864
+ return {
1865
+ ngModule: AXPComponentSlotModule,
1866
+ providers: [
1867
+ {
1868
+ provide: 'AXPComponentSlotModuleFactory',
1869
+ useFactory: (registry) => () => {
1870
+ if (configs) {
1871
+ for (const [key, value] of Object.entries(configs)) {
1872
+ value.forEach(v => {
1873
+ registry.register(key, v);
1874
+ });
1875
+ }
1876
+ }
1877
+ },
1878
+ deps: [AXPComponentSlotRegistryService],
1879
+ multi: true
1880
+ }
1881
+ ]
1882
+ };
1883
+ }
1884
+ /**
1885
+ * @ignore
1886
+ */
1887
+ constructor(instances) {
1888
+ instances?.forEach(f => {
1889
+ f();
1890
+ });
1891
+ }
1892
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPComponentSlotModule, deps: [{ token: 'AXPComponentSlotModuleFactory', optional: true }], target: i0.ɵɵFactoryTarget.NgModule }); }
1893
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.9", ngImport: i0, type: AXPComponentSlotModule, declarations: [AXPComponentSlotDirective], exports: [AXPComponentSlotDirective] }); }
1894
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPComponentSlotModule }); }
1895
+ }
1896
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPComponentSlotModule, decorators: [{
1897
+ type: NgModule,
1898
+ args: [{
1899
+ declarations: [AXPComponentSlotDirective],
1900
+ exports: [AXPComponentSlotDirective]
1901
+ }]
1902
+ }], ctorParameters: () => [{ type: undefined, decorators: [{
1903
+ type: Optional
1904
+ }, {
1905
+ type: Inject,
1906
+ args: ['AXPComponentSlotModuleFactory']
1907
+ }] }] });
1908
+
1909
+ //#region ---- AI metadata ----
1910
+ //#endregion
1911
+
1912
+ const AXP_CONTEXT_DEFINITION_PROVIDER = new InjectionToken('AXP_CONTEXT_DEFINITION_PROVIDER');
1913
+ //#endregion
1914
+
1915
+ //#region ---- Aggregator service ----
1916
+ /**
1917
+ * Collects {@link AXPContextDefinition} entries from all registered {@link AXPContextDefinitionProvider} instances.
1918
+ */
1919
+ class AXPContextDefinitionProviderService {
1920
+ constructor() {
1921
+ //#region ---- Services & Dependencies ----
1922
+ this.providers = inject(AXP_CONTEXT_DEFINITION_PROVIDER, { optional: true });
1923
+ }
1924
+ //#endregion
1925
+ //#region ---- Public API ----
1926
+ async items() {
1927
+ const items = [];
1928
+ if (!Array.isArray(this.providers)) {
1929
+ return items;
1930
+ }
1931
+ for (const raw of this.providers) {
1932
+ const provider = await Promise.resolve(raw);
1933
+ if (provider && typeof provider.items === 'function') {
1934
+ items.push(...(await provider.items()));
1935
+ }
1936
+ }
1937
+ return items;
1938
+ }
1939
+ async get(name) {
1940
+ return (await this.items()).find((d) => d.name === name);
1941
+ }
1942
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPContextDefinitionProviderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1943
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPContextDefinitionProviderService, providedIn: 'root' }); }
1944
+ }
1945
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPContextDefinitionProviderService, decorators: [{
1946
+ type: Injectable,
1947
+ args: [{ providedIn: 'root' }]
1948
+ }] });
1949
+
1950
+ class AXPDataGenerator {
1951
+ static uuid() {
1952
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (char) => {
1953
+ const random = (Math.random() * 16) | 0;
1954
+ const value = char === 'x' ? random : (random & 0x3) | 0x8;
1955
+ return value.toString(16);
1956
+ });
1957
+ }
1958
+ static number(...args) {
1959
+ let min = 0;
1960
+ let max = 100;
1961
+ if (args.length == 1)
1962
+ max = args[0];
1963
+ if (args.length == 2) {
1964
+ min = args[0];
1965
+ max = args[1];
1966
+ }
1967
+ return Math.floor(Math.random() * (max - min + 1)) + min;
1968
+ }
1969
+ static date(...args) {
1970
+ let start = new Date(2000, 0, 1);
1971
+ let end = new Date();
1972
+ if (args.length === 1) {
1973
+ start = args[0];
1974
+ }
1975
+ else if (args.length === 2) {
1976
+ start = args[0];
1977
+ end = args[1];
1978
+ }
1979
+ const startTime = start.getTime();
1980
+ const endTime = end.getTime();
671
1981
  return new Date(startTime + Math.random() * (endTime - startTime));
672
1982
  }
673
1983
  static array(length = 5, generator) {
@@ -829,11 +2139,14 @@ class AXPDataSourceDefinitionProviderService {
829
2139
  }
830
2140
  async items() {
831
2141
  const items = [];
832
- // Load from DI tokens
2142
+ // Load from DI tokens; resolve lazy providers (provideLazyProvider uses useFactory that returns Promise<T>)
833
2143
  if (Array.isArray(this.providers)) {
834
- for (const provider of this.providers) {
835
- set(provider, '__parent__', this);
836
- items.push(...(await provider.items()));
2144
+ for (const raw of this.providers) {
2145
+ const provider = await Promise.resolve(raw);
2146
+ if (provider && typeof provider.items === 'function') {
2147
+ set(provider, '__parent__', this);
2148
+ items.push(...(await provider.items()));
2149
+ }
837
2150
  }
838
2151
  }
839
2152
  return items;
@@ -841,52 +2154,32 @@ class AXPDataSourceDefinitionProviderService {
841
2154
  async get(name) {
842
2155
  return (await this.items()).find((c) => c.name == name);
843
2156
  }
844
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPDataSourceDefinitionProviderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
845
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPDataSourceDefinitionProviderService, providedIn: 'root' }); }
2157
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPDataSourceDefinitionProviderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2158
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPDataSourceDefinitionProviderService, providedIn: 'root' }); }
846
2159
  }
847
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPDataSourceDefinitionProviderService, decorators: [{
2160
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPDataSourceDefinitionProviderService, decorators: [{
848
2161
  type: Injectable,
849
2162
  args: [{ providedIn: 'root' }]
850
2163
  }] });
851
2164
 
852
- class AXPElementDataDirective {
853
- constructor(elementRef) {
854
- this.elementRef = elementRef;
855
- this.data = input(null, ...(ngDevMode ? [{ debugName: "data", alias: 'axp-data' }] : [{ alias: 'axp-data' }]));
856
- this.path = input('__data__', ...(ngDevMode ? [{ debugName: "path", alias: 'axp-data-path' }] : [{ alias: 'axp-data-path' }]));
857
- // Effect to update element data when inputs change
858
- this.updateEffect = effect(() => {
859
- const currentData = this.data();
860
- const currentPath = this.path();
861
- set(this.elementRef.nativeElement, currentPath, currentData);
862
- }, ...(ngDevMode ? [{ debugName: "updateEffect" }] : []));
863
- }
864
- ngOnInit() {
865
- // Initial data setup
866
- set(this.elementRef.nativeElement, this.path(), this.data());
867
- }
868
- /**
869
- * Get data from the element
870
- */
871
- getData() {
872
- return get(this.elementRef.nativeElement, this.path());
873
- }
874
- /**
875
- * Update data at runtime
876
- */
877
- setData(data) {
878
- set(this.elementRef.nativeElement, this.path(), data);
2165
+ // src/app/directives/grid-layout.directive.ts
2166
+ class AXPContentCheckerDirective {
2167
+ constructor() {
2168
+ this.viewContainerRef = inject(ViewContainerRef);
2169
+ this.elementRef = inject((ElementRef));
2170
+ this.isEmpty = computed(() => this.viewContainerRef.length === 0, ...(ngDevMode ? [{ debugName: "isEmpty" }] : /* istanbul ignore next */ []));
879
2171
  }
880
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPElementDataDirective, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Directive }); }
881
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.3.12", type: AXPElementDataDirective, isStandalone: true, selector: "[axp-data]", inputs: { data: { classPropertyName: "data", publicName: "axp-data", isSignal: true, isRequired: false, transformFunction: null }, path: { classPropertyName: "path", publicName: "axp-data-path", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 }); }
2172
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPContentCheckerDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
2173
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: AXPContentCheckerDirective, isStandalone: true, selector: "[axp-content-checker]", exportAs: ["checker"], ngImport: i0 }); }
882
2174
  }
883
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPElementDataDirective, decorators: [{
2175
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPContentCheckerDirective, decorators: [{
884
2176
  type: Directive,
885
2177
  args: [{
886
- selector: '[axp-data]',
887
- standalone: true
2178
+ selector: '[axp-content-checker]',
2179
+ standalone: true,
2180
+ exportAs: 'checker'
888
2181
  }]
889
- }], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "axp-data", required: false }] }], path: [{ type: i0.Input, args: [{ isSignal: true, alias: "axp-data-path", required: false }] }] } });
2182
+ }] });
890
2183
 
891
2184
  class AXPDblClickDirective {
892
2185
  constructor() {
@@ -909,10 +2202,10 @@ class AXPDblClickDirective {
909
2202
  }
910
2203
  this.lastTap = currentTime;
911
2204
  }
912
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPDblClickDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
913
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.12", type: AXPDblClickDirective, isStandalone: true, selector: "[onDblClick]", outputs: { onDblClick: "onDblClick" }, host: { listeners: { "dblclick": "handleOnDblClick($event)", "touchend": "onTouchEnd($event)" } }, ngImport: i0 }); }
2205
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPDblClickDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
2206
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: AXPDblClickDirective, isStandalone: true, selector: "[onDblClick]", outputs: { onDblClick: "onDblClick" }, host: { listeners: { "dblclick": "handleOnDblClick($event)", "touchend": "onTouchEnd($event)" } }, ngImport: i0 }); }
914
2207
  }
915
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPDblClickDirective, decorators: [{
2208
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPDblClickDirective, decorators: [{
916
2209
  type: Directive,
917
2210
  args: [{
918
2211
  selector: '[onDblClick]'
@@ -927,12 +2220,51 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImpo
927
2220
  args: ['touchend', ['$event']]
928
2221
  }] } });
929
2222
 
2223
+ class AXPElementDataDirective {
2224
+ constructor(elementRef) {
2225
+ this.elementRef = elementRef;
2226
+ this.data = input(null, { ...(ngDevMode ? { debugName: "data" } : /* istanbul ignore next */ {}), alias: 'axp-data' });
2227
+ this.path = input('__data__', { ...(ngDevMode ? { debugName: "path" } : /* istanbul ignore next */ {}), alias: 'axp-data-path' });
2228
+ // Effect to update element data when inputs change
2229
+ this.updateEffect = effect(() => {
2230
+ const currentData = this.data();
2231
+ const currentPath = this.path();
2232
+ set(this.elementRef.nativeElement, currentPath, currentData);
2233
+ }, ...(ngDevMode ? [{ debugName: "updateEffect" }] : /* istanbul ignore next */ []));
2234
+ }
2235
+ ngOnInit() {
2236
+ // Initial data setup
2237
+ set(this.elementRef.nativeElement, this.path(), this.data());
2238
+ }
2239
+ /**
2240
+ * Get data from the element
2241
+ */
2242
+ getData() {
2243
+ return get(this.elementRef.nativeElement, this.path());
2244
+ }
2245
+ /**
2246
+ * Update data at runtime
2247
+ */
2248
+ setData(data) {
2249
+ set(this.elementRef.nativeElement, this.path(), data);
2250
+ }
2251
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPElementDataDirective, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Directive }); }
2252
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: AXPElementDataDirective, isStandalone: true, selector: "[axp-data]", inputs: { data: { classPropertyName: "data", publicName: "axp-data", isSignal: true, isRequired: false, transformFunction: null }, path: { classPropertyName: "path", publicName: "axp-data-path", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 }); }
2253
+ }
2254
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPElementDataDirective, decorators: [{
2255
+ type: Directive,
2256
+ args: [{
2257
+ selector: '[axp-data]',
2258
+ standalone: true
2259
+ }]
2260
+ }], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "axp-data", required: false }] }], path: [{ type: i0.Input, args: [{ isSignal: true, alias: "axp-data-path", required: false }] }] } });
2261
+
930
2262
  // src/app/directives/grid-layout.directive.ts
931
2263
  class AXPGridLayoutDirective {
932
2264
  constructor(el, renderer) {
933
2265
  this.el = el;
934
2266
  this.renderer = renderer;
935
- this.options = input.required(...(ngDevMode ? [{ debugName: "options", alias: 'axp-grid-layout' }] : [{ alias: 'axp-grid-layout' }]));
2267
+ this.options = input.required({ ...(ngDevMode ? { debugName: "options" } : /* istanbul ignore next */ {}), alias: 'axp-grid-layout' });
936
2268
  }
937
2269
  ngOnChanges(changes) {
938
2270
  if (changes['options']) {
@@ -968,240 +2300,24 @@ class AXPGridLayoutDirective {
968
2300
  }
969
2301
  });
970
2302
  }
971
- clearClasses() {
972
- const currentClasses = this.el.nativeElement.className.split(' ');
973
- currentClasses.forEach((className) => {
974
- if (className.startsWith('ax-col-') || className.startsWith('ax-row-') || className.startsWith('ax-order-')) {
975
- this.renderer.removeClass(this.el.nativeElement, className);
976
- }
977
- });
978
- }
979
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPGridLayoutDirective, deps: [{ token: i0.ElementRef }, { token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Directive }); }
980
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.3.12", type: AXPGridLayoutDirective, isStandalone: true, selector: "[axp-grid-layout]", inputs: { options: { classPropertyName: "options", publicName: "axp-grid-layout", isSignal: true, isRequired: true, transformFunction: null } }, usesOnChanges: true, ngImport: i0 }); }
981
- }
982
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPGridLayoutDirective, decorators: [{
983
- type: Directive,
984
- args: [{
985
- selector: '[axp-grid-layout]',
986
- standalone: true,
987
- }]
988
- }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.Renderer2 }], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "axp-grid-layout", required: true }] }] } });
989
-
990
- // src/app/directives/grid-layout.directive.ts
991
- class AXPContentCheckerDirective {
992
- constructor() {
993
- this.viewContainerRef = inject(ViewContainerRef);
994
- this.elementRef = inject((ElementRef));
995
- this.isEmpty = computed(() => this.viewContainerRef.length === 0, ...(ngDevMode ? [{ debugName: "isEmpty" }] : []));
996
- }
997
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPContentCheckerDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
998
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.12", type: AXPContentCheckerDirective, isStandalone: true, selector: "[axp-content-checker]", exportAs: ["checker"], ngImport: i0 }); }
999
- }
1000
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPContentCheckerDirective, decorators: [{
1001
- type: Directive,
1002
- args: [{
1003
- selector: '[axp-content-checker]',
1004
- standalone: true,
1005
- exportAs: 'checker'
1006
- }]
1007
- }] });
1008
-
1009
- //#region ---- Imports ----
1010
- //#endregion
1011
- //#region ---- Directive ----
1012
- /**
1013
- * Fullscreen directive that provides CSS-based fullscreen functionality
1014
- * Usage: <element axFullscreen #fullscreen="axFullscreen"></element>
1015
- * Then: fullscreen.toggle() or fullscreen.enter() or fullscreen.exit()
1016
- */
1017
- class AXFullscreenDirective {
1018
- constructor(elementRef) {
1019
- this.elementRef = elementRef;
1020
- //#region ---- Properties ----
1021
- /**
1022
- * Current fullscreen state
1023
- */
1024
- this.isFullscreenState = signal(false, ...(ngDevMode ? [{ debugName: "isFullscreenState" }] : []));
1025
- /**
1026
- * Original element styles to restore
1027
- */
1028
- this.originalStyles = {};
1029
- /**
1030
- * Original parent element reference
1031
- */
1032
- this.originalParent = null;
1033
- /**
1034
- * Fullscreen container element
1035
- */
1036
- this.fullscreenContainer = null;
1037
- /**
1038
- * Fullscreen change event
1039
- */
1040
- this.fullscreenChange = output();
1041
- //#endregion
1042
- //#region ---- Constructor ----
1043
- this.destroyRef = inject(DestroyRef);
1044
- this.renderer = inject(Renderer2);
1045
- // Sync state changes to output
1046
- effect(() => {
1047
- const isFullscreen = this.isFullscreenState();
1048
- untracked(() => {
1049
- this.fullscreenChange.emit(isFullscreen);
1050
- });
1051
- });
1052
- }
1053
- //#endregion
1054
- //#region ---- Public API ----
1055
- /**
1056
- * Toggle fullscreen state
1057
- */
1058
- async toggle() {
1059
- if (this.isFullscreenState()) {
1060
- await this.exit();
1061
- }
1062
- else {
1063
- await this.enter();
1064
- }
1065
- }
1066
- /**
1067
- * Enter fullscreen mode using CSS
1068
- */
1069
- async enter() {
1070
- if (this.isFullscreenState()) {
1071
- return;
1072
- }
1073
- const element = this.elementRef.nativeElement;
1074
- try {
1075
- // Store original styles and parent
1076
- this.storeOriginalStyles(element);
1077
- this.originalParent = element.parentElement;
1078
- // Create fullscreen container
1079
- this.fullscreenContainer = this.renderer.createElement('div');
1080
- this.renderer.setStyle(this.fullscreenContainer, 'position', 'fixed');
1081
- this.renderer.setStyle(this.fullscreenContainer, 'top', '0');
1082
- this.renderer.setStyle(this.fullscreenContainer, 'left', '0');
1083
- this.renderer.setStyle(this.fullscreenContainer, 'width', '100vw');
1084
- this.renderer.setStyle(this.fullscreenContainer, 'height', '100vh');
1085
- this.renderer.setStyle(this.fullscreenContainer, 'z-index', '9999');
1086
- this.renderer.setStyle(this.fullscreenContainer, 'background-color', '#ffffff');
1087
- this.renderer.setStyle(this.fullscreenContainer, 'overflow', 'auto');
1088
- // Move element to container
1089
- this.renderer.appendChild(this.fullscreenContainer, element);
1090
- // Apply fullscreen styles to element
1091
- this.renderer.setStyle(element, 'position', 'relative');
1092
- this.renderer.setStyle(element, 'width', '100%');
1093
- this.renderer.setStyle(element, 'height', '100%');
1094
- this.renderer.setStyle(element, 'margin', '0');
1095
- this.renderer.setStyle(element, 'padding', '0');
1096
- // Append container to body
1097
- this.renderer.appendChild(document.body, this.fullscreenContainer);
1098
- // Prevent body scroll
1099
- this.renderer.setStyle(document.body, 'overflow', 'hidden');
1100
- this.isFullscreenState.set(true);
1101
- }
1102
- catch (error) {
1103
- console.error('Error entering fullscreen:', error);
1104
- this.restoreOriginalStyles(element);
1105
- }
1106
- }
1107
- /**
1108
- * Exit fullscreen mode
1109
- */
1110
- async exit() {
1111
- if (!this.isFullscreenState()) {
1112
- return;
1113
- }
1114
- const element = this.elementRef.nativeElement;
1115
- try {
1116
- // Restore body scroll
1117
- this.renderer.removeStyle(document.body, 'overflow');
1118
- // Move element back to original parent
1119
- if (this.fullscreenContainer && this.originalParent) {
1120
- this.renderer.removeChild(this.fullscreenContainer, element);
1121
- this.renderer.appendChild(this.originalParent, element);
1122
- // Remove container
1123
- this.renderer.removeChild(document.body, this.fullscreenContainer);
1124
- this.fullscreenContainer = null;
1125
- }
1126
- // Restore original styles
1127
- this.restoreOriginalStyles(element);
1128
- this.isFullscreenState.set(false);
1129
- }
1130
- catch (error) {
1131
- console.error('Error exiting fullscreen:', error);
1132
- }
1133
- }
1134
- /**
1135
- * Check if currently in fullscreen mode
1136
- */
1137
- isFullscreen() {
1138
- return this.isFullscreenState();
1139
- }
1140
- //#endregion
1141
- //#region ---- Private Methods ----
1142
- /**
1143
- * Store original element styles
1144
- */
1145
- storeOriginalStyles(element) {
1146
- const computedStyle = window.getComputedStyle(element);
1147
- this.originalStyles = {
1148
- position: computedStyle.position,
1149
- top: computedStyle.top,
1150
- left: computedStyle.left,
1151
- width: computedStyle.width,
1152
- height: computedStyle.height,
1153
- zIndex: computedStyle.zIndex,
1154
- backgroundColor: computedStyle.backgroundColor,
1155
- margin: computedStyle.margin,
1156
- padding: computedStyle.padding,
1157
- };
1158
- }
1159
- /**
1160
- * Restore original element styles
1161
- */
1162
- restoreOriginalStyles(element) {
1163
- if (!this.originalStyles) {
1164
- return;
1165
- }
1166
- // Restore each style property
1167
- Object.entries(this.originalStyles).forEach(([key, value]) => {
1168
- const styleKey = this.camelToKebabCase(key);
1169
- if (value) {
1170
- this.renderer.setStyle(element, styleKey, value);
1171
- }
1172
- else {
1173
- this.renderer.removeStyle(element, styleKey);
1174
- }
1175
- });
1176
- this.originalStyles = {};
1177
- }
1178
- /**
1179
- * Convert camelCase to kebab-case for CSS properties
1180
- */
1181
- camelToKebabCase(str) {
1182
- return str.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, '$1-$2').toLowerCase();
1183
- }
1184
- //#endregion
1185
- //#region ---- Lifecycle ----
1186
- ngOnDestroy() {
1187
- // Exit fullscreen if still active
1188
- if (this.isFullscreenState()) {
1189
- this.exit().catch(() => {
1190
- // Ignore errors during cleanup
1191
- });
1192
- }
2303
+ clearClasses() {
2304
+ const currentClasses = this.el.nativeElement.className.split(' ');
2305
+ currentClasses.forEach((className) => {
2306
+ if (className.startsWith('ax-col-') || className.startsWith('ax-row-') || className.startsWith('ax-order-')) {
2307
+ this.renderer.removeClass(this.el.nativeElement, className);
2308
+ }
2309
+ });
1193
2310
  }
1194
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXFullscreenDirective, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Directive }); }
1195
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.12", type: AXFullscreenDirective, isStandalone: true, selector: "[axFullscreen]", outputs: { fullscreenChange: "fullscreenChange" }, exportAs: ["axFullscreen"], ngImport: i0 }); }
2311
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPGridLayoutDirective, deps: [{ token: i0.ElementRef }, { token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Directive }); }
2312
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: AXPGridLayoutDirective, isStandalone: true, selector: "[axp-grid-layout]", inputs: { options: { classPropertyName: "options", publicName: "axp-grid-layout", isSignal: true, isRequired: true, transformFunction: null } }, usesOnChanges: true, ngImport: i0 }); }
1196
2313
  }
1197
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXFullscreenDirective, decorators: [{
2314
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPGridLayoutDirective, decorators: [{
1198
2315
  type: Directive,
1199
2316
  args: [{
1200
- selector: '[axFullscreen]',
2317
+ selector: '[axp-grid-layout]',
1201
2318
  standalone: true,
1202
- exportAs: 'axFullscreen',
1203
2319
  }]
1204
- }], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { fullscreenChange: [{ type: i0.Output, args: ["fullscreenChange"] }] } });
2320
+ }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.Renderer2 }], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "axp-grid-layout", required: true }] }] } });
1205
2321
 
1206
2322
  const AXP_DISTRIBUTED_EVENT_LISTENER_PROVIDER = new InjectionToken('AXP_DISTRIBUTED_EVENT_LISTENER_PROVIDER');
1207
2323
 
@@ -1339,10 +2455,10 @@ class AXPDistributedEventListenerService {
1339
2455
  this.listenersByKey.clear();
1340
2456
  this.providersLoaded = false;
1341
2457
  }
1342
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPDistributedEventListenerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1343
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPDistributedEventListenerService, providedIn: 'root' }); }
2458
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPDistributedEventListenerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2459
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPDistributedEventListenerService, providedIn: 'root' }); }
1344
2460
  }
1345
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPDistributedEventListenerService, decorators: [{
2461
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPDistributedEventListenerService, decorators: [{
1346
2462
  type: Injectable,
1347
2463
  args: [{
1348
2464
  providedIn: 'root',
@@ -1466,220 +2582,777 @@ class AXPBroadcastEventService {
1466
2582
  // Cleanup multi-tab event history
1467
2583
  this.multiTabEventHistory.clear();
1468
2584
  }
1469
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPBroadcastEventService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1470
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPBroadcastEventService, providedIn: 'root' }); }
2585
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPBroadcastEventService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2586
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPBroadcastEventService, providedIn: 'root' }); }
1471
2587
  }
1472
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPBroadcastEventService, decorators: [{
2588
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPBroadcastEventService, decorators: [{
1473
2589
  type: Injectable,
1474
2590
  args: [{
1475
2591
  providedIn: 'root',
1476
2592
  }]
1477
2593
  }], ctorParameters: () => [] });
1478
2594
 
1479
- class AXPExpressionEvaluatorScopeProviderContext {
2595
+ class AXPHookService {
1480
2596
  constructor() {
1481
- this.scopes = {};
1482
- }
1483
- addScope(namespace, functions) {
1484
- this.scopes[namespace] = { ...this.scopes[namespace], ...functions };
2597
+ this.listenerProviders = inject(AXP_DISTRIBUTED_EVENT_LISTENER_PROVIDER, { optional: true }) || [];
2598
+ this.injector = inject(Injector);
1485
2599
  }
1486
- getScopes() {
1487
- return this.scopes;
2600
+ /**
2601
+ * Resolve all providers (handle both direct providers and Promise<provider>)
2602
+ */
2603
+ async resolveProviders() {
2604
+ return Promise.all(this.listenerProviders.map(p => p instanceof Promise ? p : Promise.resolve(p)));
1488
2605
  }
1489
- }
1490
- const AXP_EXPRESSION_EVALUATOR_SCOPE_PROVIDER = new InjectionToken('AXP_EXPRESSION_EVALUATOR_SCOPE_PROVIDER');
1491
- class AXPExpressionEvaluatorScopeProviderService {
1492
- constructor() {
1493
- this.providers = inject(AXP_EXPRESSION_EVALUATOR_SCOPE_PROVIDER, { optional: true });
1494
- this.cache = null;
2606
+ /**
2607
+ * Fire sync hooks (fire-and-forget).
2608
+ * All providers with the given key will be executed.
2609
+ * Execution is not awaited.
2610
+ */
2611
+ fire(key, data) {
2612
+ this.resolveProviders().then(providers => {
2613
+ providers
2614
+ .filter(p => p.key === key)
2615
+ .sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0))
2616
+ .forEach(p => {
2617
+ try {
2618
+ runInInjectionContext(this.injector, () => p.execute(data));
2619
+ }
2620
+ catch (err) {
2621
+ console.error(`[AXPHookService] Hook '${key}' failed`, err);
2622
+ }
2623
+ });
2624
+ });
1495
2625
  }
1496
- async load() {
1497
- if (this.cache)
1498
- return;
1499
- const context = new AXPExpressionEvaluatorScopeProviderContext();
1500
- if (Array.isArray(this.providers)) {
1501
- for (const provider of this.providers) {
1502
- await provider.provide(context);
1503
- }
2626
+ /**
2627
+ * Run async hooks sequentially (waterfall).
2628
+ * The output of each hook is passed as input to the next hook.
2629
+ * Returns the final merged data after all hooks are executed.
2630
+ */
2631
+ async runAsync(key, initialData) {
2632
+ const providers = (await this.resolveProviders())
2633
+ .filter(p => p.key === key)
2634
+ .sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0));
2635
+ let data = initialData;
2636
+ for (const p of providers) {
2637
+ data = await Promise.resolve(runInInjectionContext(this.injector, () => p.execute(data)));
1504
2638
  }
1505
- this.cache = context.getScopes();
1506
- }
1507
- async getScopesAsync() {
1508
- await this.load();
1509
- return this.cache || {};
2639
+ return data;
1510
2640
  }
1511
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPExpressionEvaluatorScopeProviderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1512
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPExpressionEvaluatorScopeProviderService, providedIn: 'root' }); }
2641
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPHookService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2642
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPHookService, providedIn: 'root' }); }
1513
2643
  }
1514
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPExpressionEvaluatorScopeProviderService, decorators: [{
2644
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPHookService, decorators: [{
1515
2645
  type: Injectable,
1516
2646
  args: [{ providedIn: 'root' }]
1517
2647
  }] });
1518
2648
 
1519
- class AXPExpressionEvaluatorService {
1520
- constructor() {
1521
- // Memoization cache for compiled expressions
1522
- this.expressionCache = new Map();
1523
- this.providerService = inject(AXPExpressionEvaluatorScopeProviderService);
2649
+ //#region ---- Types ----
2650
+ /** Priority tier — higher values win when multiple bindings match. */
2651
+ var AXPKeyboardShortcutPriority;
2652
+ (function (AXPKeyboardShortcutPriority) {
2653
+ AXPKeyboardShortcutPriority[AXPKeyboardShortcutPriority["Global"] = 0] = "Global";
2654
+ AXPKeyboardShortcutPriority[AXPKeyboardShortcutPriority["Page"] = 10] = "Page";
2655
+ AXPKeyboardShortcutPriority[AXPKeyboardShortcutPriority["Panel"] = 20] = "Panel";
2656
+ AXPKeyboardShortcutPriority[AXPKeyboardShortcutPriority["Modal"] = 100] = "Modal";
2657
+ })(AXPKeyboardShortcutPriority || (AXPKeyboardShortcutPriority = {}));
2658
+ //#endregion
2659
+
2660
+ //#region ---- Overlay Layer DOM Utilities ----
2661
+ /** Acorex overlay root (modal shell, popup, dialog). */
2662
+ const AX_OVERLAY_CONTAINER_SELECTOR = '.ax-overlay-container';
2663
+ /** Anchored widget panel inside an overlay (select, date picker, dropdown). */
2664
+ const AX_OVERLAY_PANE_SELECTOR = '.ax-overlay-pane';
2665
+ /**
2666
+ * True when a visible overlay layer is foreground (modal, popup, widget popover/picker).
2667
+ */
2668
+ function hasForegroundOverlayLayer(document) {
2669
+ if (getVisibleAnchoredOverlayPanes(document).length > 0) {
2670
+ return true;
1524
2671
  }
1525
- getOrCompileFunction(expression) {
1526
- if (!this.expressionCache.has(expression)) {
1527
- // Check if expression contains multiple statements (has semicolons or newlines)
1528
- const hasMultipleStatements = expression.includes(';') || expression.includes('\n');
1529
- let fn;
1530
- if (hasMultipleStatements) {
1531
- // For multiple statements, execute them in sequence and return the last expression
1532
- fn = new Function('scope', `with (scope) { return (async function() { ${expression} })(); }`);
1533
- }
1534
- else {
1535
- // For single expressions, use return
1536
- fn = new Function('scope', `with (scope) { return (async function() { return ${expression}; })(); }`);
1537
- }
1538
- this.expressionCache.set(expression, fn);
2672
+ return getVisibleOverlayContainers(document).length > 0;
2673
+ }
2674
+ /**
2675
+ * Returns visible anchored overlay panes (widget popovers/pickers).
2676
+ */
2677
+ function getVisibleAnchoredOverlayPanes(document) {
2678
+ return Array.from(document.querySelectorAll(AX_OVERLAY_PANE_SELECTOR)).filter(isVisibleOverlayElement);
2679
+ }
2680
+ /**
2681
+ * Visible anchored panes that are not contained by `excludeContainer` (e.g. dialog shell).
2682
+ */
2683
+ function getNestedVisibleOverlayPanes(document, excludeContainer) {
2684
+ return getVisibleAnchoredOverlayPanes(document).filter((pane) => !excludeContainer?.contains(pane));
2685
+ }
2686
+ /**
2687
+ * Collects visible overlay containers in stacking order (later / higher z-index wins).
2688
+ */
2689
+ function getVisibleOverlayContainers(document) {
2690
+ return Array.from(document.querySelectorAll(AX_OVERLAY_CONTAINER_SELECTOR))
2691
+ .filter(isVisibleOverlayElement)
2692
+ .sort(compareOverlayStackOrder);
2693
+ }
2694
+ /** Topmost visible overlay container, if any. */
2695
+ function getTopVisibleOverlayContainer(document) {
2696
+ const containers = getVisibleOverlayContainers(document);
2697
+ return containers.length ? containers[containers.length - 1] : null;
2698
+ }
2699
+ /** Nearest overlay container ancestor of `element`. */
2700
+ function findOverlayContainerAncestor(element) {
2701
+ return element.closest(AX_OVERLAY_CONTAINER_SELECTOR);
2702
+ }
2703
+ /**
2704
+ * Returns true when the overlay element is rendered and visible in the viewport.
2705
+ */
2706
+ function isVisibleOverlayElement(element) {
2707
+ if (!(element instanceof HTMLElement)) {
2708
+ return false;
2709
+ }
2710
+ const style = getComputedStyle(element);
2711
+ if (style.display === 'none' || style.visibility === 'hidden' || Number(style.opacity) === 0) {
2712
+ return false;
2713
+ }
2714
+ const rect = element.getBoundingClientRect();
2715
+ return rect.width > 0 && rect.height > 0;
2716
+ }
2717
+ function compareOverlayStackOrder(a, b) {
2718
+ const za = Number(getComputedStyle(a).zIndex) || 0;
2719
+ const zb = Number(getComputedStyle(b).zIndex) || 0;
2720
+ if (za !== zb) {
2721
+ return za - zb;
2722
+ }
2723
+ const position = a.compareDocumentPosition(b);
2724
+ if (position & Node.DOCUMENT_POSITION_FOLLOWING) {
2725
+ return -1;
2726
+ }
2727
+ if (position & Node.DOCUMENT_POSITION_PRECEDING) {
2728
+ return 1;
2729
+ }
2730
+ return 0;
2731
+ }
2732
+ //#endregion
2733
+
2734
+ //#region ---- Imports ----
2735
+ const HORIZONTAL_DIRECTIONAL_KEY_MIRRORS = {
2736
+ arrowleft: 'arrowright',
2737
+ arrowright: 'arrowleft',
2738
+ left: 'right',
2739
+ right: 'left',
2740
+ '[': ']',
2741
+ ']': '[',
2742
+ };
2743
+ /** Maps a horizontal direction key to its RTL semantic counterpart. */
2744
+ function mirrorHorizontalDirectionalKey(key) {
2745
+ const normalized = normalizeShortcutToken(key);
2746
+ return HORIZONTAL_DIRECTIONAL_KEY_MIRRORS[normalized] ?? normalized;
2747
+ }
2748
+ /** True when the chord's primary key is a horizontal direction key (`←`/`→`, `[`/`]`). */
2749
+ function isHorizontalDirectionalShortcutKey(key) {
2750
+ return normalizeShortcutToken(key) in HORIZONTAL_DIRECTIONAL_KEY_MIRRORS;
2751
+ }
2752
+ /**
2753
+ * Resolves direction behavior for a chord.
2754
+ * Horizontal direction keys default to `semantic`; others default to `physical`.
2755
+ * An explicit binding value always wins.
2756
+ */
2757
+ function resolveEffectiveDirectionBehavior(chord, explicit) {
2758
+ if (explicit === 'physical' || explicit === 'semantic') {
2759
+ return explicit;
2760
+ }
2761
+ const { key } = parseKeyboardShortcutChord(chord);
2762
+ return isHorizontalDirectionalShortcutKey(key) ? 'semantic' : 'physical';
2763
+ }
2764
+ /** Resolves the key the user presses for a semantically registered chord in RTL. */
2765
+ function resolveSemanticDirectionalKey(storedKey, rtl) {
2766
+ const normalized = normalizeShortcutToken(storedKey);
2767
+ if (!rtl) {
2768
+ return normalized;
2769
+ }
2770
+ return mirrorHorizontalDirectionalKey(normalized);
2771
+ }
2772
+ /** Chord string with directional key mirrored for RTL display/matching when semantic. */
2773
+ function resolveDisplayShortcutChord(chord, isRtl, explicitDirectionBehavior) {
2774
+ const directionBehavior = resolveEffectiveDirectionBehavior(chord, explicitDirectionBehavior);
2775
+ const parts = chord
2776
+ .trim()
2777
+ .split('+')
2778
+ .map((part) => part.trim())
2779
+ .filter(Boolean);
2780
+ if (parts.length === 0) {
2781
+ return chord;
2782
+ }
2783
+ const keyPart = parts.pop() ?? '';
2784
+ const rtl = directionBehavior === 'semantic' && isRtl;
2785
+ const resolvedKey = resolveSemanticDirectionalKey(keyPart, rtl);
2786
+ return [...parts, resolvedKey].join('+');
2787
+ }
2788
+ const MODIFIER_ALIASES = {
2789
+ ctrl: 'ctrl',
2790
+ control: 'ctrl',
2791
+ shift: 'shift',
2792
+ alt: 'alt',
2793
+ option: 'alt',
2794
+ meta: 'meta',
2795
+ cmd: 'meta',
2796
+ command: 'meta',
2797
+ win: 'meta',
2798
+ };
2799
+ const KEY_DISPLAY_MAC = {
2800
+ ctrl: '⌃',
2801
+ control: '⌃',
2802
+ shift: '⇧',
2803
+ alt: '⌥',
2804
+ option: '⌥',
2805
+ meta: '⌘',
2806
+ cmd: '⌘',
2807
+ command: '⌘',
2808
+ win: '⌘',
2809
+ enter: '↵',
2810
+ escape: 'Esc',
2811
+ esc: 'Esc',
2812
+ home: 'Home',
2813
+ arrowleft: '←',
2814
+ arrowright: '→',
2815
+ arrowup: '↑',
2816
+ arrowdown: '↓',
2817
+ pageup: 'PgUp',
2818
+ pagedown: 'PgDn',
2819
+ backspace: '⌫',
2820
+ delete: '⌦',
2821
+ space: 'Space',
2822
+ };
2823
+ /**
2824
+ * Parses a shortcut chord such as `Enter`, `Escape`, or `ctrl+shift+s`.
2825
+ */
2826
+ function parseKeyboardShortcutChord(chord) {
2827
+ const parts = chord
2828
+ .trim()
2829
+ .toLowerCase()
2830
+ .split('+')
2831
+ .map((part) => part.trim())
2832
+ .filter(Boolean);
2833
+ const key = normalizeShortcutToken(parts.pop() ?? '');
2834
+ const parsed = {
2835
+ ctrl: false,
2836
+ shift: false,
2837
+ alt: false,
2838
+ meta: false,
2839
+ key,
2840
+ };
2841
+ for (const part of parts) {
2842
+ const alias = MODIFIER_ALIASES[part];
2843
+ if (alias) {
2844
+ parsed[alias] = true;
1539
2845
  }
1540
- return this.expressionCache.get(expression);
1541
2846
  }
1542
- async getMergedScope(userScope) {
1543
- const pluginScopes = await this.providerService.getScopesAsync();
1544
- // Merge pluginScopes and userScope (userScope takes precedence)
1545
- return { ...pluginScopes, ...userScope };
2847
+ return parsed;
2848
+ }
2849
+ /**
2850
+ * Returns true when the keyboard event matches the given chord.
2851
+ * On macOS, `ctrl+*` chords also match `meta+*` for common platform shortcuts.
2852
+ */
2853
+ function matchesKeyboardShortcutChord(event, chord, options) {
2854
+ const parsed = parseKeyboardShortcutChord(chord);
2855
+ const mac = isMacPlatform();
2856
+ const directionBehavior = resolveEffectiveDirectionBehavior(chord, options?.directionBehavior);
2857
+ if (!matchesShortcutModifiers(parsed, event, mac)) {
2858
+ return false;
1546
2859
  }
1547
- async evaluate(source, scope = {}) {
1548
- try {
1549
- const mergedScope = await this.getMergedScope(scope);
1550
- if (typeof source === 'string' && source.includes('{{')) {
1551
- return await this.evaluateStringExpression(source, mergedScope);
1552
- }
1553
- else if (Array.isArray(source)) {
1554
- const evaluatedArray = [];
1555
- for (const item of source) {
1556
- evaluatedArray.push(await this.evaluate(item, mergedScope));
1557
- }
1558
- return evaluatedArray;
1559
- }
1560
- else if (typeof source === 'object' && source !== null) {
1561
- const evaluatedObject = {};
1562
- for (const key in source) {
1563
- if (source.hasOwnProperty(key)) {
1564
- evaluatedObject[key] = await this.evaluate(source[key], mergedScope);
1565
- }
1566
- }
1567
- return evaluatedObject;
1568
- }
1569
- else {
1570
- return source;
1571
- }
2860
+ const actualKey = normalizeShortcutEventKey(event);
2861
+ const expectedKey = normalizeShortcutToken(parsed.key);
2862
+ const rtlSemantic = directionBehavior === 'semantic' &&
2863
+ options?.isRtl === true &&
2864
+ isHorizontalDirectionalShortcutKey(expectedKey);
2865
+ if (rtlSemantic) {
2866
+ return actualKey === mirrorHorizontalDirectionalKey(expectedKey);
2867
+ }
2868
+ return actualKey === expectedKey;
2869
+ }
2870
+ /**
2871
+ * Matches modifier keys for a parsed chord.
2872
+ * `ctrl+alt` chords also accept AltGraph (Windows) so layout-produced characters still match physical keys.
2873
+ */
2874
+ function matchesShortcutModifiers(parsed, event, mac) {
2875
+ if (parsed.shift !== event.shiftKey) {
2876
+ return false;
2877
+ }
2878
+ const altGraph = hasAltGraphModifier(event);
2879
+ if (parsed.ctrl && parsed.alt) {
2880
+ if (altGraph) {
2881
+ return true;
1572
2882
  }
1573
- catch (error) {
1574
- console.error('Expression evaluator - Error evaluating expression:', source, error);
2883
+ return event.ctrlKey && event.altKey;
2884
+ }
2885
+ if (parsed.alt !== event.altKey) {
2886
+ return false;
2887
+ }
2888
+ if (parsed.meta) {
2889
+ if (!event.metaKey) {
2890
+ return false;
2891
+ }
2892
+ return true;
2893
+ }
2894
+ if (parsed.ctrl) {
2895
+ if (mac) {
2896
+ return event.metaKey || event.ctrlKey;
2897
+ }
2898
+ return event.ctrlKey;
2899
+ }
2900
+ if (event.ctrlKey || event.metaKey) {
2901
+ return false;
2902
+ }
2903
+ return true;
2904
+ }
2905
+ function hasAltGraphModifier(event) {
2906
+ return typeof event.getModifierState === 'function' && event.getModifierState('AltGraph');
2907
+ }
2908
+ /**
2909
+ * Formats one chord for UI display (OS-aware modifier symbols).
2910
+ */
2911
+ function formatKeyboardShortcutChord(chord, options) {
2912
+ const directionBehavior = resolveEffectiveDirectionBehavior(chord, options?.directionBehavior);
2913
+ const resolvedChord = options?.skipDirectionResolve
2914
+ ? chord
2915
+ : resolveDisplayShortcutChord(chord, options?.isRtl === true, directionBehavior);
2916
+ const mac = isMacPlatform();
2917
+ const parts = resolvedChord
2918
+ .trim()
2919
+ .split('+')
2920
+ .map((part) => part.trim())
2921
+ .filter(Boolean);
2922
+ if (parts.length === 0) {
2923
+ return '';
2924
+ }
2925
+ const keyPart = normalizeShortcutToken(parts.pop() ?? '');
2926
+ const modifierParts = parts.map((part) => formatShortcutPart(part.toLowerCase(), mac));
2927
+ const keyDisplay = formatShortcutPart(keyPart, mac);
2928
+ if (mac) {
2929
+ return [...modifierParts, keyDisplay].join('');
2930
+ }
2931
+ return [...modifierParts, keyDisplay].join('+');
2932
+ }
2933
+ /**
2934
+ * Formats multiple chords as a single display string (e.g. `← / PgUp`).
2935
+ */
2936
+ function formatKeyboardShortcutChords(chords, options) {
2937
+ return chords.map((chord) => formatKeyboardShortcutChord(chord, options)).join(' / ');
2938
+ }
2939
+ /** Maps a chord to `ax-kbd-item` key parts (modifiers + display symbols). */
2940
+ function chordToKbdItemKeys(chord, options) {
2941
+ const directionBehavior = resolveEffectiveDirectionBehavior(chord, options?.directionBehavior);
2942
+ const resolvedChord = options?.skipDirectionResolve
2943
+ ? chord
2944
+ : resolveDisplayShortcutChord(chord, options?.isRtl === true, directionBehavior);
2945
+ const mac = isMacPlatform();
2946
+ const symbolKeys = {
2947
+ arrowleft: '←',
2948
+ arrowright: '→',
2949
+ arrowup: '↑',
2950
+ arrowdown: '↓',
2951
+ pageup: 'PgUp',
2952
+ pagedown: 'PgDn',
2953
+ enter: '↵',
2954
+ escape: 'Esc',
2955
+ esc: 'Esc',
2956
+ space: 'Space',
2957
+ '[': '[',
2958
+ ']': ']',
2959
+ };
2960
+ return resolvedChord
2961
+ .trim()
2962
+ .split('+')
2963
+ .map((part) => part.trim())
2964
+ .filter(Boolean)
2965
+ .map((part) => {
2966
+ const normalized = part.toLowerCase();
2967
+ if (mac && (normalized === 'ctrl' || normalized === 'control')) {
2968
+ return '⌘';
2969
+ }
2970
+ return symbolKeys[normalized] ?? part;
2971
+ });
2972
+ }
2973
+ /**
2974
+ * When focus is in a native edit control, avoid stealing plain key shortcuts.
2975
+ */
2976
+ function isKeyboardTargetInsideEditableField(target) {
2977
+ if (!(target instanceof HTMLElement)) {
2978
+ return false;
2979
+ }
2980
+ if (target.isContentEditable) {
2981
+ return true;
2982
+ }
2983
+ const field = target.closest('input, textarea, select');
2984
+ if (!field || field.disabled) {
2985
+ return false;
2986
+ }
2987
+ if (field instanceof HTMLInputElement) {
2988
+ const type = field.type;
2989
+ if (type === 'hidden' ||
2990
+ type === 'checkbox' ||
2991
+ type === 'radio' ||
2992
+ type === 'button' ||
2993
+ type === 'submit' ||
2994
+ type === 'reset' ||
2995
+ type === 'file' ||
2996
+ field.readOnly) {
1575
2997
  return false;
1576
2998
  }
2999
+ return true;
3000
+ }
3001
+ if (field instanceof HTMLTextAreaElement) {
3002
+ return !field.readOnly;
3003
+ }
3004
+ return true;
3005
+ }
3006
+ /**
3007
+ * Returns true when Esc should close an open widget overlay first (select, date picker, popover, modal).
3008
+ * Used by the global shortcut registry so page-level Esc bindings do not swallow overlay dismiss.
3009
+ */
3010
+ function shouldDeferEscapeToOpenOverlay(document) {
3011
+ return hasForegroundOverlayLayer(document);
3012
+ }
3013
+ function isMacPlatform() {
3014
+ if (typeof navigator === 'undefined') {
3015
+ return false;
3016
+ }
3017
+ return /Mac|iPhone|iPad|iPod/i.test(navigator.userAgent);
3018
+ }
3019
+ function normalizeShortcutToken(key) {
3020
+ const normalized = key.trim().toLowerCase();
3021
+ if (normalized === 'esc') {
3022
+ return 'escape';
3023
+ }
3024
+ if (normalized === 'left') {
3025
+ return 'arrowleft';
3026
+ }
3027
+ if (normalized === 'right') {
3028
+ return 'arrowright';
3029
+ }
3030
+ if (normalized === 'up') {
3031
+ return 'arrowup';
3032
+ }
3033
+ if (normalized === 'down') {
3034
+ return 'arrowdown';
3035
+ }
3036
+ if (normalized === '[' || normalized === 'bracketleft') {
3037
+ return '[';
3038
+ }
3039
+ if (normalized === ']' || normalized === 'bracketright') {
3040
+ return ']';
3041
+ }
3042
+ return normalized;
3043
+ }
3044
+ function normalizeShortcutEventKey(event) {
3045
+ if (event.key === 'Enter') {
3046
+ return 'enter';
3047
+ }
3048
+ if (event.key === 'Escape') {
3049
+ return 'escape';
3050
+ }
3051
+ if (event.key === 'Home') {
3052
+ return 'home';
3053
+ }
3054
+ if (event.key === ' ') {
3055
+ return 'space';
3056
+ }
3057
+ if (event.key === '[' || event.code === 'BracketLeft') {
3058
+ return '[';
3059
+ }
3060
+ if (event.key === ']' || event.code === 'BracketRight') {
3061
+ return ']';
3062
+ }
3063
+ const codeMatch = event.code.match(/^Key([A-Z])$/);
3064
+ if (codeMatch) {
3065
+ return codeMatch[1].toLowerCase();
3066
+ }
3067
+ const digitMatch = event.code.match(/^Digit([0-9])$/);
3068
+ if (digitMatch) {
3069
+ return digitMatch[1];
3070
+ }
3071
+ if (event.key.length === 1) {
3072
+ return event.key.toLowerCase();
3073
+ }
3074
+ const fnMatch = event.code.match(/^F(\d+)$/);
3075
+ if (fnMatch) {
3076
+ return `f${fnMatch[1]}`;
3077
+ }
3078
+ return event.key.toLowerCase();
3079
+ }
3080
+ function formatShortcutPart(part, mac) {
3081
+ const normalized = normalizeShortcutToken(part);
3082
+ if (mac && KEY_DISPLAY_MAC[normalized]) {
3083
+ return KEY_DISPLAY_MAC[normalized];
3084
+ }
3085
+ if (!mac && MODIFIER_ALIASES[normalized]) {
3086
+ return normalized.charAt(0).toUpperCase() + normalized.slice(1);
3087
+ }
3088
+ if (KEY_DISPLAY_MAC[normalized]) {
3089
+ return KEY_DISPLAY_MAC[normalized];
3090
+ }
3091
+ if (normalized.length === 1) {
3092
+ return normalized.toUpperCase();
3093
+ }
3094
+ return normalized.charAt(0).toUpperCase() + normalized.slice(1);
3095
+ }
3096
+ //#endregion
3097
+
3098
+ //#region ---- Keyboard Shortcut Declaration Utilities ----
3099
+ /**
3100
+ * Normalizes a declarative shortcut (`'ctrl+s'` or `{ keys: 'home', allowInEditableFields: false }`).
3101
+ */
3102
+ function normalizeKeyboardShortcut(shortcut) {
3103
+ if (typeof shortcut === 'string') {
3104
+ const chord = shortcut.trim();
3105
+ if (!chord) {
3106
+ return undefined;
3107
+ }
3108
+ return { keys: [chord] };
1577
3109
  }
1578
- async evaluateStringExpression(templateExpression, scope) {
1579
- // Check if the input is exactly a single {{ ... }} expression (handle multiline)
1580
- const exactMatch = templateExpression.match(/^\s*\{\{\s*([\s\S]*?)\s*\}\}\s*$/);
1581
- if (exactMatch) {
1582
- const expression = exactMatch[1];
1583
- const sandbox = this.getOrCompileFunction(expression);
1584
- const result = await sandbox(scope);
1585
- return result;
1586
- }
1587
- // Otherwise, interpolate all {{ ... }} expressions in the string
1588
- const regex = /\{\{\s*([\s\S]*?)\s*\}\}/g;
1589
- // Collect all matches and their positions
1590
- const matches = [];
1591
- let match;
1592
- while ((match = regex.exec(templateExpression)) !== null) {
1593
- matches.push({
1594
- expression: match[1],
1595
- start: match.index,
1596
- end: regex.lastIndex,
1597
- raw: match[0],
1598
- });
1599
- }
1600
- // Evaluate all expressions in parallel
1601
- const values = await Promise.all(matches.map((m) => {
1602
- const sandbox = this.getOrCompileFunction(m.expression);
1603
- return sandbox(scope);
1604
- }));
1605
- // Reconstruct the string with evaluated values
1606
- let result = '';
1607
- let lastIndex = 0;
1608
- matches.forEach((m, i) => {
1609
- result += templateExpression.slice(lastIndex, m.start);
1610
- const value = values[i];
1611
- result += value !== undefined && value !== null ? value : '';
1612
- lastIndex = m.end;
1613
- });
1614
- result += templateExpression.slice(lastIndex);
1615
- return result;
3110
+ return normalizeKeyboardShortcutConfig(shortcut);
3111
+ }
3112
+ /**
3113
+ * Normalizes an array of declarative shortcuts. Empty or invalid entries are skipped.
3114
+ */
3115
+ function normalizeKeyboardShortcuts(shortcuts) {
3116
+ if (!shortcuts?.length) {
3117
+ return [];
1616
3118
  }
1617
- isExpression(expression) {
1618
- if (typeof expression === 'string') {
1619
- return expression.includes('{{');
3119
+ const result = [];
3120
+ for (const shortcut of shortcuts) {
3121
+ const normalized = normalizeKeyboardShortcut(shortcut);
3122
+ if (normalized) {
3123
+ result.push(normalized);
1620
3124
  }
1621
- return false;
1622
3125
  }
1623
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPExpressionEvaluatorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1624
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPExpressionEvaluatorService, providedIn: 'root' }); }
3126
+ return result;
1625
3127
  }
1626
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPExpressionEvaluatorService, decorators: [{
1627
- type: Injectable,
1628
- args: [{ providedIn: 'root' }]
1629
- }] });
3128
+ /** Returns the first chord from declarative shortcuts (for UI hints such as `ax-kbd`). */
3129
+ function getPrimaryKeyboardShortcutChord(shortcuts) {
3130
+ return normalizeKeyboardShortcuts(shortcuts)[0]?.keys[0];
3131
+ }
3132
+ function normalizeKeyboardShortcutConfig(shortcut) {
3133
+ const keys = Array.isArray(shortcut.keys) ? shortcut.keys : [shortcut.keys];
3134
+ const normalizedKeys = keys.map((chord) => chord.trim()).filter(Boolean);
3135
+ if (normalizedKeys.length === 0) {
3136
+ return undefined;
3137
+ }
3138
+ return {
3139
+ keys: normalizedKeys,
3140
+ allowInEditableFields: shortcut.allowInEditableFields,
3141
+ when: shortcut.when,
3142
+ directionBehavior: shortcut.directionBehavior,
3143
+ };
3144
+ }
3145
+ //#endregion
1630
3146
 
1631
- class AXPHookService {
3147
+ //#region ---- Imports ----
3148
+ //#endregion
3149
+ class AXPKeyboardShortcutRegistry {
1632
3150
  constructor() {
1633
- this.listenerProviders = inject(AXP_DISTRIBUTED_EVENT_LISTENER_PROVIDER, { optional: true }) || [];
1634
- this.injector = inject(Injector);
3151
+ //#region ---- Services & Dependencies ----
3152
+ this.document = inject(DOCUMENT);
3153
+ this.localeService = inject(AXLocaleService);
3154
+ this.activeProfile = toSignal(this.localeService.profileChanged$, {
3155
+ initialValue: this.localeService.activeProfile(),
3156
+ });
3157
+ this.registrations = signal([], ...(ngDevMode ? [{ debugName: "registrations" }] : /* istanbul ignore next */ []));
3158
+ this.helpDialogHandler = null;
3159
+ this.listenerAttached = false;
3160
+ //#endregion
3161
+ //#region ---- Computed Properties ----
3162
+ /** Active shortcut entries for the help dialog. */
3163
+ this.helpEntries = computed(() => {
3164
+ this.activeProfile();
3165
+ return this.buildHelpEntries();
3166
+ }, ...(ngDevMode ? [{ debugName: "helpEntries" }] : /* istanbul ignore next */ []));
3167
+ this.onDocumentKeyDown = (event) => {
3168
+ if (event.key === 'Escape' && shouldDeferEscapeToOpenOverlay(this.document)) {
3169
+ return;
3170
+ }
3171
+ if (event.key === 'F1') {
3172
+ event.preventDefault();
3173
+ event.stopImmediatePropagation();
3174
+ if (hasForegroundOverlayLayer(this.document)) {
3175
+ return;
3176
+ }
3177
+ }
3178
+ const candidates = this.resolveActiveCandidates(event);
3179
+ if (candidates.length === 0) {
3180
+ return;
3181
+ }
3182
+ const match = candidates[0];
3183
+ event.preventDefault();
3184
+ event.stopImmediatePropagation();
3185
+ void match.binding.handler(event);
3186
+ };
1635
3187
  }
3188
+ //#endregion
3189
+ //#region ---- Public Methods ----
1636
3190
  /**
1637
- * Resolve all providers (handle both direct providers and Promise<provider>)
3191
+ * Registers a scope of shortcuts. Unregisters automatically when `owner` is destroyed.
1638
3192
  */
1639
- async resolveProviders() {
1640
- return Promise.all(this.listenerProviders.map(p => p instanceof Promise ? p : Promise.resolve(p)));
3193
+ register(options) {
3194
+ const { owner, ...registrationOptions } = options;
3195
+ const registrationId = `${options.id}-${this.createRegistrationId()}`;
3196
+ const entry = {
3197
+ ...registrationOptions,
3198
+ registrationId,
3199
+ priority: options.priority ?? 0,
3200
+ };
3201
+ this.registrations.update((items) => [
3202
+ ...items.filter((item) => item.id !== options.id),
3203
+ entry,
3204
+ ]);
3205
+ this.ensureListener();
3206
+ try {
3207
+ owner.onDestroy(() => {
3208
+ this.unregister(registrationId);
3209
+ });
3210
+ }
3211
+ catch {
3212
+ // Owner view was already destroyed (e.g. async registration after navigation).
3213
+ this.unregister(registrationId);
3214
+ }
1641
3215
  }
1642
3216
  /**
1643
- * Fire sync hooks (fire-and-forget).
1644
- * All providers with the given key will be executed.
1645
- * Execution is not awaited.
3217
+ * Wires the F1 help dialog opener (provided by platform/common).
1646
3218
  */
1647
- fire(key, data) {
1648
- this.resolveProviders().then(providers => {
1649
- providers
1650
- .filter(p => p.key === key)
1651
- .sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0))
1652
- .forEach(p => {
1653
- try {
1654
- runInInjectionContext(this.injector, () => p.execute(data));
3219
+ setHelpDialogHandler(handler) {
3220
+ this.helpDialogHandler = handler;
3221
+ this.ensureHelpShortcutRegistered();
3222
+ }
3223
+ /** OS-aware display string for UI hints. */
3224
+ formatDisplayKeys(chords) {
3225
+ return formatKeyboardShortcutChords(chords, { isRtl: this.isRtlProfile() });
3226
+ }
3227
+ //#endregion
3228
+ //#region ---- Private Methods ----
3229
+ ensureHelpShortcutRegistered() {
3230
+ const alreadyRegistered = this.registrations().some((entry) => entry.id === 'system:keyboard-shortcuts-help');
3231
+ if (alreadyRegistered || !this.helpDialogHandler) {
3232
+ return;
3233
+ }
3234
+ const handler = this.helpDialogHandler;
3235
+ const entry = {
3236
+ registrationId: 'system:keyboard-shortcuts-help',
3237
+ id: 'system:keyboard-shortcuts-help',
3238
+ priority: -1,
3239
+ scope: '@general:keyboard-shortcuts.groups.system',
3240
+ shortcuts: [
3241
+ {
3242
+ keys: ['f1'],
3243
+ title: '@general:keyboard-shortcuts.help-action',
3244
+ allowInEditableFields: true,
3245
+ handler: () => {
3246
+ void handler();
3247
+ },
3248
+ },
3249
+ ],
3250
+ };
3251
+ this.registrations.update((items) => [...items, entry]);
3252
+ this.ensureListener();
3253
+ }
3254
+ createRegistrationId() {
3255
+ if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
3256
+ return crypto.randomUUID();
3257
+ }
3258
+ return `${Date.now()}-${Math.random().toString(36).slice(2)}`;
3259
+ }
3260
+ unregister(registrationId) {
3261
+ this.registrations.update((items) => items.filter((item) => item.registrationId !== registrationId));
3262
+ }
3263
+ ensureListener() {
3264
+ if (this.listenerAttached || typeof this.document === 'undefined') {
3265
+ return;
3266
+ }
3267
+ this.document.addEventListener('keydown', this.onDocumentKeyDown, true);
3268
+ this.listenerAttached = true;
3269
+ }
3270
+ resolveActiveCandidates(event) {
3271
+ const editable = isKeyboardTargetInsideEditableField(event.target);
3272
+ const candidates = [];
3273
+ for (const registration of this.registrations()) {
3274
+ if (!this.isRegistrationActive(registration)) {
3275
+ continue;
3276
+ }
3277
+ const priority = registration.priority ?? 0;
3278
+ for (const binding of registration.shortcuts) {
3279
+ if (binding.when && !binding.when()) {
3280
+ continue;
1655
3281
  }
1656
- catch (err) {
1657
- console.error(`[AXPHookService] Hook '${key}' failed`, err);
3282
+ if (editable && !binding.allowInEditableFields) {
3283
+ continue;
1658
3284
  }
1659
- });
1660
- });
3285
+ for (const chord of binding.keys) {
3286
+ const directionBehavior = resolveEffectiveDirectionBehavior(chord, binding.directionBehavior);
3287
+ if (!matchesKeyboardShortcutChord(event, chord, {
3288
+ directionBehavior,
3289
+ isRtl: this.isRtlProfile(),
3290
+ })) {
3291
+ continue;
3292
+ }
3293
+ candidates.push({
3294
+ priority,
3295
+ registration,
3296
+ binding,
3297
+ chord,
3298
+ });
3299
+ break;
3300
+ }
3301
+ }
3302
+ }
3303
+ return candidates.sort((a, b) => b.priority - a.priority);
1661
3304
  }
1662
- /**
1663
- * Run async hooks sequentially (waterfall).
1664
- * The output of each hook is passed as input to the next hook.
1665
- * Returns the final merged data after all hooks are executed.
1666
- */
1667
- async runAsync(key, initialData) {
1668
- const providers = (await this.resolveProviders())
1669
- .filter(p => p.key === key)
1670
- .sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0));
1671
- let data = initialData;
1672
- for (const p of providers) {
1673
- data = await Promise.resolve(runInInjectionContext(this.injector, () => p.execute(data)));
3305
+ isRegistrationActive(registration) {
3306
+ if (registration.when && !registration.when()) {
3307
+ return false;
1674
3308
  }
1675
- return data;
3309
+ const element = registration.elementRef?.nativeElement;
3310
+ if (element && !this.document.contains(element)) {
3311
+ return false;
3312
+ }
3313
+ return true;
3314
+ }
3315
+ isRtlProfile() {
3316
+ return this.activeProfile()?.i18nMeta?.rtl === true;
3317
+ }
3318
+ buildHelpEntries() {
3319
+ const entries = [];
3320
+ const isRtl = this.isRtlProfile();
3321
+ for (const registration of this.registrations()) {
3322
+ if (!this.isRegistrationActive(registration)) {
3323
+ continue;
3324
+ }
3325
+ if (registration.id === 'system:keyboard-shortcuts-help') {
3326
+ continue;
3327
+ }
3328
+ const priority = registration.priority ?? 0;
3329
+ for (const binding of registration.shortcuts) {
3330
+ const displayChords = binding.keys.map((chord) => {
3331
+ const directionBehavior = resolveEffectiveDirectionBehavior(chord, binding.directionBehavior);
3332
+ return resolveDisplayShortcutChord(chord, isRtl, directionBehavior);
3333
+ });
3334
+ entries.push({
3335
+ registrationId: registration.registrationId,
3336
+ bindingTitle: binding.title,
3337
+ chords: [...binding.keys],
3338
+ displayChords,
3339
+ displayKeys: formatKeyboardShortcutChords(displayChords, { skipDirectionResolve: true }),
3340
+ scope: registration.scope,
3341
+ group: binding.group ?? registration.group,
3342
+ priority,
3343
+ });
3344
+ }
3345
+ }
3346
+ return entries.sort((a, b) => b.priority - a.priority || a.bindingTitle.localeCompare(b.bindingTitle));
1676
3347
  }
1677
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPHookService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1678
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPHookService, providedIn: 'root' }); }
3348
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPKeyboardShortcutRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
3349
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPKeyboardShortcutRegistry, providedIn: 'root' }); }
1679
3350
  }
1680
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPHookService, decorators: [{
3351
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPKeyboardShortcutRegistry, decorators: [{
1681
3352
  type: Injectable,
1682
- args: [{ providedIn: 'root' }]
3353
+ args: [{
3354
+ providedIn: 'root',
3355
+ }]
1683
3356
  }] });
1684
3357
 
1685
3358
  //#region ---- Imports ----
@@ -1957,37 +3630,108 @@ class AXPModuleManifestRegistry {
1957
3630
  }
1958
3631
  return { missingModules, missingFeatures };
1959
3632
  }
1960
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPModuleManifestRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1961
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPModuleManifestRegistry, providedIn: 'root' }); }
3633
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPModuleManifestRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
3634
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPModuleManifestRegistry, providedIn: 'root' }); }
1962
3635
  }
1963
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPModuleManifestRegistry, decorators: [{
3636
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPModuleManifestRegistry, decorators: [{
1964
3637
  type: Injectable,
1965
3638
  args: [{ providedIn: 'root' }]
1966
3639
  }] });
1967
3640
 
3641
+ //#region ---- Imports ----
3642
+ //#endregion
3643
+ //#region ---- Module manifests data source ----
3644
+ /**
3645
+ * Data source definition for module names/titles from {@link AXPModuleManifestRegistry}.
3646
+ */
3647
+ class AXPModuleManifestsDataSourceDefinition {
3648
+ constructor() {
3649
+ //#region ---- Services & Dependencies ----
3650
+ this.manifestRegistry = inject(AXPModuleManifestRegistry);
3651
+ //#endregion
3652
+ }
3653
+ //#endregion
3654
+ //#region ---- Public API ----
3655
+ async items() {
3656
+ return [
3657
+ {
3658
+ name: AXPPlatformCoreDataSourceKeys.ModuleManifests,
3659
+ title: 'Module manifests',
3660
+ source: () => new AXDataSource({
3661
+ key: 'id',
3662
+ load: async () => {
3663
+ await this.manifestRegistry.initialize();
3664
+ const list = this.manifestRegistry.getAll().map((m) => ({
3665
+ id: m.name,
3666
+ title: m.title || m.name,
3667
+ }));
3668
+ return { items: list, total: list.length };
3669
+ },
3670
+ byKey: async (key) => {
3671
+ await this.manifestRegistry.initialize();
3672
+ const m = this.manifestRegistry.getAll().find((x) => x.name === key);
3673
+ return m ? { id: m.name, title: m.title || m.name } : undefined;
3674
+ },
3675
+ pageSize: 1000,
3676
+ }),
3677
+ columns: [
3678
+ {
3679
+ name: 'id',
3680
+ title: 'ID',
3681
+ datatype: 'string',
3682
+ type: 'text-editor',
3683
+ },
3684
+ {
3685
+ name: 'title',
3686
+ title: 'Title',
3687
+ datatype: 'string',
3688
+ type: 'text-editor',
3689
+ },
3690
+ ],
3691
+ filters: [
3692
+ {
3693
+ field: 'title',
3694
+ title: 'Title',
3695
+ operator: { type: 'equal' },
3696
+ widget: { type: 'text-editor' },
3697
+ filterType: { advance: true, inline: true },
3698
+ },
3699
+ ],
3700
+ textField: { name: 'title', title: 'Title' },
3701
+ valueField: { name: 'id', title: 'ID' },
3702
+ },
3703
+ ];
3704
+ }
3705
+ }
3706
+ //#endregion
3707
+
1968
3708
  class AXPAppStartUpService {
1969
3709
  constructor() {
3710
+ this.translationService = inject(AXTranslationService);
1970
3711
  this.tasks = [];
1971
3712
  }
3713
+ //#endregion
3714
+ //#region ---- Public Methods ----
1972
3715
  registerTask(task) {
1973
3716
  this.tasks.push(task);
1974
3717
  }
1975
3718
  async runAllTasks() {
1976
3719
  for (const task of this.tasks.sort((a, b) => a.priority - b.priority)) {
1977
- this.updateStatus(task.statusText);
3720
+ await this.updateStatus(task.statusText);
1978
3721
  await task.run();
1979
3722
  }
1980
3723
  }
1981
- updateStatus(status) {
3724
+ async updateStatus(status) {
1982
3725
  const loadingText = document.querySelector('#loadingText');
1983
3726
  if (loadingText) {
1984
- loadingText.innerHTML = status;
3727
+ const translatedStatus = this.translationService.resolve(status);
3728
+ loadingText.innerHTML = await this.translationService.translateAsync(translatedStatus);
1985
3729
  }
1986
3730
  }
1987
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPAppStartUpService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1988
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPAppStartUpService, providedIn: 'root' }); }
3731
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPAppStartUpService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
3732
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPAppStartUpService, providedIn: 'root' }); }
1989
3733
  }
1990
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPAppStartUpService, decorators: [{
3734
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPAppStartUpService, decorators: [{
1991
3735
  type: Injectable,
1992
3736
  args: [{
1993
3737
  providedIn: 'root',
@@ -2018,24 +3762,57 @@ class AXPModuleManifestModule {
2018
3762
  // Register manifest initialization task
2019
3763
  appInitService.registerTask({
2020
3764
  name: 'ModuleManifests',
2021
- statusText: 'Loading module manifests...',
3765
+ statusText: '@general:app-startup.loading-module-manifests',
2022
3766
  priority: 1, // Load early, before features/permissions
2023
3767
  run: async () => {
2024
3768
  await manifestRegistry.initialize();
2025
3769
  },
2026
3770
  });
2027
3771
  }
2028
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPModuleManifestModule, deps: [{ token: AXPAppStartUpService }, { token: i0.Injector }], target: i0.ɵɵFactoryTarget.NgModule }); }
2029
- static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.12", ngImport: i0, type: AXPModuleManifestModule }); }
2030
- static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPModuleManifestModule }); }
3772
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPModuleManifestModule, deps: [{ token: AXPAppStartUpService }, { token: i0.Injector }], target: i0.ɵɵFactoryTarget.NgModule }); }
3773
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.9", ngImport: i0, type: AXPModuleManifestModule }); }
3774
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPModuleManifestModule }); }
2031
3775
  }
2032
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPModuleManifestModule, decorators: [{
3776
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPModuleManifestModule, decorators: [{
2033
3777
  type: NgModule,
2034
3778
  args: [{
2035
3779
  providers: [],
2036
3780
  }]
2037
3781
  }], ctorParameters: () => [{ type: AXPAppStartUpService }, { type: i0.Injector }] });
2038
3782
 
3783
+ //#region ---- Imports ----
3784
+ //#endregion
3785
+ //#region ---- Compare & sort ----
3786
+ const DEFAULT_COLLATOR_OPTIONS = { sensitivity: 'base' };
3787
+ /**
3788
+ * Locale-aware comparison of {@link AXPMultiLanguageString} values (plain string, `@scope:key`, or locale map).
3789
+ * Resolves each side with {@link resolveMultiLanguageString} for `locale`, then compares with `String#localeCompare`.
3790
+ */
3791
+ function compareMultiLanguageStrings(a, b, locale, collatorOptions = DEFAULT_COLLATOR_OPTIONS) {
3792
+ const sa = resolveMultiLanguageString(a ?? '', locale);
3793
+ const sb = resolveMultiLanguageString(b ?? '', locale);
3794
+ return sa.localeCompare(sb, locale, collatorOptions);
3795
+ }
3796
+ /**
3797
+ * Stable sort copy: orders items by resolved string for `getValue(item)` using the active `locale`.
3798
+ */
3799
+ function sortByMultiLanguageString(items, getValue, locale, collatorOptions) {
3800
+ return [...items].sort((x, y) => compareMultiLanguageStrings(getValue(x), getValue(y), locale, collatorOptions));
3801
+ }
3802
+ /**
3803
+ * True when the resolved value contains `query` (case-insensitive) for `locale`.
3804
+ * Useful for search/filter flows over {@link AXPMultiLanguageString}.
3805
+ */
3806
+ function searchInMultiLanguageString(value, query, locale) {
3807
+ const q = query.trim().toLocaleLowerCase(locale);
3808
+ if (!q) {
3809
+ return true;
3810
+ }
3811
+ const text = resolveMultiLanguageString(value ?? '', locale).toLocaleLowerCase(locale);
3812
+ return text.includes(q);
3813
+ }
3814
+ //#endregion
3815
+
2039
3816
  //#region ---- Tag Types ----
2040
3817
  //#endregion
2041
3818
 
@@ -2187,10 +3964,10 @@ class AXPTagService {
2187
3964
  const uniqueTags = allTags.filter((tag, index, self) => index === self.findIndex((t) => t.title.toLowerCase() === tag.title.toLowerCase()));
2188
3965
  this.cache = uniqueTags;
2189
3966
  }
2190
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPTagService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2191
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPTagService, providedIn: 'root' }); }
3967
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTagService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
3968
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTagService, providedIn: 'root' }); }
2192
3969
  }
2193
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPTagService, decorators: [{
3970
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPTagService, decorators: [{
2194
3971
  type: Injectable,
2195
3972
  args: [{
2196
3973
  providedIn: 'root',
@@ -3121,14 +4898,25 @@ function getNestedKeys(obj, prefix = '') {
3121
4898
  }
3122
4899
  return keys;
3123
4900
  }
4901
+ /** Normalizes definition categories to an array for iteration and serialization. */
4902
+ function normalizeDefinitionCategories(categories) {
4903
+ if (categories == null) {
4904
+ return [];
4905
+ }
4906
+ return Array.isArray(categories) ? categories : [categories];
4907
+ }
4908
+
4909
+ //#region ---- Imports ----
4910
+ //#endregion
3124
4911
 
3125
4912
  function applySystemActionDefault(action, type) {
3126
4913
  const systemAction = getSystemActions(type);
3127
4914
  return {
3128
- name: systemAction.key,
3129
- title: systemAction.title,
3130
- icon: systemAction.icon,
3131
- color: systemAction.color,
4915
+ ...action,
4916
+ name: action.name ?? systemAction.key,
4917
+ title: action.title ?? systemAction.title,
4918
+ icon: action.icon ?? systemAction.icon,
4919
+ color: action.color ?? systemAction.color,
3132
4920
  };
3133
4921
  }
3134
4922
 
@@ -3153,8 +4941,6 @@ class AXPIconLogoConfig {
3153
4941
 
3154
4942
  var AXPPlatformScope;
3155
4943
  (function (AXPPlatformScope) {
3156
- AXPPlatformScope["Environment"] = "C";
3157
- AXPPlatformScope["Global"] = "G";
3158
4944
  AXPPlatformScope["Platform"] = "P";
3159
4945
  AXPPlatformScope["Tenant"] = "T";
3160
4946
  AXPPlatformScope["User"] = "U";
@@ -3162,8 +4948,6 @@ var AXPPlatformScope;
3162
4948
  ;
3163
4949
  function resolvePlatformScopeKey(name) {
3164
4950
  const scopeMap = {
3165
- environment: AXPPlatformScope.Environment,
3166
- global: AXPPlatformScope.Global,
3167
4951
  platform: AXPPlatformScope.Platform,
3168
4952
  tenant: AXPPlatformScope.Tenant,
3169
4953
  user: AXPPlatformScope.User,
@@ -3172,8 +4956,6 @@ function resolvePlatformScopeKey(name) {
3172
4956
  }
3173
4957
  function resolvePlatformScopeName(scope) {
3174
4958
  const scopeMap = {
3175
- C: 'environment',
3176
- G: 'global',
3177
4959
  P: 'platform',
3178
4960
  T: 'tenant',
3179
4961
  U: 'user',
@@ -3193,7 +4975,7 @@ var AXPExportTemplateToken;
3193
4975
  class AXPCountdownPipe {
3194
4976
  constructor() {
3195
4977
  this.calendarService = inject(AXCalendarService);
3196
- this.countdownSignal = signal(this.setupTimer(), ...(ngDevMode ? [{ debugName: "countdownSignal" }] : []));
4978
+ this.countdownSignal = signal(this.setupTimer(), ...(ngDevMode ? [{ debugName: "countdownSignal" }] : /* istanbul ignore next */ []));
3197
4979
  this.targetDate = 0;
3198
4980
  this.prevValue = 0;
3199
4981
  }
@@ -3222,10 +5004,10 @@ class AXPCountdownPipe {
3222
5004
  updateTargetDate(value) {
3223
5005
  this.targetDate = new Date(value).getTime();
3224
5006
  }
3225
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPCountdownPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
3226
- static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "20.3.12", ngImport: i0, type: AXPCountdownPipe, isStandalone: true, name: "countdown", pure: false }); }
5007
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPCountdownPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
5008
+ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.2.9", ngImport: i0, type: AXPCountdownPipe, isStandalone: true, name: "countdown", pure: false }); }
3227
5009
  }
3228
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPCountdownPipe, decorators: [{
5010
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPCountdownPipe, decorators: [{
3229
5011
  type: Pipe,
3230
5012
  args: [{
3231
5013
  name: 'countdown',
@@ -3235,6 +5017,31 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImpo
3235
5017
  }] });
3236
5018
 
3237
5019
  const loggingEnabled = false; // Set to true to enable logging, false to disable
5020
+ //#region ---- Multilingual string helpers ----
5021
+ /**
5022
+ * Lowercased text for client-side filtering: plain string or all locale values joined.
5023
+ */
5024
+ function toFilterSearchText(value) {
5025
+ if (typeof value === 'string') {
5026
+ return value.toLowerCase();
5027
+ }
5028
+ if (isLocaleStringMap(value)) {
5029
+ return Object.values(value).join(' ').toLowerCase();
5030
+ }
5031
+ return undefined;
5032
+ }
5033
+ function filterValuesAreEqual(itemRaw, condRaw) {
5034
+ if (typeof condRaw === 'string' && isLocaleStringMap(itemRaw)) {
5035
+ return Object.values(itemRaw).some((v) => v.toLowerCase() === condRaw.toLowerCase());
5036
+ }
5037
+ if (typeof itemRaw === 'string' && isLocaleStringMap(condRaw)) {
5038
+ return Object.values(condRaw).some((v) => v.toLowerCase() === itemRaw.toLowerCase());
5039
+ }
5040
+ const itemScalar = typeof itemRaw === 'string' ? itemRaw.toLowerCase() : itemRaw;
5041
+ const condScalar = typeof condRaw === 'string' ? condRaw.toLowerCase() : condRaw;
5042
+ return isEqual(itemScalar, condScalar);
5043
+ }
5044
+ //#endregion
3238
5045
  function applyCondition(item, condition) {
3239
5046
  const rawValue = condition.field ? get(item, condition.field) : null;
3240
5047
  const itemValue = typeof rawValue === 'string' ? rawValue.toLowerCase() : rawValue;
@@ -3255,7 +5062,12 @@ function applyCondition(item, condition) {
3255
5062
  switch (op) {
3256
5063
  case 'equal':
3257
5064
  case 'eq':
3258
- result = isEqual(valueToCompare, conditionValue);
5065
+ if (!isNil(condition.field) && condition.field !== '') {
5066
+ result = filterValuesAreEqual(rawValue, condition.value);
5067
+ }
5068
+ else {
5069
+ result = isEqual(valueToCompare, conditionValue);
5070
+ }
3259
5071
  if (loggingEnabled) {
3260
5072
  console.log('Equal check result:', result);
3261
5073
  }
@@ -3263,7 +5075,12 @@ function applyCondition(item, condition) {
3263
5075
  case 'notEqual':
3264
5076
  case 'ne':
3265
5077
  case 'neq':
3266
- result = !isEqual(valueToCompare, conditionValue);
5078
+ if (!isNil(condition.field) && condition.field !== '') {
5079
+ result = !filterValuesAreEqual(rawValue, condition.value);
5080
+ }
5081
+ else {
5082
+ result = !isEqual(valueToCompare, conditionValue);
5083
+ }
3267
5084
  if (loggingEnabled) {
3268
5085
  console.log('Not equal check result:', result);
3269
5086
  }
@@ -3296,12 +5113,15 @@ function applyCondition(item, condition) {
3296
5113
  console.log('Less than or equal check result:', result);
3297
5114
  }
3298
5115
  break;
3299
- case 'contains':
3300
- if (typeof valueToCompare === 'string') {
3301
- result = includes(valueToCompare, conditionValue);
5116
+ case 'contains': {
5117
+ const needle = toFilterSearchText(condition.value) ??
5118
+ (condition.value == null ? '' : String(condition.value).toLowerCase());
5119
+ const haystack = toFilterSearchText(rawValue);
5120
+ if (haystack !== undefined) {
5121
+ result = includes(haystack, needle);
3302
5122
  }
3303
- else if (Array.isArray(valueToCompare)) {
3304
- result = includes(valueToCompare.map((val) => val.toString().toLowerCase()), conditionValue);
5123
+ else if (Array.isArray(rawValue)) {
5124
+ result = includes(rawValue.map((val) => toFilterSearchText(val) ?? val.toString().toLowerCase()), needle);
3305
5125
  }
3306
5126
  else {
3307
5127
  result = false;
@@ -3310,13 +5130,18 @@ function applyCondition(item, condition) {
3310
5130
  console.log('Contains check result:', result);
3311
5131
  }
3312
5132
  break;
5133
+ }
3313
5134
  case 'in':
3314
5135
  if (Array.isArray(conditionValue)) {
3315
5136
  if (typeof valueToCompare === 'string') {
3316
- result = conditionValue.some(val => typeof val === 'string' ? val.toLowerCase() === valueToCompare : val === valueToCompare);
5137
+ result = conditionValue.some((val) => typeof val === 'string' ? val.toLowerCase() === valueToCompare : val === valueToCompare);
5138
+ }
5139
+ else if (isLocaleStringMap(rawValue)) {
5140
+ const localeValues = Object.values(rawValue).map((v) => v.toLowerCase());
5141
+ result = localeValues.some((localeVal) => conditionValue.some((val) => typeof val === 'string' ? val.toLowerCase() === localeVal : val === localeVal));
3317
5142
  }
3318
5143
  else if (Array.isArray(valueToCompare)) {
3319
- result = valueToCompare.some(val => conditionValue.some(condVal => typeof val === 'string' && typeof condVal === 'string'
5144
+ result = valueToCompare.some((val) => conditionValue.some((condVal) => typeof val === 'string' && typeof condVal === 'string'
3320
5145
  ? val.toLowerCase() === condVal.toLowerCase()
3321
5146
  : val === condVal));
3322
5147
  }
@@ -3331,12 +5156,15 @@ function applyCondition(item, condition) {
3331
5156
  console.log('In check result:', result);
3332
5157
  }
3333
5158
  break;
3334
- case 'notContains':
3335
- if (typeof valueToCompare === 'string') {
3336
- result = !includes(valueToCompare, conditionValue);
5159
+ case 'notContains': {
5160
+ const needleNc = toFilterSearchText(condition.value) ??
5161
+ (condition.value == null ? '' : String(condition.value).toLowerCase());
5162
+ const haystackNc = toFilterSearchText(rawValue);
5163
+ if (haystackNc !== undefined) {
5164
+ result = !includes(haystackNc, needleNc);
3337
5165
  }
3338
- else if (Array.isArray(valueToCompare)) {
3339
- result = !includes(valueToCompare.map((val) => val.toString().toLowerCase()), conditionValue);
5166
+ else if (Array.isArray(rawValue)) {
5167
+ result = !includes(rawValue.map((val) => toFilterSearchText(val) ?? val.toString().toLowerCase()), needleNc);
3340
5168
  }
3341
5169
  else {
3342
5170
  result = false;
@@ -3345,18 +5173,27 @@ function applyCondition(item, condition) {
3345
5173
  console.log('Not contains check result:', result);
3346
5174
  }
3347
5175
  break;
3348
- case 'startsWith':
3349
- result = typeof valueToCompare === 'string' && startsWith(valueToCompare, conditionValue);
5176
+ }
5177
+ case 'startsWith': {
5178
+ const prefix = toFilterSearchText(condition.value) ??
5179
+ (condition.value == null ? '' : String(condition.value).toLowerCase());
5180
+ const haystackSw = toFilterSearchText(rawValue);
5181
+ result = haystackSw !== undefined && startsWith(haystackSw, prefix);
3350
5182
  if (loggingEnabled) {
3351
5183
  console.log('Starts with check result:', result);
3352
5184
  }
3353
5185
  break;
3354
- case 'endsWith':
3355
- result = typeof valueToCompare === 'string' && endsWith(valueToCompare, conditionValue);
5186
+ }
5187
+ case 'endsWith': {
5188
+ const suffix = toFilterSearchText(condition.value) ??
5189
+ (condition.value == null ? '' : String(condition.value).toLowerCase());
5190
+ const haystackEw = toFilterSearchText(rawValue);
5191
+ result = haystackEw !== undefined && endsWith(haystackEw, suffix);
3356
5192
  if (loggingEnabled) {
3357
5193
  console.log('Ends with check result:', result);
3358
5194
  }
3359
5195
  break;
5196
+ }
3360
5197
  case 'isEmpty':
3361
5198
  result = isEmpty(valueToCompare);
3362
5199
  if (loggingEnabled) {
@@ -3508,45 +5345,45 @@ class AXPDeviceService {
3508
5345
  /**
3509
5346
  * Current screen size (Small, Medium, Large)
3510
5347
  */
3511
- this.screenSize = signal(getScreenSize(), ...(ngDevMode ? [{ debugName: "screenSize" }] : []));
5348
+ this.screenSize = signal(getScreenSize(), ...(ngDevMode ? [{ debugName: "screenSize" }] : /* istanbul ignore next */ []));
3512
5349
  /**
3513
5350
  * Current device type (Mobile, Tablet, Desktop)
3514
5351
  */
3515
- this.deviceType = signal(getDeviceType(), ...(ngDevMode ? [{ debugName: "deviceType" }] : []));
5352
+ this.deviceType = signal(getDeviceType(), ...(ngDevMode ? [{ debugName: "deviceType" }] : /* istanbul ignore next */ []));
3516
5353
  /**
3517
5354
  * Whether the device supports touch input
3518
5355
  */
3519
- this.isTouchDevice = signal(isTouchDevice(), ...(ngDevMode ? [{ debugName: "isTouchDevice" }] : []));
5356
+ this.isTouchDevice = signal(isTouchDevice(), ...(ngDevMode ? [{ debugName: "isTouchDevice" }] : /* istanbul ignore next */ []));
3520
5357
  //#endregion
3521
5358
  //#region ---- Computed Signals ----
3522
5359
  /**
3523
5360
  * Whether the screen size is Small
3524
5361
  */
3525
- this.isSmall = computed(() => this.screenSize() === AXPScreenSize.Small, ...(ngDevMode ? [{ debugName: "isSmall" }] : []));
5362
+ this.isSmall = computed(() => this.screenSize() === AXPScreenSize.Small, ...(ngDevMode ? [{ debugName: "isSmall" }] : /* istanbul ignore next */ []));
3526
5363
  /**
3527
5364
  * Whether the screen size is Medium
3528
5365
  */
3529
- this.isMedium = computed(() => this.screenSize() === AXPScreenSize.Medium, ...(ngDevMode ? [{ debugName: "isMedium" }] : []));
5366
+ this.isMedium = computed(() => this.screenSize() === AXPScreenSize.Medium, ...(ngDevMode ? [{ debugName: "isMedium" }] : /* istanbul ignore next */ []));
3530
5367
  /**
3531
5368
  * Whether the screen size is Large
3532
5369
  */
3533
- this.isLarge = computed(() => this.screenSize() === AXPScreenSize.Large, ...(ngDevMode ? [{ debugName: "isLarge" }] : []));
5370
+ this.isLarge = computed(() => this.screenSize() === AXPScreenSize.Large, ...(ngDevMode ? [{ debugName: "isLarge" }] : /* istanbul ignore next */ []));
3534
5371
  /**
3535
5372
  * Whether the device is Mobile
3536
5373
  */
3537
- this.isMobileDevice = computed(() => this.deviceType() === AXPDeviceType.Mobile, ...(ngDevMode ? [{ debugName: "isMobileDevice" }] : []));
5374
+ this.isMobileDevice = computed(() => this.deviceType() === AXPDeviceType.Mobile, ...(ngDevMode ? [{ debugName: "isMobileDevice" }] : /* istanbul ignore next */ []));
3538
5375
  /**
3539
5376
  * Whether the device is Tablet
3540
5377
  */
3541
- this.isTabletDevice = computed(() => this.deviceType() === AXPDeviceType.Tablet, ...(ngDevMode ? [{ debugName: "isTabletDevice" }] : []));
5378
+ this.isTabletDevice = computed(() => this.deviceType() === AXPDeviceType.Tablet, ...(ngDevMode ? [{ debugName: "isTabletDevice" }] : /* istanbul ignore next */ []));
3542
5379
  /**
3543
5380
  * Whether the device is Desktop
3544
5381
  */
3545
- this.isDesktopDevice = computed(() => this.deviceType() === AXPDeviceType.Desktop, ...(ngDevMode ? [{ debugName: "isDesktopDevice" }] : []));
5382
+ this.isDesktopDevice = computed(() => this.deviceType() === AXPDeviceType.Desktop, ...(ngDevMode ? [{ debugName: "isDesktopDevice" }] : /* istanbul ignore next */ []));
3546
5383
  /**
3547
5384
  * Whether the device supports touch (alias for isTouchDevice)
3548
5385
  */
3549
- this.isTouchScreen = computed(() => this.isTouchDevice(), ...(ngDevMode ? [{ debugName: "isTouchScreen" }] : []));
5386
+ this.isTouchScreen = computed(() => this.isTouchDevice(), ...(ngDevMode ? [{ debugName: "isTouchScreen" }] : /* istanbul ignore next */ []));
3550
5387
  //#endregion
3551
5388
  //#region ---- Private Properties ----
3552
5389
  this._resizeListener = null;
@@ -3612,10 +5449,10 @@ class AXPDeviceService {
3612
5449
  destroy() {
3613
5450
  this._removeResizeListener();
3614
5451
  }
3615
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPDeviceService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
3616
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPDeviceService, providedIn: 'root' }); }
5452
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPDeviceService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
5453
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPDeviceService, providedIn: 'root' }); }
3617
5454
  }
3618
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPDeviceService, decorators: [{
5455
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPDeviceService, decorators: [{
3619
5456
  type: Injectable,
3620
5457
  args: [{
3621
5458
  providedIn: 'root',
@@ -3719,10 +5556,10 @@ class AXHighlightService {
3719
5556
  resultHTML += '</span>';
3720
5557
  return resultHTML;
3721
5558
  }
3722
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXHighlightService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
3723
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXHighlightService, providedIn: 'root' }); }
5559
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXHighlightService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
5560
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXHighlightService, providedIn: 'root' }); }
3724
5561
  }
3725
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXHighlightService, decorators: [{
5562
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXHighlightService, decorators: [{
3726
5563
  type: Injectable,
3727
5564
  args: [{
3728
5565
  providedIn: 'root',
@@ -3754,17 +5591,23 @@ async function createProviderWithInjectionContext(loader) {
3754
5591
  *
3755
5592
  * @param token - The injection token to provide
3756
5593
  * @param loader - Function that returns a promise resolving to the provider class
3757
- * @param multi - Whether the provider is multi-provider (defaults to true)
5594
+ * @param multi - Optional. Whether the provider is a multi-provider (array of values). Defaults to `true`. Pass `false` for a single provider.
3758
5595
  * @returns Provider configuration object
3759
5596
  *
3760
5597
  * @example
3761
5598
  * ```typescript
3762
- * providers: [
3763
- * provideLazyProvider(
3764
- * AXP_SETTING_DEFINITION_PROVIDER,
3765
- * () => import('./settings.provider').then(m => m.AXMSettingProvider)
3766
- * )
3767
- * ]
5599
+ * // Multi-provider (default)
5600
+ * provideLazyProvider(
5601
+ * AXP_DATASOURCE_DEFINITION_PROVIDER,
5602
+ * () => import('./datasource.provider').then(m => m.AXMDataSourceProvider)
5603
+ * )
5604
+ *
5605
+ * // Single provider
5606
+ * provideLazyProvider(
5607
+ * SOME_TOKEN,
5608
+ * () => import('./my.provider').then(m => m.MyProvider),
5609
+ * false
5610
+ * )
3768
5611
  * ```
3769
5612
  */
3770
5613
  function provideLazyProvider(token, loader, multi = true) {
@@ -3775,11 +5618,113 @@ function provideLazyProvider(token, loader, multi = true) {
3775
5618
  };
3776
5619
  }
3777
5620
 
5621
+ //#region ---- Unwrapping ----
5622
+ /**
5623
+ * If `raw` is a plain non-array object with a `value` property, returns `value`; otherwise returns `raw`.
5624
+ */
5625
+ function unwrapValueProperty(raw) {
5626
+ if (raw && typeof raw === 'object' && !Array.isArray(raw) && !(raw instanceof Date) && 'value' in raw) {
5627
+ return raw.value;
5628
+ }
5629
+ return raw;
5630
+ }
5631
+ //#endregion
5632
+ //#region ---- Scalar coercion ----
5633
+ /**
5634
+ * Coerces to a trimmed string. Plain objects may carry `{ value }` from setting widgets or `{ id }` from select payloads using `valueField: 'id'`.
5635
+ */
5636
+ function coerceUnknownToTrimmedString(raw) {
5637
+ if (typeof raw === 'string') {
5638
+ return raw.trim();
5639
+ }
5640
+ if (raw && typeof raw === 'object' && !Array.isArray(raw) && !(raw instanceof Date)) {
5641
+ const r = raw;
5642
+ if ('value' in r && r['value'] != null) {
5643
+ return String(r['value']).trim();
5644
+ }
5645
+ if ('id' in r && r['id'] != null) {
5646
+ return String(r['id']).trim();
5647
+ }
5648
+ }
5649
+ return '';
5650
+ }
5651
+ /**
5652
+ * Coerces to boolean: direct booleans pass through; wrapped `{ value: true | 'true' }` is respected; otherwise false.
5653
+ */
5654
+ function coerceUnknownToBoolean(raw) {
5655
+ if (typeof raw === 'boolean') {
5656
+ return raw;
5657
+ }
5658
+ if (raw && typeof raw === 'object' && 'value' in raw) {
5659
+ const v = raw.value;
5660
+ return v === true || v === 'true';
5661
+ }
5662
+ return false;
5663
+ }
5664
+ /** Parses a finite number after optional `{ value }` unwrap; non-finite or empty string yields NaN. */
5665
+ function coerceUnknownToFiniteNumber(raw) {
5666
+ const v = unwrapValueProperty(raw);
5667
+ if (v === null || v === undefined) {
5668
+ return NaN;
5669
+ }
5670
+ if (typeof v === 'number') {
5671
+ return Number.isFinite(v) ? v : NaN;
5672
+ }
5673
+ if (typeof v === 'boolean') {
5674
+ return v ? 1 : 0;
5675
+ }
5676
+ if (typeof v === 'string') {
5677
+ const t = v.trim();
5678
+ if (t === '') {
5679
+ return NaN;
5680
+ }
5681
+ const n = Number(t);
5682
+ return Number.isFinite(n) ? n : NaN;
5683
+ }
5684
+ return NaN;
5685
+ }
5686
+ /** Parses a Date after optional `{ value }` unwrap; invalid input yields Invalid Date. */
5687
+ function coerceUnknownToDate(raw) {
5688
+ const v = unwrapValueProperty(raw);
5689
+ if (v === null || v === undefined) {
5690
+ return new Date(NaN);
5691
+ }
5692
+ if (v instanceof Date) {
5693
+ return new Date(v.getTime());
5694
+ }
5695
+ if (typeof v === 'number' && Number.isFinite(v)) {
5696
+ return new Date(v);
5697
+ }
5698
+ if (typeof v === 'string') {
5699
+ const t = v.trim();
5700
+ if (t === '') {
5701
+ return new Date(NaN);
5702
+ }
5703
+ return new Date(t);
5704
+ }
5705
+ return new Date(NaN);
5706
+ }
5707
+ //#endregion
5708
+
3778
5709
  function extractTextFromHtml(value) {
3779
5710
  const div = document.createElement('div');
3780
5711
  div.innerHTML = value;
3781
5712
  return div.textContent || div.innerText || '';
3782
5713
  }
5714
+ /**
5715
+ * True when the string likely contains HTML markup (e.g. `<p>`, `<ul>`), as opposed to plain text.
5716
+ * Used to choose innerHTML vs text interpolation for multilingual fields.
5717
+ */
5718
+ function containsHtmlMarkup(value) {
5719
+ if (value == null) {
5720
+ return false;
5721
+ }
5722
+ const t = value.trim();
5723
+ if (!t) {
5724
+ return false;
5725
+ }
5726
+ return /<\/?[a-z][\s\S]*?>/i.test(t);
5727
+ }
3783
5728
  /**
3784
5729
  * Generate kebab-case group name from title
3785
5730
  */
@@ -3797,5 +5742,5 @@ function generateKebabCase(title) {
3797
5742
  * Generated bundle index. Do not edit.
3798
5743
  */
3799
5744
 
3800
- export { AXFullscreenDirective, AXHighlightService, AXPActivityLogProvider, AXPActivityLogService, AXPAppStartUpProvider, AXPAppStartUpService, AXPBroadcastEventService, AXPColorPaletteProvider, AXPColorPaletteService, AXPComponentLogoConfig, AXPContentCheckerDirective, AXPContextChangeEvent, AXPContextStore, AXPCountdownPipe, AXPDataGenerator, AXPDataSourceDefinitionProviderService, AXPDblClickDirective, AXPDefaultColorPalettesProvider, AXPDeviceService, AXPDeviceType, AXPDistributedEventListenerService, AXPElementDataDirective, AXPExportTemplateToken, AXPExpressionEvaluatorScopeProviderContext, AXPExpressionEvaluatorScopeProviderService, AXPExpressionEvaluatorService, AXPFeatureDefinitionProviderContext, AXPGridLayoutDirective, AXPHookService, AXPIconLogoConfig, AXPImageUrlLogoConfig, AXPModuleManifestModule, AXPModuleManifestRegistry, AXPPlatformScope, AXPScreenSize, AXPSystemActionType, AXPSystemActions, AXPTagProvider, AXPTagService, AXP_ACTIVITY_LOG_PROVIDER, AXP_COLOR_PALETTE_PROVIDER, AXP_DATASOURCE_DEFINITION_PROVIDER, AXP_DISTRIBUTED_EVENT_LISTENER_PROVIDER, AXP_EXPRESSION_EVALUATOR_SCOPE_PROVIDER, AXP_FEATURE_DEFINITION_PROVIDER, AXP_MODULE_MANIFEST_PROVIDER, AXP_SESSION_SERVICE, AXP_TAG_PROVIDER, applyFilterArray, applyPagination, applyQueryArray, applySortArray, applySystemActionDefault, cleanDeep, createProviderWithInjectionContext, extractNestedFieldsWildcard, extractTextFromHtml, extractValue, generateKebabCase, getActionButton, getChangedPaths, getDetailedChanges, getEnumValues, getNestedKeys, getSmart, getSystemActions, objectKeyValueTransforms, provideLazyProvider, resolveActionLook, resolvePlatformScopeKey, resolvePlatformScopeName, setSmart };
5745
+ export { AXHighlightService, AXPActivityLogProvider, AXPActivityLogService, AXPAppStartUpProvider, AXPAppStartUpService, AXPBroadcastEventService, AXPCatalogScopeDefinitionProviderService, AXPCatalogScopeDefinitionsDataSourceDefinition, AXPColorPaletteProvider, AXPColorPaletteService, AXPColumnWidthService, AXPComponentLogoConfig, AXPComponentSlot, AXPComponentSlotDirective, AXPComponentSlotModule, AXPComponentSlotRegistryService, AXPContentCheckerDirective, AXPContextChangeEvent, AXPContextDefinitionProviderService, AXPContextStore, AXPCountdownPipe, AXPDataGenerator, AXPDataSourceDefinitionProviderService, AXPDblClickDirective, AXPDefaultColorPalettesProvider, AXPDeviceService, AXPDeviceType, AXPDistributedEventListenerService, AXPElementDataDirective, AXPExportTemplateToken, AXPExpressionEvaluatorScopeProviderContext, AXPExpressionEvaluatorScopeProviderService, AXPExpressionEvaluatorService, AXPFeatureDefinitionProviderContext, AXPGridLayoutDirective, AXPHookService, AXPIconLogoConfig, AXPImageUrlLogoConfig, AXPKeyboardShortcutPriority, AXPKeyboardShortcutRegistry, AXPModuleManifestModule, AXPModuleManifestRegistry, AXPModuleManifestsDataSourceDefinition, AXPPlatformCoreDataSourceKeys, AXPPlatformScope, AXPScreenSize, AXPSystemActionType, AXPSystemActions, AXPTagProvider, AXPTagService, AXP_ACTIVITY_LOG_PROVIDER, AXP_CATALOG_SCOPE_DEFINITION_PROVIDER, AXP_COLOR_PALETTE_PROVIDER, AXP_COLUMN_WIDTH_PROVIDER, AXP_CONTEXT_DEFINITION_PROVIDER, AXP_DATASOURCE_DEFINITION_PROVIDER, AXP_DISTRIBUTED_EVENT_LISTENER_PROVIDER, AXP_EXPRESSION_EVALUATOR_SCOPE_PROVIDER, AXP_FEATURE_DEFINITION_PROVIDER, AXP_MODULE_MANIFEST_PROVIDER, AXP_SESSION_SERVICE, AXP_TAG_PROVIDER, AX_OVERLAY_CONTAINER_SELECTOR, AX_OVERLAY_PANE_SELECTOR, applyFilterArray, applyPagination, applyQueryArray, applySortArray, applySystemActionDefault, buildLocaleTextMapValue, captureFormContextBaseline, chordToKbdItemKeys, cleanDeep, coerceUnknownToBoolean, coerceUnknownToDate, coerceUnknownToFiniteNumber, coerceUnknownToTrimmedString, compareMultiLanguageStrings, containsHtmlMarkup, createProviderWithInjectionContext, defaultColumnWidthProvider, extractNestedFieldsWildcard, extractTextFromHtml, extractValue, findOverlayContainerAncestor, formatKeyboardShortcutChord, formatKeyboardShortcutChords, generateKebabCase, getActionButton, getChangedPaths, getDetailedChanges, getEnumValues, getNestedKeys, getNestedVisibleOverlayPanes, getPrimaryKeyboardShortcutChord, getSmart, getSystemActions, getTopVisibleOverlayContainer, getVisibleAnchoredOverlayPanes, getVisibleOverlayContainers, hasForegroundOverlayLayer, isFormContextDirty, isFormValueEqual, isHorizontalDirectionalShortcutKey, isKeyboardTargetInsideEditableField, isLocaleStringMap, isMacPlatform, isSelectionValueEqual, isVisibleOverlayElement, matchesKeyboardShortcutChord, mirrorHorizontalDirectionalKey, normalizeDefinitionCategories, normalizeKeyboardShortcut, normalizeKeyboardShortcuts, objectKeyValueTransforms, parseKeyboardShortcutChord, provideLazyProvider, resolveActionLook, resolveDisplayShortcutChord, resolveEffectiveDirectionBehavior, resolvePlatformScopeKey, resolvePlatformScopeName, resolveSemanticDirectionalKey, searchInMultiLanguageString, setSmart, shouldDeferEscapeToOpenOverlay, shouldUseLocaleMapShape, sortByMultiLanguageString, unwrapValueProperty };
3801
5746
  //# sourceMappingURL=acorex-platform-core.mjs.map