@acorex/platform 20.6.0-next.22 → 20.6.0-next.23

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.
@@ -1,20 +1,20 @@
1
1
  import { AXToastService } from '@acorex/components/toast';
2
2
  import * as i6 from '@acorex/core/translation';
3
3
  import { AXTranslationService, AXTranslationModule } from '@acorex/core/translation';
4
- import * as i4 from '@acorex/platform/common';
4
+ import * as i4$1 from '@acorex/platform/common';
5
5
  import { AXPSettingService, AXPFilterOperatorMiddlewareService, AXPEntityCommandScope, getEntityInfo, AXPRefreshEvent, AXPReloadEvent, AXPCommonSettings, AXPCleanNestedFilters, AXPWorkflowNavigateAction, AXPToastAction, AXP_SEARCH_DEFINITION_PROVIDER } from '@acorex/platform/common';
6
- import * as i1$2 from '@acorex/platform/core';
6
+ import * as i1$1 from '@acorex/platform/core';
7
7
  import { AXPDeviceService, AXPBroadcastEventService, resolveActionLook, AXPExpressionEvaluatorService, AXPDistributedEventListenerService, AXPPlatformScope, AXHighlightService, extractValue, setSmart, getChangedPaths, AXPSystemActionType } from '@acorex/platform/core';
8
8
  import * as i0 from '@angular/core';
9
9
  import { InjectionToken, inject, Injector, runInInjectionContext, Injectable, input, viewChild, signal, ElementRef, ChangeDetectionStrategy, Component, ApplicationRef, EnvironmentInjector, createComponent, computed, effect, Input, afterNextRender, ViewEncapsulation, ChangeDetectorRef, viewChildren, linkedSignal, untracked, HostBinding, ViewChild, NgModule } from '@angular/core';
10
10
  import { Subject, takeUntil } from 'rxjs';
11
11
  import { AXPLayoutBuilderService } from '@acorex/platform/layout/builder';
12
- import { merge, castArray, get, cloneDeep, set, orderBy, isNil, isEqual, isEmpty, sortBy } from 'lodash-es';
12
+ import { merge, castArray, get, cloneDeep, set, orderBy, isNil, isEmpty, isEqual, sortBy } from 'lodash-es';
13
13
  import { AXPSessionService, AXPAuthGuard } from '@acorex/platform/auth';
14
14
  import { Router, RouterModule, ROUTES } from '@angular/router';
15
15
  import * as i3 from '@acorex/components/button';
16
16
  import { AXButtonModule } from '@acorex/components/button';
17
- import * as i1 from '@acorex/components/loading';
17
+ import * as i4 from '@acorex/components/loading';
18
18
  import { AXLoadingModule } from '@acorex/components/loading';
19
19
  import * as i2 from '@acorex/components/popover';
20
20
  import { AXPopoverModule } from '@acorex/components/popover';
@@ -33,32 +33,33 @@ import { AXDialogService } from '@acorex/components/dialog';
33
33
  import { AXLoadingDialogService } from '@acorex/components/loading-dialog';
34
34
  import { AXPopupService } from '@acorex/components/popup';
35
35
  import { AXPlatform } from '@acorex/core/platform';
36
+ import * as i2$1 from '@acorex/components/check-box';
36
37
  import { AXCheckBoxModule } from '@acorex/components/check-box';
37
- import * as i2$1 from '@acorex/components/decorators';
38
+ import * as i3$2 from '@acorex/components/decorators';
38
39
  import { AXDecoratorModule } from '@acorex/components/decorators';
39
40
  import { AXBasePageComponent } from '@acorex/components/page';
40
- import * as i3$2 from '@acorex/components/search-box';
41
+ import * as i4$2 from '@acorex/components/search-box';
41
42
  import { AXSearchBoxModule, AXSearchBoxComponent } from '@acorex/components/search-box';
42
- import * as i4$1 from '@acorex/components/skeleton';
43
+ import * as i5$1 from '@acorex/components/skeleton';
43
44
  import { AXSkeletonModule } from '@acorex/components/skeleton';
44
45
  import { AXTreeViewComponent } from '@acorex/components/tree-view';
45
46
  import { AXPStateMessageComponent, AXPDataSelectorService, AXPWidgetPropertyViewerComponent } from '@acorex/platform/layout/components';
46
- import * as i1$1 from '@angular/forms';
47
+ import * as i1 from '@angular/forms';
47
48
  import { FormsModule } from '@angular/forms';
48
49
  import * as i2$2 from '@acorex/components/badge';
49
50
  import { AXBadgeModule } from '@acorex/components/badge';
50
- import * as i5$1 from '@acorex/components/form';
51
+ import * as i5$2 from '@acorex/components/form';
51
52
  import { AXFormModule } from '@acorex/components/form';
53
+ import * as i6$1 from '@acorex/components/tag-box';
54
+ import { AXTagBoxModule, AXTagBoxComponent } from '@acorex/components/tag-box';
52
55
  import { AXValidationModule } from '@acorex/core/validation';
53
56
  import { AXP_DISABLED_PROPERTY, AXP_ALLOW_CLEAR_PROPERTY, AXP_DATA_PATH_PROPERTY, AXP_DATA_PROPERTY_GROUP, AXP_ALLOW_MULTIPLE_PROPERTY, AXP_NAME_PROPERTY, AXPFileUploaderWidgetService } from '@acorex/platform/layout/widgets';
54
- import * as i4$2 from '@acorex/components/dropdown';
57
+ import * as i4$3 from '@acorex/components/dropdown';
55
58
  import { AXDropdownModule } from '@acorex/components/dropdown';
56
59
  import * as i7 from '@acorex/components/select-box';
57
60
  import { AXSelectBoxModule } from '@acorex/components/select-box';
58
61
  import * as i2$3 from '@acorex/components/text-box';
59
62
  import { AXTextBoxModule, AXTextBoxComponent } from '@acorex/components/text-box';
60
- import * as i6$1 from '@acorex/components/tag-box';
61
- import { AXTagBoxComponent, AXTagBoxModule } from '@acorex/components/tag-box';
62
63
  import { transform, isEqual as isEqual$1 } from 'lodash';
63
64
 
64
65
  function ensureListActions(ctx) {
@@ -1035,7 +1036,7 @@ class AXPCreateEntityCommand {
1035
1036
  const entityRef = await this.entityService.resolve(moduleName, entityName);
1036
1037
  let dialogRef;
1037
1038
  try {
1038
- let chain = this.entityForm.entity(`${moduleName}.${entityName}`).create(input.__metadata__);
1039
+ let chain = this.entityForm.entity(`${moduleName}.${entityName}`).create(data);
1039
1040
  chain.actions((actions) => {
1040
1041
  actions.cancel('@general:actions.cancel.title');
1041
1042
  actions.submit('@general:actions.create.title');
@@ -1680,7 +1681,7 @@ class AXPEntityDetailPopoverComponent {
1680
1681
  return importantProperties;
1681
1682
  }
1682
1683
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPEntityDetailPopoverComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1683
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.12", type: AXPEntityDetailPopoverComponent, isStandalone: true, selector: "axp-entity-detail-popover", inputs: { entity: { classPropertyName: "entity", publicName: "entity", isSignal: true, isRequired: true, transformFunction: null }, entityId: { classPropertyName: "entityId", publicName: "entityId", isSignal: true, isRequired: true, transformFunction: null }, textField: { classPropertyName: "textField", publicName: "textField", isSignal: true, isRequired: false, transformFunction: null }, valueField: { classPropertyName: "valueField", publicName: "valueField", isSignal: true, isRequired: false, transformFunction: null }, item: { classPropertyName: "item", publicName: "item", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "detailPopover", first: true, predicate: ["detailPopover"], descendants: true, isSignal: true }], ngImport: i0, template: "<ax-popover [openOn]=\"'manual'\" #detailPopover (openChange)=\"onDetailPopoverOpenChange($event)\">\n <div class=\"ax-lightest-surface ax-border ax-rounded-lg ax-shadow-lg ax-p-4 ax-min-w-[400px]\">\n <div class=\"ax-mb-4 ax-border-b ax-pb-2\">\n <h3 class=\"ax-text-base ax-font-semibold ax-text-on-lightest-surface\">\n @if (entityDetails()?.entityData?.[textField()]) {\n {{ entityDetails()?.entityData[textField()] }}\n } @else {\n {{ item()?.[textField()] }}\n }\n </h3>\n </div>\n @if (isLoadingDetails()) {\n <div class=\"ax-flex ax-items-center ax-justify-center ax-py-8\">\n <ax-loading>Loading details...</ax-loading>\n </div>\n } @else if (entityDetails()) {\n <div class=\"ax-space-y-3 ax-mb-4\">\n <!-- Important Entity Data -->\n @if (entityDetails()?.entityData) {\n <axp-widgets-container [context]=\"entityDetails()?.entityData\">\n <div class=\"ax-space-y-2\">\n @for (item of getEntityPropertiesWithWidgets(); track item.name) {\n <div class=\"ax-flex ax-justify-between ax-items-center\">\n <span class=\"ax-text-sm ax-font-medium\">{{ item.title | translate | async }}:</span>\n <div class=\"ax-flex-1 ax-ml-2 ax-max-w-48\">\n <ng-container axp-widget-renderer [node]=\"item.node\" [mode]=\"'view'\"></ng-container>\n </div>\n </div>\n }\n </div>\n </axp-widgets-container>\n }\n </div>\n <div class=\"ax-flex ax-gap-2 ax-justify-end ax-sm\">\n <ax-button [color]=\"'primary'\" [look]=\"'solid'\" text=\"Open Details\" (click)=\"navigateToDetails()\"> </ax-button>\n </div>\n }\n </div>\n</ax-popover>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: AXButtonModule }, { kind: "component", type: i3.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: AXPopoverModule }, { kind: "component", type: i2.AXPopoverComponent, selector: "ax-popover", inputs: ["width", "disabled", "offsetX", "offsetY", "target", "placement", "content", "openOn", "closeOn", "hasBackdrop", "openAfter", "closeAfter", "backdropClass", "panelClass", "adaptivityEnabled"], outputs: ["onOpened", "onClosed"] }, { kind: "ngmodule", type: AXPWidgetCoreModule }, { kind: "component", type: i3$1.AXPWidgetContainerComponent, selector: "axp-widgets-container", inputs: ["context", "functions"], outputs: ["onContextChanged"] }, { kind: "directive", type: i3$1.AXPWidgetRendererDirective, selector: "[axp-widget-renderer]", inputs: ["parentNode", "index", "mode", "node"], outputs: ["onOptionsChanged", "onValueChanged"], exportAs: ["widgetRenderer"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "ngmodule", type: AXLoadingModule }, { kind: "component", type: i1.AXLoadingComponent, selector: "ax-loading", inputs: ["visible", "type", "context"], outputs: ["visibleChange"] }, { kind: "pipe", type: i5.AsyncPipe, name: "async" }, { kind: "pipe", type: i6.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1684
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.12", type: AXPEntityDetailPopoverComponent, isStandalone: true, selector: "axp-entity-detail-popover", inputs: { entity: { classPropertyName: "entity", publicName: "entity", isSignal: true, isRequired: true, transformFunction: null }, entityId: { classPropertyName: "entityId", publicName: "entityId", isSignal: true, isRequired: true, transformFunction: null }, textField: { classPropertyName: "textField", publicName: "textField", isSignal: true, isRequired: false, transformFunction: null }, valueField: { classPropertyName: "valueField", publicName: "valueField", isSignal: true, isRequired: false, transformFunction: null }, item: { classPropertyName: "item", publicName: "item", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "detailPopover", first: true, predicate: ["detailPopover"], descendants: true, isSignal: true }], ngImport: i0, template: "<ax-popover [openOn]=\"'manual'\" #detailPopover (openChange)=\"onDetailPopoverOpenChange($event)\">\n <div class=\"ax-lightest-surface ax-border ax-rounded-lg ax-shadow-lg ax-p-4 ax-min-w-[400px]\">\n <div class=\"ax-mb-4 ax-border-b ax-pb-2\">\n <h3 class=\"ax-text-base ax-font-semibold ax-text-on-lightest-surface\">\n @if (entityDetails()?.entityData?.[textField()]) {\n {{ entityDetails()?.entityData[textField()] }}\n } @else {\n {{ item()?.[textField()] }}\n }\n </h3>\n </div>\n @if (isLoadingDetails()) {\n <div class=\"ax-flex ax-items-center ax-justify-center ax-py-8\">\n <ax-loading>Loading details...</ax-loading>\n </div>\n } @else if (entityDetails()) {\n <div class=\"ax-space-y-3 ax-mb-4\">\n <!-- Important Entity Data -->\n @if (entityDetails()?.entityData) {\n <axp-widgets-container [context]=\"entityDetails()?.entityData\">\n <div class=\"ax-space-y-2\">\n @for (item of getEntityPropertiesWithWidgets(); track item.name) {\n <div class=\"ax-flex ax-justify-between ax-items-center\">\n <span class=\"ax-text-sm ax-font-medium\">{{ item.title | translate | async }}:</span>\n <div class=\"ax-flex-1 ax-ml-2 ax-max-w-48\">\n <ng-container axp-widget-renderer [node]=\"item.node\" [mode]=\"'view'\"></ng-container>\n </div>\n </div>\n }\n </div>\n </axp-widgets-container>\n }\n </div>\n <div class=\"ax-flex ax-gap-2 ax-justify-end ax-sm\">\n <ax-button [color]=\"'primary'\" [look]=\"'solid'\" text=\"Open Details\" (click)=\"navigateToDetails()\"> </ax-button>\n </div>\n }\n </div>\n</ax-popover>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: AXButtonModule }, { kind: "component", type: i3.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: AXPopoverModule }, { kind: "component", type: i2.AXPopoverComponent, selector: "ax-popover", inputs: ["width", "disabled", "offsetX", "offsetY", "target", "placement", "content", "openOn", "closeOn", "hasBackdrop", "openAfter", "closeAfter", "backdropClass", "panelClass", "adaptivityEnabled"], outputs: ["onOpened", "onClosed"] }, { kind: "ngmodule", type: AXPWidgetCoreModule }, { kind: "component", type: i3$1.AXPWidgetContainerComponent, selector: "axp-widgets-container", inputs: ["context", "functions"], outputs: ["onContextChanged"] }, { kind: "directive", type: i3$1.AXPWidgetRendererDirective, selector: "[axp-widget-renderer]", inputs: ["parentNode", "index", "mode", "node"], outputs: ["onOptionsChanged", "onValueChanged"], exportAs: ["widgetRenderer"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "ngmodule", type: AXLoadingModule }, { kind: "component", type: i4.AXLoadingComponent, selector: "ax-loading", inputs: ["visible", "type", "context"], outputs: ["visibleChange"] }, { kind: "pipe", type: i5.AsyncPipe, name: "async" }, { kind: "pipe", type: i6.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1684
1685
  }
1685
1686
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPEntityDetailPopoverComponent, decorators: [{
1686
1687
  type: Component,
@@ -4324,24 +4325,48 @@ class AXPPageListConverter extends AXPBaseRelatedEntityConverter {
4324
4325
  message: {
4325
4326
  code: 'ACTION_NOT_FOUND',
4326
4327
  text: `Action ${commandName} not found`,
4327
- }
4328
+ },
4328
4329
  };
4329
4330
  }
4330
- await context.workflowService.execute(commandName, {
4331
- entity: getEntityInfo(entityDef).source,
4332
- entityInfo: {
4333
- name: entityDef.name,
4334
- module: entityDef.module,
4335
- title: entityDef.title,
4336
- parentKey: entityDef.parentKey,
4337
- source: `${entityDef.module}.${entityDef.name}`,
4338
- },
4339
- data: action.scope == AXPEntityCommandScope.Selected
4340
- ? executeContext
4341
- : action.options?.['process']?.data || null,
4342
- options: action.options,
4343
- metadata: action.metadata,
4344
- });
4331
+ if (context.commandService.exists(commandName)) {
4332
+ // check options for evaluation
4333
+ await context.commandService.execute(commandName, {
4334
+ __context__: {
4335
+ entity: getEntityInfo(entityDef).source,
4336
+ entityInfo: {
4337
+ name: entityDef.name,
4338
+ module: entityDef.module,
4339
+ title: entityDef.title,
4340
+ parentKey: entityDef.parentKey,
4341
+ source: `${entityDef.module}.${entityDef.name}`,
4342
+ },
4343
+ data: action.scope == AXPEntityCommandScope.Selected
4344
+ ? executeContext
4345
+ : action.options?.['process']?.data || null,
4346
+ options: action.options,
4347
+ metadata: action.metadata,
4348
+ },
4349
+ options: action.options,
4350
+ metadata: action.metadata,
4351
+ });
4352
+ }
4353
+ else {
4354
+ await context.workflowService.execute(commandName, {
4355
+ entity: getEntityInfo(entityDef).source,
4356
+ entityInfo: {
4357
+ name: entityDef.name,
4358
+ module: entityDef.module,
4359
+ title: entityDef.title,
4360
+ parentKey: entityDef.parentKey,
4361
+ source: `${entityDef.module}.${entityDef.name}`,
4362
+ },
4363
+ data: action.scope == AXPEntityCommandScope.Selected
4364
+ ? executeContext
4365
+ : action.options?.['process']?.data || null,
4366
+ options: action.options,
4367
+ metadata: action.metadata,
4368
+ });
4369
+ }
4345
4370
  return { success: true };
4346
4371
  }
4347
4372
  catch (error) {
@@ -4368,6 +4393,7 @@ class AXPPageListConverter extends AXPBaseRelatedEntityConverter {
4368
4393
  entity: relatedEntity.entity,
4369
4394
  showEntityActions: false,
4370
4395
  actions: evaluatedActions,
4396
+ includeColumns: relatedEntity.columns,
4371
4397
  },
4372
4398
  },
4373
4399
  ],
@@ -5275,7 +5301,7 @@ class AXPLayoutAdapterFactory {
5275
5301
  const title = await dependencies.expressionEvaluator.evaluate(entity.interfaces?.master?.single?.title, rootContext);
5276
5302
  return title;
5277
5303
  }
5278
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPLayoutAdapterFactory, deps: [{ token: AXPRelatedEntityConverterFactory }, { token: AXPMainEntityContentBuilder }, { token: AXPLayoutAdapterBuilder }, { token: i4.AXPFilterOperatorMiddlewareService }], target: i0.ɵɵFactoryTarget.Injectable }); }
5304
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPLayoutAdapterFactory, deps: [{ token: AXPRelatedEntityConverterFactory }, { token: AXPMainEntityContentBuilder }, { token: AXPLayoutAdapterBuilder }, { token: i4$1.AXPFilterOperatorMiddlewareService }], target: i0.ɵɵFactoryTarget.Injectable }); }
5279
5305
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPLayoutAdapterFactory, providedIn: 'root' }); }
5280
5306
  }
