@acorex/platform 21.0.0-next.71 → 21.0.0-next.73

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 (60) hide show
  1. package/contracts/README.md +3 -0
  2. package/fesm2022/acorex-platform-auth.mjs +82 -60
  3. package/fesm2022/acorex-platform-auth.mjs.map +1 -1
  4. package/fesm2022/{acorex-platform-common-common-settings.provider-Bi1RYif5.mjs → acorex-platform-common-common-settings.provider-Ytey9uhY.mjs} +15 -1
  5. package/fesm2022/acorex-platform-common-common-settings.provider-Ytey9uhY.mjs.map +1 -0
  6. package/fesm2022/acorex-platform-common.mjs +3796 -1677
  7. package/fesm2022/acorex-platform-common.mjs.map +1 -1
  8. package/fesm2022/acorex-platform-contracts.mjs +10 -0
  9. package/fesm2022/acorex-platform-contracts.mjs.map +1 -0
  10. package/fesm2022/acorex-platform-core.mjs +1126 -114
  11. package/fesm2022/acorex-platform-core.mjs.map +1 -1
  12. package/fesm2022/acorex-platform-layout-builder.mjs +56 -170
  13. package/fesm2022/acorex-platform-layout-builder.mjs.map +1 -1
  14. package/fesm2022/acorex-platform-layout-components.mjs +254 -53
  15. package/fesm2022/acorex-platform-layout-components.mjs.map +1 -1
  16. package/fesm2022/acorex-platform-layout-designer.mjs +199 -126
  17. package/fesm2022/acorex-platform-layout-designer.mjs.map +1 -1
  18. package/fesm2022/{acorex-platform-layout-entity-attachments-page.component-D8iQnT-R.mjs → acorex-platform-layout-entity-attachments-page.component-B0EkdqvH.mjs} +6 -1
  19. package/fesm2022/acorex-platform-layout-entity-attachments-page.component-B0EkdqvH.mjs.map +1 -0
  20. package/fesm2022/acorex-platform-layout-entity.mjs +403 -460
  21. package/fesm2022/acorex-platform-layout-entity.mjs.map +1 -1
  22. package/fesm2022/acorex-platform-layout-views.mjs +675 -301
  23. package/fesm2022/acorex-platform-layout-views.mjs.map +1 -1
  24. package/fesm2022/acorex-platform-layout-widget-core.mjs +126 -75
  25. package/fesm2022/acorex-platform-layout-widget-core.mjs.map +1 -1
  26. package/fesm2022/{acorex-platform-layout-widgets-tabular-data-edit-popup.component-BcpRkpJp.mjs → acorex-platform-layout-widgets-tabular-data-edit-popup.component-DjpZU6gz.mjs} +2 -2
  27. package/fesm2022/{acorex-platform-layout-widgets-tabular-data-edit-popup.component-BcpRkpJp.mjs.map → acorex-platform-layout-widgets-tabular-data-edit-popup.component-DjpZU6gz.mjs.map} +1 -1
  28. package/fesm2022/{acorex-platform-layout-widgets-tabular-data-view-popup.component-DQtK4lxl.mjs → acorex-platform-layout-widgets-tabular-data-view-popup.component-gX-3Kx9I.mjs} +2 -2
  29. package/fesm2022/{acorex-platform-layout-widgets-tabular-data-view-popup.component-DQtK4lxl.mjs.map → acorex-platform-layout-widgets-tabular-data-view-popup.component-gX-3Kx9I.mjs.map} +1 -1
  30. package/fesm2022/acorex-platform-layout-widgets.mjs +450 -750
  31. package/fesm2022/acorex-platform-layout-widgets.mjs.map +1 -1
  32. package/fesm2022/acorex-platform-themes-default-error-401.component-B1nsdpTY.mjs +48 -0
  33. package/fesm2022/acorex-platform-themes-default-error-401.component-B1nsdpTY.mjs.map +1 -0
  34. package/fesm2022/acorex-platform-themes-default-error-404.component-D4UvRe8u.mjs +42 -0
  35. package/fesm2022/acorex-platform-themes-default-error-404.component-D4UvRe8u.mjs.map +1 -0
  36. package/fesm2022/acorex-platform-themes-default.mjs +79 -35
  37. package/fesm2022/acorex-platform-themes-default.mjs.map +1 -1
  38. package/fesm2022/acorex-platform-themes-shared-settings.provider-BjuzSe0T.mjs.map +1 -1
  39. package/fesm2022/acorex-platform-themes-shared.mjs +3 -0
  40. package/fesm2022/acorex-platform-themes-shared.mjs.map +1 -1
  41. package/fesm2022/acorex-platform-workflow.mjs +57 -54
  42. package/fesm2022/acorex-platform-workflow.mjs.map +1 -1
  43. package/package.json +5 -1
  44. package/types/acorex-platform-auth.d.ts +31 -22
  45. package/types/acorex-platform-common.d.ts +892 -259
  46. package/types/acorex-platform-contracts.d.ts +39 -0
  47. package/types/acorex-platform-core.d.ts +291 -47
  48. package/types/acorex-platform-layout-builder.d.ts +10 -22
  49. package/types/acorex-platform-layout-components.d.ts +86 -14
  50. package/types/acorex-platform-layout-entity.d.ts +218 -212
  51. package/types/acorex-platform-layout-views.d.ts +125 -67
  52. package/types/acorex-platform-layout-widget-core.d.ts +56 -62
  53. package/types/acorex-platform-layout-widgets.d.ts +78 -65
  54. package/types/acorex-platform-themes-default.d.ts +14 -4
  55. package/fesm2022/acorex-platform-common-common-settings.provider-Bi1RYif5.mjs.map +0 -1
  56. package/fesm2022/acorex-platform-layout-entity-attachments-page.component-D8iQnT-R.mjs.map +0 -1
  57. package/fesm2022/acorex-platform-themes-default-error-401.component-C7EYJzSr.mjs +0 -31
  58. package/fesm2022/acorex-platform-themes-default-error-401.component-C7EYJzSr.mjs.map +0 -1
  59. package/fesm2022/acorex-platform-themes-default-error-404.component-7MVLMwIa.mjs +0 -25
  60. package/fesm2022/acorex-platform-themes-default-error-404.component-7MVLMwIa.mjs.map +0 -1
