@acorex/platform 21.0.0-next.85 → 21.0.0-next.89
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 +9 -3
- package/fesm2022/acorex-platform-auth.mjs.map +1 -1
- package/fesm2022/{acorex-platform-common-common-settings.provider-Ytey9uhY.mjs → acorex-platform-common-common-settings.provider-DilV4dql.mjs} +16 -3
- package/fesm2022/acorex-platform-common-common-settings.provider-DilV4dql.mjs.map +1 -0
- package/fesm2022/acorex-platform-common.mjs +3426 -2488
- package/fesm2022/acorex-platform-common.mjs.map +1 -1
- package/fesm2022/acorex-platform-contracts.mjs +1160 -9
- package/fesm2022/acorex-platform-contracts.mjs.map +1 -1
- package/fesm2022/acorex-platform-core.mjs +826 -152
- package/fesm2022/acorex-platform-core.mjs.map +1 -1
- package/fesm2022/acorex-platform-domain-contracts.mjs +58 -0
- package/fesm2022/acorex-platform-domain-contracts.mjs.map +1 -0
- package/fesm2022/acorex-platform-domain.mjs +2 -50
- package/fesm2022/acorex-platform-domain.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-builder.mjs +98 -189
- package/fesm2022/acorex-platform-layout-builder.mjs.map +1 -1
- package/fesm2022/{acorex-platform-layout-components-binding-expression-editor-popup.component-CXEdvDTf.mjs → acorex-platform-layout-components-binding-expression-editor-popup.component-RttFwaw9.mjs} +17 -4
- package/fesm2022/acorex-platform-layout-components-binding-expression-editor-popup.component-RttFwaw9.mjs.map +1 -0
- package/fesm2022/acorex-platform-layout-components.mjs +181 -46
- package/fesm2022/acorex-platform-layout-components.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-designer.mjs +7 -6
- package/fesm2022/acorex-platform-layout-designer.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-entity-attachments-page.component-CwG6n_Yb.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-entity-contracts.mjs +1208 -0
- package/fesm2022/acorex-platform-layout-entity-contracts.mjs.map +1 -0
- package/fesm2022/acorex-platform-layout-entity-file-list-popup.component--IeMZLIr.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-entity.mjs +1080 -889
- package/fesm2022/acorex-platform-layout-entity.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-views.mjs +1150 -247
- package/fesm2022/acorex-platform-layout-views.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-widget-core.mjs +388 -207
- package/fesm2022/acorex-platform-layout-widget-core.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-widgets-repeater-widget-column.component-CFyfix31.mjs +513 -0
- package/fesm2022/acorex-platform-layout-widgets-repeater-widget-column.component-CFyfix31.mjs.map +1 -0
- package/fesm2022/{acorex-platform-layout-widgets-tabular-data-edit-popup.component-DjpZU6gz.mjs → acorex-platform-layout-widgets-tabular-data-edit-popup.component-CZc2zFlg.mjs} +4 -4
- package/fesm2022/acorex-platform-layout-widgets-tabular-data-edit-popup.component-CZc2zFlg.mjs.map +1 -0
- package/fesm2022/{acorex-platform-layout-widgets-tabular-data-view-popup.component-gX-3Kx9I.mjs → acorex-platform-layout-widgets-tabular-data-view-popup.component-XWRUVoL4.mjs} +2 -2
- package/fesm2022/{acorex-platform-layout-widgets-tabular-data-view-popup.component-gX-3Kx9I.mjs.map → acorex-platform-layout-widgets-tabular-data-view-popup.component-XWRUVoL4.mjs.map} +1 -1
- package/fesm2022/acorex-platform-layout-widgets.mjs +1541 -394
- package/fesm2022/acorex-platform-layout-widgets.mjs.map +1 -1
- package/fesm2022/acorex-platform-runtime.mjs.map +1 -1
- package/fesm2022/{acorex-platform-themes-default-entity-master-single-view.component-Br9p5aXT.mjs → acorex-platform-themes-default-entity-master-single-view.component-DrUwqKKA.mjs} +2 -3
- package/fesm2022/acorex-platform-themes-default-entity-master-single-view.component-DrUwqKKA.mjs.map +1 -0
- package/fesm2022/acorex-platform-themes-default.mjs +956 -115
- package/fesm2022/acorex-platform-themes-default.mjs.map +1 -1
- package/fesm2022/{acorex-platform-themes-shared-settings.provider-BjuzSe0T.mjs → acorex-platform-themes-shared-settings.provider-UyKdkeyk.mjs} +50 -3
- package/fesm2022/acorex-platform-themes-shared-settings.provider-UyKdkeyk.mjs.map +1 -0
- package/fesm2022/acorex-platform-themes-shared.mjs +104 -24
- package/fesm2022/acorex-platform-themes-shared.mjs.map +1 -1
- package/fesm2022/acorex-platform-workflow.mjs +6 -8
- package/fesm2022/acorex-platform-workflow.mjs.map +1 -1
- package/package.json +9 -5
- package/types/acorex-platform-auth.d.ts +3 -1
- package/types/acorex-platform-common.d.ts +655 -1268
- package/types/acorex-platform-contracts.d.ts +1791 -260
- package/types/acorex-platform-core.d.ts +2302 -2800
- package/types/acorex-platform-domain-contracts.d.ts +416 -0
- package/types/acorex-platform-domain.d.ts +4 -415
- package/types/acorex-platform-layout-builder.d.ts +11 -20
- package/types/acorex-platform-layout-components.d.ts +29 -5
- package/types/acorex-platform-layout-designer.d.ts +2 -1
- package/types/acorex-platform-layout-entity-contracts.d.ts +889 -0
- package/types/acorex-platform-layout-entity.d.ts +143 -275
- package/types/acorex-platform-layout-views.d.ts +147 -31
- package/types/acorex-platform-layout-widget-core.d.ts +74 -14
- package/types/acorex-platform-layout-widgets.d.ts +136 -38
- package/types/acorex-platform-runtime.d.ts +1 -1
- package/types/acorex-platform-themes-default.d.ts +227 -73
- package/types/acorex-platform-themes-shared.d.ts +4 -0
- package/types/acorex-platform-workflow.d.ts +5 -18
- package/fesm2022/acorex-platform-common-common-settings.provider-Ytey9uhY.mjs.map +0 -1
- package/fesm2022/acorex-platform-layout-components-binding-expression-editor-popup.component-CXEdvDTf.mjs.map +0 -1
- package/fesm2022/acorex-platform-layout-widgets-repeater-widget-column.component-J0zcGKBX.mjs +0 -116
- package/fesm2022/acorex-platform-layout-widgets-repeater-widget-column.component-J0zcGKBX.mjs.map +0 -1
- package/fesm2022/acorex-platform-layout-widgets-tabular-data-edit-popup.component-DjpZU6gz.mjs.map +0 -1
- package/fesm2022/acorex-platform-native.mjs +0 -155
- package/fesm2022/acorex-platform-native.mjs.map +0 -1
- package/fesm2022/acorex-platform-themes-default-entity-master-single-view.component-Br9p5aXT.mjs.map +0 -1
- package/fesm2022/acorex-platform-themes-shared-settings.provider-BjuzSe0T.mjs.map +0 -1
- package/native/README.md +0 -4
- package/types/acorex-platform-native.d.ts +0 -28
|
@@ -1,29 +1,21 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, inject, Injectable, Directive, computed, Injector, ChangeDetectionStrategy, Component, input, ElementRef, ViewContainerRef, signal, effect, runInInjectionContext, Optional, Inject, NgModule, EventEmitter, HostListener, Output, provideAppInitializer, Pipe } from '@angular/core';
|
|
2
|
+
import { InjectionToken, inject, Injectable, Directive, computed, Injector, ChangeDetectionStrategy, Component, input, ElementRef, ViewContainerRef, signal, effect, runInInjectionContext, Optional, Inject, NgModule, EventEmitter, HostListener, Output, DestroyRef, untracked, provideAppInitializer, Pipe } from '@angular/core';
|
|
3
3
|
import { AXTranslationService, resolveMultiLanguageString } from '@acorex/core/translation';
|
|
4
4
|
import { AXDataSource } from '@acorex/cdk/common';
|
|
5
|
-
import {
|
|
5
|
+
import { AXPPlatformCoreDataSourceKeys, AXPSystemActionType } from '@acorex/platform/contracts';
|
|
6
|
+
import { isPlainObject, get, set, isNil, isEmpty, isArray, merge, isObjectLike, transform, isEqual, differenceWith, union, cloneDeep, has, unset, sortBy, isUndefined, endsWith, startsWith, includes, lte, gte, lt, gt, orderBy } from 'lodash-es';
|
|
6
7
|
import { signalStore, withState, withComputed, withMethods, patchState } from '@ngrx/signals';
|
|
8
|
+
import { AXFormatService } from '@acorex/core/format';
|
|
7
9
|
import * as i1 from '@acorex/components/skeleton';
|
|
8
10
|
import { AXSkeletonModule } from '@acorex/components/skeleton';
|
|
9
11
|
import { Subject, interval, fromEvent } from 'rxjs';
|
|
10
12
|
import { toSignal } from '@angular/core/rxjs-interop';
|
|
11
13
|
import { DOCUMENT } from '@angular/common';
|
|
12
14
|
import { AXLocaleService } from '@acorex/core/locale';
|
|
15
|
+
import { delay } from '@acorex/core/utils';
|
|
13
16
|
import { AXCalendarService } from '@acorex/core/date-time';
|
|
14
17
|
import { startWith, map, debounceTime } from 'rxjs/operators';
|
|
15
18
|
|
|
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
|
-
|
|
27
19
|
const AXP_ACTIVITY_LOG_PROVIDER = new InjectionToken('AXP_ACTIVITY_LOGS_PROVIDER');
|
|
28
20
|
class AXPActivityLogService {
|
|
29
21
|
constructor() {
|
|
@@ -715,6 +707,104 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
715
707
|
type: Directive
|
|
716
708
|
}] });
|
|
717
709
|
|
|
710
|
+
//#region ---- Imports ----
|
|
711
|
+
//#endregion
|
|
712
|
+
//#region ---- Debug Service ----
|
|
713
|
+
/**
|
|
714
|
+
* Platform debug and trace logging.
|
|
715
|
+
* Call `trace` / `traceGroup` for diagnostics (gated by tenant Debug Mode).
|
|
716
|
+
* Call `warn` / `error` for always-on console output.
|
|
717
|
+
*
|
|
718
|
+
* Debug Mode is applied via `setDebugMode()` (wired from settings in platform common).
|
|
719
|
+
*/
|
|
720
|
+
class AXPDebugService {
|
|
721
|
+
//#endregion
|
|
722
|
+
//#region ---- Lifecycle ----
|
|
723
|
+
constructor() {
|
|
724
|
+
//#endregion
|
|
725
|
+
//#region ---- State ----
|
|
726
|
+
this.debugModeEnabled = false;
|
|
727
|
+
AXPDebugService.instance = this;
|
|
728
|
+
}
|
|
729
|
+
//#endregion
|
|
730
|
+
//#region ---- Debug Mode ----
|
|
731
|
+
isDebugMode() {
|
|
732
|
+
return this.debugModeEnabled;
|
|
733
|
+
}
|
|
734
|
+
setDebugMode(enabled) {
|
|
735
|
+
this.debugModeEnabled = enabled;
|
|
736
|
+
}
|
|
737
|
+
//#endregion
|
|
738
|
+
//#region ---- Trace API ----
|
|
739
|
+
/**
|
|
740
|
+
* @param namespace Module/feature namespace (kebab-case). Filter console with `[AXP:<namespace>]`.
|
|
741
|
+
* @param title Short area name (component, service, middleware).
|
|
742
|
+
* @param message What happened.
|
|
743
|
+
* @param data Optional structured payload, or a factory evaluated only when Debug Mode is on.
|
|
744
|
+
*/
|
|
745
|
+
trace(namespace, title, message, data) {
|
|
746
|
+
if (!this.debugModeEnabled) {
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
this.log('log', namespace, title, message, data);
|
|
750
|
+
}
|
|
751
|
+
/**
|
|
752
|
+
* Same as {@link trace} but emits a collapsible console group (useful for multi-field payloads).
|
|
753
|
+
*/
|
|
754
|
+
traceGroup(namespace, title, message, data) {
|
|
755
|
+
if (!this.debugModeEnabled) {
|
|
756
|
+
return;
|
|
757
|
+
}
|
|
758
|
+
const resolved = this.resolveData(data);
|
|
759
|
+
console.groupCollapsed(this.formatPrefix(namespace, title, message));
|
|
760
|
+
if (resolved != null) {
|
|
761
|
+
for (const [key, value] of Object.entries(resolved)) {
|
|
762
|
+
if (value !== undefined) {
|
|
763
|
+
console.log(key, value);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
console.groupEnd();
|
|
768
|
+
}
|
|
769
|
+
warn(namespace, title, message, data) {
|
|
770
|
+
this.log('warn', namespace, title, message, data);
|
|
771
|
+
}
|
|
772
|
+
error(namespace, title, message, data) {
|
|
773
|
+
this.log('error', namespace, title, message, data);
|
|
774
|
+
}
|
|
775
|
+
//#endregion
|
|
776
|
+
//#region ---- Utility Methods ----
|
|
777
|
+
formatPrefix(namespace, title, message) {
|
|
778
|
+
return `[AXP:${namespace}] ${title} — ${message}`;
|
|
779
|
+
}
|
|
780
|
+
resolveData(data) {
|
|
781
|
+
if (data == null) {
|
|
782
|
+
return undefined;
|
|
783
|
+
}
|
|
784
|
+
return typeof data === 'function' ? data() : data;
|
|
785
|
+
}
|
|
786
|
+
log(level, namespace, title, message, data) {
|
|
787
|
+
const prefix = this.formatPrefix(namespace, title, message);
|
|
788
|
+
const resolved = this.resolveData(data);
|
|
789
|
+
if (resolved != null) {
|
|
790
|
+
console[level](prefix, resolved);
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
793
|
+
console[level](prefix);
|
|
794
|
+
}
|
|
795
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPDebugService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
796
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPDebugService, providedIn: 'root' }); }
|
|
797
|
+
}
|
|
798
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPDebugService, decorators: [{
|
|
799
|
+
type: Injectable,
|
|
800
|
+
args: [{
|
|
801
|
+
providedIn: 'root',
|
|
802
|
+
}]
|
|
803
|
+
}], ctorParameters: () => [] });
|
|
804
|
+
|
|
805
|
+
/** Trace namespace for form dirty-state diagnostics (lookup/select revert, discard, baseline). */
|
|
806
|
+
const FORM_DIRTY_TRACE_NS = 'FORM STATE';
|
|
807
|
+
|
|
718
808
|
//#region ---- Locale map shape ----
|
|
719
809
|
/** Per-locale string map (`{ 'en-US': '...', 'fa-IR': '...' }`). */
|
|
720
810
|
function isLocaleStringMap(value) {
|
|
@@ -1037,6 +1127,13 @@ function normalizeLocaleStringMap(value) {
|
|
|
1037
1127
|
}
|
|
1038
1128
|
return Object.keys(normalized).length === 0 ? null : normalized;
|
|
1039
1129
|
}
|
|
1130
|
+
function compareMultiLanguageLocaleStrings(a, b) {
|
|
1131
|
+
const htmlEqual = compareHtmlFormValues(a, b);
|
|
1132
|
+
if (htmlEqual !== null) {
|
|
1133
|
+
return htmlEqual;
|
|
1134
|
+
}
|
|
1135
|
+
return a === b;
|
|
1136
|
+
}
|
|
1040
1137
|
/**
|
|
1041
1138
|
* Compares multi-language field values (plain string vs locale map) for dirty/baseline checks.
|
|
1042
1139
|
* Returns `null` when either side is not a multi-language shape (caller uses generic normalization).
|
|
@@ -1067,15 +1164,13 @@ function compareMultiLanguageFormValues(a, b) {
|
|
|
1067
1164
|
return false;
|
|
1068
1165
|
}
|
|
1069
1166
|
if (typeof left === 'string' && typeof right === 'string') {
|
|
1070
|
-
return left
|
|
1167
|
+
return compareMultiLanguageLocaleStrings(left, right);
|
|
1071
1168
|
}
|
|
1072
1169
|
if (typeof left === 'string' && typeof right === 'object') {
|
|
1073
|
-
|
|
1074
|
-
return values.length > 0 && values.every((text) => text === left);
|
|
1170
|
+
return Object.values(right).some((text) => typeof text === 'string' && compareMultiLanguageLocaleStrings(left, text));
|
|
1075
1171
|
}
|
|
1076
1172
|
if (typeof right === 'string' && typeof left === 'object') {
|
|
1077
|
-
|
|
1078
|
-
return values.length > 0 && values.every((text) => text === right);
|
|
1173
|
+
return Object.values(left).some((text) => typeof text === 'string' && compareMultiLanguageLocaleStrings(right, text));
|
|
1079
1174
|
}
|
|
1080
1175
|
if (typeof left !== 'object' || typeof right !== 'object') {
|
|
1081
1176
|
return false;
|
|
@@ -1087,7 +1182,56 @@ function compareMultiLanguageFormValues(a, b) {
|
|
|
1087
1182
|
if (leftKeys.length !== rightKeys.length) {
|
|
1088
1183
|
return false;
|
|
1089
1184
|
}
|
|
1090
|
-
return leftKeys.every((key) => leftMap[key]
|
|
1185
|
+
return leftKeys.every((key) => compareMultiLanguageLocaleStrings(leftMap[key], rightMap[key]));
|
|
1186
|
+
}
|
|
1187
|
+
/**
|
|
1188
|
+
* Lookup/select expose may write a resolved MLS string while the baseline keeps the locale map.
|
|
1189
|
+
* Restore baseline shape when the string matches any locale entry.
|
|
1190
|
+
*/
|
|
1191
|
+
function shouldRestoreSavedMultiLanguageShape(value, savedAtPath) {
|
|
1192
|
+
if (typeof value !== 'string' || !isLocaleStringMap(savedAtPath)) {
|
|
1193
|
+
return false;
|
|
1194
|
+
}
|
|
1195
|
+
const trimmed = value.trim();
|
|
1196
|
+
if (trimmed === '') {
|
|
1197
|
+
return false;
|
|
1198
|
+
}
|
|
1199
|
+
return Object.values(savedAtPath).some((entry) => typeof entry === 'string' && entry.trim() === trimmed);
|
|
1200
|
+
}
|
|
1201
|
+
/** Decode common HTML entities for rich-text baseline compare. */
|
|
1202
|
+
function decodeHtmlEntities(value) {
|
|
1203
|
+
return value
|
|
1204
|
+
.replace(/&#x([0-9a-f]+);/gi, (_, hex) => String.fromCharCode(parseInt(hex, 16)))
|
|
1205
|
+
.replace(/&#(\d+);/g, (_, dec) => String.fromCharCode(parseInt(dec, 10)))
|
|
1206
|
+
.replace(/&/g, '&')
|
|
1207
|
+
.replace(/</g, '<')
|
|
1208
|
+
.replace(/>/g, '>')
|
|
1209
|
+
.replace(/"/g, '"');
|
|
1210
|
+
}
|
|
1211
|
+
function looksLikeHtmlFormValue(value) {
|
|
1212
|
+
return /<[a-z][\s\S]*>/i.test(value.trim());
|
|
1213
|
+
}
|
|
1214
|
+
/** Normalizes rich-text HTML for semantic dirty/baseline compare (e.g. Quill `p`/`br` drift). */
|
|
1215
|
+
function normalizeRichTextHtmlForCompare(value) {
|
|
1216
|
+
const html = decodeHtmlEntities(value.trim());
|
|
1217
|
+
if (typeof document === 'undefined') {
|
|
1218
|
+
return html.replace(/\s+/g, ' ').trim();
|
|
1219
|
+
}
|
|
1220
|
+
const doc = new DOMParser().parseFromString(html, 'text/html');
|
|
1221
|
+
return (doc.body.textContent ?? '').replace(/\s+/g, ' ').trim();
|
|
1222
|
+
}
|
|
1223
|
+
/**
|
|
1224
|
+
* Compares rich-text HTML strings by visible text (ignores `p` vs `br` structure drift).
|
|
1225
|
+
* Returns `null` when either side is not HTML markup.
|
|
1226
|
+
*/
|
|
1227
|
+
function compareHtmlFormValues(a, b) {
|
|
1228
|
+
if (typeof a !== 'string' || typeof b !== 'string') {
|
|
1229
|
+
return null;
|
|
1230
|
+
}
|
|
1231
|
+
if (!looksLikeHtmlFormValue(a) || !looksLikeHtmlFormValue(b)) {
|
|
1232
|
+
return null;
|
|
1233
|
+
}
|
|
1234
|
+
return normalizeRichTextHtmlForCompare(a) === normalizeRichTextHtmlForCompare(b);
|
|
1091
1235
|
}
|
|
1092
1236
|
/** Lookup/select filter-mode payload: `{ value, displayText?, operation }`. */
|
|
1093
1237
|
function isFilterFormShape(value) {
|
|
@@ -1295,6 +1439,12 @@ function isFormValueEqual(a, b) {
|
|
|
1295
1439
|
}
|
|
1296
1440
|
return true;
|
|
1297
1441
|
}
|
|
1442
|
+
if (typeof a === 'string' && typeof b === 'string') {
|
|
1443
|
+
const htmlEqual = compareHtmlFormValues(a, b);
|
|
1444
|
+
if (htmlEqual !== null) {
|
|
1445
|
+
return htmlEqual;
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1298
1448
|
return isEqual(normalizeFormValue(a), normalizeFormValue(b));
|
|
1299
1449
|
}
|
|
1300
1450
|
/**
|
|
@@ -1303,6 +1453,45 @@ function isFormValueEqual(a, b) {
|
|
|
1303
1453
|
function isFormContextDirty(current, baseline) {
|
|
1304
1454
|
return !isFormValueEqual(current, baseline);
|
|
1305
1455
|
}
|
|
1456
|
+
/**
|
|
1457
|
+
* Lists paths where live context differs from baseline (for dirty-state trace logs).
|
|
1458
|
+
*/
|
|
1459
|
+
function summarizeFormContextDirtyDiff(current, baseline, maxEntries = 12) {
|
|
1460
|
+
const diffs = [];
|
|
1461
|
+
const walk = (path, currentValue, baselineValue) => {
|
|
1462
|
+
if (diffs.length >= maxEntries) {
|
|
1463
|
+
return;
|
|
1464
|
+
}
|
|
1465
|
+
if (isFormValueEqual(currentValue, baselineValue)) {
|
|
1466
|
+
return;
|
|
1467
|
+
}
|
|
1468
|
+
const bothPlainObjects = isPlainObject(currentValue) &&
|
|
1469
|
+
isPlainObject(baselineValue) &&
|
|
1470
|
+
!isArray(currentValue) &&
|
|
1471
|
+
!isArray(baselineValue);
|
|
1472
|
+
if (bothPlainObjects) {
|
|
1473
|
+
const left = currentValue;
|
|
1474
|
+
const right = baselineValue;
|
|
1475
|
+
const keys = new Set([...Object.keys(left), ...Object.keys(right)]);
|
|
1476
|
+
for (const key of keys) {
|
|
1477
|
+
if (diffs.length >= maxEntries) {
|
|
1478
|
+
return;
|
|
1479
|
+
}
|
|
1480
|
+
const nextPath = path ? `${path}.${key}` : key;
|
|
1481
|
+
walk(nextPath, left[key], right[key]);
|
|
1482
|
+
}
|
|
1483
|
+
return;
|
|
1484
|
+
}
|
|
1485
|
+
diffs.push({
|
|
1486
|
+
path: path || '(root)',
|
|
1487
|
+
current: currentValue,
|
|
1488
|
+
baseline: baselineValue,
|
|
1489
|
+
selectionEqual: isSelectionValueEqual(currentValue, baselineValue),
|
|
1490
|
+
});
|
|
1491
|
+
};
|
|
1492
|
+
walk('', current, baseline);
|
|
1493
|
+
return diffs;
|
|
1494
|
+
}
|
|
1306
1495
|
/**
|
|
1307
1496
|
* Clones a context object for use as a dirty-tracking baseline.
|
|
1308
1497
|
*/
|
|
@@ -1321,20 +1510,26 @@ const AXPContextStore = signalStore(withState(() => ({
|
|
|
1321
1510
|
/** Last committed / saved baseline — discard reverts to this snapshot. */
|
|
1322
1511
|
savedSnapshot: {},
|
|
1323
1512
|
previousSnapshot: {},
|
|
1513
|
+
/** True after a user-origin update since the last {@link commitSaved}. */
|
|
1514
|
+
userEditedSinceCommit: false,
|
|
1324
1515
|
lastChange: {
|
|
1325
1516
|
state: 'initiated',
|
|
1326
1517
|
},
|
|
1327
|
-
})), withComputed(({ data, state, lastChange, savedSnapshot, previousSnapshot }) => ({
|
|
1518
|
+
})), withComputed(({ data, state, lastChange, savedSnapshot, previousSnapshot, userEditedSinceCommit }) => ({
|
|
1328
1519
|
isChanged: computed(() => state() === 'changed'),
|
|
1329
1520
|
isReset: computed(() => state() === 'restored'),
|
|
1330
1521
|
isInitiated: computed(() => state() === 'initiated'),
|
|
1331
1522
|
isEmpty: computed(() => Object.keys(data()).length === 0),
|
|
1332
1523
|
isSavedCommitted: computed(() => !isEmpty(savedSnapshot())),
|
|
1524
|
+
hasUserEditedSinceCommit: computed(() => userEditedSinceCommit()),
|
|
1333
1525
|
isDirty: computed(() => {
|
|
1334
1526
|
const saved = savedSnapshot();
|
|
1335
1527
|
if (isEmpty(saved)) {
|
|
1336
1528
|
return false;
|
|
1337
1529
|
}
|
|
1530
|
+
if (!userEditedSinceCommit()) {
|
|
1531
|
+
return false;
|
|
1532
|
+
}
|
|
1338
1533
|
return isFormContextDirty(data(), saved);
|
|
1339
1534
|
}),
|
|
1340
1535
|
snapshot: computed(() => cloneDeep(data())),
|
|
@@ -1344,24 +1539,80 @@ const AXPContextStore = signalStore(withState(() => ({
|
|
|
1344
1539
|
previous: computed(() => cloneDeep(previousSnapshot())),
|
|
1345
1540
|
changeEvent: computed(() => lastChange()),
|
|
1346
1541
|
})), withMethods((store) => {
|
|
1542
|
+
const traceDirty = (title, message, data) => {
|
|
1543
|
+
AXPDebugService.instance?.trace(FORM_DIRTY_TRACE_NS, title, message, data);
|
|
1544
|
+
};
|
|
1545
|
+
const traceDirtyState = (reason, data, path) => {
|
|
1546
|
+
const saved = store.savedSnapshot();
|
|
1547
|
+
const current = store.data();
|
|
1548
|
+
const hasSaved = !isEmpty(saved);
|
|
1549
|
+
const contextDirty = hasSaved && isFormContextDirty(current, saved);
|
|
1550
|
+
traceDirty('context-store', reason, () => ({
|
|
1551
|
+
path,
|
|
1552
|
+
userEditedSinceCommit: store.userEditedSinceCommit(),
|
|
1553
|
+
isSavedCommitted: hasSaved,
|
|
1554
|
+
isFormContextDirty: contextDirty,
|
|
1555
|
+
isDirty: store.isDirty(),
|
|
1556
|
+
...(hasSaved
|
|
1557
|
+
? {
|
|
1558
|
+
dirtyPaths: contextDirty ? summarizeFormContextDirtyDiff(current, saved) : [],
|
|
1559
|
+
}
|
|
1560
|
+
: {}),
|
|
1561
|
+
...data,
|
|
1562
|
+
}));
|
|
1563
|
+
};
|
|
1347
1564
|
const updateValue = (path, value, options) => {
|
|
1348
1565
|
const currentData = cloneDeep(store.data());
|
|
1349
1566
|
const oldValue = getSmart(currentData, path);
|
|
1350
1567
|
const hasSaved = !isEmpty(store.savedSnapshot());
|
|
1351
|
-
const
|
|
1568
|
+
const saved = store.savedSnapshot();
|
|
1569
|
+
const savedAtPath = hasSaved ? getSmart(saved, path) : undefined;
|
|
1570
|
+
const origin = options?.origin ?? 'system';
|
|
1352
1571
|
if (hasSaved && isSelectionValueEqual(value, savedAtPath) && !isEqual(value, savedAtPath)) {
|
|
1353
1572
|
value = cloneDeep(savedAtPath);
|
|
1354
1573
|
}
|
|
1574
|
+
if (hasSaved && !isEqual(value, savedAtPath) && isFormValueEqual(value, savedAtPath)) {
|
|
1575
|
+
value = cloneDeep(savedAtPath);
|
|
1576
|
+
}
|
|
1355
1577
|
const isArrayLengthChange = Array.isArray(value) && (!Array.isArray(oldValue) || value.length !== oldValue.length);
|
|
1356
|
-
|
|
1578
|
+
/** Same selection id as baseline but different object shape (lookup/select item vs scalar). */
|
|
1579
|
+
const needsSavedShapeRestore = hasSaved && isSelectionValueEqual(value, savedAtPath) && !isFormValueEqual(value, savedAtPath);
|
|
1580
|
+
if (!isArrayLengthChange && isEqual(oldValue, value) && !needsSavedShapeRestore) {
|
|
1581
|
+
traceDirty('context-store', 'updateValue skipped (lodash isEqual)', () => ({
|
|
1582
|
+
path,
|
|
1583
|
+
origin,
|
|
1584
|
+
oldValue,
|
|
1585
|
+
value,
|
|
1586
|
+
needsSavedShapeRestore,
|
|
1587
|
+
selectionEqualToSaved: hasSaved ? isSelectionValueEqual(value, savedAtPath) : null,
|
|
1588
|
+
savedAtPath,
|
|
1589
|
+
}));
|
|
1357
1590
|
return;
|
|
1358
1591
|
}
|
|
1359
1592
|
const shouldNormalizeSavedShape = hasSaved && isSelectionValueEqual(value, savedAtPath) && !isEqual(oldValue, value);
|
|
1360
|
-
if (!isArrayLengthChange &&
|
|
1593
|
+
if (!isArrayLengthChange &&
|
|
1594
|
+
isFormValueEqual(oldValue, value) &&
|
|
1595
|
+
!shouldNormalizeSavedShape &&
|
|
1596
|
+
!needsSavedShapeRestore) {
|
|
1597
|
+
traceDirty('context-store', 'updateValue skipped (isFormValueEqual)', () => ({
|
|
1598
|
+
path,
|
|
1599
|
+
origin,
|
|
1600
|
+
oldValue,
|
|
1601
|
+
value,
|
|
1602
|
+
needsSavedShapeRestore,
|
|
1603
|
+
shouldNormalizeSavedShape,
|
|
1604
|
+
selectionEqualToSaved: hasSaved ? isSelectionValueEqual(value, savedAtPath) : null,
|
|
1605
|
+
savedAtPath,
|
|
1606
|
+
}));
|
|
1361
1607
|
return;
|
|
1362
1608
|
}
|
|
1363
|
-
const origin = options?.origin ?? 'system';
|
|
1364
1609
|
const updatedData = setSmart(currentData, path, value);
|
|
1610
|
+
const isStillDirty = hasSaved && isFormContextDirty(updatedData, saved);
|
|
1611
|
+
const nextUserEdited = isStillDirty
|
|
1612
|
+
? origin === 'user'
|
|
1613
|
+
? true
|
|
1614
|
+
: store.userEditedSinceCommit()
|
|
1615
|
+
: false;
|
|
1365
1616
|
const changeEvent = {
|
|
1366
1617
|
oldValue,
|
|
1367
1618
|
newValue: value,
|
|
@@ -1375,7 +1626,22 @@ const AXPContextStore = signalStore(withState(() => ({
|
|
|
1375
1626
|
data: updatedData,
|
|
1376
1627
|
state: 'changed',
|
|
1377
1628
|
lastChange: changeEvent,
|
|
1629
|
+
userEditedSinceCommit: nextUserEdited,
|
|
1378
1630
|
});
|
|
1631
|
+
traceDirty('context-store', 'updateValue applied', () => ({
|
|
1632
|
+
path,
|
|
1633
|
+
origin,
|
|
1634
|
+
oldValue,
|
|
1635
|
+
newValue: value,
|
|
1636
|
+
savedAtPath,
|
|
1637
|
+
selectionEqualToSaved: hasSaved ? isSelectionValueEqual(value, savedAtPath) : null,
|
|
1638
|
+
formValueEqualToSaved: hasSaved ? isFormValueEqual(value, savedAtPath) : null,
|
|
1639
|
+
needsSavedShapeRestore,
|
|
1640
|
+
isStillDirty,
|
|
1641
|
+
nextUserEdited,
|
|
1642
|
+
isDirty: !isEmpty(saved) && nextUserEdited && isStillDirty,
|
|
1643
|
+
dirtyPaths: isStillDirty ? summarizeFormContextDirtyDiff(updatedData, saved) : [],
|
|
1644
|
+
}));
|
|
1379
1645
|
};
|
|
1380
1646
|
const applyObjectPaths = (obj, options, prefix = '') => {
|
|
1381
1647
|
for (const [key, value] of Object.entries(obj ?? {})) {
|
|
@@ -1388,6 +1654,57 @@ const AXPContextStore = signalStore(withState(() => ({
|
|
|
1388
1654
|
}
|
|
1389
1655
|
}
|
|
1390
1656
|
};
|
|
1657
|
+
const removePathFromContext = (path, origin = 'system') => {
|
|
1658
|
+
const currentData = cloneDeep(store.data());
|
|
1659
|
+
if (!has(currentData, path)) {
|
|
1660
|
+
return;
|
|
1661
|
+
}
|
|
1662
|
+
unset(currentData, path);
|
|
1663
|
+
const parts = path.split('.');
|
|
1664
|
+
for (let i = parts.length - 1; i > 0; i--) {
|
|
1665
|
+
const parentPath = parts.slice(0, i).join('.');
|
|
1666
|
+
const parentObj = getSmart(currentData, parentPath);
|
|
1667
|
+
if (isPlainObject(parentObj) && isEmpty(Object.keys(parentObj))) {
|
|
1668
|
+
unset(currentData, parentPath);
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
const saved = store.savedSnapshot();
|
|
1672
|
+
const isStillDirty = !isEmpty(saved) && isFormContextDirty(currentData, saved);
|
|
1673
|
+
patchState(store, {
|
|
1674
|
+
previousSnapshot: store.snapshot(),
|
|
1675
|
+
data: currentData,
|
|
1676
|
+
state: 'changed',
|
|
1677
|
+
lastChange: {
|
|
1678
|
+
path,
|
|
1679
|
+
state: 'changed',
|
|
1680
|
+
data: currentData,
|
|
1681
|
+
origin,
|
|
1682
|
+
},
|
|
1683
|
+
userEditedSinceCommit: isStillDirty
|
|
1684
|
+
? origin === 'user'
|
|
1685
|
+
? true
|
|
1686
|
+
: store.userEditedSinceCommit()
|
|
1687
|
+
: false,
|
|
1688
|
+
});
|
|
1689
|
+
traceDirty('context-store', 'removePathFromContext', () => ({
|
|
1690
|
+
path,
|
|
1691
|
+
origin,
|
|
1692
|
+
isStillDirty,
|
|
1693
|
+
dirtyPaths: isStillDirty ? summarizeFormContextDirtyDiff(currentData, saved) : [],
|
|
1694
|
+
}));
|
|
1695
|
+
};
|
|
1696
|
+
const syncPathToSavedBaseline = (path, options) => {
|
|
1697
|
+
const saved = store.savedSnapshot();
|
|
1698
|
+
if (isEmpty(saved)) {
|
|
1699
|
+
return;
|
|
1700
|
+
}
|
|
1701
|
+
const origin = options?.origin ?? 'system';
|
|
1702
|
+
if (has(saved, path)) {
|
|
1703
|
+
updateValue(path, cloneDeep(getSmart(saved, path)), { origin });
|
|
1704
|
+
return;
|
|
1705
|
+
}
|
|
1706
|
+
removePathFromContext(path, origin);
|
|
1707
|
+
};
|
|
1391
1708
|
const revertToSaved = () => {
|
|
1392
1709
|
const savedData = store.saved();
|
|
1393
1710
|
const changeEvent = {
|
|
@@ -1402,13 +1719,17 @@ const AXPContextStore = signalStore(withState(() => ({
|
|
|
1402
1719
|
data: savedData,
|
|
1403
1720
|
state: 'restored',
|
|
1404
1721
|
lastChange: changeEvent,
|
|
1722
|
+
userEditedSinceCommit: false,
|
|
1405
1723
|
});
|
|
1724
|
+
traceDirtyState('revertToSaved');
|
|
1406
1725
|
};
|
|
1407
1726
|
return {
|
|
1408
1727
|
update(path, value, options) {
|
|
1409
1728
|
updateValue(path, value, options);
|
|
1410
1729
|
},
|
|
1411
1730
|
applyObjectPaths,
|
|
1731
|
+
/** Aligns a context path with the saved baseline (restore value or remove if absent). */
|
|
1732
|
+
syncPathToSavedBaseline,
|
|
1412
1733
|
patch(context, options) {
|
|
1413
1734
|
const currentData = cloneDeep(store.data());
|
|
1414
1735
|
const updatedData = { ...currentData, ...context };
|
|
@@ -1449,6 +1770,7 @@ const AXPContextStore = signalStore(withState(() => ({
|
|
|
1449
1770
|
data: initialData,
|
|
1450
1771
|
state: 'initiated',
|
|
1451
1772
|
lastChange: changeEvent,
|
|
1773
|
+
userEditedSinceCommit: false,
|
|
1452
1774
|
});
|
|
1453
1775
|
},
|
|
1454
1776
|
getValue(path) {
|
|
@@ -1467,11 +1789,13 @@ const AXPContextStore = signalStore(withState(() => ({
|
|
|
1467
1789
|
savedSnapshot: snapshot,
|
|
1468
1790
|
previousSnapshot: snapshot,
|
|
1469
1791
|
state: 'initiated',
|
|
1792
|
+
userEditedSinceCommit: false,
|
|
1470
1793
|
lastChange: {
|
|
1471
1794
|
state: 'initiated',
|
|
1472
1795
|
data: snapshot,
|
|
1473
1796
|
},
|
|
1474
1797
|
});
|
|
1798
|
+
traceDirtyState('commitSaved');
|
|
1475
1799
|
},
|
|
1476
1800
|
/** Merges parent-bound entity fields without resetting the saved baseline. */
|
|
1477
1801
|
applyParentBind(merged) {
|
|
@@ -1486,6 +1810,98 @@ const AXPContextStore = signalStore(withState(() => ({
|
|
|
1486
1810
|
}));
|
|
1487
1811
|
//#endregion
|
|
1488
1812
|
|
|
1813
|
+
//#region ---- Imports ----
|
|
1814
|
+
//#endregion
|
|
1815
|
+
//#region ---- Pipe normalization ----
|
|
1816
|
+
/** Normalizes shorthand pipe names to `{ type, options? }` specs. */
|
|
1817
|
+
function normalizeContextEvalPipes(pipes) {
|
|
1818
|
+
return pipes.map((pipe) => (typeof pipe === 'string' ? { type: pipe } : pipe));
|
|
1819
|
+
}
|
|
1820
|
+
//#endregion
|
|
1821
|
+
//#region ---- Pipe application ----
|
|
1822
|
+
/**
|
|
1823
|
+
* Applies a left-to-right formatter pipeline via {@link AXFormatService}.
|
|
1824
|
+
* Unknown formatters fall back to a trimmed string representation.
|
|
1825
|
+
*/
|
|
1826
|
+
function applyContextEvalPipes(value, pipes, formatService) {
|
|
1827
|
+
if (!pipes?.length) {
|
|
1828
|
+
return value;
|
|
1829
|
+
}
|
|
1830
|
+
let current = value;
|
|
1831
|
+
for (const pipe of normalizeContextEvalPipes(pipes)) {
|
|
1832
|
+
try {
|
|
1833
|
+
current = formatService.format(current, pipe.type, pipe.options);
|
|
1834
|
+
}
|
|
1835
|
+
catch {
|
|
1836
|
+
current = current == null ? '' : String(current);
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
return current;
|
|
1840
|
+
}
|
|
1841
|
+
/** Reads a nested path from data and optionally applies display pipes. */
|
|
1842
|
+
function contextEvalWithPipes(data, path, formatService, pipes) {
|
|
1843
|
+
return applyContextEvalPipes(get(data, path), pipes, formatService);
|
|
1844
|
+
}
|
|
1845
|
+
//#endregion
|
|
1846
|
+
|
|
1847
|
+
//#region ---- Imports ----
|
|
1848
|
+
//#endregion
|
|
1849
|
+
/**
|
|
1850
|
+
* Builds sync `context.eval(path, { pipes })` functions backed by {@link AXFormatService}.
|
|
1851
|
+
* Supports built-in and custom formatters registered on {@link AXFormatModule}.
|
|
1852
|
+
*/
|
|
1853
|
+
class AXPContextEvalFactory {
|
|
1854
|
+
constructor() {
|
|
1855
|
+
//#region ---- Services & Dependencies ----
|
|
1856
|
+
this.formatService = inject(AXFormatService);
|
|
1857
|
+
}
|
|
1858
|
+
//#endregion
|
|
1859
|
+
//#region ---- Factory ----
|
|
1860
|
+
/** Creates a sync eval function for the given record/context data object. */
|
|
1861
|
+
create(data) {
|
|
1862
|
+
return (path, options) => {
|
|
1863
|
+
const raw = get(data, path);
|
|
1864
|
+
return applyContextEvalPipes(raw, options?.pipes, this.formatService);
|
|
1865
|
+
};
|
|
1866
|
+
}
|
|
1867
|
+
/** Creates `{ eval }` scope object for expression evaluator scopes. */
|
|
1868
|
+
createScope(data) {
|
|
1869
|
+
return { eval: this.create(data) };
|
|
1870
|
+
}
|
|
1871
|
+
/**
|
|
1872
|
+
* Display-tier scope that re-reads data on each eval (for live stores / changing row context).
|
|
1873
|
+
*/
|
|
1874
|
+
createLiveScope(getData) {
|
|
1875
|
+
return {
|
|
1876
|
+
eval: (path, options) => this.create(getData())(path, options),
|
|
1877
|
+
};
|
|
1878
|
+
}
|
|
1879
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPContextEvalFactory, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1880
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPContextEvalFactory, providedIn: 'root' }); }
|
|
1881
|
+
}
|
|
1882
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPContextEvalFactory, decorators: [{
|
|
1883
|
+
type: Injectable,
|
|
1884
|
+
args: [{ providedIn: 'root' }]
|
|
1885
|
+
}] });
|
|
1886
|
+
|
|
1887
|
+
//#region ---- Types ----
|
|
1888
|
+
//#endregion
|
|
1889
|
+
|
|
1890
|
+
//#region ---- Template normalization ----
|
|
1891
|
+
/**
|
|
1892
|
+
* Normalizes display templates before {@link AXPExpressionEvaluatorService} evaluation.
|
|
1893
|
+
* Converts simple dot paths (e.g. `{{ position.title }}`) to `context.eval("position.title")`
|
|
1894
|
+
* so row data is read from scope.context instead of bare JavaScript identifiers.
|
|
1895
|
+
*/
|
|
1896
|
+
function normalizeExpressionTemplate(template) {
|
|
1897
|
+
let normalized = template.replace(/\{\{\s*context\.eval\(['"]([^'"]+)['"]\)\s*\}\}/g, '{{ context.eval("$1") }}');
|
|
1898
|
+
if (!normalized.includes('{{')) {
|
|
1899
|
+
normalized = normalized.replace(/\{/g, '{{').replace(/\}/g, '}}');
|
|
1900
|
+
}
|
|
1901
|
+
return normalized.replace(/\{\{\s*(?!context\.)([\w]+(?:\.[\w]+)*)\s*\}\}/g, '{{ context.eval("$1") }}');
|
|
1902
|
+
}
|
|
1903
|
+
//#endregion
|
|
1904
|
+
|
|
1489
1905
|
class AXPExpressionEvaluatorScopeProviderContext {
|
|
1490
1906
|
constructor() {
|
|
1491
1907
|
this.scopes = {};
|
|
@@ -1595,8 +2011,9 @@ class AXPExpressionEvaluatorService {
|
|
|
1595
2011
|
}
|
|
1596
2012
|
}
|
|
1597
2013
|
async evaluateStringExpression(templateExpression, scope) {
|
|
2014
|
+
const normalizedTemplate = normalizeExpressionTemplate(templateExpression);
|
|
1598
2015
|
// Check if the input is exactly a single {{ ... }} expression (handle multiline)
|
|
1599
|
-
const exactMatch =
|
|
2016
|
+
const exactMatch = normalizedTemplate.match(/^\s*\{\{\s*([\s\S]*?)\s*\}\}\s*$/);
|
|
1600
2017
|
if (exactMatch) {
|
|
1601
2018
|
const expression = exactMatch[1];
|
|
1602
2019
|
const sandbox = this.getOrCompileFunction(expression);
|
|
@@ -1608,7 +2025,7 @@ class AXPExpressionEvaluatorService {
|
|
|
1608
2025
|
// Collect all matches and their positions
|
|
1609
2026
|
const matches = [];
|
|
1610
2027
|
let match;
|
|
1611
|
-
while ((match = regex.exec(
|
|
2028
|
+
while ((match = regex.exec(normalizedTemplate)) !== null) {
|
|
1612
2029
|
matches.push({
|
|
1613
2030
|
expression: match[1],
|
|
1614
2031
|
start: match.index,
|
|
@@ -1625,12 +2042,12 @@ class AXPExpressionEvaluatorService {
|
|
|
1625
2042
|
let result = '';
|
|
1626
2043
|
let lastIndex = 0;
|
|
1627
2044
|
matches.forEach((m, i) => {
|
|
1628
|
-
result +=
|
|
2045
|
+
result += normalizedTemplate.slice(lastIndex, m.start);
|
|
1629
2046
|
const value = values[i];
|
|
1630
2047
|
result += value !== undefined && value !== null ? value : '';
|
|
1631
2048
|
lastIndex = m.end;
|
|
1632
2049
|
});
|
|
1633
|
-
result +=
|
|
2050
|
+
result += normalizedTemplate.slice(lastIndex);
|
|
1634
2051
|
return result;
|
|
1635
2052
|
}
|
|
1636
2053
|
isExpression(expression) {
|
|
@@ -2742,12 +3159,12 @@ const HORIZONTAL_DIRECTIONAL_KEY_MIRRORS = {
|
|
|
2742
3159
|
};
|
|
2743
3160
|
/** Maps a horizontal direction key to its RTL semantic counterpart. */
|
|
2744
3161
|
function mirrorHorizontalDirectionalKey(key) {
|
|
2745
|
-
const normalized = normalizeShortcutToken(key);
|
|
3162
|
+
const normalized = normalizeShortcutToken$1(key);
|
|
2746
3163
|
return HORIZONTAL_DIRECTIONAL_KEY_MIRRORS[normalized] ?? normalized;
|
|
2747
3164
|
}
|
|
2748
3165
|
/** True when the chord's primary key is a horizontal direction key (`←`/`→`, `[`/`]`). */
|
|
2749
3166
|
function isHorizontalDirectionalShortcutKey(key) {
|
|
2750
|
-
return normalizeShortcutToken(key) in HORIZONTAL_DIRECTIONAL_KEY_MIRRORS;
|
|
3167
|
+
return normalizeShortcutToken$1(key) in HORIZONTAL_DIRECTIONAL_KEY_MIRRORS;
|
|
2751
3168
|
}
|
|
2752
3169
|
/**
|
|
2753
3170
|
* Resolves direction behavior for a chord.
|
|
@@ -2763,7 +3180,7 @@ function resolveEffectiveDirectionBehavior(chord, explicit) {
|
|
|
2763
3180
|
}
|
|
2764
3181
|
/** Resolves the key the user presses for a semantically registered chord in RTL. */
|
|
2765
3182
|
function resolveSemanticDirectionalKey(storedKey, rtl) {
|
|
2766
|
-
const normalized = normalizeShortcutToken(storedKey);
|
|
3183
|
+
const normalized = normalizeShortcutToken$1(storedKey);
|
|
2767
3184
|
if (!rtl) {
|
|
2768
3185
|
return normalized;
|
|
2769
3186
|
}
|
|
@@ -2830,7 +3247,7 @@ function parseKeyboardShortcutChord(chord) {
|
|
|
2830
3247
|
.split('+')
|
|
2831
3248
|
.map((part) => part.trim())
|
|
2832
3249
|
.filter(Boolean);
|
|
2833
|
-
const key = normalizeShortcutToken(parts.pop() ?? '');
|
|
3250
|
+
const key = normalizeShortcutToken$1(parts.pop() ?? '');
|
|
2834
3251
|
const parsed = {
|
|
2835
3252
|
ctrl: false,
|
|
2836
3253
|
shift: false,
|
|
@@ -2858,7 +3275,7 @@ function matchesKeyboardShortcutChord(event, chord, options) {
|
|
|
2858
3275
|
return false;
|
|
2859
3276
|
}
|
|
2860
3277
|
const actualKey = normalizeShortcutEventKey(event);
|
|
2861
|
-
const expectedKey = normalizeShortcutToken(parsed.key);
|
|
3278
|
+
const expectedKey = normalizeShortcutToken$1(parsed.key);
|
|
2862
3279
|
const rtlSemantic = directionBehavior === 'semantic' &&
|
|
2863
3280
|
options?.isRtl === true &&
|
|
2864
3281
|
isHorizontalDirectionalShortcutKey(expectedKey);
|
|
@@ -2922,7 +3339,7 @@ function formatKeyboardShortcutChord(chord, options) {
|
|
|
2922
3339
|
if (parts.length === 0) {
|
|
2923
3340
|
return '';
|
|
2924
3341
|
}
|
|
2925
|
-
const keyPart = normalizeShortcutToken(parts.pop() ?? '');
|
|
3342
|
+
const keyPart = normalizeShortcutToken$1(parts.pop() ?? '');
|
|
2926
3343
|
const modifierParts = parts.map((part) => formatShortcutPart(part.toLowerCase(), mac));
|
|
2927
3344
|
const keyDisplay = formatShortcutPart(keyPart, mac);
|
|
2928
3345
|
if (mac) {
|
|
@@ -3016,7 +3433,7 @@ function isMacPlatform() {
|
|
|
3016
3433
|
}
|
|
3017
3434
|
return /Mac|iPhone|iPad|iPod/i.test(navigator.userAgent);
|
|
3018
3435
|
}
|
|
3019
|
-
function normalizeShortcutToken(key) {
|
|
3436
|
+
function normalizeShortcutToken$1(key) {
|
|
3020
3437
|
const normalized = key.trim().toLowerCase();
|
|
3021
3438
|
if (normalized === 'esc') {
|
|
3022
3439
|
return 'escape';
|
|
@@ -3078,7 +3495,7 @@ function normalizeShortcutEventKey(event) {
|
|
|
3078
3495
|
return event.key.toLowerCase();
|
|
3079
3496
|
}
|
|
3080
3497
|
function formatShortcutPart(part, mac) {
|
|
3081
|
-
const normalized = normalizeShortcutToken(part);
|
|
3498
|
+
const normalized = normalizeShortcutToken$1(part);
|
|
3082
3499
|
if (mac && KEY_DISPLAY_MAC[normalized]) {
|
|
3083
3500
|
return KEY_DISPLAY_MAC[normalized];
|
|
3084
3501
|
}
|
|
@@ -3355,6 +3772,271 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
3355
3772
|
}]
|
|
3356
3773
|
}] });
|
|
3357
3774
|
|
|
3775
|
+
//#region ---- Dialog Action Shortcut Utilities ----
|
|
3776
|
+
const DEFAULT_CANCEL_DIALOG_ACTION_SHORTCUTS = ['Esc'];
|
|
3777
|
+
const DEFAULT_SUBMIT_DIALOG_ACTION_SHORTCUTS = ['Enter', 'ctrl+s'];
|
|
3778
|
+
const PRIMARY_DIALOG_ACTION_COMMANDS = new Set([
|
|
3779
|
+
'submit',
|
|
3780
|
+
'create',
|
|
3781
|
+
'apply',
|
|
3782
|
+
'save',
|
|
3783
|
+
'select',
|
|
3784
|
+
'entity-form-done',
|
|
3785
|
+
'upload-image',
|
|
3786
|
+
]);
|
|
3787
|
+
/**
|
|
3788
|
+
* Parses a shortcut string such as `Enter`, `Escape`, or `ctrl+shift+s`.
|
|
3789
|
+
*/
|
|
3790
|
+
function parseDialogActionShortcut(shortcut) {
|
|
3791
|
+
const parts = shortcut
|
|
3792
|
+
.trim()
|
|
3793
|
+
.toLowerCase()
|
|
3794
|
+
.split('+')
|
|
3795
|
+
.map((part) => part.trim())
|
|
3796
|
+
.filter(Boolean);
|
|
3797
|
+
const key = normalizeShortcutToken(parts.pop() ?? '');
|
|
3798
|
+
return {
|
|
3799
|
+
ctrl: parts.includes('ctrl') || parts.includes('control'),
|
|
3800
|
+
shift: parts.includes('shift'),
|
|
3801
|
+
alt: parts.includes('alt') || parts.includes('option'),
|
|
3802
|
+
meta: parts.includes('meta') || parts.includes('cmd') || parts.includes('command'),
|
|
3803
|
+
key,
|
|
3804
|
+
};
|
|
3805
|
+
}
|
|
3806
|
+
/**
|
|
3807
|
+
* Returns true when the keyboard event matches the given shortcut definition.
|
|
3808
|
+
*/
|
|
3809
|
+
function matchesDialogActionShortcut(event, shortcut) {
|
|
3810
|
+
const parsed = parseDialogActionShortcut(shortcut);
|
|
3811
|
+
if (parsed.ctrl !== event.ctrlKey) {
|
|
3812
|
+
return false;
|
|
3813
|
+
}
|
|
3814
|
+
if (parsed.shift !== event.shiftKey) {
|
|
3815
|
+
return false;
|
|
3816
|
+
}
|
|
3817
|
+
if (parsed.alt !== event.altKey) {
|
|
3818
|
+
return false;
|
|
3819
|
+
}
|
|
3820
|
+
if (parsed.meta !== event.metaKey) {
|
|
3821
|
+
return false;
|
|
3822
|
+
}
|
|
3823
|
+
return normalizeShortcutKey(event) === normalizeShortcutToken(parsed.key);
|
|
3824
|
+
}
|
|
3825
|
+
/**
|
|
3826
|
+
* Resolves footer action shortcuts: defaults when omitted, merge when extras are provided, none when `[]`.
|
|
3827
|
+
*/
|
|
3828
|
+
function resolveDialogActionShortcuts(defaults, overrides) {
|
|
3829
|
+
if (overrides !== undefined) {
|
|
3830
|
+
if (overrides.length === 0) {
|
|
3831
|
+
return undefined;
|
|
3832
|
+
}
|
|
3833
|
+
return flattenDialogActionShortcutChords([...defaults, ...overrides]);
|
|
3834
|
+
}
|
|
3835
|
+
return flattenDialogActionShortcutChords(defaults);
|
|
3836
|
+
}
|
|
3837
|
+
/**
|
|
3838
|
+
* Applies built-in footer shortcuts when actions are declared without `shortcuts`
|
|
3839
|
+
* (e.g. workflow `show-layout-popup` raw action config).
|
|
3840
|
+
*/
|
|
3841
|
+
function resolveConfiguredFooterActionShortcuts(command, explicit) {
|
|
3842
|
+
if (explicit !== undefined) {
|
|
3843
|
+
return resolveDialogActionShortcuts([], explicit);
|
|
3844
|
+
}
|
|
3845
|
+
if (command === 'cancel') {
|
|
3846
|
+
return resolveDialogActionShortcuts(DEFAULT_CANCEL_DIALOG_ACTION_SHORTCUTS, undefined);
|
|
3847
|
+
}
|
|
3848
|
+
if (command && PRIMARY_DIALOG_ACTION_COMMANDS.has(command)) {
|
|
3849
|
+
return resolveDialogActionShortcuts(DEFAULT_SUBMIT_DIALOG_ACTION_SHORTCUTS, undefined);
|
|
3850
|
+
}
|
|
3851
|
+
return undefined;
|
|
3852
|
+
}
|
|
3853
|
+
function flattenDialogActionShortcutChords(shortcuts) {
|
|
3854
|
+
const chords = normalizeKeyboardShortcuts(shortcuts).flatMap((shortcut) => shortcut.keys);
|
|
3855
|
+
const deduped = dedupeDialogActionShortcuts(chords);
|
|
3856
|
+
return deduped.length ? deduped : undefined;
|
|
3857
|
+
}
|
|
3858
|
+
function dedupeDialogActionShortcuts(shortcuts) {
|
|
3859
|
+
const seen = new Set();
|
|
3860
|
+
const result = [];
|
|
3861
|
+
for (const shortcut of shortcuts) {
|
|
3862
|
+
const token = normalizeShortcutToken(shortcut);
|
|
3863
|
+
if (!token || seen.has(token)) {
|
|
3864
|
+
continue;
|
|
3865
|
+
}
|
|
3866
|
+
seen.add(token);
|
|
3867
|
+
result.push(shortcut.trim());
|
|
3868
|
+
}
|
|
3869
|
+
return result;
|
|
3870
|
+
}
|
|
3871
|
+
function normalizeShortcutToken(key) {
|
|
3872
|
+
const normalized = key.trim().toLowerCase();
|
|
3873
|
+
if (normalized === 'esc') {
|
|
3874
|
+
return 'escape';
|
|
3875
|
+
}
|
|
3876
|
+
return normalized;
|
|
3877
|
+
}
|
|
3878
|
+
/**
|
|
3879
|
+
* Whether a dialog footer shortcut may fire while focus is inside an editable field.
|
|
3880
|
+
*/
|
|
3881
|
+
function isDialogShortcutAllowedInEditableField(shortcut) {
|
|
3882
|
+
const parsed = parseDialogActionShortcut(shortcut);
|
|
3883
|
+
const isPlainEnter = parsed.key === 'enter' && !parsed.ctrl && !parsed.shift && !parsed.alt && !parsed.meta;
|
|
3884
|
+
return !isPlainEnter;
|
|
3885
|
+
}
|
|
3886
|
+
/**
|
|
3887
|
+
* Normalizes dialog footer chord strings for {@link AXPKeyboardShortcutRegistry} (`Esc` → `escape`).
|
|
3888
|
+
*/
|
|
3889
|
+
function normalizeDialogActionShortcutChord(shortcut) {
|
|
3890
|
+
const parts = shortcut
|
|
3891
|
+
.trim()
|
|
3892
|
+
.split('+')
|
|
3893
|
+
.map((part) => part.trim())
|
|
3894
|
+
.filter(Boolean);
|
|
3895
|
+
if (parts.length === 0) {
|
|
3896
|
+
return shortcut.trim();
|
|
3897
|
+
}
|
|
3898
|
+
const key = normalizeShortcutToken(parts.pop() ?? '');
|
|
3899
|
+
const modifiers = parts.map((part) => part.toLowerCase());
|
|
3900
|
+
return modifiers.length ? `${modifiers.join('+')}+${key}` : key;
|
|
3901
|
+
}
|
|
3902
|
+
/**
|
|
3903
|
+
* Whether a shortcut should fire given the current focus target.
|
|
3904
|
+
* Enter without modifiers is suppressed in multiline / rich-text fields.
|
|
3905
|
+
*/
|
|
3906
|
+
function shouldTriggerDialogActionShortcut(event, shortcut) {
|
|
3907
|
+
const parsed = parseDialogActionShortcut(shortcut);
|
|
3908
|
+
const isPlainEnter = parsed.key === 'enter' && !parsed.ctrl && !parsed.shift && !parsed.alt && !parsed.meta;
|
|
3909
|
+
if (!isPlainEnter) {
|
|
3910
|
+
return true;
|
|
3911
|
+
}
|
|
3912
|
+
return !isKeyboardTargetMultilineEditable(event.target);
|
|
3913
|
+
}
|
|
3914
|
+
function normalizeShortcutKey(event) {
|
|
3915
|
+
if (event.key === 'Enter') {
|
|
3916
|
+
return 'enter';
|
|
3917
|
+
}
|
|
3918
|
+
if (event.key === 'Escape') {
|
|
3919
|
+
return 'escape';
|
|
3920
|
+
}
|
|
3921
|
+
if (event.key === ' ') {
|
|
3922
|
+
return 'space';
|
|
3923
|
+
}
|
|
3924
|
+
if (event.key.length === 1) {
|
|
3925
|
+
return event.key.toLowerCase();
|
|
3926
|
+
}
|
|
3927
|
+
const codeMatch = event.code.match(/^Key([A-Z])$/);
|
|
3928
|
+
if (codeMatch) {
|
|
3929
|
+
return codeMatch[1].toLowerCase();
|
|
3930
|
+
}
|
|
3931
|
+
const digitMatch = event.code.match(/^Digit([0-9])$/);
|
|
3932
|
+
if (digitMatch) {
|
|
3933
|
+
return digitMatch[1];
|
|
3934
|
+
}
|
|
3935
|
+
return event.key.toLowerCase();
|
|
3936
|
+
}
|
|
3937
|
+
function isKeyboardTargetMultilineEditable(target) {
|
|
3938
|
+
if (!(target instanceof HTMLElement)) {
|
|
3939
|
+
return false;
|
|
3940
|
+
}
|
|
3941
|
+
if (target.isContentEditable) {
|
|
3942
|
+
return true;
|
|
3943
|
+
}
|
|
3944
|
+
const textarea = target.closest('textarea');
|
|
3945
|
+
if (textarea && !textarea.readOnly && !textarea.disabled) {
|
|
3946
|
+
return true;
|
|
3947
|
+
}
|
|
3948
|
+
const richText = target.closest('[contenteditable="true"]');
|
|
3949
|
+
return richText instanceof HTMLElement;
|
|
3950
|
+
}
|
|
3951
|
+
//#endregion
|
|
3952
|
+
|
|
3953
|
+
//#region ---- Popup Footer Keyboard Shortcuts ----
|
|
3954
|
+
/**
|
|
3955
|
+
* Registers popup footer shortcuts at modal priority so they win over page bindings and stop bubbling.
|
|
3956
|
+
*/
|
|
3957
|
+
function registerPopupFooterKeyboardShortcuts(options) {
|
|
3958
|
+
const group = '@general:keyboard-shortcuts.groups.dialog';
|
|
3959
|
+
const scope = options.scope ?? group;
|
|
3960
|
+
const shortcuts = [];
|
|
3961
|
+
for (const action of options.actions) {
|
|
3962
|
+
if (action.hidden?.()) {
|
|
3963
|
+
continue;
|
|
3964
|
+
}
|
|
3965
|
+
const chords = action.shortcuts !== undefined
|
|
3966
|
+
? resolveConfiguredFooterActionShortcuts(action.command, action.shortcuts)
|
|
3967
|
+
: resolveConfiguredFooterActionShortcuts(action.command, undefined);
|
|
3968
|
+
if (!chords?.length) {
|
|
3969
|
+
continue;
|
|
3970
|
+
}
|
|
3971
|
+
for (const shortcut of chords) {
|
|
3972
|
+
shortcuts.push({
|
|
3973
|
+
keys: [normalizeDialogActionShortcutChord(shortcut)],
|
|
3974
|
+
title: action.title,
|
|
3975
|
+
group,
|
|
3976
|
+
allowInEditableFields: isDialogShortcutAllowedInEditableField(shortcut),
|
|
3977
|
+
when: () => {
|
|
3978
|
+
if (options.when && !options.when()) {
|
|
3979
|
+
return false;
|
|
3980
|
+
}
|
|
3981
|
+
if (action.hidden?.()) {
|
|
3982
|
+
return false;
|
|
3983
|
+
}
|
|
3984
|
+
if (action.disabled?.()) {
|
|
3985
|
+
return false;
|
|
3986
|
+
}
|
|
3987
|
+
return true;
|
|
3988
|
+
},
|
|
3989
|
+
handler: () => {
|
|
3990
|
+
void action.handler();
|
|
3991
|
+
},
|
|
3992
|
+
});
|
|
3993
|
+
}
|
|
3994
|
+
}
|
|
3995
|
+
options.registry.register({
|
|
3996
|
+
owner: options.owner,
|
|
3997
|
+
elementRef: options.elementRef,
|
|
3998
|
+
id: options.id,
|
|
3999
|
+
priority: AXPKeyboardShortcutPriority.Modal,
|
|
4000
|
+
scope,
|
|
4001
|
+
when: options.when,
|
|
4002
|
+
shortcuts,
|
|
4003
|
+
});
|
|
4004
|
+
}
|
|
4005
|
+
/**
|
|
4006
|
+
* Binds footer shortcuts for AXPopupService-hosted components. Call from the popup constructor.
|
|
4007
|
+
*/
|
|
4008
|
+
function setupPopupFooterKeyboardShortcuts(getActions, config) {
|
|
4009
|
+
const destroyRef = inject(DestroyRef);
|
|
4010
|
+
const elementRef = inject(ElementRef);
|
|
4011
|
+
const registry = inject(AXPKeyboardShortcutRegistry);
|
|
4012
|
+
const id = config?.id ?? 'popup-footer';
|
|
4013
|
+
effect(() => {
|
|
4014
|
+
getActions();
|
|
4015
|
+
config?.when?.();
|
|
4016
|
+
untracked(() => {
|
|
4017
|
+
registerPopupFooterKeyboardShortcuts({
|
|
4018
|
+
registry,
|
|
4019
|
+
owner: destroyRef,
|
|
4020
|
+
elementRef,
|
|
4021
|
+
id,
|
|
4022
|
+
scope: config?.scope,
|
|
4023
|
+
when: config?.when,
|
|
4024
|
+
actions: getActions(),
|
|
4025
|
+
});
|
|
4026
|
+
});
|
|
4027
|
+
});
|
|
4028
|
+
}
|
|
4029
|
+
//#endregion
|
|
4030
|
+
|
|
4031
|
+
/** Global overlay flag + task count ({@link AXPLayoutThemeService}). */
|
|
4032
|
+
const OVERLAY_LAYOUT_TRACE_NS = 'OVERLAY-LAYOUT';
|
|
4033
|
+
/** Root shell loading dialog sync ({@link AXPRootLayoutComponent}). */
|
|
4034
|
+
const OVERLAY_ROOT_LAYOUT_TRACE_NS = 'OVERLAY-ROOT-LAYOUT';
|
|
4035
|
+
/** Details view page load and overlay release orchestration. */
|
|
4036
|
+
const OVERLAY_DETAILS_VIEW_TRACE_NS = 'OVERLAY-DETAILS-VIEW';
|
|
4037
|
+
/** Widget tree mount / settle polling ({@link AXPWidgetContainerComponent}). */
|
|
4038
|
+
const OVERLAY_WIDGET_SETTLE_TRACE_NS = 'OVERLAY-WIDGET-SETTLE';
|
|
4039
|
+
|
|
3358
4040
|
//#region ---- Imports ----
|
|
3359
4041
|
//#endregion
|
|
3360
4042
|
|
|
@@ -3813,6 +4495,112 @@ function searchInMultiLanguageString(value, query, locale) {
|
|
|
3813
4495
|
}
|
|
3814
4496
|
//#endregion
|
|
3815
4497
|
|
|
4498
|
+
//#region ---- Imports ----
|
|
4499
|
+
//#endregion
|
|
4500
|
+
//#region ---- Constants ----
|
|
4501
|
+
const DEFAULT_IDLE_TIMEOUT_MS = 2_000;
|
|
4502
|
+
const DEFAULT_MIN_IDLE_MS = 16;
|
|
4503
|
+
//#endregion
|
|
4504
|
+
//#region ---- Service ----
|
|
4505
|
+
/**
|
|
4506
|
+
* Schedules low-priority work during browser idle periods.
|
|
4507
|
+
* Pauses when the tab is hidden or when explicitly paused (e.g. user-initiated seeding).
|
|
4508
|
+
*/
|
|
4509
|
+
class AXPIdleSchedulerService {
|
|
4510
|
+
constructor() {
|
|
4511
|
+
//#region ---- State ----
|
|
4512
|
+
this.pausedState = signal(false, ...(ngDevMode ? [{ debugName: "pausedState" }] : /* istanbul ignore next */ []));
|
|
4513
|
+
this.paused = this.pausedState.asReadonly();
|
|
4514
|
+
this.queue = [];
|
|
4515
|
+
this.running = false;
|
|
4516
|
+
}
|
|
4517
|
+
//#endregion
|
|
4518
|
+
//#region ---- Public API ----
|
|
4519
|
+
pause() {
|
|
4520
|
+
this.pausedState.set(true);
|
|
4521
|
+
}
|
|
4522
|
+
resume() {
|
|
4523
|
+
this.pausedState.set(false);
|
|
4524
|
+
void this.drainQueue();
|
|
4525
|
+
}
|
|
4526
|
+
/**
|
|
4527
|
+
* Resolves after the browser reports idle time, or after timeoutMs (whichever comes first).
|
|
4528
|
+
*/
|
|
4529
|
+
waitForIdle(options) {
|
|
4530
|
+
const timeoutMs = options?.timeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS;
|
|
4531
|
+
const minIdleMs = options?.minIdleMs ?? DEFAULT_MIN_IDLE_MS;
|
|
4532
|
+
if (this.pausedState() || typeof document !== 'undefined' && document.visibilityState === 'hidden') {
|
|
4533
|
+
return delay(timeoutMs);
|
|
4534
|
+
}
|
|
4535
|
+
return new Promise((resolve) => {
|
|
4536
|
+
let settled = false;
|
|
4537
|
+
const finish = () => {
|
|
4538
|
+
if (settled)
|
|
4539
|
+
return;
|
|
4540
|
+
settled = true;
|
|
4541
|
+
resolve();
|
|
4542
|
+
};
|
|
4543
|
+
const timeoutId = setTimeout(finish, timeoutMs);
|
|
4544
|
+
if (typeof requestIdleCallback === 'function') {
|
|
4545
|
+
requestIdleCallback(() => {
|
|
4546
|
+
clearTimeout(timeoutId);
|
|
4547
|
+
finish();
|
|
4548
|
+
}, { timeout: timeoutMs });
|
|
4549
|
+
}
|
|
4550
|
+
else {
|
|
4551
|
+
clearTimeout(timeoutId);
|
|
4552
|
+
setTimeout(finish, minIdleMs);
|
|
4553
|
+
}
|
|
4554
|
+
});
|
|
4555
|
+
}
|
|
4556
|
+
/** Enqueues async work processed sequentially during idle slices. */
|
|
4557
|
+
enqueue(task, options) {
|
|
4558
|
+
if (options?.key) {
|
|
4559
|
+
this.queue = this.queue.filter((item) => item.key !== options.key);
|
|
4560
|
+
}
|
|
4561
|
+
this.queue.push({ key: options?.key, task });
|
|
4562
|
+
void this.drainQueue();
|
|
4563
|
+
}
|
|
4564
|
+
//#endregion
|
|
4565
|
+
//#region ---- Private ----
|
|
4566
|
+
async drainQueue() {
|
|
4567
|
+
if (this.running || this.pausedState()) {
|
|
4568
|
+
return;
|
|
4569
|
+
}
|
|
4570
|
+
this.running = true;
|
|
4571
|
+
try {
|
|
4572
|
+
while (this.queue.length > 0 && !this.pausedState()) {
|
|
4573
|
+
const next = this.queue.shift();
|
|
4574
|
+
if (!next)
|
|
4575
|
+
break;
|
|
4576
|
+
await this.waitForIdle();
|
|
4577
|
+
try {
|
|
4578
|
+
await next.task();
|
|
4579
|
+
}
|
|
4580
|
+
catch (err) {
|
|
4581
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4582
|
+
console.error('[AXPIdleScheduler] Task failed:', message);
|
|
4583
|
+
}
|
|
4584
|
+
}
|
|
4585
|
+
}
|
|
4586
|
+
finally {
|
|
4587
|
+
this.running = false;
|
|
4588
|
+
if (this.queue.length > 0 && !this.pausedState()) {
|
|
4589
|
+
void this.drainQueue();
|
|
4590
|
+
}
|
|
4591
|
+
}
|
|
4592
|
+
}
|
|
4593
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPIdleSchedulerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
4594
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPIdleSchedulerService, providedIn: 'root' }); }
|
|
4595
|
+
}
|
|
4596
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPIdleSchedulerService, decorators: [{
|
|
4597
|
+
type: Injectable,
|
|
4598
|
+
args: [{ providedIn: 'root' }]
|
|
4599
|
+
}] });
|
|
4600
|
+
|
|
4601
|
+
//#region ---- Idle scheduler types ----
|
|
4602
|
+
//#endregion
|
|
4603
|
+
|
|
3816
4604
|
//#region ---- Tag Types ----
|
|
3817
4605
|
//#endregion
|
|
3818
4606
|
|
|
@@ -4066,67 +4854,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
4066
4854
|
* - Review = 'review' - Review content
|
|
4067
4855
|
* - Recommend = 'recommend' - Recommend content
|
|
4068
4856
|
*/
|
|
4069
|
-
var AXPSystemActionType;
|
|
4070
|
-
(function (AXPSystemActionType) {
|
|
4071
|
-
AXPSystemActionType["View"] = "view";
|
|
4072
|
-
AXPSystemActionType["Create"] = "create";
|
|
4073
|
-
AXPSystemActionType["Update"] = "update";
|
|
4074
|
-
AXPSystemActionType["Delete"] = "delete";
|
|
4075
|
-
AXPSystemActionType["Approve"] = "approve";
|
|
4076
|
-
AXPSystemActionType["Reject"] = "reject";
|
|
4077
|
-
AXPSystemActionType["Submit"] = "submit";
|
|
4078
|
-
AXPSystemActionType["Export"] = "export";
|
|
4079
|
-
AXPSystemActionType["Import"] = "import";
|
|
4080
|
-
AXPSystemActionType["Print"] = "print";
|
|
4081
|
-
AXPSystemActionType["Assign"] = "assign";
|
|
4082
|
-
AXPSystemActionType["Lock"] = "lock";
|
|
4083
|
-
AXPSystemActionType["Unlock"] = "unlock";
|
|
4084
|
-
AXPSystemActionType["Share"] = "share";
|
|
4085
|
-
AXPSystemActionType["Configure"] = "configure";
|
|
4086
|
-
AXPSystemActionType["Reorder"] = "reorder";
|
|
4087
|
-
AXPSystemActionType["Preview"] = "preview";
|
|
4088
|
-
AXPSystemActionType["Duplicate"] = "duplicate";
|
|
4089
|
-
AXPSystemActionType["Archive"] = "archive";
|
|
4090
|
-
AXPSystemActionType["Publish"] = "publish";
|
|
4091
|
-
AXPSystemActionType["Unpublish"] = "unpublish";
|
|
4092
|
-
AXPSystemActionType["Upload"] = "upload";
|
|
4093
|
-
AXPSystemActionType["Download"] = "download";
|
|
4094
|
-
AXPSystemActionType["Copy"] = "copy";
|
|
4095
|
-
AXPSystemActionType["Move"] = "move";
|
|
4096
|
-
AXPSystemActionType["Rename"] = "rename";
|
|
4097
|
-
AXPSystemActionType["Restore"] = "restore";
|
|
4098
|
-
AXPSystemActionType["Manage"] = "manage";
|
|
4099
|
-
AXPSystemActionType["Info"] = "info";
|
|
4100
|
-
AXPSystemActionType["Confirm"] = "confirm";
|
|
4101
|
-
AXPSystemActionType["Design"] = "design";
|
|
4102
|
-
AXPSystemActionType["VersionHistory"] = "version-history";
|
|
4103
|
-
AXPSystemActionType["Compare"] = "compare";
|
|
4104
|
-
AXPSystemActionType["Comments"] = "comments";
|
|
4105
|
-
AXPSystemActionType["Sign"] = "sign";
|
|
4106
|
-
AXPSystemActionType["Setup"] = "setup";
|
|
4107
|
-
AXPSystemActionType["Send"] = "send";
|
|
4108
|
-
AXPSystemActionType["Report"] = "report";
|
|
4109
|
-
AXPSystemActionType["Sent"] = "sent";
|
|
4110
|
-
AXPSystemActionType["Review"] = "review";
|
|
4111
|
-
AXPSystemActionType["Generate"] = "generate";
|
|
4112
|
-
AXPSystemActionType["Refresh"] = "refresh";
|
|
4113
|
-
AXPSystemActionType["Reload"] = "reload";
|
|
4114
|
-
AXPSystemActionType["Search"] = "search";
|
|
4115
|
-
AXPSystemActionType["Filter"] = "filter";
|
|
4116
|
-
AXPSystemActionType["Sort"] = "sort";
|
|
4117
|
-
AXPSystemActionType["Start"] = "start";
|
|
4118
|
-
AXPSystemActionType["Stop"] = "stop";
|
|
4119
|
-
AXPSystemActionType["Pause"] = "pause";
|
|
4120
|
-
AXPSystemActionType["Cancel"] = "cancel";
|
|
4121
|
-
AXPSystemActionType["Close"] = "close";
|
|
4122
|
-
AXPSystemActionType["Complete"] = "complete";
|
|
4123
|
-
AXPSystemActionType["Save"] = "save";
|
|
4124
|
-
AXPSystemActionType["SaveAs"] = "save-as";
|
|
4125
|
-
AXPSystemActionType["Sync"] = "sync";
|
|
4126
|
-
AXPSystemActionType["Reset"] = "reset";
|
|
4127
|
-
AXPSystemActionType["Clear"] = "clear";
|
|
4128
|
-
AXPSystemActionType["Distribution"] = "distribution";
|
|
4129
|
-
})(AXPSystemActionType || (AXPSystemActionType = {}));
|
|
4130
4857
|
const i18n = (key) => `@general:actions.${key}`;
|
|
4131
4858
|
const AXPSystemActions = Object.freeze({
|
|
4132
4859
|
View: {
|
|
@@ -4886,27 +5613,7 @@ function getActionButton(actionType) {
|
|
|
4886
5613
|
};
|
|
4887
5614
|
}
|
|
4888
5615
|
|
|
4889
|
-
|
|
4890
|
-
let keys = [];
|
|
4891
|
-
for (const key in obj) {
|
|
4892
|
-
if (typeof obj[key] === 'object' && obj[key] !== null && !Array.isArray(obj[key])) {
|
|
4893
|
-
keys = [...keys, ...getNestedKeys(obj[key], prefix + key + '.')];
|
|
4894
|
-
}
|
|
4895
|
-
else {
|
|
4896
|
-
keys.push(prefix + key);
|
|
4897
|
-
}
|
|
4898
|
-
}
|
|
4899
|
-
return keys;
|
|
4900
|
-
}
|
|
4901
|
-
/** Normalizes definition categories to an array for iteration and serialization. */
|
|
4902
|
-
function normalizeDefinitionCategories(categories) {
|
|
4903
|
-
if (categories == null) {
|
|
4904
|
-
return [];
|
|
4905
|
-
}
|
|
4906
|
-
return Array.isArray(categories) ? categories : [categories];
|
|
4907
|
-
}
|
|
4908
|
-
|
|
4909
|
-
//#region ---- Imports ----
|
|
5616
|
+
//#region ---- Core-only data types (CDK-coupled) ----
|
|
4910
5617
|
//#endregion
|
|
4911
5618
|
|
|
4912
5619
|
function applySystemActionDefault(action, type) {
|
|
@@ -4939,39 +5646,6 @@ class AXPIconLogoConfig {
|
|
|
4939
5646
|
}
|
|
4940
5647
|
}
|
|
4941
5648
|
|
|
4942
|
-
var AXPPlatformScope;
|
|
4943
|
-
(function (AXPPlatformScope) {
|
|
4944
|
-
AXPPlatformScope["Platform"] = "P";
|
|
4945
|
-
AXPPlatformScope["Tenant"] = "T";
|
|
4946
|
-
AXPPlatformScope["User"] = "U";
|
|
4947
|
-
})(AXPPlatformScope || (AXPPlatformScope = {}));
|
|
4948
|
-
;
|
|
4949
|
-
function resolvePlatformScopeKey(name) {
|
|
4950
|
-
const scopeMap = {
|
|
4951
|
-
platform: AXPPlatformScope.Platform,
|
|
4952
|
-
tenant: AXPPlatformScope.Tenant,
|
|
4953
|
-
user: AXPPlatformScope.User,
|
|
4954
|
-
};
|
|
4955
|
-
return scopeMap[name.toLowerCase()] ?? AXPPlatformScope.User;
|
|
4956
|
-
}
|
|
4957
|
-
function resolvePlatformScopeName(scope) {
|
|
4958
|
-
const scopeMap = {
|
|
4959
|
-
P: 'platform',
|
|
4960
|
-
T: 'tenant',
|
|
4961
|
-
U: 'user',
|
|
4962
|
-
};
|
|
4963
|
-
return scopeMap[scope] ?? 'user';
|
|
4964
|
-
}
|
|
4965
|
-
|
|
4966
|
-
var AXPExportTemplateToken;
|
|
4967
|
-
(function (AXPExportTemplateToken) {
|
|
4968
|
-
AXPExportTemplateToken["Date"] = "{date}";
|
|
4969
|
-
AXPExportTemplateToken["Time"] = "{time}";
|
|
4970
|
-
AXPExportTemplateToken["User"] = "{user}";
|
|
4971
|
-
AXPExportTemplateToken["ReportTitle"] = "{title}";
|
|
4972
|
-
AXPExportTemplateToken["UUID"] = "{uuid}";
|
|
4973
|
-
})(AXPExportTemplateToken || (AXPExportTemplateToken = {}));
|
|
4974
|
-
|
|
4975
5649
|
class AXPCountdownPipe {
|
|
4976
5650
|
constructor() {
|
|
4977
5651
|
this.calendarService = inject(AXCalendarService);
|
|
@@ -5742,5 +6416,5 @@ function generateKebabCase(title) {
|
|
|
5742
6416
|
* Generated bundle index. Do not edit.
|
|
5743
6417
|
*/
|
|
5744
6418
|
|
|
5745
|
-
export { AXHighlightService, AXPActivityLogProvider, AXPActivityLogService, AXPAppStartUpProvider, AXPAppStartUpService, AXPBroadcastEventService, AXPCatalogScopeDefinitionProviderService, AXPCatalogScopeDefinitionsDataSourceDefinition, AXPColorPaletteProvider, AXPColorPaletteService, AXPColumnWidthService, AXPComponentLogoConfig, AXPComponentSlot, AXPComponentSlotDirective, AXPComponentSlotModule, AXPComponentSlotRegistryService, AXPContentCheckerDirective, AXPContextChangeEvent, AXPContextDefinitionProviderService, AXPContextStore, AXPCountdownPipe, AXPDataGenerator, AXPDataSourceDefinitionProviderService, AXPDblClickDirective, AXPDefaultColorPalettesProvider, AXPDeviceService, AXPDeviceType, AXPDistributedEventListenerService, AXPElementDataDirective,
|
|
6419
|
+
export { AXHighlightService, AXPActivityLogProvider, AXPActivityLogService, AXPAppStartUpProvider, AXPAppStartUpService, AXPBroadcastEventService, AXPCatalogScopeDefinitionProviderService, AXPCatalogScopeDefinitionsDataSourceDefinition, AXPColorPaletteProvider, AXPColorPaletteService, AXPColumnWidthService, AXPComponentLogoConfig, AXPComponentSlot, AXPComponentSlotDirective, AXPComponentSlotModule, AXPComponentSlotRegistryService, AXPContentCheckerDirective, AXPContextChangeEvent, AXPContextDefinitionProviderService, AXPContextEvalFactory, AXPContextStore, AXPCountdownPipe, AXPDataGenerator, AXPDataSourceDefinitionProviderService, AXPDblClickDirective, AXPDebugService, AXPDefaultColorPalettesProvider, AXPDeviceService, AXPDeviceType, AXPDistributedEventListenerService, AXPElementDataDirective, AXPExpressionEvaluatorScopeProviderContext, AXPExpressionEvaluatorScopeProviderService, AXPExpressionEvaluatorService, AXPFeatureDefinitionProviderContext, AXPGridLayoutDirective, AXPHookService, AXPIconLogoConfig, AXPIdleSchedulerService, AXPImageUrlLogoConfig, AXPKeyboardShortcutPriority, AXPKeyboardShortcutRegistry, AXPModuleManifestModule, AXPModuleManifestRegistry, AXPModuleManifestsDataSourceDefinition, AXPScreenSize, 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, DEFAULT_CANCEL_DIALOG_ACTION_SHORTCUTS, DEFAULT_SUBMIT_DIALOG_ACTION_SHORTCUTS, FORM_DIRTY_TRACE_NS, OVERLAY_DETAILS_VIEW_TRACE_NS, OVERLAY_LAYOUT_TRACE_NS, OVERLAY_ROOT_LAYOUT_TRACE_NS, OVERLAY_WIDGET_SETTLE_TRACE_NS, applyContextEvalPipes, applyFilterArray, applyPagination, applyQueryArray, applySortArray, applySystemActionDefault, buildLocaleTextMapValue, captureFormContextBaseline, chordToKbdItemKeys, cleanDeep, coerceUnknownToBoolean, coerceUnknownToDate, coerceUnknownToFiniteNumber, coerceUnknownToTrimmedString, compareMultiLanguageStrings, containsHtmlMarkup, contextEvalWithPipes, createProviderWithInjectionContext, defaultColumnWidthProvider, extractNestedFieldsWildcard, extractTextFromHtml, extractValue, findOverlayContainerAncestor, formatKeyboardShortcutChord, formatKeyboardShortcutChords, generateKebabCase, getActionButton, getChangedPaths, getDetailedChanges, getEnumValues, getNestedVisibleOverlayPanes, getPrimaryKeyboardShortcutChord, getSmart, getSystemActions, getTopVisibleOverlayContainer, getVisibleAnchoredOverlayPanes, getVisibleOverlayContainers, hasForegroundOverlayLayer, isDialogShortcutAllowedInEditableField, isFormContextDirty, isFormValueEqual, isHorizontalDirectionalShortcutKey, isKeyboardTargetInsideEditableField, isLocaleStringMap, isMacPlatform, isSelectionValueEqual, isVisibleOverlayElement, matchesDialogActionShortcut, matchesKeyboardShortcutChord, mirrorHorizontalDirectionalKey, normalizeContextEvalPipes, normalizeDialogActionShortcutChord, normalizeExpressionTemplate, normalizeKeyboardShortcut, normalizeKeyboardShortcuts, objectKeyValueTransforms, parseDialogActionShortcut, parseKeyboardShortcutChord, provideLazyProvider, registerPopupFooterKeyboardShortcuts, resolveActionLook, resolveConfiguredFooterActionShortcuts, resolveDialogActionShortcuts, resolveDisplayShortcutChord, resolveEffectiveDirectionBehavior, resolveSemanticDirectionalKey, searchInMultiLanguageString, setSmart, setupPopupFooterKeyboardShortcuts, shouldDeferEscapeToOpenOverlay, shouldRestoreSavedMultiLanguageShape, shouldTriggerDialogActionShortcut, shouldUseLocaleMapShape, sortByMultiLanguageString, summarizeFormContextDirtyDiff, unwrapValueProperty };
|
|
5746
6420
|
//# sourceMappingURL=acorex-platform-core.mjs.map
|