@acorex/platform 20.9.6 → 20.9.8

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.
@@ -55,7 +55,7 @@ import { AXDataTableModule } from '@acorex/components/data-table';
55
55
  import * as i3$4 from '@acorex/components/dropdown-button';
56
56
  import { AXDropdownButtonModule } from '@acorex/components/dropdown-button';
57
57
  import { AXBasePageComponent } from '@acorex/components/page';
58
- import { setupPopupFooterKeyboardShortcuts, AXPExpressionEvaluatorService, AXPColumnWidthService, AXPKeyboardShortcutRegistry, chordToKbdItemKeys, AXPDeviceService, AXPImageUrlLogoConfig, AXPComponentLogoConfig, AXPIconLogoConfig } from '@acorex/platform/core';
58
+ import { setupPopupFooterKeyboardShortcuts, AXPExpressionEvaluatorService, AXPColumnWidthService, AXPKeyboardShortcutRegistry, AXPDataSourceQueryService, chordToKbdItemKeys, AXPDeviceService, AXPImageUrlLogoConfig, AXPComponentLogoConfig, AXPIconLogoConfig } from '@acorex/platform/core';
59
59
  import { AXPopupService } from '@acorex/components/popup';
60
60
  import { moveItemInArray as moveItemInArray$1, AXDragDirective, AXDragHandleDirective, AXDropListDirective } from '@acorex/cdk/drag-drop';
61
61
  import * as i2$5 from '@acorex/components/select-box';
@@ -69,6 +69,7 @@ import { AXTagBoxModule } from '@acorex/components/tag-box';
69
69
  import { AXCalendarService } from '@acorex/core/date-time';
70
70
  import { AXPFilterOperatorMiddlewareService, AXPSettingsService, ALL_DEFAULT_OPERATORS, AXPFileStorageService, withDefaultMultiLanguageOnWidgetProperty, AXPDefaultMultiLanguageConfigService, resolveSearchableText, AXP_PLATFORM_CONFIG_TOKEN } from '@acorex/platform/common';
71
71
  import { resolveEntityListFilterQueryField, resolveEntityListFilterContextField } from '@acorex/platform/layout/entity-contracts';
72
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
72
73
  import { Subject, filter, BehaviorSubject, timer, takeUntil } from 'rxjs';
73
74
  import * as i1$7 from '@acorex/components/image-editor';
74
75
  import { AXImageEditorContainerComponent, AXImageEditorModule } from '@acorex/components/image-editor';