@@ -2,14 +2,28 @@ import * as i0 from '@angular/core';
2
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
3
  import { AXTranslationService, resolveMultiLanguageString } from '@acorex/core/translation';
4
4
  import { AXDataSource } from '@acorex/cdk/common';
5
- import { get, isPlainObject, set, isNil, isEmpty, isArray, merge, isObjectLike, transform, isEqual, differenceWith, union, cloneDeep, isObject, has, sortBy, isUndefined, endsWith, startsWith, includes, lte, gte, lt, gt, orderBy } from 'lodash-es';
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';
6
6
  import { signalStore, withState, withComputed, withMethods, patchState } from '@ngrx/signals';
7
7
  import * as i1 from '@acorex/components/skeleton';
8
8
  import { AXSkeletonModule } from '@acorex/components/skeleton';
9
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';
10
13
  import { AXCalendarService } from '@acorex/core/date-time';
11
14
  import { startWith, map, debounceTime } from 'rxjs/operators';
12
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
+
13
27
  const AXP_ACTIVITY_LOG_PROVIDER = new InjectionToken('AXP_ACTIVITY_LOGS_PROVIDER');
14
28
  class AXPActivityLogService {
15
29
  constructor() {
@@ -119,13 +133,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
119
133
 
120
134
  //#region ---- Imports ----
121
135
  //#endregion
122
- //#region ---- Constants ----
123
- /** Data source name for select widgets listing registered catalog scopes. */
124
- const PLATFORM_CATALOG_SCOPES_DATASOURCE_NAME = 'platform-catalog-scopes';
125
- //#endregion
126
136
  //#region ---- Data source definition ----
127
137
  /**
128
- * Registered catalog scopes for select / selection-list widgets via {@link PLATFORM_CATALOG_SCOPES_DATASOURCE_NAME}.
138
+ * Registered catalog scopes for select / selection-list widgets via {@link AXPPlatformCoreDataSourceKeys.CatalogScopes}.
129
139
  */
130
140
  class AXPCatalogScopeDefinitionsDataSourceDefinition {
131
141
  constructor() {
@@ -138,7 +148,7 @@ class AXPCatalogScopeDefinitionsDataSourceDefinition {
138
148
  async items() {
139
149
  return [
140
150
  {
141
- name: PLATFORM_CATALOG_SCOPES_DATASOURCE_NAME,
151
+ name: AXPPlatformCoreDataSourceKeys.CatalogScopes,
142
152
  title: 'Catalog scopes',
143
153
  source: () => new AXDataSource({
144
154
  key: 'name',
@@ -705,6 +715,42 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
705
715
  type: Directive
706
716
  }] });
707
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
+
708
754
  function extractNestedFieldsWildcard(obj, basePath, fields) {
709
755
  const result = {};
710
756
  if (fields.length === 1 && fields[0] === '*') {
@@ -966,13 +1012,296 @@ function getDetailedChanges(obj1, obj2) {
966
1012
  function getEnumValues(enumType) {
967
1013
  return Object.entries(enumType).map(([key, value]) => ({ id: value, title: key }));
968
1014
  }
969
-
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);
1025
+ }
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;
1032
+ }
1033
+ const trimmed = entry.trim();
1034
+ if (trimmed !== '') {
1035
+ normalized[key] = trimmed;
1036
+ }
1037
+ }
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;
1047
+ }
1048
+ const normalizeScalar = (value) => {
1049
+ if (value === null || value === undefined) {
1050
+ return null;
1051
+ }
1052
+ if (typeof value === 'string') {
1053
+ const trimmed = value.trim();
1054
+ return trimmed === '' ? null : trimmed;
1055
+ }
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
+ }
970
1300
  /**
971
1301
  * Returns whether `current` differs from the saved/clean `baseline` snapshot.
972
- * Used by entity details pages, layout-builder dialogs, and {@link AXPContextStore}.
973
1302
  */
974
1303
  function isFormContextDirty(current, baseline) {
975
- return !isEqual(current, baseline);
1304
+ return !isFormValueEqual(current, baseline);
976
1305
  }
977
1306
  /**
978
1307
  * Clones a context object for use as a dirty-tracking baseline.
@@ -980,66 +1309,55 @@ function isFormContextDirty(current, baseline) {
980
1309
  function captureFormContextBaseline(context) {
981
1310
  return cloneDeep(context);
982
1311
  }
1312
+ //#endregion
983
1313
 
984
1314
  class AXPContextChangeEvent {
985
1315
  }
986
1316
  //#endregion
987
1317
  //#region ---- Context signal store ----
988
- // Shared reactive context: root injector has a default instance; widget/layout trees
989
- const AXPContextStore = signalStore(
990
- // Initial State
991
- withState(() => ({
992
- data: {}, // Shared context data
993
- state: 'initiated', // Current state
994
- initialSnapshot: {}, // Snapshot of the first initialized state
995
- previousSnapshot: {}, // Snapshot of the previous state
996
- userDirtyPaths: [], // Paths changed by user interaction
1318
+ const AXPContextStore = signalStore(withState(() => ({
1319
+ data: {},
1320
+ state: 'initiated',
1321
+ /** Last committed / saved baseline — discard reverts to this snapshot. */
1322
+ savedSnapshot: {},
1323
+ previousSnapshot: {},
997
1324
  lastChange: {
998
1325
  state: 'initiated',
999
- }, // Last change event
1000
- })),
1001
- // Computed Signals
1002
- withComputed(({ data, state, lastChange, initialSnapshot, previousSnapshot, userDirtyPaths }) => ({
1326
+ },
1327
+ })), withComputed(({ data, state, lastChange, savedSnapshot, previousSnapshot }) => ({
1003
1328
  isChanged: computed(() => state() === 'changed'),
1004
1329
  isReset: computed(() => state() === 'restored'),
1005
1330
  isInitiated: computed(() => state() === 'initiated'),
1006
1331
  isEmpty: computed(() => Object.keys(data()).length === 0),
1007
- isDirty: computed(() => isFormContextDirty(data(), initialSnapshot())),
1008
- isUserDirty: computed(() => userDirtyPaths().length > 0),
1009
- snapshot: computed(() => cloneDeep(data())), // Current data snapshot
1010
- initial: computed(() => cloneDeep(initialSnapshot())), // Initial snapshot
1011
- previous: computed(() => cloneDeep(previousSnapshot())), // Previous snapshot
1012
- changeEvent: computed(() => lastChange()), // Reactive last change event
1013
- })),
1014
- // Methods for State Management
1015
- withMethods((store) => {
1016
- const markUserDirtyPath = (path) => {
1017
- const normalized = path?.trim();
1018
- if (!normalized) {
1019
- return;
1020
- }
1021
- const current = store.userDirtyPaths();
1022
- if (current.includes(normalized)) {
1023
- return;
1024
- }
1025
- patchState(store, { userDirtyPaths: [...current, normalized] });
1026
- };
1027
- const clearUserDirtyPaths = () => {
1028
- if (store.userDirtyPaths().length === 0) {
1029
- return;
1030
- }
1031
- patchState(store, { userDirtyPaths: [] });
1032
- };
1033
- const resolvePatchOptions = (second) => {
1034
- if (typeof second === 'boolean') {
1035
- return { skipDirtyTracking: second };
1332
+ isSavedCommitted: computed(() => !isEmpty(savedSnapshot())),
1333
+ isDirty: computed(() => {
1334
+ const saved = savedSnapshot();
1335
+ if (isEmpty(saved)) {
1336
+ return false;
1036
1337
  }
1037
- return second ?? {};
1038
- };
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) => {
1039
1347
  const updateValue = (path, value, options) => {
1040
1348
  const currentData = cloneDeep(store.data());
1041
1349
  const oldValue = getSmart(currentData, path);
1042
- if (isEqual(oldValue, value)) {
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) {
1043
1361
  return;
1044
1362
  }
1045
1363
  const origin = options?.origin ?? 'system';
@@ -1058,14 +1376,11 @@ withMethods((store) => {
1058
1376
  state: 'changed',
1059
1377
  lastChange: changeEvent,
1060
1378
  });
1061
- if (origin === 'user') {
1062
- markUserDirtyPath(path);
1063
- }
1064
1379
  };
1065
1380
  const applyObjectPaths = (obj, options, prefix = '') => {
1066
1381
  for (const [key, value] of Object.entries(obj ?? {})) {
1067
1382
  const path = prefix ? `${prefix}.${key}` : key;
1068
- if (value !== null && isObject(value) && !Array.isArray(value)) {
1383
+ if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
1069
1384
  applyObjectPaths(value, options, path);
1070
1385
  }
1071
1386
  else {
@@ -1073,58 +1388,54 @@ withMethods((store) => {
1073
1388
  }
1074
1389
  }
1075
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
+ };
1076
1407
  return {
1077
- // Update a specific value
1078
1408
  update(path, value, options) {
1079
1409
  updateValue(path, value, options);
1080
1410
  },
1081
1411
  applyObjectPaths,
1082
- patch(context, second) {
1083
- const options = resolvePatchOptions(second);
1412
+ patch(context, options) {
1084
1413
  const currentData = cloneDeep(store.data());
1085
1414
  const updatedData = { ...currentData, ...context };
1086
- const origin = options.origin ?? 'system';
1087
1415
  const changeEvent = {
1088
1416
  state: 'patch',
1089
1417
  data: updatedData,
1090
- origin,
1091
1418
  };
1092
1419
  const syncedSnapshot = cloneDeep(updatedData);
1093
1420
  patchState(store, {
1094
- ...(options.skipDirtyTracking
1095
- ? { initialSnapshot: syncedSnapshot, previousSnapshot: syncedSnapshot }
1421
+ ...(options?.updateSaved
1422
+ ? { savedSnapshot: syncedSnapshot, previousSnapshot: syncedSnapshot }
1096
1423
  : { previousSnapshot: store.snapshot() }),
1097
1424
  data: updatedData,
1098
1425
  state: 'changed',
1099
1426
  lastChange: changeEvent,
1100
1427
  });
1101
- if (!options.skipDirtyTracking && origin === 'user') {
1102
- Object.keys(context).forEach((key) => markUserDirtyPath(key));
1103
- }
1104
1428
  },
1105
- // Reset to the initial state
1429
+ /** Reverts live data to the last saved snapshot. */
1430
+ revertToSaved,
1106
1431
  reset() {
1107
- const initialData = store.initial();
1108
- const changeEvent = {
1109
- oldValue: cloneDeep(store.data()), // Current data becomes old value
1110
- newValue: cloneDeep(initialData), // Reset to the initial state
1111
- path: '',
1112
- state: 'restored',
1113
- data: initialData,
1114
- };
1115
- patchState(store, {
1116
- previousSnapshot: store.snapshot(), // Save the previous state
1117
- data: initialData,
1118
- state: 'restored',
1119
- lastChange: changeEvent,
1120
- userDirtyPaths: [],
1121
- });
1432
+ revertToSaved();
1122
1433
  },
1123
- // Initialize the state
1434
+ /** Loads live data; saved baseline is committed separately via {@link commitSaved}. */
1124
1435
  set(initialData) {
1125
1436
  const currentData = store.data();
1126
- if (isEqual(currentData, initialData)) {
1127
- return; // Skip if the current state matches the initial state
1437
+ if (isFormValueEqual(currentData, initialData)) {
1438
+ return;
1128
1439
  }
1129
1440
  const changeEvent = {
1130
1441
  oldValue: null,
@@ -1134,33 +1445,43 @@ withMethods((store) => {
1134
1445
  data: initialData,
1135
1446
  };
1136
1447
  patchState(store, {
1137
- initialSnapshot: cloneDeep(initialData), // Save the initial state
1138
- previousSnapshot: store.snapshot(), // Save the current state as the previous
1448
+ previousSnapshot: store.snapshot(),
1139
1449
  data: initialData,
1140
1450
  state: 'initiated',
1141
1451
  lastChange: changeEvent,
1142
- userDirtyPaths: [],
1143
1452
  });
1144
1453
  },
1145
- // Get a specific value
1146
1454
  getValue(path) {
1147
1455
  return getSmart(store.data(), path);
1148
1456
  },
1149
- // Check if a path exists in the context
1457
+ getSavedValue(path) {
1458
+ return getSmart(store.savedSnapshot(), path);
1459
+ },
1150
1460
  hasValue(path) {
1151
1461
  return has(store.data(), path);
1152
1462
  },
1153
- /** Marks the current data as the clean baseline (after init/normalization settles). */
1154
- commitBaseline() {
1463
+ /** Marks the current data as the saved baseline. */
1464
+ commitSaved() {
1155
1465
  const snapshot = cloneDeep(store.data());
1156
1466
  patchState(store, {
1157
- initialSnapshot: snapshot,
1467
+ savedSnapshot: snapshot,
1158
1468
  previousSnapshot: snapshot,
1159
1469
  state: 'initiated',
1160
- userDirtyPaths: [],
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,
1161
1483
  });
1162
1484
  },
1163
- clearUserDirtyPaths,
1164
1485
  };
1165
1486
  }));
1166
1487
  //#endregion
@@ -2325,6 +2646,709 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
2325
2646
  args: [{ providedIn: 'root' }]
2326
2647
  }] });
2327
2648
 
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;
2671
+ }
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;
2845
+ }
2846
+ }
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;
2859
+ }
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;
2882
+ }
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) {
2997
+ return false;
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] };
3109
+ }
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 [];
3118
+ }
3119
+ const result = [];
3120
+ for (const shortcut of shortcuts) {
3121
+ const normalized = normalizeKeyboardShortcut(shortcut);
3122
+ if (normalized) {
3123
+ result.push(normalized);
3124
+ }
3125
+ }
3126
+ return result;
3127
+ }
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
3146
+
3147
+ //#region ---- Imports ----
3148
+ //#endregion
3149
+ class AXPKeyboardShortcutRegistry {
3150
+ constructor() {
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
+ };
3187
+ }
3188
+ //#endregion
3189
+ //#region ---- Public Methods ----
3190
+ /**
3191
+ * Registers a scope of shortcuts. Unregisters automatically when `owner` is destroyed.
3192
+ */
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
+ owner.onDestroy(() => {
3207
+ this.unregister(registrationId);
3208
+ });
3209
+ }
3210
+ /**
3211
+ * Wires the F1 help dialog opener (provided by platform/common).
3212
+ */
3213
+ setHelpDialogHandler(handler) {
3214
+ this.helpDialogHandler = handler;
3215
+ this.ensureHelpShortcutRegistered();
3216
+ }
3217
+ /** OS-aware display string for UI hints. */
3218
+ formatDisplayKeys(chords) {
3219
+ return formatKeyboardShortcutChords(chords, { isRtl: this.isRtlProfile() });
3220
+ }
3221
+ //#endregion
3222
+ //#region ---- Private Methods ----
3223
+ ensureHelpShortcutRegistered() {
3224
+ const alreadyRegistered = this.registrations().some((entry) => entry.id === 'system:keyboard-shortcuts-help');
3225
+ if (alreadyRegistered || !this.helpDialogHandler) {
3226
+ return;
3227
+ }
3228
+ const handler = this.helpDialogHandler;
3229
+ const entry = {
3230
+ registrationId: 'system:keyboard-shortcuts-help',
3231
+ id: 'system:keyboard-shortcuts-help',
3232
+ priority: -1,
3233
+ scope: '@general:keyboard-shortcuts.groups.system',
3234
+ shortcuts: [
3235
+ {
3236
+ keys: ['f1'],
3237
+ title: '@general:keyboard-shortcuts.help-action',
3238
+ allowInEditableFields: true,
3239
+ handler: () => {
3240
+ void handler();
3241
+ },
3242
+ },
3243
+ ],
3244
+ };
3245
+ this.registrations.update((items) => [...items, entry]);
3246
+ this.ensureListener();
3247
+ }
3248
+ createRegistrationId() {
3249
+ if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
3250
+ return crypto.randomUUID();
3251
+ }
3252
+ return `${Date.now()}-${Math.random().toString(36).slice(2)}`;
3253
+ }
3254
+ unregister(registrationId) {
3255
+ this.registrations.update((items) => items.filter((item) => item.registrationId !== registrationId));
3256
+ }
3257
+ ensureListener() {
3258
+ if (this.listenerAttached || typeof this.document === 'undefined') {
3259
+ return;
3260
+ }
3261
+ this.document.addEventListener('keydown', this.onDocumentKeyDown, true);
3262
+ this.listenerAttached = true;
3263
+ }
3264
+ resolveActiveCandidates(event) {
3265
+ const editable = isKeyboardTargetInsideEditableField(event.target);
3266
+ const candidates = [];
3267
+ for (const registration of this.registrations()) {
3268
+ if (!this.isRegistrationActive(registration)) {
3269
+ continue;
3270
+ }
3271
+ const priority = registration.priority ?? 0;
3272
+ for (const binding of registration.shortcuts) {
3273
+ if (binding.when && !binding.when()) {
3274
+ continue;
3275
+ }
3276
+ if (editable && !binding.allowInEditableFields) {
3277
+ continue;
3278
+ }
3279
+ for (const chord of binding.keys) {
3280
+ const directionBehavior = resolveEffectiveDirectionBehavior(chord, binding.directionBehavior);
3281
+ if (!matchesKeyboardShortcutChord(event, chord, {
3282
+ directionBehavior,
3283
+ isRtl: this.isRtlProfile(),
3284
+ })) {
3285
+ continue;
3286
+ }
3287
+ candidates.push({
3288
+ priority,
3289
+ registration,
3290
+ binding,
3291
+ chord,
3292
+ });
3293
+ break;
3294
+ }
3295
+ }
3296
+ }
3297
+ return candidates.sort((a, b) => b.priority - a.priority);
3298
+ }
3299
+ isRegistrationActive(registration) {
3300
+ if (registration.when && !registration.when()) {
3301
+ return false;
3302
+ }
3303
+ const element = registration.elementRef?.nativeElement;
3304
+ if (element && !this.document.contains(element)) {
3305
+ return false;
3306
+ }
3307
+ return true;
3308
+ }
3309
+ isRtlProfile() {
3310
+ return this.activeProfile()?.i18nMeta?.rtl === true;
3311
+ }
3312
+ buildHelpEntries() {
3313
+ const entries = [];
3314
+ const isRtl = this.isRtlProfile();
3315
+ for (const registration of this.registrations()) {
3316
+ if (!this.isRegistrationActive(registration)) {
3317
+ continue;
3318
+ }
3319
+ if (registration.id === 'system:keyboard-shortcuts-help') {
3320
+ continue;
3321
+ }
3322
+ const priority = registration.priority ?? 0;
3323
+ for (const binding of registration.shortcuts) {
3324
+ const displayChords = binding.keys.map((chord) => {
3325
+ const directionBehavior = resolveEffectiveDirectionBehavior(chord, binding.directionBehavior);
3326
+ return resolveDisplayShortcutChord(chord, isRtl, directionBehavior);
3327
+ });
3328
+ entries.push({
3329
+ registrationId: registration.registrationId,
3330
+ bindingTitle: binding.title,
3331
+ chords: [...binding.keys],
3332
+ displayChords,
3333
+ displayKeys: formatKeyboardShortcutChords(displayChords, { skipDirectionResolve: true }),
3334
+ scope: registration.scope,
3335
+ group: binding.group ?? registration.group,
3336
+ priority,
3337
+ });
3338
+ }
3339
+ }
3340
+ return entries.sort((a, b) => b.priority - a.priority || a.bindingTitle.localeCompare(b.bindingTitle));
3341
+ }
3342
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPKeyboardShortcutRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
3343
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPKeyboardShortcutRegistry, providedIn: 'root' }); }
3344
+ }
3345
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPKeyboardShortcutRegistry, decorators: [{
3346
+ type: Injectable,
3347
+ args: [{
3348
+ providedIn: 'root',
3349
+ }]
3350
+ }] });
3351
+
2328
3352
  //#region ---- Imports ----