5281
5307
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPLayoutAdapterFactory, decorators: [{
@@ -5283,7 +5309,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImpo
5283
5309
  args: [{
5284
5310
  providedIn: 'root',
5285
5311
  }]
5286
- }], ctorParameters: () => [{ type: AXPRelatedEntityConverterFactory }, { type: AXPMainEntityContentBuilder }, { type: AXPLayoutAdapterBuilder }, { type: i4.AXPFilterOperatorMiddlewareService }] });
5312
+ }], ctorParameters: () => [{ type: AXPRelatedEntityConverterFactory }, { type: AXPMainEntityContentBuilder }, { type: AXPLayoutAdapterBuilder }, { type: i4$1.AXPFilterOperatorMiddlewareService }] });
5287
5313
 
5288
5314
  const AXPLayoutDetailsViewRouteResolver = async (route, state, entityResolver = inject(AXPEntityDefinitionRegistryService), expressionEvaluator = inject(AXPExpressionEvaluatorService), session = inject(AXPSessionService), formatService = inject(AXFormatService), workflowService = inject(AXPWorkflowService), commandService = inject(AXPCommandService), layoutAdapterFactory = inject(AXPLayoutAdapterFactory)) => {
5289
5315
  const moduleName = route.parent?.paramMap.get('module');
@@ -5905,7 +5931,6 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
5905
5931
  this.textField = signal('title', ...(ngDevMode ? [{ debugName: "textField" }] : []));
5906
5932
  this.valueField = signal('id', ...(ngDevMode ? [{ debugName: "valueField" }] : []));
5907
5933
  this.allowMultiple = signal(false, ...(ngDevMode ? [{ debugName: "allowMultiple" }] : []));
5908
- this.selectionBehavior = signal('all', ...(ngDevMode ? [{ debugName: "selectionBehavior" }] : []));
5909
5934
  this.selectedValues = signal([], ...(ngDevMode ? [{ debugName: "selectedValues" }] : []));
5910
5935
  this.searchPlaceholder = signal('@general:terms.interface.category.search.placeholder', ...(ngDevMode ? [{ debugName: "searchPlaceholder" }] : []));
5911
5936
  this.excludedNodeId = signal(undefined, ...(ngDevMode ? [{ debugName: "excludedNodeId" }] : [])); // Node ID to disable
@@ -5924,6 +5949,7 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
5924
5949
  this.noRecordsTitle = signal('', ...(ngDevMode ? [{ debugName: "noRecordsTitle" }] : []));
5925
5950
  this.currentSearchTerm = null;
5926
5951
  this.isRefreshing = false;
5952
+ this.isUpdatingSelection = false; // Flag to prevent recursive updates during batch operations
5927
5953
  /**
5928
5954
  * Computed property to check if we should show the "no search results" empty state.
5929
5955
  * Returns true when search is active, not searching, and no results found.
@@ -5943,6 +5969,7 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
5943
5969
  this.relevantNodeIds = new Set(); // For search filtering
5944
5970
  this.expandedNodesBeforeSearch = []; // Store expanded nodes before search to restore after
5945
5971
  this.nodesExpandedDuringSearch = []; // Track nodes we expanded during search
5972
+ this.isInitializing = false; // Flag to track if we're in initialization phase
5946
5973
  /** Datasource callback for tree-view component. */
5947
5974
  this.datasource = async (id) => {
5948
5975
  if (!this.treeData || !this.treeConfig) {
@@ -5994,15 +6021,42 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
5994
6021
  this.markNodeAsSelectedIfNeeded(node);
5995
6022
  });
5996
6023
  // After children load, programmatically select pre-selected nodes
6024
+ // Use a small delay to ensure nodes are fully added to tree structure
5997
6025
  const treeComponent = this.tree();
5998
6026
  if (treeComponent) {
5999
- const selectedIds = this.selectedNodeIds();
6000
- childNodes.forEach((node) => {
6001
- const nodeId = String(node['id'] ?? '');
6002
- if (nodeId && selectedIds.includes(nodeId) && nodeId !== 'all') {
6003
- treeComponent.selectNode(nodeId);
6004
- }
6005
- });
6027
+ // Use setTimeout to ensure nodes are in tree structure before selecting
6028
+ setTimeout(() => {
6029
+ const selectedIds = this.selectedNodeIds();
6030
+ childNodes.forEach((node) => {
6031
+ const nodeId = String(node['id'] ?? '');
6032
+ if (nodeId && selectedIds.includes(nodeId) && nodeId !== 'all') {
6033
+ try {
6034
+ // Try to find and select the node
6035
+ const treeNode = treeComponent.findNode(nodeId);
6036
+ if (treeNode) {
6037
+ treeComponent.selectNode(nodeId);
6038
+ }
6039
+ else {
6040
+ // If node not found, try again after a short delay (might still be loading)
6041
+ setTimeout(() => {
6042
+ try {
6043
+ const retryNode = treeComponent.findNode(nodeId);
6044
+ if (retryNode) {
6045
+ treeComponent.selectNode(nodeId);
6046
+ }
6047
+ }
6048
+ catch {
6049
+ // Node still not found, will be selected when it loads
6050
+ }
6051
+ }, 50);
6052
+ }
6053
+ }
6054
+ catch (error) {
6055
+ // Node might not be in tree yet, will be selected when it loads
6056
+ }
6057
+ }
6058
+ });
6059
+ }, 10);
6006
6060
  }
6007
6061
  return childNodes;
6008
6062
  };
@@ -6021,6 +6075,7 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
6021
6075
  return;
6022
6076
  }
6023
6077
  this.loading.set(true);
6078
+ this.isInitializing = true; // Mark that we're in initialization phase
6024
6079
  try {
6025
6080
  this.treeConfig = {
6026
6081
  entityKey: this.entityKey(),
@@ -6030,6 +6085,7 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
6030
6085
  this.treeData = await this.categoryTreeService.initializeCategoryTree(this.treeConfig);
6031
6086
  if (!this.treeData) {
6032
6087
  this.loading.set(false);
6088
+ this.isInitializing = false;
6033
6089
  return;
6034
6090
  }
6035
6091
  // Get parentKey from entity definition
@@ -6037,13 +6093,22 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
6037
6093
  this.treeConfig.parentKey = this.treeData.categoryEntityDef.parentKey;
6038
6094
  }
6039
6095
  // Initialize selected nodes and load their data into cache
6096
+ // Wait for tree to be ready before syncing selection
6097
+ await new Promise((resolve) => setTimeout(resolve, 100));
6040
6098
  await this.updateSelectedNodes(this.selectedValues());
6099
+ // After initial sync, wait a bit more and sync again to catch any nodes that loaded late
6100
+ await new Promise((resolve) => setTimeout(resolve, 200));
6101
+ const finalSelectedIds = this.selectedNodeIds();
6102
+ if (finalSelectedIds.length > 0) {
6103
+ await this.syncSelectionWithTree(finalSelectedIds);
6104
+ }
6041
6105
  }
6042
6106
  catch (error) {
6043
6107
  console.error('Error loading entity definition:', error);
6044
6108
  }
6045
6109
  finally {
6046
6110
  this.loading.set(false);
6111
+ this.isInitializing = false; // Mark initialization as complete
6047
6112
  }
6048
6113
  }
6049
6114
  //#endregion
@@ -6499,11 +6564,22 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
6499
6564
  if (nodeData && typeof nodeData === 'object' && nodeData !== null && !Array.isArray(nodeData)) {
6500
6565
  this.nodeDataCache.set(nodeId, nodeData);
6501
6566
  }
6567
+ // NOTE: We do NOT call loadAndSelectChildrenRecursively here
6568
+ // The recursive selection should only happen when user explicitly selects a node via checkbox
6569
+ // (handled in handleCheckboxChange). This prevents intermediate parent states from
6570
+ // triggering unwanted sibling selections.
6502
6571
  }
