@acorex/platform 21.0.0-next.10 → 21.0.0-next.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/core/index.d.ts +109 -3
- package/fesm2022/acorex-platform-core.mjs +3 -0
- package/fesm2022/acorex-platform-core.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-builder.mjs +0 -2
- package/fesm2022/acorex-platform-layout-builder.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-components.mjs +6 -6
- package/fesm2022/acorex-platform-layout-components.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-entity.mjs +619 -466
- package/fesm2022/acorex-platform-layout-entity.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-views.mjs +2 -2
- package/fesm2022/acorex-platform-layout-views.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-widget-core.mjs +3 -4
- package/fesm2022/acorex-platform-layout-widget-core.mjs.map +1 -1
- package/fesm2022/acorex-platform-layout-widgets.mjs +50 -55
- package/fesm2022/acorex-platform-layout-widgets.mjs.map +1 -1
- package/fesm2022/{acorex-platform-themes-default-entity-master-list-view.component-CIV6YDDZ.mjs → acorex-platform-themes-default-entity-master-list-view.component-CD4Q_UIG.mjs} +3 -3
- package/fesm2022/acorex-platform-themes-default-entity-master-list-view.component-CD4Q_UIG.mjs.map +1 -0
- package/fesm2022/acorex-platform-themes-default.mjs +2 -2
- package/fesm2022/acorex-platform-workflow.mjs.map +1 -1
- package/layout/entity/index.d.ts +47 -16
- package/layout/widgets/index.d.ts +4 -6
- package/package.json +9 -9
- package/workflow/index.d.ts +4 -4
- package/fesm2022/acorex-platform-themes-default-entity-master-list-view.component-CIV6YDDZ.mjs.map +0 -1
|
@@ -5,7 +5,7 @@ import * as i4$1 from '@acorex/platform/common';
|
|
|
5
5
|
import { AXPSettingsService, AXPFilterOperatorMiddlewareService, AXPEntityCommandScope, getEntityInfo, AXPRefreshEvent, AXPReloadEvent, AXPCommonSettings, AXPEntityQueryType, AXPCleanNestedFilters, AXPWorkflowNavigateAction, AXPToastAction, AXP_SEARCH_DEFINITION_PROVIDER } from '@acorex/platform/common';
|
|
6
6
|
import { AXPDeviceService, AXPBroadcastEventService, resolveActionLook, AXPExpressionEvaluatorService, AXPDistributedEventListenerService, AXPPlatformScope, AXPColumnWidthService, AXHighlightService, extractValue, setSmart, getChangedPaths, defaultColumnWidthProvider, AXP_COLUMN_WIDTH_PROVIDER, AXPSystemActionType } from '@acorex/platform/core';
|
|
7
7
|
import * as i0 from '@angular/core';
|
|
8
|
-
import { InjectionToken, inject, Injector, runInInjectionContext, Injectable, input, viewChild, signal, ElementRef, ChangeDetectionStrategy, Component, ApplicationRef, EnvironmentInjector, createComponent, computed, effect, Input, afterNextRender, untracked, ViewEncapsulation,
|
|
8
|
+
import { InjectionToken, inject, Injector, runInInjectionContext, Injectable, input, viewChild, signal, ElementRef, ChangeDetectionStrategy, Component, ApplicationRef, EnvironmentInjector, createComponent, computed, ChangeDetectorRef, effect, Input, afterNextRender, untracked, ViewEncapsulation, viewChildren, linkedSignal, HostBinding, output, NgModule } from '@angular/core';
|
|
9
9
|
import { Subject, takeUntil } from 'rxjs';
|
|
10
10
|
import { AXPLayoutBuilderService } from '@acorex/platform/layout/builder';
|
|
11
11
|
import { merge, castArray, get, cloneDeep, set, orderBy, isNil, isEmpty, isEqual } from 'lodash-es';
|
|
@@ -44,9 +44,11 @@ import { AXSearchBoxModule, AXSearchBoxComponent } from '@acorex/components/sear
|
|
|
44
44
|
import * as i6$1 from '@acorex/components/skeleton';
|
|
45
45
|
import { AXSkeletonModule } from '@acorex/components/skeleton';
|
|
46
46
|
import { AXTreeViewComponent } from '@acorex/components/tree-view';
|
|
47
|
-
import { AXPStateMessageComponent, AXPDataSelectorService } from '@acorex/platform/layout/components';
|
|
47
|
+
import { AXPStateMessageComponent, AXPColumnItemListComponent, AXPDataSelectorService } from '@acorex/platform/layout/components';
|
|
48
48
|
import * as i1 from '@angular/forms';
|
|
49
49
|
import { FormsModule } from '@angular/forms';
|
|
50
|
+
import * as i3$4 from '@acorex/components/tooltip';
|
|
51
|
+
import { AXTooltipModule } from '@acorex/components/tooltip';
|
|
50
52
|
import * as i5$2 from '@acorex/components/form';
|
|
51
53
|
import { AXFormModule } from '@acorex/components/form';
|
|
52
54
|
import * as i6$2 from '@acorex/components/tag-box';
|
|
@@ -6339,6 +6341,7 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
|
|
|
6339
6341
|
this.categoryTreeService = inject(AXPCategoryTreeService);
|
|
6340
6342
|
this.translationService = inject(AXTranslationService);
|
|
6341
6343
|
this.highlightService = inject(AXHighlightService);
|
|
6344
|
+
this.changeDetectorRef = inject(ChangeDetectorRef);
|
|
6342
6345
|
//#endregion
|
|
6343
6346
|
//#region ---- Properties (Set by popup service) ----
|
|
6344
6347
|
this.entityKey = signal('', ...(ngDevMode ? [{ debugName: "entityKey" }] : []));
|
|
@@ -6430,43 +6433,27 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
|
|
|
6430
6433
|
}
|
|
6431
6434
|
});
|
|
6432
6435
|
}
|
|
6433
|
-
// Mark pre-selected nodes as selected in the child nodes
|
|
6436
|
+
// Mark pre-selected leaf nodes as selected in the child nodes
|
|
6437
|
+
const selectedIds = this.selectedNodeIds();
|
|
6434
6438
|
childNodes.forEach((node) => {
|
|
6435
|
-
|
|
6439
|
+
const nodeId = String(node['id'] ?? '');
|
|
6440
|
+
if (nodeId && selectedIds.includes(nodeId)) {
|
|
6441
|
+
node['selected'] = true;
|
|
6442
|
+
}
|
|
6436
6443
|
});
|
|
6437
|
-
// After children load, programmatically select pre-selected nodes
|
|
6438
|
-
// Use a small delay to ensure nodes are fully added to tree structure
|
|
6444
|
+
// After children load, programmatically select pre-selected nodes in tree component
|
|
6439
6445
|
const treeComponent = this.tree();
|
|
6440
|
-
if (treeComponent) {
|
|
6446
|
+
if (treeComponent && selectedIds.length > 0) {
|
|
6441
6447
|
// Use setTimeout to ensure nodes are in tree structure before selecting
|
|
6442
6448
|
setTimeout(() => {
|
|
6443
|
-
const selectedIds = this.selectedNodeIds();
|
|
6444
6449
|
childNodes.forEach((node) => {
|
|
6445
6450
|
const nodeId = String(node['id'] ?? '');
|
|
6446
6451
|
if (nodeId && selectedIds.includes(nodeId) && nodeId !== 'all') {
|
|
6447
6452
|
try {
|
|
6448
|
-
|
|
6449
|
-
const treeNode = treeComponent.findNode(nodeId);
|
|
6450
|
-
if (treeNode) {
|
|
6451
|
-
treeComponent.selectNode(nodeId);
|
|
6452
|
-
}
|
|
6453
|
-
else {
|
|
6454
|
-
// If node not found, try again after a short delay (might still be loading)
|
|
6455
|
-
setTimeout(() => {
|
|
6456
|
-
try {
|
|
6457
|
-
const retryNode = treeComponent.findNode(nodeId);
|
|
6458
|
-
if (retryNode) {
|
|
6459
|
-
treeComponent.selectNode(nodeId);
|
|
6460
|
-
}
|
|
6461
|
-
}
|
|
6462
|
-
catch {
|
|
6463
|
-
// Node still not found, will be selected when it loads
|
|
6464
|
-
}
|
|
6465
|
-
}, 50);
|
|
6466
|
-
}
|
|
6453
|
+
treeComponent.selectNode(nodeId);
|
|
6467
6454
|
}
|
|
6468
|
-
catch
|
|
6469
|
-
// Node might not be in tree yet
|
|
6455
|
+
catch {
|
|
6456
|
+
// Node might not be in tree yet
|
|
6470
6457
|
}
|
|
6471
6458
|
}
|
|
6472
6459
|
});
|
|
@@ -6988,14 +6975,10 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
|
|
|
6988
6975
|
if (this.isInitializing || this.isUpdatingSelection) {
|
|
6989
6976
|
return;
|
|
6990
6977
|
}
|
|
6991
|
-
//
|
|
6992
|
-
//
|
|
6978
|
+
// The tree component fires selection changes, but we only want to track LEAF nodes
|
|
6979
|
+
// Parent nodes are auto-selected/deselected by the tree's intermediate-nested behavior
|
|
6993
6980
|
const selectedNodes = event.selectedNodes || [];
|
|
6994
|
-
|
|
6995
|
-
// Sync with tree component's selection state
|
|
6996
|
-
// This includes parents that are automatically selected when all children are selected
|
|
6997
|
-
this.selectedNodeIds.set(selectedIds);
|
|
6998
|
-
// Cache node data for all selected nodes
|
|
6981
|
+
// Cache node data for all selected nodes first
|
|
6999
6982
|
selectedNodes.forEach((node) => {
|
|
7000
6983
|
const nodeId = String(node['id'] ?? '');
|
|
7001
6984
|
const nodeData = node['data'];
|
|
@@ -7003,6 +6986,23 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
|
|
|
7003
6986
|
this.nodeDataCache.set(nodeId, nodeData);
|
|
7004
6987
|
}
|
|
7005
6988
|
});
|
|
6989
|
+
// Filter to only leaf nodes (nodes with no children or childrenCount === 0)
|
|
6990
|
+
const leafNodeIds = [];
|
|
6991
|
+
for (const node of selectedNodes) {
|
|
6992
|
+
const nodeId = String(node['id'] ?? '');
|
|
6993
|
+
if (!nodeId || nodeId === 'all') {
|
|
6994
|
+
continue;
|
|
6995
|
+
}
|
|
6996
|
+
// Check if this is a leaf node
|
|
6997
|
+
const children = node['children'];
|
|
6998
|
+
const childrenCount = node['childrenCount'];
|
|
6999
|
+
const isLeaf = (!children || children.length === 0) && (childrenCount === undefined || childrenCount === 0);
|
|
7000
|
+
if (isLeaf) {
|
|
7001
|
+
leafNodeIds.push(nodeId);
|
|
7002
|
+
}
|
|
7003
|
+
}
|
|
7004
|
+
// Update selectedNodeIds with only leaf nodes
|
|
7005
|
+
this.selectedNodeIds.set(leafNodeIds);
|
|
7006
7006
|
}
|
|
7007
7007
|
// protected handleNodeClick(event: any): void {
|
|
7008
7008
|
// // Extract node from event - could be { node: AXTreeNode } or just AXTreeNode
|
|
@@ -7040,6 +7040,26 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
|
|
|
7040
7040
|
async onCancel() {
|
|
7041
7041
|
await this.close();
|
|
7042
7042
|
}
|
|
7043
|
+
/**
|
|
7044
|
+
* Clears all selected items
|
|
7045
|
+
*/
|
|
7046
|
+
onClearAll() {
|
|
7047
|
+
const treeComponent = this.tree();
|
|
7048
|
+
if (treeComponent) {
|
|
7049
|
+
// Deselect all nodes in tree component
|
|
7050
|
+
const currentSelected = this.selectedNodeIds();
|
|
7051
|
+
for (const id of currentSelected) {
|
|
7052
|
+
try {
|
|
7053
|
+
treeComponent.deselectNode(id);
|
|
7054
|
+
}
|
|
7055
|
+
catch {
|
|
7056
|
+
// Node might not be in tree
|
|
7057
|
+
}
|
|
7058
|
+
}
|
|
7059
|
+
}
|
|
7060
|
+
// Clear the selection
|
|
7061
|
+
this.selectedNodeIds.set([]);
|
|
7062
|
+
}
|
|
7043
7063
|
/**
|
|
7044
7064
|
* Checks if a node matches the current search term
|
|
7045
7065
|
*/
|
|
@@ -7058,8 +7078,8 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
|
|
|
7058
7078
|
}
|
|
7059
7079
|
/**
|
|
7060
7080
|
* Handles checkbox change event to toggle node selection
|
|
7061
|
-
* In multiple mode: recursively selects/deselects children
|
|
7062
|
-
* Parent states are
|
|
7081
|
+
* In multiple mode: recursively selects/deselects LEAF children only
|
|
7082
|
+
* Parent states are calculated based on leaf descendants
|
|
7063
7083
|
*/
|
|
7064
7084
|
async handleCheckboxChange(nodeId, checked) {
|
|
7065
7085
|
if (!nodeId || nodeId === 'all') {
|
|
@@ -7071,15 +7091,142 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
|
|
|
7071
7091
|
return;
|
|
7072
7092
|
}
|
|
7073
7093
|
if (checked) {
|
|
7074
|
-
// Select
|
|
7075
|
-
await this.
|
|
7094
|
+
// Select all descendant LEAF nodes
|
|
7095
|
+
await this.selectLeafDescendants(id);
|
|
7076
7096
|
}
|
|
7077
7097
|
else {
|
|
7078
|
-
// Deselect
|
|
7079
|
-
await this.
|
|
7098
|
+
// Deselect all descendant LEAF nodes
|
|
7099
|
+
await this.deselectLeafDescendants(id);
|
|
7100
|
+
}
|
|
7101
|
+
// Update parent states after selection change
|
|
7102
|
+
await this.refreshParentStatesInTree();
|
|
7103
|
+
}
|
|
7104
|
+
/**
|
|
7105
|
+
* Selects all leaf descendants of a node (and the node itself if it's a leaf)
|
|
7106
|
+
*/
|
|
7107
|
+
async selectLeafDescendants(nodeId) {
|
|
7108
|
+
this.isUpdatingSelection = true;
|
|
7109
|
+
try {
|
|
7110
|
+
// Collect all leaf descendants
|
|
7111
|
+
const leafNodes = new Set();
|
|
7112
|
+
await this.collectLeafDescendants(nodeId, leafNodes);
|
|
7113
|
+
if (leafNodes.size === 0) {
|
|
7114
|
+
// Node itself is a leaf
|
|
7115
|
+
leafNodes.add(nodeId);
|
|
7116
|
+
}
|
|
7117
|
+
// Update selectedNodeIds with leaf nodes only
|
|
7118
|
+
const currentSelected = new Set(this.selectedNodeIds());
|
|
7119
|
+
leafNodes.forEach((id) => currentSelected.add(id));
|
|
7120
|
+
this.selectedNodeIds.set(Array.from(currentSelected));
|
|
7121
|
+
// Sync with tree component
|
|
7122
|
+
const treeComponent = this.tree();
|
|
7123
|
+
if (treeComponent) {
|
|
7124
|
+
for (const leafId of leafNodes) {
|
|
7125
|
+
try {
|
|
7126
|
+
const node = treeComponent.findNode(leafId);
|
|
7127
|
+
if (node) {
|
|
7128
|
+
treeComponent.selectNode(leafId);
|
|
7129
|
+
}
|
|
7130
|
+
}
|
|
7131
|
+
catch {
|
|
7132
|
+
// Node might not be in tree
|
|
7133
|
+
}
|
|
7134
|
+
}
|
|
7135
|
+
}
|
|
7136
|
+
}
|
|
7137
|
+
finally {
|
|
7138
|
+
this.isUpdatingSelection = false;
|
|
7139
|
+
}
|
|
7140
|
+
}
|
|
7141
|
+
/**
|
|
7142
|
+
* Deselects all leaf descendants of a node (and the node itself if it's a leaf)
|
|
7143
|
+
*/
|
|
7144
|
+
async deselectLeafDescendants(nodeId) {
|
|
7145
|
+
this.isUpdatingSelection = true;
|
|
7146
|
+
try {
|
|
7147
|
+
// Collect all leaf descendants
|
|
7148
|
+
const leafNodes = new Set();
|
|
7149
|
+
await this.collectLeafDescendants(nodeId, leafNodes);
|
|
7150
|
+
if (leafNodes.size === 0) {
|
|
7151
|
+
// Node itself is a leaf
|
|
7152
|
+
leafNodes.add(nodeId);
|
|
7153
|
+
}
|
|
7154
|
+
// Remove leaf nodes from selectedNodeIds
|
|
7155
|
+
const currentSelected = this.selectedNodeIds();
|
|
7156
|
+
const newSelected = currentSelected.filter((id) => !leafNodes.has(id));
|
|
7157
|
+
this.selectedNodeIds.set(newSelected);
|
|
7158
|
+
// Sync with tree component
|
|
7159
|
+
const treeComponent = this.tree();
|
|
7160
|
+
if (treeComponent) {
|
|
7161
|
+
for (const leafId of leafNodes) {
|
|
7162
|
+
try {
|
|
7163
|
+
const node = treeComponent.findNode(leafId);
|
|
7164
|
+
if (node) {
|
|
7165
|
+
treeComponent.deselectNode(leafId);
|
|
7166
|
+
}
|
|
7167
|
+
}
|
|
7168
|
+
catch {
|
|
7169
|
+
// Node might not be in tree
|
|
7170
|
+
}
|
|
7171
|
+
}
|
|
7172
|
+
}
|
|
7173
|
+
}
|
|
7174
|
+
finally {
|
|
7175
|
+
this.isUpdatingSelection = false;
|
|
7176
|
+
}
|
|
7177
|
+
}
|
|
7178
|
+
/**
|
|
7179
|
+
* Collects all LEAF descendant node IDs recursively
|
|
7180
|
+
* A leaf node is one that has no children
|
|
7181
|
+
*/
|
|
7182
|
+
async collectLeafDescendants(parentId, collection) {
|
|
7183
|
+
if (!this.treeData || !this.treeConfig || !parentId || parentId === 'all') {
|
|
7184
|
+
return;
|
|
7185
|
+
}
|
|
7186
|
+
try {
|
|
7187
|
+
const childNodes = await this.datasource(parentId);
|
|
7188
|
+
if (!childNodes || childNodes.length === 0) {
|
|
7189
|
+
// No children means this node is a leaf - add the parent itself
|
|
7190
|
+
// But only if it wasn't already added through the parent call
|
|
7191
|
+
return;
|
|
7192
|
+
}
|
|
7193
|
+
for (const childNode of childNodes) {
|
|
7194
|
+
const childId = String(childNode['id'] ?? '');
|
|
7195
|
+
if (!childId || childId === 'all') {
|
|
7196
|
+
continue;
|
|
7197
|
+
}
|
|
7198
|
+
// Cache node data
|
|
7199
|
+
const nodeData = childNode['data'];
|
|
7200
|
+
if (nodeData && typeof nodeData === 'object') {
|
|
7201
|
+
this.nodeDataCache.set(childId, nodeData);
|
|
7202
|
+
}
|
|
7203
|
+
// Check if this child is a leaf by trying to load its children
|
|
7204
|
+
const grandchildNodes = await this.datasource(childId);
|
|
7205
|
+
if (!grandchildNodes || grandchildNodes.length === 0) {
|
|
7206
|
+
// This child is a leaf node
|
|
7207
|
+
collection.add(childId);
|
|
7208
|
+
}
|
|
7209
|
+
else {
|
|
7210
|
+
// This child has children, recurse
|
|
7211
|
+
await this.collectLeafDescendants(childId, collection);
|
|
7212
|
+
}
|
|
7213
|
+
}
|
|
7214
|
+
}
|
|
7215
|
+
catch (error) {
|
|
7216
|
+
console.error(`Error collecting leaf descendants for node ${parentId}:`, error);
|
|
7217
|
+
}
|
|
7218
|
+
}
|
|
7219
|
+
/**
|
|
7220
|
+
* Refreshes parent states in the tree based on leaf selection
|
|
7221
|
+
*/
|
|
7222
|
+
async refreshParentStatesInTree() {
|
|
7223
|
+
const treeComponent = this.tree();
|
|
7224
|
+
if (!treeComponent) {
|
|
7225
|
+
return;
|
|
7080
7226
|
}
|
|
7081
|
-
//
|
|
7082
|
-
//
|
|
7227
|
+
// The tree component with intermediate-nested behavior should handle this
|
|
7228
|
+
// But we need to force a refresh by reloading data
|
|
7229
|
+
// For now, we rely on the tree component's built-in behavior
|
|
7083
7230
|
}
|
|
7084
7231
|
/**
|
|
7085
7232
|
* Selects a node and recursively selects all its children
|
|
@@ -7452,357 +7599,296 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
|
|
|
7452
7599
|
// Set flag to prevent recursive updates during initialization
|
|
7453
7600
|
this.isUpdatingSelection = true;
|
|
7454
7601
|
try {
|
|
7455
|
-
// Fetch node data for pre-selected items that aren't in the cache
|
|
7602
|
+
// Step 1: Fetch node data for pre-selected items that aren't in the cache
|
|
7456
7603
|
await this.loadMissingNodeData(ids);
|
|
7457
|
-
//
|
|
7458
|
-
const
|
|
7459
|
-
|
|
7460
|
-
|
|
7461
|
-
|
|
7462
|
-
|
|
7463
|
-
|
|
7464
|
-
|
|
7465
|
-
await this.expandParentsOfSelectedNodes(Array.from(allNodesToSelect));
|
|
7466
|
-
// Sync selection with tree component - wait for tree to be ready
|
|
7467
|
-
await this.syncSelectionWithTree(Array.from(allNodesToSelect));
|
|
7604
|
+
// Step 2: Build complete ancestor chains for all selected nodes
|
|
7605
|
+
const ancestorChains = await this.buildAncestorChains(ids);
|
|
7606
|
+
// Step 3: Set selected node IDs (these should be leaf nodes only)
|
|
7607
|
+
this.selectedNodeIds.set(ids);
|
|
7608
|
+
// Step 4: Expand ancestor nodes in order (root to leaf) to load them into tree
|
|
7609
|
+
await this.expandAncestorNodesInOrder(ancestorChains);
|
|
7610
|
+
// Step 5: Wait for tree to render and sync selection
|
|
7611
|
+
await this.syncSelectionWithTree(ids);
|
|
7468
7612
|
}
|
|
7469
7613
|
finally {
|
|
7470
7614
|
this.isUpdatingSelection = false;
|
|
7471
7615
|
}
|
|
7472
7616
|
}
|
|
7473
7617
|
/**
|
|
7474
|
-
*
|
|
7475
|
-
*
|
|
7618
|
+
* Builds complete ancestor chains for all selected node IDs.
|
|
7619
|
+
* Returns a Map where key is the selected node ID and value is array of ancestor IDs from root to parent.
|
|
7476
7620
|
*/
|
|
7477
|
-
async
|
|
7478
|
-
|
|
7479
|
-
|
|
7621
|
+
async buildAncestorChains(selectedIds) {
|
|
7622
|
+
const ancestorChains = new Map();
|
|
7623
|
+
if (!this.treeData || !this.treeConfig) {
|
|
7624
|
+
return ancestorChains;
|
|
7480
7625
|
}
|
|
7481
7626
|
const parentKey = this.treeData.categoryEntityDef?.parentKey;
|
|
7482
7627
|
if (!parentKey) {
|
|
7483
|
-
|
|
7484
|
-
|
|
7485
|
-
const treeComponent = this.tree();
|
|
7486
|
-
if (!treeComponent) {
|
|
7487
|
-
return;
|
|
7628
|
+
// No parent key means flat structure
|
|
7629
|
+
return ancestorChains;
|
|
7488
7630
|
}
|
|
7489
|
-
const
|
|
7490
|
-
|
|
7491
|
-
|
|
7492
|
-
|
|
7493
|
-
if (selectedId === 'all') {
|
|
7494
|
-
continue;
|
|
7495
|
-
}
|
|
7496
|
-
const nodeData = this.nodeDataCache.get(selectedId);
|
|
7497
|
-
if (!nodeData) {
|
|
7631
|
+
for (const nodeId of selectedIds) {
|
|
7632
|
+
const chain = [];
|
|
7633
|
+
let currentData = this.nodeDataCache.get(nodeId);
|
|
7634
|
+
if (!currentData) {
|
|
7498
7635
|
continue;
|
|
7499
7636
|
}
|
|
7500
|
-
|
|
7501
|
-
|
|
7502
|
-
|
|
7503
|
-
|
|
7637
|
+
// Traverse up to root, collecting ancestor IDs
|
|
7638
|
+
const visited = new Set();
|
|
7639
|
+
while (currentData) {
|
|
7640
|
+
const parentId = currentData[parentKey];
|
|
7641
|
+
if (!parentId || parentId === 'all' || visited.has(String(parentId))) {
|
|
7504
7642
|
break;
|
|
7505
7643
|
}
|
|
7506
|
-
const parentId = currentNodeData[parentKey];
|
|
7507
7644
|
const parentIdStr = String(parentId);
|
|
7508
|
-
|
|
7509
|
-
|
|
7510
|
-
|
|
7511
|
-
|
|
7512
|
-
|
|
7513
|
-
|
|
7514
|
-
|
|
7515
|
-
|
|
7516
|
-
// (when a parent is selected, its parent might also need to be selected)
|
|
7517
|
-
let hasChanges = true;
|
|
7518
|
-
let iterations = 0;
|
|
7519
|
-
const maxIterations = 10; // Prevent infinite loops
|
|
7520
|
-
while (hasChanges && iterations < maxIterations) {
|
|
7521
|
-
iterations++;
|
|
7522
|
-
hasChanges = false;
|
|
7523
|
-
// Update selectedSet with current selectedNodeIds
|
|
7524
|
-
const currentSelected = this.selectedNodeIds();
|
|
7525
|
-
currentSelected.forEach((id) => selectedSet.add(id));
|
|
7526
|
-
// Update each parent's state
|
|
7527
|
-
for (const parentId of processedParents) {
|
|
7528
|
-
try {
|
|
7529
|
-
const childNodes = await this.datasource(parentId);
|
|
7530
|
-
if (!childNodes || childNodes.length === 0) {
|
|
7531
|
-
continue;
|
|
7532
|
-
}
|
|
7533
|
-
let selectedCount = 0;
|
|
7534
|
-
let totalCount = 0;
|
|
7535
|
-
for (const childNode of childNodes) {
|
|
7536
|
-
const childId = String(childNode['id'] ?? '');
|
|
7537
|
-
if (childId && childId !== 'all') {
|
|
7538
|
-
totalCount++;
|
|
7539
|
-
if (selectedSet.has(childId)) {
|
|
7540
|
-
selectedCount++;
|
|
7541
|
-
}
|
|
7542
|
-
}
|
|
7645
|
+
visited.add(parentIdStr);
|
|
7646
|
+
chain.unshift(parentIdStr); // Add to beginning (root first)
|
|
7647
|
+
// Load parent data if not cached
|
|
7648
|
+
if (!this.nodeDataCache.has(parentIdStr)) {
|
|
7649
|
+
const parentData = await this.fetchItemById(parentIdStr);
|
|
7650
|
+
if (parentData) {
|
|
7651
|
+
this.nodeDataCache.set(parentIdStr, parentData);
|
|
7652
|
+
currentData = parentData;
|
|
7543
7653
|
}
|
|
7544
|
-
|
|
7545
|
-
|
|
7546
|
-
if (selectedCount === totalCount) {
|
|
7547
|
-
// All children selected - select parent
|
|
7548
|
-
if (!isParentSelected) {
|
|
7549
|
-
this.selectedNodeIds.set([...currentSelected, parentId]);
|
|
7550
|
-
selectedSet.add(parentId);
|
|
7551
|
-
hasChanges = true;
|
|
7552
|
-
try {
|
|
7553
|
-
treeComponent.selectNode(parentId);
|
|
7554
|
-
}
|
|
7555
|
-
catch {
|
|
7556
|
-
// Parent might not be in tree
|
|
7557
|
-
}
|
|
7558
|
-
}
|
|
7559
|
-
}
|
|
7560
|
-
else if (selectedCount > 0) {
|
|
7561
|
-
// Some children selected - parent should be in intermediate state
|
|
7562
|
-
if (isParentSelected) {
|
|
7563
|
-
// Deselect parent to show intermediate state
|
|
7564
|
-
this.selectedNodeIds.set(currentSelected.filter((id) => id !== parentId));
|
|
7565
|
-
selectedSet.delete(parentId);
|
|
7566
|
-
hasChanges = true;
|
|
7567
|
-
try {
|
|
7568
|
-
treeComponent.deselectNode(parentId);
|
|
7569
|
-
}
|
|
7570
|
-
catch {
|
|
7571
|
-
// Parent might not be in tree
|
|
7572
|
-
}
|
|
7573
|
-
}
|
|
7574
|
-
}
|
|
7575
|
-
else {
|
|
7576
|
-
// No children selected - deselect parent
|
|
7577
|
-
if (isParentSelected) {
|
|
7578
|
-
this.selectedNodeIds.set(currentSelected.filter((id) => id !== parentId));
|
|
7579
|
-
selectedSet.delete(parentId);
|
|
7580
|
-
hasChanges = true;
|
|
7581
|
-
try {
|
|
7582
|
-
treeComponent.deselectNode(parentId);
|
|
7583
|
-
}
|
|
7584
|
-
catch {
|
|
7585
|
-
// Parent might not be in tree
|
|
7586
|
-
}
|
|
7587
|
-
}
|
|
7588
|
-
}
|
|
7654
|
+
else {
|
|
7655
|
+
break;
|
|
7589
7656
|
}
|
|
7590
7657
|
}
|
|
7591
|
-
|
|
7592
|
-
|
|
7658
|
+
else {
|
|
7659
|
+
currentData = this.nodeDataCache.get(parentIdStr);
|
|
7593
7660
|
}
|
|
7594
7661
|
}
|
|
7662
|
+
ancestorChains.set(nodeId, chain);
|
|
7595
7663
|
}
|
|
7664
|
+
return ancestorChains;
|
|
7596
7665
|
}
|
|
7597
7666
|
/**
|
|
7598
|
-
* Expands
|
|
7667
|
+
* Expands ancestor nodes in order from root to leaf.
|
|
7668
|
+
* This ensures parent nodes are loaded into the tree before selecting children.
|
|
7599
7669
|
*/
|
|
7600
|
-
async
|
|
7601
|
-
if (!this.treeData || !this.treeConfig || selectedIds.length === 0) {
|
|
7602
|
-
return;
|
|
7603
|
-
}
|
|
7604
|
-
const parentKey = this.treeData.categoryEntityDef?.parentKey;
|
|
7605
|
-
if (!parentKey) {
|
|
7606
|
-
return; // No parent key means flat structure
|
|
7607
|
-
}
|
|
7670
|
+
async expandAncestorNodesInOrder(ancestorChains) {
|
|
7608
7671
|
const treeComponent = this.tree();
|
|
7609
7672
|
if (!treeComponent) {
|
|
7610
7673
|
return;
|
|
7611
7674
|
}
|
|
7612
|
-
// Collect all unique
|
|
7613
|
-
const
|
|
7614
|
-
|
|
7615
|
-
|
|
7616
|
-
|
|
7617
|
-
|
|
7618
|
-
|
|
7619
|
-
|
|
7620
|
-
|
|
7621
|
-
|
|
7622
|
-
const parentId = nodeData[parentKey];
|
|
7623
|
-
if (parentId && parentId !== 'all') {
|
|
7624
|
-
const parentIdStr = String(parentId);
|
|
7625
|
-
parentsToExpand.add(parentIdStr);
|
|
7626
|
-
// Also expand grandparents, etc.
|
|
7627
|
-
let currentParentId = parentIdStr;
|
|
7628
|
-
while (currentParentId) {
|
|
7629
|
-
const parentData = this.nodeDataCache.get(currentParentId);
|
|
7630
|
-
if (!parentData) {
|
|
7631
|
-
break;
|
|
7632
|
-
}
|
|
7633
|
-
const grandParentId = parentData[parentKey];
|
|
7634
|
-
if (grandParentId && grandParentId !== 'all' && grandParentId !== currentParentId) {
|
|
7635
|
-
parentsToExpand.add(String(grandParentId));
|
|
7636
|
-
currentParentId = String(grandParentId);
|
|
7637
|
-
}
|
|
7638
|
-
else {
|
|
7639
|
-
break;
|
|
7640
|
-
}
|
|
7675
|
+
// Collect all unique ancestors and sort by depth (shallowest first)
|
|
7676
|
+
const allAncestors = new Set();
|
|
7677
|
+
const ancestorDepths = new Map();
|
|
7678
|
+
ancestorChains.forEach((chain) => {
|
|
7679
|
+
chain.forEach((ancestorId, depth) => {
|
|
7680
|
+
allAncestors.add(ancestorId);
|
|
7681
|
+
// Keep the minimum depth if ancestor appears in multiple chains
|
|
7682
|
+
const existingDepth = ancestorDepths.get(ancestorId);
|
|
7683
|
+
if (existingDepth === undefined || depth < existingDepth) {
|
|
7684
|
+
ancestorDepths.set(ancestorId, depth);
|
|
7641
7685
|
}
|
|
7642
|
-
}
|
|
7643
|
-
}
|
|
7644
|
-
// Expand all parents (starting from root to leaves)
|
|
7645
|
-
const sortedParents = Array.from(parentsToExpand).sort((a, b) => {
|
|
7646
|
-
// Simple sort - expand root nodes first
|
|
7647
|
-
return a.localeCompare(b);
|
|
7686
|
+
});
|
|
7648
7687
|
});
|
|
7649
|
-
|
|
7650
|
-
|
|
7651
|
-
|
|
7652
|
-
|
|
7653
|
-
|
|
7654
|
-
|
|
7655
|
-
|
|
7656
|
-
|
|
7657
|
-
|
|
7658
|
-
// Parent might not be in tree yet, ignore
|
|
7688
|
+
// Sort by depth (root ancestors first)
|
|
7689
|
+
const sortedAncestors = Array.from(allAncestors).sort((a, b) => {
|
|
7690
|
+
return (ancestorDepths.get(a) ?? 0) - (ancestorDepths.get(b) ?? 0);
|
|
7691
|
+
});
|
|
7692
|
+
// First expand root node if not expanded
|
|
7693
|
+
try {
|
|
7694
|
+
if (!treeComponent.isNodeExpanded('all')) {
|
|
7695
|
+
await treeComponent.expandNode('all');
|
|
7696
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
7659
7697
|
}
|
|
7660
7698
|
}
|
|
7661
|
-
|
|
7662
|
-
|
|
7663
|
-
* Selects parents whose all children are selected
|
|
7664
|
-
* This ensures that when all children of a parent are selected, the parent also appears selected
|
|
7665
|
-
*/
|
|
7666
|
-
async selectParentsWithAllChildrenSelected(selectedIds) {
|
|
7667
|
-
if (!this.treeData || !this.treeConfig || selectedIds.length === 0) {
|
|
7668
|
-
return;
|
|
7669
|
-
}
|
|
7670
|
-
const parentKey = this.treeData.categoryEntityDef?.parentKey;
|
|
7671
|
-
if (!parentKey) {
|
|
7672
|
-
return; // No parent key means flat structure
|
|
7699
|
+
catch {
|
|
7700
|
+
// Root might not exist
|
|
7673
7701
|
}
|
|
7674
|
-
//
|
|
7675
|
-
const
|
|
7676
|
-
const selectedSet = new Set(currentSelectedIds);
|
|
7677
|
-
const processedParents = new Set();
|
|
7678
|
-
const parentsToSelect = new Set();
|
|
7679
|
-
// For each selected node, check its parent
|
|
7680
|
-
for (const selectedId of currentSelectedIds) {
|
|
7681
|
-
if (selectedId === 'all') {
|
|
7682
|
-
continue;
|
|
7683
|
-
}
|
|
7684
|
-
// Get parent ID from cached data
|
|
7685
|
-
const nodeData = this.nodeDataCache.get(selectedId);
|
|
7686
|
-
if (!nodeData) {
|
|
7687
|
-
continue;
|
|
7688
|
-
}
|
|
7689
|
-
const parentId = nodeData[parentKey];
|
|
7690
|
-
if (!parentId || parentId === 'all' || processedParents.has(String(parentId))) {
|
|
7691
|
-
continue;
|
|
7692
|
-
}
|
|
7693
|
-
const parentIdStr = String(parentId);
|
|
7694
|
-
processedParents.add(parentIdStr);
|
|
7695
|
-
// Load all children of this parent
|
|
7702
|
+
// Expand ancestors in order - this loads them into the tree via datasource callback
|
|
7703
|
+
for (const ancestorId of sortedAncestors) {
|
|
7696
7704
|
try {
|
|
7697
|
-
|
|
7698
|
-
|
|
7699
|
-
|
|
7700
|
-
|
|
7701
|
-
// Check if all children are selected
|
|
7702
|
-
let allChildrenSelected = true;
|
|
7703
|
-
let hasChildren = false;
|
|
7704
|
-
for (const childNode of childNodes) {
|
|
7705
|
-
const childId = String(childNode['id'] ?? '');
|
|
7706
|
-
if (childId && childId !== 'all') {
|
|
7707
|
-
hasChildren = true;
|
|
7708
|
-
if (!selectedSet.has(childId)) {
|
|
7709
|
-
allChildrenSelected = false;
|
|
7710
|
-
break;
|
|
7711
|
-
}
|
|
7712
|
-
}
|
|
7713
|
-
}
|
|
7714
|
-
// If parent has children and all are selected, mark parent for selection
|
|
7715
|
-
if (hasChildren && allChildrenSelected) {
|
|
7716
|
-
parentsToSelect.add(parentIdStr);
|
|
7717
|
-
// Cache parent data if not already cached
|
|
7718
|
-
if (!this.nodeDataCache.has(parentIdStr)) {
|
|
7719
|
-
const parentData = await this.fetchItemById(parentIdStr);
|
|
7720
|
-
if (parentData) {
|
|
7721
|
-
this.nodeDataCache.set(parentIdStr, parentData);
|
|
7722
|
-
}
|
|
7723
|
-
}
|
|
7724
|
-
}
|
|
7725
|
-
}
|
|
7726
|
-
catch (error) {
|
|
7727
|
-
console.error(`Error checking parent ${parentIdStr}:`, error);
|
|
7728
|
-
}
|
|
7729
|
-
}
|
|
7730
|
-
// Add parents to selectedNodeIds
|
|
7731
|
-
if (parentsToSelect.size > 0) {
|
|
7732
|
-
const currentSelected = this.selectedNodeIds();
|
|
7733
|
-
const newSelected = [...currentSelected];
|
|
7734
|
-
let hasNewSelections = false;
|
|
7735
|
-
for (const parentId of parentsToSelect) {
|
|
7736
|
-
if (!newSelected.includes(parentId)) {
|
|
7737
|
-
newSelected.push(parentId);
|
|
7738
|
-
hasNewSelections = true;
|
|
7705
|
+
if (!treeComponent.isNodeExpanded(ancestorId)) {
|
|
7706
|
+
await treeComponent.expandNode(ancestorId);
|
|
7707
|
+
// Small delay to allow tree to process the expansion
|
|
7708
|
+
await new Promise((resolve) => setTimeout(resolve, 30));
|
|
7739
7709
|
}
|
|
7740
7710
|
}
|
|
7741
|
-
|
|
7742
|
-
|
|
7743
|
-
// Recursively check parents of these parents
|
|
7744
|
-
const updatedSelected = this.selectedNodeIds();
|
|
7745
|
-
await this.selectParentsWithAllChildrenSelected(updatedSelected);
|
|
7711
|
+
catch {
|
|
7712
|
+
// Ancestor might not be in tree yet, continue
|
|
7746
7713
|
}
|
|
7747
7714
|
}
|
|
7748
7715
|
}
|
|
7749
7716
|
/**
|
|
7750
|
-
* Syncs selection state with the tree component
|
|
7751
|
-
*
|
|
7717
|
+
* Syncs selection state with the tree component.
|
|
7718
|
+
* Selects leaf nodes and manually updates parent states (indeterminate/selected).
|
|
7752
7719
|
*/
|
|
7753
7720
|
async syncSelectionWithTree(selectedIds) {
|
|
7754
7721
|
const treeComponent = this.tree();
|
|
7755
7722
|
if (!treeComponent || selectedIds.length === 0) {
|
|
7756
7723
|
return;
|
|
7757
7724
|
}
|
|
7758
|
-
// Wait for tree to be fully initialized
|
|
7759
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
7725
|
+
// Wait for tree to be fully initialized after ancestor expansion
|
|
7726
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
7727
|
+
// Keep track of successfully synced nodes
|
|
7728
|
+
const syncedNodes = new Set();
|
|
7760
7729
|
// Try multiple times to sync selection (nodes might load progressively)
|
|
7761
|
-
for (let attempt = 0; attempt <
|
|
7762
|
-
|
|
7763
|
-
|
|
7764
|
-
if (id && id !== 'all') {
|
|
7730
|
+
for (let attempt = 0; attempt < 5; attempt++) {
|
|
7731
|
+
for (const id of selectedIds) {
|
|
7732
|
+
if (id && id !== 'all' && !syncedNodes.has(id)) {
|
|
7765
7733
|
try {
|
|
7766
7734
|
const node = treeComponent.findNode(id);
|
|
7767
7735
|
if (node) {
|
|
7768
|
-
// Node exists in tree
|
|
7769
|
-
|
|
7770
|
-
|
|
7736
|
+
// Node exists in tree - mark it as selected using both methods
|
|
7737
|
+
// 1. Direct property assignment for immediate visual feedback
|
|
7738
|
+
node['selected'] = true;
|
|
7739
|
+
node['indeterminate'] = false;
|
|
7740
|
+
// 2. Use selectNode API for tree's internal state tracking
|
|
7741
|
+
try {
|
|
7742
|
+
treeComponent.selectNode(id);
|
|
7743
|
+
}
|
|
7744
|
+
catch {
|
|
7745
|
+
// selectNode might fail but direct assignment should work
|
|
7746
|
+
}
|
|
7747
|
+
syncedNodes.add(id);
|
|
7771
7748
|
}
|
|
7772
7749
|
}
|
|
7773
7750
|
catch {
|
|
7774
|
-
// Node not in tree yet
|
|
7751
|
+
// Node not in tree yet
|
|
7775
7752
|
}
|
|
7776
7753
|
}
|
|
7777
|
-
}
|
|
7754
|
+
}
|
|
7778
7755
|
// If all nodes are synced, we're done
|
|
7779
|
-
if (
|
|
7756
|
+
if (syncedNodes.size === selectedIds.length) {
|
|
7780
7757
|
break;
|
|
7781
7758
|
}
|
|
7782
|
-
// Wait
|
|
7783
|
-
|
|
7784
|
-
|
|
7759
|
+
// Wait before next attempt
|
|
7760
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
7761
|
+
}
|
|
7762
|
+
// Now update parent states (indeterminate/selected) based on children
|
|
7763
|
+
// This uses bottom-up traversal with datasource fallback for loading children
|
|
7764
|
+
await this.updateParentStatesAfterSelection(selectedIds);
|
|
7765
|
+
// Refresh tree to apply changes
|
|
7766
|
+
treeComponent.refresh();
|
|
7767
|
+
// Trigger change detection to update UI
|
|
7768
|
+
this.changeDetectorRef.markForCheck();
|
|
7769
|
+
}
|
|
7770
|
+
/**
|
|
7771
|
+
* Updates parent node states (selected/indeterminate) based on children selection.
|
|
7772
|
+
* Called after leaf nodes are selected to properly show parent states.
|
|
7773
|
+
* Works bottom-up from deepest parents to root, tracking states in a Map.
|
|
7774
|
+
*/
|
|
7775
|
+
async updateParentStatesAfterSelection(selectedLeafIds) {
|
|
7776
|
+
if (!this.treeData || !this.treeConfig || selectedLeafIds.length === 0) {
|
|
7777
|
+
return;
|
|
7778
|
+
}
|
|
7779
|
+
const parentKey = this.treeData.categoryEntityDef?.parentKey;
|
|
7780
|
+
if (!parentKey) {
|
|
7781
|
+
return;
|
|
7782
|
+
}
|
|
7783
|
+
const treeComponent = this.tree();
|
|
7784
|
+
if (!treeComponent) {
|
|
7785
|
+
return;
|
|
7786
|
+
}
|
|
7787
|
+
// Track node states: { selected: boolean, indeterminate: boolean }
|
|
7788
|
+
// This allows us to reference child states when processing parents
|
|
7789
|
+
const nodeStates = new Map();
|
|
7790
|
+
// Initialize with selected leaf nodes
|
|
7791
|
+
for (const leafId of selectedLeafIds) {
|
|
7792
|
+
nodeStates.set(leafId, { selected: true, indeterminate: false });
|
|
7793
|
+
}
|
|
7794
|
+
// Collect all unique parent IDs from selected nodes
|
|
7795
|
+
const parentsToUpdate = new Set();
|
|
7796
|
+
for (const leafId of selectedLeafIds) {
|
|
7797
|
+
const nodeData = this.nodeDataCache.get(leafId);
|
|
7798
|
+
if (!nodeData) {
|
|
7799
|
+
continue;
|
|
7800
|
+
}
|
|
7801
|
+
// Traverse up the parent chain
|
|
7802
|
+
let currentData = nodeData;
|
|
7803
|
+
while (currentData) {
|
|
7804
|
+
const parentId = currentData[parentKey];
|
|
7805
|
+
if (!parentId || parentId === 'all') {
|
|
7806
|
+
break;
|
|
7807
|
+
}
|
|
7808
|
+
parentsToUpdate.add(String(parentId));
|
|
7809
|
+
currentData = this.nodeDataCache.get(String(parentId));
|
|
7785
7810
|
}
|
|
7786
7811
|
}
|
|
7787
|
-
//
|
|
7788
|
-
|
|
7789
|
-
|
|
7812
|
+
// Sort parents by depth (deepest first so we update bottom-up)
|
|
7813
|
+
const sortedParents = await this.sortParentsByDepth(Array.from(parentsToUpdate));
|
|
7814
|
+
const reversedParents = [...sortedParents].reverse(); // Deepest first
|
|
7815
|
+
for (const parentId of reversedParents) {
|
|
7790
7816
|
try {
|
|
7791
|
-
const
|
|
7792
|
-
|
|
7793
|
-
|
|
7794
|
-
|
|
7795
|
-
|
|
7796
|
-
|
|
7817
|
+
const parentNode = treeComponent.findNode(parentId);
|
|
7818
|
+
// Get all direct children of this parent
|
|
7819
|
+
// First try from tree node, then fall back to datasource
|
|
7820
|
+
let childrenList = [];
|
|
7821
|
+
const treeChildren = parentNode?.['children'];
|
|
7822
|
+
if (treeChildren && treeChildren.length > 0) {
|
|
7823
|
+
childrenList = treeChildren.map((c) => ({ id: String(c['id'] ?? '') }));
|
|
7824
|
+
}
|
|
7825
|
+
else {
|
|
7826
|
+
// Children not in tree node - load via datasource
|
|
7827
|
+
try {
|
|
7828
|
+
const dsChildren = await this.datasource(parentId);
|
|
7829
|
+
if (dsChildren && dsChildren.length > 0) {
|
|
7830
|
+
childrenList = dsChildren.map((c) => ({ id: String(c['id'] ?? '') }));
|
|
7831
|
+
}
|
|
7832
|
+
}
|
|
7833
|
+
catch {
|
|
7834
|
+
// Datasource failed, skip this parent
|
|
7835
|
+
continue;
|
|
7836
|
+
}
|
|
7837
|
+
}
|
|
7838
|
+
if (childrenList.length === 0) {
|
|
7839
|
+
continue;
|
|
7840
|
+
}
|
|
7841
|
+
// Count child states using our tracked nodeStates Map
|
|
7842
|
+
let fullySelectedCount = 0;
|
|
7843
|
+
let indeterminateCount = 0;
|
|
7844
|
+
let totalCount = 0;
|
|
7845
|
+
for (const child of childrenList) {
|
|
7846
|
+
if (!child.id || child.id === 'all') {
|
|
7847
|
+
continue;
|
|
7848
|
+
}
|
|
7849
|
+
totalCount++;
|
|
7850
|
+
const childState = nodeStates.get(child.id);
|
|
7851
|
+
if (childState) {
|
|
7852
|
+
if (childState.selected && !childState.indeterminate) {
|
|
7853
|
+
// Child is fully selected
|
|
7854
|
+
fullySelectedCount++;
|
|
7855
|
+
}
|
|
7856
|
+
else if (childState.indeterminate) {
|
|
7857
|
+
// Child is indeterminate
|
|
7858
|
+
indeterminateCount++;
|
|
7859
|
+
}
|
|
7860
|
+
// If neither, child is not selected at all
|
|
7861
|
+
}
|
|
7862
|
+
// If childState is undefined, child is not in our tracking = not selected
|
|
7863
|
+
}
|
|
7864
|
+
// Determine parent state based on children
|
|
7865
|
+
let parentSelected = false;
|
|
7866
|
+
let parentIndeterminate = false;
|
|
7867
|
+
if (totalCount > 0) {
|
|
7868
|
+
if (fullySelectedCount === totalCount && indeterminateCount === 0) {
|
|
7869
|
+
// All children are fully selected - parent is fully selected
|
|
7870
|
+
parentSelected = true;
|
|
7871
|
+
parentIndeterminate = false;
|
|
7872
|
+
}
|
|
7873
|
+
else if (fullySelectedCount > 0 || indeterminateCount > 0) {
|
|
7874
|
+
// Some children selected or indeterminate - parent is indeterminate
|
|
7875
|
+
parentSelected = true;
|
|
7876
|
+
parentIndeterminate = true;
|
|
7877
|
+
}
|
|
7878
|
+
// else: no children selected - parent stays unselected
|
|
7879
|
+
}
|
|
7880
|
+
// Store state in our tracking Map (for use by parent's parent)
|
|
7881
|
+
nodeStates.set(parentId, { selected: parentSelected, indeterminate: parentIndeterminate });
|
|
7882
|
+
// Update tree node if it exists
|
|
7883
|
+
if (parentNode) {
|
|
7884
|
+
parentNode['selected'] = parentSelected;
|
|
7885
|
+
parentNode['indeterminate'] = parentIndeterminate;
|
|
7886
|
+
}
|
|
7797
7887
|
}
|
|
7798
|
-
catch {
|
|
7799
|
-
|
|
7888
|
+
catch (error) {
|
|
7889
|
+
console.error(`Error updating parent state for ${parentId}:`, error);
|
|
7800
7890
|
}
|
|
7801
7891
|
}
|
|
7802
|
-
// Note: Selection state is maintained in selectedNodeIds signal
|
|
7803
|
-
// The tree component will sync selection state when nodes are loaded via datasource
|
|
7804
|
-
// We mark nodes as selected in the datasource callback by setting node.selected = true
|
|
7805
|
-
// Nodes that aren't in the tree yet will be selected when their parent is expanded
|
|
7806
7892
|
}
|
|
7807
7893
|
/**
|
|
7808
7894
|
* Loads node data for IDs that are selected but not yet in the cache.
|
|
@@ -7970,7 +8056,7 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
|
|
|
7970
8056
|
}
|
|
7971
8057
|
/**
|
|
7972
8058
|
* Calculate the full path from root to the selected node.
|
|
7973
|
-
* Returns an array of strings like ["
|
|
8059
|
+
* Returns an array of strings like ["Operations", "IT Operations"] for node "IT Operations" under parent "Operations".
|
|
7974
8060
|
* Uses tree-view's getNodePath() API when available, otherwise falls back to manual calculation.
|
|
7975
8061
|
*/
|
|
7976
8062
|
async calculateNodePath(nodeId, nodeData) {
|
|
@@ -7982,6 +8068,7 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
|
|
|
7982
8068
|
if (treeComponent) {
|
|
7983
8069
|
try {
|
|
7984
8070
|
// Use tree-view's getNodePath() to get ID path, then convert to titles
|
|
8071
|
+
// Note: getNodePath() returns the path INCLUDING the node itself
|
|
7985
8072
|
const idPath = treeComponent.getNodePath(nodeId);
|
|
7986
8073
|
if (idPath && idPath.length > 0) {
|
|
7987
8074
|
const titlePath = [];
|
|
@@ -8006,12 +8093,8 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
|
|
|
8006
8093
|
}
|
|
8007
8094
|
}
|
|
8008
8095
|
}
|
|
8009
|
-
//
|
|
8010
|
-
|
|
8011
|
-
if (currentTitle) {
|
|
8012
|
-
titlePath.push(currentTitle);
|
|
8013
|
-
}
|
|
8014
|
-
return titlePath.length > 0 ? titlePath : currentTitle ? [currentTitle] : [];
|
|
8096
|
+
// DO NOT add current node title again - it's already included in idPath
|
|
8097
|
+
return titlePath.length > 0 ? titlePath : this.getNodeTitle(nodeData) ? [this.getNodeTitle(nodeData)] : [];
|
|
8015
8098
|
}
|
|
8016
8099
|
}
|
|
8017
8100
|
catch (error) {
|
|
@@ -8152,15 +8235,13 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
|
|
|
8152
8235
|
}
|
|
8153
8236
|
}
|
|
8154
8237
|
async getSelectedItems() {
|
|
8155
|
-
//
|
|
8156
|
-
// This ensures we get all selected nodes, even if they're not in the tree structure yet
|
|
8238
|
+
// selectedNodeIds now only contains LEAF nodes (already filtered)
|
|
8157
8239
|
const selectedIds = this.selectedNodeIds();
|
|
8158
8240
|
if (selectedIds.length === 0) {
|
|
8159
8241
|
return [];
|
|
8160
8242
|
}
|
|
8161
8243
|
const treeComponent = this.tree();
|
|
8162
8244
|
// Get node data from cache and calculate paths
|
|
8163
|
-
// For nodes not in cache, try to get from tree component
|
|
8164
8245
|
const items = await Promise.all(selectedIds.map(async (id) => {
|
|
8165
8246
|
// First try to get from cache
|
|
8166
8247
|
let nodeData = this.nodeDataCache.get(id);
|
|
@@ -8177,20 +8258,22 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
|
|
|
8177
8258
|
}
|
|
8178
8259
|
}
|
|
8179
8260
|
catch {
|
|
8180
|
-
// Node not found in tree
|
|
8261
|
+
// Node not found in tree
|
|
8262
|
+
}
|
|
8263
|
+
}
|
|
8264
|
+
// If still no data, try to fetch from server
|
|
8265
|
+
if (!nodeData) {
|
|
8266
|
+
const fetchedData = await this.fetchItemById(id);
|
|
8267
|
+
if (fetchedData) {
|
|
8268
|
+
nodeData = fetchedData;
|
|
8269
|
+
this.nodeDataCache.set(id, fetchedData);
|
|
8181
8270
|
}
|
|
8182
8271
|
}
|
|
8183
8272
|
// If still no data, skip this node
|
|
8184
8273
|
if (!nodeData) {
|
|
8274
|
+
console.warn(`Could not find data for selected node: ${id}`);
|
|
8185
8275
|
return null;
|
|
8186
8276
|
}
|
|
8187
|
-
// When selectionBehavior is intermediate-nested, only return leaf nodes
|
|
8188
|
-
if (this.allowMultiple()) {
|
|
8189
|
-
const isLeaf = await this.isLeafNode(id, treeComponent ?? null);
|
|
8190
|
-
if (!isLeaf) {
|
|
8191
|
-
return null; // Skip non-leaf nodes
|
|
8192
|
-
}
|
|
8193
|
-
}
|
|
8194
8277
|
const path = await this.calculateNodePath(id, nodeData);
|
|
8195
8278
|
return {
|
|
8196
8279
|
...nodeData,
|
|
@@ -8301,6 +8384,13 @@ class AXPEntityCategoryTreeSelectorComponent extends AXBasePageComponent {
|
|
|
8301
8384
|
<span class="ax-text-base ax-font-medium ax-text-text-primary">
|
|
8302
8385
|
{{ '@general:terms.interface.items-selected' | translate | async }}
|
|
8303
8386
|
</span>
|
|
8387
|
+
<ax-button
|
|
8388
|
+
look="blank"
|
|
8389
|
+
color="ghost"
|
|
8390
|
+
class="ax-text-sm"
|
|
8391
|
+
[text]="'@general:terms.interface.unselect-all' | translate | async"
|
|
8392
|
+
(onClick)="onClearAll()"
|
|
8393
|
+
></ax-button>
|
|
8304
8394
|
</div>
|
|
8305
8395
|
</ax-prefix>
|
|
8306
8396
|
}
|
|
@@ -8425,6 +8515,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
|
|
|
8425
8515
|
<span class="ax-text-base ax-font-medium ax-text-text-primary">
|
|
8426
8516
|
{{ '@general:terms.interface.items-selected' | translate | async }}
|
|
8427
8517
|
</span>
|
|
8518
|
+
<ax-button
|
|
8519
|
+
look="blank"
|
|
8520
|
+
color="ghost"
|
|
8521
|
+
class="ax-text-sm"
|
|
8522
|
+
[text]="'@general:terms.interface.unselect-all' | translate | async"
|
|
8523
|
+
(onClick)="onClearAll()"
|
|
8524
|
+
></ax-button>
|
|
8428
8525
|
</div>
|
|
8429
8526
|
</ax-prefix>
|
|
8430
8527
|
}
|
|
@@ -8526,6 +8623,13 @@ class AXPEntityCategoryWidgetColumnComponent extends AXPColumnWidgetComponent {
|
|
|
8526
8623
|
//#endregion
|
|
8527
8624
|
//#region ---- Computed Properties (Derived) ----
|
|
8528
8625
|
this.visibleItems = computed(() => this.displayItems(), ...(ngDevMode ? [{ debugName: "visibleItems" }] : []));
|
|
8626
|
+
this.columnItems = computed(() => {
|
|
8627
|
+
return this.visibleItems().map((item) => ({
|
|
8628
|
+
id: this.getItemId(item),
|
|
8629
|
+
content: this.getItemText(item),
|
|
8630
|
+
originalItem: item, // Store original item for template access
|
|
8631
|
+
}));
|
|
8632
|
+
}, ...(ngDevMode ? [{ debugName: "columnItems" }] : []));
|
|
8529
8633
|
}
|
|
8530
8634
|
set rawValueInput(value) {
|
|
8531
8635
|
this.rawValueSignal.set(value);
|
|
@@ -8544,15 +8648,19 @@ class AXPEntityCategoryWidgetColumnComponent extends AXPColumnWidgetComponent {
|
|
|
8544
8648
|
textField: this.textField(),
|
|
8545
8649
|
valueField: this.valueField(),
|
|
8546
8650
|
item,
|
|
8547
|
-
breadcrumb: this.
|
|
8651
|
+
breadcrumb: this.getBreadcrumbPath(item),
|
|
8548
8652
|
});
|
|
8549
8653
|
}
|
|
8550
8654
|
}
|
|
8551
|
-
handleItemClick(
|
|
8655
|
+
handleItemClick(listItem) {
|
|
8656
|
+
const originalItem = listItem.originalItem;
|
|
8657
|
+
if (!originalItem) {
|
|
8658
|
+
return;
|
|
8659
|
+
}
|
|
8552
8660
|
const items = this.visibleItems();
|
|
8553
|
-
|
|
8554
|
-
|
|
8555
|
-
this.showItemDetail(
|
|
8661
|
+
const itemIndex = items.findIndex((item) => this.getItemId(item) === listItem.id);
|
|
8662
|
+
if (itemIndex >= 0) {
|
|
8663
|
+
this.showItemDetail(originalItem, itemIndex);
|
|
8556
8664
|
}
|
|
8557
8665
|
}
|
|
8558
8666
|
getItemPath(item) {
|
|
@@ -8588,6 +8696,13 @@ class AXPEntityCategoryWidgetColumnComponent extends AXPColumnWidgetComponent {
|
|
|
8588
8696
|
}
|
|
8589
8697
|
return String(get(item, this.valueField()) ?? '');
|
|
8590
8698
|
}
|
|
8699
|
+
getBreadcrumbPath(item) {
|
|
8700
|
+
const path = this.getItemPath(item);
|
|
8701
|
+
if (path.length === 0) {
|
|
8702
|
+
return '';
|
|
8703
|
+
}
|
|
8704
|
+
return this.joinPath(path);
|
|
8705
|
+
}
|
|
8591
8706
|
//#endregion
|
|
8592
8707
|
//#region ---- Private Methods ----
|
|
8593
8708
|
async extractItemWithPath(item) {
|
|
@@ -8703,23 +8818,38 @@ class AXPEntityCategoryWidgetColumnComponent extends AXPColumnWidgetComponent {
|
|
|
8703
8818
|
}
|
|
8704
8819
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AXPEntityCategoryWidgetColumnComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
8705
8820
|
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.15", type: AXPEntityCategoryWidgetColumnComponent, isStandalone: true, selector: "axp-entity-category-widget-column", inputs: { rawValue: "rawValue", rowData: "rowData", rawValueInput: ["rawValue", "rawValueInput"] }, host: { classAttribute: "ax-w-full" }, usesInheritance: true, ngImport: i0, template: `
|
|
8706
|
-
<
|
|
8707
|
-
|
|
8708
|
-
|
|
8709
|
-
|
|
8710
|
-
|
|
8711
|
-
|
|
8712
|
-
|
|
8713
|
-
|
|
8714
|
-
|
|
8715
|
-
|
|
8716
|
-
|
|
8717
|
-
|
|
8718
|
-
|
|
8719
|
-
|
|
8720
|
-
|
|
8721
|
-
|
|
8722
|
-
|
|
8821
|
+
<axp-column-item-list [items]="columnItems()" [itemTemplate]="itemTemplate" />
|
|
8822
|
+
|
|
8823
|
+
<!-- Item Template -->
|
|
8824
|
+
<ng-template #itemTemplate let-item>
|
|
8825
|
+
<div>
|
|
8826
|
+
@if (hasParent(item.originalItem)) {
|
|
8827
|
+
<ax-badge
|
|
8828
|
+
[color]="'primary'"
|
|
8829
|
+
[look]="'twotone'"
|
|
8830
|
+
class="ax-p-0.5"
|
|
8831
|
+
[text]="getItemText(item.originalItem)"
|
|
8832
|
+
[axTooltip]="getBreadcrumbPath(item.originalItem)"
|
|
8833
|
+
[axTooltipPlacement]="'top'"
|
|
8834
|
+
>
|
|
8835
|
+
<ax-prefix>
|
|
8836
|
+
<i class="fal fa-ellipsis-h ax-text-on-primary ax-text-xs ax-ps-1"></i>
|
|
8837
|
+
<i class="fal fa-chevron-right rtl:ax-rotate-180 ax-text-on-primary ax-text-xs ax-px-1"></i>
|
|
8838
|
+
</ax-prefix>
|
|
8839
|
+
</ax-badge>
|
|
8840
|
+
} @else {
|
|
8841
|
+
<ax-badge
|
|
8842
|
+
[color]="'primary'"
|
|
8843
|
+
[look]="'twotone'"
|
|
8844
|
+
class="ax-p-0.5"
|
|
8845
|
+
[text]="getItemText(item.originalItem)"
|
|
8846
|
+
[axTooltip]="getBreadcrumbPath(item.originalItem)"
|
|
8847
|
+
[axTooltipPlacement]="'top'"
|
|
8848
|
+
></ax-badge>
|
|
8849
|
+
}
|
|
8850
|
+
</div>
|
|
8851
|
+
</ng-template>
|
|
8852
|
+
`, isInline: true, dependencies: [{ kind: "component", type: AXPColumnItemListComponent, selector: "axp-column-item-list", inputs: ["items", "itemTemplate"], outputs: ["itemClick"] }, { kind: "ngmodule", type: AXBadgeModule }, { kind: "component", type: i2$1.AXBadgeComponent, selector: "ax-badge", inputs: ["color", "look", "text"] }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i3$3.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: AXTooltipModule }, { kind: "directive", type: i3$4.AXTooltipDirective, selector: "[axTooltip]", inputs: ["axTooltipDisabled", "axTooltip", "axTooltipContext", "axTooltipPlacement", "axTooltipOffsetX", "axTooltipOffsetY", "axTooltipOpenAfter", "axTooltipCloseAfter"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
8723
8853
|
}
|
|
8724
8854
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AXPEntityCategoryWidgetColumnComponent, decorators: [{
|
|
8725
8855
|
type: Component,
|
|
@@ -8729,25 +8859,40 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
|
|
|
8729
8859
|
class: 'ax-w-full',
|
|
8730
8860
|
},
|
|
8731
8861
|
template: `
|
|
8732
|
-
<
|
|
8733
|
-
|
|
8734
|
-
|
|
8735
|
-
|
|
8736
|
-
|
|
8737
|
-
|
|
8738
|
-
|
|
8739
|
-
|
|
8740
|
-
|
|
8741
|
-
|
|
8742
|
-
|
|
8743
|
-
|
|
8744
|
-
|
|
8745
|
-
|
|
8746
|
-
|
|
8747
|
-
|
|
8862
|
+
<axp-column-item-list [items]="columnItems()" [itemTemplate]="itemTemplate" />
|
|
8863
|
+
|
|
8864
|
+
<!-- Item Template -->
|
|
8865
|
+
<ng-template #itemTemplate let-item>
|
|
8866
|
+
<div>
|
|
8867
|
+
@if (hasParent(item.originalItem)) {
|
|
8868
|
+
<ax-badge
|
|
8869
|
+
[color]="'primary'"
|
|
8870
|
+
[look]="'twotone'"
|
|
8871
|
+
class="ax-p-0.5"
|
|
8872
|
+
[text]="getItemText(item.originalItem)"
|
|
8873
|
+
[axTooltip]="getBreadcrumbPath(item.originalItem)"
|
|
8874
|
+
[axTooltipPlacement]="'top'"
|
|
8875
|
+
>
|
|
8876
|
+
<ax-prefix>
|
|
8877
|
+
<i class="fal fa-ellipsis-h ax-text-on-primary ax-text-xs ax-ps-1"></i>
|
|
8878
|
+
<i class="fal fa-chevron-right rtl:ax-rotate-180 ax-text-on-primary ax-text-xs ax-px-1"></i>
|
|
8879
|
+
</ax-prefix>
|
|
8880
|
+
</ax-badge>
|
|
8881
|
+
} @else {
|
|
8882
|
+
<ax-badge
|
|
8883
|
+
[color]="'primary'"
|
|
8884
|
+
[look]="'twotone'"
|
|
8885
|
+
class="ax-p-0.5"
|
|
8886
|
+
[text]="getItemText(item.originalItem)"
|
|
8887
|
+
[axTooltip]="getBreadcrumbPath(item.originalItem)"
|
|
8888
|
+
[axTooltipPlacement]="'top'"
|
|
8889
|
+
></ax-badge>
|
|
8890
|
+
}
|
|
8891
|
+
</div>
|
|
8892
|
+
</ng-template>
|
|
8748
8893
|
`,
|
|
8749
8894
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
8750
|
-
imports: [
|
|
8895
|
+
imports: [AXPColumnItemListComponent, AXBadgeModule, AXDecoratorModule, AXTooltipModule],
|
|
8751
8896
|
inputs: ['rawValue', 'rowData'],
|
|
8752
8897
|
}]
|
|
8753
8898
|
}], propDecorators: { rawValueInput: [{
|
|
@@ -9011,6 +9156,11 @@ class AXPEntityCategoryWidgetEditComponent extends AXPValueWidgetComponent {
|
|
|
9011
9156
|
//#endregion
|
|
9012
9157
|
//#region ---- Private Properties ----
|
|
9013
9158
|
this.entityDef = signal(null, ...(ngDevMode ? [{ debugName: "entityDef" }] : []));
|
|
9159
|
+
/**
|
|
9160
|
+
* Flag to prevent the value effect from running during internal updates.
|
|
9161
|
+
* This prevents circular dependency when setItems() calls setValue().
|
|
9162
|
+
*/
|
|
9163
|
+
this.isInternalUpdate = false;
|
|
9014
9164
|
//#endregion
|
|
9015
9165
|
//#region ---- Effects ----
|
|
9016
9166
|
this.#efEntity = effect(async () => {
|
|
@@ -9027,6 +9177,11 @@ class AXPEntityCategoryWidgetEditComponent extends AXPValueWidgetComponent {
|
|
|
9027
9177
|
if (!entity) {
|
|
9028
9178
|
return;
|
|
9029
9179
|
}
|
|
9180
|
+
// Skip if this is an internal update (e.g., from setItems -> setValue)
|
|
9181
|
+
// This prevents circular dependency
|
|
9182
|
+
if (this.isInternalUpdate) {
|
|
9183
|
+
return;
|
|
9184
|
+
}
|
|
9030
9185
|
if (value) {
|
|
9031
9186
|
this.findByValue();
|
|
9032
9187
|
}
|
|
@@ -9144,44 +9299,22 @@ class AXPEntityCategoryWidgetEditComponent extends AXPValueWidgetComponent {
|
|
|
9144
9299
|
},
|
|
9145
9300
|
});
|
|
9146
9301
|
if (result?.data?.selected && Array.isArray(result.data.selected)) {
|
|
9147
|
-
|
|
9148
|
-
|
|
9149
|
-
|
|
9150
|
-
|
|
9151
|
-
//
|
|
9152
|
-
|
|
9153
|
-
existingItems.forEach((item) => {
|
|
9154
|
-
const id = String(get(item, this.valueField()) ?? '');
|
|
9155
|
-
if (id) {
|
|
9156
|
-
existingItemsMap.set(id, item);
|
|
9157
|
-
}
|
|
9158
|
-
});
|
|
9159
|
-
// Merge items, preferring new items (with paths) over existing ones
|
|
9160
|
-
const mergedItems = [];
|
|
9161
|
-
const processedIds = new Set();
|
|
9162
|
-
// First, add all new items (they have paths)
|
|
9163
|
-
newItems.forEach((item) => {
|
|
9164
|
-
const id = String(get(item, this.valueField()) ?? '');
|
|
9165
|
-
if (id && !processedIds.has(id)) {
|
|
9166
|
-
mergedItems.push(item);
|
|
9167
|
-
processedIds.add(id);
|
|
9168
|
-
}
|
|
9169
|
-
});
|
|
9170
|
-
// Then, add existing items that weren't in new items (preserve them)
|
|
9171
|
-
existingItems.forEach((item) => {
|
|
9172
|
-
const id = String(get(item, this.valueField()) ?? '');
|
|
9173
|
-
if (id && !processedIds.has(id)) {
|
|
9174
|
-
mergedItems.push(item);
|
|
9175
|
-
processedIds.add(id);
|
|
9176
|
-
}
|
|
9177
|
-
});
|
|
9178
|
-
await this.setItems(mergedItems);
|
|
9302
|
+
// Use the popup result directly as the source of truth
|
|
9303
|
+
// This ensures that deselected items are properly removed
|
|
9304
|
+
const newItems = result.data.selected;
|
|
9305
|
+
if (newItems.length === 0) {
|
|
9306
|
+
// All items were deselected
|
|
9307
|
+
this.clear();
|
|
9179
9308
|
}
|
|
9180
9309
|
else {
|
|
9181
|
-
//
|
|
9182
|
-
await this.setItems(
|
|
9310
|
+
// Set the new selection (replaces existing selection entirely)
|
|
9311
|
+
await this.setItems(newItems);
|
|
9183
9312
|
}
|
|
9184
9313
|
}
|
|
9314
|
+
else if (result?.data?.selected === undefined && result?.data !== undefined) {
|
|
9315
|
+
// User cancelled or closed popup without confirming - don't change anything
|
|
9316
|
+
// This case is handled by the catch block or no result.data.selected
|
|
9317
|
+
}
|
|
9185
9318
|
}
|
|
9186
9319
|
catch (error) {
|
|
9187
9320
|
console.error('Error opening category tree selector:', error);
|
|
@@ -9192,10 +9325,20 @@ class AXPEntityCategoryWidgetEditComponent extends AXPValueWidgetComponent {
|
|
|
9192
9325
|
}
|
|
9193
9326
|
}
|
|
9194
9327
|
clear() {
|
|
9195
|
-
|
|
9196
|
-
this.
|
|
9197
|
-
|
|
9198
|
-
|
|
9328
|
+
// Set flag to prevent circular dependency with the value effect
|
|
9329
|
+
this.isInternalUpdate = true;
|
|
9330
|
+
try {
|
|
9331
|
+
this.setValue(null);
|
|
9332
|
+
this.clearInput();
|
|
9333
|
+
this.selectedItems.set([]);
|
|
9334
|
+
this.cdr.markForCheck();
|
|
9335
|
+
}
|
|
9336
|
+
finally {
|
|
9337
|
+
// Reset flag after a short delay to ensure effect has time to skip
|
|
9338
|
+
setTimeout(() => {
|
|
9339
|
+
this.isInternalUpdate = false;
|
|
9340
|
+
}, 0);
|
|
9341
|
+
}
|
|
9199
9342
|
}
|
|
9200
9343
|
//#endregion
|
|
9201
9344
|
//#region ---- Private Methods ----
|
|
@@ -9268,51 +9411,61 @@ class AXPEntityCategoryWidgetEditComponent extends AXPValueWidgetComponent {
|
|
|
9268
9411
|
}
|
|
9269
9412
|
}
|
|
9270
9413
|
async setItems(items) {
|
|
9271
|
-
|
|
9272
|
-
|
|
9273
|
-
|
|
9414
|
+
// Set flag to prevent circular dependency with the value effect
|
|
9415
|
+
this.isInternalUpdate = true;
|
|
9416
|
+
try {
|
|
9417
|
+
if (!items || items.length == 0) {
|
|
9418
|
+
this.selectedItems.set([]);
|
|
9419
|
+
this.setValue(null);
|
|
9420
|
+
this.cdr.markForCheck();
|
|
9421
|
+
return;
|
|
9422
|
+
}
|
|
9423
|
+
items = castArray(items);
|
|
9424
|
+
this.clearInput();
|
|
9425
|
+
// Ensure all items have paths
|
|
9426
|
+
const itemsWithPaths = await Promise.all(items.map(async (item) => {
|
|
9427
|
+
// If item already has a path array, return it as is
|
|
9428
|
+
if (item.path && Array.isArray(item.path) && item.path.length > 0) {
|
|
9429
|
+
return item;
|
|
9430
|
+
}
|
|
9431
|
+
// Otherwise, calculate the path
|
|
9432
|
+
return await this.calculateItemPath(item);
|
|
9433
|
+
}));
|
|
9434
|
+
this.selectedItems.set(itemsWithPaths);
|
|
9435
|
+
const keys = itemsWithPaths.map((item) => get(item, this.valueField()));
|
|
9436
|
+
// Extract data from valueField and set context by expose path
|
|
9437
|
+
if (this.expose()) {
|
|
9438
|
+
const exposeValue = castArray(this.expose());
|
|
9439
|
+
const itemToExpose = {};
|
|
9440
|
+
exposeValue.forEach((i) => {
|
|
9441
|
+
if (typeof i == 'string') {
|
|
9442
|
+
const values = itemsWithPaths.map((item) => set({}, i, get(item, i)));
|
|
9443
|
+
setSmart(itemToExpose, i, this.singleOrMultiple(values));
|
|
9444
|
+
}
|
|
9445
|
+
else {
|
|
9446
|
+
// Extract data from item by source path and set context by target path
|
|
9447
|
+
const values = this.multiple()
|
|
9448
|
+
? itemsWithPaths.map((item) => set({}, i.source, get(item, i.source)))
|
|
9449
|
+
: itemsWithPaths.map((item) => get(item, i.source));
|
|
9450
|
+
setSmart(itemToExpose, i.target, this.singleOrMultiple(values));
|
|
9451
|
+
}
|
|
9452
|
+
});
|
|
9453
|
+
this.contextService.patch(itemToExpose, true);
|
|
9454
|
+
// Fire triggers
|
|
9455
|
+
this.setValue(this.singleOrMultiple(keys));
|
|
9456
|
+
}
|
|
9457
|
+
else {
|
|
9458
|
+
this.setValue(this.singleOrMultiple(keys));
|
|
9459
|
+
}
|
|
9460
|
+
// Trigger change detection
|
|
9274
9461
|
this.cdr.markForCheck();
|
|
9275
|
-
return;
|
|
9276
|
-
}
|
|
9277
|
-
items = castArray(items);
|
|
9278
|
-
this.clearInput();
|
|
9279
|
-
// Ensure all items have paths
|
|
9280
|
-
const itemsWithPaths = await Promise.all(items.map(async (item) => {
|
|
9281
|
-
// If item already has a path array, return it as is
|
|
9282
|
-
if (item.path && Array.isArray(item.path) && item.path.length > 0) {
|
|
9283
|
-
return item;
|
|
9284
|
-
}
|
|
9285
|
-
// Otherwise, calculate the path
|
|
9286
|
-
return await this.calculateItemPath(item);
|
|
9287
|
-
}));
|
|
9288
|
-
this.selectedItems.set(itemsWithPaths);
|
|
9289
|
-
const keys = itemsWithPaths.map((item) => get(item, this.valueField()));
|
|
9290
|
-
// Extract data from valueField and set context by expose path
|
|
9291
|
-
if (this.expose()) {
|
|
9292
|
-
const exposeValue = castArray(this.expose());
|
|
9293
|
-
const itemToExpose = {};
|
|
9294
|
-
exposeValue.forEach((i) => {
|
|
9295
|
-
if (typeof i == 'string') {
|
|
9296
|
-
const values = itemsWithPaths.map((item) => set({}, i, get(item, i)));
|
|
9297
|
-
setSmart(itemToExpose, i, this.singleOrMultiple(values));
|
|
9298
|
-
}
|
|
9299
|
-
else {
|
|
9300
|
-
// Extract data from item by source path and set context by target path
|
|
9301
|
-
const values = this.multiple()
|
|
9302
|
-
? itemsWithPaths.map((item) => set({}, i.source, get(item, i.source)))
|
|
9303
|
-
: itemsWithPaths.map((item) => get(item, i.source));
|
|
9304
|
-
setSmart(itemToExpose, i.target, this.singleOrMultiple(values));
|
|
9305
|
-
}
|
|
9306
|
-
});
|
|
9307
|
-
this.contextService.patch(itemToExpose, true);
|
|
9308
|
-
// Fire triggers
|
|
9309
|
-
this.setValue(this.singleOrMultiple(keys));
|
|
9310
9462
|
}
|
|
9311
|
-
|
|
9312
|
-
|
|
9463
|
+
finally {
|
|
9464
|
+
// Reset flag after a short delay to ensure effect has time to skip
|
|
9465
|
+
setTimeout(() => {
|
|
9466
|
+
this.isInternalUpdate = false;
|
|
9467
|
+
}, 0);
|
|
9313
9468
|
}
|
|
9314
|
-
// Trigger change detection
|
|
9315
|
-
this.cdr.markForCheck();
|
|
9316
9469
|
}
|
|
9317
9470
|
singleOrMultiple(values) {
|
|
9318
9471
|
return this.multiple() ? values : values[0];
|