2329
3353
  //#endregion
2330
3354
 
@@ -2611,10 +3635,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
2611
3635
  //#region ---- Imports ----
2612
3636
  //#endregion
2613
3637
  //#region ---- Module manifests data source ----
2614
- /**
2615
- * Registered module manifests for select widgets via dataSource name {@link MODULE_MANIFESTS_DATASOURCE_NAME}.
2616
- */
2617
- const MODULE_MANIFESTS_DATASOURCE_NAME = 'platform-module-manifests';
2618
3638
  /**
2619
3639
  * Data source definition for module names/titles from {@link AXPModuleManifestRegistry}.
2620
3640
  */
@@ -2629,7 +3649,7 @@ class AXPModuleManifestsDataSourceDefinition {
2629
3649
  async items() {
2630
3650
  return [
2631
3651
  {
2632
- name: MODULE_MANIFESTS_DATASOURCE_NAME,
3652
+ name: AXPPlatformCoreDataSourceKeys.ModuleManifests,
2633
3653
  title: 'Module manifests',
2634
3654
  source: () => new AXDataSource({
2635
3655
  key: 'id',
@@ -2684,6 +3704,8 @@ class AXPAppStartUpService {
2684
3704
  this.translationService = inject(AXTranslationService);
2685
3705
  this.tasks = [];
2686
3706
  }
3707
+ //#endregion
3708
+ //#region ---- Public Methods ----
2687
3709
  registerTask(task) {
2688
3710
  this.tasks.push(task);
2689
3711
  }
@@ -3990,16 +5012,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
3990
5012
 
3991
5013
  const loggingEnabled = false; // Set to true to enable logging, false to disable
3992
5014
  //#region ---- Multilingual string helpers ----
3993
- /**
3994
- * Per-locale string map as produced by multilingual editors (e.g. `{ "en-US": "...", "fa-IR": "..." }`).
3995
- */
3996
- function isLocaleStringMap(value) {
3997
- if (value === null || typeof value !== 'object' || Array.isArray(value) || value instanceof Date) {
3998
- return false;
3999
- }
4000
- const values = Object.values(value);
4001
- return values.length > 0 && values.every((v) => typeof v === 'string');
4002
- }
4003
5015
  /**
4004
5016
  * Lowercased text for client-side filtering: plain string or all locale values joined.
4005
5017
  */
@@ -4724,5 +5736,5 @@ function generateKebabCase(title) {
4724
5736
  * Generated bundle index. Do not edit.
4725
5737
  */
4726
5738
 
4727
- 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, AXPModuleManifestModule, AXPModuleManifestRegistry, AXPModuleManifestsDataSourceDefinition, 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, MODULE_MANIFESTS_DATASOURCE_NAME, PLATFORM_CATALOG_SCOPES_DATASOURCE_NAME, applyFilterArray, applyPagination, applyQueryArray, applySortArray, applySystemActionDefault, captureFormContextBaseline, cleanDeep, coerceUnknownToBoolean, coerceUnknownToDate, coerceUnknownToFiniteNumber, coerceUnknownToTrimmedString, compareMultiLanguageStrings, containsHtmlMarkup, createProviderWithInjectionContext, defaultColumnWidthProvider, extractNestedFieldsWildcard, extractTextFromHtml, extractValue, generateKebabCase, getActionButton, getChangedPaths, getDetailedChanges, getEnumValues, getNestedKeys, getSmart, getSystemActions, isFormContextDirty, normalizeDefinitionCategories, objectKeyValueTransforms, provideLazyProvider, resolveActionLook, resolvePlatformScopeKey, resolvePlatformScopeName, searchInMultiLanguageString, setSmart, sortByMultiLanguageString, unwrapValueProperty };
5739
+ 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 };
4728
5740
  //# sourceMappingURL=acorex-platform-core.mjs.map