6503
6572
  async onSelectionChange(event) {
6573
+ // Don't process during initialization or batch updates - let those handle it
6574
+ if (this.isInitializing || this.isUpdatingSelection) {
6575
+ return;
6576
+ }
6504
6577
  // Update selected node IDs from the tree component's selection state
6578
+ // The tree component with intermediate-nested behavior automatically manages parent states
6505
6579
  const selectedNodes = event.selectedNodes || [];
6506
6580
  const selectedIds = selectedNodes.map((node) => String(node['id'] ?? '')).filter((id) => id && id !== 'all');
6581
+ // Sync with tree component's selection state
6582
+ // This includes parents that are automatically selected when all children are selected
6507
6583
  this.selectedNodeIds.set(selectedIds);
6508
6584
  // Cache node data for all selected nodes
6509
6585
  selectedNodes.forEach((node) => {
@@ -6568,171 +6644,884 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
6568
6644
  }
6569
6645
  /**
6570
6646
  * Handles checkbox change event to toggle node selection
6647
+ * In multiple mode: recursively selects/deselects children
6648
+ * Parent states are handled automatically by tree component's intermediate-nested behavior
6571
6649
  */
6572
- // protected handleCheckboxChange(nodeId: string | number | undefined, checked: boolean): void {
6573
- // if (!nodeId || nodeId === 'all') {
6574
- // return;
6575
- // }
6576
- // const id = String(nodeId);
6577
- // const treeComponent = this.tree();
6578
- // if (!treeComponent) {
6579
- // return;
6580
- // }
6581
- // if (checked) {
6582
- // // Select the node
6583
- // treeComponent.selectNode(id);
6584
- // } else {
6585
- // // Deselect the node
6586
- // treeComponent.deselectNode(id);
6587
- // }
6588
- // }
6589
- //#endregion
6590
- async updateSelectedNodes(selectedIds) {
6591
- if (!selectedIds || selectedIds.length === 0) {
6592
- this.selectedNodeIds.set([]);
6650
+ async handleCheckboxChange(nodeId, checked) {
6651
+ if (!nodeId || nodeId === 'all') {
6593
6652
  return;
6594
6653
  }
6595
- const ids = selectedIds.filter((id) => id && id !== 'all');
6596
- this.selectedNodeIds.set(ids);
6597
- // Fetch node data for pre-selected items that aren't in the cache
6598
- // This ensures getSelectedItems() can retrieve them even if their branches aren't expanded
6599
- await this.loadMissingNodeData(ids);
6600
- // Note: Selection state is maintained in selectedNodeIds signal
6601
- // The tree component will sync selection state when nodes are loaded via datasource
6602
- // We mark nodes as selected in the datasource callback by setting node.selected = true
6654
+ const id = String(nodeId);
6655
+ const treeComponent = this.tree();
6656
+ if (!treeComponent) {
6657
+ return;
6658
+ }
6659
+ if (checked) {
6660
+ // Select the node and recursively select all its children
6661
+ await this.selectNodeAndChildren(id);
6662
+ }
6663
+ else {
6664
+ // Deselect the node and recursively deselect all its children
6665
+ await this.deselectNodeAndChildren(id);
6666
+ }
6667
+ // Note: Parent states (select/intermediate) are handled automatically by the tree component
6668
+ // with selectionBehavior: 'intermediate-nested'. We don't need to manually update them.
6603
6669
  }
6604
6670
  /**
6605
- * Loads node data for IDs that are selected but not yet in the cache.
6606
- * This is critical for pre-selected values in collapsed branches.
6671
+ * Selects a node and recursively selects all its children
6607
6672
  */
6608
- async loadMissingNodeData(selectedIds) {
6609
- if (!this.treeData || !this.treeConfig || selectedIds.length === 0) {
6673
+ async selectNodeAndChildren(nodeId) {
6674
+ const treeComponent = this.tree();
6675
+ if (!treeComponent) {
6610
6676
  return;
6611
6677
  }
6612
- // Find IDs that are selected but not in cache
6613
- const missingIds = selectedIds.filter((id) => !this.nodeDataCache.has(id));
6614
- if (missingIds.length === 0) {
6678
+ // Set flag to prevent recursive updates during batch operation
6679
+ this.isUpdatingSelection = true;
6680
+ try {
6681
+ // Collect all nodes to select (node + all descendants)
6682
+ const nodesToSelect = new Set([nodeId]);
6683
+ await this.collectAllDescendants(nodeId, nodesToSelect);
6684
+ // Batch update selectedNodeIds
6685
+ const currentSelected = this.selectedNodeIds();
6686
+ const newSelected = [...currentSelected];
6687
+ for (const id of nodesToSelect) {
6688
+ if (!newSelected.includes(id)) {
6689
+ newSelected.push(id);
6690
+ }
6691
+ }
6692
+ this.selectedNodeIds.set(newSelected);
6693
+ // Batch select in tree component (with small delays to prevent glitches)
6694
+ for (const id of nodesToSelect) {
6695
+ try {
6696
+ const node = treeComponent.findNode(id);
6697
+ if (node) {
6698
+ treeComponent.selectNode(id);
6699
+ // Small delay to prevent overwhelming the tree component
6700
+ await new Promise((resolve) => setTimeout(resolve, 5));
6701
+ }
6702
+ }
6703
+ catch {
6704
+ // Node might not be in tree yet
6705
+ }
6706
+ }
6707
+ }
6708
+ finally {
6709
+ this.isUpdatingSelection = false;
6710
+ }
6711
+ }
6712
+ /**
6713
+ * Collects all descendant node IDs recursively
6714
+ */
6715
+ async collectAllDescendants(parentId, collection) {
6716
+ if (!this.treeData || !this.treeConfig || !parentId || parentId === 'all') {
6615
6717
  return;
6616
6718
  }
6617
6719
  try {
6618
- const valueField = this.treeConfig.valueField || 'id';
6619
- const categoryEntityQueryFunc = this.treeData.categoryEntityQueryFunc;
6620
- if (!categoryEntityQueryFunc) {
6720
+ const childNodes = await this.datasource(parentId);
6721
+ if (!childNodes || childNodes.length === 0) {
6621
6722
  return;
6622
6723
  }
6623
- // Query for missing nodes by their IDs
6624
- const event = {
6625
- ...this.treeData.basicQueryEvent,
6626
- filter: {
6627
- filters: missingIds.map((id) => ({
6628
- field: valueField,
6629
- value: id,
6630
- operator: { type: 'equal' },
6631
- })),
6632
- logic: 'or',
6633
- },
6634
- };
6635
- const res = await categoryEntityQueryFunc(event);
6636
- if (res?.items) {
6637
- // Cache the fetched node data
6638
- res.items.forEach((item) => {
6639
- const itemId = String(item[valueField] ?? '');
6640
- if (itemId) {
6641
- this.nodeDataCache.set(itemId, item);
6724
+ for (const childNode of childNodes) {
6725
+ const childId = String(childNode['id'] ?? '');
6726
+ if (childId && childId !== 'all' && !collection.has(childId)) {
6727
+ collection.add(childId);
6728
+ // Cache node data
6729
+ const nodeData = childNode['data'];
6730
+ if (nodeData && typeof nodeData === 'object') {
6731
+ this.nodeDataCache.set(childId, nodeData);
6642
6732
  }
6643
- });
6733
+ else if (childNode && typeof childNode === 'object') {
6734
+ const valueField = this.treeConfig.valueField || 'id';
6735
+ const textField = this.treeConfig.textField || 'title';
6736
+ const dataObj = {
6737
+ [valueField]: childId,
6738
+ [textField]: childNode['title'] ?? '',
6739
+ ...childNode,
6740
+ };
6741
+ this.nodeDataCache.set(childId, dataObj);
6742
+ }
6743
+ // Recursively collect descendants
6744
+ await this.collectAllDescendants(childId, collection);
6745
+ }
6644
6746
  }
6645
6747
  }
6646
6748
  catch (error) {
6647
- console.error('Error loading missing node data:', error);
6749
+ console.error(`Error collecting descendants for node ${parentId}:`, error);
6648
6750
  }
6649
6751
  }
6650
6752
  /**
6651
- * Marks nodes as selected in the tree structure based on selectedNodeIds.
6652
- * This ensures pre-selected nodes appear selected when the tree is rendered.
6753
+ * Deselects a node and recursively deselects all its children
6653
6754
  */
6654
- markNodesAsSelected(node) {
6655
- const selectedIds = this.selectedNodeIds();
6656
- if (selectedIds.length === 0) {
6755
+ async deselectNodeAndChildren(nodeId) {
6756
+ const treeComponent = this.tree();
6757
+ if (!treeComponent) {
6657
6758
  return;
6658
6759
  }
6659
- // Mark the node itself if it's selected
6660
- this.markNodeAsSelectedIfNeeded(node);
6661
- // Recursively mark children
6662
- const nodeChildren = node['children'];
6663
- if (nodeChildren) {
6664
- nodeChildren.forEach((child) => {
6665
- this.markNodesAsSelected(child);
6666
- });
6760
+ // Set flag to prevent recursive updates during batch operation
6761
+ this.isUpdatingSelection = true;
6762
+ try {
6763
+ // Collect all nodes to deselect (node + all descendants)
6764
+ const nodesToDeselect = new Set([nodeId]);
6765
+ await this.collectAllDescendants(nodeId, nodesToDeselect);
6766
+ // Batch update selectedNodeIds
6767
+ const currentSelected = this.selectedNodeIds();
6768
+ const newSelected = currentSelected.filter((id) => !nodesToDeselect.has(id));
6769
+ this.selectedNodeIds.set(newSelected);
6770
+ // Batch deselect in tree component (with small delays to prevent glitches)
6771
+ for (const id of nodesToDeselect) {
6772
+ try {
6773
+ const node = treeComponent.findNode(id);
6774
+ if (node) {
6775
+ treeComponent.deselectNode(id);
6776
+ // Small delay to prevent overwhelming the tree component
6777
+ await new Promise((resolve) => setTimeout(resolve, 5));
6778
+ }
6779
+ }
6780
+ catch {
6781
+ // Node might not be in tree
6782
+ }
6783
+ }
6667
6784
  }
6668
- }
6669
- /**
6670
- * Marks a single node as selected if it's in the selectedNodeIds list.
6671
- */
6672
- markNodeAsSelectedIfNeeded(node) {
6673
- const selectedIds = this.selectedNodeIds();
6674
- const nodeId = String(node['id'] ?? '');
6675
- if (nodeId && selectedIds.includes(nodeId)) {
6676
- node['selected'] = true;
6785
+ finally {
6786
+ this.isUpdatingSelection = false;
6677
6787
  }
6678
6788
  }
6679
6789
  /**
6680
- * Marks a node and its children as disabled if the node ID matches the excluded ID.
6790
+ * Recursively deselects all children of a parent node
6681
6791
  */
6682
- markNodeAsDisabled(node, excludedId) {
6683
- const nodeId = String(node['id'] ?? '');
6684
- if (nodeId === excludedId) {
6685
- node['disabled'] = true;
6792
+ async loadAndDeselectChildrenRecursively(parentId) {
6793
+ if (!this.treeData || !this.treeConfig || !parentId || parentId === 'all') {
6794
+ return;
6686
6795
  }
6687
- // Recursively mark children
6688
- const nodeChildren = node['children'];
6689
- if (nodeChildren) {
6690
- nodeChildren.forEach((child) => {
6691
- this.markNodeAsDisabled(child, excludedId);
6692
- });
6796
+ const treeComponent = this.tree();
6797
+ if (!treeComponent) {
6798
+ return;
6799
+ }
6800
+ try {
6801
+ // Load children using datasource
6802
+ const childNodes = await this.datasource(parentId);
6803
+ if (!childNodes || childNodes.length === 0) {
6804
+ return;
6805
+ }
6806
+ const childIdsToDeselect = [];
6807
+ // Collect all child IDs
6808
+ for (const childNode of childNodes) {
6809
+ const childId = String(childNode['id'] ?? '');
6810
+ if (childId && childId !== 'all') {
6811
+ childIdsToDeselect.push(childId);
6812
+ }
6813
+ }
6814
+ // Remove children from selectedNodeIds
6815
+ const currentSelected = this.selectedNodeIds();
6816
+ const updatedSelected = currentSelected.filter((id) => !childIdsToDeselect.includes(id));
6817
+ this.selectedNodeIds.set(updatedSelected);
6818
+ // Deselect in tree component
6819
+ for (const childId of childIdsToDeselect) {
6820
+ try {
6821
+ treeComponent.deselectNode(childId);
6822
+ }
6823
+ catch {
6824
+ // Node might not be in tree
6825
+ }
6826
+ }
6827
+ // Recursively deselect children of each child
6828
+ await Promise.all(childIdsToDeselect.map((childId) => this.loadAndDeselectChildrenRecursively(childId)));
6829
+ }
6830
+ catch (error) {
6831
+ console.error(`Error deselecting children for node ${parentId}:`, error);
6693
6832
  }
6694
6833
  }
6695
6834
  /**
6696
- * Processes root node: marks excluded as disabled, marks selected, and syncs selection with tree component
6835
+ * Updates parent states based on children selection (select/intermediate)
6836
+ * Called after a node is selected/deselected to update parent checkbox states
6697
6837
  */
6698
- processRootNode(rootNode) {
6699
- const excludedId = this.excludedNodeId();
6700
- if (excludedId) {
6701
- this.markNodeAsDisabled(rootNode, excludedId);
6838
+ async updateParentStates(changedNodeId) {
6839
+ if (!this.treeData || !this.treeConfig || !this.allowMultiple()) {
6840
+ return;
6841
+ }
6842
+ const parentKey = this.treeData.categoryEntityDef?.parentKey;
6843
+ if (!parentKey) {
6844
+ return; // No parent key means flat structure
6702
6845
  }
6703
- this.markNodesAsSelected(rootNode);
6704
- // Sync selection with tree component after a short delay to ensure tree is rendered
6705
6846
  const treeComponent = this.tree();
6706
- if (treeComponent) {
6707
- setTimeout(() => {
6708
- const selectedIds = this.selectedNodeIds();
6709
- selectedIds.forEach((id) => {
6710
- if (id && id !== 'all') {
6711
- treeComponent.selectNode(id);
6847
+ if (!treeComponent) {
6848
+ return;
6849
+ }
6850
+ // Start from the changed node's parent and work up the tree
6851
+ const processedParents = new Set();
6852
+ let currentId = changedNodeId;
6853
+ // Process all parents up to root
6854
+ while (currentId && currentId !== 'all') {
6855
+ const nodeData = this.nodeDataCache.get(currentId);
6856
+ if (!nodeData) {
6857
+ break;
6858
+ }
6859
+ const parentId = nodeData[parentKey];
6860
+ const parentIdStr = String(parentId);
6861
+ if (!parentId || parentId === 'all' || parentId === currentId || processedParents.has(parentIdStr)) {
6862
+ break;
6863
+ }
6864
+ processedParents.add(parentIdStr);
6865
+ // Load all children of this parent
6866
+ try {
6867
+ const childNodes = await this.datasource(parentIdStr);
6868
+ if (!childNodes || childNodes.length === 0) {
6869
+ currentId = parentIdStr;
6870
+ continue;
6871
+ }
6872
+ // Get current selection state (updated after each parent change)
6873
+ const currentSelected = this.selectedNodeIds();
6874
+ const selectedSet = new Set(currentSelected);
6875
+ let selectedCount = 0;
6876
+ let totalCount = 0;
6877
+ // Count selected children
6878
+ for (const childNode of childNodes) {
6879
+ const childId = String(childNode['id'] ?? '');
6880
+ if (childId && childId !== 'all') {
6881
+ totalCount++;
6882
+ if (selectedSet.has(childId)) {
6883
+ selectedCount++;
6884
+ }
6712
6885
  }
6713
- });
6714
- }, 0);
6886
+ }
6887
+ // Update parent selection state
6888
+ const isParentSelected = currentSelected.includes(parentIdStr);
6889
+ if (totalCount > 0) {
6890
+ if (selectedCount === totalCount) {
6891
+ // All children selected - select parent
6892
+ if (!isParentSelected) {
6893
+ this.selectedNodeIds.set([...currentSelected, parentIdStr]);
6894
+ try {
6895
+ treeComponent.selectNode(parentIdStr);
6896
+ }
6897
+ catch {
6898
+ // Parent might not be in tree
6899
+ }
6900
+ }
6901
+ }
6902
+ else if (selectedCount > 0) {
6903
+ // Some children selected - parent should be in intermediate state
6904
+ // The tree component handles intermediate state automatically via selectionBehavior
6905
+ // We just need to ensure parent is not fully selected
6906
+ if (isParentSelected) {
6907
+ // Deselect parent to show intermediate state
6908
+ this.selectedNodeIds.set(currentSelected.filter((id) => id !== parentIdStr));
6909
+ try {
6910
+ treeComponent.deselectNode(parentIdStr);
6911
+ }
6912
+ catch {
6913
+ // Parent might not be in tree
6914
+ }
6915
+ }
6916
+ }
6917
+ else {
6918
+ // No children selected - deselect parent
6919
+ if (isParentSelected) {
6920
+ this.selectedNodeIds.set(currentSelected.filter((id) => id !== parentIdStr));
6921
+ try {
6922
+ treeComponent.deselectNode(parentIdStr);
6923
+ }
6924
+ catch {
6925
+ // Parent might not be in tree
6926
+ }
6927
+ }
6928
+ }
6929
+ }
6930
+ // Cache parent data if not already cached
6931
+ if (!this.nodeDataCache.has(parentIdStr)) {
6932
+ const parentData = await this.fetchItemById(parentIdStr);
6933
+ if (parentData) {
6934
+ this.nodeDataCache.set(parentIdStr, parentData);
6935
+ }
6936
+ }
6937
+ currentId = parentIdStr;
6938
+ }
6939
+ catch (error) {
6940
+ console.error(`Error updating parent state for ${parentIdStr}:`, error);
6941
+ break;
6942
+ }
6715
6943
  }
6716
6944
  }
6717
6945
  /**
6718
- * Processes child nodes: marks excluded as disabled, marks selected, and syncs selection
6946
+ * Recursively loads and selects all children of a parent node using the datasource
6947
+ * This method directly calls datasource without expanding/collapsing nodes to avoid UI glitches
6719
6948
  */
6720
- processChildNodes(childNodes) {
6721
- const excludedId = this.excludedNodeId();
6722
- const selectedIds = this.selectedNodeIds();
6723
- childNodes.forEach((node) => {
6724
- const nodeId = String(node['id'] ?? '');
6725
- if (excludedId && nodeId === excludedId) {
6726
- node['disabled'] = true;
6727
- }
6728
- if (nodeId && selectedIds.includes(nodeId)) {
6729
- node['selected'] = true;
6730
- }
6731
- });
6732
- // Sync selection with tree component
6949
+ async loadAndSelectChildrenRecursively(parentId) {
6950
+ if (!this.treeData || !this.treeConfig || !parentId || parentId === 'all') {
6951
+ return;
6952
+ }
6733
6953
  const treeComponent = this.tree();
6734
- if (treeComponent) {
6735
- setTimeout(() => {
6954
+ if (!treeComponent) {
6955
+ return;
6956
+ }
6957
+ try {
6958
+ // Directly call datasource to get children data without expanding the node
6959
+ // This avoids UI glitches from expand/collapse operations
6960
+ // If node has no children, datasource will return empty array
6961
+ const childNodes = await this.datasource(parentId);
6962
+ if (!childNodes || childNodes.length === 0) {
6963
+ return; // No children to process
6964
+ }
6965
+ // Collect all child IDs to add to selectedNodeIds
6966
+ const childIdsToSelect = [];
6967
+ // Process all children and cache their data
6968
+ for (const childNode of childNodes) {
6969
+ const childId = String(childNode['id'] ?? '');
6970
+ if (childId && childId !== 'all') {
6971
+ childIdsToSelect.push(childId);
6972
+ // Cache node data for getSelectedItems
6973
+ // Try to get data from 'data' property first, then fallback to node itself
6974
+ let nodeData = childNode['data'];
6975
+ // If no data property, try to extract data from the node
6976
+ if (!nodeData || typeof nodeData !== 'object') {
6977
+ // Create a data object from the node properties
6978
+ const valueField = this.treeConfig.valueField || 'id';
6979
+ const textField = this.treeConfig.textField || 'title';
6980
+ nodeData = {
6981
+ [valueField]: childId,
6982
+ [textField]: childNode['title'] ?? '',
6983
+ ...childNode,
6984
+ };
6985
+ }
6986
+ // Cache the node data
6987
+ if (nodeData && typeof nodeData === 'object') {
6988
+ this.nodeDataCache.set(childId, nodeData);
6989
+ }
6990
+ }
6991
+ }
6992
+ if (childIdsToSelect.length === 0) {
6993
+ return; // No valid children to select
6994
+ }
6995
+ // Update selectedNodeIds to include all children
6996
+ const currentSelected = this.selectedNodeIds();
6997
+ const newSelected = [...currentSelected];
6998
+ let hasNewSelections = false;
6999
+ for (const childId of childIdsToSelect) {
7000
+ if (!newSelected.includes(childId)) {
7001
+ newSelected.push(childId);
7002
+ hasNewSelections = true;
7003
+ }
7004
+ }
7005
+ if (hasNewSelections) {
7006
+ this.selectedNodeIds.set(newSelected);
7007
+ }
7008
+ // Try to select children in tree component if they're already loaded
7009
+ // If not loaded yet, they'll be selected when the tree loads them (via markNodeAsSelectedIfNeeded)
7010
+ for (const childId of childIdsToSelect) {
7011
+ try {
7012
+ // Only try to select if node exists in tree (might not be loaded if parent isn't expanded)
7013
+ const node = treeComponent.findNode(childId);
7014
+ if (node) {
7015
+ treeComponent.selectNode(childId);
7016
+ }
7017
+ }
7018
+ catch {
7019
+ // If selection fails, it's okay - we've already added to selectedNodeIds
7020
+ // The tree will sync selection when the node is loaded via datasource callback
7021
+ }
7022
+ }
7023
+ // Recursively load and select children of each child
7024
+ // Use Promise.all for parallel processing to improve performance
7025
+ await Promise.all(childIdsToSelect.map((childId) => this.loadAndSelectChildrenRecursively(childId)));
7026
+ }
7027
+ catch (error) {
7028
+ console.error(`Error loading children for node ${parentId}:`, error);
7029
+ }
7030
+ }
7031
+ //#endregion
7032
+ async updateSelectedNodes(selectedIds) {
7033
+ if (!selectedIds || selectedIds.length === 0) {
7034
+ this.selectedNodeIds.set([]);
7035
+ return;
7036
+ }
7037
+ const ids = selectedIds.filter((id) => id && id !== 'all');
7038
+ // Set flag to prevent recursive updates during initialization
7039
+ this.isUpdatingSelection = true;
7040
+ try {
7041
+ // Fetch node data for pre-selected items that aren't in the cache
7042
+ await this.loadMissingNodeData(ids);
7043
+ // Collect all nodes to select (pre-selected + all their descendants)
7044
+ const allNodesToSelect = new Set(ids);
7045
+ for (const nodeId of ids) {
7046
+ await this.collectAllDescendants(nodeId, allNodesToSelect);
7047
+ }
7048
+ // Set all selected nodes at once
7049
+ this.selectedNodeIds.set(Array.from(allNodesToSelect));
7050
+ // Expand parents of pre-selected nodes so they're visible in the tree
7051
+ await this.expandParentsOfSelectedNodes(Array.from(allNodesToSelect));
7052
+ // Sync selection with tree component - wait for tree to be ready
7053
+ await this.syncSelectionWithTree(Array.from(allNodesToSelect));
7054
+ }
7055
+ finally {
7056
+ this.isUpdatingSelection = false;
7057
+ }
7058
+ }
7059
+ /**
7060
+ * Updates all parent states based on their children's selection
7061
+ * Used during initialization to ensure proper parent states (select/intermediate)
7062
+ */
7063
+ async updateAllParentStates(selectedIds) {
7064
+ if (!this.treeData || !this.treeConfig || !this.allowMultiple() || selectedIds.length === 0) {
7065
+ return;
7066
+ }
7067
+ const parentKey = this.treeData.categoryEntityDef?.parentKey;
7068
+ if (!parentKey) {
7069
+ return;
7070
+ }
7071
+ const treeComponent = this.tree();
7072
+ if (!treeComponent) {
7073
+ return;
7074
+ }
7075
+ const selectedSet = new Set(selectedIds);
7076
+ const processedParents = new Set();
7077
+ // Collect all unique parent IDs
7078
+ for (const selectedId of selectedIds) {
7079
+ if (selectedId === 'all') {
7080
+ continue;
7081
+ }
7082
+ const nodeData = this.nodeDataCache.get(selectedId);
7083
+ if (!nodeData) {
7084
+ continue;
7085
+ }
7086
+ let currentId = selectedId;
7087
+ while (currentId && currentId !== 'all') {
7088
+ const currentNodeData = this.nodeDataCache.get(currentId);
7089
+ if (!currentNodeData) {
7090
+ break;
7091
+ }
7092
+ const parentId = currentNodeData[parentKey];
7093
+ const parentIdStr = String(parentId);
7094
+ if (!parentId || parentId === 'all' || parentId === currentId || processedParents.has(parentIdStr)) {
7095
+ break;
7096
+ }
7097
+ processedParents.add(parentIdStr);
7098
+ currentId = parentIdStr;
7099
+ }
7100
+ }
7101
+ // Process parents multiple times to handle cascading parent selections
7102
+ // (when a parent is selected, its parent might also need to be selected)
7103
+ let hasChanges = true;
7104
+ let iterations = 0;
7105
+ const maxIterations = 10; // Prevent infinite loops
7106
+ while (hasChanges && iterations < maxIterations) {
7107
+ iterations++;
7108
+ hasChanges = false;
7109
+ // Update selectedSet with current selectedNodeIds
7110
+ const currentSelected = this.selectedNodeIds();
7111
+ currentSelected.forEach((id) => selectedSet.add(id));
7112
+ // Update each parent's state
7113
+ for (const parentId of processedParents) {
7114
+ try {
7115
+ const childNodes = await this.datasource(parentId);
7116
+ if (!childNodes || childNodes.length === 0) {
7117
+ continue;
7118
+ }
7119
+ let selectedCount = 0;
7120
+ let totalCount = 0;
7121
+ for (const childNode of childNodes) {
7122
+ const childId = String(childNode['id'] ?? '');
7123
+ if (childId && childId !== 'all') {
7124
+ totalCount++;
7125
+ if (selectedSet.has(childId)) {
7126
+ selectedCount++;
7127
+ }
7128
+ }
7129
+ }
7130
+ const isParentSelected = currentSelected.includes(parentId);
7131
+ if (totalCount > 0) {
7132
+ if (selectedCount === totalCount) {
7133
+ // All children selected - select parent
7134
+ if (!isParentSelected) {
7135
+ this.selectedNodeIds.set([...currentSelected, parentId]);
7136
+ selectedSet.add(parentId);
7137
+ hasChanges = true;
7138
+ try {
7139
+ treeComponent.selectNode(parentId);
7140
+ }
7141
+ catch {
7142
+ // Parent might not be in tree
7143
+ }
7144
+ }
7145
+ }
7146
+ else if (selectedCount > 0) {
7147
+ // Some children selected - parent should be in intermediate state
7148
+ if (isParentSelected) {
7149
+ // Deselect parent to show intermediate state
7150
+ this.selectedNodeIds.set(currentSelected.filter((id) => id !== parentId));
7151
+ selectedSet.delete(parentId);
7152
+ hasChanges = true;
7153
+ try {
7154
+ treeComponent.deselectNode(parentId);
7155
+ }
7156
+ catch {
7157
+ // Parent might not be in tree
7158
+ }
7159
+ }
7160
+ }
7161
+ else {
7162
+ // No children selected - deselect parent
7163
+ if (isParentSelected) {
7164
+ this.selectedNodeIds.set(currentSelected.filter((id) => id !== parentId));
7165
+ selectedSet.delete(parentId);
7166
+ hasChanges = true;
7167
+ try {
7168
+ treeComponent.deselectNode(parentId);
7169
+ }
7170
+ catch {
7171
+ // Parent might not be in tree
7172
+ }
7173
+ }
7174
+ }
7175
+ }
7176
+ }
7177
+ catch (error) {
7178
+ console.error(`Error updating parent state for ${parentId}:`, error);
7179
+ }
7180
+ }
7181
+ }
7182
+ }
7183
+ /**
7184
+ * Expands parents of selected nodes so they're visible when popup opens
7185
+ */
7186
+ async expandParentsOfSelectedNodes(selectedIds) {
7187
+ if (!this.treeData || !this.treeConfig || selectedIds.length === 0) {
7188
+ return;
7189
+ }
7190
+ const parentKey = this.treeData.categoryEntityDef?.parentKey;
7191
+ if (!parentKey) {
7192
+ return; // No parent key means flat structure
7193
+ }
7194
+ const treeComponent = this.tree();
7195
+ if (!treeComponent) {
7196
+ return;
7197
+ }
7198
+ // Collect all unique parent IDs that need to be expanded
7199
+ const parentsToExpand = new Set();
7200
+ for (const selectedId of selectedIds) {
7201
+ if (selectedId === 'all') {
7202
+ continue;
7203
+ }
7204
+ const nodeData = this.nodeDataCache.get(selectedId);
7205
+ if (!nodeData) {
7206
+ continue;
7207
+ }
7208
+ const parentId = nodeData[parentKey];
7209
+ if (parentId && parentId !== 'all') {
7210
+ const parentIdStr = String(parentId);
7211
+ parentsToExpand.add(parentIdStr);
7212
+ // Also expand grandparents, etc.
7213
+ let currentParentId = parentIdStr;
7214
+ while (currentParentId) {
7215
+ const parentData = this.nodeDataCache.get(currentParentId);
7216
+ if (!parentData) {
7217
+ break;
7218
+ }
7219
+ const grandParentId = parentData[parentKey];
7220
+ if (grandParentId && grandParentId !== 'all' && grandParentId !== currentParentId) {
7221
+ parentsToExpand.add(String(grandParentId));
7222
+ currentParentId = String(grandParentId);
7223
+ }
7224
+ else {
7225
+ break;
7226
+ }
7227
+ }
7228
+ }
7229
+ }
7230
+ // Expand all parents (starting from root to leaves)
7231
+ const sortedParents = Array.from(parentsToExpand).sort((a, b) => {
7232
+ // Simple sort - expand root nodes first
7233
+ return a.localeCompare(b);
7234
+ });
7235
+ for (const parentId of sortedParents) {
7236
+ try {
7237
+ if (!treeComponent.isNodeExpanded(parentId)) {
7238
+ await treeComponent.expandNode(parentId);
7239
+ // Small delay to ensure children are loaded
7240
+ await new Promise((resolve) => setTimeout(resolve, 50));
7241
+ }
7242
+ }
7243
+ catch {
7244
+ // Parent might not be in tree yet, ignore
7245
+ }
7246
+ }
7247
+ }
7248
+ /**
7249
+ * Selects parents whose all children are selected
7250
+ * This ensures that when all children of a parent are selected, the parent also appears selected
7251
+ */
7252
+ async selectParentsWithAllChildrenSelected(selectedIds) {
7253
+ if (!this.treeData || !this.treeConfig || selectedIds.length === 0) {
7254
+ return;
7255
+ }
7256
+ const parentKey = this.treeData.categoryEntityDef?.parentKey;
7257
+ if (!parentKey) {
7258
+ return; // No parent key means flat structure
7259
+ }
7260
+ // Use current selectedNodeIds signal value (may have been updated)
7261
+ const currentSelectedIds = this.selectedNodeIds();
7262
+ const selectedSet = new Set(currentSelectedIds);
7263
+ const processedParents = new Set();
7264
+ const parentsToSelect = new Set();
7265
+ // For each selected node, check its parent
7266
+ for (const selectedId of currentSelectedIds) {
7267
+ if (selectedId === 'all') {
7268
+ continue;
7269
+ }
7270
+ // Get parent ID from cached data
7271
+ const nodeData = this.nodeDataCache.get(selectedId);
7272
+ if (!nodeData) {
7273
+ continue;
7274
+ }
7275
+ const parentId = nodeData[parentKey];
7276
+ if (!parentId || parentId === 'all' || processedParents.has(String(parentId))) {
7277
+ continue;
7278
+ }
7279
+ const parentIdStr = String(parentId);
7280
+ processedParents.add(parentIdStr);
7281
+ // Load all children of this parent
7282
+ try {
7283
+ const childNodes = await this.datasource(parentIdStr);
7284
+ if (!childNodes || childNodes.length === 0) {
7285
+ continue;
7286
+ }
7287
+ // Check if all children are selected
7288
+ let allChildrenSelected = true;
7289
+ let hasChildren = false;
7290
+ for (const childNode of childNodes) {
7291
+ const childId = String(childNode['id'] ?? '');
7292
+ if (childId && childId !== 'all') {
7293
+ hasChildren = true;
7294
+ if (!selectedSet.has(childId)) {
7295
+ allChildrenSelected = false;
7296
+ break;
7297
+ }
7298
+ }
7299
+ }
7300
+ // If parent has children and all are selected, mark parent for selection
7301
+ if (hasChildren && allChildrenSelected) {
7302
+ parentsToSelect.add(parentIdStr);
7303
+ // Cache parent data if not already cached
7304
+ if (!this.nodeDataCache.has(parentIdStr)) {
7305
+ const parentData = await this.fetchItemById(parentIdStr);
7306
+ if (parentData) {
7307
+ this.nodeDataCache.set(parentIdStr, parentData);
7308
+ }
7309
+ }
7310
+ }
7311
+ }
7312
+ catch (error) {
7313
+ console.error(`Error checking parent ${parentIdStr}:`, error);
7314
+ }
7315
+ }
7316
+ // Add parents to selectedNodeIds
7317
+ if (parentsToSelect.size > 0) {
7318
+ const currentSelected = this.selectedNodeIds();
7319
+ const newSelected = [...currentSelected];
7320
+ let hasNewSelections = false;
7321
+ for (const parentId of parentsToSelect) {
7322
+ if (!newSelected.includes(parentId)) {
7323
+ newSelected.push(parentId);
7324
+ hasNewSelections = true;
7325
+ }
7326
+ }
7327
+ if (hasNewSelections) {
7328
+ this.selectedNodeIds.set(newSelected);
7329
+ // Recursively check parents of these parents
7330
+ const updatedSelected = this.selectedNodeIds();
7331
+ await this.selectParentsWithAllChildrenSelected(updatedSelected);
7332
+ }
7333
+ }
7334
+ }
7335
+ /**
7336
+ * Syncs selection state with the tree component
7337
+ * Handles cases where nodes might not be in the tree structure yet
7338
+ */
7339
+ async syncSelectionWithTree(selectedIds) {
7340
+ const treeComponent = this.tree();
7341
+ if (!treeComponent || selectedIds.length === 0) {
7342
+ return;
7343
+ }
7344
+ // Wait for tree to be fully initialized and rendered
7345
+ await new Promise((resolve) => setTimeout(resolve, 300));
7346
+ // Try multiple times to sync selection (nodes might load progressively)
7347
+ for (let attempt = 0; attempt < 3; attempt++) {
7348
+ let syncedCount = 0;
7349
+ selectedIds.forEach((id) => {
7350
+ if (id && id !== 'all') {
7351
+ try {
7352
+ const node = treeComponent.findNode(id);
7353
+ if (node) {
7354
+ // Node exists in tree, select it
7355
+ treeComponent.selectNode(id);
7356
+ syncedCount++;
7357
+ }
7358
+ }
7359
+ catch {
7360
+ // Node not in tree yet - will be selected when loaded via datasource callback
7361
+ }
7362
+ }
7363
+ });
7364
+ // If all nodes are synced, we're done
7365
+ if (syncedCount === selectedIds.length) {
7366
+ break;
7367
+ }
7368
+ // Wait a bit before next attempt
7369
+ if (attempt < 2) {
7370
+ await new Promise((resolve) => setTimeout(resolve, 100));
7371
+ }
7372
+ }
7373
+ // After syncing, read back from tree component to get any parents that were automatically selected
7374
+ // (due to intermediate-nested behavior when all children are selected)
7375
+ if (this.allowMultiple()) {
7376
+ try {
7377
+ const treeSelectedNodes = treeComponent.getSelectedNodes();
7378
+ const treeSelectedIds = treeSelectedNodes
7379
+ .map((node) => String(node['id'] ?? ''))
7380
+ .filter((id) => id && id !== 'all');
7381
+ // Update selectedNodeIds to match tree component's state (includes auto-selected parents)
7382
+ this.selectedNodeIds.set(treeSelectedIds);
7383
+ }
7384
+ catch {
7385
+ // Tree component might not be ready yet
7386
+ }
7387
+ }
7388
+ // Note: Selection state is maintained in selectedNodeIds signal
7389
+ // The tree component will sync selection state when nodes are loaded via datasource
7390
+ // We mark nodes as selected in the datasource callback by setting node.selected = true
7391
+ // Nodes that aren't in the tree yet will be selected when their parent is expanded
7392
+ }
7393
+ /**
7394
+ * Loads node data for IDs that are selected but not yet in the cache.
7395
+ * This is critical for pre-selected values in collapsed branches.
7396
+ */
7397
+ async loadMissingNodeData(selectedIds) {
7398
+ if (!this.treeData || !this.treeConfig || selectedIds.length === 0) {
7399
+ return;
7400
+ }
7401
+ // Find IDs that are selected but not in cache
7402
+ const missingIds = selectedIds.filter((id) => !this.nodeDataCache.has(id));
7403
+ if (missingIds.length === 0) {
7404
+ return;
7405
+ }
7406
+ try {
7407
+ const valueField = this.treeConfig.valueField || 'id';
7408
+ const categoryEntityQueryFunc = this.treeData.categoryEntityQueryFunc;
7409
+ if (!categoryEntityQueryFunc) {
7410
+ return;
7411
+ }
7412
+ // Query for missing nodes by their IDs
7413
+ const event = {
7414
+ ...this.treeData.basicQueryEvent,
7415
+ filter: {
7416
+ filters: missingIds.map((id) => ({
7417
+ field: valueField,
7418
+ value: id,
7419
+ operator: { type: 'equal' },
7420
+ })),
7421
+ logic: 'or',
7422
+ },
7423
+ };
7424
+ const res = await categoryEntityQueryFunc(event);
7425
+ if (res?.items) {
7426
+ // Cache the fetched node data
7427
+ res.items.forEach((item) => {
7428
+ const itemId = String(item[valueField] ?? '');
7429
+ if (itemId) {
7430
+ this.nodeDataCache.set(itemId, item);
7431
+ }
7432
+ });
7433
+ }
7434
+ }
7435
+ catch (error) {
7436
+ console.error('Error loading missing node data:', error);
7437
+ }
7438
+ }
7439
+ /**
7440
+ * Marks nodes as selected in the tree structure based on selectedNodeIds.
7441
+ * This ensures pre-selected nodes appear selected when the tree is rendered.
7442
+ */
7443
+ markNodesAsSelected(node) {
7444
+ const selectedIds = this.selectedNodeIds();
7445
+ if (selectedIds.length === 0) {
7446
+ return;
7447
+ }
7448
+ // Mark the node itself if it's selected
7449
+ this.markNodeAsSelectedIfNeeded(node);
7450
+ // Recursively mark children
7451
+ const nodeChildren = node['children'];
7452
+ if (nodeChildren) {
7453
+ nodeChildren.forEach((child) => {
7454
+ this.markNodesAsSelected(child);
7455
+ });
7456
+ }
7457
+ }
7458
+ /**
7459
+ * Marks a single node as selected if it's in the selectedNodeIds list.
7460
+ */
7461
+ markNodeAsSelectedIfNeeded(node) {
7462
+ const selectedIds = this.selectedNodeIds();
7463
+ const nodeId = String(node['id'] ?? '');
7464
+ if (nodeId && selectedIds.includes(nodeId)) {
7465
+ node['selected'] = true;
7466
+ }
7467
+ }
7468
+ /**
7469
+ * Marks a node and its children as disabled if the node ID matches the excluded ID.
7470
+ */
7471
+ markNodeAsDisabled(node, excludedId) {
7472
+ const nodeId = String(node['id'] ?? '');
7473
+ if (nodeId === excludedId) {
7474
+ node['disabled'] = true;
7475
+ }
7476
+ // Recursively mark children
7477
+ const nodeChildren = node['children'];
7478
+ if (nodeChildren) {
7479
+ nodeChildren.forEach((child) => {
7480
+ this.markNodeAsDisabled(child, excludedId);
7481
+ });
7482
+ }
7483
+ }
7484
+ /**
7485
+ * Processes root node: marks excluded as disabled, marks selected, and syncs selection with tree component
7486
+ */
7487
+ processRootNode(rootNode) {
7488
+ const excludedId = this.excludedNodeId();
7489
+ if (excludedId) {
7490
+ this.markNodeAsDisabled(rootNode, excludedId);
7491
+ }
7492
+ this.markNodesAsSelected(rootNode);
7493
+ // Sync selection with tree component after a short delay to ensure tree is rendered
7494
+ const treeComponent = this.tree();
7495
+ if (treeComponent) {
7496
+ setTimeout(() => {
7497
+ const selectedIds = this.selectedNodeIds();
7498
+ selectedIds.forEach((id) => {
7499
+ if (id && id !== 'all') {
7500
+ treeComponent.selectNode(id);
7501
+ }
7502
+ });
7503
+ }, 0);
7504
+ }
7505
+ }
7506
+ /**
7507
+ * Processes child nodes: marks excluded as disabled, marks selected, and syncs selection
7508
+ */
7509
+ processChildNodes(childNodes) {
7510
+ const excludedId = this.excludedNodeId();
7511
+ const selectedIds = this.selectedNodeIds();
7512
+ childNodes.forEach((node) => {
7513
+ const nodeId = String(node['id'] ?? '');
7514
+ if (excludedId && nodeId === excludedId) {
7515
+ node['disabled'] = true;
7516
+ }
7517
+ if (nodeId && selectedIds.includes(nodeId)) {
7518
+ node['selected'] = true;
7519
+ }
7520
+ });
7521
+ // Sync selection with tree component
7522
+ const treeComponent = this.tree();
7523
+ if (treeComponent) {
7524
+ setTimeout(() => {
6736
7525
  childNodes.forEach((node) => {
6737
7526
  const nodeId = String(node['id'] ?? '');
6738
7527
  if (nodeId && selectedIds.includes(nodeId) && nodeId !== 'all') {
@@ -6887,41 +7676,107 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
6887
7676
  const textField = this.treeConfig.textField || 'title';
6888
7677
  return String(nodeData[textField] ?? '');
6889
7678
  }
6890
- async getSelectedItems() {
6891
- // Use tree component's getSelectedNodes if available for more accurate selection
6892
- const treeComponent = this.tree();
6893
- if (treeComponent) {
6894
- const selectedNodes = treeComponent.getSelectedNodes();
6895
- const items = await Promise.all(selectedNodes.map(async (node) => {
6896
- const nodeId = String(node['id'] ?? '');
6897
- if (nodeId === 'all') {
6898
- return null;
6899
- }
6900
- const nodeData = node['data'] || node;
6901
- if (!nodeData) {
6902
- return null;
6903
- }
6904
- // Calculate path for this node
6905
- const path = await this.calculateNodePath(nodeId, nodeData);
6906
- return {
6907
- ...nodeData,
6908
- id: nodeId,
6909
- path: path,
6910
- };
6911
- }));
6912
- return items.filter((item) => item != null);
7679
+ /**
7680
+ * Checks if a node is a leaf node (has no children)
7681
+ */
7682
+ async isLeafNode(nodeId, treeComponent) {
7683
+ if (!treeComponent) {
7684
+ // If no tree component, check if node has children by querying
7685
+ return await this.checkIfNodeHasChildren(nodeId);
7686
+ }
7687
+ try {
7688
+ const node = treeComponent.findNode(nodeId);
7689
+ if (!node) {
7690
+ // Node not found in tree, check via query
7691
+ return await this.checkIfNodeHasChildren(nodeId);
7692
+ }
7693
+ // Check if node has children
7694
+ const children = node['children'];
7695
+ const childrenCount = node['childrenCount'];
7696
+ // If children are loaded, check the array
7697
+ if (children !== undefined) {
7698
+ return !children || children.length === 0;
7699
+ }
7700
+ // If childrenCount is available, use it
7701
+ if (childrenCount !== undefined) {
7702
+ return childrenCount === 0;
7703
+ }
7704
+ // If neither is available, try to check via query
7705
+ return await this.checkIfNodeHasChildren(nodeId);
7706
+ }
7707
+ catch {
7708
+ // If findNode fails, check via query
7709
+ return await this.checkIfNodeHasChildren(nodeId);
7710
+ }
7711
+ }
7712
+ /**
7713
+ * Checks if a node has children by querying the data source
7714
+ */
7715
+ async checkIfNodeHasChildren(nodeId) {
7716
+ if (!this.treeData?.categoryEntityQueryFunc || !this.treeConfig) {
7717
+ return true; // Assume leaf if we can't check
7718
+ }
7719
+ try {
7720
+ const parentKey = this.treeData.categoryEntityDef?.parentKey;
7721
+ if (!parentKey) {
7722
+ return true; // No parent key means flat structure, all nodes are leaves
7723
+ }
7724
+ const event = {
7725
+ ...this.treeData.basicQueryEvent,
7726
+ filter: {
7727
+ field: parentKey,
7728
+ value: nodeId,
7729
+ operator: { type: 'equal' },
7730
+ },
7731
+ take: 1, // Only need to check if any children exist
7732
+ };
7733
+ const res = await this.treeData.categoryEntityQueryFunc(event);
7734
+ return !res?.items || res.items.length === 0;
6913
7735
  }
6914
- // Fallback to cached data
7736
+ catch {
7737
+ return true; // Assume leaf on error
7738
+ }
7739
+ }
7740
+ async getSelectedItems() {
7741
+ // Always use selectedNodeIds as the source of truth
7742
+ // This ensures we get all selected nodes, even if they're not in the tree structure yet
6915
7743
  const selectedIds = this.selectedNodeIds();
6916
7744
  if (selectedIds.length === 0) {
6917
7745
  return [];
6918
7746
  }
7747
+ const treeComponent = this.tree();
6919
7748
  // Get node data from cache and calculate paths
7749
+ // For nodes not in cache, try to get from tree component
6920
7750
  const items = await Promise.all(selectedIds.map(async (id) => {
6921
- const nodeData = this.nodeDataCache.get(id);
7751
+ // First try to get from cache
7752
+ let nodeData = this.nodeDataCache.get(id);
7753
+ // If not in cache, try to get from tree component
7754
+ if (!nodeData && treeComponent) {
7755
+ try {
7756
+ const node = treeComponent.findNode(id);
7757
+ if (node) {
7758
+ nodeData = node['data'] || node;
7759
+ // Cache it for future use
7760
+ if (nodeData && typeof nodeData === 'object') {
7761
+ this.nodeDataCache.set(id, nodeData);
7762
+ }
7763
+ }
7764
+ }
7765
+ catch {
7766
+ // Node not found in tree, continue with cache lookup
7767
+ }
7768
+ }
7769
+ // If still no data, skip this node
6922
7770
  if (!nodeData) {
6923
7771
  return null;
6924
7772
  }
7773
+ // When selectionBehavior is intermediate-nested, only return leaf nodes
7774
+ if (this.allowMultiple()) {
7775
+ const isLeaf = await this.isLeafNode(id, treeComponent ?? null);
7776
+ if (!isLeaf) {
7777
+ return null; // Skip non-leaf nodes
7778
+ }
7779
+ }
6925
7780
  const path = await this.calculateNodePath(id, nodeData);
6926
7781
  return {
6927
7782
  ...nodeData,
@@ -6936,9 +7791,9 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
6936
7791
  <div class="ax-flex ax-flex-col ax-h-full">
6937
7792
  @if (loading()) {
6938
7793
  <div class="ax-p-4 ax-flex ax-flex-col ax-gap-3">
6939
- <ax-skeleton class="ax-w-full ax-h-6 ax-rounded-md"></ax-skeleton>
6940
- <ax-skeleton class="ax-w-full ax-h-6 ax-rounded-md"></ax-skeleton>
6941
- <ax-skeleton class="ax-w-full ax-h-6 ax-rounded-md"></ax-skeleton>
7794
+ @for (i of [1, 2, 3, 4]; track i) {
7795
+ <ax-skeleton class="ax-w-full ax-h-6 ax-rounded-md"></ax-skeleton>
7796
+ }
6942
7797
  </div>
6943
7798
  } @else if (treeData) {
6944
7799
  <div class="ax-flex ax-flex-col ax-flex-1 ax-min-h-0">
@@ -6976,14 +7831,14 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
6976
7831
  [datasource]="datasource"
6977
7832
  [dragBehavior]="'none'"
6978
7833
  [selectMode]="allowMultiple() ? 'multiple' : 'single'"
6979
- [selectionBehavior]="selectionBehavior()"
7834
+ [selectionBehavior]="allowMultiple() ? 'intermediate-nested' : 'all'"
6980
7835
  [showIcons]="true"
6981
7836
  [titleField]="textField()"
6982
7837
  [idField]="valueField()"
6983
- [nodeTemplate]="itemTemplate"
6984
7838
  (onNodeSelect)="onNodeSelect($event)"
6985
7839
  (onSelectionChange)="onSelectionChange($event)"
6986
7840
  (onNodeToggle)="onNodeToggle($event)"
7841
+ [nodeTemplate]="itemTemplate"
6987
7842
  #tree
6988
7843
  >
6989
7844
  </ax-tree-view>
@@ -6992,12 +7847,16 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
6992
7847
  @if (node) {
6993
7848
  @let item = node.data || node;
6994
7849
  <div class="ax-flex ax-items-center ax-gap-2 ax-w-full ax-overflow-hidden ax-p-2 ax-pe-0">
6995
- <!-- <ax-check-box
6996
- [ngModel]="isNodeSelected(node.id)"
6997
- (onValueChanged)="handleCheckboxChange(node.id, $event.value)"
6998
- (click)="$event.stopPropagation()"
6999
- [disabled]="node.disabled || node.id === 'all'"
7000
- ></ax-check-box> -->
7850
+ @if (allowMultiple()) {
7851
+ <ax-check-box
7852
+ [ngModel]="node['indeterminate'] ? null : node['selected'] || false"
7853
+ [indeterminate]="node['indeterminate'] || false"
7854
+ (onValueChanged)="handleCheckboxChange(node.id, $event.value)"
7855
+ (click)="$event.stopPropagation()"
7856
+ (pointerdown)="$event.stopPropagation()"
7857
+ [disabled]="node.disabled || node.id === 'all'"
7858
+ ></ax-check-box>
7859
+ }
7001
7860
  <ax-icon
7002
7861
  class="fas fa-folder"
7003
7862
  [style.color]="item.color ?? 'rgba(var(--ax-sys-color-warning-500), 1)'"
@@ -7036,7 +7895,7 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
7036
7895
  ></ax-button>
7037
7896
  </ax-suffix>
7038
7897
  </ax-footer>
7039
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: AXButtonModule }, { kind: "component", type: i3.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: AXCheckBoxModule }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i2$1.AXDecoratorIconComponent, selector: "ax-icon", inputs: ["icon"] }, { kind: "component", type: i2$1.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: AXSearchBoxModule }, { kind: "component", type: i3$2.AXSearchBoxComponent, selector: "ax-search-box", inputs: ["disabled", "readonly", "tabIndex", "placeholder", "value", "state", "name", "id", "look", "class", "delayTime", "type", "autoSearch"], outputs: ["valueChange", "stateChange", "onValueChanged", "onBlur", "onFocus", "readonlyChange", "disabledChange", "onKeyDown", "onKeyUp", "onKeyPress"] }, { kind: "ngmodule", type: AXSkeletonModule }, { kind: "component", type: i4$1.AXSkeletonComponent, selector: "ax-skeleton", inputs: ["animated"] }, { kind: "component", type: AXTreeViewComponent, selector: "ax-tree-view", inputs: ["datasource", "selectMode", "selectionBehavior", "dragArea", "dragBehavior", "showIcons", "showChildrenBadge", "expandedIcon", "collapsedIcon", "indentSize", "look", "nodeTemplate", "idField", "titleField", "tooltipField", "iconField", "expandedField", "selectedField", "indeterminateField", "disabledField", "hiddenField", "childrenField", "childrenCountField", "dataField", "inheritDisabled", "expandOnDoubleClick"], outputs: ["datasourceChange", "onBeforeDrop", "onNodeToggle", "onNodeSelect", "onNodeDoubleClick", "onNodeClick", "onSelectionChange", "onOrderChange", "onMoveChange", "onItemsChange"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "component", type: AXPStateMessageComponent, selector: "axp-state-message", inputs: ["mode", "icon", "title", "description", "variant"] }, { kind: "ngmodule", type: FormsModule }, { kind: "pipe", type: i5.AsyncPipe, name: "async" }, { kind: "pipe", type: i6.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
7898
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: AXButtonModule }, { kind: "component", type: i3.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: AXCheckBoxModule }, { kind: "component", type: i2$1.AXCheckBoxComponent, selector: "ax-check-box", inputs: ["disabled", "tabIndex", "readonly", "color", "value", "name", "id", "isLoading", "indeterminate"], outputs: ["onBlur", "onFocus", "valueChange", "onValueChanged"] }, { 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: AXSearchBoxModule }, { kind: "component", type: i4$2.AXSearchBoxComponent, selector: "ax-search-box", inputs: ["disabled", "readonly", "tabIndex", "placeholder", "value", "state", "name", "id", "look", "class", "delayTime", "type", "autoSearch"], outputs: ["valueChange", "stateChange", "onValueChanged", "onBlur", "onFocus", "readonlyChange", "disabledChange", "onKeyDown", "onKeyUp", "onKeyPress"] }, { kind: "ngmodule", type: AXSkeletonModule }, { kind: "component", type: i5$1.AXSkeletonComponent, selector: "ax-skeleton", inputs: ["animated"] }, { kind: "component", type: AXTreeViewComponent, selector: "ax-tree-view", inputs: ["datasource", "selectMode", "selectionBehavior", "dragArea", "dragBehavior", "showIcons", "showChildrenBadge", "expandedIcon", "collapsedIcon", "indentSize", "look", "nodeTemplate", "idField", "titleField", "tooltipField", "iconField", "expandedField", "selectedField", "indeterminateField", "disabledField", "hiddenField", "childrenField", "childrenCountField", "dataField", "inheritDisabled", "expandOnDoubleClick"], outputs: ["datasourceChange", "onBeforeDrop", "onNodeToggle", "onNodeSelect", "onNodeDoubleClick", "onNodeClick", "onSelectionChange", "onOrderChange", "onMoveChange", "onItemsChange"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "component", type: AXPStateMessageComponent, selector: "axp-state-message", inputs: ["mode", "icon", "title", "description", "variant"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "pipe", type: i5.AsyncPipe, name: "async" }, { kind: "pipe", type: i6.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
7040
7899
  }
7041
7900
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPEntityCategoryTreeSelectorComponent, decorators: [{
7042
7901
  type: Component,
@@ -7046,9 +7905,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImpo
7046
7905
  <div class="ax-flex ax-flex-col ax-h-full">
7047
7906
  @if (loading()) {
7048
7907
  <div class="ax-p-4 ax-flex ax-flex-col ax-gap-3">
7049
- <ax-skeleton class="ax-w-full ax-h-6 ax-rounded-md"></ax-skeleton>
7050
- <ax-skeleton class="ax-w-full ax-h-6 ax-rounded-md"></ax-skeleton>
7051
- <ax-skeleton class="ax-w-full ax-h-6 ax-rounded-md"></ax-skeleton>
7908
+ @for (i of [1, 2, 3, 4]; track i) {
7909
+ <ax-skeleton class="ax-w-full ax-h-6 ax-rounded-md"></ax-skeleton>
7910
+ }
7052
7911
  </div>
7053
7912
  } @else if (treeData) {
7054
7913
  <div class="ax-flex ax-flex-col ax-flex-1 ax-min-h-0">
@@ -7086,14 +7945,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImpo
7086
7945
  [datasource]="datasource"
7087
7946
  [dragBehavior]="'none'"
7088
7947
  [selectMode]="allowMultiple() ? 'multiple' : 'single'"
7089
- [selectionBehavior]="selectionBehavior()"
7948
+ [selectionBehavior]="allowMultiple() ? 'intermediate-nested' : 'all'"
7090
7949
  [showIcons]="true"
7091
7950
  [titleField]="textField()"
7092
7951
  [idField]="valueField()"
7093
- [nodeTemplate]="itemTemplate"
7094
7952
  (onNodeSelect)="onNodeSelect($event)"
7095
7953
  (onSelectionChange)="onSelectionChange($event)"
7096
7954
  (onNodeToggle)="onNodeToggle($event)"
7955
+ [nodeTemplate]="itemTemplate"
7097
7956
  #tree
7098
7957
  >
7099
7958
  </ax-tree-view>
@@ -7102,12 +7961,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImpo
7102
7961
  @if (node) {
7103
7962
  @let item = node.data || node;
7104
7963
  <div class="ax-flex ax-items-center ax-gap-2 ax-w-full ax-overflow-hidden ax-p-2 ax-pe-0">
7105
- <!-- <ax-check-box
7106
- [ngModel]="isNodeSelected(node.id)"
7107
- (onValueChanged)="handleCheckboxChange(node.id, $event.value)"
7108
- (click)="$event.stopPropagation()"
7109
- [disabled]="node.disabled || node.id === 'all'"
7110
- ></ax-check-box> -->
7964
+ @if (allowMultiple()) {
7965
+ <ax-check-box
7966
+ [ngModel]="node['indeterminate'] ? null : node['selected'] || false"
7967
+ [indeterminate]="node['indeterminate'] || false"
7968
+ (onValueChanged)="handleCheckboxChange(node.id, $event.value)"
7969
+ (click)="$event.stopPropagation()"
7970
+ (pointerdown)="$event.stopPropagation()"
7971
+ [disabled]="node.disabled || node.id === 'all'"
7972
+ ></ax-check-box>
7973
+ }
7111
7974
  <ax-icon
7112
7975
  class="fas fa-folder"
7113
7976
  [style.color]="item.color ?? 'rgba(var(--ax-sys-color-warning-500), 1)'"
@@ -7400,7 +8263,7 @@ class AXPEntityCategoryWidgetColumnComponent extends AXPColumnWidgetComponent {
7400
8263
  </div>
7401
8264
  }
7402
8265
  </div>
7403
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: AXBadgeModule }, { kind: "component", type: i2$2.AXBadgeComponent, selector: "ax-badge", inputs: ["color", "look", "text"] }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i2$1.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" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
8266
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: AXBadgeModule }, { kind: "component", type: i2$2.AXBadgeComponent, selector: "ax-badge", inputs: ["color", "look", "text"] }, { kind: "ngmodule", type: AXDecoratorModule }, { 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" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
7404
8267
  }
7405
8268
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPEntityCategoryWidgetColumnComponent, decorators: [{
7406
8269
  type: Component,
@@ -7741,7 +8604,7 @@ class AXPTruncatedBreadcrumbComponent {
7741
8604
  }
7742
8605
  }
7743
8606
  </div>
7744
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i2$1.AXDecoratorIconComponent, selector: "ax-icon", inputs: ["icon"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
8607
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i3$2.AXDecoratorIconComponent, selector: "ax-icon", inputs: ["icon"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
7745
8608
  }
7746
8609
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPTruncatedBreadcrumbComponent, decorators: [{
7747
8610
  type: Component,
@@ -7858,6 +8721,8 @@ class AXPEntityCategoryWidgetEditComponent extends AXPValueWidgetComponent {
7858
8721
  this.selectedItems = signal([], ...(ngDevMode ? [{ debugName: "selectedItems" }] : []));
7859
8722
  this.isLoading = signal(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
7860
8723
  this.isOpen = signal(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : []));
8724
+ this.tagBox = viewChild('tagBoxComponent', ...(ngDevMode ? [{ debugName: "tagBox" }] : []));
8725
+ this.searchTerm = signal(null, ...(ngDevMode ? [{ debugName: "searchTerm" }] : []));
7861
8726
  //#endregion
7862
8727
  //#region ---- Private Properties ----
7863
8728
  this.entityDef = signal(null, ...(ngDevMode ? [{ debugName: "entityDef" }] : []));
@@ -7912,13 +8777,62 @@ class AXPEntityCategoryWidgetEditComponent extends AXPValueWidgetComponent {
7912
8777
  handleAddClick(e) {
7913
8778
  this.showTreeSelector();
7914
8779
  }
7915
- handleRemoveItemClick(event, item) {
7916
- event.nativeEvent.stopPropagation();
7917
- this.removeItem(item);
8780
+ handleValueChange(e) {
8781
+ if (e.isUserInteraction) {
8782
+ if (isNil(e.value) || isEmpty(e.value)) {
8783
+ this.clear();
8784
+ }
8785
+ else {
8786
+ const items = Array.isArray(e.value) ? e.value : [e.value];
8787
+ this.setItems(items);
8788
+ }
8789
+ }
8790
+ }
8791
+ handleOnBlur(e) {
8792
+ setTimeout(() => {
8793
+ if (!this.isOpen()) {
8794
+ this.clearInput();
8795
+ }
8796
+ }, 100);
8797
+ }
8798
+ async handleKeyUp(e) {
8799
+ const keyEvent = e.nativeEvent;
8800
+ const value = this.tagBox()?.inputValue() ?? '';
8801
+ this.searchTerm.set(value);
8802
+ if ((keyEvent.code == 'Enter' || keyEvent.code == 'NumpadEnter') && value) {
8803
+ this.isLoading.set(true);
8804
+ // For category widget, always open selector on Enter
8805
+ this.showTreeSelector();
8806
+ this.isLoading.set(false);
8807
+ }
8808
+ if (keyEvent.code == 'ArrowDown') {
8809
+ this.showTreeSelector();
8810
+ }
7918
8811
  }
7919
8812
  handleClearClick() {
7920
8813
  this.clear();
7921
8814
  }
8815
+ async handleRemoveItem(event, index) {
8816
+ event.stopPropagation();
8817
+ if (this.disabled()) {
8818
+ return;
8819
+ }
8820
+ const currentItems = this.selectedItems();
8821
+ if (index < 0 || index >= currentItems.length) {
8822
+ return;
8823
+ }
8824
+ const filteredItems = currentItems.filter((_, i) => i !== index);
8825
+ if (filteredItems.length === 0) {
8826
+ this.clear();
8827
+ }
8828
+ else {
8829
+ await this.setItems(filteredItems);
8830
+ }
8831
+ }
8832
+ clearInput() {
8833
+ this.tagBox()?.inputValue.set('');
8834
+ this.searchTerm.set('');
8835
+ }
7922
8836
  async showTreeSelector() {
7923
8837
  this.isOpen.set(true);
7924
8838
  const currentValue = this.getValue();
@@ -7989,24 +8903,12 @@ class AXPEntityCategoryWidgetEditComponent extends AXPValueWidgetComponent {
7989
8903
  }
7990
8904
  finally {
7991
8905
  this.isOpen.set(false);
7992
- }
7993
- }
7994
- async removeItem(item) {
7995
- if (this.disabled()) {
7996
- return;
7997
- }
7998
- const currentItems = this.selectedItems();
7999
- const itemId = get(item, this.valueField());
8000
- const filteredItems = currentItems.filter((i) => get(i, this.valueField()) !== itemId);
8001
- if (filteredItems.length === 0) {
8002
- this.clear();
8003
- }
8004
- else {
8005
- await this.setItems(filteredItems);
8906
+ this.tagBox()?.focus();
8006
8907
  }
8007
8908
  }
8008
8909
  clear() {
8009
8910
  this.setValue(null);
8911
+ this.clearInput();
8010
8912
  this.selectedItems.set([]);
8011
8913
  this.cdr.markForCheck();
8012
8914
  }
@@ -8088,6 +8990,7 @@ class AXPEntityCategoryWidgetEditComponent extends AXPValueWidgetComponent {
8088
8990
  return;
8089
8991
  }
8090
8992
  items = castArray(items);
8993
+ this.clearInput();
8091
8994
  // Ensure all items have paths
8092
8995
  const itemsWithPaths = await Promise.all(items.map(async (item) => {
8093
8996
  // If item already has a path array, return it as is
@@ -8229,47 +9132,78 @@ class AXPEntityCategoryWidgetEditComponent extends AXPValueWidgetComponent {
8229
9132
  return { ...item, path: pathArray };
8230
9133
  }
8231
9134
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPEntityCategoryWidgetEditComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
8232
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.12", type: AXPEntityCategoryWidgetEditComponent, isStandalone: true, selector: "axp-entity-category-widget-edit", usesInheritance: true, ngImport: i0, template: `
8233
- <div class="ax-flex ax-flex-col ax-gap-3">
8234
- @if (selectedItems().length > 0) {
8235
- <div class="ax-flex ax-flex-col ax-gap-2">
8236
- @for (item of selectedItems(); track getItemId(item)) {
8237
- <div class="ax-flex ax-items-center ax-gap-2 ax-p-2 ax-border ax-primary-lightest ax-rounded-xl">
8238
- <axp-truncated-breadcrumb
8239
- [sections]="getItemPath(item)"
8240
- [characterLimit]="characterLimit()"
8241
- [sectionLimit]="sectionLimit()"
8242
- class="ax-flex-1"
8243
- ></axp-truncated-breadcrumb>
8244
- <ax-button color="ghost" look="blank" class="ax-xs" (onClick)="handleRemoveItemClick($event, item)">
8245
- <ax-icon icon="ax-icon ax-icon-close"></ax-icon>
8246
- </ax-button>
8247
- </div>
8248
- }
8249
- </div>
9135
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.12", type: AXPEntityCategoryWidgetEditComponent, isStandalone: true, selector: "axp-entity-category-widget-edit", viewQueries: [{ propertyName: "tagBox", first: true, predicate: ["tagBoxComponent"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: `
9136
+ <ax-tag-box
9137
+ #tagBoxComponent
9138
+ [tagTemplate]="tagTemplate"
9139
+ [ngModel]="selectedItems()"
9140
+ [textField]="displayField()"
9141
+ [valueField]="valueField()"
9142
+ (onValueChanged)="handleValueChange($event)"
9143
+ [placeholder]="selectedItems().length ? '' : searchPlaceholderText()"
9144
+ [addOnEnter]="false"
9145
+ [addOnComma]="false"
9146
+ [readonly]="true"
9147
+ [disabled]="disabled()"
9148
+ (onKeyUp)="handleKeyUp($event)"
9149
+ (onBlur)="handleOnBlur($event)"
9150
+ >
9151
+ @for (validation of validationRules(); track $index) {
9152
+ <ax-validation-rule
9153
+ [rule]="validation.rule"
9154
+ [message]="validation.options?.message"
9155
+ [options]="validation.options"
9156
+ ></ax-validation-rule>
9157
+ }
9158
+ @if (selectedItems().length > 1 || allowClear()) {
9159
+ <ax-clear-button (click)="handleClearClick()"></ax-clear-button>
8250
9160
  }
8251
9161
 
8252
- <div class="ax-flex ax-items-center ax-gap-2">
8253
- <ax-button
8254
- [text]="'@general:actions.add.title' | translate | async"
8255
- color="primary"
8256
- [disabled]="isLoading() || disabled()"
8257
- (onClick)="handleAddClick($event)"
8258
- >
9162
+ <ax-suffix>
9163
+ <ax-button color="ghost" look="blank" [disabled]="isLoading() || disabled()" (onClick)="handleAddClick($event)">
8259
9164
  @if (isLoading()) {
8260
9165
  <ax-loading></ax-loading>
8261
9166
  } @else {
8262
- <ax-icon icon="far fa-plus"></ax-icon>
9167
+ <ax-icon icon="far fa-search"></ax-icon>
8263
9168
  }
8264
9169
  </ax-button>
8265
-
8266
- @if (allowClear() && selectedItems().length > 0) {
8267
- <ax-button color="ghost" look="outline" [disabled]="disabled()" (onClick)="handleClearClick()">
8268
- Clear All
8269
- </ax-button>
8270
- }
9170
+ </ax-suffix>
9171
+ </ax-tag-box>
9172
+ <ng-template #tagTemplate let-item let-index="index">
9173
+ <div class="ax-inline-flex ax-items-center ax-gap-1.5 ax-rounded-md ax-px-3 ax-py-1 ax-text-sm ax-surface">
9174
+ <axp-truncated-breadcrumb
9175
+ [attr.data-color]="item.color"
9176
+ [sections]="getItemPath(item)"
9177
+ [characterLimit]="characterLimit()"
9178
+ [sectionLimit]="sectionLimit()"
9179
+ ></axp-truncated-breadcrumb>
9180
+ <button type="button" (click)="handleRemoveItem($event, index)">
9181
+ <ax-icon class="ax-icon ax-icon-close"></ax-icon>
9182
+ </button>
8271
9183
  </div>
8272
-
9184
+ </ng-template>
9185
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: AXButtonModule }, { kind: "component", type: i3.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.AXDecoratorClearButtonComponent, selector: "ax-clear-button", 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: AXLoadingModule }, { kind: "component", type: i4.AXLoadingComponent, selector: "ax-loading", inputs: ["visible", "type", "context"], outputs: ["visibleChange"] }, { kind: "ngmodule", type: AXValidationModule }, { kind: "ngmodule", type: AXFormModule }, { kind: "directive", type: i5$2.AXValidationRuleDirective, selector: "ax-validation-rule", inputs: ["rule", "options", "message", "disabled"] }, { kind: "ngmodule", type: AXTagBoxModule }, { kind: "component", type: i6$1.AXTagBoxComponent, selector: "ax-tag-box", inputs: ["disabled", "tabIndex", "readonly", "value", "state", "name", "id", "placeholder", "allowNull", "type", "look", "addOnComma", "addOnEnter", "valueField", "textField", "readonlyField", "allowDuplicateValues", "tagTemplate"], outputs: ["onBlur", "onFocus", "valueChange", "stateChange", "onValueChanged", "readonlyChange", "disabledChange", "onKeyDown", "onKeyUp", "onKeyPress", "onTagClick", "onTagDblClick", "onTagContextMenu"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "component", type: AXPTruncatedBreadcrumbComponent, selector: "axp-truncated-breadcrumb", inputs: ["sections", "characterLimit", "sectionLimit", "separatorIcon", "ellipsisIcon", "eyeIcon"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
9186
+ }
9187
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPEntityCategoryWidgetEditComponent, decorators: [{
9188
+ type: Component,
9189
+ args: [{
9190
+ selector: 'axp-entity-category-widget-edit',
9191
+ template: `
9192
+ <ax-tag-box
9193
+ #tagBoxComponent
9194
+ [tagTemplate]="tagTemplate"
9195
+ [ngModel]="selectedItems()"
9196
+ [textField]="displayField()"
9197
+ [valueField]="valueField()"
9198
+ (onValueChanged)="handleValueChange($event)"
9199
+ [placeholder]="selectedItems().length ? '' : searchPlaceholderText()"
9200
+ [addOnEnter]="false"
9201
+ [addOnComma]="false"
9202
+ [readonly]="true"
9203
+ [disabled]="disabled()"
9204
+ (onKeyUp)="handleKeyUp($event)"
9205
+ (onBlur)="handleOnBlur($event)"
9206
+ >
8273
9207
  @for (validation of validationRules(); track $index) {
8274
9208
  <ax-validation-rule
8275
9209
  [rule]="validation.rule"
@@ -8277,76 +9211,49 @@ class AXPEntityCategoryWidgetEditComponent extends AXPValueWidgetComponent {
8277
9211
  [options]="validation.options"
8278
9212
  ></ax-validation-rule>
8279
9213
  }
8280
- </div>
8281
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: AXButtonModule }, { kind: "component", type: i3.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: i2$1.AXDecoratorIconComponent, selector: "ax-icon", inputs: ["icon"] }, { kind: "ngmodule", type: AXLoadingModule }, { kind: "component", type: i1.AXLoadingComponent, selector: "ax-loading", inputs: ["visible", "type", "context"], outputs: ["visibleChange"] }, { kind: "ngmodule", type: AXValidationModule }, { kind: "ngmodule", type: AXFormModule }, { kind: "directive", type: i5$1.AXValidationRuleDirective, selector: "ax-validation-rule", inputs: ["rule", "options", "message", "disabled"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "component", type: AXPTruncatedBreadcrumbComponent, selector: "axp-truncated-breadcrumb", inputs: ["sections", "characterLimit", "sectionLimit", "separatorIcon", "ellipsisIcon", "eyeIcon"] }, { kind: "pipe", type: i5.AsyncPipe, name: "async" }, { kind: "pipe", type: i6.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
8282
- }
8283
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPEntityCategoryWidgetEditComponent, decorators: [{
8284
- type: Component,
8285
- args: [{
8286
- selector: 'axp-entity-category-widget-edit',
8287
- template: `
8288
- <div class="ax-flex ax-flex-col ax-gap-3">
8289
- @if (selectedItems().length > 0) {
8290
- <div class="ax-flex ax-flex-col ax-gap-2">
8291
- @for (item of selectedItems(); track getItemId(item)) {
8292
- <div class="ax-flex ax-items-center ax-gap-2 ax-p-2 ax-border ax-primary-lightest ax-rounded-xl">
8293
- <axp-truncated-breadcrumb
8294
- [sections]="getItemPath(item)"
8295
- [characterLimit]="characterLimit()"
8296
- [sectionLimit]="sectionLimit()"
8297
- class="ax-flex-1"
8298
- ></axp-truncated-breadcrumb>
8299
- <ax-button color="ghost" look="blank" class="ax-xs" (onClick)="handleRemoveItemClick($event, item)">
8300
- <ax-icon icon="ax-icon ax-icon-close"></ax-icon>
8301
- </ax-button>
8302
- </div>
8303
- }
8304
- </div>
9214
+ @if (selectedItems().length > 1 || allowClear()) {
9215
+ <ax-clear-button (click)="handleClearClick()"></ax-clear-button>
8305
9216
  }
8306
9217
 
8307
- <div class="ax-flex ax-items-center ax-gap-2">
8308
- <ax-button
8309
- [text]="'@general:actions.add.title' | translate | async"
8310
- color="primary"
8311
- [disabled]="isLoading() || disabled()"
8312
- (onClick)="handleAddClick($event)"
8313
- >
9218
+ <ax-suffix>
9219
+ <ax-button color="ghost" look="blank" [disabled]="isLoading() || disabled()" (onClick)="handleAddClick($event)">
8314
9220
  @if (isLoading()) {
8315
9221
  <ax-loading></ax-loading>
8316
9222
  } @else {
8317
- <ax-icon icon="far fa-plus"></ax-icon>
9223
+ <ax-icon icon="far fa-search"></ax-icon>
8318
9224
  }
8319
9225
  </ax-button>
8320
-
8321
- @if (allowClear() && selectedItems().length > 0) {
8322
- <ax-button color="ghost" look="outline" [disabled]="disabled()" (onClick)="handleClearClick()">
8323
- Clear All
8324
- </ax-button>
8325
- }
9226
+ </ax-suffix>
9227
+ </ax-tag-box>
9228
+ <ng-template #tagTemplate let-item let-index="index">
9229
+ <div class="ax-inline-flex ax-items-center ax-gap-1.5 ax-rounded-md ax-px-3 ax-py-1 ax-text-sm ax-surface">
9230
+ <axp-truncated-breadcrumb
9231
+ [attr.data-color]="item.color"
9232
+ [sections]="getItemPath(item)"
9233
+ [characterLimit]="characterLimit()"
9234
+ [sectionLimit]="sectionLimit()"
9235
+ ></axp-truncated-breadcrumb>
9236
+ <button type="button" (click)="handleRemoveItem($event, index)">
9237
+ <ax-icon class="ax-icon ax-icon-close"></ax-icon>
9238
+ </button>
8326
9239
  </div>
8327
-
8328
- @for (validation of validationRules(); track $index) {
8329
- <ax-validation-rule
8330
- [rule]="validation.rule"
8331
- [message]="validation.options?.message"
8332
- [options]="validation.options"
8333
- ></ax-validation-rule>
8334
- }
8335
- </div>
9240
+ </ng-template>
8336
9241
  `,
8337
9242
  changeDetection: ChangeDetectionStrategy.OnPush,
8338
9243
  imports: [
8339
9244
  CommonModule,
9245
+ FormsModule,
8340
9246
  AXButtonModule,
8341
9247
  AXDecoratorModule,
8342
9248
  AXLoadingModule,
8343
9249
  AXValidationModule,
8344
9250
  AXFormModule,
9251
+ AXTagBoxModule,
8345
9252
  AXTranslationModule,
8346
9253
  AXPTruncatedBreadcrumbComponent,
8347
9254
  ],
8348
9255
  }]
8349
- }] });
9256
+ }], propDecorators: { tagBox: [{ type: i0.ViewChild, args: ['tagBoxComponent', { isSignal: true }] }] } });
8350
9257
 
8351
9258
  var entityCategoryWidgetEdit_component = /*#__PURE__*/Object.freeze({
8352
9259
  __proto__: null,
@@ -8448,7 +9355,7 @@ class AXPEntityCategoryWidgetViewComponent extends AXPValueWidgetComponent {
8448
9355
  <span class="ax-text-muted">---</span>
8449
9356
  }
8450
9357
  }
8451
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: AXLoadingModule }, { kind: "component", type: i1.AXLoadingComponent, selector: "ax-loading", inputs: ["visible", "type", "context"], outputs: ["visibleChange"] }, { kind: "ngmodule", type: AXBadgeModule }, { kind: "component", type: i2$2.AXBadgeComponent, selector: "ax-badge", inputs: ["color", "look", "text"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
9358
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: AXLoadingModule }, { kind: "component", type: i4.AXLoadingComponent, selector: "ax-loading", inputs: ["visible", "type", "context"], outputs: ["visibleChange"] }, { kind: "ngmodule", type: AXBadgeModule }, { kind: "component", type: i2$2.AXBadgeComponent, selector: "ax-badge", inputs: ["color", "look", "text"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
8452
9359
  }
8453
9360
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPEntityCategoryWidgetViewComponent, decorators: [{
8454
9361
  type: Component,
@@ -8942,6 +9849,8 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
8942
9849
  this.entityListTableService = inject(AXPEntityListTableService);
8943
9850
  this.entityListToolbarService = inject(AXPEntityListToolbarService);
8944
9851
  this.deviceService = inject(AXPDeviceService);
9852
+ this.commandService = inject(AXPCommandService);
9853
+ this.eventService = inject(AXPBroadcastEventService);
8945
9854
  this.isMounted = signal(false, ...(ngDevMode ? [{ debugName: "isMounted" }] : []));
8946
9855
  this.entity = signal(null, ...(ngDevMode ? [{ debugName: "entity" }] : []));
8947
9856
  this.listNode = signal(null, ...(ngDevMode ? [{ debugName: "listNode" }] : []));
@@ -9024,17 +9933,21 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
9024
9933
  return actions;
9025
9934
  }, ...(ngDevMode ? [{ debugName: "secondaryActions" }] : []));
9026
9935
  //#region ---- Query Change Handler ----
9936
+ this.queries = undefined;
9027
9937
  this.#effect = effect(() => {
9028
- const queries = this.getValue()?.toolbar;
9938
+ //TODO: this is a temporary solution to handle the query changes; this should be removed when the query changes are handled in the widget core;
9939
+ if (this.getValue()?.toolbar) {
9940
+ this.queries = this.getValue()?.toolbar;
9941
+ }
9029
9942
  const listInstance = this.listWidget()?.instance;
9030
9943
  // const dataSource = this.listWidget()?.options()['dataSource'] as AXDataSource;
9031
9944
  const dataSource = listInstance?.options()?.['dataSource'];
9032
9945
  const isMounted = this.isMounted();
9033
- if (!this.hasRequiredDependencies(dataSource, queries, listInstance)) {
9946
+ if (!this.hasRequiredDependencies(dataSource, this.queries, listInstance)) {
9034
9947
  return;
9035
9948
  }
9036
9949
  untracked(() => {
9037
- this.handleQueryChanges(queries, dataSource, listInstance, isMounted);
9950
+ this.handleQueryChanges(this.queries, dataSource, listInstance, isMounted);
9038
9951
  });
9039
9952
  }, ...(ngDevMode ? [{ debugName: "#effect" }] : []));
9040
9953
  //#endregion
@@ -9067,23 +9980,43 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
9067
9980
  });
9068
9981
  const command = commandName.split('&')[0];
9069
9982
  // const options = await this.evaluateExpressions(action?.options, data);
9070
- await this.workflow.execute(command, {
9071
- entity: this.entitySource(),
9072
- entityInfo: {
9073
- name: this.entity()?.name,
9074
- module: this.entity()?.module,
9075
- title: this.entity()?.title,
9076
- parentKey: this.entity()?.parentKey,
9077
- source: `${this.entity()?.module}.${this.entity()?.name}`,
9078
- },
9079
- data: action?.scope == AXPEntityCommandScope.Selected
9080
- ? this.selectedItems()
9081
- : action?.options?.['process']?.data || null,
9082
- options: action?.options,
9083
- metadata: action?.metadata,
9084
- });
9983
+ if (this.commandService.exists(command)) {
9984
+ await this.commandService.execute(command, {
9985
+ __context__: {
9986
+ entity: this.entitySource(),
9987
+ entityInfo: {
9988
+ name: this.entity()?.name,
9989
+ module: this.entity()?.module,
9990
+ title: this.entity()?.title,
9991
+ parentKey: this.entity()?.parentKey,
9992
+ source: `${this.entity()?.module}.${this.entity()?.name}`,
9993
+ },
9994
+ data: action?.scope == AXPEntityCommandScope.Selected
9995
+ ? this.selectedItems()
9996
+ : action?.options?.['process']?.data || null,
9997
+ options: action?.options,
9998
+ metadata: action?.metadata,
9999
+ },
10000
+ });
10001
+ }
10002
+ else {
10003
+ await this.workflow.execute(command, {
10004
+ entity: this.entitySource(),
10005
+ entityInfo: {
10006
+ name: this.entity()?.name,
10007
+ module: this.entity()?.module,
10008
+ title: this.entity()?.title,
10009
+ parentKey: this.entity()?.parentKey,
10010
+ source: `${this.entity()?.module}.${this.entity()?.name}`,
10011
+ },
10012
+ data: action?.scope == AXPEntityCommandScope.Selected
10013
+ ? this.selectedItems()
10014
+ : action?.options?.['process']?.data || null,
10015
+ options: action?.options,
10016
+ metadata: action?.metadata,
10017
+ });
10018
+ }
9085
10019
  }
9086
- //#region ---- Query Change Handler ----
9087
10020
  #effect;
9088
10021
  /**
9089
10022
  * Validates that all required dependencies are available
@@ -9216,6 +10149,14 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
9216
10149
  this.listWidget()?.instance.call('refresh');
9217
10150
  }
9218
10151
  });
10152
+ this.eventService
10153
+ .listen(AXPEntityEventsKeys.REFRESH_LAYOUT)
10154
+ .pipe(takeUntil(this.destroyed))
10155
+ .subscribe((e) => {
10156
+ if (e.data.name == `${this.entity()?.module}.${this.entity()?.name}`) {
10157
+ this.listWidget()?.instance.call('refresh');
10158
+ }
10159
+ });
9219
10160
  const listWidget = (await this.layoutService.waitForWidget(`${this.entitySource()}-tab-list_table`, 500));
9220
10161
  if (listWidget?.api && typeof listWidget.api === 'function') {
9221
10162
  const onSelectionChange = listWidget.api()['onSelectionChange'];
@@ -9344,7 +10285,7 @@ class AXPEntityListWidgetViewComponent extends AXPValueWidgetComponent {
9344
10285
  ></ng-container>
9345
10286
  }
9346
10287
  </div>
9347
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i2$1.AXDecoratorIconComponent, selector: "ax-icon", inputs: ["icon"] }, { kind: "component", type: i2$1.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: AXPWidgetCoreModule }, { kind: "directive", type: i3$1.AXPWidgetRendererDirective, selector: "[axp-widget-renderer]", inputs: ["parentNode", "index", "mode", "node"], outputs: ["onOptionsChanged", "onValueChanged"], exportAs: ["widgetRenderer"] }, { kind: "ngmodule", type: AXButtonModule }, { kind: "component", type: i3.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: "component", type: i3.AXButtonItemComponent, selector: "ax-button-item", inputs: ["color", "disabled", "text", "selected", "divided", "data", "name"], outputs: ["onClick", "onFocus", "onBlur", "disabledChange"] }, { kind: "component", type: i3.AXButtonItemListComponent, selector: "ax-button-item-list", inputs: ["items", "closeParentOnClick", "lockOnLoading"], outputs: ["onItemClick"] }, { kind: "ngmodule", type: AXDropdownModule }, { kind: "component", type: i4$2.AXDropdownPanelComponent, selector: "ax-dropdown-panel", inputs: ["isOpen", "fitParent", "dropdownWidth", "position", "placement", "_target", "adaptivityEnabled"], outputs: ["onOpened", "onClosed"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "pipe", type: i5.AsyncPipe, name: "async" }, { kind: "pipe", type: i6.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
10288
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { 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: AXPWidgetCoreModule }, { kind: "directive", type: i3$1.AXPWidgetRendererDirective, selector: "[axp-widget-renderer]", inputs: ["parentNode", "index", "mode", "node"], outputs: ["onOptionsChanged", "onValueChanged"], exportAs: ["widgetRenderer"] }, { kind: "ngmodule", type: AXButtonModule }, { kind: "component", type: i3.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: "component", type: i3.AXButtonItemComponent, selector: "ax-button-item", inputs: ["color", "disabled", "text", "selected", "divided", "data", "name"], outputs: ["onClick", "onFocus", "onBlur", "disabledChange"] }, { kind: "component", type: i3.AXButtonItemListComponent, selector: "ax-button-item-list", inputs: ["items", "closeParentOnClick", "lockOnLoading"], outputs: ["onItemClick"] }, { kind: "ngmodule", type: AXDropdownModule }, { kind: "component", type: i4$3.AXDropdownPanelComponent, selector: "ax-dropdown-panel", inputs: ["isOpen", "fitParent", "dropdownWidth", "position", "placement", "_target", "adaptivityEnabled"], outputs: ["onOpened", "onClosed"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "pipe", type: i5.AsyncPipe, name: "async" }, { kind: "pipe", type: i6.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
9348
10289
  }
9349
10290
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPEntityListWidgetViewComponent, decorators: [{
9350
10291
  type: Component,
@@ -9915,7 +10856,7 @@ class AXPLookupWidgetViewComponent extends AXPValueWidgetComponent {
9915
10856
  <span class="ax-text-muted">---</span>
9916
10857
  }
9917
10858
  }
9918
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: AXLoadingModule }, { kind: "component", type: i1.AXLoadingComponent, selector: "ax-loading", inputs: ["visible", "type", "context"], outputs: ["visibleChange"] }, { kind: "ngmodule", type: AXBadgeModule }, { kind: "component", type: i2$2.AXBadgeComponent, selector: "ax-badge", inputs: ["color", "look", "text"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
10859
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: AXLoadingModule }, { kind: "component", type: i4.AXLoadingComponent, selector: "ax-loading", inputs: ["visible", "type", "context"], outputs: ["visibleChange"] }, { kind: "ngmodule", type: AXBadgeModule }, { kind: "component", type: i2$2.AXBadgeComponent, selector: "ax-badge", inputs: ["color", "look", "text"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
9919
10860
  }
9920
10861
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPLookupWidgetViewComponent, decorators: [{
9921
10862
  type: Component,
@@ -10167,6 +11108,7 @@ class AXPLookupWidgetEditComponent extends AXPValueWidgetComponent {
10167
11108
  this.disabled = computed(() => this.options()['disabled'], ...(ngDevMode ? [{ debugName: "disabled" }] : []));
10168
11109
  this.columns = computed(() => this.options()['columns'] ?? [], ...(ngDevMode ? [{ debugName: "columns" }] : []));
10169
11110
  this.textField = computed(() => this.options()['textField'] ?? '', ...(ngDevMode ? [{ debugName: "textField" }] : []));
11111
+ this.hasClearButton = computed(() => this.options()['hasClearButton'] ?? false, ...(ngDevMode ? [{ debugName: "hasClearButton" }] : []));
10170
11112
  this.customFilter = computed(() => this.options()['filter'], ...(ngDevMode ? [{ debugName: "customFilter" }] : []));
10171
11113
  this.multiple = computed(() => (this.options()['multiple'] ?? false), ...(ngDevMode ? [{ debugName: "multiple" }] : []));
10172
11114
  this.look = computed(() => this.options()['look'] ?? 'lookup', ...(ngDevMode ? [{ debugName: "look" }] : []));
@@ -10563,6 +11505,9 @@ class AXPLookupWidgetEditComponent extends AXPValueWidgetComponent {
10563
11505
  [multiple]="multiple()"
10564
11506
  (onValueChanged)="selectBoxValueChange($event)"
10565
11507
  >
11508
+ @if (hasClearButton()) {
11509
+ <ax-clear-button></ax-clear-button>
11510
+ }
10566
11511
  <ax-search-box
10567
11512
  [placeholder]="selectedItems().length ? '' : searchPlaceholderText()"
10568
11513
  (onValueChanged)="handleSearchInputChange($event)"
@@ -10631,9 +11576,9 @@ class AXPLookupWidgetEditComponent extends AXPValueWidgetComponent {
10631
11576
  </ng-template>
10632
11577
  }
10633
11578
  }
10634
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type:
11579
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type:
10635
11580
  //
10636
- AXButtonModule }, { kind: "component", type: i3.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: i2$1.AXDecoratorIconComponent, selector: "ax-icon", inputs: ["icon"] }, { kind: "component", type: i2$1.AXDecoratorClearButtonComponent, selector: "ax-clear-button", inputs: ["icon"] }, { kind: "component", type: i2$1.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: AXLoadingModule }, { kind: "component", type: i1.AXLoadingComponent, selector: "ax-loading", inputs: ["visible", "type", "context"], outputs: ["visibleChange"] }, { kind: "ngmodule", type: AXValidationModule }, { kind: "ngmodule", type: AXFormModule }, { kind: "directive", type: i5$1.AXValidationRuleDirective, selector: "ax-validation-rule", inputs: ["rule", "options", "message", "disabled"] }, { kind: "ngmodule", type: AXTagBoxModule }, { kind: "component", type: i6$1.AXTagBoxComponent, selector: "ax-tag-box", inputs: ["disabled", "tabIndex", "readonly", "value", "state", "name", "id", "placeholder", "allowNull", "type", "look", "addOnComma", "addOnEnter", "valueField", "textField", "readonlyField", "allowDuplicateValues", "tagTemplate"], outputs: ["onBlur", "onFocus", "valueChange", "stateChange", "onValueChanged", "readonlyChange", "disabledChange", "onKeyDown", "onKeyUp", "onKeyPress", "onTagClick", "onTagDblClick", "onTagContextMenu"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "ngmodule", type: AXSelectBoxModule }, { kind: "component", type: i7.AXSelectBoxComponent, selector: "ax-select-box", inputs: ["disabled", "readonly", "tabIndex", "placeholder", "minValue", "maxValue", "value", "state", "name", "id", "type", "look", "multiple", "valueField", "textField", "disabledField", "textTemplate", "selectedItems", "isItemTruncated", "showItemTooltip", "itemHeight", "maxVisibleItems", "dataSource", "minRecordsForSearch", "caption", "itemTemplate", "selectedTemplate", "emptyTemplate", "loadingTemplate", "dropdownWidth", "searchBoxAutoFocus"], outputs: ["valueChange", "stateChange", "onValueChanged", "onBlur", "onFocus", "readonlyChange", "disabledChange", "onOpened", "onClosed", "onItemSelected", "onItemClick"] }, { kind: "component", type: AXSearchBoxComponent, selector: "ax-search-box", inputs: ["disabled", "readonly", "tabIndex", "placeholder", "value", "state", "name", "id", "look", "class", "delayTime", "type", "autoSearch"], outputs: ["valueChange", "stateChange", "onValueChanged", "onBlur", "onFocus", "readonlyChange", "disabledChange", "onKeyDown", "onKeyUp", "onKeyPress"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
11581
+ AXButtonModule }, { kind: "component", type: i3.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.AXDecoratorClearButtonComponent, selector: "ax-clear-button", 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: AXLoadingModule }, { kind: "component", type: i4.AXLoadingComponent, selector: "ax-loading", inputs: ["visible", "type", "context"], outputs: ["visibleChange"] }, { kind: "ngmodule", type: AXValidationModule }, { kind: "ngmodule", type: AXFormModule }, { kind: "directive", type: i5$2.AXValidationRuleDirective, selector: "ax-validation-rule", inputs: ["rule", "options", "message", "disabled"] }, { kind: "ngmodule", type: AXTagBoxModule }, { kind: "component", type: i6$1.AXTagBoxComponent, selector: "ax-tag-box", inputs: ["disabled", "tabIndex", "readonly", "value", "state", "name", "id", "placeholder", "allowNull", "type", "look", "addOnComma", "addOnEnter", "valueField", "textField", "readonlyField", "allowDuplicateValues", "tagTemplate"], outputs: ["onBlur", "onFocus", "valueChange", "stateChange", "onValueChanged", "readonlyChange", "disabledChange", "onKeyDown", "onKeyUp", "onKeyPress", "onTagClick", "onTagDblClick", "onTagContextMenu"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "ngmodule", type: AXSelectBoxModule }, { kind: "component", type: i7.AXSelectBoxComponent, selector: "ax-select-box", inputs: ["disabled", "readonly", "tabIndex", "placeholder", "minValue", "maxValue", "value", "state", "name", "id", "type", "look", "multiple", "valueField", "textField", "disabledField", "textTemplate", "selectedItems", "isItemTruncated", "showItemTooltip", "itemHeight", "maxVisibleItems", "dataSource", "minRecordsForSearch", "caption", "itemTemplate", "selectedTemplate", "emptyTemplate", "loadingTemplate", "dropdownWidth", "searchBoxAutoFocus"], outputs: ["valueChange", "stateChange", "onValueChanged", "onBlur", "onFocus", "readonlyChange", "disabledChange", "onOpened", "onClosed", "onItemSelected", "onItemClick"] }, { kind: "component", type: AXSearchBoxComponent, selector: "ax-search-box", inputs: ["disabled", "readonly", "tabIndex", "placeholder", "value", "state", "name", "id", "look", "class", "delayTime", "type", "autoSearch"], outputs: ["valueChange", "stateChange", "onValueChanged", "onBlur", "onFocus", "readonlyChange", "disabledChange", "onKeyDown", "onKeyUp", "onKeyPress"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
10637
11582
  }
10638
11583
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPLookupWidgetEditComponent, decorators: [{
10639
11584
  type: Component,
@@ -10652,6 +11597,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImpo
10652
11597
  [multiple]="multiple()"
10653
11598
  (onValueChanged)="selectBoxValueChange($event)"
10654
11599
  >
11600
+ @if (hasClearButton()) {
11601
+ <ax-clear-button></ax-clear-button>
11602
+ }
10655
11603
  <ax-search-box
10656
11604
  [placeholder]="selectedItems().length ? '' : searchPlaceholderText()"
10657
11605
  (onValueChanged)="handleSearchInputChange($event)"
@@ -11006,7 +11954,7 @@ class AXPWidgetSelectorWidgetEditComponent extends AXPValueWidgetComponent {
11006
11954
  <axp-widget-property-viewer [widget]="selectedWidgetNode()!" (onChanged)="handleChangeWidget($event)">
11007
11955
  </axp-widget-property-viewer>
11008
11956
  }
11009
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: AXSelectBoxModule }, { kind: "ngmodule", type: AXTextBoxModule }, { kind: "component", type: i2$3.AXTextBoxComponent, selector: "ax-text-box", inputs: ["disabled", "tabIndex", "readonly", "value", "state", "name", "id", "placeholder", "maxLength", "allowNull", "type", "autoComplete", "look", "mask-options", "class"], outputs: ["onBlur", "onFocus", "valueChange", "stateChange", "onValueChanged", "readonlyChange", "disabledChange", "onKeyDown", "onKeyUp", "onKeyPress"] }, { kind: "ngmodule", type: AXButtonModule }, { kind: "component", type: i3.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: i2$1.AXDecoratorIconComponent, selector: "ax-icon", inputs: ["icon"] }, { kind: "component", type: i2$1.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: AXLoadingModule }, { kind: "ngmodule", type: AXValidationModule }, { kind: "ngmodule", type: AXFormModule }, { kind: "directive", type: i5$1.AXValidationRuleDirective, selector: "ax-validation-rule", inputs: ["rule", "options", "message", "disabled"] }, { kind: "component", type: AXPWidgetPropertyViewerComponent, selector: "axp-widget-property-viewer", inputs: ["widget", "mode"], outputs: ["onChanged"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
11957
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: AXSelectBoxModule }, { kind: "ngmodule", type: AXTextBoxModule }, { kind: "component", type: i2$3.AXTextBoxComponent, selector: "ax-text-box", inputs: ["disabled", "tabIndex", "readonly", "value", "state", "name", "id", "placeholder", "maxLength", "allowNull", "type", "autoComplete", "look", "mask-options", "class"], outputs: ["onBlur", "onFocus", "valueChange", "stateChange", "onValueChanged", "readonlyChange", "disabledChange", "onKeyDown", "onKeyUp", "onKeyPress"] }, { kind: "ngmodule", type: AXButtonModule }, { kind: "component", type: i3.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: AXLoadingModule }, { kind: "ngmodule", type: AXValidationModule }, { kind: "ngmodule", type: AXFormModule }, { kind: "directive", type: i5$2.AXValidationRuleDirective, selector: "ax-validation-rule", inputs: ["rule", "options", "message", "disabled"] }, { kind: "component", type: AXPWidgetPropertyViewerComponent, selector: "axp-widget-property-viewer", inputs: ["widget", "mode"], outputs: ["onChanged"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
11010
11958
  }
11011
11959
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPWidgetSelectorWidgetEditComponent, decorators: [{
11012
11960
  type: Component,
@@ -11525,7 +12473,7 @@ class AXPEntityModule {
11525
12473
  },
11526
12474
  });
11527
12475
  }
11528
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPEntityModule, deps: [{ token: i1$2.AXPAppStartUpService }, { token: i0.Injector }], target: i0.ɵɵFactoryTarget.NgModule }); }
12476
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPEntityModule, deps: [{ token: i1$1.AXPAppStartUpService }, { token: i0.Injector }], target: i0.ɵɵFactoryTarget.NgModule }); }
11529
12477
  static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.12", ngImport: i0, type: AXPEntityModule, imports: [RouterModule, i2$4.AXPWorkflowModule, i3$1.AXPWidgetCoreModule] }); }
11530
12478
  static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXPEntityModule, providers: [
11531
12479
  {
@@ -11717,7 +12665,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImpo
11717
12665
  ]),
11718
12666
  ],
11719
12667
  }]
11720
- }], ctorParameters: () => [{ type: i1$2.AXPAppStartUpService }, { type: i0.Injector }] });
12668
+ }], ctorParameters: () => [{ type: i1$1.AXPAppStartUpService }, { type: i0.Injector }] });
11721
12669
 
11722
12670
  //#endregion
11723
12671
  //#region ---- Get Entity Details Query ----
@@ -11871,7 +12819,7 @@ function entityDetailsCreateActions(parentId) {
11871
12819
  return {
11872
12820
  title: '@general:actions.create.title',
11873
12821
  command: {
11874
- name: 'create-entity',
12822
+ name: 'Entity:Create',
11875
12823
  options: {
11876
12824
  process: {
11877
12825
  redirect: false,
@@ -11948,7 +12896,7 @@ function entityDetailsReferenceCreateActions(type) {
11948
12896
  {
11949
12897
  title: '@general:actions.create.title',
11950
12898
  command: {
11951
- name: 'create-entity',
12899
+ name: 'Entity:Create',
11952
12900
  options: {
11953
12901
  process: {
11954
12902
  redirect: false,