@@ -2887,6 +2888,7 @@ class AXPQueryFiltersComponent {
2887
2888
  this.shortcutRegistry = inject(AXPKeyboardShortcutRegistry);
2888
2889
  this.destroyRef = inject(DestroyRef);
2889
2890
  this.elementRef = inject((ElementRef));
2891
+ this.dataSourceQueryService = inject(AXPDataSourceQueryService);
2890
2892
  this.filterFocusShortcutKeys = chordToKbdItemKeys('ctrl+f');
2891
2893
  this.filtersDefinitions = input([], ...(ngDevMode ? [{ debugName: "filtersDefinitions" }] : /* istanbul ignore next */ []));
2892
2894
  this.initialFilters = input([], ...(ngDevMode ? [{ debugName: "initialFilters" }] : /* istanbul ignore next */ []));
@@ -2906,6 +2908,10 @@ class AXPQueryFiltersComponent {
2906
2908
  this.filterContextPath$ = new Subject();
2907
2909
  this.filterContextPathSnapshotReady = false;
2908
2910
  this.previousFilterContextSnapshot = {};
2911
+ /** Committed context entry for the filter being edited; restored when popover closes without Apply. */
2912
+ this.editingContextSnapshot = null;
2913
+ /** Bumped on locale change so filter chip labels re-resolve i18n keys. */
2914
+ this.translationEpoch = signal(0, ...(ngDevMode ? [{ debugName: "translationEpoch" }] : /* istanbul ignore next */ []));
2909
2915
  this.inlineFilters = () => {
2910
2916
  return this.filtersDefinitions().filter((f) => f.filterType.inline &&
2911
2917
  !this.filterOperatorMiddleware
@@ -2931,9 +2937,22 @@ class AXPQueryFiltersComponent {
2931
2937
  }
2932
2938
  }
2933
2939
  if (filter.widget?.type === 'select-editor' || filter.widget?.type === 'select-filter') {
2934
- const dataSource = filter.widget.options?.['dataSource'];
2935
- const textField = filter.widget.options?.['textField'];
2936
- const valueField = filter.widget.options?.['valueField'];
2940
+ const rawDataSource = filter.widget.options?.['dataSource'];
2941
+ const textField = filter.widget.options?.['textField'] ?? 'title';
2942
+ const valueField = filter.widget.options?.['valueField'] ?? 'id';
2943
+ if (typeof rawDataSource === 'string') {
2944
+ const found = await this.dataSourceQueryService.queryByKey(rawDataSource, val, {
2945
+ valueField,
2946
+ });
2947
+ if (Array.isArray(found)) {
2948
+ return found.map((data) => String(get(data, textField) ?? '')).join(', ');
2949
+ }
2950
+ if (found) {
2951
+ return String(get(found, textField) ?? val);
2952
+ }
2953
+ return String(val ?? '');
2954
+ }
2955
+ const dataSource = rawDataSource;
2937
2956
  if (dataSource?.config?.byKey) {
2938
2957
  if (Array.isArray(val)) {
2939
2958
  const results = await Promise.all(val.map((v) => dataSource.config.byKey(v)));
@@ -3076,17 +3095,24 @@ class AXPQueryFiltersComponent {
3076
3095
  this.#updateTagsEffect = effect(() => {
3077
3096
  const filters = this.selectedFilters();
3078
3097
  const context = this.context();
3098
+ const editingField = this.activeFilter()?.field;
3099
+ void this.translationEpoch();
3079
3100
  Promise.all(filters.map(async (filter) => {
3080
- const contextField = this.resolveFilterContextField(filter);
3081
- const val = context[contextField]?.value;
3082
- const displayText = context[contextField]?.displayText;
3101
+ const isEditingThisFilter = editingField === filter.field;
3102
+ const committedEntry = this.getCommittedFilterEntry(filter, context, editingField);
3103
+ const val = committedEntry?.value ?? filter.value;
3104
+ const displayText = isEditingThisFilter ? undefined : committedEntry?.displayText;
3083
3105
  const rawDisplay = displayText != null && displayText !== '' ? displayText : await this.getDisplayValue(filter, val);
3084
- const displayValue = this.formatFilterDisplayValue(rawDisplay);
3106
+ const displayValue = await this.formatFilterDisplayValue(rawDisplay);
3107
+ const operatorType = committedEntry?.operation?.type ?? filter.operator?.type;
3108
+ const operatorKey = ALL_DEFAULT_OPERATORS.find((o) => o.name === operatorType)?.title;
3109
+ const title = await this.translateLabel(filter.title);
3110
+ const operator = operatorKey ? await this.translateLabel(operatorKey) : '';
3085
3111
  return {
3086
3112
  ...filter,
3087
3113
  readOnly: filter.readOnly === true ||
3088
3114
  filter.widget?.options?.['readonly'] === true,
3089
- query: `${this.translate.translateSync(filter.title)} ${this.translate.translateSync(this.getActiveOperator(filter)) || ''} '${displayValue}'`,
3115
+ query: `${title} ${operator} '${displayValue}'`.trim(),
3090
3116
  };
3091
3117
  })).then((results) => {
3092
3118
  this.asyncTags.set(results.filter((r) => !r.hidden));
@@ -3217,6 +3243,9 @@ class AXPQueryFiltersComponent {
3217
3243
  },
3218
3244
  ],
3219
3245
  });
3246
+ this.translate.langChanges$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
3247
+ this.translationEpoch.update((epoch) => epoch + 1);
3248
+ });
3220
3249
  }
3221
3250
  convertQueriesToDefinitions(queries) {
3222
3251
  const definitions = this.filtersDefinitions();
@@ -3262,6 +3291,7 @@ class AXPQueryFiltersComponent {
3262
3291
  if (this.isFilterDisabled(field)) {
3263
3292
  return;
3264
3293
  }
3294
+ this.captureEditingContextSnapshot(field);
3265
3295
  this.activeFilter.set(field);
3266
3296
  this.tagBox()?.inputValue.set('');
3267
3297
  }
@@ -3298,13 +3328,19 @@ class AXPQueryFiltersComponent {
3298
3328
  const contextField = this.resolveFilterContextField(reservedFitler);
3299
3329
  const selectedFiltersFields = this.selectedFilters().map((f) => f.field);
3300
3330
  const context = this.context();
3301
- const appliedValue = context[contextField]?.value ?? context[contextField];
3331
+ const appliedEntry = context[contextField];
3332
+ const appliedValue = appliedEntry?.value ?? appliedEntry;
3333
+ const appliedOperator = appliedEntry?.operation ?? reservedFitler.operator;
3302
3334
  if (selectedFiltersFields.includes(field)) {
3303
- this.selectedFilters.update((prev) => prev.map((f) => (f.field === field ? { ...f, value: appliedValue } : f)));
3335
+ this.selectedFilters.update((prev) => prev.map((f) => f.field === field ? { ...f, value: appliedValue, operator: appliedOperator } : f));
3304
3336
  }
3305
3337
  else {
3306
- this.selectedFilters.update((prev) => [...prev, { ...reservedFitler, value: appliedValue }]);
3338
+ this.selectedFilters.update((prev) => [
3339
+ ...prev,
3340
+ { ...reservedFitler, value: appliedValue, operator: appliedOperator },
3341
+ ]);
3307
3342
  }
3343
+ this.editingContextSnapshot = null;
3308
3344
  this.activeFilter.set(null);
3309
3345
  this.popover()?.close();
3310
3346
  }
@@ -3349,6 +3385,7 @@ class AXPQueryFiltersComponent {
3349
3385
  if (item.field === 'all') {
3350
3386
  return;
3351
3387
  }
3388
+ this.captureEditingContextSnapshot(item);
3352
3389
  this.activeFilter.set(item);
3353
3390
  this.popover()?.open();
3354
3391
  }
@@ -3437,6 +3474,7 @@ class AXPQueryFiltersComponent {
3437
3474
  }
3438
3475
  }
3439
3476
  handlePopoverClosed(e) {
3477
+ this.restoreEditingContextSnapshot();
3440
3478
  this.activeFilter.set(null);
3441
3479
  }
3442
3480
  onPopoverOpened(e) {
@@ -3468,21 +3506,60 @@ class AXPQueryFiltersComponent {
3468
3506
  /**
3469
3507
  * Resolves filter chip labels; translates i18n keys stored in select `displayText` or datasource titles.
3470
3508
  */
3471
- formatFilterDisplayValue(value) {
3509
+ async formatFilterDisplayValue(value) {
3472
3510
  if (value == null || value === '') {
3473
3511
  return '';
3474
3512
  }
3475
3513
  const parts = Array.isArray(value) ? value : [value];
3476
- return parts
3477
- .map((part) => {
3514
+ const labels = await Promise.all(parts.map(async (part) => {
3478
3515
  const text = String(part ?? '').trim();
3479
3516
  if (!text) {
3480
3517
  return '';
3481
3518
  }
3482
- return text.startsWith('@') ? this.translate.translateSync(text) || text : text;
3483
- })
3484
- .filter(Boolean)
3485
- .join(', ');
3519
+ return this.translateLabel(text);
3520
+ }));
3521
+ return labels.filter(Boolean).join(', ');
3522
+ }
3523
+ async translateLabel(text) {
3524
+ const trimmed = text.trim();
3525
+ if (!trimmed) {
3526
+ return '';
3527
+ }
3528
+ if (trimmed.startsWith('@')) {
3529
+ return (await this.translate.translateAsync(trimmed)) || trimmed;
3530
+ }
3531
+ return trimmed;
3532
+ }
3533
+ captureEditingContextSnapshot(filter) {
3534
+ const contextField = this.resolveFilterContextField(filter);
3535
+ const context = this.context();
3536
+ this.editingContextSnapshot = {
3537
+ contextField,
3538
+ entry: cloneDeep(context[contextField]),
3539
+ };
3540
+ }
3541
+ restoreEditingContextSnapshot() {
3542
+ const snapshot = this.editingContextSnapshot;
3543
+ if (!snapshot) {
3544
+ return;
3545
+ }
3546
+ const { contextField, entry } = snapshot;
3547
+ this.context.update((prev) => ({
3548
+ ...prev,
3549
+ [contextField]: cloneDeep(entry),
3550
+ }));
3551
+ this.editingContextSnapshot = null;
3552
+ }
3553
+ /**
3554
+ * Returns the committed filter context entry (snapshot while editing, live context otherwise).
3555
+ */
3556
+ getCommittedFilterEntry(filter, context, editingField) {
3557
+ const contextField = this.resolveFilterContextField(filter);
3558
+ const snapshot = this.editingContextSnapshot;
3559
+ if (editingField === filter.field && snapshot?.contextField === contextField) {
3560
+ return snapshot.entry;
3561
+ }
3562
+ return context[contextField];
3486
3563
  }
3487
3564
  createExpressionScope(filtersContext) {
3488
3565
  const base = (filtersContext ?? {});