@acorex/platform 21.0.0-next.70 → 21.0.0-next.72
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.
- package/fesm2022/acorex-platform-auth.mjs +10 -2
- package/fesm2022/acorex-platform-auth.mjs.map +1 -1
- package/fesm2022/{acorex-platform-common-common-settings.provider-Bi1RYif5.mjs → acorex-platform-common-common-settings.provider-Ytey9uhY.mjs} +15 -1
- package/fesm2022/acorex-platform-common-common-settings.provider-Ytey9uhY.mjs.map +1 -0
- package/fesm2022/acorex-platform-common.mjs +3798 -1674
- package/fesm2022/acorex-platform-common.mjs.map +1 -1
- package/fesm2022/acorex-platform-core.mjs +1362 -97
- package/fesm2022/acorex-platform-core.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-builder.mjs +446 -44
- package/fesm2022/acorex-platform-layout-builder.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-components.mjs +149 -109
- package/fesm2022/acorex-platform-layout-components.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-designer.mjs +199 -126
- package/fesm2022/acorex-platform-layout-designer.mjs.map +1 -1
- package/fesm2022/{acorex-platform-layout-entity-attachments-page.component-D8iQnT-R.mjs → acorex-platform-layout-entity-attachments-page.component-B0EkdqvH.mjs} +6 -1
- package/fesm2022/acorex-platform-layout-entity-attachments-page.component-B0EkdqvH.mjs.map +1 -0
- package/fesm2022/acorex-platform-layout-entity.mjs +823 -594
- package/fesm2022/acorex-platform-layout-entity.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-views.mjs +845 -218
- package/fesm2022/acorex-platform-layout-views.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-widget-core.mjs +122 -33
- package/fesm2022/acorex-platform-layout-widget-core.mjs.map +1 -1
- 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
- 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
- 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
- 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
- package/fesm2022/acorex-platform-layout-widgets.mjs +312 -676
- package/fesm2022/acorex-platform-layout-widgets.mjs.map +1 -1
- package/fesm2022/acorex-platform-themes-default-error-401.component-B1nsdpTY.mjs +48 -0
- package/fesm2022/acorex-platform-themes-default-error-401.component-B1nsdpTY.mjs.map +1 -0
- package/fesm2022/acorex-platform-themes-default-error-404.component-D4UvRe8u.mjs +42 -0
- package/fesm2022/acorex-platform-themes-default-error-404.component-D4UvRe8u.mjs.map +1 -0
- package/fesm2022/acorex-platform-themes-default.mjs +89 -46
- package/fesm2022/acorex-platform-themes-default.mjs.map +1 -1
- package/fesm2022/acorex-platform-themes-shared.mjs +50 -30
- package/fesm2022/acorex-platform-themes-shared.mjs.map +1 -1
- package/package.json +1 -1
- package/types/acorex-platform-auth.d.ts +2 -0
- package/types/acorex-platform-common.d.ts +899 -256
- package/types/acorex-platform-core.d.ts +394 -60
- package/types/acorex-platform-layout-builder.d.ts +78 -13
- package/types/acorex-platform-layout-components.d.ts +30 -24
- package/types/acorex-platform-layout-entity.d.ts +93 -44
- package/types/acorex-platform-layout-views.d.ts +162 -42
- package/types/acorex-platform-layout-widget-core.d.ts +60 -33
- package/types/acorex-platform-layout-widgets.d.ts +48 -20
- package/types/acorex-platform-themes-default.d.ts +38 -8
- package/types/acorex-platform-themes-shared.d.ts +6 -0
- package/types/acorex-platform-workflow.d.ts +1 -1
- package/fesm2022/acorex-platform-common-common-settings.provider-Bi1RYif5.mjs.map +0 -1
- package/fesm2022/acorex-platform-layout-entity-attachments-page.component-D8iQnT-R.mjs.map +0 -1
- package/fesm2022/acorex-platform-themes-default-error-401.component-C7EYJzSr.mjs +0 -31
- package/fesm2022/acorex-platform-themes-default-error-401.component-C7EYJzSr.mjs.map +0 -1
- package/fesm2022/acorex-platform-themes-default-error-404.component-7MVLMwIa.mjs +0 -25
- package/fesm2022/acorex-platform-themes-default-error-404.component-7MVLMwIa.mjs.map +0 -1
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
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
|
-
import {
|
|
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';
|
|
4
6
|
import { signalStore, withState, withComputed, withMethods, patchState } from '@ngrx/signals';
|
|
5
7
|
import * as i1 from '@acorex/components/skeleton';
|
|
6
8
|
import { AXSkeletonModule } from '@acorex/components/skeleton';
|
|
7
9
|
import { Subject, interval, fromEvent } from 'rxjs';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
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
|
|
|
@@ -32,6 +35,184 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
32
35
|
class AXPActivityLogProvider {
|
|
33
36
|
}
|
|
34
37
|
|
|
38
|
+
//#region ---- Catalog scope definition ----
|
|
39
|
+
//#endregion
|
|
40
|
+
|
|
41
|
+
//#region ---- Provider contract ----
|
|
42
|
+
const AXP_CATALOG_SCOPE_DEFINITION_PROVIDER = new InjectionToken('AXP_CATALOG_SCOPE_DEFINITION_PROVIDER');
|
|
43
|
+
//#endregion
|
|
44
|
+
|
|
45
|
+
//#region ---- Imports ----
|
|
46
|
+
//#endregion
|
|
47
|
+
//#region ---- Aggregator service ----
|
|
48
|
+
/**
|
|
49
|
+
* Collects {@link AXPCatalogScopeDefinition} entries from all registered providers.
|
|
50
|
+
*/
|
|
51
|
+
class AXPCatalogScopeDefinitionProviderService {
|
|
52
|
+
constructor() {
|
|
53
|
+
//#region ---- Services & Dependencies ----
|
|
54
|
+
this.providers = inject(AXP_CATALOG_SCOPE_DEFINITION_PROVIDER, { optional: true });
|
|
55
|
+
this.translation = inject(AXTranslationService);
|
|
56
|
+
}
|
|
57
|
+
//#endregion
|
|
58
|
+
//#region ---- Public API ----
|
|
59
|
+
async items() {
|
|
60
|
+
const merged = [];
|
|
61
|
+
const seen = new Set();
|
|
62
|
+
if (!Array.isArray(this.providers)) {
|
|
63
|
+
return merged;
|
|
64
|
+
}
|
|
65
|
+
for (const raw of this.providers) {
|
|
66
|
+
const provider = await Promise.resolve(raw);
|
|
67
|
+
if (!provider || typeof provider.items !== 'function') {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
for (const item of await provider.items()) {
|
|
71
|
+
const name = item.name?.trim();
|
|
72
|
+
if (!name) {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (seen.has(name)) {
|
|
76
|
+
throw new Error(`Duplicate catalog scope definition: "${name}"`);
|
|
77
|
+
}
|
|
78
|
+
seen.add(name);
|
|
79
|
+
merged.push(item);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return merged.sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
83
|
+
}
|
|
84
|
+
async get(name) {
|
|
85
|
+
const key = name?.trim();
|
|
86
|
+
if (!key) {
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
return (await this.items()).find((d) => d.name === key);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Scopes applicable to the given entity ref (`Module.Entity`).
|
|
93
|
+
*/
|
|
94
|
+
async byTarget(entityRef) {
|
|
95
|
+
const target = entityRef?.trim();
|
|
96
|
+
if (!target) {
|
|
97
|
+
return [];
|
|
98
|
+
}
|
|
99
|
+
return (await this.items()).filter((d) => d.targets?.some((t) => t === target));
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Resolves display title for a scope key (active locale).
|
|
103
|
+
*/
|
|
104
|
+
resolveTitle(definition) {
|
|
105
|
+
const locale = this.translation.getActiveLang() ?? 'en-US';
|
|
106
|
+
if (typeof definition.title === 'string') {
|
|
107
|
+
const title = definition.title.trim();
|
|
108
|
+
if (title.startsWith('@')) {
|
|
109
|
+
return this.translation.translateSync(title) || title;
|
|
110
|
+
}
|
|
111
|
+
return title;
|
|
112
|
+
}
|
|
113
|
+
return resolveMultiLanguageString(definition.title, locale) || definition.name;
|
|
114
|
+
}
|
|
115
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPCatalogScopeDefinitionProviderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
116
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPCatalogScopeDefinitionProviderService, providedIn: 'root' }); }
|
|
117
|
+
}
|
|
118
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPCatalogScopeDefinitionProviderService, decorators: [{
|
|
119
|
+
type: Injectable,
|
|
120
|
+
args: [{ providedIn: 'root' }]
|
|
121
|
+
}] });
|
|
122
|
+
|
|
123
|
+
//#region ---- Imports ----
|
|
124
|
+
//#endregion
|
|
125
|
+
//#region ---- Constants ----
|
|
126
|
+
/** Data source name for select widgets listing registered catalog scopes. */
|
|
127
|
+
const PLATFORM_CATALOG_SCOPES_DATASOURCE_NAME = 'platform-catalog-scopes';
|
|
128
|
+
//#endregion
|
|
129
|
+
//#region ---- Data source definition ----
|
|
130
|
+
/**
|
|
131
|
+
* Registered catalog scopes for select / selection-list widgets via {@link PLATFORM_CATALOG_SCOPES_DATASOURCE_NAME}.
|
|
132
|
+
*/
|
|
133
|
+
class AXPCatalogScopeDefinitionsDataSourceDefinition {
|
|
134
|
+
constructor() {
|
|
135
|
+
//#region ---- Services & Dependencies ----
|
|
136
|
+
this.catalogScopeRegistry = inject(AXPCatalogScopeDefinitionProviderService);
|
|
137
|
+
//#endregion
|
|
138
|
+
}
|
|
139
|
+
//#endregion
|
|
140
|
+
//#region ---- Public API ----
|
|
141
|
+
async items() {
|
|
142
|
+
return [
|
|
143
|
+
{
|
|
144
|
+
name: PLATFORM_CATALOG_SCOPES_DATASOURCE_NAME,
|
|
145
|
+
title: 'Catalog scopes',
|
|
146
|
+
source: () => new AXDataSource({
|
|
147
|
+
key: 'name',
|
|
148
|
+
pageSize: 500,
|
|
149
|
+
load: async (e) => {
|
|
150
|
+
const all = await this.catalogScopeRegistry.items();
|
|
151
|
+
const localeFilter = this.readTargetFilter(e?.filter);
|
|
152
|
+
const items = all
|
|
153
|
+
.filter((scope) => !localeFilter || scope.targets?.includes(localeFilter))
|
|
154
|
+
.map((scope) => ({
|
|
155
|
+
name: scope.name,
|
|
156
|
+
title: this.catalogScopeRegistry.resolveTitle(scope),
|
|
157
|
+
description: scope.description,
|
|
158
|
+
targets: scope.targets,
|
|
159
|
+
icon: scope.icon,
|
|
160
|
+
cssClass: scope.cssClass,
|
|
161
|
+
}));
|
|
162
|
+
return { items, total: items.length };
|
|
163
|
+
},
|
|
164
|
+
byKey: async (key) => {
|
|
165
|
+
const name = String(key ?? '').trim();
|
|
166
|
+
if (!name) {
|
|
167
|
+
return undefined;
|
|
168
|
+
}
|
|
169
|
+
const scope = await this.catalogScopeRegistry.get(name);
|
|
170
|
+
if (!scope) {
|
|
171
|
+
return undefined;
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
name: scope.name,
|
|
175
|
+
title: this.catalogScopeRegistry.resolveTitle(scope),
|
|
176
|
+
description: scope.description,
|
|
177
|
+
targets: scope.targets,
|
|
178
|
+
icon: scope.icon,
|
|
179
|
+
cssClass: scope.cssClass,
|
|
180
|
+
};
|
|
181
|
+
},
|
|
182
|
+
}),
|
|
183
|
+
columns: [
|
|
184
|
+
{ name: 'name', title: 'Name', datatype: 'string', type: 'text-editor' },
|
|
185
|
+
{ name: 'title', title: 'Title', datatype: 'string', type: 'text-editor' },
|
|
186
|
+
],
|
|
187
|
+
filters: [
|
|
188
|
+
{
|
|
189
|
+
field: 'targets',
|
|
190
|
+
title: 'Target entity',
|
|
191
|
+
operator: { type: 'contains' },
|
|
192
|
+
widget: { type: 'text-editor' },
|
|
193
|
+
filterType: { advance: false, inline: false },
|
|
194
|
+
},
|
|
195
|
+
],
|
|
196
|
+
textField: { name: 'title', title: 'Title' },
|
|
197
|
+
valueField: { name: 'name', title: 'Name' },
|
|
198
|
+
},
|
|
199
|
+
];
|
|
200
|
+
}
|
|
201
|
+
//#endregion
|
|
202
|
+
//#region ---- Helpers ----
|
|
203
|
+
readTargetFilter(filter) {
|
|
204
|
+
if (!filter || typeof filter !== 'object') {
|
|
205
|
+
return undefined;
|
|
206
|
+
}
|
|
207
|
+
const f = filter;
|
|
208
|
+
if (f.field === 'targets' && typeof f.value === 'string' && f.value.trim()) {
|
|
209
|
+
return f.value.trim();
|
|
210
|
+
}
|
|
211
|
+
return undefined;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
//#endregion
|
|
215
|
+
|
|
35
216
|
//#region ---- Color Palette Provider ----
|
|
36
217
|
/**
|
|
37
218
|
* Abstract class for color palette providers
|
|
@@ -527,6 +708,42 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
527
708
|
type: Directive
|
|
528
709
|
}] });
|
|
529
710
|
|
|
711
|
+
//#region ---- Locale map shape ----
|
|
712
|
+
/** Per-locale string map (`{ 'en-US': '...', 'fa-IR': '...' }`). */
|
|
713
|
+
function isLocaleStringMap(value) {
|
|
714
|
+
if (!isPlainObject(value)) {
|
|
715
|
+
return false;
|
|
716
|
+
}
|
|
717
|
+
const entries = Object.values(value);
|
|
718
|
+
if (entries.length === 0) {
|
|
719
|
+
return false;
|
|
720
|
+
}
|
|
721
|
+
return entries.every((entry) => entry === null || entry === undefined || typeof entry === 'string');
|
|
722
|
+
}
|
|
723
|
+
/**
|
|
724
|
+
* Use locale-map read/write when the option is on or live/saved data is already a map.
|
|
725
|
+
* Auto-enables when API data is a map but the editor option was left off.
|
|
726
|
+
*/
|
|
727
|
+
function shouldUseLocaleMapShape(localeMapOptionEnabled, current, saved) {
|
|
728
|
+
return localeMapOptionEnabled || isLocaleStringMap(current) || isLocaleStringMap(saved);
|
|
729
|
+
}
|
|
730
|
+
/** Updates one locale entry; preserves sibling locales from current or saved map. */
|
|
731
|
+
function buildLocaleTextMapValue(current, saved, lang, input) {
|
|
732
|
+
const seed = isLocaleStringMap(current)
|
|
733
|
+
? { ...current }
|
|
734
|
+
: isLocaleStringMap(saved)
|
|
735
|
+
? { ...saved }
|
|
736
|
+
: {};
|
|
737
|
+
seed[lang] = input ?? '';
|
|
738
|
+
for (const key of Object.keys(seed)) {
|
|
739
|
+
if ((seed[key] ?? '').trim() === '') {
|
|
740
|
+
delete seed[key];
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
return Object.keys(seed).length === 0 ? null : seed;
|
|
744
|
+
}
|
|
745
|
+
//#endregion
|
|
746
|
+
|
|
530
747
|
function extractNestedFieldsWildcard(obj, basePath, fields) {
|
|
531
748
|
const result = {};
|
|
532
749
|
if (fields.length === 1 && fields[0] === '*') {
|
|
@@ -788,46 +1005,355 @@ function getDetailedChanges(obj1, obj2) {
|
|
|
788
1005
|
function getEnumValues(enumType) {
|
|
789
1006
|
return Object.entries(enumType).map(([key, value]) => ({ id: value, title: key }));
|
|
790
1007
|
}
|
|
1008
|
+
//#region ---- Form value equality (dirty / baseline) ----
|
|
1009
|
+
/** Whether a value uses the multi-language text widget shape (string or locale map). */
|
|
1010
|
+
function isMultiLanguageFormShape(value) {
|
|
1011
|
+
if (value === null || value === undefined) {
|
|
1012
|
+
return true;
|
|
1013
|
+
}
|
|
1014
|
+
if (typeof value === 'string') {
|
|
1015
|
+
return true;
|
|
1016
|
+
}
|
|
1017
|
+
return isLocaleStringMap(value);
|
|
1018
|
+
}
|
|
1019
|
+
/** Drops empty/whitespace locale entries from a multi-language map. */
|
|
1020
|
+
function normalizeLocaleStringMap(value) {
|
|
1021
|
+
const normalized = {};
|
|
1022
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
1023
|
+
if (typeof entry !== 'string') {
|
|
1024
|
+
continue;
|
|
1025
|
+
}
|
|
1026
|
+
const trimmed = entry.trim();
|
|
1027
|
+
if (trimmed !== '') {
|
|
1028
|
+
normalized[key] = trimmed;
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
return Object.keys(normalized).length === 0 ? null : normalized;
|
|
1032
|
+
}
|
|
1033
|
+
/**
|
|
1034
|
+
* Compares multi-language field values (plain string vs locale map) for dirty/baseline checks.
|
|
1035
|
+
* Returns `null` when either side is not a multi-language shape (caller uses generic normalization).
|
|
1036
|
+
*/
|
|
1037
|
+
function compareMultiLanguageFormValues(a, b) {
|
|
1038
|
+
if (!isMultiLanguageFormShape(a) || !isMultiLanguageFormShape(b)) {
|
|
1039
|
+
return null;
|
|
1040
|
+
}
|
|
1041
|
+
const normalizeScalar = (value) => {
|
|
1042
|
+
if (value === null || value === undefined) {
|
|
1043
|
+
return null;
|
|
1044
|
+
}
|
|
1045
|
+
if (typeof value === 'string') {
|
|
1046
|
+
const trimmed = value.trim();
|
|
1047
|
+
return trimmed === '' ? null : trimmed;
|
|
1048
|
+
}
|
|
1049
|
+
if (isLocaleStringMap(value)) {
|
|
1050
|
+
return normalizeLocaleStringMap(value);
|
|
1051
|
+
}
|
|
1052
|
+
return null;
|
|
1053
|
+
};
|
|
1054
|
+
const left = normalizeScalar(a);
|
|
1055
|
+
const right = normalizeScalar(b);
|
|
1056
|
+
if (left === null && right === null) {
|
|
1057
|
+
return true;
|
|
1058
|
+
}
|
|
1059
|
+
if (left === null || right === null) {
|
|
1060
|
+
return false;
|
|
1061
|
+
}
|
|
1062
|
+
if (typeof left === 'string' && typeof right === 'string') {
|
|
1063
|
+
return left === right;
|
|
1064
|
+
}
|
|
1065
|
+
if (typeof left === 'string' && typeof right === 'object') {
|
|
1066
|
+
const values = Object.values(right);
|
|
1067
|
+
return values.length > 0 && values.every((text) => text === left);
|
|
1068
|
+
}
|
|
1069
|
+
if (typeof right === 'string' && typeof left === 'object') {
|
|
1070
|
+
const values = Object.values(left);
|
|
1071
|
+
return values.length > 0 && values.every((text) => text === right);
|
|
1072
|
+
}
|
|
1073
|
+
if (typeof left !== 'object' || typeof right !== 'object') {
|
|
1074
|
+
return false;
|
|
1075
|
+
}
|
|
1076
|
+
const leftMap = left;
|
|
1077
|
+
const rightMap = right;
|
|
1078
|
+
const leftKeys = Object.keys(leftMap).sort();
|
|
1079
|
+
const rightKeys = Object.keys(rightMap).sort();
|
|
1080
|
+
if (leftKeys.length !== rightKeys.length) {
|
|
1081
|
+
return false;
|
|
1082
|
+
}
|
|
1083
|
+
return leftKeys.every((key) => leftMap[key] === rightMap[key]);
|
|
1084
|
+
}
|
|
1085
|
+
/** Lookup/select filter-mode payload: `{ value, displayText?, operation }`. */
|
|
1086
|
+
function isFilterFormShape(value) {
|
|
1087
|
+
if (!isPlainObject(value)) {
|
|
1088
|
+
return false;
|
|
1089
|
+
}
|
|
1090
|
+
const record = value;
|
|
1091
|
+
return 'value' in record && 'operation' in record;
|
|
1092
|
+
}
|
|
1093
|
+
/**
|
|
1094
|
+
* Compares filter-mode widget values by selection identity; ignores derived `displayText`.
|
|
1095
|
+
*/
|
|
1096
|
+
function compareFilterFormValues(a, b) {
|
|
1097
|
+
if (!isFilterFormShape(a) || !isFilterFormShape(b)) {
|
|
1098
|
+
return null;
|
|
1099
|
+
}
|
|
1100
|
+
const left = a;
|
|
1101
|
+
const right = b;
|
|
1102
|
+
if (!isFormValueEqual(left['value'], right['value'])) {
|
|
1103
|
+
return false;
|
|
1104
|
+
}
|
|
1105
|
+
const opLeft = get(left, 'operation.type');
|
|
1106
|
+
const opRight = get(right, 'operation.type');
|
|
1107
|
+
if (opLeft !== undefined || opRight !== undefined) {
|
|
1108
|
+
return opLeft === opRight;
|
|
1109
|
+
}
|
|
1110
|
+
return true;
|
|
1111
|
+
}
|
|
1112
|
+
function isReferenceScalar(value) {
|
|
1113
|
+
return typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean';
|
|
1114
|
+
}
|
|
1115
|
+
/** Scalar id or `{ id }` identity used by lookup/select widgets. */
|
|
1116
|
+
function getReferenceId(value) {
|
|
1117
|
+
if (isNil(value)) {
|
|
1118
|
+
return undefined;
|
|
1119
|
+
}
|
|
1120
|
+
if (isReferenceScalar(value)) {
|
|
1121
|
+
return value;
|
|
1122
|
+
}
|
|
1123
|
+
if (isPlainObject(value)) {
|
|
1124
|
+
const id = get(value, 'id');
|
|
1125
|
+
if (!isNil(id)) {
|
|
1126
|
+
return id;
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
return undefined;
|
|
1130
|
+
}
|
|
1131
|
+
/**
|
|
1132
|
+
* Compares scalar selection ids to full lookup/select items (`5` vs `{ id: 5, ... }`).
|
|
1133
|
+
* Does not apply when both sides are objects — those use structural compare so field edits stay dirty.
|
|
1134
|
+
*/
|
|
1135
|
+
function compareReferenceFormValues(a, b) {
|
|
1136
|
+
const refA = getReferenceId(a);
|
|
1137
|
+
const refB = getReferenceId(b);
|
|
1138
|
+
if (refA === undefined || refB === undefined) {
|
|
1139
|
+
return null;
|
|
1140
|
+
}
|
|
1141
|
+
const aIsScalar = isReferenceScalar(a);
|
|
1142
|
+
const bIsScalar = isReferenceScalar(b);
|
|
1143
|
+
if (!aIsScalar && !bIsScalar) {
|
|
1144
|
+
return null;
|
|
1145
|
+
}
|
|
1146
|
+
return isEqual(normalizeFormValue(refA), normalizeFormValue(refB));
|
|
1147
|
+
}
|
|
1148
|
+
/**
|
|
1149
|
+
* Semantic equality for lookup/select values when restoring saved baseline shape.
|
|
1150
|
+
* Compares by selection identity (id), including two full items with the same id.
|
|
1151
|
+
*/
|
|
1152
|
+
function isSelectionValueEqual(a, b) {
|
|
1153
|
+
const filterEqual = compareFilterFormValues(a, b);
|
|
1154
|
+
if (filterEqual !== null) {
|
|
1155
|
+
return filterEqual;
|
|
1156
|
+
}
|
|
1157
|
+
const refA = getReferenceId(a);
|
|
1158
|
+
const refB = getReferenceId(b);
|
|
1159
|
+
if (refA !== undefined && refB !== undefined) {
|
|
1160
|
+
return isEqual(normalizeFormValue(refA), normalizeFormValue(refB));
|
|
1161
|
+
}
|
|
1162
|
+
return isFormValueEqual(a, b);
|
|
1163
|
+
}
|
|
1164
|
+
/**
|
|
1165
|
+
* Stable sort key for order-insensitive array comparison (after normalization).
|
|
1166
|
+
*/
|
|
1167
|
+
function formValueSortKey(value) {
|
|
1168
|
+
if (value === null) {
|
|
1169
|
+
return '\0';
|
|
1170
|
+
}
|
|
1171
|
+
if (typeof value === 'number') {
|
|
1172
|
+
return `n:${value}`;
|
|
1173
|
+
}
|
|
1174
|
+
if (typeof value === 'boolean') {
|
|
1175
|
+
return `b:${value}`;
|
|
1176
|
+
}
|
|
1177
|
+
if (typeof value === 'string') {
|
|
1178
|
+
return `s:${value}`;
|
|
1179
|
+
}
|
|
1180
|
+
try {
|
|
1181
|
+
return `j:${JSON.stringify(value)}`;
|
|
1182
|
+
}
|
|
1183
|
+
catch {
|
|
1184
|
+
return `u:${String(value)}`;
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
/**
|
|
1188
|
+
* Sorts normalized array items so order does not affect dirty checks.
|
|
1189
|
+
*/
|
|
1190
|
+
function sortNormalizedArrayItems(items) {
|
|
1191
|
+
return [...items].sort((a, b) => formValueSortKey(a).localeCompare(formValueSortKey(b)));
|
|
1192
|
+
}
|
|
1193
|
+
/**
|
|
1194
|
+
* Normalizes a form/context value for semantic baseline comparison.
|
|
1195
|
+
*
|
|
1196
|
+
* Rules:
|
|
1197
|
+
* - `null`, `undefined`, whitespace-only strings, and empty arrays → `null` (empty)
|
|
1198
|
+
* - Numeric strings → numbers (`"5"` and `5` match)
|
|
1199
|
+
* - Arrays → elements normalized; empty/all-empty → `null`; otherwise sorted (order ignored)
|
|
1200
|
+
* - Plain objects → keys sorted; `null`/empty nested values omitted
|
|
1201
|
+
* - `Date` → epoch milliseconds
|
|
1202
|
+
*/
|
|
1203
|
+
function normalizeFormValue(value) {
|
|
1204
|
+
if (value === undefined || value === null) {
|
|
1205
|
+
return null;
|
|
1206
|
+
}
|
|
1207
|
+
if (typeof value === 'string') {
|
|
1208
|
+
const trimmed = value.trim();
|
|
1209
|
+
if (trimmed === '') {
|
|
1210
|
+
return null;
|
|
1211
|
+
}
|
|
1212
|
+
if (/^-?\d+(\.\d+)?$/.test(trimmed)) {
|
|
1213
|
+
const num = Number(trimmed);
|
|
1214
|
+
if (!Number.isNaN(num)) {
|
|
1215
|
+
return num;
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
return trimmed;
|
|
1219
|
+
}
|
|
1220
|
+
if (typeof value === 'number') {
|
|
1221
|
+
return Number.isNaN(value) ? null : value;
|
|
1222
|
+
}
|
|
1223
|
+
if (typeof value === 'boolean') {
|
|
1224
|
+
return value;
|
|
1225
|
+
}
|
|
1226
|
+
if (value instanceof Date) {
|
|
1227
|
+
return value.getTime();
|
|
1228
|
+
}
|
|
1229
|
+
if (isArray(value)) {
|
|
1230
|
+
const normalizedItems = value
|
|
1231
|
+
.map((item) => normalizeFormValue(item))
|
|
1232
|
+
.filter((item) => item !== null);
|
|
1233
|
+
if (normalizedItems.length === 0) {
|
|
1234
|
+
return null;
|
|
1235
|
+
}
|
|
1236
|
+
return sortNormalizedArrayItems(normalizedItems);
|
|
1237
|
+
}
|
|
1238
|
+
if (isPlainObject(value)) {
|
|
1239
|
+
const record = value;
|
|
1240
|
+
if (isLocaleStringMap(record)) {
|
|
1241
|
+
return normalizeLocaleStringMap(record);
|
|
1242
|
+
}
|
|
1243
|
+
const normalized = {};
|
|
1244
|
+
for (const key of Object.keys(record).sort()) {
|
|
1245
|
+
const next = normalizeFormValue(record[key]);
|
|
1246
|
+
if (next !== null) {
|
|
1247
|
+
normalized[key] = next;
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
return normalized;
|
|
1251
|
+
}
|
|
1252
|
+
return value;
|
|
1253
|
+
}
|
|
1254
|
+
/**
|
|
1255
|
+
* Semantic equality for dirty-state and baseline checks.
|
|
1256
|
+
* Prefer over lodash `isEqual` when comparing user-edited form/context data to a saved baseline.
|
|
1257
|
+
*
|
|
1258
|
+
* Arrays: `[]`, `null`, and all-empty lists are equivalent; non-empty arrays compare as multisets (order ignored).
|
|
1259
|
+
*/
|
|
1260
|
+
function isFormValueEqual(a, b) {
|
|
1261
|
+
const multiLanguageEqual = compareMultiLanguageFormValues(a, b);
|
|
1262
|
+
if (multiLanguageEqual !== null) {
|
|
1263
|
+
return multiLanguageEqual;
|
|
1264
|
+
}
|
|
1265
|
+
const filterEqual = compareFilterFormValues(a, b);
|
|
1266
|
+
if (filterEqual !== null) {
|
|
1267
|
+
return filterEqual;
|
|
1268
|
+
}
|
|
1269
|
+
const referenceEqual = compareReferenceFormValues(a, b);
|
|
1270
|
+
if (referenceEqual !== null) {
|
|
1271
|
+
return referenceEqual;
|
|
1272
|
+
}
|
|
1273
|
+
if (isArray(a) && isArray(b)) {
|
|
1274
|
+
// Empty repeater rows normalize away; raw length must differ before multiset compare.
|
|
1275
|
+
if (a.length !== b.length) {
|
|
1276
|
+
return false;
|
|
1277
|
+
}
|
|
1278
|
+
return isEqual(normalizeFormValue(a), normalizeFormValue(b));
|
|
1279
|
+
}
|
|
1280
|
+
if (isPlainObject(a) && isPlainObject(b)) {
|
|
1281
|
+
const left = a;
|
|
1282
|
+
const right = b;
|
|
1283
|
+
const keys = new Set([...Object.keys(left), ...Object.keys(right)]);
|
|
1284
|
+
for (const key of keys) {
|
|
1285
|
+
if (!isFormValueEqual(left[key], right[key])) {
|
|
1286
|
+
return false;
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
return true;
|
|
1290
|
+
}
|
|
1291
|
+
return isEqual(normalizeFormValue(a), normalizeFormValue(b));
|
|
1292
|
+
}
|
|
1293
|
+
/**
|
|
1294
|
+
* Returns whether `current` differs from the saved/clean `baseline` snapshot.
|
|
1295
|
+
*/
|
|
1296
|
+
function isFormContextDirty(current, baseline) {
|
|
1297
|
+
return !isFormValueEqual(current, baseline);
|
|
1298
|
+
}
|
|
1299
|
+
/**
|
|
1300
|
+
* Clones a context object for use as a dirty-tracking baseline.
|
|
1301
|
+
*/
|
|
1302
|
+
function captureFormContextBaseline(context) {
|
|
1303
|
+
return cloneDeep(context);
|
|
1304
|
+
}
|
|
1305
|
+
//#endregion
|
|
791
1306
|
|
|
792
1307
|
class AXPContextChangeEvent {
|
|
793
1308
|
}
|
|
794
1309
|
//#endregion
|
|
795
1310
|
//#region ---- Context signal store ----
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
initialSnapshot: {}, // Snapshot of the first initialized state
|
|
803
|
-
previousSnapshot: {}, // Snapshot of the previous state
|
|
1311
|
+
const AXPContextStore = signalStore(withState(() => ({
|
|
1312
|
+
data: {},
|
|
1313
|
+
state: 'initiated',
|
|
1314
|
+
/** Last committed / saved baseline — discard reverts to this snapshot. */
|
|
1315
|
+
savedSnapshot: {},
|
|
1316
|
+
previousSnapshot: {},
|
|
804
1317
|
lastChange: {
|
|
805
1318
|
state: 'initiated',
|
|
806
|
-
},
|
|
807
|
-
})),
|
|
808
|
-
// Computed Signals
|
|
809
|
-
withComputed(({ data, state, lastChange, initialSnapshot, previousSnapshot }) => ({
|
|
1319
|
+
},
|
|
1320
|
+
})), withComputed(({ data, state, lastChange, savedSnapshot, previousSnapshot }) => ({
|
|
810
1321
|
isChanged: computed(() => state() === 'changed'),
|
|
811
1322
|
isReset: computed(() => state() === 'restored'),
|
|
812
1323
|
isInitiated: computed(() => state() === 'initiated'),
|
|
813
1324
|
isEmpty: computed(() => Object.keys(data()).length === 0),
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
1325
|
+
isSavedCommitted: computed(() => !isEmpty(savedSnapshot())),
|
|
1326
|
+
isDirty: computed(() => {
|
|
1327
|
+
const saved = savedSnapshot();
|
|
1328
|
+
if (isEmpty(saved)) {
|
|
1329
|
+
return false;
|
|
1330
|
+
}
|
|
1331
|
+
return isFormContextDirty(data(), saved);
|
|
1332
|
+
}),
|
|
1333
|
+
snapshot: computed(() => cloneDeep(data())),
|
|
1334
|
+
saved: computed(() => cloneDeep(savedSnapshot())),
|
|
1335
|
+
/** @deprecated Use {@link saved} — only saved baseline is tracked. */
|
|
1336
|
+
initial: computed(() => cloneDeep(savedSnapshot())),
|
|
1337
|
+
previous: computed(() => cloneDeep(previousSnapshot())),
|
|
1338
|
+
changeEvent: computed(() => lastChange()),
|
|
1339
|
+
})), withMethods((store) => {
|
|
1340
|
+
const updateValue = (path, value, options) => {
|
|
824
1341
|
const currentData = cloneDeep(store.data());
|
|
825
1342
|
const oldValue = getSmart(currentData, path);
|
|
826
|
-
|
|
827
|
-
|
|
1343
|
+
const hasSaved = !isEmpty(store.savedSnapshot());
|
|
1344
|
+
const savedAtPath = hasSaved ? getSmart(store.savedSnapshot(), path) : undefined;
|
|
1345
|
+
if (hasSaved && isSelectionValueEqual(value, savedAtPath) && !isEqual(value, savedAtPath)) {
|
|
1346
|
+
value = cloneDeep(savedAtPath);
|
|
1347
|
+
}
|
|
1348
|
+
const isArrayLengthChange = Array.isArray(value) && (!Array.isArray(oldValue) || value.length !== oldValue.length);
|
|
1349
|
+
if (!isArrayLengthChange && isEqual(oldValue, value)) {
|
|
1350
|
+
return;
|
|
1351
|
+
}
|
|
1352
|
+
const shouldNormalizeSavedShape = hasSaved && isSelectionValueEqual(value, savedAtPath) && !isEqual(oldValue, value);
|
|
1353
|
+
if (!isArrayLengthChange && isFormValueEqual(oldValue, value) && !shouldNormalizeSavedShape) {
|
|
828
1354
|
return;
|
|
829
1355
|
}
|
|
830
|
-
|
|
1356
|
+
const origin = options?.origin ?? 'system';
|
|
831
1357
|
const updatedData = setSmart(currentData, path, value);
|
|
832
1358
|
const changeEvent = {
|
|
833
1359
|
oldValue,
|
|
@@ -835,78 +1361,122 @@ withMethods((store) => ({
|
|
|
835
1361
|
path,
|
|
836
1362
|
state: 'changed',
|
|
837
1363
|
data: updatedData,
|
|
1364
|
+
origin,
|
|
838
1365
|
};
|
|
839
|
-
// Patch the state
|
|
840
1366
|
patchState(store, {
|
|
841
|
-
previousSnapshot: store.snapshot(),
|
|
1367
|
+
previousSnapshot: store.snapshot(),
|
|
842
1368
|
data: updatedData,
|
|
843
1369
|
state: 'changed',
|
|
844
1370
|
lastChange: changeEvent,
|
|
845
1371
|
});
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
const
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
lastChange: changeEvent,
|
|
861
|
-
});
|
|
862
|
-
},
|
|
863
|
-
// Reset to the initial state
|
|
864
|
-
reset() {
|
|
865
|
-
const initialData = store.initial();
|
|
1372
|
+
};
|
|
1373
|
+
const applyObjectPaths = (obj, options, prefix = '') => {
|
|
1374
|
+
for (const [key, value] of Object.entries(obj ?? {})) {
|
|
1375
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
1376
|
+
if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
|
|
1377
|
+
applyObjectPaths(value, options, path);
|
|
1378
|
+
}
|
|
1379
|
+
else {
|
|
1380
|
+
updateValue(path, value, options);
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
};
|
|
1384
|
+
const revertToSaved = () => {
|
|
1385
|
+
const savedData = store.saved();
|
|
866
1386
|
const changeEvent = {
|
|
867
|
-
oldValue: cloneDeep(store.data()),
|
|
868
|
-
newValue: cloneDeep(
|
|
1387
|
+
oldValue: cloneDeep(store.data()),
|
|
1388
|
+
newValue: cloneDeep(savedData),
|
|
869
1389
|
path: '',
|
|
870
1390
|
state: 'restored',
|
|
871
|
-
data:
|
|
1391
|
+
data: savedData,
|
|
872
1392
|
};
|
|
873
1393
|
patchState(store, {
|
|
874
|
-
previousSnapshot: store.snapshot(),
|
|
875
|
-
data:
|
|
1394
|
+
previousSnapshot: store.snapshot(),
|
|
1395
|
+
data: savedData,
|
|
876
1396
|
state: 'restored',
|
|
877
1397
|
lastChange: changeEvent,
|
|
878
1398
|
});
|
|
879
|
-
}
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
1399
|
+
};
|
|
1400
|
+
return {
|
|
1401
|
+
update(path, value, options) {
|
|
1402
|
+
updateValue(path, value, options);
|
|
1403
|
+
},
|
|
1404
|
+
applyObjectPaths,
|
|
1405
|
+
patch(context, options) {
|
|
1406
|
+
const currentData = cloneDeep(store.data());
|
|
1407
|
+
const updatedData = { ...currentData, ...context };
|
|
1408
|
+
const changeEvent = {
|
|
1409
|
+
state: 'patch',
|
|
1410
|
+
data: updatedData,
|
|
1411
|
+
};
|
|
1412
|
+
const syncedSnapshot = cloneDeep(updatedData);
|
|
1413
|
+
patchState(store, {
|
|
1414
|
+
...(options?.updateSaved
|
|
1415
|
+
? { savedSnapshot: syncedSnapshot, previousSnapshot: syncedSnapshot }
|
|
1416
|
+
: { previousSnapshot: store.snapshot() }),
|
|
1417
|
+
data: updatedData,
|
|
1418
|
+
state: 'changed',
|
|
1419
|
+
lastChange: changeEvent,
|
|
1420
|
+
});
|
|
1421
|
+
},
|
|
1422
|
+
/** Reverts live data to the last saved snapshot. */
|
|
1423
|
+
revertToSaved,
|
|
1424
|
+
reset() {
|
|
1425
|
+
revertToSaved();
|
|
1426
|
+
},
|
|
1427
|
+
/** Loads live data; saved baseline is committed separately via {@link commitSaved}. */
|
|
1428
|
+
set(initialData) {
|
|
1429
|
+
const currentData = store.data();
|
|
1430
|
+
if (isFormValueEqual(currentData, initialData)) {
|
|
1431
|
+
return;
|
|
1432
|
+
}
|
|
1433
|
+
const changeEvent = {
|
|
1434
|
+
oldValue: null,
|
|
1435
|
+
newValue: cloneDeep(initialData),
|
|
1436
|
+
path: '',
|
|
1437
|
+
state: 'initiated',
|
|
1438
|
+
data: initialData,
|
|
1439
|
+
};
|
|
1440
|
+
patchState(store, {
|
|
1441
|
+
previousSnapshot: store.snapshot(),
|
|
1442
|
+
data: initialData,
|
|
1443
|
+
state: 'initiated',
|
|
1444
|
+
lastChange: changeEvent,
|
|
1445
|
+
});
|
|
1446
|
+
},
|
|
1447
|
+
getValue(path) {
|
|
1448
|
+
return getSmart(store.data(), path);
|
|
1449
|
+
},
|
|
1450
|
+
getSavedValue(path) {
|
|
1451
|
+
return getSmart(store.savedSnapshot(), path);
|
|
1452
|
+
},
|
|
1453
|
+
hasValue(path) {
|
|
1454
|
+
return has(store.data(), path);
|
|
1455
|
+
},
|
|
1456
|
+
/** Marks the current data as the saved baseline. */
|
|
1457
|
+
commitSaved() {
|
|
1458
|
+
const snapshot = cloneDeep(store.data());
|
|
1459
|
+
patchState(store, {
|
|
1460
|
+
savedSnapshot: snapshot,
|
|
1461
|
+
previousSnapshot: snapshot,
|
|
1462
|
+
state: 'initiated',
|
|
1463
|
+
lastChange: {
|
|
1464
|
+
state: 'initiated',
|
|
1465
|
+
data: snapshot,
|
|
1466
|
+
},
|
|
1467
|
+
});
|
|
1468
|
+
},
|
|
1469
|
+
/** Merges parent-bound entity fields without resetting the saved baseline. */
|
|
1470
|
+
applyParentBind(merged) {
|
|
1471
|
+
if (isFormValueEqual(store.data(), merged)) {
|
|
1472
|
+
return;
|
|
1473
|
+
}
|
|
1474
|
+
patchState(store, {
|
|
1475
|
+
data: merged,
|
|
1476
|
+
});
|
|
1477
|
+
},
|
|
1478
|
+
};
|
|
1479
|
+
}));
|
|
910
1480
|
//#endregion
|
|
911
1481
|
|
|
912
1482
|
class AXPExpressionEvaluatorScopeProviderContext {
|
|
@@ -2069,6 +2639,709 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
2069
2639
|
args: [{ providedIn: 'root' }]
|
|
2070
2640
|
}] });
|
|
2071
2641
|
|
|
2642
|
+
//#region ---- Types ----
|
|
2643
|
+
/** Priority tier — higher values win when multiple bindings match. */
|
|
2644
|
+
var AXPKeyboardShortcutPriority;
|
|
2645
|
+
(function (AXPKeyboardShortcutPriority) {
|
|
2646
|
+
AXPKeyboardShortcutPriority[AXPKeyboardShortcutPriority["Global"] = 0] = "Global";
|
|
2647
|
+
AXPKeyboardShortcutPriority[AXPKeyboardShortcutPriority["Page"] = 10] = "Page";
|
|
2648
|
+
AXPKeyboardShortcutPriority[AXPKeyboardShortcutPriority["Panel"] = 20] = "Panel";
|
|
2649
|
+
AXPKeyboardShortcutPriority[AXPKeyboardShortcutPriority["Modal"] = 100] = "Modal";
|
|
2650
|
+
})(AXPKeyboardShortcutPriority || (AXPKeyboardShortcutPriority = {}));
|
|
2651
|
+
//#endregion
|
|
2652
|
+
|
|
2653
|
+
//#region ---- Overlay Layer DOM Utilities ----
|
|
2654
|
+
/** Acorex overlay root (modal shell, popup, dialog). */
|
|
2655
|
+
const AX_OVERLAY_CONTAINER_SELECTOR = '.ax-overlay-container';
|
|
2656
|
+
/** Anchored widget panel inside an overlay (select, date picker, dropdown). */
|
|
2657
|
+
const AX_OVERLAY_PANE_SELECTOR = '.ax-overlay-pane';
|
|
2658
|
+
/**
|
|
2659
|
+
* True when a visible overlay layer is foreground (modal, popup, widget popover/picker).
|
|
2660
|
+
*/
|
|
2661
|
+
function hasForegroundOverlayLayer(document) {
|
|
2662
|
+
if (getVisibleAnchoredOverlayPanes(document).length > 0) {
|
|
2663
|
+
return true;
|
|
2664
|
+
}
|
|
2665
|
+
return getVisibleOverlayContainers(document).length > 0;
|
|
2666
|
+
}
|
|
2667
|
+
/**
|
|
2668
|
+
* Returns visible anchored overlay panes (widget popovers/pickers).
|
|
2669
|
+
*/
|
|
2670
|
+
function getVisibleAnchoredOverlayPanes(document) {
|
|
2671
|
+
return Array.from(document.querySelectorAll(AX_OVERLAY_PANE_SELECTOR)).filter(isVisibleOverlayElement);
|
|
2672
|
+
}
|
|
2673
|
+
/**
|
|
2674
|
+
* Visible anchored panes that are not contained by `excludeContainer` (e.g. dialog shell).
|
|
2675
|
+
*/
|
|
2676
|
+
function getNestedVisibleOverlayPanes(document, excludeContainer) {
|
|
2677
|
+
return getVisibleAnchoredOverlayPanes(document).filter((pane) => !excludeContainer?.contains(pane));
|
|
2678
|
+
}
|
|
2679
|
+
/**
|
|
2680
|
+
* Collects visible overlay containers in stacking order (later / higher z-index wins).
|
|
2681
|
+
*/
|
|
2682
|
+
function getVisibleOverlayContainers(document) {
|
|
2683
|
+
return Array.from(document.querySelectorAll(AX_OVERLAY_CONTAINER_SELECTOR))
|
|
2684
|
+
.filter(isVisibleOverlayElement)
|
|
2685
|
+
.sort(compareOverlayStackOrder);
|
|
2686
|
+
}
|
|
2687
|
+
/** Topmost visible overlay container, if any. */
|
|
2688
|
+
function getTopVisibleOverlayContainer(document) {
|
|
2689
|
+
const containers = getVisibleOverlayContainers(document);
|
|
2690
|
+
return containers.length ? containers[containers.length - 1] : null;
|
|
2691
|
+
}
|
|
2692
|
+
/** Nearest overlay container ancestor of `element`. */
|
|
2693
|
+
function findOverlayContainerAncestor(element) {
|
|
2694
|
+
return element.closest(AX_OVERLAY_CONTAINER_SELECTOR);
|
|
2695
|
+
}
|
|
2696
|
+
/**
|
|
2697
|
+
* Returns true when the overlay element is rendered and visible in the viewport.
|
|
2698
|
+
*/
|
|
2699
|
+
function isVisibleOverlayElement(element) {
|
|
2700
|
+
if (!(element instanceof HTMLElement)) {
|
|
2701
|
+
return false;
|
|
2702
|
+
}
|
|
2703
|
+
const style = getComputedStyle(element);
|
|
2704
|
+
if (style.display === 'none' || style.visibility === 'hidden' || Number(style.opacity) === 0) {
|
|
2705
|
+
return false;
|
|
2706
|
+
}
|
|
2707
|
+
const rect = element.getBoundingClientRect();
|
|
2708
|
+
return rect.width > 0 && rect.height > 0;
|
|
2709
|
+
}
|
|
2710
|
+
function compareOverlayStackOrder(a, b) {
|
|
2711
|
+
const za = Number(getComputedStyle(a).zIndex) || 0;
|
|
2712
|
+
const zb = Number(getComputedStyle(b).zIndex) || 0;
|
|
2713
|
+
if (za !== zb) {
|
|
2714
|
+
return za - zb;
|
|
2715
|
+
}
|
|
2716
|
+
const position = a.compareDocumentPosition(b);
|
|
2717
|
+
if (position & Node.DOCUMENT_POSITION_FOLLOWING) {
|
|
2718
|
+
return -1;
|
|
2719
|
+
}
|
|
2720
|
+
if (position & Node.DOCUMENT_POSITION_PRECEDING) {
|
|
2721
|
+
return 1;
|
|
2722
|
+
}
|
|
2723
|
+
return 0;
|
|
2724
|
+
}
|
|
2725
|
+
//#endregion
|
|
2726
|
+
|
|
2727
|
+
//#region ---- Imports ----
|
|
2728
|
+
const HORIZONTAL_DIRECTIONAL_KEY_MIRRORS = {
|
|
2729
|
+
arrowleft: 'arrowright',
|
|
2730
|
+
arrowright: 'arrowleft',
|
|
2731
|
+
left: 'right',
|
|
2732
|
+
right: 'left',
|
|
2733
|
+
'[': ']',
|
|
2734
|
+
']': '[',
|
|
2735
|
+
};
|
|
2736
|
+
/** Maps a horizontal direction key to its RTL semantic counterpart. */
|
|
2737
|
+
function mirrorHorizontalDirectionalKey(key) {
|
|
2738
|
+
const normalized = normalizeShortcutToken(key);
|
|
2739
|
+
return HORIZONTAL_DIRECTIONAL_KEY_MIRRORS[normalized] ?? normalized;
|
|
2740
|
+
}
|
|
2741
|
+
/** True when the chord's primary key is a horizontal direction key (`←`/`→`, `[`/`]`). */
|
|
2742
|
+
function isHorizontalDirectionalShortcutKey(key) {
|
|
2743
|
+
return normalizeShortcutToken(key) in HORIZONTAL_DIRECTIONAL_KEY_MIRRORS;
|
|
2744
|
+
}
|
|
2745
|
+
/**
|
|
2746
|
+
* Resolves direction behavior for a chord.
|
|
2747
|
+
* Horizontal direction keys default to `semantic`; others default to `physical`.
|
|
2748
|
+
* An explicit binding value always wins.
|
|
2749
|
+
*/
|
|
2750
|
+
function resolveEffectiveDirectionBehavior(chord, explicit) {
|
|
2751
|
+
if (explicit === 'physical' || explicit === 'semantic') {
|
|
2752
|
+
return explicit;
|
|
2753
|
+
}
|
|
2754
|
+
const { key } = parseKeyboardShortcutChord(chord);
|
|
2755
|
+
return isHorizontalDirectionalShortcutKey(key) ? 'semantic' : 'physical';
|
|
2756
|
+
}
|
|
2757
|
+
/** Resolves the key the user presses for a semantically registered chord in RTL. */
|
|
2758
|
+
function resolveSemanticDirectionalKey(storedKey, rtl) {
|
|
2759
|
+
const normalized = normalizeShortcutToken(storedKey);
|
|
2760
|
+
if (!rtl) {
|
|
2761
|
+
return normalized;
|
|
2762
|
+
}
|
|
2763
|
+
return mirrorHorizontalDirectionalKey(normalized);
|
|
2764
|
+
}
|
|
2765
|
+
/** Chord string with directional key mirrored for RTL display/matching when semantic. */
|
|
2766
|
+
function resolveDisplayShortcutChord(chord, isRtl, explicitDirectionBehavior) {
|
|
2767
|
+
const directionBehavior = resolveEffectiveDirectionBehavior(chord, explicitDirectionBehavior);
|
|
2768
|
+
const parts = chord
|
|
2769
|
+
.trim()
|
|
2770
|
+
.split('+')
|
|
2771
|
+
.map((part) => part.trim())
|
|
2772
|
+
.filter(Boolean);
|
|
2773
|
+
if (parts.length === 0) {
|
|
2774
|
+
return chord;
|
|
2775
|
+
}
|
|
2776
|
+
const keyPart = parts.pop() ?? '';
|
|
2777
|
+
const rtl = directionBehavior === 'semantic' && isRtl;
|
|
2778
|
+
const resolvedKey = resolveSemanticDirectionalKey(keyPart, rtl);
|
|
2779
|
+
return [...parts, resolvedKey].join('+');
|
|
2780
|
+
}
|
|
2781
|
+
const MODIFIER_ALIASES = {
|
|
2782
|
+
ctrl: 'ctrl',
|
|
2783
|
+
control: 'ctrl',
|
|
2784
|
+
shift: 'shift',
|
|
2785
|
+
alt: 'alt',
|
|
2786
|
+
option: 'alt',
|
|
2787
|
+
meta: 'meta',
|
|
2788
|
+
cmd: 'meta',
|
|
2789
|
+
command: 'meta',
|
|
2790
|
+
win: 'meta',
|
|
2791
|
+
};
|
|
2792
|
+
const KEY_DISPLAY_MAC = {
|
|
2793
|
+
ctrl: '⌃',
|
|
2794
|
+
control: '⌃',
|
|
2795
|
+
shift: '⇧',
|
|
2796
|
+
alt: '⌥',
|
|
2797
|
+
option: '⌥',
|
|
2798
|
+
meta: '⌘',
|
|
2799
|
+
cmd: '⌘',
|
|
2800
|
+
command: '⌘',
|
|
2801
|
+
win: '⌘',
|
|
2802
|
+
enter: '↵',
|
|
2803
|
+
escape: 'Esc',
|
|
2804
|
+
esc: 'Esc',
|
|
2805
|
+
home: 'Home',
|
|
2806
|
+
arrowleft: '←',
|
|
2807
|
+
arrowright: '→',
|
|
2808
|
+
arrowup: '↑',
|
|
2809
|
+
arrowdown: '↓',
|
|
2810
|
+
pageup: 'PgUp',
|
|
2811
|
+
pagedown: 'PgDn',
|
|
2812
|
+
backspace: '⌫',
|
|
2813
|
+
delete: '⌦',
|
|
2814
|
+
space: 'Space',
|
|
2815
|
+
};
|
|
2816
|
+
/**
|
|
2817
|
+
* Parses a shortcut chord such as `Enter`, `Escape`, or `ctrl+shift+s`.
|
|
2818
|
+
*/
|
|
2819
|
+
function parseKeyboardShortcutChord(chord) {
|
|
2820
|
+
const parts = chord
|
|
2821
|
+
.trim()
|
|
2822
|
+
.toLowerCase()
|
|
2823
|
+
.split('+')
|
|
2824
|
+
.map((part) => part.trim())
|
|
2825
|
+
.filter(Boolean);
|
|
2826
|
+
const key = normalizeShortcutToken(parts.pop() ?? '');
|
|
2827
|
+
const parsed = {
|
|
2828
|
+
ctrl: false,
|
|
2829
|
+
shift: false,
|
|
2830
|
+
alt: false,
|
|
2831
|
+
meta: false,
|
|
2832
|
+
key,
|
|
2833
|
+
};
|
|
2834
|
+
for (const part of parts) {
|
|
2835
|
+
const alias = MODIFIER_ALIASES[part];
|
|
2836
|
+
if (alias) {
|
|
2837
|
+
parsed[alias] = true;
|
|
2838
|
+
}
|
|
2839
|
+
}
|
|
2840
|
+
return parsed;
|
|
2841
|
+
}
|
|
2842
|
+
/**
|
|
2843
|
+
* Returns true when the keyboard event matches the given chord.
|
|
2844
|
+
* On macOS, `ctrl+*` chords also match `meta+*` for common platform shortcuts.
|
|
2845
|
+
*/
|
|
2846
|
+
function matchesKeyboardShortcutChord(event, chord, options) {
|
|
2847
|
+
const parsed = parseKeyboardShortcutChord(chord);
|
|
2848
|
+
const mac = isMacPlatform();
|
|
2849
|
+
const directionBehavior = resolveEffectiveDirectionBehavior(chord, options?.directionBehavior);
|
|
2850
|
+
if (!matchesShortcutModifiers(parsed, event, mac)) {
|
|
2851
|
+
return false;
|
|
2852
|
+
}
|
|
2853
|
+
const actualKey = normalizeShortcutEventKey(event);
|
|
2854
|
+
const expectedKey = normalizeShortcutToken(parsed.key);
|
|
2855
|
+
const rtlSemantic = directionBehavior === 'semantic' &&
|
|
2856
|
+
options?.isRtl === true &&
|
|
2857
|
+
isHorizontalDirectionalShortcutKey(expectedKey);
|
|
2858
|
+
if (rtlSemantic) {
|
|
2859
|
+
return actualKey === mirrorHorizontalDirectionalKey(expectedKey);
|
|
2860
|
+
}
|
|
2861
|
+
return actualKey === expectedKey;
|
|
2862
|
+
}
|
|
2863
|
+
/**
|
|
2864
|
+
* Matches modifier keys for a parsed chord.
|
|
2865
|
+
* `ctrl+alt` chords also accept AltGraph (Windows) so layout-produced characters still match physical keys.
|
|
2866
|
+
*/
|
|
2867
|
+
function matchesShortcutModifiers(parsed, event, mac) {
|
|
2868
|
+
if (parsed.shift !== event.shiftKey) {
|
|
2869
|
+
return false;
|
|
2870
|
+
}
|
|
2871
|
+
const altGraph = hasAltGraphModifier(event);
|
|
2872
|
+
if (parsed.ctrl && parsed.alt) {
|
|
2873
|
+
if (altGraph) {
|
|
2874
|
+
return true;
|
|
2875
|
+
}
|
|
2876
|
+
return event.ctrlKey && event.altKey;
|
|
2877
|
+
}
|
|
2878
|
+
if (parsed.alt !== event.altKey) {
|
|
2879
|
+
return false;
|
|
2880
|
+
}
|
|
2881
|
+
if (parsed.meta) {
|
|
2882
|
+
if (!event.metaKey) {
|
|
2883
|
+
return false;
|
|
2884
|
+
}
|
|
2885
|
+
return true;
|
|
2886
|
+
}
|
|
2887
|
+
if (parsed.ctrl) {
|
|
2888
|
+
if (mac) {
|
|
2889
|
+
return event.metaKey || event.ctrlKey;
|
|
2890
|
+
}
|
|
2891
|
+
return event.ctrlKey;
|
|
2892
|
+
}
|
|
2893
|
+
if (event.ctrlKey || event.metaKey) {
|
|
2894
|
+
return false;
|
|
2895
|
+
}
|
|
2896
|
+
return true;
|
|
2897
|
+
}
|
|
2898
|
+
function hasAltGraphModifier(event) {
|
|
2899
|
+
return typeof event.getModifierState === 'function' && event.getModifierState('AltGraph');
|
|
2900
|
+
}
|
|
2901
|
+
/**
|
|
2902
|
+
* Formats one chord for UI display (OS-aware modifier symbols).
|
|
2903
|
+
*/
|
|
2904
|
+
function formatKeyboardShortcutChord(chord, options) {
|
|
2905
|
+
const directionBehavior = resolveEffectiveDirectionBehavior(chord, options?.directionBehavior);
|
|
2906
|
+
const resolvedChord = options?.skipDirectionResolve
|
|
2907
|
+
? chord
|
|
2908
|
+
: resolveDisplayShortcutChord(chord, options?.isRtl === true, directionBehavior);
|
|
2909
|
+
const mac = isMacPlatform();
|
|
2910
|
+
const parts = resolvedChord
|
|
2911
|
+
.trim()
|
|
2912
|
+
.split('+')
|
|
2913
|
+
.map((part) => part.trim())
|
|
2914
|
+
.filter(Boolean);
|
|
2915
|
+
if (parts.length === 0) {
|
|
2916
|
+
return '';
|
|
2917
|
+
}
|
|
2918
|
+
const keyPart = normalizeShortcutToken(parts.pop() ?? '');
|
|
2919
|
+
const modifierParts = parts.map((part) => formatShortcutPart(part.toLowerCase(), mac));
|
|
2920
|
+
const keyDisplay = formatShortcutPart(keyPart, mac);
|
|
2921
|
+
if (mac) {
|
|
2922
|
+
return [...modifierParts, keyDisplay].join('');
|
|
2923
|
+
}
|
|
2924
|
+
return [...modifierParts, keyDisplay].join('+');
|
|
2925
|
+
}
|
|
2926
|
+
/**
|
|
2927
|
+
* Formats multiple chords as a single display string (e.g. `← / PgUp`).
|
|
2928
|
+
*/
|
|
2929
|
+
function formatKeyboardShortcutChords(chords, options) {
|
|
2930
|
+
return chords.map((chord) => formatKeyboardShortcutChord(chord, options)).join(' / ');
|
|
2931
|
+
}
|
|
2932
|
+
/** Maps a chord to `ax-kbd-item` key parts (modifiers + display symbols). */
|
|
2933
|
+
function chordToKbdItemKeys(chord, options) {
|
|
2934
|
+
const directionBehavior = resolveEffectiveDirectionBehavior(chord, options?.directionBehavior);
|
|
2935
|
+
const resolvedChord = options?.skipDirectionResolve
|
|
2936
|
+
? chord
|
|
2937
|
+
: resolveDisplayShortcutChord(chord, options?.isRtl === true, directionBehavior);
|
|
2938
|
+
const mac = isMacPlatform();
|
|
2939
|
+
const symbolKeys = {
|
|
2940
|
+
arrowleft: '←',
|
|
2941
|
+
arrowright: '→',
|
|
2942
|
+
arrowup: '↑',
|
|
2943
|
+
arrowdown: '↓',
|
|
2944
|
+
pageup: 'PgUp',
|
|
2945
|
+
pagedown: 'PgDn',
|
|
2946
|
+
enter: '↵',
|
|
2947
|
+
escape: 'Esc',
|
|
2948
|
+
esc: 'Esc',
|
|
2949
|
+
space: 'Space',
|
|
2950
|
+
'[': '[',
|
|
2951
|
+
']': ']',
|
|
2952
|
+
};
|
|
2953
|
+
return resolvedChord
|
|
2954
|
+
.trim()
|
|
2955
|
+
.split('+')
|
|
2956
|
+
.map((part) => part.trim())
|
|
2957
|
+
.filter(Boolean)
|
|
2958
|
+
.map((part) => {
|
|
2959
|
+
const normalized = part.toLowerCase();
|
|
2960
|
+
if (mac && (normalized === 'ctrl' || normalized === 'control')) {
|
|
2961
|
+
return '⌘';
|
|
2962
|
+
}
|
|
2963
|
+
return symbolKeys[normalized] ?? part;
|
|
2964
|
+
});
|
|
2965
|
+
}
|
|
2966
|
+
/**
|
|
2967
|
+
* When focus is in a native edit control, avoid stealing plain key shortcuts.
|
|
2968
|
+
*/
|
|
2969
|
+
function isKeyboardTargetInsideEditableField(target) {
|
|
2970
|
+
if (!(target instanceof HTMLElement)) {
|
|
2971
|
+
return false;
|
|
2972
|
+
}
|
|
2973
|
+
if (target.isContentEditable) {
|
|
2974
|
+
return true;
|
|
2975
|
+
}
|
|
2976
|
+
const field = target.closest('input, textarea, select');
|
|
2977
|
+
if (!field || field.disabled) {
|
|
2978
|
+
return false;
|
|
2979
|
+
}
|
|
2980
|
+
if (field instanceof HTMLInputElement) {
|
|
2981
|
+
const type = field.type;
|
|
2982
|
+
if (type === 'hidden' ||
|
|
2983
|
+
type === 'checkbox' ||
|
|
2984
|
+
type === 'radio' ||
|
|
2985
|
+
type === 'button' ||
|
|
2986
|
+
type === 'submit' ||
|
|
2987
|
+
type === 'reset' ||
|
|
2988
|
+
type === 'file' ||
|
|
2989
|
+
field.readOnly) {
|
|
2990
|
+
return false;
|
|
2991
|
+
}
|
|
2992
|
+
return true;
|
|
2993
|
+
}
|
|
2994
|
+
if (field instanceof HTMLTextAreaElement) {
|
|
2995
|
+
return !field.readOnly;
|
|
2996
|
+
}
|
|
2997
|
+
return true;
|
|
2998
|
+
}
|
|
2999
|
+
/**
|
|
3000
|
+
* Returns true when Esc should close an open widget overlay first (select, date picker, popover, modal).
|
|
3001
|
+
* Used by the global shortcut registry so page-level Esc bindings do not swallow overlay dismiss.
|
|
3002
|
+
*/
|
|
3003
|
+
function shouldDeferEscapeToOpenOverlay(document) {
|
|
3004
|
+
return hasForegroundOverlayLayer(document);
|
|
3005
|
+
}
|
|
3006
|
+
function isMacPlatform() {
|
|
3007
|
+
if (typeof navigator === 'undefined') {
|
|
3008
|
+
return false;
|
|
3009
|
+
}
|
|
3010
|
+
return /Mac|iPhone|iPad|iPod/i.test(navigator.userAgent);
|
|
3011
|
+
}
|
|
3012
|
+
function normalizeShortcutToken(key) {
|
|
3013
|
+
const normalized = key.trim().toLowerCase();
|
|
3014
|
+
if (normalized === 'esc') {
|
|
3015
|
+
return 'escape';
|
|
3016
|
+
}
|
|
3017
|
+
if (normalized === 'left') {
|
|
3018
|
+
return 'arrowleft';
|
|
3019
|
+
}
|
|
3020
|
+
if (normalized === 'right') {
|
|
3021
|
+
return 'arrowright';
|
|
3022
|
+
}
|
|
3023
|
+
if (normalized === 'up') {
|
|
3024
|
+
return 'arrowup';
|
|
3025
|
+
}
|
|
3026
|
+
if (normalized === 'down') {
|
|
3027
|
+
return 'arrowdown';
|
|
3028
|
+
}
|
|
3029
|
+
if (normalized === '[' || normalized === 'bracketleft') {
|
|
3030
|
+
return '[';
|
|
3031
|
+
}
|
|
3032
|
+
if (normalized === ']' || normalized === 'bracketright') {
|
|
3033
|
+
return ']';
|
|
3034
|
+
}
|
|
3035
|
+
return normalized;
|
|
3036
|
+
}
|
|
3037
|
+
function normalizeShortcutEventKey(event) {
|
|
3038
|
+
if (event.key === 'Enter') {
|
|
3039
|
+
return 'enter';
|
|
3040
|
+
}
|
|
3041
|
+
if (event.key === 'Escape') {
|
|
3042
|
+
return 'escape';
|
|
3043
|
+
}
|
|
3044
|
+
if (event.key === 'Home') {
|
|
3045
|
+
return 'home';
|
|
3046
|
+
}
|
|
3047
|
+
if (event.key === ' ') {
|
|
3048
|
+
return 'space';
|
|
3049
|
+
}
|
|
3050
|
+
if (event.key === '[' || event.code === 'BracketLeft') {
|
|
3051
|
+
return '[';
|
|
3052
|
+
}
|
|
3053
|
+
if (event.key === ']' || event.code === 'BracketRight') {
|
|
3054
|
+
return ']';
|
|
3055
|
+
}
|
|
3056
|
+
const codeMatch = event.code.match(/^Key([A-Z])$/);
|
|
3057
|
+
if (codeMatch) {
|
|
3058
|
+
return codeMatch[1].toLowerCase();
|
|
3059
|
+
}
|
|
3060
|
+
const digitMatch = event.code.match(/^Digit([0-9])$/);
|
|
3061
|
+
if (digitMatch) {
|
|
3062
|
+
return digitMatch[1];
|
|
3063
|
+
}
|
|
3064
|
+
if (event.key.length === 1) {
|
|
3065
|
+
return event.key.toLowerCase();
|
|
3066
|
+
}
|
|
3067
|
+
const fnMatch = event.code.match(/^F(\d+)$/);
|
|
3068
|
+
if (fnMatch) {
|
|
3069
|
+
return `f${fnMatch[1]}`;
|
|
3070
|
+
}
|
|
3071
|
+
return event.key.toLowerCase();
|
|
3072
|
+
}
|
|
3073
|
+
function formatShortcutPart(part, mac) {
|
|
3074
|
+
const normalized = normalizeShortcutToken(part);
|
|
3075
|
+
if (mac && KEY_DISPLAY_MAC[normalized]) {
|
|
3076
|
+
return KEY_DISPLAY_MAC[normalized];
|
|
3077
|
+
}
|
|
3078
|
+
if (!mac && MODIFIER_ALIASES[normalized]) {
|
|
3079
|
+
return normalized.charAt(0).toUpperCase() + normalized.slice(1);
|
|
3080
|
+
}
|
|
3081
|
+
if (KEY_DISPLAY_MAC[normalized]) {
|
|
3082
|
+
return KEY_DISPLAY_MAC[normalized];
|
|
3083
|
+
}
|
|
3084
|
+
if (normalized.length === 1) {
|
|
3085
|
+
return normalized.toUpperCase();
|
|
3086
|
+
}
|
|
3087
|
+
return normalized.charAt(0).toUpperCase() + normalized.slice(1);
|
|
3088
|
+
}
|
|
3089
|
+
//#endregion
|
|
3090
|
+
|
|
3091
|
+
//#region ---- Keyboard Shortcut Declaration Utilities ----
|
|
3092
|
+
/**
|
|
3093
|
+
* Normalizes a declarative shortcut (`'ctrl+s'` or `{ keys: 'home', allowInEditableFields: false }`).
|
|
3094
|
+
*/
|
|
3095
|
+
function normalizeKeyboardShortcut(shortcut) {
|
|
3096
|
+
if (typeof shortcut === 'string') {
|
|
3097
|
+
const chord = shortcut.trim();
|
|
3098
|
+
if (!chord) {
|
|
3099
|
+
return undefined;
|
|
3100
|
+
}
|
|
3101
|
+
return { keys: [chord] };
|
|
3102
|
+
}
|
|
3103
|
+
return normalizeKeyboardShortcutConfig(shortcut);
|
|
3104
|
+
}
|
|
3105
|
+
/**
|
|
3106
|
+
* Normalizes an array of declarative shortcuts. Empty or invalid entries are skipped.
|
|
3107
|
+
*/
|
|
3108
|
+
function normalizeKeyboardShortcuts(shortcuts) {
|
|
3109
|
+
if (!shortcuts?.length) {
|
|
3110
|
+
return [];
|
|
3111
|
+
}
|
|
3112
|
+
const result = [];
|
|
3113
|
+
for (const shortcut of shortcuts) {
|
|
3114
|
+
const normalized = normalizeKeyboardShortcut(shortcut);
|
|
3115
|
+
if (normalized) {
|
|
3116
|
+
result.push(normalized);
|
|
3117
|
+
}
|
|
3118
|
+
}
|
|
3119
|
+
return result;
|
|
3120
|
+
}
|
|
3121
|
+
/** Returns the first chord from declarative shortcuts (for UI hints such as `ax-kbd`). */
|
|
3122
|
+
function getPrimaryKeyboardShortcutChord(shortcuts) {
|
|
3123
|
+
return normalizeKeyboardShortcuts(shortcuts)[0]?.keys[0];
|
|
3124
|
+
}
|
|
3125
|
+
function normalizeKeyboardShortcutConfig(shortcut) {
|
|
3126
|
+
const keys = Array.isArray(shortcut.keys) ? shortcut.keys : [shortcut.keys];
|
|
3127
|
+
const normalizedKeys = keys.map((chord) => chord.trim()).filter(Boolean);
|
|
3128
|
+
if (normalizedKeys.length === 0) {
|
|
3129
|
+
return undefined;
|
|
3130
|
+
}
|
|
3131
|
+
return {
|
|
3132
|
+
keys: normalizedKeys,
|
|
3133
|
+
allowInEditableFields: shortcut.allowInEditableFields,
|
|
3134
|
+
when: shortcut.when,
|
|
3135
|
+
directionBehavior: shortcut.directionBehavior,
|
|
3136
|
+
};
|
|
3137
|
+
}
|
|
3138
|
+
//#endregion
|
|
3139
|
+
|
|
3140
|
+
//#region ---- Imports ----
|
|
3141
|
+
//#endregion
|
|
3142
|
+
class AXPKeyboardShortcutRegistry {
|
|
3143
|
+
constructor() {
|
|
3144
|
+
//#region ---- Services & Dependencies ----
|
|
3145
|
+
this.document = inject(DOCUMENT);
|
|
3146
|
+
this.localeService = inject(AXLocaleService);
|
|
3147
|
+
this.activeProfile = toSignal(this.localeService.profileChanged$, {
|
|
3148
|
+
initialValue: this.localeService.activeProfile(),
|
|
3149
|
+
});
|
|
3150
|
+
this.registrations = signal([], ...(ngDevMode ? [{ debugName: "registrations" }] : /* istanbul ignore next */ []));
|
|
3151
|
+
this.helpDialogHandler = null;
|
|
3152
|
+
this.listenerAttached = false;
|
|
3153
|
+
//#endregion
|
|
3154
|
+
//#region ---- Computed Properties ----
|
|
3155
|
+
/** Active shortcut entries for the help dialog. */
|
|
3156
|
+
this.helpEntries = computed(() => {
|
|
3157
|
+
this.activeProfile();
|
|
3158
|
+
return this.buildHelpEntries();
|
|
3159
|
+
}, ...(ngDevMode ? [{ debugName: "helpEntries" }] : /* istanbul ignore next */ []));
|
|
3160
|
+
this.onDocumentKeyDown = (event) => {
|
|
3161
|
+
if (event.key === 'Escape' && shouldDeferEscapeToOpenOverlay(this.document)) {
|
|
3162
|
+
return;
|
|
3163
|
+
}
|
|
3164
|
+
if (event.key === 'F1') {
|
|
3165
|
+
event.preventDefault();
|
|
3166
|
+
event.stopImmediatePropagation();
|
|
3167
|
+
if (hasForegroundOverlayLayer(this.document)) {
|
|
3168
|
+
return;
|
|
3169
|
+
}
|
|
3170
|
+
}
|
|
3171
|
+
const candidates = this.resolveActiveCandidates(event);
|
|
3172
|
+
if (candidates.length === 0) {
|
|
3173
|
+
return;
|
|
3174
|
+
}
|
|
3175
|
+
const match = candidates[0];
|
|
3176
|
+
event.preventDefault();
|
|
3177
|
+
event.stopImmediatePropagation();
|
|
3178
|
+
void match.binding.handler(event);
|
|
3179
|
+
};
|
|
3180
|
+
}
|
|
3181
|
+
//#endregion
|
|
3182
|
+
//#region ---- Public Methods ----
|
|
3183
|
+
/**
|
|
3184
|
+
* Registers a scope of shortcuts. Unregisters automatically when `owner` is destroyed.
|
|
3185
|
+
*/
|
|
3186
|
+
register(options) {
|
|
3187
|
+
const { owner, ...registrationOptions } = options;
|
|
3188
|
+
const registrationId = `${options.id}-${this.createRegistrationId()}`;
|
|
3189
|
+
const entry = {
|
|
3190
|
+
...registrationOptions,
|
|
3191
|
+
registrationId,
|
|
3192
|
+
priority: options.priority ?? 0,
|
|
3193
|
+
};
|
|
3194
|
+
this.registrations.update((items) => [
|
|
3195
|
+
...items.filter((item) => item.id !== options.id),
|
|
3196
|
+
entry,
|
|
3197
|
+
]);
|
|
3198
|
+
this.ensureListener();
|
|
3199
|
+
owner.onDestroy(() => {
|
|
3200
|
+
this.unregister(registrationId);
|
|
3201
|
+
});
|
|
3202
|
+
}
|
|
3203
|
+
/**
|
|
3204
|
+
* Wires the F1 help dialog opener (provided by platform/common).
|
|
3205
|
+
*/
|
|
3206
|
+
setHelpDialogHandler(handler) {
|
|
3207
|
+
this.helpDialogHandler = handler;
|
|
3208
|
+
this.ensureHelpShortcutRegistered();
|
|
3209
|
+
}
|
|
3210
|
+
/** OS-aware display string for UI hints. */
|
|
3211
|
+
formatDisplayKeys(chords) {
|
|
3212
|
+
return formatKeyboardShortcutChords(chords, { isRtl: this.isRtlProfile() });
|
|
3213
|
+
}
|
|
3214
|
+
//#endregion
|
|
3215
|
+
//#region ---- Private Methods ----
|
|
3216
|
+
ensureHelpShortcutRegistered() {
|
|
3217
|
+
const alreadyRegistered = this.registrations().some((entry) => entry.id === 'system:keyboard-shortcuts-help');
|
|
3218
|
+
if (alreadyRegistered || !this.helpDialogHandler) {
|
|
3219
|
+
return;
|
|
3220
|
+
}
|
|
3221
|
+
const handler = this.helpDialogHandler;
|
|
3222
|
+
const entry = {
|
|
3223
|
+
registrationId: 'system:keyboard-shortcuts-help',
|
|
3224
|
+
id: 'system:keyboard-shortcuts-help',
|
|
3225
|
+
priority: -1,
|
|
3226
|
+
scope: '@general:keyboard-shortcuts.groups.system',
|
|
3227
|
+
shortcuts: [
|
|
3228
|
+
{
|
|
3229
|
+
keys: ['f1'],
|
|
3230
|
+
title: '@general:keyboard-shortcuts.help-action',
|
|
3231
|
+
allowInEditableFields: true,
|
|
3232
|
+
handler: () => {
|
|
3233
|
+
void handler();
|
|
3234
|
+
},
|
|
3235
|
+
},
|
|
3236
|
+
],
|
|
3237
|
+
};
|
|
3238
|
+
this.registrations.update((items) => [...items, entry]);
|
|
3239
|
+
this.ensureListener();
|
|
3240
|
+
}
|
|
3241
|
+
createRegistrationId() {
|
|
3242
|
+
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
|
|
3243
|
+
return crypto.randomUUID();
|
|
3244
|
+
}
|
|
3245
|
+
return `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
3246
|
+
}
|
|
3247
|
+
unregister(registrationId) {
|
|
3248
|
+
this.registrations.update((items) => items.filter((item) => item.registrationId !== registrationId));
|
|
3249
|
+
}
|
|
3250
|
+
ensureListener() {
|
|
3251
|
+
if (this.listenerAttached || typeof this.document === 'undefined') {
|
|
3252
|
+
return;
|
|
3253
|
+
}
|
|
3254
|
+
this.document.addEventListener('keydown', this.onDocumentKeyDown, true);
|
|
3255
|
+
this.listenerAttached = true;
|
|
3256
|
+
}
|
|
3257
|
+
resolveActiveCandidates(event) {
|
|
3258
|
+
const editable = isKeyboardTargetInsideEditableField(event.target);
|
|
3259
|
+
const candidates = [];
|
|
3260
|
+
for (const registration of this.registrations()) {
|
|
3261
|
+
if (!this.isRegistrationActive(registration)) {
|
|
3262
|
+
continue;
|
|
3263
|
+
}
|
|
3264
|
+
const priority = registration.priority ?? 0;
|
|
3265
|
+
for (const binding of registration.shortcuts) {
|
|
3266
|
+
if (binding.when && !binding.when()) {
|
|
3267
|
+
continue;
|
|
3268
|
+
}
|
|
3269
|
+
if (editable && !binding.allowInEditableFields) {
|
|
3270
|
+
continue;
|
|
3271
|
+
}
|
|
3272
|
+
for (const chord of binding.keys) {
|
|
3273
|
+
const directionBehavior = resolveEffectiveDirectionBehavior(chord, binding.directionBehavior);
|
|
3274
|
+
if (!matchesKeyboardShortcutChord(event, chord, {
|
|
3275
|
+
directionBehavior,
|
|
3276
|
+
isRtl: this.isRtlProfile(),
|
|
3277
|
+
})) {
|
|
3278
|
+
continue;
|
|
3279
|
+
}
|
|
3280
|
+
candidates.push({
|
|
3281
|
+
priority,
|
|
3282
|
+
registration,
|
|
3283
|
+
binding,
|
|
3284
|
+
chord,
|
|
3285
|
+
});
|
|
3286
|
+
break;
|
|
3287
|
+
}
|
|
3288
|
+
}
|
|
3289
|
+
}
|
|
3290
|
+
return candidates.sort((a, b) => b.priority - a.priority);
|
|
3291
|
+
}
|
|
3292
|
+
isRegistrationActive(registration) {
|
|
3293
|
+
if (registration.when && !registration.when()) {
|
|
3294
|
+
return false;
|
|
3295
|
+
}
|
|
3296
|
+
const element = registration.elementRef?.nativeElement;
|
|
3297
|
+
if (element && !this.document.contains(element)) {
|
|
3298
|
+
return false;
|
|
3299
|
+
}
|
|
3300
|
+
return true;
|
|
3301
|
+
}
|
|
3302
|
+
isRtlProfile() {
|
|
3303
|
+
return this.activeProfile()?.i18nMeta?.rtl === true;
|
|
3304
|
+
}
|
|
3305
|
+
buildHelpEntries() {
|
|
3306
|
+
const entries = [];
|
|
3307
|
+
const isRtl = this.isRtlProfile();
|
|
3308
|
+
for (const registration of this.registrations()) {
|
|
3309
|
+
if (!this.isRegistrationActive(registration)) {
|
|
3310
|
+
continue;
|
|
3311
|
+
}
|
|
3312
|
+
if (registration.id === 'system:keyboard-shortcuts-help') {
|
|
3313
|
+
continue;
|
|
3314
|
+
}
|
|
3315
|
+
const priority = registration.priority ?? 0;
|
|
3316
|
+
for (const binding of registration.shortcuts) {
|
|
3317
|
+
const displayChords = binding.keys.map((chord) => {
|
|
3318
|
+
const directionBehavior = resolveEffectiveDirectionBehavior(chord, binding.directionBehavior);
|
|
3319
|
+
return resolveDisplayShortcutChord(chord, isRtl, directionBehavior);
|
|
3320
|
+
});
|
|
3321
|
+
entries.push({
|
|
3322
|
+
registrationId: registration.registrationId,
|
|
3323
|
+
bindingTitle: binding.title,
|
|
3324
|
+
chords: [...binding.keys],
|
|
3325
|
+
displayChords,
|
|
3326
|
+
displayKeys: formatKeyboardShortcutChords(displayChords, { skipDirectionResolve: true }),
|
|
3327
|
+
scope: registration.scope,
|
|
3328
|
+
group: binding.group ?? registration.group,
|
|
3329
|
+
priority,
|
|
3330
|
+
});
|
|
3331
|
+
}
|
|
3332
|
+
}
|
|
3333
|
+
return entries.sort((a, b) => b.priority - a.priority || a.bindingTitle.localeCompare(b.bindingTitle));
|
|
3334
|
+
}
|
|
3335
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPKeyboardShortcutRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
3336
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPKeyboardShortcutRegistry, providedIn: 'root' }); }
|
|
3337
|
+
}
|
|
3338
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPKeyboardShortcutRegistry, decorators: [{
|
|
3339
|
+
type: Injectable,
|
|
3340
|
+
args: [{
|
|
3341
|
+
providedIn: 'root',
|
|
3342
|
+
}]
|
|
3343
|
+
}] });
|
|
3344
|
+
|
|
2072
3345
|
//#region ---- Imports ----
|
|
2073
3346
|
//#endregion
|
|
2074
3347
|
|
|
@@ -2428,6 +3701,8 @@ class AXPAppStartUpService {
|
|
|
2428
3701
|
this.translationService = inject(AXTranslationService);
|
|
2429
3702
|
this.tasks = [];
|
|
2430
3703
|
}
|
|
3704
|
+
//#endregion
|
|
3705
|
+
//#region ---- Public Methods ----
|
|
2431
3706
|
registerTask(task) {
|
|
2432
3707
|
this.tasks.push(task);
|
|
2433
3708
|
}
|
|
@@ -3734,16 +5009,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
3734
5009
|
|
|
3735
5010
|
const loggingEnabled = false; // Set to true to enable logging, false to disable
|
|
3736
5011
|
//#region ---- Multilingual string helpers ----
|
|
3737
|
-
/**
|
|
3738
|
-
* Per-locale string map as produced by multilingual editors (e.g. `{ "en-US": "...", "fa-IR": "..." }`).
|
|
3739
|
-
*/
|
|
3740
|
-
function isLocaleStringMap(value) {
|
|
3741
|
-
if (value === null || typeof value !== 'object' || Array.isArray(value) || value instanceof Date) {
|
|
3742
|
-
return false;
|
|
3743
|
-
}
|
|
3744
|
-
const values = Object.values(value);
|
|
3745
|
-
return values.length > 0 && values.every((v) => typeof v === 'string');
|
|
3746
|
-
}
|
|
3747
5012
|
/**
|
|
3748
5013
|
* Lowercased text for client-side filtering: plain string or all locale values joined.
|
|
3749
5014
|
*/
|
|
@@ -4468,5 +5733,5 @@ function generateKebabCase(title) {
|
|
|
4468
5733
|
* Generated bundle index. Do not edit.
|
|
4469
5734
|
*/
|
|
4470
5735
|
|
|
4471
|
-
export { AXHighlightService, AXPActivityLogProvider, AXPActivityLogService, AXPAppStartUpProvider, AXPAppStartUpService, AXPBroadcastEventService, 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_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, applyFilterArray, applyPagination, applyQueryArray, applySortArray, applySystemActionDefault, cleanDeep, coerceUnknownToBoolean, coerceUnknownToDate, coerceUnknownToFiniteNumber, coerceUnknownToTrimmedString, compareMultiLanguageStrings, containsHtmlMarkup, createProviderWithInjectionContext, defaultColumnWidthProvider, extractNestedFieldsWildcard, extractTextFromHtml, extractValue, generateKebabCase, getActionButton, getChangedPaths, getDetailedChanges, getEnumValues, getNestedKeys, getSmart, getSystemActions, normalizeDefinitionCategories, objectKeyValueTransforms, provideLazyProvider, resolveActionLook, resolvePlatformScopeKey, resolvePlatformScopeName, searchInMultiLanguageString, setSmart, sortByMultiLanguageString, unwrapValueProperty };
|
|
5736
|
+
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, 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, MODULE_MANIFESTS_DATASOURCE_NAME, PLATFORM_CATALOG_SCOPES_DATASOURCE_NAME, 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 };
|
|
4472
5737
|
//# sourceMappingURL=acorex-platform-core.mjs.map
|