@acorex/platform 20.9.39 → 20.9.40

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.
@@ -24,7 +24,7 @@ import { AXButtonModule } from '@acorex/components/button';
24
24
  import * as i3 from '@acorex/components/skeleton';
25
25
  import { AXSkeletonModule } from '@acorex/components/skeleton';
26
26
  import * as i2 from '@acorex/platform/layout/widget-core';
27
- import { AXPWidgetCoreModule, AXPWidgetRegistryService, AXPColumnWidgetComponent, AXPValueWidgetComponent, AXPWidgetRendererDirective, AXPBaseWidgetComponent, WIDGET_CASCADE_TRACE_NS, AXP_WIDGETS_ADVANCE_SUB_MEDIA, AXP_WIDGETS_ADVANCE_CATEGORY, AXP_WIDGETS_EDITOR_CATEGORY, AXP_WIDGET_DEFINITION_PROVIDER } from '@acorex/platform/layout/widget-core';
27
+ import { AXPWidgetCoreModule, AXPWidgetRegistryService, AXPColumnWidgetComponent, AXPValueWidgetComponent, AXPWidgetRendererDirective, AXPBaseWidgetComponent, WIDGET_CASCADE_TRACE_NS, AXP_WIDGETS_EDITOR_CATEGORY, AXP_WIDGETS_ADVANCE_SUB_MEDIA, AXP_WIDGETS_ADVANCE_CATEGORY, AXP_WIDGET_DEFINITION_PROVIDER } from '@acorex/platform/layout/widget-core';
28
28
  import { AXPCommandService, AXPQueryService, AXPQueryExecutor, AXPCommandExecutor, provideCommandSetups, provideQuerySetups, AXPCommandRegistry, AXPQueryRegistry } from '@acorex/platform/runtime';
29
29
  import * as i5 from '@angular/common';
30
30
  import { CommonModule, DOCUMENT, AsyncPipe, NgClass } from '@angular/common';
@@ -12183,6 +12183,8 @@ class AXPEntityDefinitionProviderWidgetEditComponent extends AXPProviderSelectWi
12183
12183
  super(...arguments);
12184
12184
  //#region ---- Services & Dependencies ----
12185
12185
  this.entityRegistry = inject(AXPEntityDefinitionRegistryService);
12186
+ this.entityResolver = inject(AXPEntityResolver);
12187
+ this.translationService = inject(AXTranslationService);
12186
12188
  /**
12187
12189
  * Last applied module filter (from context) to avoid redundant reloads.
12188
12190
  */
@@ -12221,18 +12223,26 @@ class AXPEntityDefinitionProviderWidgetEditComponent extends AXPProviderSelectWi
12221
12223
  * - `valueFormat`: `fullKey` (`module.name`, default) or `entityName` (short name only, for menus that store `entityListRef.entity`).
12222
12224
  */
12223
12225
  async loadDataSourceRows() {
12224
- const entities = this.entityRegistry.getAll();
12226
+ const entries = await this.entityResolver.listAll();
12225
12227
  const filterModuleField = this.options()['filterModuleField'];
12226
12228
  const valueFormat = this.options()['valueFormat'] ?? 'fullKey';
12227
- let list = entities;
12229
+ let list = entries;
12228
12230
  if (filterModuleField) {
12229
12231
  const mod = this.readModuleFromContext(filterModuleField);
12230
- list = mod ? entities.filter((e) => e.module === mod) : [];
12232
+ list = mod ? entries.filter((e) => e.module === mod) : [];
12231
12233
  }
12232
- return list.map((e) => ({
12233
- value: valueFormat === 'entityName' ? e.name : `${e.module}.${e.name}`,
12234
- title: `${e.title} (${e.module}.${e.name})`,
12235
- }));
12234
+ const locale = this.translationService.getActiveLang();
12235
+ const registeredByKey = new Map(this.entityRegistry.getAll().map((e) => [`${e.module}.${e.name}`, e]));
12236
+ return list.map((e) => {
12237
+ const cached = registeredByKey.get(e.fullName);
12238
+ const resolvedTitle = cached
12239
+ ? resolveSearchableText(cached.title, locale, this.translationService).trim() || e.name
12240
+ : e.name;
12241
+ return {
12242
+ value: valueFormat === 'entityName' ? e.name : e.fullName,
12243
+ title: `${resolvedTitle} (${e.fullName})`,
12244
+ };
12245
+ });
12236
12246
  }
12237
12247
  //#endregion
12238
12248
  //#region ---- Refresh (e.g. when sibling module field changes) ----
@@ -17404,7 +17414,7 @@ const AXPLookupWidget = {
17404
17414
  name: 'lookup-editor',
17405
17415
  title: 'Lookup',
17406
17416
  categories: [],
17407
- groups: [AXPWidgetGroupEnum.EntityWidget],
17417
+ groups: [AXPWidgetGroupEnum.EntityWidget, AXPWidgetGroupEnum.FormElement],
17408
17418
  defaultFilterWidgetName: 'lookup-filter',
17409
17419
  type: 'editor',
17410
17420
  icon: 'fa-light fa-list-radio',
@@ -17435,7 +17445,25 @@ const AXPLookupWidget = {
17435
17445
  interface: {
17436
17446
  name: 'entity',
17437
17447
  path: 'options.entity',
17438
- type: AXPWidgetsCatalog.text,
17448
+ type: AXPWidgetsCatalog.entityDefinitionProvider,
17449
+ options: {
17450
+ hasClearButton: true,
17451
+ allowSearch: true,
17452
+ },
17453
+ },
17454
+ },
17455
+ visible: true,
17456
+ },
17457
+ {
17458
+ name: 'filter',
17459
+ title: 'Filters',
17460
+ group: AXP_DATA_PROPERTY_GROUP,
17461
+ schema: {
17462
+ dataType: 'object',
17463
+ interface: {
17464
+ name: 'filter',
17465
+ path: 'options.filter',
17466
+ type: 'lookup-filter-options',
17439
17467
  },
17440
17468
  },
17441
17469
  visible: true,
@@ -17573,6 +17601,250 @@ const AXPLookupWidget = {
17573
17601
  },
17574
17602
  };
17575
17603
 
17604
+ //#region ---- Imports ----
17605
+ //#endregion
17606
+ //#region ---- Widget Config ----
17607
+ const AXPLookupFilterOptionsWidget = {
17608
+ name: 'lookup-filter-options',
17609
+ title: 'Lookup Filter Options',
17610
+ description: 'Configure lookup entity filter conditions',
17611
+ icon: 'fa-light fa-filter',
17612
+ categories: AXP_WIDGETS_EDITOR_CATEGORY,
17613
+ groups: [AXPWidgetGroupEnum.EntityWidget],
17614
+ type: 'editor',
17615
+ properties: [AXP_NAME_PROPERTY, AXP_DATA_PATH_PROPERTY],
17616
+ components: {
17617
+ edit: {
17618
+ component: () => Promise.resolve().then(function () { return lookupFilterOptionsWidgetEditor_component; }).then((c) => c.AXPLookupFilterOptionsWidgetEditComponent),
17619
+ },
17620
+ },
17621
+ };
17622
+ //#endregion
17623
+
17624
+ //#endregion
17625
+ //#region ---- Constants ----
17626
+ const LOOKUP_FILTER_OPTIONS_I18N = {
17627
+ filters: '@general:widgets.data-source-options.filters',
17628
+ filtersButtonEmpty: '@general:widgets.data-source-options.filters-button-empty',
17629
+ filtersButtonWithCount: '@general:widgets.data-source-options.filters-button-with-count',
17630
+ filtersModalTitle: '@general:widgets.data-source-options.filters-modal-title',
17631
+ selectEntityFirst: '@general:widgets.lookup-filter-options.select-entity-first',
17632
+ noFilterableFields: '@general:widgets.lookup-filter-options.no-filterable-fields',
17633
+ };
17634
+ //#endregion
17635
+ //#region ---- Helpers ----
17636
+ function getFilterKey(filter, index) {
17637
+ return filter.id ?? filter.field ?? slugFilterTitle(filter.title ?? '') ?? `filter_${index}`;
17638
+ }
17639
+ function slugFilterTitle(title) {
17640
+ return (title
17641
+ .toLowerCase()
17642
+ .replace(/[^a-z0-9\s]/g, '')
17643
+ .replace(/\s+/g, '_')
17644
+ .substring(0, 50) || 'filter');
17645
+ }
17646
+ function flattenFilterLeaves(filter) {
17647
+ if (!filter) {
17648
+ return [];
17649
+ }
17650
+ if (filter.filters?.length) {
17651
+ return filter.filters.flatMap((child) => flattenFilterLeaves(child));
17652
+ }
17653
+ if (filter.field) {
17654
+ return [filter];
17655
+ }
17656
+ return [];
17657
+ }
17658
+ function filterToExpressionMap(filter, definitions) {
17659
+ const map = {};
17660
+ definitions.forEach((definition, index) => {
17661
+ map[getFilterKey(definition, index)] = '';
17662
+ });
17663
+ for (const leaf of flattenFilterLeaves(filter)) {
17664
+ const definitionIndex = definitions.findIndex((definition) => definition.field === leaf.field);
17665
+ if (definitionIndex < 0) {
17666
+ continue;
17667
+ }
17668
+ const key = getFilterKey(definitions[definitionIndex], definitionIndex);
17669
+ const value = leaf.value;
17670
+ map[key] = typeof value === 'string' ? value : value != null ? String(value) : '';
17671
+ }
17672
+ return map;
17673
+ }
17674
+ function expressionsToFilter(expressions, definitions) {
17675
+ const clauses = definitions
17676
+ .map((definition, index) => {
17677
+ const key = getFilterKey(definition, index);
17678
+ const expression = expressions[key]?.trim();
17679
+ if (!expression) {
17680
+ return null;
17681
+ }
17682
+ return {
17683
+ field: definition.field,
17684
+ operator: definition.operator ?? { type: 'equal' },
17685
+ value: expression,
17686
+ };
17687
+ })
17688
+ .filter((clause) => clause != null);
17689
+ if (!clauses.length) {
17690
+ return undefined;
17691
+ }
17692
+ return {
17693
+ logic: 'and',
17694
+ filters: clauses,
17695
+ };
17696
+ }
17697
+ function countConfiguredExpressions(filter) {
17698
+ return flattenFilterLeaves(filter).filter((leaf) => {
17699
+ const value = leaf.value;
17700
+ const text = typeof value === 'string' ? value : value != null ? String(value) : '';
17701
+ return text.trim().length > 0;
17702
+ }).length;
17703
+ }
17704
+ //#endregion
17705
+ //#region ---- Component ----
17706
+ class AXPLookupFilterOptionsWidgetEditComponent extends AXPValueWidgetComponent {
17707
+ constructor() {
17708
+ super(...arguments);
17709
+ //#region ---- Services & Dependencies ----
17710
+ this.entityRegistry = inject(AXPEntityDefinitionRegistryService);
17711
+ this.widgetRegistry = inject(AXPWidgetRegistryService);
17712
+ this.layoutBuilder = inject(AXPLayoutBuilderService);
17713
+ this.translationService = inject(AXTranslationService);
17714
+ //#endregion
17715
+ //#region ---- State ----
17716
+ this.I18N = LOOKUP_FILTER_OPTIONS_I18N;
17717
+ this.filterDefinitions = signal([], ...(ngDevMode ? [{ debugName: "filterDefinitions" }] : /* istanbul ignore next */ []));
17718
+ this.entityTitle = signal('', ...(ngDevMode ? [{ debugName: "entityTitle" }] : /* istanbul ignore next */ []));
17719
+ this.loadedEntityKey = signal(null, ...(ngDevMode ? [{ debugName: "loadedEntityKey" }] : /* istanbul ignore next */ []));
17720
+ //#endregion
17721
+ //#region ---- Computed ----
17722
+ this.entityKey = computed(() => {
17723
+ this.contextService.data();
17724
+ const key = this.contextService.getValue('options.entity');
17725
+ const trimmed = key?.trim();
17726
+ return trimmed && trimmed.length > 0 ? trimmed : null;
17727
+ }, ...(ngDevMode ? [{ debugName: "entityKey" }] : /* istanbul ignore next */ []));
17728
+ this.filterCount = computed(() => countConfiguredExpressions(this.getValue()), ...(ngDevMode ? [{ debugName: "filterCount" }] : /* istanbul ignore next */ []));
17729
+ //#endregion
17730
+ //#region ---- Effects ----
17731
+ this.loadFilterDefinitionsEffect = effect(() => {
17732
+ const key = this.entityKey();
17733
+ untracked(() => {
17734
+ void this.loadFilterDefinitions(key);
17735
+ });
17736
+ }, ...(ngDevMode ? [{ debugName: "loadFilterDefinitionsEffect" }] : /* istanbul ignore next */ []));
17737
+ }
17738
+ //#endregion
17739
+ //#region ---- UI Handlers ----
17740
+ async openFilterEditor() {
17741
+ const definitions = this.filterDefinitions();
17742
+ if (!definitions.length) {
17743
+ return;
17744
+ }
17745
+ const initialFilters = filterToExpressionMap(this.getValue(), definitions);
17746
+ const filtersModalTitle = await this.translationService.translateAsync(LOOKUP_FILTER_OPTIONS_I18N.filtersModalTitle);
17747
+ const ref = await this.layoutBuilder
17748
+ .create()
17749
+ .dialog((dialog) => {
17750
+ dialog
17751
+ .setTitle(filtersModalTitle)
17752
+ .setMessage(this.entityTitle())
17753
+ .setSize('md')
17754
+ .setContext({ filters: initialFilters })
17755
+ .content((layoutBuilder) => {
17756
+ layoutBuilder.flex((flex) => {
17757
+ flex
17758
+ .mode('edit')
17759
+ .setOptions({
17760
+ flexDirection: 'column',
17761
+ maxHeight: '60vh',
17762
+ overflowY: 'auto',
17763
+ });
17764
+ definitions.forEach((filter, index) => {
17765
+ const key = getFilterKey(filter, index);
17766
+ flex.formField(filter.title, (field) => {
17767
+ field.path(`filters.${key}`).customWidget('code-editor', {
17768
+ language: 'javascript',
17769
+ lineNumbers: true,
17770
+ height: 'min-h-48',
17771
+ });
17772
+ });
17773
+ });
17774
+ });
17775
+ })
17776
+ .setActions((actions) => {
17777
+ actions.cancel('@general:actions.cancel.title');
17778
+ actions.submit('@general:actions.save.title');
17779
+ });
17780
+ })
17781
+ .show();
17782
+ if (ref.action() === 'submit') {
17783
+ const context = ref.context();
17784
+ const expressions = context?.filters ?? {};
17785
+ this.setValue(expressionsToFilter(expressions, definitions));
17786
+ ref.close();
17787
+ return;
17788
+ }
17789
+ ref.close();
17790
+ }
17791
+ //#endregion
17792
+ //#region ---- Private Methods ----
17793
+ async loadFilterDefinitions(entityKey) {
17794
+ if (!entityKey) {
17795
+ this.filterDefinitions.set([]);
17796
+ this.entityTitle.set('');
17797
+ if (this.loadedEntityKey()) {
17798
+ this.setValue(undefined);
17799
+ }
17800
+ this.loadedEntityKey.set(null);
17801
+ return;
17802
+ }
17803
+ const [module, entity] = entityKey.split('.');
17804
+ if (!module || !entity) {
17805
+ this.filterDefinitions.set([]);
17806
+ this.entityTitle.set('');
17807
+ if (this.loadedEntityKey()) {
17808
+ this.setValue(undefined);
17809
+ }
17810
+ this.loadedEntityKey.set(null);
17811
+ return;
17812
+ }
17813
+ const previousKey = this.loadedEntityKey();
17814
+ if (previousKey && previousKey !== entityKey) {
17815
+ this.setValue(undefined);
17816
+ }
17817
+ const entityDef = await this.entityRegistry.resolve(module, entity).catch(() => null);
17818
+ if (!entityDef) {
17819
+ this.filterDefinitions.set([]);
17820
+ this.entityTitle.set('');
17821
+ this.loadedEntityKey.set(null);
17822
+ return;
17823
+ }
17824
+ this.entityTitle.set(typeof entityDef.title === 'string' ? entityDef.title : entityKey);
17825
+ const definitions = await collectEntityListFilterDefinitions(entityDef, (moduleName, entityName) => this.entityRegistry.resolve(moduleName, entityName).catch(() => null), {
17826
+ resolveWidget: (type) => this.widgetRegistry.resolve(type),
17827
+ });
17828
+ this.filterDefinitions.set(definitions);
17829
+ this.loadedEntityKey.set(entityKey);
17830
+ const current = this.getValue();
17831
+ if (current && !isEmpty(current) && countConfiguredExpressions(current) === 0) {
17832
+ this.setValue(undefined);
17833
+ }
17834
+ }
17835
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLookupFilterOptionsWidgetEditComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
17836
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: AXPLookupFilterOptionsWidgetEditComponent, isStandalone: true, selector: "axp-lookup-filter-options-widget-editor", usesInheritance: true, ngImport: i0, template: "<div class=\"axp-lookup-filter-options ax-flex ax-flex-col ax-gap-2 ax-py-2\">\n @if (!entityKey()) {\n <span class=\"ax-text-sm ax-opacity-70\">{{ I18N.selectEntityFirst | translate | async }}</span>\n } @else if (filterDefinitions().length === 0) {\n <span class=\"ax-text-sm ax-opacity-70\">{{ I18N.noFilterableFields | translate | async }}</span>\n } @else {\n @if (filterCount() === 0) {\n <ax-button\n look=\"twotone\"\n color=\"default\"\n size=\"sm\"\n [text]=\"(I18N.filtersButtonEmpty | translate | async) ?? ''\"\n (onClick)=\"openFilterEditor()\"\n class=\"ax-w-full\"\n >\n <ax-prefix>\n <ax-icon icon=\"fa-light fa-filter\"></ax-icon>\n </ax-prefix>\n </ax-button>\n } @else {\n <ax-button\n look=\"twotone\"\n color=\"default\"\n size=\"sm\"\n [text]=\"((I18N.filtersButtonWithCount | translate: { params: { count: filterCount() } }) | async) ?? ''\"\n (onClick)=\"openFilterEditor()\"\n class=\"ax-w-full\"\n >\n <ax-prefix>\n <ax-icon icon=\"fa-light fa-filter\"></ax-icon>\n </ax-prefix>\n </ax-button>\n }\n }\n</div>\n", styles: [".axp-lookup-filter-options{min-width:0}\n"], dependencies: [{ kind: "ngmodule", type: AXButtonModule }, { kind: "component", type: i1.AXButtonComponent, selector: "ax-button", inputs: ["disabled", "size", "tabIndex", "color", "look", "text", "toggleable", "selected", "iconOnly", "type", "loadingText"], outputs: ["onBlur", "onFocus", "onClick", "selectedChange", "toggleableChange", "lookChange", "colorChange", "disabledChange", "loadingTextChange"] }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i3$2.AXDecoratorIconComponent, selector: "ax-icon", inputs: ["icon"] }, { kind: "component", type: i3$2.AXDecoratorGenericComponent, selector: "ax-footer, ax-header, ax-content, ax-divider, ax-form-hint, ax-prefix, ax-suffix, ax-text, ax-title, ax-subtitle, ax-placeholder, ax-overlay" }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "pipe", type: i3$1.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
17837
+ }
17838
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPLookupFilterOptionsWidgetEditComponent, decorators: [{
17839
+ type: Component,
17840
+ args: [{ selector: 'axp-lookup-filter-options-widget-editor', changeDetection: ChangeDetectionStrategy.OnPush, imports: [AsyncPipe, AXButtonModule, AXDecoratorModule, AXTranslationModule], template: "<div class=\"axp-lookup-filter-options ax-flex ax-flex-col ax-gap-2 ax-py-2\">\n @if (!entityKey()) {\n <span class=\"ax-text-sm ax-opacity-70\">{{ I18N.selectEntityFirst | translate | async }}</span>\n } @else if (filterDefinitions().length === 0) {\n <span class=\"ax-text-sm ax-opacity-70\">{{ I18N.noFilterableFields | translate | async }}</span>\n } @else {\n @if (filterCount() === 0) {\n <ax-button\n look=\"twotone\"\n color=\"default\"\n size=\"sm\"\n [text]=\"(I18N.filtersButtonEmpty | translate | async) ?? ''\"\n (onClick)=\"openFilterEditor()\"\n class=\"ax-w-full\"\n >\n <ax-prefix>\n <ax-icon icon=\"fa-light fa-filter\"></ax-icon>\n </ax-prefix>\n </ax-button>\n } @else {\n <ax-button\n look=\"twotone\"\n color=\"default\"\n size=\"sm\"\n [text]=\"((I18N.filtersButtonWithCount | translate: { params: { count: filterCount() } }) | async) ?? ''\"\n (onClick)=\"openFilterEditor()\"\n class=\"ax-w-full\"\n >\n <ax-prefix>\n <ax-icon icon=\"fa-light fa-filter\"></ax-icon>\n </ax-prefix>\n </ax-button>\n }\n }\n</div>\n", styles: [".axp-lookup-filter-options{min-width:0}\n"] }]
17841
+ }] });
17842
+
17843
+ var lookupFilterOptionsWidgetEditor_component = /*#__PURE__*/Object.freeze({
17844
+ __proto__: null,
17845
+ AXPLookupFilterOptionsWidgetEditComponent: AXPLookupFilterOptionsWidgetEditComponent
17846
+ });
17847
+
17576
17848
  /**
17577
17849
  * Source type
17578
17850
  */
@@ -22526,6 +22798,7 @@ const AXPSelectorStructureWidget = {
22526
22798
  //#region ---- Entity Widgets Provider ----
22527
22799
  const ENTITY_WIDGETS = [
22528
22800
  AXPLookupWidget,
22801
+ AXPLookupFilterOptionsWidget,
22529
22802
  AXPSelectorStructureWidget,
22530
22803
  AXPEntityListWidget,
22531
22804
  AXPPageComponentWidget,