@acorex/components 21.0.0-next.37 → 21.0.0-next.39
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/accordion/index.d.ts +1 -0
- package/button-group/index.d.ts +4 -6
- package/calendar/index.d.ts +4 -0
- package/chips/index.d.ts +8 -3
- package/dialog/index.d.ts +1 -1
- package/fesm2022/acorex-components-accordion.mjs +21 -16
- package/fesm2022/acorex-components-accordion.mjs.map +1 -1
- package/fesm2022/acorex-components-action-sheet.mjs +12 -12
- package/fesm2022/acorex-components-action-sheet.mjs.map +1 -1
- package/fesm2022/acorex-components-alert.mjs +13 -13
- package/fesm2022/acorex-components-alert.mjs.map +1 -1
- package/fesm2022/acorex-components-aspect-ratio.mjs +3 -3
- package/fesm2022/acorex-components-aspect-ratio.mjs.map +1 -1
- package/fesm2022/acorex-components-audio-wave.mjs +10 -11
- package/fesm2022/acorex-components-audio-wave.mjs.map +1 -1
- package/fesm2022/acorex-components-autocomplete.mjs +7 -7
- package/fesm2022/acorex-components-autocomplete.mjs.map +1 -1
- package/fesm2022/acorex-components-avatar.mjs +12 -12
- package/fesm2022/acorex-components-avatar.mjs.map +1 -1
- package/fesm2022/acorex-components-badge.mjs +9 -9
- package/fesm2022/acorex-components-badge.mjs.map +1 -1
- package/fesm2022/acorex-components-bottom-navigation.mjs +11 -11
- package/fesm2022/acorex-components-bottom-navigation.mjs.map +1 -1
- package/fesm2022/acorex-components-breadcrumbs.mjs +11 -11
- package/fesm2022/acorex-components-breadcrumbs.mjs.map +1 -1
- package/fesm2022/acorex-components-button-group.mjs +19 -23
- package/fesm2022/acorex-components-button-group.mjs.map +1 -1
- package/fesm2022/acorex-components-button.mjs +18 -18
- package/fesm2022/acorex-components-button.mjs.map +1 -1
- package/fesm2022/acorex-components-calendar.mjs +32 -17
- package/fesm2022/acorex-components-calendar.mjs.map +1 -1
- package/fesm2022/acorex-components-check-box.mjs +10 -10
- package/fesm2022/acorex-components-check-box.mjs.map +1 -1
- package/fesm2022/acorex-components-chips.mjs +14 -12
- package/fesm2022/acorex-components-chips.mjs.map +1 -1
- package/fesm2022/acorex-components-circular-progress.mjs +10 -12
- package/fesm2022/acorex-components-circular-progress.mjs.map +1 -1
- package/fesm2022/acorex-components-code-editor.mjs +10 -10
- package/fesm2022/acorex-components-code-editor.mjs.map +1 -1
- package/fesm2022/acorex-components-collapse.mjs +13 -13
- package/fesm2022/acorex-components-collapse.mjs.map +1 -1
- package/fesm2022/acorex-components-color-box.mjs +9 -9
- package/fesm2022/acorex-components-color-box.mjs.map +1 -1
- package/fesm2022/acorex-components-color-palette.mjs +30 -30
- package/fesm2022/acorex-components-color-palette.mjs.map +1 -1
- package/fesm2022/acorex-components-command.mjs +9 -9
- package/fesm2022/acorex-components-command.mjs.map +1 -1
- package/fesm2022/acorex-components-comment.mjs +32 -32
- package/fesm2022/acorex-components-comment.mjs.map +1 -1
- package/fesm2022/acorex-components-conversation.mjs +51 -51
- package/fesm2022/acorex-components-conversation.mjs.map +1 -1
- package/fesm2022/acorex-components-conversation2.mjs +186 -186
- package/fesm2022/acorex-components-conversation2.mjs.map +1 -1
- package/fesm2022/acorex-components-cron-job.mjs +46 -46
- package/fesm2022/acorex-components-cron-job.mjs.map +1 -1
- package/fesm2022/acorex-components-data-list.mjs +3 -3
- package/fesm2022/acorex-components-data-list.mjs.map +1 -1
- package/fesm2022/acorex-components-data-pager.mjs +37 -34
- package/fesm2022/acorex-components-data-pager.mjs.map +1 -1
- package/fesm2022/acorex-components-data-table.mjs +43 -43
- package/fesm2022/acorex-components-data-table.mjs.map +1 -1
- package/fesm2022/acorex-components-datetime-box.mjs +9 -9
- package/fesm2022/acorex-components-datetime-box.mjs.map +1 -1
- package/fesm2022/acorex-components-datetime-input.mjs +9 -9
- package/fesm2022/acorex-components-datetime-input.mjs.map +1 -1
- package/fesm2022/acorex-components-datetime-picker.mjs +9 -9
- package/fesm2022/acorex-components-datetime-picker.mjs.map +1 -1
- package/fesm2022/acorex-components-decorators.mjs +36 -43
- package/fesm2022/acorex-components-decorators.mjs.map +1 -1
- package/fesm2022/acorex-components-dialog.mjs +14 -15
- package/fesm2022/acorex-components-dialog.mjs.map +1 -1
- package/fesm2022/acorex-components-drawer-legacy.mjs +13 -13
- package/fesm2022/acorex-components-drawer-legacy.mjs.map +1 -1
- package/fesm2022/acorex-components-drawer.mjs +16 -15
- package/fesm2022/acorex-components-drawer.mjs.map +1 -1
- package/fesm2022/acorex-components-dropdown-button.mjs +9 -9
- package/fesm2022/acorex-components-dropdown-button.mjs.map +1 -1
- package/fesm2022/acorex-components-dropdown.mjs +16 -18
- package/fesm2022/acorex-components-dropdown.mjs.map +1 -1
- package/fesm2022/acorex-components-editor.mjs +11 -11
- package/fesm2022/acorex-components-editor.mjs.map +1 -1
- package/fesm2022/acorex-components-file-explorer.mjs +32 -32
- package/fesm2022/acorex-components-file-explorer.mjs.map +1 -1
- package/fesm2022/acorex-components-flow-chart.mjs +16 -16
- package/fesm2022/acorex-components-flow-chart.mjs.map +1 -1
- package/fesm2022/acorex-components-form.mjs +24 -32
- package/fesm2022/acorex-components-form.mjs.map +1 -1
- package/fesm2022/acorex-components-grid-layout-builder.mjs +13 -12
- package/fesm2022/acorex-components-grid-layout-builder.mjs.map +1 -1
- package/fesm2022/acorex-components-image-editor.mjs +47 -46
- package/fesm2022/acorex-components-image-editor.mjs.map +1 -1
- package/fesm2022/acorex-components-image.mjs +9 -9
- package/fesm2022/acorex-components-image.mjs.map +1 -1
- package/fesm2022/acorex-components-json-viewer.mjs +8 -8
- package/fesm2022/acorex-components-json-viewer.mjs.map +1 -1
- package/fesm2022/acorex-components-kanban.mjs +6 -8
- package/fesm2022/acorex-components-kanban.mjs.map +1 -1
- package/fesm2022/acorex-components-kbd.mjs +7 -7
- package/fesm2022/acorex-components-kbd.mjs.map +1 -1
- package/fesm2022/acorex-components-label.mjs +9 -9
- package/fesm2022/acorex-components-label.mjs.map +1 -1
- package/fesm2022/acorex-components-list.mjs +9 -9
- package/fesm2022/acorex-components-list.mjs.map +1 -1
- package/fesm2022/acorex-components-loading-dialog.mjs +12 -12
- package/fesm2022/acorex-components-loading-dialog.mjs.map +1 -1
- package/fesm2022/acorex-components-loading.mjs +23 -23
- package/fesm2022/acorex-components-loading.mjs.map +1 -1
- package/fesm2022/acorex-components-map.mjs +289 -31
- package/fesm2022/acorex-components-map.mjs.map +1 -1
- package/fesm2022/acorex-components-media-viewer.mjs +41 -41
- package/fesm2022/acorex-components-media-viewer.mjs.map +1 -1
- package/fesm2022/acorex-components-menu.mjs +21 -21
- package/fesm2022/acorex-components-menu.mjs.map +1 -1
- package/fesm2022/{acorex-components-modal-acorex-components-modal-BmmAkCKJ.mjs → acorex-components-modal-acorex-components-modal-iYSPzoLn.mjs} +22 -22
- package/fesm2022/acorex-components-modal-acorex-components-modal-iYSPzoLn.mjs.map +1 -0
- package/fesm2022/acorex-components-modal-modal-content.component-sZWKhYM-.mjs +212 -0
- package/fesm2022/acorex-components-modal-modal-content.component-sZWKhYM-.mjs.map +1 -0
- package/fesm2022/acorex-components-modal.mjs +1 -1
- package/fesm2022/acorex-components-navbar.mjs +9 -9
- package/fesm2022/acorex-components-navbar.mjs.map +1 -1
- package/fesm2022/acorex-components-notification.mjs +23 -16
- package/fesm2022/acorex-components-notification.mjs.map +1 -1
- package/fesm2022/acorex-components-number-box-legacy.mjs +9 -9
- package/fesm2022/acorex-components-number-box-legacy.mjs.map +1 -1
- package/fesm2022/acorex-components-number-box.mjs +15 -14
- package/fesm2022/acorex-components-number-box.mjs.map +1 -1
- package/fesm2022/acorex-components-otp.mjs +9 -9
- package/fesm2022/acorex-components-otp.mjs.map +1 -1
- package/fesm2022/acorex-components-page.mjs +10 -10
- package/fesm2022/acorex-components-page.mjs.map +1 -1
- package/fesm2022/acorex-components-paint.mjs +39 -34
- package/fesm2022/acorex-components-paint.mjs.map +1 -1
- package/fesm2022/acorex-components-password-box.mjs +12 -12
- package/fesm2022/acorex-components-password-box.mjs.map +1 -1
- package/fesm2022/acorex-components-pdf-reader.mjs +8 -8
- package/fesm2022/acorex-components-pdf-reader.mjs.map +1 -1
- package/fesm2022/acorex-components-phone-box.mjs +9 -9
- package/fesm2022/acorex-components-phone-box.mjs.map +1 -1
- package/fesm2022/acorex-components-picker.mjs +15 -15
- package/fesm2022/acorex-components-picker.mjs.map +1 -1
- package/fesm2022/acorex-components-popover.mjs +11 -11
- package/fesm2022/acorex-components-popover.mjs.map +1 -1
- package/fesm2022/acorex-components-popup.mjs +13 -13
- package/fesm2022/acorex-components-popup.mjs.map +1 -1
- package/fesm2022/acorex-components-progress-bar.mjs +9 -11
- package/fesm2022/acorex-components-progress-bar.mjs.map +1 -1
- package/fesm2022/acorex-components-qrcode.mjs +7 -7
- package/fesm2022/acorex-components-qrcode.mjs.map +1 -1
- package/fesm2022/acorex-components-query-builder.mjs +8 -8
- package/fesm2022/acorex-components-query-builder.mjs.map +1 -1
- package/fesm2022/acorex-components-radio.mjs +7 -7
- package/fesm2022/acorex-components-radio.mjs.map +1 -1
- package/fesm2022/acorex-components-rail-navigation.mjs +36 -38
- package/fesm2022/acorex-components-rail-navigation.mjs.map +1 -1
- package/fesm2022/acorex-components-range-slider.mjs +10 -10
- package/fesm2022/acorex-components-range-slider.mjs.map +1 -1
- package/fesm2022/acorex-components-rate-picker.mjs +35 -20
- package/fesm2022/acorex-components-rate-picker.mjs.map +1 -1
- package/fesm2022/acorex-components-rest-api-generator.mjs +22 -22
- package/fesm2022/acorex-components-rest-api-generator.mjs.map +1 -1
- package/fesm2022/acorex-components-result.mjs +8 -8
- package/fesm2022/acorex-components-result.mjs.map +1 -1
- package/fesm2022/acorex-components-routing-progress.mjs +8 -8
- package/fesm2022/acorex-components-routing-progress.mjs.map +1 -1
- package/fesm2022/acorex-components-rrule.mjs +9 -9
- package/fesm2022/acorex-components-rrule.mjs.map +1 -1
- package/fesm2022/acorex-components-scheduler-picker.mjs +56 -56
- package/fesm2022/acorex-components-scheduler-picker.mjs.map +1 -1
- package/fesm2022/acorex-components-scheduler.mjs +43 -43
- package/fesm2022/acorex-components-scheduler.mjs.map +1 -1
- package/fesm2022/acorex-components-scss.mjs +4 -4
- package/fesm2022/acorex-components-scss.mjs.map +1 -1
- package/fesm2022/acorex-components-search-box.mjs +10 -16
- package/fesm2022/acorex-components-search-box.mjs.map +1 -1
- package/fesm2022/acorex-components-select-box.mjs +9 -11
- package/fesm2022/acorex-components-select-box.mjs.map +1 -1
- package/fesm2022/acorex-components-selection-list-2.mjs +11 -11
- package/fesm2022/acorex-components-selection-list-2.mjs.map +1 -1
- package/fesm2022/acorex-components-selection-list.mjs +9 -9
- package/fesm2022/acorex-components-selection-list.mjs.map +1 -1
- package/fesm2022/acorex-components-side-menu.mjs +14 -14
- package/fesm2022/acorex-components-side-menu.mjs.map +1 -1
- package/fesm2022/acorex-components-skeleton.mjs +8 -8
- package/fesm2022/acorex-components-skeleton.mjs.map +1 -1
- package/fesm2022/acorex-components-slider.mjs +10 -10
- package/fesm2022/acorex-components-slider.mjs.map +1 -1
- package/fesm2022/acorex-components-sliding-item.mjs +14 -14
- package/fesm2022/acorex-components-sliding-item.mjs.map +1 -1
- package/fesm2022/acorex-components-step-wizard.mjs +14 -14
- package/fesm2022/acorex-components-step-wizard.mjs.map +1 -1
- package/fesm2022/acorex-components-switch.mjs +14 -14
- package/fesm2022/acorex-components-switch.mjs.map +1 -1
- package/fesm2022/acorex-components-tabs.mjs +15 -15
- package/fesm2022/acorex-components-tabs.mjs.map +1 -1
- package/fesm2022/acorex-components-tag-box.mjs +9 -9
- package/fesm2022/acorex-components-tag-box.mjs.map +1 -1
- package/fesm2022/acorex-components-tag.mjs +9 -9
- package/fesm2022/acorex-components-tag.mjs.map +1 -1
- package/fesm2022/acorex-components-text-area.mjs +9 -9
- package/fesm2022/acorex-components-text-area.mjs.map +1 -1
- package/fesm2022/acorex-components-text-box.mjs +12 -12
- package/fesm2022/acorex-components-text-box.mjs.map +1 -1
- package/fesm2022/acorex-components-time-duration.mjs +7 -7
- package/fesm2022/acorex-components-time-duration.mjs.map +1 -1
- package/fesm2022/acorex-components-time-line.mjs +12 -12
- package/fesm2022/acorex-components-time-line.mjs.map +1 -1
- package/fesm2022/acorex-components-toast.mjs +14 -14
- package/fesm2022/acorex-components-toast.mjs.map +1 -1
- package/fesm2022/acorex-components-toolbar.mjs +8 -8
- package/fesm2022/acorex-components-toolbar.mjs.map +1 -1
- package/fesm2022/acorex-components-tooltip.mjs +11 -11
- package/fesm2022/acorex-components-tooltip.mjs.map +1 -1
- package/fesm2022/acorex-components-tree-view-legacy.mjs +511 -0
- package/fesm2022/acorex-components-tree-view-legacy.mjs.map +1 -0
- package/fesm2022/acorex-components-tree-view.mjs +2016 -405
- package/fesm2022/acorex-components-tree-view.mjs.map +1 -1
- package/fesm2022/acorex-components-uploader.mjs +16 -16
- package/fesm2022/acorex-components-uploader.mjs.map +1 -1
- package/fesm2022/acorex-components-video-player.mjs +7 -7
- package/fesm2022/acorex-components-video-player.mjs.map +1 -1
- package/fesm2022/acorex-components-wysiwyg.mjs +42 -42
- package/fesm2022/acorex-components-wysiwyg.mjs.map +1 -1
- package/fesm2022/acorex-components.mjs.map +1 -1
- package/file-explorer/index.d.ts +6 -6
- package/grid-layout-builder/index.d.ts +2 -1
- package/loading/index.d.ts +1 -1
- package/map/index.d.ts +28 -1
- package/notification/index.d.ts +2 -0
- package/number-box/index.d.ts +1 -1
- package/package.json +6 -6
- package/paint/index.d.ts +6 -1
- package/rate-picker/index.d.ts +15 -5
- package/tree-view/index.d.ts +826 -168
- package/tree-view-legacy/README.md +3 -0
- package/tree-view-legacy/index.d.ts +184 -0
- package/fesm2022/acorex-components-modal-acorex-components-modal-BmmAkCKJ.mjs.map +0 -1
- package/fesm2022/acorex-components-modal-modal-content.component-5GqTzNOs.mjs +0 -214
- package/fesm2022/acorex-components-modal-modal-content.component-5GqTzNOs.mjs.map +0 -1
- package/fesm2022/acorex-components-tree2.mjs +0 -730
- package/fesm2022/acorex-components-tree2.mjs.map +0 -1
- package/tree2/README.md +0 -3
- package/tree2/index.d.ts +0 -346
|
@@ -1,505 +1,2116 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { moveItemInArray, transferArrayItem, AXDragDirective, AXDragHandleDirective, AXDropListDirective } from '@acorex/cdk/drag-drop';
|
|
2
|
+
import { AXFocusTrapDirective } from '@acorex/cdk/focus-trap';
|
|
3
|
+
import { AXBadgeComponent } from '@acorex/components/badge';
|
|
4
|
+
import { AXButtonComponent } from '@acorex/components/button';
|
|
5
|
+
import { AXCheckBoxComponent } from '@acorex/components/check-box';
|
|
6
|
+
import { AXDecoratorIconComponent } from '@acorex/components/decorators';
|
|
7
|
+
import { AXTooltipDirective } from '@acorex/components/tooltip';
|
|
4
8
|
import { AXPlatform } from '@acorex/core/platform';
|
|
5
|
-
import { AXTranslatorPipe } from '@acorex/core/translation';
|
|
6
9
|
import * as i1 from '@angular/common';
|
|
7
|
-
import { CommonModule, NgTemplateOutlet
|
|
10
|
+
import { CommonModule, NgTemplateOutlet } from '@angular/common';
|
|
8
11
|
import * as i0 from '@angular/core';
|
|
9
|
-
import { inject,
|
|
10
|
-
import {
|
|
11
|
-
import
|
|
12
|
-
import * as i1$1 from '@angular/forms';
|
|
12
|
+
import { Injectable, inject, DestroyRef, model, input, output, signal, computed, effect, afterNextRender, ViewEncapsulation, ChangeDetectionStrategy, Component, NgModule } from '@angular/core';
|
|
13
|
+
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
14
|
+
import * as i2 from '@angular/forms';
|
|
13
15
|
import { FormsModule } from '@angular/forms';
|
|
14
|
-
import {
|
|
16
|
+
import { map } from 'rxjs/operators';
|
|
15
17
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Service for tree node operations
|
|
20
|
+
* Handles all business logic for tree node manipulation
|
|
21
|
+
*/
|
|
22
|
+
class AXTreeViewService {
|
|
23
|
+
// ==================== Constants ====================
|
|
24
|
+
static { this.ROOT_LIST_ID = 'ax-tree-view-root-list'; }
|
|
25
|
+
static { this.NODE_DROP_PREFIX = 'ax-tree-view-node-drop-'; }
|
|
26
|
+
static { this.LIST_PREFIX = 'ax-tree-view-list-'; }
|
|
27
|
+
// ==================== Node Finding & Traversal ====================
|
|
28
|
+
/**
|
|
29
|
+
* Find a node by ID in the tree
|
|
30
|
+
*/
|
|
31
|
+
findNodeById(nodes, id, idField = 'id') {
|
|
32
|
+
for (const node of nodes) {
|
|
33
|
+
if (node[idField] === id)
|
|
34
|
+
return node;
|
|
35
|
+
const children = node['children'];
|
|
36
|
+
if (children) {
|
|
37
|
+
const found = this.findNodeById(children, id, idField);
|
|
38
|
+
if (found)
|
|
39
|
+
return found;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Find parent node of a given node
|
|
46
|
+
*/
|
|
47
|
+
findParentNode(nodes, targetNode, idField = 'id', childrenField = 'children') {
|
|
48
|
+
const targetId = targetNode[idField];
|
|
49
|
+
for (const node of nodes) {
|
|
50
|
+
const children = node[childrenField];
|
|
51
|
+
if (children?.some((child) => child[idField] === targetId)) {
|
|
52
|
+
return node;
|
|
53
|
+
}
|
|
54
|
+
if (children) {
|
|
55
|
+
const found = this.findParentNode(children, targetNode, idField, childrenField);
|
|
56
|
+
if (found)
|
|
57
|
+
return found;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Check if targetNode is a descendant of ancestorNode (or the same node)
|
|
64
|
+
* Prevents circular references by checking if target exists in ancestor's children tree
|
|
65
|
+
*/
|
|
66
|
+
isValidDropTarget(movedNode, targetNode, idField = 'id', childrenField = 'children') {
|
|
67
|
+
const movedId = movedNode[idField];
|
|
68
|
+
const targetId = targetNode[idField];
|
|
69
|
+
if (movedId === targetId || this.isNodeDescendantOf(targetNode, movedNode, idField, childrenField)) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Check if targetNode is a descendant of ancestorNode
|
|
76
|
+
*/
|
|
77
|
+
isNodeDescendantOf(targetNode, ancestorNode, idField = 'id', childrenField = 'children') {
|
|
78
|
+
const targetId = targetNode[idField];
|
|
79
|
+
const ancestorId = ancestorNode[idField];
|
|
80
|
+
if (targetId === ancestorId) {
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
const children = ancestorNode[childrenField];
|
|
84
|
+
if (!children || children.length === 0) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
for (const child of children) {
|
|
88
|
+
const childId = child[idField];
|
|
89
|
+
if (childId === targetId) {
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
if (this.isNodeDescendantOf(targetNode, child, idField, childrenField)) {
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Build a flat list of all visible focusable nodes
|
|
100
|
+
*/
|
|
101
|
+
buildFlatNodeList(nodes, hiddenField = 'hidden', disabledField = 'disabled', expandedField = 'expanded', childrenField = 'children') {
|
|
102
|
+
const flatList = [];
|
|
103
|
+
const traverse = (nodeList, level, parent) => {
|
|
104
|
+
for (const node of nodeList) {
|
|
105
|
+
const hidden = node[hiddenField];
|
|
106
|
+
const disabled = node[disabledField];
|
|
107
|
+
if (hidden !== true && !disabled) {
|
|
108
|
+
flatList.push({ node, level, parent });
|
|
109
|
+
const expanded = node[expandedField];
|
|
110
|
+
const children = node[childrenField];
|
|
111
|
+
if (expanded && children) {
|
|
112
|
+
traverse(children, level + 1, node);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
traverse(nodes, 0);
|
|
118
|
+
return flatList;
|
|
119
|
+
}
|
|
120
|
+
// ==================== Node State Checks ====================
|
|
121
|
+
/**
|
|
122
|
+
* Check if node has children
|
|
123
|
+
*/
|
|
124
|
+
hasChildren(node, childrenField = 'children') {
|
|
125
|
+
const children = node[childrenField];
|
|
126
|
+
return Boolean(children?.length);
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Check if node can be lazy loaded
|
|
130
|
+
*/
|
|
131
|
+
canLazyLoad(node, isLazyDataSource, childrenCountField = 'childrenCount', childrenField = 'children') {
|
|
132
|
+
const childrenCount = node[childrenCountField];
|
|
133
|
+
return isLazyDataSource && Boolean(childrenCount && childrenCount > 0 && !this.hasChildren(node, childrenField));
|
|
134
|
+
}
|
|
135
|
+
// ==================== Selection Management ====================
|
|
136
|
+
/**
|
|
137
|
+
* Recursively select/deselect all children
|
|
138
|
+
*/
|
|
139
|
+
selectAllChildren(children, selected, selectedField = 'selected', indeterminateField = 'indeterminate', childrenField = 'children') {
|
|
140
|
+
for (const child of children) {
|
|
141
|
+
child[selectedField] = selected;
|
|
142
|
+
child[indeterminateField] = false;
|
|
143
|
+
const childChildren = child[childrenField];
|
|
144
|
+
if (childChildren) {
|
|
145
|
+
this.selectAllChildren(childChildren, selected, selectedField, indeterminateField, childrenField);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Get selection state of children
|
|
151
|
+
*/
|
|
152
|
+
getChildrenSelectionState(children, selectedField = 'selected', indeterminateField = 'indeterminate') {
|
|
153
|
+
let selectedCount = 0;
|
|
154
|
+
let indeterminateCount = 0;
|
|
155
|
+
for (const child of children) {
|
|
156
|
+
const selected = child[selectedField];
|
|
157
|
+
const indeterminate = child[indeterminateField];
|
|
158
|
+
if (selected && !indeterminate) {
|
|
159
|
+
selectedCount++;
|
|
160
|
+
}
|
|
161
|
+
if (indeterminate) {
|
|
162
|
+
indeterminateCount++;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
allSelected: selectedCount === children.length,
|
|
167
|
+
someSelected: selectedCount > 0 || indeterminateCount > 0,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Update parent node states based on children selection (with intermediate state support)
|
|
172
|
+
*/
|
|
173
|
+
updateParentStates(nodes, changedNode, intermediateState, idField = 'id', childrenField = 'children', selectedField = 'selected', indeterminateField = 'indeterminate') {
|
|
174
|
+
const parent = this.findParentNode(nodes, changedNode, idField, childrenField);
|
|
175
|
+
const parentChildren = parent?.[childrenField];
|
|
176
|
+
if (!parent || !parentChildren)
|
|
177
|
+
return;
|
|
178
|
+
const { allSelected, someSelected } = this.getChildrenSelectionState(parentChildren, selectedField, indeterminateField);
|
|
179
|
+
if (allSelected) {
|
|
180
|
+
parent[selectedField] = true;
|
|
181
|
+
parent[indeterminateField] = false;
|
|
182
|
+
}
|
|
183
|
+
else if (someSelected) {
|
|
184
|
+
if (intermediateState) {
|
|
185
|
+
parent[selectedField] = true;
|
|
186
|
+
parent[indeterminateField] = true;
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
parent[selectedField] = false;
|
|
190
|
+
parent[indeterminateField] = false;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
parent[selectedField] = false;
|
|
195
|
+
parent[indeterminateField] = false;
|
|
196
|
+
}
|
|
197
|
+
this.updateParentStates(nodes, parent, intermediateState, idField, childrenField, selectedField, indeterminateField);
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Recursively deselect all nodes
|
|
201
|
+
*/
|
|
202
|
+
deselectAllNodes(nodes, selectedField = 'selected', indeterminateField = 'indeterminate', childrenField = 'children') {
|
|
203
|
+
for (const node of nodes) {
|
|
204
|
+
node[selectedField] = false;
|
|
205
|
+
node[indeterminateField] = false;
|
|
206
|
+
const children = node[childrenField];
|
|
207
|
+
if (children) {
|
|
208
|
+
this.deselectAllNodes(children, selectedField, indeterminateField, childrenField);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Recursively set selection state for all nodes
|
|
214
|
+
*/
|
|
215
|
+
setAllSelection(nodes, selected, selectedField = 'selected', indeterminateField = 'indeterminate', childrenField = 'children') {
|
|
216
|
+
for (const node of nodes) {
|
|
217
|
+
node[selectedField] = selected;
|
|
218
|
+
node[indeterminateField] = false;
|
|
219
|
+
const children = node[childrenField];
|
|
220
|
+
if (children) {
|
|
221
|
+
this.setAllSelection(children, selected, selectedField, indeterminateField, childrenField);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Recursively count selected nodes
|
|
227
|
+
*/
|
|
228
|
+
countSelected(nodes, selectedField = 'selected', childrenField = 'children') {
|
|
229
|
+
let count = 0;
|
|
230
|
+
for (const node of nodes) {
|
|
231
|
+
if (node[selectedField])
|
|
232
|
+
count++;
|
|
233
|
+
const children = node[childrenField];
|
|
234
|
+
if (children) {
|
|
235
|
+
count += this.countSelected(children, selectedField, childrenField);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return count;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Recursively collect selected nodes
|
|
242
|
+
*/
|
|
243
|
+
collectSelected(nodes, result, selectedField = 'selected', childrenField = 'children') {
|
|
244
|
+
for (const node of nodes) {
|
|
245
|
+
if (node[selectedField])
|
|
246
|
+
result.push(node);
|
|
247
|
+
const children = node[childrenField];
|
|
248
|
+
if (children) {
|
|
249
|
+
this.collectSelected(children, result, selectedField, childrenField);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Recursively remove selected nodes
|
|
255
|
+
*/
|
|
256
|
+
removeSelected(nodes, selectedField = 'selected', indeterminateField = 'indeterminate', childrenField = 'children') {
|
|
257
|
+
for (let i = nodes.length - 1; i >= 0; i--) {
|
|
258
|
+
const node = nodes[i];
|
|
259
|
+
const selected = node[selectedField];
|
|
260
|
+
const indeterminate = node[indeterminateField];
|
|
261
|
+
if (selected && !indeterminate) {
|
|
262
|
+
nodes.splice(i, 1);
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
const children = node[childrenField];
|
|
266
|
+
if (children) {
|
|
267
|
+
this.removeSelected(children, selectedField, indeterminateField, childrenField);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Recursively update all parent states in the tree (used after deletion)
|
|
274
|
+
*/
|
|
275
|
+
updateAllParentStates(nodes, intermediateState, childrenField = 'children', selectedField = 'selected', indeterminateField = 'indeterminate') {
|
|
276
|
+
for (const node of nodes) {
|
|
277
|
+
const children = node[childrenField];
|
|
278
|
+
if (children && children.length > 0) {
|
|
279
|
+
this.updateAllParentStates(children, intermediateState, childrenField, selectedField, indeterminateField);
|
|
280
|
+
const { allSelected, someSelected } = this.getChildrenSelectionState(children, selectedField, indeterminateField);
|
|
281
|
+
if (allSelected) {
|
|
282
|
+
node[selectedField] = true;
|
|
283
|
+
node[indeterminateField] = false;
|
|
284
|
+
}
|
|
285
|
+
else if (someSelected) {
|
|
286
|
+
if (intermediateState) {
|
|
287
|
+
node[selectedField] = true;
|
|
288
|
+
node[indeterminateField] = true;
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
node[selectedField] = false;
|
|
292
|
+
node[indeterminateField] = false;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
node[selectedField] = false;
|
|
297
|
+
node[indeterminateField] = false;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
// ==================== Expansion Management ====================
|
|
303
|
+
/**
|
|
304
|
+
* Recursively set expanded state (with lazy loading)
|
|
305
|
+
*/
|
|
306
|
+
async setExpandedState(nodes, expanded, isLazyDataSource, loadNodeChildren, expandedField = 'expanded', childrenField = 'children', childrenCountField = 'childrenCount') {
|
|
307
|
+
for (const node of nodes) {
|
|
308
|
+
const hasChildren = this.hasChildren(node, childrenField);
|
|
309
|
+
const canLazyLoad = this.canLazyLoad(node, isLazyDataSource, childrenCountField, childrenField);
|
|
310
|
+
if (hasChildren || canLazyLoad) {
|
|
311
|
+
if (expanded && canLazyLoad) {
|
|
312
|
+
await loadNodeChildren(node);
|
|
313
|
+
}
|
|
314
|
+
node[expandedField] = expanded;
|
|
315
|
+
const children = node[childrenField];
|
|
316
|
+
if (children) {
|
|
317
|
+
await this.setExpandedState(children, expanded, isLazyDataSource, loadNodeChildren, expandedField, childrenField, childrenCountField);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
// ==================== Drag & Drop Helpers ====================
|
|
323
|
+
/**
|
|
324
|
+
* Get array reference by drop list ID
|
|
325
|
+
*/
|
|
326
|
+
getArrayByListId(nodes, listId, idField = 'id', childrenField = 'children') {
|
|
327
|
+
if (listId === AXTreeViewService.ROOT_LIST_ID) {
|
|
328
|
+
return nodes;
|
|
329
|
+
}
|
|
330
|
+
if (listId.startsWith(AXTreeViewService.NODE_DROP_PREFIX)) {
|
|
331
|
+
const nodeId = listId.replace(AXTreeViewService.NODE_DROP_PREFIX, '');
|
|
332
|
+
const node = this.findNodeById(nodes, nodeId, idField);
|
|
333
|
+
return node ? [node] : null;
|
|
334
|
+
}
|
|
335
|
+
const nodeId = listId.replace(AXTreeViewService.LIST_PREFIX, '');
|
|
336
|
+
const node = this.findNodeById(nodes, nodeId, idField);
|
|
337
|
+
const children = node?.[childrenField];
|
|
338
|
+
return children ?? null;
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Find parent node by list ID
|
|
342
|
+
*/
|
|
343
|
+
findParentByListId(nodes, listId, idField = 'id') {
|
|
344
|
+
if (listId === AXTreeViewService.ROOT_LIST_ID) {
|
|
345
|
+
return undefined;
|
|
346
|
+
}
|
|
347
|
+
const prefix = listId.startsWith(AXTreeViewService.NODE_DROP_PREFIX)
|
|
348
|
+
? AXTreeViewService.NODE_DROP_PREFIX
|
|
349
|
+
: AXTreeViewService.LIST_PREFIX;
|
|
350
|
+
const nodeId = listId.replace(prefix, '');
|
|
351
|
+
return this.findNodeById(nodes, nodeId, idField) ?? undefined;
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Generate unique list ID for each node
|
|
355
|
+
*/
|
|
356
|
+
getListId(node, idField = 'id') {
|
|
357
|
+
if (!node)
|
|
358
|
+
return AXTreeViewService.ROOT_LIST_ID;
|
|
359
|
+
const nodeId = node[idField];
|
|
360
|
+
return `${AXTreeViewService.LIST_PREFIX}${nodeId}`;
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Get root list ID constant
|
|
364
|
+
*/
|
|
365
|
+
getRootListId() {
|
|
366
|
+
return AXTreeViewService.ROOT_LIST_ID;
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Get node drop prefix constant
|
|
370
|
+
*/
|
|
371
|
+
getNodeDropPrefix() {
|
|
372
|
+
return AXTreeViewService.NODE_DROP_PREFIX;
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Get list prefix constant
|
|
376
|
+
*/
|
|
377
|
+
getListPrefix() {
|
|
378
|
+
return AXTreeViewService.LIST_PREFIX;
|
|
379
|
+
}
|
|
380
|
+
// ==================== Node Utilities ====================
|
|
381
|
+
/**
|
|
382
|
+
* Get all nodes in a flat array
|
|
383
|
+
*/
|
|
384
|
+
getAllNodes(nodes, childrenField = 'children') {
|
|
385
|
+
const allNodes = [];
|
|
386
|
+
const traverse = (nodeList) => {
|
|
387
|
+
for (const node of nodeList) {
|
|
388
|
+
allNodes.push(node);
|
|
389
|
+
const children = node[childrenField];
|
|
390
|
+
if (children) {
|
|
391
|
+
traverse(children);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
traverse(nodes);
|
|
396
|
+
return allNodes;
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Get the path to a node (array of parent nodes from root to node)
|
|
400
|
+
*/
|
|
401
|
+
getNodePath(nodes, nodeId) {
|
|
402
|
+
const path = [];
|
|
403
|
+
const node = this.findNodeById(nodes, nodeId);
|
|
404
|
+
if (!node) {
|
|
405
|
+
return path;
|
|
406
|
+
}
|
|
407
|
+
let current = node;
|
|
408
|
+
while (current) {
|
|
409
|
+
path.unshift(current);
|
|
410
|
+
const parent = this.findParentNode(nodes, current);
|
|
411
|
+
current = parent ?? null;
|
|
412
|
+
}
|
|
413
|
+
return path;
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Get the level/depth of a node (0 = root level)
|
|
417
|
+
*/
|
|
418
|
+
getNodeLevel(nodes, nodeId) {
|
|
419
|
+
const path = this.getNodePath(nodes, nodeId);
|
|
420
|
+
return path.length > 0 ? path.length - 1 : -1;
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Get sibling nodes of a given node
|
|
424
|
+
*/
|
|
425
|
+
getSiblings(nodes, nodeId, idField = 'id', childrenField = 'children') {
|
|
426
|
+
const node = this.findNodeById(nodes, nodeId, idField);
|
|
427
|
+
if (!node) {
|
|
428
|
+
return [];
|
|
429
|
+
}
|
|
430
|
+
const parent = this.findParentNode(nodes, node, idField, childrenField);
|
|
431
|
+
const siblingsArray = parent?.[childrenField] ?? nodes;
|
|
432
|
+
return siblingsArray.filter((n) => n[idField] !== nodeId);
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Clone a node (creates a deep copy)
|
|
436
|
+
*/
|
|
437
|
+
cloneNode(node, idField = 'id', titleField = 'title', tooltipField = 'tooltip', iconField = 'icon', expandedField = 'expanded', selectedField = 'selected', indeterminateField = 'indeterminate', disabledField = 'disabled', hiddenField = 'hidden', childrenCountField = 'childrenCount', dataField = 'data', childrenField = 'children') {
|
|
438
|
+
const cloned = {
|
|
439
|
+
[idField]: `${node[idField]}-clone-${Date.now()}`,
|
|
440
|
+
[titleField]: node[titleField],
|
|
441
|
+
[tooltipField]: node[tooltipField],
|
|
442
|
+
[iconField]: node[iconField],
|
|
443
|
+
[expandedField]: node[expandedField],
|
|
444
|
+
[selectedField]: false, // Cloned nodes are not selected by default
|
|
445
|
+
[indeterminateField]: false,
|
|
446
|
+
[disabledField]: node[disabledField],
|
|
447
|
+
[hiddenField]: node[hiddenField],
|
|
448
|
+
[childrenCountField]: node[childrenCountField],
|
|
449
|
+
[dataField]: node[dataField] ? JSON.parse(JSON.stringify(node[dataField])) : undefined,
|
|
450
|
+
};
|
|
451
|
+
const children = node[childrenField];
|
|
452
|
+
if (children && children.length > 0) {
|
|
453
|
+
cloned[childrenField] = children.map((child) => this.cloneNode(child, idField, titleField, tooltipField, iconField, expandedField, selectedField, indeterminateField, disabledField, hiddenField, childrenCountField, dataField, childrenField));
|
|
454
|
+
cloned[childrenCountField] = cloned[childrenField].length;
|
|
455
|
+
}
|
|
456
|
+
return cloned;
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Validate node structure (check for required fields and circular references)
|
|
460
|
+
*/
|
|
461
|
+
validateNode(node, visitedIds = new Set(), idField = 'id', titleField = 'title', childrenField = 'children', childrenCountField = 'childrenCount') {
|
|
462
|
+
const errors = [];
|
|
463
|
+
const nodeId = node[idField];
|
|
464
|
+
const nodeTitle = node[titleField];
|
|
465
|
+
if (!nodeId) {
|
|
466
|
+
errors.push(`Node must have an ${idField}`);
|
|
467
|
+
}
|
|
468
|
+
if (!nodeTitle) {
|
|
469
|
+
errors.push(`Node must have a ${titleField}`);
|
|
470
|
+
}
|
|
471
|
+
if (nodeId && visitedIds.has(nodeId)) {
|
|
472
|
+
errors.push(`Circular reference detected: node ${nodeId} appears multiple times in the tree`);
|
|
473
|
+
}
|
|
474
|
+
const children = node[childrenField];
|
|
475
|
+
if (children) {
|
|
476
|
+
const newVisited = new Set(visitedIds);
|
|
477
|
+
if (nodeId) {
|
|
478
|
+
newVisited.add(nodeId);
|
|
479
|
+
}
|
|
480
|
+
for (const child of children) {
|
|
481
|
+
const childValidation = this.validateNode(child, newVisited, idField, titleField, childrenField, childrenCountField);
|
|
482
|
+
if (!childValidation.valid) {
|
|
483
|
+
errors.push(...childValidation.errors.map((e) => `Child of ${nodeId}: ${e}`));
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
const childrenCount = node[childrenCountField];
|
|
487
|
+
if (childrenCount !== undefined && childrenCount !== children.length) {
|
|
488
|
+
errors.push(`Node ${nodeId}: ${childrenCountField} (${childrenCount}) does not match ${childrenField} array length (${children.length})`);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
return {
|
|
492
|
+
valid: errors.length === 0,
|
|
493
|
+
errors,
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: AXTreeViewService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
497
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: AXTreeViewService }); }
|
|
19
498
|
}
|
|
499
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: AXTreeViewService, decorators: [{
|
|
500
|
+
type: Injectable
|
|
501
|
+
}] });
|
|
20
502
|
|
|
21
|
-
class
|
|
503
|
+
class AXTreeViewComponent {
|
|
22
504
|
constructor() {
|
|
23
|
-
|
|
24
|
-
this.
|
|
25
|
-
this.item = input(...(ngDevMode ? [undefined, { debugName: "item" }] : []));
|
|
26
|
-
this.isExpanded = model(false, ...(ngDevMode ? [{ debugName: "isExpanded" }] : []));
|
|
27
|
-
this.isActive = model(false, ...(ngDevMode ? [{ debugName: "isActive" }] : []));
|
|
28
|
-
this.isLoading = input(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
|
|
29
|
-
this.executorChanges = input(...(ngDevMode ? [undefined, { debugName: "executorChanges" }] : []));
|
|
505
|
+
// ==================== Dependencies ====================
|
|
506
|
+
this.treeService = inject(AXTreeViewService);
|
|
30
507
|
this.platformService = inject(AXPlatform);
|
|
31
|
-
this.
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
508
|
+
this.destroyRef = inject(DestroyRef);
|
|
509
|
+
// ==================== Inputs ====================
|
|
510
|
+
/** Tree data source - can be static array or lazy loading function */
|
|
511
|
+
this.datasource = model.required(...(ngDevMode ? [{ debugName: "datasource" }] : []));
|
|
512
|
+
/** Selection mode: 'single' (click to select) or 'multiple' (checkbox selection) */
|
|
513
|
+
this.selectMode = input('multiple', ...(ngDevMode ? [{ debugName: "selectMode" }] : []));
|
|
514
|
+
/** Whether to show checkboxes for selection (only applies to multiple mode) */
|
|
515
|
+
this.showCheckbox = input(true, ...(ngDevMode ? [{ debugName: "showCheckbox" }] : []));
|
|
516
|
+
/** Selection behavior: 'all' (select anything, no special behavior), 'intermediate' (parent indeterminate state when children selected), 'leaf' (only leaf nodes selectable), 'nested' (selecting parent selects all children) */
|
|
517
|
+
this.selectionBehavior = input('all', ...(ngDevMode ? [{ debugName: "selectionBehavior" }] : []));
|
|
518
|
+
/** When true, clicking on a node toggles its selection (works for both single and multiple modes) */
|
|
519
|
+
this.checkOnClick = input(false, ...(ngDevMode ? [{ debugName: "checkOnClick" }] : []));
|
|
520
|
+
/** Drag and drop mode: 'none' (disabled), 'handler' (drag handle), 'item' (entire item) */
|
|
521
|
+
this.dragMode = input('handler', ...(ngDevMode ? [{ debugName: "dragMode" }] : []));
|
|
522
|
+
/** Drag operation type: 'order-only' (reorder only), 'move' (move between parents), 'both' (allow both) */
|
|
523
|
+
this.dragOperationType = input('both', ...(ngDevMode ? [{ debugName: "dragOperationType" }] : []));
|
|
524
|
+
/** Whether to show icons */
|
|
525
|
+
this.showIcons = input(true, ...(ngDevMode ? [{ debugName: "showIcons" }] : []));
|
|
526
|
+
/** Whether to show children count badge */
|
|
527
|
+
this.showChildrenBadge = input(true, ...(ngDevMode ? [{ debugName: "showChildrenBadge" }] : []));
|
|
528
|
+
/** Custom icon for expanded nodes */
|
|
529
|
+
this.expandedIcon = input('fa-solid fa-chevron-down', ...(ngDevMode ? [{ debugName: "expandedIcon" }] : []));
|
|
530
|
+
/** Custom icon for collapsed nodes */
|
|
531
|
+
this.collapsedIcon = input('fa-solid fa-chevron-right', ...(ngDevMode ? [{ debugName: "collapsedIcon" }] : []));
|
|
532
|
+
/** Indent size in pixels for each level */
|
|
533
|
+
this.indentSize = input(16, ...(ngDevMode ? [{ debugName: "indentSize" }] : []));
|
|
534
|
+
/** Visual style variant */
|
|
535
|
+
this.look = input('default', ...(ngDevMode ? [{ debugName: "look" }] : []));
|
|
536
|
+
/** Custom template for tree items */
|
|
537
|
+
this.nodeTemplate = input(...(ngDevMode ? [undefined, { debugName: "nodeTemplate" }] : []));
|
|
538
|
+
/** Field name for node ID (default: 'id') */
|
|
539
|
+
this.idField = input('id', ...(ngDevMode ? [{ debugName: "idField" }] : []));
|
|
540
|
+
/** Field name for node title (default: 'title') */
|
|
541
|
+
this.titleField = input('title', ...(ngDevMode ? [{ debugName: "titleField" }] : []));
|
|
542
|
+
/** Field name for node tooltip (default: 'tooltip') */
|
|
543
|
+
this.tooltipField = input('tooltip', ...(ngDevMode ? [{ debugName: "tooltipField" }] : []));
|
|
544
|
+
/** Field name for node icon (default: 'icon') */
|
|
545
|
+
this.iconField = input('icon', ...(ngDevMode ? [{ debugName: "iconField" }] : []));
|
|
546
|
+
/** Field name for expanded state (default: 'expanded') */
|
|
547
|
+
this.expandedField = input('expanded', ...(ngDevMode ? [{ debugName: "expandedField" }] : []));
|
|
548
|
+
/** Field name for selected state (default: 'selected') */
|
|
549
|
+
this.selectedField = input('selected', ...(ngDevMode ? [{ debugName: "selectedField" }] : []));
|
|
550
|
+
/** Field name for indeterminate state (default: 'indeterminate') */
|
|
551
|
+
this.indeterminateField = input('indeterminate', ...(ngDevMode ? [{ debugName: "indeterminateField" }] : []));
|
|
552
|
+
/** Field name for disabled state (default: 'disabled') */
|
|
553
|
+
this.disabledField = input('disabled', ...(ngDevMode ? [{ debugName: "disabledField" }] : []));
|
|
554
|
+
/** Field name for hidden state (default: 'hidden') */
|
|
555
|
+
this.hiddenField = input('hidden', ...(ngDevMode ? [{ debugName: "hiddenField" }] : []));
|
|
556
|
+
/** Field name for children array (default: 'children') */
|
|
557
|
+
this.childrenField = input('children', ...(ngDevMode ? [{ debugName: "childrenField" }] : []));
|
|
558
|
+
/** Field name for children count (default: 'childrenCount') */
|
|
559
|
+
this.childrenCountField = input('childrenCount', ...(ngDevMode ? [{ debugName: "childrenCountField" }] : []));
|
|
560
|
+
/** Field name for custom data (default: 'data') */
|
|
561
|
+
this.dataField = input('data', ...(ngDevMode ? [{ debugName: "dataField" }] : []));
|
|
562
|
+
// ==================== Outputs ====================
|
|
563
|
+
/** Emitted before a drop operation - set canceled to true to prevent drop */
|
|
564
|
+
this.onBeforeDrop = output();
|
|
565
|
+
/** Emitted when a node is toggled (expanded/collapsed) */
|
|
566
|
+
this.onNodeToggle = output();
|
|
567
|
+
/** Emitted when a node is selected/deselected */
|
|
568
|
+
this.onNodeSelect = output();
|
|
569
|
+
/** Emitted when selection changes - returns all currently selected nodes */
|
|
570
|
+
this.onSelectionChange = output();
|
|
571
|
+
/** Emitted when nodes are reordered within the same parent */
|
|
572
|
+
this.onOrderChange = output();
|
|
573
|
+
/** Emitted when a node is moved to a different parent */
|
|
574
|
+
this.onMoveChange = output();
|
|
575
|
+
/** Emitted for any item change (order or move) */
|
|
576
|
+
this.onItemsChange = output();
|
|
577
|
+
// ==================== Internal State ====================
|
|
578
|
+
/** Internal signal for tree nodes */
|
|
579
|
+
this.nodes = signal([], ...(ngDevMode ? [{ debugName: "nodes" }] : []));
|
|
580
|
+
/** Internal signal for tracking loading state */
|
|
581
|
+
this.loadingNodes = signal(new Set(), ...(ngDevMode ? [{ debugName: "loadingNodes" }] : []));
|
|
582
|
+
/** Currently focused node ID for keyboard navigation */
|
|
583
|
+
this.focusedNodeId = signal(null, ...(ngDevMode ? [{ debugName: "focusedNodeId" }] : []));
|
|
584
|
+
/** RTL detection signal */
|
|
585
|
+
this.isRtl = signal(this.platformService.isRtl(), ...(ngDevMode ? [{ debugName: "isRtl" }] : []));
|
|
586
|
+
/** Computed chevron icons that flip for RTL */
|
|
587
|
+
this.directionExpandedIcon = computed(() => this.expandedIcon(), ...(ngDevMode ? [{ debugName: "directionExpandedIcon" }] : []));
|
|
588
|
+
this.directionCollapsedIcon = computed(() => {
|
|
589
|
+
const isRtlDirection = this.isRtl();
|
|
590
|
+
const defaultIcon = this.collapsedIcon();
|
|
591
|
+
if (isRtlDirection && defaultIcon === 'fa-solid fa-chevron-right') {
|
|
592
|
+
return 'fa-solid fa-chevron-left';
|
|
593
|
+
}
|
|
594
|
+
if (!isRtlDirection && defaultIcon === 'fa-solid fa-chevron-left') {
|
|
595
|
+
return 'fa-solid fa-chevron-right';
|
|
596
|
+
}
|
|
597
|
+
return defaultIcon;
|
|
598
|
+
}, ...(ngDevMode ? [{ debugName: "directionCollapsedIcon" }] : []));
|
|
599
|
+
/** Flag to prevent infinite loops when syncing datasource */
|
|
600
|
+
this.isUpdatingFromDatasource = false;
|
|
601
|
+
/** Computed to check if datasource is a function */
|
|
602
|
+
this.isLazyDataSource = computed(() => typeof this.datasource() === 'function', ...(ngDevMode ? [{ debugName: "isLazyDataSource" }] : []));
|
|
603
|
+
/** Computed: Returns true when selection is restricted to leaf nodes only */
|
|
604
|
+
this.isLeafOnlyMode = computed(() => this.selectionBehavior() === 'leaf', ...(ngDevMode ? [{ debugName: "isLeafOnlyMode" }] : []));
|
|
605
|
+
/** Computed: Returns true when selecting a parent automatically selects all its children */
|
|
606
|
+
this.cascadesToChildren = computed(() => {
|
|
607
|
+
const behavior = this.selectionBehavior();
|
|
608
|
+
return behavior === 'nested' || behavior === 'intermediate-nested';
|
|
609
|
+
}, ...(ngDevMode ? [{ debugName: "cascadesToChildren" }] : []));
|
|
610
|
+
/** Computed: Returns true when parent nodes show indeterminate state based on children selection */
|
|
611
|
+
this.hasIntermediateState = computed(() => {
|
|
612
|
+
const behavior = this.selectionBehavior();
|
|
613
|
+
return behavior === 'intermediate' || behavior === 'intermediate-nested';
|
|
614
|
+
}, ...(ngDevMode ? [{ debugName: "hasIntermediateState" }] : []));
|
|
615
|
+
// ==================== Effects ====================
|
|
616
|
+
/** Effect to handle datasource changes */
|
|
617
|
+
this.#datasourceEffect = effect(async () => {
|
|
618
|
+
if (this.isUpdatingFromDatasource) {
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
const ds = this.datasource();
|
|
622
|
+
if (Array.isArray(ds)) {
|
|
623
|
+
this.nodes.set([...ds]);
|
|
624
|
+
}
|
|
625
|
+
else if (typeof ds === 'function') {
|
|
626
|
+
try {
|
|
627
|
+
await this.loadRootItems(ds);
|
|
628
|
+
}
|
|
629
|
+
catch (error) {
|
|
630
|
+
this.handleError('Failed to load root items', error);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}, ...(ngDevMode ? [{ debugName: "#datasourceEffect" }] : []));
|
|
634
|
+
/** Initialize direction change listener */
|
|
635
|
+
this.#initDirectionListener = afterNextRender(() => {
|
|
636
|
+
this.platformService.directionChange
|
|
637
|
+
.pipe(map((event) => event.data === 'rtl'), takeUntilDestroyed(this.destroyRef))
|
|
638
|
+
.subscribe((isRtl) => this.isRtl.set(isRtl));
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
// ==================== Node Property Helpers ====================
|
|
642
|
+
/**
|
|
643
|
+
* Get a property value from a node using the configured field name
|
|
644
|
+
*/
|
|
645
|
+
getNodeProp(node, fieldName, defaultValue) {
|
|
646
|
+
return node[fieldName] ?? defaultValue;
|
|
647
|
+
}
|
|
648
|
+
/**
|
|
649
|
+
* Set a property value on a node using the configured field name
|
|
650
|
+
*/
|
|
651
|
+
setNodeProp(node, fieldName, value) {
|
|
652
|
+
node[fieldName] = value;
|
|
653
|
+
}
|
|
654
|
+
/**
|
|
655
|
+
* Get node ID
|
|
656
|
+
*/
|
|
657
|
+
getNodeId(node) {
|
|
658
|
+
return this.getNodeProp(node, this.idField(), '');
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* Get node title
|
|
662
|
+
*/
|
|
663
|
+
getNodeTitle(node) {
|
|
664
|
+
return this.getNodeProp(node, this.titleField(), '');
|
|
665
|
+
}
|
|
666
|
+
/**
|
|
667
|
+
* Get node tooltip
|
|
668
|
+
*/
|
|
669
|
+
getNodeTooltip(node) {
|
|
670
|
+
return this.getNodeProp(node, this.tooltipField(), undefined);
|
|
671
|
+
}
|
|
672
|
+
/**
|
|
673
|
+
* Get node icon
|
|
674
|
+
*/
|
|
675
|
+
getNodeIcon(node) {
|
|
676
|
+
return this.getNodeProp(node, this.iconField(), undefined);
|
|
677
|
+
}
|
|
678
|
+
/**
|
|
679
|
+
* Get node expanded state
|
|
680
|
+
*/
|
|
681
|
+
getNodeExpanded(node) {
|
|
682
|
+
return this.getNodeProp(node, this.expandedField(), false);
|
|
683
|
+
}
|
|
684
|
+
/**
|
|
685
|
+
* Set node expanded state
|
|
686
|
+
*/
|
|
687
|
+
setNodeExpanded(node, value) {
|
|
688
|
+
this.setNodeProp(node, this.expandedField(), value);
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* Get node selected state
|
|
692
|
+
*/
|
|
693
|
+
getNodeSelected(node) {
|
|
694
|
+
return this.getNodeProp(node, this.selectedField(), false);
|
|
695
|
+
}
|
|
696
|
+
/**
|
|
697
|
+
* Set node selected state
|
|
698
|
+
*/
|
|
699
|
+
setNodeSelected(node, value) {
|
|
700
|
+
this.setNodeProp(node, this.selectedField(), value);
|
|
701
|
+
}
|
|
702
|
+
/**
|
|
703
|
+
* Get node indeterminate state
|
|
704
|
+
*/
|
|
705
|
+
getNodeIndeterminate(node) {
|
|
706
|
+
return this.getNodeProp(node, this.indeterminateField(), false);
|
|
707
|
+
}
|
|
708
|
+
/**
|
|
709
|
+
* Set node indeterminate state
|
|
710
|
+
*/
|
|
711
|
+
setNodeIndeterminate(node, value) {
|
|
712
|
+
this.setNodeProp(node, this.indeterminateField(), value);
|
|
713
|
+
}
|
|
714
|
+
/**
|
|
715
|
+
* Get node disabled state
|
|
716
|
+
*/
|
|
717
|
+
getNodeDisabled(node) {
|
|
718
|
+
return this.getNodeProp(node, this.disabledField(), false);
|
|
719
|
+
}
|
|
720
|
+
/**
|
|
721
|
+
* Get node hidden state
|
|
722
|
+
*/
|
|
723
|
+
getNodeHidden(node) {
|
|
724
|
+
return this.getNodeProp(node, this.hiddenField(), false);
|
|
725
|
+
}
|
|
726
|
+
/**
|
|
727
|
+
* Get node children array
|
|
728
|
+
*/
|
|
729
|
+
getNodeChildren(node) {
|
|
730
|
+
return this.getNodeProp(node, this.childrenField(), undefined);
|
|
731
|
+
}
|
|
732
|
+
/**
|
|
733
|
+
* Set node children array
|
|
734
|
+
*/
|
|
735
|
+
setNodeChildren(node, value) {
|
|
736
|
+
this.setNodeProp(node, this.childrenField(), value);
|
|
737
|
+
}
|
|
738
|
+
/**
|
|
739
|
+
* Get node children count
|
|
740
|
+
*/
|
|
741
|
+
getNodeChildrenCount(node) {
|
|
742
|
+
return this.getNodeProp(node, this.childrenCountField(), undefined);
|
|
743
|
+
}
|
|
744
|
+
/**
|
|
745
|
+
* Set node children count
|
|
746
|
+
*/
|
|
747
|
+
setNodeChildrenCount(node, value) {
|
|
748
|
+
this.setNodeProp(node, this.childrenCountField(), value);
|
|
749
|
+
}
|
|
750
|
+
// ==================== Effects ====================
|
|
751
|
+
/** Effect to handle datasource changes */
|
|
752
|
+
#datasourceEffect;
|
|
753
|
+
/** Initialize direction change listener */
|
|
754
|
+
#initDirectionListener;
|
|
755
|
+
// ==================== Public API ====================
|
|
756
|
+
/**
|
|
757
|
+
* Expand all nodes in the tree (with lazy loading support)
|
|
758
|
+
*/
|
|
759
|
+
async expandAll() {
|
|
760
|
+
await this.treeService.setExpandedState(this.nodes(), true, this.isLazyDataSource(), (node) => this.loadNodeChildren(node), this.expandedField(), this.childrenField(), this.childrenCountField());
|
|
761
|
+
this.refreshNodes();
|
|
762
|
+
}
|
|
763
|
+
/**
|
|
764
|
+
* Collapse all nodes in the tree
|
|
765
|
+
*/
|
|
766
|
+
collapseAll() {
|
|
767
|
+
this.treeService.setExpandedState(this.nodes(), false, this.isLazyDataSource(), (node) => this.loadNodeChildren(node), this.expandedField(), this.childrenField(), this.childrenCountField());
|
|
768
|
+
this.refreshNodes();
|
|
769
|
+
}
|
|
770
|
+
/**
|
|
771
|
+
* Get count of selected nodes
|
|
772
|
+
*/
|
|
773
|
+
getSelectedCount() {
|
|
774
|
+
return this.treeService.countSelected(this.nodes(), this.selectedField(), this.childrenField());
|
|
775
|
+
}
|
|
776
|
+
/**
|
|
777
|
+
* Check if any nodes are selected
|
|
778
|
+
*/
|
|
779
|
+
hasSelection() {
|
|
780
|
+
return this.getSelectedCount() > 0;
|
|
781
|
+
}
|
|
782
|
+
/**
|
|
783
|
+
* Get all selected nodes
|
|
784
|
+
*/
|
|
785
|
+
getSelectedNodes() {
|
|
786
|
+
const selected = [];
|
|
787
|
+
this.treeService.collectSelected(this.nodes(), selected, this.selectedField(), this.childrenField());
|
|
788
|
+
return selected;
|
|
789
|
+
}
|
|
790
|
+
/**
|
|
791
|
+
* Delete selected nodes from the tree
|
|
792
|
+
*/
|
|
793
|
+
deleteSelected() {
|
|
794
|
+
this.treeService.removeSelected(this.nodes(), this.selectedField(), this.indeterminateField(), this.childrenField());
|
|
795
|
+
if (!this.isLeafOnlyMode()) {
|
|
796
|
+
this.treeService.updateAllParentStates(this.nodes(), this.hasIntermediateState(), this.childrenField(), this.selectedField(), this.indeterminateField());
|
|
797
|
+
}
|
|
798
|
+
this.refreshNodes();
|
|
799
|
+
this.emitSelectionChange();
|
|
800
|
+
}
|
|
801
|
+
/**
|
|
802
|
+
* Select all nodes in the tree
|
|
803
|
+
*/
|
|
804
|
+
selectAll() {
|
|
805
|
+
if (this.selectMode() === 'none') {
|
|
47
806
|
return;
|
|
48
807
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
808
|
+
if (this.isLeafOnlyMode()) {
|
|
809
|
+
// Only select leaf nodes
|
|
810
|
+
const selectLeafs = (nodes) => {
|
|
811
|
+
for (const node of nodes) {
|
|
812
|
+
if (this.isLeafNode(node) && !this.getNodeDisabled(node)) {
|
|
813
|
+
this.setNodeSelected(node, true);
|
|
814
|
+
this.setNodeIndeterminate(node, false);
|
|
815
|
+
}
|
|
816
|
+
const children = this.getNodeChildren(node);
|
|
817
|
+
if (children) {
|
|
818
|
+
selectLeafs(children);
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
};
|
|
822
|
+
selectLeafs(this.nodes());
|
|
823
|
+
}
|
|
824
|
+
else {
|
|
825
|
+
this.treeService.setAllSelection(this.nodes(), true, this.selectedField(), this.indeterminateField(), this.childrenField());
|
|
826
|
+
}
|
|
827
|
+
this.refreshNodes();
|
|
828
|
+
this.emitSelectionChange();
|
|
829
|
+
}
|
|
830
|
+
/**
|
|
831
|
+
* Deselect all nodes in the tree
|
|
832
|
+
*/
|
|
833
|
+
deselectAll() {
|
|
834
|
+
this.treeService.setAllSelection(this.nodes(), false, this.selectedField(), this.indeterminateField(), this.childrenField());
|
|
835
|
+
this.refreshNodes();
|
|
836
|
+
this.emitSelectionChange();
|
|
837
|
+
}
|
|
838
|
+
/**
|
|
839
|
+
* Find a node by ID in the tree
|
|
840
|
+
*/
|
|
841
|
+
findNode(id) {
|
|
842
|
+
return this.treeService.findNodeById(this.nodes(), id, this.idField());
|
|
843
|
+
}
|
|
844
|
+
/**
|
|
845
|
+
* Refresh the tree to trigger change detection
|
|
846
|
+
*/
|
|
847
|
+
refresh() {
|
|
848
|
+
this.refreshNodes();
|
|
849
|
+
}
|
|
850
|
+
/**
|
|
851
|
+
* Check if a node is currently loading
|
|
852
|
+
*/
|
|
853
|
+
isNodeLoading(nodeId) {
|
|
854
|
+
return this.loadingNodes().has(nodeId);
|
|
855
|
+
}
|
|
856
|
+
/**
|
|
857
|
+
* Get loading state for a node (internal state)
|
|
858
|
+
*/
|
|
859
|
+
getNodeLoading(node) {
|
|
860
|
+
return this.loadingNodes().has(this.getNodeId(node));
|
|
861
|
+
}
|
|
862
|
+
/**
|
|
863
|
+
* Edit/update a node's properties
|
|
864
|
+
* @param nodeId - The ID of the node to edit
|
|
865
|
+
* @param updates - Partial node object with properties to update
|
|
866
|
+
* @returns true if node was found and updated, false otherwise
|
|
867
|
+
*/
|
|
868
|
+
editNode(nodeId, updates) {
|
|
869
|
+
const node = this.findNode(nodeId);
|
|
870
|
+
if (!node) {
|
|
871
|
+
return false;
|
|
872
|
+
}
|
|
873
|
+
// Update node properties
|
|
874
|
+
Object.assign(node, updates);
|
|
875
|
+
// If children array is provided, ensure it exists
|
|
876
|
+
const childrenField = this.childrenField();
|
|
877
|
+
if (updates[childrenField] !== undefined) {
|
|
878
|
+
this.setNodeChildren(node, updates[childrenField]);
|
|
879
|
+
}
|
|
880
|
+
// Update childrenCount if children array is provided
|
|
881
|
+
if (updates[childrenField] !== undefined) {
|
|
882
|
+
const children = updates[childrenField];
|
|
883
|
+
this.setNodeChildrenCount(node, children?.length);
|
|
884
|
+
}
|
|
885
|
+
this.refreshNodes();
|
|
886
|
+
return true;
|
|
887
|
+
}
|
|
888
|
+
/**
|
|
889
|
+
* Add a child node to a parent node
|
|
890
|
+
* @param parentId - The ID of the parent node
|
|
891
|
+
* @param childNode - The child node to add
|
|
892
|
+
* @param index - Optional index to insert at (default: append to end)
|
|
893
|
+
* @returns true if parent was found and child was added, false otherwise
|
|
894
|
+
*/
|
|
895
|
+
addChild(parentId, childNode, index) {
|
|
896
|
+
const parent = this.findNode(parentId);
|
|
897
|
+
if (!parent) {
|
|
898
|
+
return false;
|
|
899
|
+
}
|
|
900
|
+
// Ensure children array exists
|
|
901
|
+
let children = this.getNodeChildren(parent);
|
|
902
|
+
if (!children) {
|
|
903
|
+
children = [];
|
|
904
|
+
this.setNodeChildren(parent, children);
|
|
905
|
+
}
|
|
906
|
+
// Insert or append child
|
|
907
|
+
if (index !== undefined && index >= 0 && index <= children.length) {
|
|
908
|
+
children.splice(index, 0, childNode);
|
|
909
|
+
}
|
|
910
|
+
else {
|
|
911
|
+
children.push(childNode);
|
|
912
|
+
}
|
|
913
|
+
// Update childrenCount
|
|
914
|
+
this.setNodeChildrenCount(parent, children.length);
|
|
915
|
+
// Auto-expand parent if it was collapsed
|
|
916
|
+
if (!this.getNodeExpanded(parent)) {
|
|
917
|
+
this.setNodeExpanded(parent, true);
|
|
918
|
+
}
|
|
919
|
+
this.refreshNodes();
|
|
920
|
+
return true;
|
|
921
|
+
}
|
|
922
|
+
/**
|
|
923
|
+
* Remove a node from the tree
|
|
924
|
+
* @param nodeId - The ID of the node to remove
|
|
925
|
+
* @returns The removed node if found, null otherwise
|
|
926
|
+
*/
|
|
927
|
+
removeNode(nodeId) {
|
|
928
|
+
const node = this.findNode(nodeId);
|
|
929
|
+
if (!node) {
|
|
930
|
+
return null;
|
|
931
|
+
}
|
|
932
|
+
// Find parent to remove from its children array
|
|
933
|
+
const parent = this.treeService.findParentNode(this.nodes(), node, this.idField(), this.childrenField());
|
|
934
|
+
const parentChildren = parent ? this.getNodeChildren(parent) : undefined;
|
|
935
|
+
const targetArray = parentChildren ?? this.nodes();
|
|
936
|
+
// Find and remove the node
|
|
937
|
+
const index = targetArray.findIndex((n) => this.getNodeId(n) === nodeId);
|
|
938
|
+
if (index !== -1) {
|
|
939
|
+
const removed = targetArray.splice(index, 1)[0];
|
|
940
|
+
// Update parent's childrenCount if it exists
|
|
941
|
+
if (parent) {
|
|
942
|
+
const updatedChildren = this.getNodeChildren(parent);
|
|
943
|
+
this.setNodeChildrenCount(parent, updatedChildren?.length ?? 0);
|
|
944
|
+
}
|
|
945
|
+
// Update parent states if needed
|
|
946
|
+
if (this.hasIntermediateState()) {
|
|
947
|
+
this.treeService.updateAllParentStates(this.nodes(), this.hasIntermediateState(), this.childrenField(), this.selectedField(), this.indeterminateField());
|
|
948
|
+
}
|
|
949
|
+
this.refreshNodes();
|
|
950
|
+
return removed;
|
|
951
|
+
}
|
|
952
|
+
return null;
|
|
953
|
+
}
|
|
954
|
+
/**
|
|
955
|
+
* Expand a specific node
|
|
956
|
+
* @param nodeId - The ID of the node to expand
|
|
957
|
+
* @returns Promise that resolves when expansion is complete (if lazy loading)
|
|
958
|
+
*/
|
|
959
|
+
async expandNode(nodeId) {
|
|
960
|
+
const node = this.findNode(nodeId);
|
|
961
|
+
if (!node) {
|
|
962
|
+
return;
|
|
963
|
+
}
|
|
964
|
+
const hasChildren = this.treeService.hasChildren(node, this.childrenField());
|
|
965
|
+
const canLazyLoad = this.treeService.canLazyLoad(node, this.isLazyDataSource(), this.childrenCountField(), this.childrenField());
|
|
966
|
+
if (hasChildren || canLazyLoad) {
|
|
967
|
+
if (canLazyLoad) {
|
|
968
|
+
await this.loadNodeChildren(node);
|
|
969
|
+
}
|
|
970
|
+
this.setNodeExpanded(node, true);
|
|
971
|
+
this.refreshNodes();
|
|
972
|
+
this.onNodeToggle.emit({ component: this, node, nativeEvent: new Event('expand') });
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
/**
|
|
976
|
+
* Collapse a specific node
|
|
977
|
+
* @param nodeId - The ID of the node to collapse
|
|
978
|
+
*/
|
|
979
|
+
collapseNode(nodeId) {
|
|
980
|
+
const node = this.findNode(nodeId);
|
|
981
|
+
if (!node) {
|
|
982
|
+
return;
|
|
983
|
+
}
|
|
984
|
+
if (this.getNodeExpanded(node)) {
|
|
985
|
+
this.setNodeExpanded(node, false);
|
|
986
|
+
this.refreshNodes();
|
|
987
|
+
this.onNodeToggle.emit({ component: this, node, nativeEvent: new Event('collapse') });
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
/**
|
|
991
|
+
* Toggle expansion state of a specific node
|
|
992
|
+
* @param nodeId - The ID of the node to toggle
|
|
993
|
+
* @returns Promise that resolves when toggle is complete (if lazy loading)
|
|
994
|
+
*/
|
|
995
|
+
async toggleNodeExpansion(nodeId) {
|
|
996
|
+
const node = this.findNode(nodeId);
|
|
997
|
+
if (!node) {
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
1000
|
+
if (this.getNodeExpanded(node)) {
|
|
1001
|
+
this.collapseNode(nodeId);
|
|
1002
|
+
}
|
|
1003
|
+
else {
|
|
1004
|
+
await this.expandNode(nodeId);
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
/**
|
|
1008
|
+
* Programmatically select a node
|
|
1009
|
+
* @param nodeId - The ID of the node to select
|
|
1010
|
+
* @returns true if node was found and selected, false otherwise
|
|
1011
|
+
*/
|
|
1012
|
+
selectNode(nodeId) {
|
|
1013
|
+
if (this.selectMode() === 'none') {
|
|
1014
|
+
return false;
|
|
1015
|
+
}
|
|
1016
|
+
const node = this.findNode(nodeId);
|
|
1017
|
+
if (!node || this.getNodeDisabled(node) || !this.canSelectNode(node)) {
|
|
1018
|
+
return false;
|
|
1019
|
+
}
|
|
1020
|
+
const mode = this.selectMode();
|
|
1021
|
+
if (mode === 'single') {
|
|
1022
|
+
this.treeService.deselectAllNodes(this.nodes(), this.selectedField(), this.indeterminateField(), this.childrenField());
|
|
1023
|
+
this.setNodeSelected(node, true);
|
|
1024
|
+
this.setNodeIndeterminate(node, false);
|
|
1025
|
+
}
|
|
1026
|
+
else {
|
|
1027
|
+
this.setNodeSelected(node, true);
|
|
1028
|
+
this.setNodeIndeterminate(node, false);
|
|
1029
|
+
const children = this.getNodeChildren(node);
|
|
1030
|
+
if (this.cascadesToChildren() && children && !this.isLeafOnlyMode()) {
|
|
1031
|
+
this.treeService.selectAllChildren(children, true, this.selectedField(), this.indeterminateField(), this.childrenField());
|
|
1032
|
+
}
|
|
1033
|
+
if (this.hasIntermediateState() && !this.isLeafOnlyMode()) {
|
|
1034
|
+
this.treeService.updateParentStates(this.nodes(), node, this.hasIntermediateState(), this.idField(), this.childrenField(), this.selectedField(), this.indeterminateField());
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
this.refreshNodes();
|
|
1038
|
+
this.onNodeSelect.emit({
|
|
1039
|
+
component: this,
|
|
1040
|
+
node,
|
|
1041
|
+
isUserInteraction: false,
|
|
1042
|
+
});
|
|
1043
|
+
this.emitSelectionChange();
|
|
1044
|
+
return true;
|
|
1045
|
+
}
|
|
1046
|
+
/**
|
|
1047
|
+
* Programmatically deselect a node
|
|
1048
|
+
* @param nodeId - The ID of the node to deselect
|
|
1049
|
+
* @returns true if node was found and deselected, false otherwise
|
|
1050
|
+
*/
|
|
1051
|
+
deselectNode(nodeId) {
|
|
1052
|
+
const node = this.findNode(nodeId);
|
|
1053
|
+
if (!node) {
|
|
1054
|
+
return false;
|
|
1055
|
+
}
|
|
1056
|
+
this.setNodeSelected(node, false);
|
|
1057
|
+
this.setNodeIndeterminate(node, false);
|
|
1058
|
+
const children = this.getNodeChildren(node);
|
|
1059
|
+
if (this.cascadesToChildren() && children && !this.isLeafOnlyMode()) {
|
|
1060
|
+
this.treeService.selectAllChildren(children, false, this.selectedField(), this.indeterminateField(), this.childrenField());
|
|
1061
|
+
}
|
|
1062
|
+
if (this.hasIntermediateState() && !this.isLeafOnlyMode()) {
|
|
1063
|
+
this.treeService.updateParentStates(this.nodes(), node, this.hasIntermediateState(), this.idField(), this.childrenField(), this.selectedField(), this.indeterminateField());
|
|
1064
|
+
}
|
|
1065
|
+
this.refreshNodes();
|
|
1066
|
+
this.onNodeSelect.emit({
|
|
1067
|
+
component: this,
|
|
1068
|
+
node,
|
|
1069
|
+
isUserInteraction: false,
|
|
1070
|
+
});
|
|
1071
|
+
this.emitSelectionChange();
|
|
1072
|
+
return true;
|
|
1073
|
+
}
|
|
1074
|
+
/**
|
|
1075
|
+
* Get parent node of a given node
|
|
1076
|
+
* @param nodeId - The ID of the node
|
|
1077
|
+
* @returns The parent node if found, null otherwise
|
|
1078
|
+
*/
|
|
1079
|
+
getParent(nodeId) {
|
|
1080
|
+
const node = this.findNode(nodeId);
|
|
1081
|
+
if (!node) {
|
|
1082
|
+
return null;
|
|
1083
|
+
}
|
|
1084
|
+
return this.treeService.findParentNode(this.nodes(), node, this.idField(), this.childrenField()) ?? null;
|
|
1085
|
+
}
|
|
1086
|
+
/**
|
|
1087
|
+
* Get children of a node
|
|
1088
|
+
* @param nodeId - The ID of the parent node
|
|
1089
|
+
* @returns Array of child nodes, or null if node not found
|
|
1090
|
+
*/
|
|
1091
|
+
getChildren(nodeId) {
|
|
1092
|
+
const node = this.findNode(nodeId);
|
|
1093
|
+
if (!node) {
|
|
1094
|
+
return null;
|
|
1095
|
+
}
|
|
1096
|
+
return this.getNodeChildren(node) ?? [];
|
|
1097
|
+
}
|
|
1098
|
+
/**
|
|
1099
|
+
* Get all root nodes
|
|
1100
|
+
* @returns Array of root nodes
|
|
1101
|
+
*/
|
|
1102
|
+
getRootNodes() {
|
|
1103
|
+
return [...this.nodes()];
|
|
1104
|
+
}
|
|
1105
|
+
/**
|
|
1106
|
+
* Get all nodes in a flat array
|
|
1107
|
+
* @returns Array of all nodes in the tree
|
|
1108
|
+
*/
|
|
1109
|
+
getAllNodes() {
|
|
1110
|
+
const allNodes = [];
|
|
1111
|
+
const traverse = (nodes) => {
|
|
1112
|
+
for (const node of nodes) {
|
|
1113
|
+
allNodes.push(node);
|
|
1114
|
+
const children = this.getNodeChildren(node);
|
|
1115
|
+
if (children) {
|
|
1116
|
+
traverse(children);
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
};
|
|
1120
|
+
traverse(this.nodes());
|
|
1121
|
+
return allNodes;
|
|
1122
|
+
}
|
|
1123
|
+
/**
|
|
1124
|
+
* Get the path to a node (array of parent IDs from root to node)
|
|
1125
|
+
* @param nodeId - The ID of the node
|
|
1126
|
+
* @returns Array of node IDs representing the path, or empty array if node not found
|
|
1127
|
+
*/
|
|
1128
|
+
getNodePath(nodeId) {
|
|
1129
|
+
const path = [];
|
|
1130
|
+
const node = this.findNode(nodeId);
|
|
1131
|
+
if (!node) {
|
|
1132
|
+
return path;
|
|
1133
|
+
}
|
|
1134
|
+
let current = node;
|
|
1135
|
+
while (current) {
|
|
1136
|
+
path.unshift(this.getNodeId(current));
|
|
1137
|
+
const parent = this.treeService.findParentNode(this.nodes(), current, this.idField(), this.childrenField());
|
|
1138
|
+
current = parent ?? null;
|
|
1139
|
+
}
|
|
1140
|
+
return path;
|
|
1141
|
+
}
|
|
1142
|
+
/**
|
|
1143
|
+
* Get the level/depth of a node (0 = root level)
|
|
1144
|
+
* @param nodeId - The ID of the node
|
|
1145
|
+
* @returns The level of the node, or -1 if node not found
|
|
1146
|
+
*/
|
|
1147
|
+
getNodeLevel(nodeId) {
|
|
1148
|
+
const path = this.getNodePath(nodeId);
|
|
1149
|
+
return path.length > 0 ? path.length - 1 : -1;
|
|
1150
|
+
}
|
|
1151
|
+
/**
|
|
1152
|
+
* Programmatically move a node to a new parent
|
|
1153
|
+
* @param nodeId - The ID of the node to move
|
|
1154
|
+
* @param newParentId - The ID of the new parent (undefined for root level)
|
|
1155
|
+
* @param index - Optional index to insert at (default: append to end)
|
|
1156
|
+
* @returns true if move was successful, false otherwise
|
|
1157
|
+
*/
|
|
1158
|
+
moveNode(nodeId, newParentId, index) {
|
|
1159
|
+
const node = this.findNode(nodeId);
|
|
1160
|
+
if (!node) {
|
|
1161
|
+
return false;
|
|
1162
|
+
}
|
|
1163
|
+
// Find current parent
|
|
1164
|
+
const currentParent = this.treeService.findParentNode(this.nodes(), node, this.idField(), this.childrenField());
|
|
1165
|
+
const currentParentChildren = currentParent ? this.getNodeChildren(currentParent) : undefined;
|
|
1166
|
+
const currentArray = currentParentChildren ?? this.nodes();
|
|
1167
|
+
// Find and remove from current location
|
|
1168
|
+
const currentIndex = currentArray.findIndex((n) => this.getNodeId(n) === nodeId);
|
|
1169
|
+
if (currentIndex === -1) {
|
|
1170
|
+
return false;
|
|
1171
|
+
}
|
|
1172
|
+
const movedNode = currentArray.splice(currentIndex, 1)[0];
|
|
1173
|
+
// Find new parent
|
|
1174
|
+
let targetArray;
|
|
1175
|
+
let newParent;
|
|
1176
|
+
if (newParentId) {
|
|
1177
|
+
newParent = this.findNode(newParentId);
|
|
1178
|
+
if (!newParent) {
|
|
1179
|
+
// Restore node if new parent not found
|
|
1180
|
+
currentArray.splice(currentIndex, 0, movedNode);
|
|
1181
|
+
return false;
|
|
1182
|
+
}
|
|
1183
|
+
// Validate drop target
|
|
1184
|
+
if (!this.treeService.isValidDropTarget(movedNode, newParent, this.idField(), this.childrenField())) {
|
|
1185
|
+
// Restore node if invalid drop target
|
|
1186
|
+
currentArray.splice(currentIndex, 0, movedNode);
|
|
1187
|
+
return false;
|
|
1188
|
+
}
|
|
1189
|
+
let newParentChildren = this.getNodeChildren(newParent);
|
|
1190
|
+
if (!newParentChildren) {
|
|
1191
|
+
newParentChildren = [];
|
|
1192
|
+
this.setNodeChildren(newParent, newParentChildren);
|
|
1193
|
+
}
|
|
1194
|
+
targetArray = newParentChildren;
|
|
1195
|
+
}
|
|
1196
|
+
else {
|
|
1197
|
+
targetArray = this.nodes();
|
|
1198
|
+
}
|
|
1199
|
+
// Calculate new index before inserting
|
|
1200
|
+
const newIndex = index !== undefined && index >= 0 && index <= targetArray.length ? index : targetArray.length;
|
|
1201
|
+
// Insert at new location
|
|
1202
|
+
if (index !== undefined && index >= 0 && index <= targetArray.length) {
|
|
1203
|
+
targetArray.splice(index, 0, movedNode);
|
|
1204
|
+
}
|
|
1205
|
+
else {
|
|
1206
|
+
targetArray.push(movedNode);
|
|
1207
|
+
}
|
|
1208
|
+
// Update childrenCount
|
|
1209
|
+
if (currentParent) {
|
|
1210
|
+
const updatedChildren = this.getNodeChildren(currentParent);
|
|
1211
|
+
this.setNodeChildrenCount(currentParent, updatedChildren?.length ?? 0);
|
|
1212
|
+
}
|
|
1213
|
+
if (newParent) {
|
|
1214
|
+
const updatedChildren = this.getNodeChildren(newParent);
|
|
1215
|
+
this.setNodeChildrenCount(newParent, updatedChildren?.length ?? 0);
|
|
1216
|
+
this.setNodeExpanded(newParent, true); // Auto-expand new parent
|
|
1217
|
+
}
|
|
1218
|
+
// Emit drop events
|
|
1219
|
+
this.emitDropEvents(movedNode, currentParent, newParent, currentIndex, newIndex, false);
|
|
1220
|
+
this.refreshNodes();
|
|
1221
|
+
return true;
|
|
1222
|
+
}
|
|
1223
|
+
/**
|
|
1224
|
+
* Clone a node (creates a deep copy)
|
|
1225
|
+
* @param nodeId - The ID of the node to clone
|
|
1226
|
+
* @returns The cloned node, or null if node not found
|
|
1227
|
+
*/
|
|
1228
|
+
cloneNode(nodeId) {
|
|
1229
|
+
const node = this.findNode(nodeId);
|
|
1230
|
+
if (!node) {
|
|
1231
|
+
return null;
|
|
1232
|
+
}
|
|
1233
|
+
return this.treeService.cloneNode(node, this.idField(), this.titleField(), this.tooltipField(), this.iconField(), this.expandedField(), this.selectedField(), this.indeterminateField(), this.disabledField(), this.hiddenField(), this.childrenCountField(), this.dataField(), this.childrenField());
|
|
1234
|
+
}
|
|
1235
|
+
/**
|
|
1236
|
+
* Focus a specific node by ID
|
|
1237
|
+
* @param nodeId - The ID of the node to focus
|
|
1238
|
+
* @returns true if node was found and focused, false otherwise
|
|
1239
|
+
*/
|
|
1240
|
+
focusNode(nodeId) {
|
|
1241
|
+
const node = this.findNode(nodeId);
|
|
1242
|
+
if (!node || this.getNodeHidden(node) === true || this.getNodeDisabled(node)) {
|
|
1243
|
+
return false;
|
|
1244
|
+
}
|
|
1245
|
+
this.focusNodeById(nodeId);
|
|
1246
|
+
return true;
|
|
1247
|
+
}
|
|
1248
|
+
/**
|
|
1249
|
+
* Get all expanded nodes
|
|
1250
|
+
* @returns Array of expanded nodes
|
|
1251
|
+
*/
|
|
1252
|
+
getExpandedNodes() {
|
|
1253
|
+
const expanded = [];
|
|
1254
|
+
const traverse = (nodes) => {
|
|
1255
|
+
for (const node of nodes) {
|
|
1256
|
+
if (this.getNodeExpanded(node)) {
|
|
1257
|
+
expanded.push(node);
|
|
1258
|
+
}
|
|
1259
|
+
const children = this.getNodeChildren(node);
|
|
1260
|
+
if (children) {
|
|
1261
|
+
traverse(children);
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
};
|
|
1265
|
+
traverse(this.nodes());
|
|
1266
|
+
return expanded;
|
|
1267
|
+
}
|
|
1268
|
+
/**
|
|
1269
|
+
* Get all collapsed nodes that have children
|
|
1270
|
+
* @returns Array of collapsed nodes with children
|
|
1271
|
+
*/
|
|
1272
|
+
getCollapsedNodes() {
|
|
1273
|
+
const collapsed = [];
|
|
1274
|
+
const traverse = (nodes) => {
|
|
1275
|
+
for (const node of nodes) {
|
|
1276
|
+
const children = this.getNodeChildren(node);
|
|
1277
|
+
const childrenCount = this.getNodeChildrenCount(node);
|
|
1278
|
+
if (!this.getNodeExpanded(node) && (children?.length || childrenCount)) {
|
|
1279
|
+
collapsed.push(node);
|
|
1280
|
+
}
|
|
1281
|
+
if (children) {
|
|
1282
|
+
traverse(children);
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
};
|
|
1286
|
+
traverse(this.nodes());
|
|
1287
|
+
return collapsed;
|
|
1288
|
+
}
|
|
1289
|
+
/**
|
|
1290
|
+
* Check if a node is expanded
|
|
1291
|
+
* @param nodeId - The ID of the node
|
|
1292
|
+
* @returns true if node is expanded, false otherwise
|
|
1293
|
+
*/
|
|
1294
|
+
isNodeExpanded(nodeId) {
|
|
1295
|
+
const node = this.findNode(nodeId);
|
|
1296
|
+
return node ? this.getNodeExpanded(node) : false;
|
|
1297
|
+
}
|
|
1298
|
+
/**
|
|
1299
|
+
* Check if a node is selected
|
|
1300
|
+
* @param nodeId - The ID of the node
|
|
1301
|
+
* @returns true if node is selected, false otherwise
|
|
1302
|
+
*/
|
|
1303
|
+
isNodeSelected(nodeId) {
|
|
1304
|
+
const node = this.findNode(nodeId);
|
|
1305
|
+
return node ? this.getNodeSelected(node) : false;
|
|
1306
|
+
}
|
|
1307
|
+
/**
|
|
1308
|
+
* Check if a node has children
|
|
1309
|
+
* @param nodeId - The ID of the node
|
|
1310
|
+
* @returns true if node has children, false otherwise
|
|
1311
|
+
*/
|
|
1312
|
+
hasChildren(nodeId) {
|
|
1313
|
+
const node = this.findNode(nodeId);
|
|
1314
|
+
return this.treeService.hasChildren(node ?? {}, this.childrenField());
|
|
1315
|
+
}
|
|
1316
|
+
/**
|
|
1317
|
+
* Get template context for a node
|
|
1318
|
+
*/
|
|
1319
|
+
getTemplateContext(node, level = 0) {
|
|
1320
|
+
const children = this.getNodeChildren(node);
|
|
1321
|
+
const childrenCount = this.getNodeChildrenCount(node);
|
|
1322
|
+
return {
|
|
1323
|
+
$implicit: node,
|
|
1324
|
+
node,
|
|
1325
|
+
level,
|
|
1326
|
+
expanded: this.getNodeExpanded(node),
|
|
1327
|
+
childrenCount: childrenCount ?? children?.length ?? 0,
|
|
1328
|
+
loading: this.getNodeLoading(node),
|
|
1329
|
+
};
|
|
1330
|
+
}
|
|
1331
|
+
/**
|
|
1332
|
+
* Calculate padding-inline for a node based on its level
|
|
1333
|
+
*/
|
|
1334
|
+
getNodePaddingInline(level) {
|
|
1335
|
+
const indent = this.indentSize();
|
|
1336
|
+
const currentLook = this.look();
|
|
1337
|
+
const multiplier = currentLook === 'with-line' ? 1 / 3 : 1;
|
|
1338
|
+
return level * indent * multiplier;
|
|
1339
|
+
}
|
|
1340
|
+
/**
|
|
1341
|
+
* Check if node should show expand toggle
|
|
1342
|
+
*/
|
|
1343
|
+
shouldShowExpandToggle(node) {
|
|
1344
|
+
return (this.treeService.hasChildren(node, this.childrenField()) ||
|
|
1345
|
+
this.treeService.canLazyLoad(node, this.isLazyDataSource(), this.childrenCountField(), this.childrenField()));
|
|
1346
|
+
}
|
|
1347
|
+
/**
|
|
1348
|
+
* Check if checkboxes should be shown (only for multiple mode)
|
|
1349
|
+
*/
|
|
1350
|
+
shouldShowCheckbox() {
|
|
1351
|
+
return this.selectMode() === 'multiple' && this.showCheckbox();
|
|
1352
|
+
}
|
|
1353
|
+
/**
|
|
1354
|
+
* Check if a node is a leaf (has no children)
|
|
1355
|
+
* A node is a leaf if it has no loaded children AND no childrenCount (or childrenCount is 0)
|
|
1356
|
+
*/
|
|
1357
|
+
isLeafNode(node) {
|
|
1358
|
+
const hasChildren = this.treeService.hasChildren(node, this.childrenField());
|
|
1359
|
+
const childrenCount = this.getNodeChildrenCount(node);
|
|
1360
|
+
const hasChildrenCount = childrenCount && childrenCount > 0;
|
|
1361
|
+
const canLazyLoad = this.treeService.canLazyLoad(node, this.isLazyDataSource(), this.childrenCountField(), this.childrenField());
|
|
1362
|
+
// A node is a leaf if:
|
|
1363
|
+
// 1. It has no loaded children
|
|
1364
|
+
// 2. AND it has no childrenCount (or childrenCount is 0)
|
|
1365
|
+
// 3. AND it cannot be lazy loaded
|
|
1366
|
+
return !hasChildren && !hasChildrenCount && !canLazyLoad;
|
|
1367
|
+
}
|
|
1368
|
+
/**
|
|
1369
|
+
* Check if a node can be selected (considering selectMode and isLeafOnlyMode)
|
|
1370
|
+
*/
|
|
1371
|
+
canSelectNode(node) {
|
|
1372
|
+
if (this.selectMode() === 'none') {
|
|
1373
|
+
return false;
|
|
1374
|
+
}
|
|
1375
|
+
if (this.isLeafOnlyMode()) {
|
|
1376
|
+
return this.isLeafNode(node);
|
|
1377
|
+
}
|
|
1378
|
+
return true;
|
|
1379
|
+
}
|
|
1380
|
+
/**
|
|
1381
|
+
* Check if checkbox should be shown for a specific node
|
|
1382
|
+
*/
|
|
1383
|
+
shouldShowCheckboxForNode(node) {
|
|
1384
|
+
if (!this.shouldShowCheckbox()) {
|
|
1385
|
+
return false;
|
|
1386
|
+
}
|
|
1387
|
+
if (this.isLeafOnlyMode()) {
|
|
1388
|
+
return this.isLeafNode(node);
|
|
1389
|
+
}
|
|
1390
|
+
return true;
|
|
1391
|
+
}
|
|
1392
|
+
/**
|
|
1393
|
+
* Generate unique list ID for each node
|
|
1394
|
+
*/
|
|
1395
|
+
getListId(node) {
|
|
1396
|
+
return this.treeService.getListId(node, this.idField());
|
|
1397
|
+
}
|
|
1398
|
+
/**
|
|
1399
|
+
* Check if a node is currently focused
|
|
1400
|
+
*/
|
|
1401
|
+
isNodeFocused(nodeId) {
|
|
1402
|
+
return this.focusedNodeId() === nodeId;
|
|
1403
|
+
}
|
|
1404
|
+
/**
|
|
1405
|
+
* Get ARIA level for a node
|
|
1406
|
+
*/
|
|
1407
|
+
getNodeAriaLevel(level) {
|
|
1408
|
+
return level + 1;
|
|
1409
|
+
}
|
|
1410
|
+
/**
|
|
1411
|
+
* Get ARIA expanded state for a node
|
|
1412
|
+
*/
|
|
1413
|
+
getNodeAriaExpanded(node) {
|
|
1414
|
+
if (!this.shouldShowExpandToggle(node)) {
|
|
1415
|
+
return null;
|
|
1416
|
+
}
|
|
1417
|
+
return this.getNodeExpanded(node) ? 'true' : 'false';
|
|
1418
|
+
}
|
|
1419
|
+
/**
|
|
1420
|
+
* Get ARIA selected state for a node
|
|
1421
|
+
*/
|
|
1422
|
+
getNodeAriaSelected(node) {
|
|
1423
|
+
if (this.selectMode() === 'none') {
|
|
1424
|
+
return null;
|
|
1425
|
+
}
|
|
1426
|
+
const selected = this.getNodeSelected(node);
|
|
1427
|
+
if (this.selectMode() === 'single') {
|
|
1428
|
+
return selected ? 'true' : 'false';
|
|
53
1429
|
}
|
|
54
|
-
|
|
1430
|
+
if (this.selectMode() === 'multiple') {
|
|
1431
|
+
return selected ? 'true' : 'false';
|
|
1432
|
+
}
|
|
1433
|
+
return null;
|
|
1434
|
+
}
|
|
1435
|
+
/**
|
|
1436
|
+
* Emit selection change event with all selected nodes
|
|
1437
|
+
*/
|
|
1438
|
+
emitSelectionChange() {
|
|
1439
|
+
const selectedNodes = this.getSelectedNodes();
|
|
1440
|
+
this.onSelectionChange.emit({
|
|
1441
|
+
component: this,
|
|
1442
|
+
selectedNodes,
|
|
1443
|
+
});
|
|
55
1444
|
}
|
|
56
|
-
|
|
57
|
-
|
|
1445
|
+
// ==================== Event Handlers ====================
|
|
1446
|
+
/**
|
|
1447
|
+
* Handle node click - for single selection mode or multiple mode with checkOnClick enabled
|
|
1448
|
+
*/
|
|
1449
|
+
onNodeClick(node, event) {
|
|
1450
|
+
if (this.getNodeDisabled(node))
|
|
58
1451
|
return;
|
|
1452
|
+
if (this.selectMode() === 'none')
|
|
1453
|
+
return;
|
|
1454
|
+
if (!this.canSelectNode(node))
|
|
1455
|
+
return;
|
|
1456
|
+
const mode = this.selectMode();
|
|
1457
|
+
const shouldCheckOnClick = this.checkOnClick();
|
|
1458
|
+
if (mode === 'single') {
|
|
1459
|
+
this.handleSingleSelection(node, event);
|
|
59
1460
|
}
|
|
60
|
-
if (
|
|
61
|
-
this.
|
|
62
|
-
this.item()[this.treeView.activeField()] = true;
|
|
1461
|
+
else if (mode === 'multiple' && shouldCheckOnClick) {
|
|
1462
|
+
this.handleMultipleSelection(node, event);
|
|
63
1463
|
}
|
|
64
|
-
this.treeView.onNodeClick.emit({ component: this, data: this.item(), nativeElement: this.nativeElement });
|
|
65
1464
|
}
|
|
66
|
-
|
|
67
|
-
|
|
1465
|
+
/**
|
|
1466
|
+
* Toggle node expansion state with lazy loading support
|
|
1467
|
+
*/
|
|
1468
|
+
async toggleNode(node, event) {
|
|
1469
|
+
if (this.getNodeDisabled(node))
|
|
68
1470
|
return;
|
|
1471
|
+
if (this.isEvent(event) && typeof event.stopPropagation === 'function') {
|
|
1472
|
+
event.stopPropagation();
|
|
69
1473
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
1474
|
+
const hasChildren = this.treeService.hasChildren(node, this.childrenField());
|
|
1475
|
+
const canLazyLoad = this.treeService.canLazyLoad(node, this.isLazyDataSource(), this.childrenCountField(), this.childrenField());
|
|
1476
|
+
if (hasChildren || canLazyLoad) {
|
|
1477
|
+
const willExpand = !this.getNodeExpanded(node);
|
|
1478
|
+
if (willExpand && canLazyLoad) {
|
|
1479
|
+
await this.loadNodeChildren(node);
|
|
75
1480
|
}
|
|
1481
|
+
this.setNodeExpanded(node, willExpand);
|
|
1482
|
+
this.refreshNodes();
|
|
1483
|
+
this.onNodeToggle.emit({ component: this, node, nativeEvent: event });
|
|
76
1484
|
}
|
|
77
|
-
this.treeView.onNodedbClick.emit({ component: this, data: this.item(), nativeElement: this.nativeElement });
|
|
78
|
-
this.treeView.onCollapsedChanged.emit({ component: this, data: this.item(), nativeElement: this.nativeElement });
|
|
79
1485
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
this.
|
|
95
|
-
this.
|
|
96
|
-
|
|
97
|
-
this.
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
this.
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
this.
|
|
104
|
-
this.
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
this.
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
this.
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
1486
|
+
/**
|
|
1487
|
+
* Toggle node selection state with indeterminate support (for multiple mode)
|
|
1488
|
+
*/
|
|
1489
|
+
toggleSelection(node, event) {
|
|
1490
|
+
if (!event.isUserInteraction)
|
|
1491
|
+
return;
|
|
1492
|
+
if (this.selectMode() === 'none')
|
|
1493
|
+
return;
|
|
1494
|
+
if (!this.canSelectNode(node))
|
|
1495
|
+
return;
|
|
1496
|
+
const mode = this.selectMode();
|
|
1497
|
+
if (mode !== 'multiple')
|
|
1498
|
+
return;
|
|
1499
|
+
const newValue = event.value === null ? true : event.value;
|
|
1500
|
+
this.setNodeSelected(node, newValue);
|
|
1501
|
+
this.setNodeIndeterminate(node, false);
|
|
1502
|
+
const children = this.getNodeChildren(node);
|
|
1503
|
+
if (this.cascadesToChildren() && children && !this.isLeafOnlyMode()) {
|
|
1504
|
+
this.treeService.selectAllChildren(children, newValue, this.selectedField(), this.indeterminateField(), this.childrenField());
|
|
1505
|
+
}
|
|
1506
|
+
if (this.hasIntermediateState() && !this.isLeafOnlyMode()) {
|
|
1507
|
+
this.treeService.updateParentStates(this.nodes(), node, this.hasIntermediateState(), this.idField(), this.childrenField(), this.selectedField(), this.indeterminateField());
|
|
1508
|
+
}
|
|
1509
|
+
this.refreshNodes();
|
|
1510
|
+
this.onNodeSelect.emit({
|
|
1511
|
+
component: this,
|
|
1512
|
+
node,
|
|
1513
|
+
isUserInteraction: event.isUserInteraction,
|
|
1514
|
+
});
|
|
1515
|
+
this.emitSelectionChange();
|
|
1516
|
+
}
|
|
1517
|
+
/**
|
|
1518
|
+
* Handle drop events for tree nodes
|
|
1519
|
+
*/
|
|
1520
|
+
onDrop(event, parentNode) {
|
|
1521
|
+
const targetArray = parentNode ? (this.getNodeChildren(parentNode) ?? []) : this.nodes();
|
|
1522
|
+
const isReordering = event.previousContainer === event.container;
|
|
1523
|
+
if (isReordering) {
|
|
1524
|
+
this.handleReorder(event, targetArray, parentNode);
|
|
1525
|
+
}
|
|
1526
|
+
else {
|
|
1527
|
+
this.handleMove(event, targetArray, parentNode);
|
|
1528
|
+
}
|
|
1529
|
+
this.refreshNodes();
|
|
1530
|
+
}
|
|
1531
|
+
/**
|
|
1532
|
+
* Handle drop events when dropping directly onto a node (to make it a child)
|
|
1533
|
+
*/
|
|
1534
|
+
onDropOntoNode(event, targetNode) {
|
|
1535
|
+
if (!this.canMoveToParent())
|
|
1536
|
+
return;
|
|
1537
|
+
const sourceListId = event.previousContainer.element.id;
|
|
1538
|
+
const sourceArray = this.getArrayByListId(sourceListId);
|
|
1539
|
+
if (!sourceArray)
|
|
1540
|
+
return;
|
|
1541
|
+
const movedNode = sourceArray[event.previousIndex];
|
|
1542
|
+
if (!this.treeService.isValidDropTarget(movedNode, targetNode, this.idField(), this.childrenField()))
|
|
1543
|
+
return;
|
|
1544
|
+
if (!this.emitBeforeDropEvent(movedNode, sourceListId, targetNode, event.previousIndex, 0))
|
|
1545
|
+
return;
|
|
1546
|
+
let targetChildren = this.getNodeChildren(targetNode);
|
|
1547
|
+
if (!targetChildren) {
|
|
1548
|
+
targetChildren = [];
|
|
1549
|
+
this.setNodeChildren(targetNode, targetChildren);
|
|
1550
|
+
}
|
|
1551
|
+
sourceArray.splice(event.previousIndex, 1);
|
|
1552
|
+
targetChildren.unshift(movedNode);
|
|
1553
|
+
this.setNodeExpanded(targetNode, true);
|
|
1554
|
+
this.emitDropEvents(movedNode, this.findParentByListId(sourceListId), targetNode, event.previousIndex, 0, false);
|
|
1555
|
+
this.refreshNodes();
|
|
1556
|
+
}
|
|
1557
|
+
/**
|
|
1558
|
+
* Handle node focus event
|
|
1559
|
+
*/
|
|
1560
|
+
onNodeFocus(nodeId) {
|
|
1561
|
+
this.focusedNodeId.set(nodeId);
|
|
1562
|
+
}
|
|
1563
|
+
/**
|
|
1564
|
+
* Handle tree container focus - focus first node if none is focused
|
|
1565
|
+
*/
|
|
1566
|
+
onTreeFocus(event) {
|
|
1567
|
+
if (event.target === event.currentTarget) {
|
|
1568
|
+
const flatList = this.treeService.buildFlatNodeList(this.nodes(), this.hiddenField(), this.disabledField(), this.expandedField(), this.childrenField());
|
|
1569
|
+
if (flatList.length > 0) {
|
|
1570
|
+
const focusedId = this.focusedNodeId();
|
|
1571
|
+
if (focusedId) {
|
|
1572
|
+
// Check if the focused node still exists and is not hidden
|
|
1573
|
+
const focusedNode = this.treeService.findNodeById(this.nodes(), focusedId, this.idField());
|
|
1574
|
+
if (focusedNode && this.getNodeHidden(focusedNode) !== true) {
|
|
1575
|
+
this.focusNodeById(focusedId);
|
|
1576
|
+
}
|
|
1577
|
+
else {
|
|
1578
|
+
// Focused node no longer exists, focus first node
|
|
1579
|
+
this.focusNodeById(this.getNodeId(flatList[0].node));
|
|
1580
|
+
}
|
|
129
1581
|
}
|
|
130
1582
|
else {
|
|
131
|
-
|
|
132
|
-
this.
|
|
1583
|
+
// No node is focused, focus first node
|
|
1584
|
+
this.focusNodeById(this.getNodeId(flatList[0].node));
|
|
133
1585
|
}
|
|
134
1586
|
}
|
|
135
|
-
|
|
136
|
-
this.itemsSignal.set(itemsInput);
|
|
137
|
-
this.itemsPromise = null;
|
|
138
|
-
}
|
|
139
|
-
}, ...(ngDevMode ? [{ debugName: "#effect" }] : []));
|
|
140
|
-
this.expandOn = input('defult', ...(ngDevMode ? [{ debugName: "expandOn" }] : []));
|
|
141
|
-
this.loadingState = signal({}, ...(ngDevMode ? [{ debugName: "loadingState" }] : []));
|
|
142
|
-
}
|
|
143
|
-
/** @ignore */
|
|
144
|
-
get __hostClass() {
|
|
145
|
-
return [`ax-look-${this.look()}`];
|
|
1587
|
+
}
|
|
146
1588
|
}
|
|
147
|
-
|
|
148
|
-
|
|
1589
|
+
/**
|
|
1590
|
+
* Handle tree container blur
|
|
1591
|
+
*/
|
|
1592
|
+
onTreeBlur(event) {
|
|
1593
|
+
if (event.relatedTarget && !event.currentTarget.contains(event.relatedTarget)) {
|
|
1594
|
+
this.focusedNodeId.set(null);
|
|
1595
|
+
}
|
|
149
1596
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
1597
|
+
/**
|
|
1598
|
+
* Handle keyboard navigation
|
|
1599
|
+
*/
|
|
1600
|
+
handleKeyDown(event) {
|
|
1601
|
+
const flatList = this.treeService.buildFlatNodeList(this.nodes(), this.hiddenField(), this.disabledField(), this.expandedField(), this.childrenField());
|
|
1602
|
+
if (flatList.length === 0)
|
|
153
1603
|
return;
|
|
1604
|
+
const currentFocused = this.getFocusedNode();
|
|
1605
|
+
let currentIndex = currentFocused
|
|
1606
|
+
? flatList.findIndex((item) => this.getNodeId(item.node) === this.getNodeId(currentFocused))
|
|
1607
|
+
: -1;
|
|
1608
|
+
if (currentIndex === -1 && event.target === event.currentTarget) {
|
|
1609
|
+
currentIndex = 0;
|
|
154
1610
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
this.setNodeLoading(item[this.valueField()], true);
|
|
166
|
-
this.fetchData(item);
|
|
167
|
-
}
|
|
168
|
-
this.toggleExpand(item);
|
|
169
|
-
}
|
|
170
|
-
break;
|
|
171
|
-
case 'cascade':
|
|
172
|
-
this.expandAndToggleSelection(item, event.value);
|
|
173
|
-
break;
|
|
174
|
-
case 'indeterminate':
|
|
175
|
-
if (item?.[this.childrenField()]?.length) {
|
|
176
|
-
this.applySelectionToChildren(item, event.value, item[this.valueField()]);
|
|
177
|
-
}
|
|
178
|
-
this.updateParentSelection(item, event.value);
|
|
179
|
-
break;
|
|
180
|
-
default:
|
|
181
|
-
break;
|
|
182
|
-
}
|
|
1611
|
+
const navigationResult = this.handleNavigationKey(event, flatList, currentIndex, currentFocused);
|
|
1612
|
+
if (navigationResult.handled) {
|
|
1613
|
+
if (navigationResult.shouldPreventDefault) {
|
|
1614
|
+
event.preventDefault();
|
|
1615
|
+
event.stopPropagation();
|
|
1616
|
+
}
|
|
1617
|
+
if (navigationResult.targetIndex !== null &&
|
|
1618
|
+
navigationResult.targetIndex >= 0 &&
|
|
1619
|
+
navigationResult.targetIndex < flatList.length) {
|
|
1620
|
+
this.focusNodeById(this.getNodeId(flatList[navigationResult.targetIndex].node));
|
|
183
1621
|
}
|
|
184
|
-
this.onItemSelectedChanged.emit({
|
|
185
|
-
component: this,
|
|
186
|
-
data: item,
|
|
187
|
-
nativeElement: this.nativeElement,
|
|
188
|
-
});
|
|
189
|
-
const result = this.findSelectedNodes(this.itemsSignal());
|
|
190
|
-
this.onSelectionChanged.emit({
|
|
191
|
-
component: this,
|
|
192
|
-
data: result,
|
|
193
|
-
nativeElement: this.nativeElement,
|
|
194
|
-
});
|
|
195
1622
|
}
|
|
196
1623
|
}
|
|
1624
|
+
// ==================== Private Methods ====================
|
|
197
1625
|
/**
|
|
198
|
-
*
|
|
199
|
-
* auto expand
|
|
200
|
-
*
|
|
1626
|
+
* Load root items when datasource is a function
|
|
201
1627
|
*/
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
1628
|
+
async loadRootItems(loadFn) {
|
|
1629
|
+
try {
|
|
1630
|
+
const result = loadFn();
|
|
1631
|
+
const rootNodes = result instanceof Promise ? await result : result;
|
|
1632
|
+
this.nodes.set(rootNodes);
|
|
1633
|
+
}
|
|
1634
|
+
catch (error) {
|
|
1635
|
+
this.handleError('Failed to load root items', error);
|
|
1636
|
+
this.nodes.set([]);
|
|
205
1637
|
}
|
|
206
1638
|
}
|
|
207
1639
|
/**
|
|
208
|
-
*
|
|
209
|
-
* expand and change value parent change
|
|
210
|
-
*
|
|
1640
|
+
* Load children for a node using lazy loading
|
|
211
1641
|
*/
|
|
212
|
-
async
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
1642
|
+
async loadNodeChildren(node) {
|
|
1643
|
+
const nodeId = this.getNodeId(node);
|
|
1644
|
+
if (!this.isLazyDataSource() || this.loadingNodes().has(nodeId))
|
|
1645
|
+
return;
|
|
1646
|
+
if (this.treeService.hasChildren(node, this.childrenField())) {
|
|
1647
|
+
const childrenCount = this.getNodeChildrenCount(node);
|
|
1648
|
+
if (!childrenCount || childrenCount === 0) {
|
|
1649
|
+
return;
|
|
1650
|
+
}
|
|
216
1651
|
}
|
|
217
|
-
this.
|
|
218
|
-
if (
|
|
219
|
-
|
|
1652
|
+
const ds = this.datasource();
|
|
1653
|
+
if (typeof ds !== 'function')
|
|
1654
|
+
return;
|
|
1655
|
+
try {
|
|
1656
|
+
this.loadingNodes.update((set) => new Set(set).add(nodeId));
|
|
1657
|
+
this.refreshNodes();
|
|
1658
|
+
const result = ds(nodeId);
|
|
1659
|
+
const children = result instanceof Promise ? await result : result;
|
|
1660
|
+
this.setNodeChildren(node, children);
|
|
1661
|
+
this.setNodeChildrenCount(node, children.length);
|
|
1662
|
+
}
|
|
1663
|
+
catch (error) {
|
|
1664
|
+
this.handleError('Failed to load children', error);
|
|
1665
|
+
this.setNodeChildrenCount(node, 0);
|
|
1666
|
+
}
|
|
1667
|
+
finally {
|
|
1668
|
+
this.loadingNodes.update((set) => {
|
|
1669
|
+
const newSet = new Set(set);
|
|
1670
|
+
newSet.delete(nodeId);
|
|
1671
|
+
return newSet;
|
|
1672
|
+
});
|
|
1673
|
+
this.refreshNodes();
|
|
220
1674
|
}
|
|
221
|
-
}
|
|
222
|
-
applySelectionToChildren(item, isSelected, parentId) {
|
|
223
|
-
item[this.childrenField()].forEach(async (child) => {
|
|
224
|
-
if (this.itemsPromise &&
|
|
225
|
-
child[this.hasChildField()] &&
|
|
226
|
-
!child?.[this.childrenField()]?.length &&
|
|
227
|
-
this.selectionBehavior() === 'cascade') {
|
|
228
|
-
await this.setNodeLoading(child[this.valueField()], true);
|
|
229
|
-
await this.fetchData(child);
|
|
230
|
-
this.toggleExpand(child);
|
|
231
|
-
}
|
|
232
|
-
child[this.parentField()] = parentId;
|
|
233
|
-
child[this.selectedField()] = isSelected;
|
|
234
|
-
if (child[this.childrenField()]?.length) {
|
|
235
|
-
this.applySelectionToChildren(child, isSelected, child[this.valueField()]);
|
|
236
|
-
}
|
|
237
|
-
});
|
|
238
1675
|
}
|
|
239
1676
|
/**
|
|
240
|
-
*
|
|
241
|
-
*
|
|
242
|
-
*
|
|
1677
|
+
* Internal method to refresh nodes signal and sync back to datasource if it's an array
|
|
1678
|
+
* Creates new array references for all nested children to ensure reactivity
|
|
243
1679
|
*/
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
parent[this.selectedField()] = null;
|
|
257
|
-
}
|
|
258
|
-
else if (allSelected) {
|
|
259
|
-
parent[this.selectedField()] = true;
|
|
260
|
-
parent[this.indeterminateField()] = false;
|
|
261
|
-
}
|
|
262
|
-
else {
|
|
263
|
-
parent[this.indeterminateField()] = false;
|
|
264
|
-
parent[this.selectedField()] = true;
|
|
265
|
-
}
|
|
266
|
-
parent = this.findParent(parent, this.itemsSignal());
|
|
1680
|
+
refreshNodes() {
|
|
1681
|
+
const currentNodes = this.nodes();
|
|
1682
|
+
// Create new array references for all nested children to ensure reactivity
|
|
1683
|
+
// This ensures Angular's change detection picks up changes even with callback datasource
|
|
1684
|
+
this.ensureNewArrayReferences(currentNodes);
|
|
1685
|
+
this.nodes.set([...currentNodes]);
|
|
1686
|
+
if (!this.isLazyDataSource() && !this.isUpdatingFromDatasource) {
|
|
1687
|
+
this.isUpdatingFromDatasource = true;
|
|
1688
|
+
this.datasource.set([...currentNodes]);
|
|
1689
|
+
setTimeout(() => {
|
|
1690
|
+
this.isUpdatingFromDatasource = false;
|
|
1691
|
+
}, 0);
|
|
267
1692
|
}
|
|
268
1693
|
}
|
|
269
|
-
|
|
1694
|
+
/**
|
|
1695
|
+
* Recursively ensure all children arrays have new references to trigger change detection
|
|
1696
|
+
* Mutates the tree structure by replacing children arrays with new array references
|
|
1697
|
+
*/
|
|
1698
|
+
ensureNewArrayReferences(nodes) {
|
|
270
1699
|
for (const node of nodes) {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
return parent;
|
|
1700
|
+
const children = this.getNodeChildren(node);
|
|
1701
|
+
if (children && children.length > 0) {
|
|
1702
|
+
// Create new array reference for children
|
|
1703
|
+
this.setNodeChildren(node, [...children]);
|
|
1704
|
+
// Recursively process nested children
|
|
1705
|
+
this.ensureNewArrayReferences(children);
|
|
278
1706
|
}
|
|
279
1707
|
}
|
|
280
|
-
return null;
|
|
281
1708
|
}
|
|
282
1709
|
/**
|
|
283
|
-
*
|
|
284
|
-
* find node selected true for emit Selections
|
|
285
|
-
*
|
|
1710
|
+
* Handle single selection mode
|
|
286
1711
|
*/
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
1712
|
+
handleSingleSelection(node, event) {
|
|
1713
|
+
this.treeService.deselectAllNodes(this.nodes(), this.selectedField(), this.indeterminateField(), this.childrenField());
|
|
1714
|
+
this.setNodeSelected(node, true);
|
|
1715
|
+
this.setNodeIndeterminate(node, false);
|
|
1716
|
+
this.refreshNodes();
|
|
1717
|
+
this.onNodeSelect.emit({
|
|
1718
|
+
component: this,
|
|
1719
|
+
node,
|
|
1720
|
+
nativeEvent: event,
|
|
1721
|
+
isUserInteraction: true,
|
|
296
1722
|
});
|
|
297
|
-
|
|
1723
|
+
this.emitSelectionChange();
|
|
298
1724
|
}
|
|
299
1725
|
/**
|
|
300
|
-
*
|
|
301
|
-
* find for emit Selections single mode
|
|
302
|
-
*
|
|
1726
|
+
* Handle multiple selection mode with checkOnClick
|
|
303
1727
|
*/
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
1728
|
+
handleMultipleSelection(node, event) {
|
|
1729
|
+
const newValue = !this.getNodeSelected(node);
|
|
1730
|
+
this.setNodeSelected(node, newValue);
|
|
1731
|
+
this.setNodeIndeterminate(node, false);
|
|
1732
|
+
const children = this.getNodeChildren(node);
|
|
1733
|
+
if (this.cascadesToChildren() && children && !this.isLeafOnlyMode()) {
|
|
1734
|
+
this.treeService.selectAllChildren(children, newValue, this.selectedField(), this.indeterminateField(), this.childrenField());
|
|
1735
|
+
}
|
|
1736
|
+
if (this.hasIntermediateState() && !this.isLeafOnlyMode()) {
|
|
1737
|
+
this.treeService.updateParentStates(this.nodes(), node, this.hasIntermediateState(), this.idField(), this.childrenField(), this.selectedField(), this.indeterminateField());
|
|
1738
|
+
}
|
|
1739
|
+
this.refreshNodes();
|
|
1740
|
+
this.onNodeSelect.emit({
|
|
1741
|
+
component: this,
|
|
1742
|
+
node,
|
|
1743
|
+
nativeEvent: event,
|
|
1744
|
+
isUserInteraction: true,
|
|
310
1745
|
});
|
|
1746
|
+
this.emitSelectionChange();
|
|
311
1747
|
}
|
|
312
1748
|
/**
|
|
313
|
-
*
|
|
314
|
-
* lazy load logic
|
|
315
|
-
*
|
|
1749
|
+
* Get array reference by drop list ID
|
|
316
1750
|
*/
|
|
317
|
-
|
|
318
|
-
this.
|
|
319
|
-
.then((data) => {
|
|
320
|
-
if (Array.isArray(data)) {
|
|
321
|
-
if (selectedNode?.[this.valueField()]) {
|
|
322
|
-
this.findNode(selectedNode[this.valueField()], data, this.itemsSignal());
|
|
323
|
-
}
|
|
324
|
-
else {
|
|
325
|
-
const isNodeExpanded = data.filter((child) => child[this.expandedField()]);
|
|
326
|
-
isNodeExpanded.forEach((child) => {
|
|
327
|
-
this.fetchData(child);
|
|
328
|
-
});
|
|
329
|
-
this.itemsSignal.set(data);
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
})
|
|
333
|
-
.finally(() => {
|
|
334
|
-
this.setNodeLoading(selectedNode?.[this.valueField()], false);
|
|
335
|
-
});
|
|
1751
|
+
getArrayByListId(listId) {
|
|
1752
|
+
return this.treeService.getArrayByListId(this.nodes(), listId, this.idField(), this.childrenField());
|
|
336
1753
|
}
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
1754
|
+
/**
|
|
1755
|
+
* Find parent node by list ID
|
|
1756
|
+
*/
|
|
1757
|
+
findParentByListId(listId) {
|
|
1758
|
+
return this.treeService.findParentByListId(this.nodes(), listId, this.idField());
|
|
1759
|
+
}
|
|
1760
|
+
/**
|
|
1761
|
+
* Check if move operation is allowed based on dragOperationType
|
|
1762
|
+
*/
|
|
1763
|
+
canMoveToParent() {
|
|
1764
|
+
return this.dragOperationType() !== 'order-only';
|
|
1765
|
+
}
|
|
1766
|
+
/**
|
|
1767
|
+
* Check if reorder operation is allowed based on dragOperationType
|
|
1768
|
+
*/
|
|
1769
|
+
canReorder() {
|
|
1770
|
+
return this.dragOperationType() !== 'move';
|
|
1771
|
+
}
|
|
1772
|
+
/**
|
|
1773
|
+
* Handle reordering within the same list */
|
|
1774
|
+
handleReorder(event, targetArray, parentNode) {
|
|
1775
|
+
if (!this.canReorder())
|
|
1776
|
+
return;
|
|
1777
|
+
const movedNode = targetArray[event.previousIndex];
|
|
1778
|
+
moveItemInArray(targetArray, event.previousIndex, event.currentIndex);
|
|
1779
|
+
this.emitDropEvents(movedNode, parentNode, parentNode, event.previousIndex, event.currentIndex, true);
|
|
1780
|
+
}
|
|
1781
|
+
/**
|
|
1782
|
+
* Handle moving between different lists
|
|
1783
|
+
*/
|
|
1784
|
+
handleMove(event, targetArray, parentNode) {
|
|
1785
|
+
if (!this.canMoveToParent())
|
|
1786
|
+
return;
|
|
1787
|
+
const sourceListId = event.previousContainer.element.id;
|
|
1788
|
+
const sourceArray = this.getArrayByListId(sourceListId);
|
|
1789
|
+
if (!sourceArray)
|
|
1790
|
+
return;
|
|
1791
|
+
const movedNode = sourceArray[event.previousIndex];
|
|
1792
|
+
if (parentNode && !this.treeService.isValidDropTarget(movedNode, parentNode))
|
|
1793
|
+
return;
|
|
1794
|
+
if (!this.emitBeforeDropEvent(movedNode, sourceListId, parentNode, event.previousIndex, event.currentIndex)) {
|
|
1795
|
+
return;
|
|
351
1796
|
}
|
|
1797
|
+
transferArrayItem(sourceArray, targetArray, event.previousIndex, event.currentIndex);
|
|
1798
|
+
this.emitDropEvents(movedNode, this.findParentByListId(sourceListId), parentNode, event.previousIndex, event.currentIndex, false);
|
|
352
1799
|
}
|
|
353
1800
|
/**
|
|
354
|
-
*
|
|
355
|
-
* emit when arrow click
|
|
356
|
-
*
|
|
1801
|
+
* Emit beforeDrop event and return whether to continue
|
|
357
1802
|
*/
|
|
358
|
-
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
this.
|
|
1803
|
+
emitBeforeDropEvent(movedNode, sourceListId, currentParent, previousIndex, currentIndex) {
|
|
1804
|
+
const beforeDropEvent = {
|
|
1805
|
+
component: this,
|
|
1806
|
+
movedNode,
|
|
1807
|
+
previousParent: this.findParentByListId(sourceListId),
|
|
1808
|
+
currentParent,
|
|
1809
|
+
previousIndex,
|
|
1810
|
+
currentIndex,
|
|
1811
|
+
canceled: false,
|
|
1812
|
+
};
|
|
1813
|
+
this.onBeforeDrop.emit(beforeDropEvent);
|
|
1814
|
+
return !beforeDropEvent.canceled;
|
|
1815
|
+
}
|
|
1816
|
+
/**
|
|
1817
|
+
* Emit drop events based on operation type
|
|
1818
|
+
*/
|
|
1819
|
+
emitDropEvents(node, previousParent, currentParent, previousIndex, currentIndex, isReorder) {
|
|
1820
|
+
const dropEvent = {
|
|
1821
|
+
component: this,
|
|
1822
|
+
node,
|
|
1823
|
+
previousParent,
|
|
1824
|
+
currentParent,
|
|
1825
|
+
previousIndex,
|
|
1826
|
+
currentIndex,
|
|
1827
|
+
};
|
|
1828
|
+
if (isReorder) {
|
|
1829
|
+
this.onOrderChange.emit(dropEvent);
|
|
1830
|
+
}
|
|
1831
|
+
else {
|
|
1832
|
+
this.onMoveChange.emit(dropEvent);
|
|
363
1833
|
}
|
|
364
|
-
this.
|
|
1834
|
+
this.onItemsChange.emit(dropEvent);
|
|
365
1835
|
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
1836
|
+
/**
|
|
1837
|
+
* Get the currently focused node
|
|
1838
|
+
*/
|
|
1839
|
+
getFocusedNode() {
|
|
1840
|
+
const focusedId = this.focusedNodeId();
|
|
1841
|
+
if (!focusedId)
|
|
1842
|
+
return null;
|
|
1843
|
+
return this.treeService.findNodeById(this.nodes(), focusedId, this.idField());
|
|
1844
|
+
}
|
|
1845
|
+
/**
|
|
1846
|
+
* Set focus to a node by ID
|
|
1847
|
+
*/
|
|
1848
|
+
focusNodeById(nodeId) {
|
|
1849
|
+
this.focusedNodeId.set(nodeId);
|
|
1850
|
+
setTimeout(() => {
|
|
1851
|
+
const element = document.querySelector(`[data-tree-node-id="${nodeId}"]`);
|
|
1852
|
+
if (element) {
|
|
1853
|
+
element.focus();
|
|
1854
|
+
element.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
|
|
371
1855
|
}
|
|
372
|
-
});
|
|
1856
|
+
}, 0);
|
|
373
1857
|
}
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
1858
|
+
/**
|
|
1859
|
+
* Handle keyboard navigation keys
|
|
1860
|
+
*/
|
|
1861
|
+
handleNavigationKey(event, flatList, currentIndex, currentFocused) {
|
|
1862
|
+
let targetIndex = currentIndex;
|
|
1863
|
+
let shouldPreventDefault = true;
|
|
1864
|
+
let handled = true;
|
|
1865
|
+
switch (event.key) {
|
|
1866
|
+
case 'ArrowUp':
|
|
1867
|
+
if (currentIndex > 0) {
|
|
1868
|
+
targetIndex = currentIndex - 1;
|
|
1869
|
+
}
|
|
1870
|
+
else {
|
|
1871
|
+
shouldPreventDefault = false;
|
|
1872
|
+
}
|
|
1873
|
+
break;
|
|
1874
|
+
case 'ArrowDown':
|
|
1875
|
+
if (currentIndex < flatList.length - 1) {
|
|
1876
|
+
targetIndex = currentIndex + 1;
|
|
1877
|
+
}
|
|
1878
|
+
else if (currentIndex === -1) {
|
|
1879
|
+
targetIndex = 0;
|
|
1880
|
+
}
|
|
1881
|
+
else {
|
|
1882
|
+
shouldPreventDefault = false;
|
|
1883
|
+
}
|
|
1884
|
+
break;
|
|
1885
|
+
case 'ArrowLeft':
|
|
1886
|
+
if (currentFocused) {
|
|
1887
|
+
if (this.getNodeExpanded(currentFocused) && this.shouldShowExpandToggle(currentFocused)) {
|
|
1888
|
+
this.toggleNode(currentFocused, event);
|
|
1889
|
+
return { handled: true, shouldPreventDefault: true, targetIndex: null };
|
|
1890
|
+
}
|
|
1891
|
+
else {
|
|
1892
|
+
const currentItem = flatList[currentIndex];
|
|
1893
|
+
if (currentItem?.parent) {
|
|
1894
|
+
targetIndex = flatList.findIndex((item) => this.getNodeId(item.node) === this.getNodeId(currentItem.parent));
|
|
1895
|
+
}
|
|
1896
|
+
else {
|
|
1897
|
+
shouldPreventDefault = false;
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
else {
|
|
1902
|
+
shouldPreventDefault = false;
|
|
1903
|
+
}
|
|
387
1904
|
break;
|
|
388
|
-
case '
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
1905
|
+
case 'ArrowRight':
|
|
1906
|
+
if (currentFocused) {
|
|
1907
|
+
if (!this.getNodeExpanded(currentFocused) && this.shouldShowExpandToggle(currentFocused)) {
|
|
1908
|
+
this.toggleNode(currentFocused, event);
|
|
1909
|
+
return { handled: true, shouldPreventDefault: true, targetIndex: null };
|
|
1910
|
+
}
|
|
1911
|
+
else if (this.getNodeExpanded(currentFocused) &&
|
|
1912
|
+
this.treeService.hasChildren(currentFocused, this.childrenField())) {
|
|
1913
|
+
const children = this.getNodeChildren(currentFocused);
|
|
1914
|
+
if (children && children.length > 0) {
|
|
1915
|
+
const firstChild = children[0];
|
|
1916
|
+
targetIndex = flatList.findIndex((item) => this.getNodeId(item.node) === this.getNodeId(firstChild));
|
|
1917
|
+
if (targetIndex === -1) {
|
|
1918
|
+
const updatedFlatList = this.treeService.buildFlatNodeList(this.nodes(), this.hiddenField(), this.disabledField(), this.expandedField(), this.childrenField());
|
|
1919
|
+
targetIndex = updatedFlatList.findIndex((item) => this.getNodeId(item.node) === this.getNodeId(firstChild));
|
|
1920
|
+
if (targetIndex >= 0 && targetIndex < updatedFlatList.length) {
|
|
1921
|
+
this.focusNodeById(this.getNodeId(updatedFlatList[targetIndex].node));
|
|
1922
|
+
return { handled: true, shouldPreventDefault: true, targetIndex: null };
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
else {
|
|
1927
|
+
shouldPreventDefault = false;
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
else {
|
|
1931
|
+
shouldPreventDefault = false;
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1934
|
+
else {
|
|
1935
|
+
shouldPreventDefault = false;
|
|
392
1936
|
}
|
|
393
1937
|
break;
|
|
394
|
-
case '
|
|
395
|
-
|
|
1938
|
+
case 'Home':
|
|
1939
|
+
targetIndex = 0;
|
|
1940
|
+
break;
|
|
1941
|
+
case 'End':
|
|
1942
|
+
targetIndex = flatList.length - 1;
|
|
1943
|
+
break;
|
|
1944
|
+
case ' ':
|
|
1945
|
+
case 'Space':
|
|
1946
|
+
if (currentFocused && this.selectMode() === 'multiple' && this.canSelectNode(currentFocused)) {
|
|
1947
|
+
event.preventDefault();
|
|
1948
|
+
this.handleSpaceKeySelection(currentFocused, event);
|
|
1949
|
+
return { handled: true, shouldPreventDefault: true, targetIndex: null };
|
|
1950
|
+
}
|
|
1951
|
+
shouldPreventDefault = false;
|
|
396
1952
|
break;
|
|
397
|
-
case '
|
|
398
|
-
|
|
1953
|
+
case 'Enter':
|
|
1954
|
+
if (currentFocused && this.canSelectNode(currentFocused)) {
|
|
1955
|
+
event.preventDefault();
|
|
1956
|
+
this.handleEnterKeySelection(currentFocused, event);
|
|
1957
|
+
return { handled: true, shouldPreventDefault: true, targetIndex: null };
|
|
1958
|
+
}
|
|
1959
|
+
shouldPreventDefault = false;
|
|
399
1960
|
break;
|
|
400
1961
|
default:
|
|
1962
|
+
if ((event.ctrlKey || event.metaKey) && event.key === 'Enter') {
|
|
1963
|
+
if (currentFocused && this.selectMode() === 'multiple' && this.canSelectNode(currentFocused)) {
|
|
1964
|
+
event.preventDefault();
|
|
1965
|
+
this.handleCtrlEnterSelection(currentFocused, event);
|
|
1966
|
+
return { handled: true, shouldPreventDefault: true, targetIndex: null };
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1969
|
+
handled = false;
|
|
1970
|
+
shouldPreventDefault = false;
|
|
401
1971
|
break;
|
|
402
1972
|
}
|
|
403
|
-
|
|
404
|
-
*
|
|
405
|
-
* for detect changes treeviewitem
|
|
406
|
-
*
|
|
407
|
-
*/
|
|
408
|
-
this.executorChanges.set(operation);
|
|
1973
|
+
return { handled, shouldPreventDefault, targetIndex };
|
|
409
1974
|
}
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
1975
|
+
/**
|
|
1976
|
+
* Handle Space key selection
|
|
1977
|
+
*/
|
|
1978
|
+
handleSpaceKeySelection(node, event) {
|
|
1979
|
+
if (!this.canSelectNode(node))
|
|
1980
|
+
return;
|
|
1981
|
+
const newValue = !this.getNodeSelected(node);
|
|
1982
|
+
this.setNodeSelected(node, newValue);
|
|
1983
|
+
this.setNodeIndeterminate(node, false);
|
|
1984
|
+
const children = this.getNodeChildren(node);
|
|
1985
|
+
if (this.cascadesToChildren() && children && !this.isLeafOnlyMode()) {
|
|
1986
|
+
this.treeService.selectAllChildren(children, newValue, this.selectedField(), this.indeterminateField(), this.childrenField());
|
|
413
1987
|
}
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
const nodesToExpand = [];
|
|
417
|
-
for (const valueField of valueFields) {
|
|
418
|
-
const foundNodes = this.findNodesByValueField(this.itemsSignal(), valueField);
|
|
419
|
-
nodesToExpand.push(...foundNodes);
|
|
1988
|
+
if (this.hasIntermediateState() && !this.isLeafOnlyMode()) {
|
|
1989
|
+
this.treeService.updateParentStates(this.nodes(), node, this.hasIntermediateState(), this.idField(), this.childrenField(), this.selectedField(), this.indeterminateField());
|
|
420
1990
|
}
|
|
421
|
-
|
|
1991
|
+
this.refreshNodes();
|
|
1992
|
+
this.onNodeSelect.emit({
|
|
1993
|
+
component: this,
|
|
1994
|
+
node,
|
|
1995
|
+
nativeEvent: event,
|
|
1996
|
+
isUserInteraction: true,
|
|
1997
|
+
});
|
|
1998
|
+
this.emitSelectionChange();
|
|
422
1999
|
}
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
2000
|
+
/**
|
|
2001
|
+
* Handle Enter key selection
|
|
2002
|
+
*/
|
|
2003
|
+
handleEnterKeySelection(node, event) {
|
|
2004
|
+
if (!this.canSelectNode(node))
|
|
2005
|
+
return;
|
|
2006
|
+
const mode = this.selectMode();
|
|
2007
|
+
this.treeService.deselectAllNodes(this.nodes(), this.selectedField(), this.indeterminateField(), this.childrenField());
|
|
2008
|
+
this.setNodeSelected(node, true);
|
|
2009
|
+
this.setNodeIndeterminate(node, false);
|
|
2010
|
+
if (mode === 'multiple') {
|
|
2011
|
+
const children = this.getNodeChildren(node);
|
|
2012
|
+
if (this.cascadesToChildren() && children && !this.isLeafOnlyMode()) {
|
|
2013
|
+
this.treeService.selectAllChildren(children, true, this.selectedField(), this.indeterminateField(), this.childrenField());
|
|
428
2014
|
}
|
|
429
|
-
if (
|
|
430
|
-
|
|
2015
|
+
if (this.hasIntermediateState() && !this.isLeafOnlyMode()) {
|
|
2016
|
+
this.treeService.updateParentStates(this.nodes(), node, this.hasIntermediateState(), this.idField(), this.childrenField(), this.selectedField(), this.indeterminateField());
|
|
431
2017
|
}
|
|
432
2018
|
}
|
|
433
|
-
|
|
2019
|
+
this.refreshNodes();
|
|
2020
|
+
this.onNodeSelect.emit({
|
|
2021
|
+
component: this,
|
|
2022
|
+
node,
|
|
2023
|
+
nativeEvent: event,
|
|
2024
|
+
isUserInteraction: true,
|
|
2025
|
+
});
|
|
2026
|
+
this.emitSelectionChange();
|
|
2027
|
+
}
|
|
2028
|
+
/**
|
|
2029
|
+
* Handle Ctrl/Cmd + Enter key selection
|
|
2030
|
+
*/
|
|
2031
|
+
handleCtrlEnterSelection(node, event) {
|
|
2032
|
+
if (!this.canSelectNode(node))
|
|
2033
|
+
return;
|
|
2034
|
+
const newValue = !this.getNodeSelected(node);
|
|
2035
|
+
this.setNodeSelected(node, newValue);
|
|
2036
|
+
this.setNodeIndeterminate(node, false);
|
|
2037
|
+
const children = this.getNodeChildren(node);
|
|
2038
|
+
if (this.cascadesToChildren() && children && !this.isLeafOnlyMode()) {
|
|
2039
|
+
this.treeService.selectAllChildren(children, newValue, this.selectedField(), this.indeterminateField(), this.childrenField());
|
|
2040
|
+
}
|
|
2041
|
+
if (this.hasIntermediateState() && !this.isLeafOnlyMode()) {
|
|
2042
|
+
this.treeService.updateParentStates(this.nodes(), node, this.hasIntermediateState(), this.idField(), this.childrenField(), this.selectedField(), this.indeterminateField());
|
|
2043
|
+
}
|
|
2044
|
+
this.refreshNodes();
|
|
2045
|
+
this.onNodeSelect.emit({
|
|
2046
|
+
component: this,
|
|
2047
|
+
node,
|
|
2048
|
+
nativeEvent: event,
|
|
2049
|
+
isUserInteraction: true,
|
|
2050
|
+
});
|
|
2051
|
+
this.emitSelectionChange();
|
|
2052
|
+
}
|
|
2053
|
+
/**
|
|
2054
|
+
* Type guard to check if value is an Event
|
|
2055
|
+
*/
|
|
2056
|
+
isEvent(value) {
|
|
2057
|
+
return value instanceof Event;
|
|
434
2058
|
}
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
2059
|
+
/**
|
|
2060
|
+
* Handle errors consistently
|
|
2061
|
+
*/
|
|
2062
|
+
handleError(message, error) {
|
|
2063
|
+
if (error instanceof Error) {
|
|
2064
|
+
console.error(`${message}:`, error.message);
|
|
440
2065
|
}
|
|
441
|
-
|
|
442
|
-
|
|
2066
|
+
else {
|
|
2067
|
+
console.error(`${message}:`, error);
|
|
443
2068
|
}
|
|
444
2069
|
}
|
|
445
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
446
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.12", type: AXTreeViewComponent, isStandalone: true, selector: "ax-tree-view", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, showCheckbox: { classPropertyName: "showCheckbox", publicName: "showCheckbox", isSignal: true, isRequired: false, transformFunction: null }, hasCheckboxField: { classPropertyName: "hasCheckboxField", publicName: "hasCheckboxField", isSignal: true, isRequired: false, transformFunction: null }, selectionMode: { classPropertyName: "selectionMode", publicName: "selectionMode", isSignal: true, isRequired: false, transformFunction: null }, selectionBehavior: { classPropertyName: "selectionBehavior", publicName: "selectionBehavior", isSignal: true, isRequired: false, transformFunction: null }, selectionScope: { classPropertyName: "selectionScope", publicName: "selectionScope", isSignal: true, isRequired: false, transformFunction: null }, focusNodeEnabled: { classPropertyName: "focusNodeEnabled", publicName: "focusNodeEnabled", isSignal: true, isRequired: false, transformFunction: null }, valueField: { classPropertyName: "valueField", publicName: "valueField", isSignal: true, isRequired: false, transformFunction: null }, textField: { classPropertyName: "textField", publicName: "textField", isSignal: true, isRequired: false, transformFunction: null }, visibleField: { classPropertyName: "visibleField", publicName: "visibleField", isSignal: true, isRequired: false, transformFunction: null }, disableField: { classPropertyName: "disableField", publicName: "disableField", isSignal: true, isRequired: false, transformFunction: null }, hasChildField: { classPropertyName: "hasChildField", publicName: "hasChildField", isSignal: true, isRequired: false, transformFunction: null }, selectedField: { classPropertyName: "selectedField", publicName: "selectedField", isSignal: true, isRequired: false, transformFunction: null }, expandedField: { classPropertyName: "expandedField", publicName: "expandedField", isSignal: true, isRequired: false, transformFunction: null }, tooltipField: { classPropertyName: "tooltipField", publicName: "tooltipField", isSignal: true, isRequired: false, transformFunction: null }, childrenField: { classPropertyName: "childrenField", publicName: "childrenField", isSignal: true, isRequired: false, transformFunction: null }, activeField: { classPropertyName: "activeField", publicName: "activeField", isSignal: true, isRequired: false, transformFunction: null }, indeterminateField: { classPropertyName: "indeterminateField", publicName: "indeterminateField", isSignal: true, isRequired: false, transformFunction: null }, parentField: { classPropertyName: "parentField", publicName: "parentField", isSignal: true, isRequired: false, transformFunction: null }, iconField: { classPropertyName: "iconField", publicName: "iconField", isSignal: true, isRequired: false, transformFunction: null }, toggleIcons: { classPropertyName: "toggleIcons", publicName: "toggleIcons", isSignal: true, isRequired: false, transformFunction: null }, look: { classPropertyName: "look", publicName: "look", isSignal: true, isRequired: false, transformFunction: null }, showEmptyNodeMassage: { classPropertyName: "showEmptyNodeMassage", publicName: "showEmptyNodeMassage", isSignal: true, isRequired: false, transformFunction: null }, itemTemplate: { classPropertyName: "itemTemplate", publicName: "itemTemplate", isSignal: false, isRequired: false, transformFunction: null }, emptyTemplate: { classPropertyName: "emptyTemplate", publicName: "emptyTemplate", isSignal: false, isRequired: false, transformFunction: null }, expandOn: { classPropertyName: "expandOn", publicName: "expandOn", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onSelectionChanged: "onSelectionChanged", onItemSelectedChanged: "onItemSelectedChanged", onNodeClick: "onNodeClick", onCollapsedChanged: "onCollapsedChanged", onNodedbClick: "onNodedbClick" }, host: { properties: { "class": "this.__hostClass" } }, providers: [
|
|
447
|
-
{ provide: AXComponent, useExisting: AXTreeViewComponent },
|
|
448
|
-
{ provide: AXTreeViewBase, useExisting: AXTreeViewComponent },
|
|
449
|
-
], usesInheritance: true, ngImport: i0, template: "@if (resolvedItems?.length) {\n @for (node of resolvedItems; track $index) {\n <ng-container [ngTemplateOutlet]=\"recursion\" [ngTemplateOutletContext]=\"{ $implicit: node }\"></ng-container>\n }\n} @else {\n <ng-container [ngTemplateOutlet]=\"emptyTemplate || empty\"></ng-container>\n}\n\n<ng-template #recursion let-item>\n @if (item[visibleField()] !== false) {\n <ax-tree-view-item\n [item]=\"item\"\n [isExpanded]=\"item[expandedField()]\"\n [(isActive)]=\"item[activeField()]\"\n [isLoading]=\"isNodeLoading(item[valueField()])\"\n [executorChanges]=\"executorChanges()\"\n >\n @if (\n (showCheckbox() && selectionScope() === 'all' && item[hasCheckboxField()] !== false) ||\n (showCheckbox() &&\n selectionScope() === 'parent' &&\n item[childrenField()]?.length &&\n item[hasCheckboxField()]) !== false ||\n (showCheckbox() &&\n selectionScope() === 'children' &&\n !item[childrenField()]?.length &&\n item[hasCheckboxField()] !== false)\n ) {\n <ax-check-box\n [disabled]=\"item[disableField()]\"\n [indeterminate]=\"item[indeterminateField()]\"\n [(ngModel)]=\"item[selectedField()]\"\n (onValueChanged)=\"handleNodeSelectionClick($event, item)\"\n ></ax-check-box>\n }\n @if (item[iconField()]) {\n <ax-prefix>\n <ax-icon [icon]=\"item[iconField()]\"></ax-icon>\n </ax-prefix>\n }\n @if (item[textField()]) {\n <ax-text>{{ item[textField()] }}</ax-text>\n }\n\n @for (child of item?.[childrenField()]; track $index) {\n <ng-container [ngTemplateOutlet]=\"recursion\" [ngTemplateOutletContext]=\"{ $implicit: child }\"></ng-container>\n }\n </ax-tree-view-item>\n }\n</ng-template>\n\n<ng-template #empty>\n {{ '@acorex:common.general.no-result-found' | translate | async }}\n</ng-template>\n", styles: ["ax-tree-view{--ax-comp-tree-view-arrow-size: .875rem;--ax-comp-tree-view-text-size: .875rem;--ax-comp-tree-view-active-bg-color: var(--ax-sys-color-primary-surface);--ax-comp-tree-view-active-text-color: var(--ax-sys-color-on-primary-surface);--ax-comp-tree-view-hover-bg-color: var(--ax-sys-color-dark-surface);--ax-comp-tree-view-indicator-size: 2px}ax-tree-view:has(>ax-tree-view-item i) ax-tree-view-item .ax-tree-view-container:not(:has(i)){padding-inline-start:1.5rem}ax-tree-view.ax-look-with-line ax-tree-view-item{position:relative}ax-tree-view.ax-look-with-line ax-tree-view-item:before{content:\"\";position:absolute;top:0;inset-inline-start:.625rem;width:1px;height:100%;background-color:#ccc}ax-tree-view.ax-look-with-line ax-tree-view-item .ax-tree-view-container{padding-inline-start:1.25rem}ax-tree-view.ax-look-with-line ax-tree-view-item .ax-tree-view-container .ax-tree-view-icon-container{padding:.25rem}ax-tree-view ax-tree-view-item ax-check-box{margin-inline-start:.5rem}ax-tree-view ax-tree-view-item .ax-tree-view-container{display:flex;align-items:center;margin-bottom:.125rem}ax-tree-view ax-tree-view-item .ax-tree-view-container ax-text{font-size:var(--ax-comp-tree-view-text-size)}ax-tree-view ax-tree-view-item .ax-tree-view-container{cursor:pointer}ax-tree-view ax-tree-view-item .ax-tree-view-container .ax-checkbox-end-side{display:none!important}ax-tree-view ax-tree-view-item .ax-tree-view-container .ax-tree-view-arrow{font-size:var(--ax-comp-tree-view-arrow-size)!important}ax-tree-view ax-tree-view-item .ax-tree-view-container ax-suffix:empty{display:none}ax-tree-view ax-tree-view-item .ax-tree-view-container .ax-tree-view-icon-container{width:1.5rem;height:1.5rem;display:flex;align-items:center;justify-content:center}ax-tree-view ax-tree-view-item .ax-tree-view-container .ax-tree-view-items{display:flex;align-items:center}ax-tree-view ax-tree-view-item .ax-tree-view-container .ax-tree-view-items .ax-tree-view-items-prefix{display:flex;align-items:center;gap:.5rem;padding:.25rem .5rem;border-radius:.25rem;overflow-x:auto;margin-inline-start:.25rem}ax-tree-view ax-tree-view-item .ax-tree-view-container .ax-tree-view-items .ax-tree-view-items-prefix.ax-noselect-tree-view{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}ax-tree-view ax-tree-view-item .ax-tree-view-container .ax-tree-view-items .ax-tree-view-items-prefix.ax-state-tree-view-active{background-color:rgba(var(--ax-comp-tree-view-active-bg-color));color:rgba(var(--ax-comp-tree-view-active-text-color))}ax-tree-view ax-tree-view-item .ax-tree-view-container .ax-tree-view-items .ax-tree-view-items-prefix:hover:not(.ax-state-tree-view-active){background-color:rgba(var(--ax-comp-tree-view-hover-bg-color))}ax-tree-view ax-tree-view-item .ax-tree-view-child{padding-inline-start:1rem}ax-tree-view ax-tree-view-item .ax-tree-view-child .ax-tree-view-container:not(:has(.ax-tree-view-icon-container i)){padding-inline-start:1.5rem}ax-tree-view ax-tree-view-item .ax-tree-view-child.ax-tree-view-empty-child{padding-inline-start:2rem}ax-tree-view ax-tree-view-item .ax-state-disabled{cursor:not-allowed!important;opacity:.5!important}@keyframes fadeSlideIn{0%{opacity:0;max-height:0}to{opacity:1;max-height:50px}}@keyframes fadeSlideOut{0%{opacity:1;max-height:50px}to{opacity:0;max-height:0}}ax-tree-view ax-tree-view-item .ax-fade-slide-in{animation:fadeSlideIn var(--ax-sys-transition-duration) var(--ax-sys-transition-timing-function)}ax-tree-view ax-tree-view-item .ax-fade-slide-out{animation:fadeSlideOut var(--ax-sys-transition-duration) var(--ax-sys-transition-timing-function)}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: AXTreeViewItemComponent, selector: "ax-tree-view-item", inputs: ["item", "isExpanded", "isActive", "isLoading", "executorChanges"], outputs: ["isExpandedChange", "isActiveChange"] }, { kind: "component", type: AXCheckBoxComponent, selector: "ax-check-box", inputs: ["disabled", "tabIndex", "readonly", "color", "value", "name", "id", "isLoading", "indeterminate"], outputs: ["onBlur", "onFocus", "valueChange", "onValueChanged"] }, { 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: "component", type: 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: "component", type: AXDecoratorIconComponent, selector: "ax-icon", inputs: ["icon"] }, { kind: "pipe", type: AXTranslatorPipe, name: "translate" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
|
|
2070
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: AXTreeViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2071
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.9", type: AXTreeViewComponent, isStandalone: true, selector: "ax-tree-view", inputs: { datasource: { classPropertyName: "datasource", publicName: "datasource", isSignal: true, isRequired: true, transformFunction: null }, selectMode: { classPropertyName: "selectMode", publicName: "selectMode", isSignal: true, isRequired: false, transformFunction: null }, showCheckbox: { classPropertyName: "showCheckbox", publicName: "showCheckbox", isSignal: true, isRequired: false, transformFunction: null }, selectionBehavior: { classPropertyName: "selectionBehavior", publicName: "selectionBehavior", isSignal: true, isRequired: false, transformFunction: null }, checkOnClick: { classPropertyName: "checkOnClick", publicName: "checkOnClick", isSignal: true, isRequired: false, transformFunction: null }, dragMode: { classPropertyName: "dragMode", publicName: "dragMode", isSignal: true, isRequired: false, transformFunction: null }, dragOperationType: { classPropertyName: "dragOperationType", publicName: "dragOperationType", isSignal: true, isRequired: false, transformFunction: null }, showIcons: { classPropertyName: "showIcons", publicName: "showIcons", isSignal: true, isRequired: false, transformFunction: null }, showChildrenBadge: { classPropertyName: "showChildrenBadge", publicName: "showChildrenBadge", isSignal: true, isRequired: false, transformFunction: null }, expandedIcon: { classPropertyName: "expandedIcon", publicName: "expandedIcon", isSignal: true, isRequired: false, transformFunction: null }, collapsedIcon: { classPropertyName: "collapsedIcon", publicName: "collapsedIcon", isSignal: true, isRequired: false, transformFunction: null }, indentSize: { classPropertyName: "indentSize", publicName: "indentSize", isSignal: true, isRequired: false, transformFunction: null }, look: { classPropertyName: "look", publicName: "look", isSignal: true, isRequired: false, transformFunction: null }, nodeTemplate: { classPropertyName: "nodeTemplate", publicName: "nodeTemplate", isSignal: true, isRequired: false, transformFunction: null }, idField: { classPropertyName: "idField", publicName: "idField", isSignal: true, isRequired: false, transformFunction: null }, titleField: { classPropertyName: "titleField", publicName: "titleField", isSignal: true, isRequired: false, transformFunction: null }, tooltipField: { classPropertyName: "tooltipField", publicName: "tooltipField", isSignal: true, isRequired: false, transformFunction: null }, iconField: { classPropertyName: "iconField", publicName: "iconField", isSignal: true, isRequired: false, transformFunction: null }, expandedField: { classPropertyName: "expandedField", publicName: "expandedField", isSignal: true, isRequired: false, transformFunction: null }, selectedField: { classPropertyName: "selectedField", publicName: "selectedField", isSignal: true, isRequired: false, transformFunction: null }, indeterminateField: { classPropertyName: "indeterminateField", publicName: "indeterminateField", isSignal: true, isRequired: false, transformFunction: null }, disabledField: { classPropertyName: "disabledField", publicName: "disabledField", isSignal: true, isRequired: false, transformFunction: null }, hiddenField: { classPropertyName: "hiddenField", publicName: "hiddenField", isSignal: true, isRequired: false, transformFunction: null }, childrenField: { classPropertyName: "childrenField", publicName: "childrenField", isSignal: true, isRequired: false, transformFunction: null }, childrenCountField: { classPropertyName: "childrenCountField", publicName: "childrenCountField", isSignal: true, isRequired: false, transformFunction: null }, dataField: { classPropertyName: "dataField", publicName: "dataField", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { datasource: "datasourceChange", onBeforeDrop: "onBeforeDrop", onNodeToggle: "onNodeToggle", onNodeSelect: "onNodeSelect", onSelectionChange: "onSelectionChange", onOrderChange: "onOrderChange", onMoveChange: "onMoveChange", onItemsChange: "onItemsChange" }, host: { attributes: { "role": "tree", "tabindex": "0" }, listeners: { "keydown": "handleKeyDown($event)", "focus": "onTreeFocus($event)", "blur": "onTreeBlur($event)" }, properties: { "class.ax-tree-view-default": "look() === 'default'", "class.ax-tree-view-card": "look() === 'card'", "class.ax-tree-view-with-line": "look() === 'with-line'", "class.ax-tree-view-rtl": "isRtl", "style.--ax-tree-view-indent-size": "indentSize() + 'px'", "style.--ax-tree-view-line-offset": "(indentSize() / 2) + 'px'", "attr.aria-label": "\"Tree navigation\"" }, classAttribute: "ax-tree-view" }, providers: [AXTreeViewService], ngImport: i0, template: "<!-- Root drop list -->\n<div\n axFocusTrap\n [axDropList]=\"dragMode() !== 'none'\"\n [sortingDisabled]=\"false\"\n [id]=\"getListId()\"\n [attr.data-node-id]=\"null\"\n dropListGroup=\"ax-tree-view-nodes\"\n (dropListDropped)=\"onDrop($event)\"\n class=\"ax-tree-view-drop-list\"\n [class.ax-tree-view-card]=\"look() === 'card'\"\n [class.ax-tree-view-with-lines]=\"look() === 'with-line'\"\n role=\"group\"\n>\n @for (node of nodes(); track getNodeId(node)) {\n @if (getNodeHidden(node) !== true) {\n <div\n [axDrag]=\"dragMode() !== 'none'\"\n [dragDisabled]=\"getNodeDisabled(node)\"\n [dragData]=\"node\"\n class=\"ax-tree-view-node\"\n [class.ax-tree-view-node-selected]=\"getNodeSelected(node)\"\n [class.ax-tree-view-node-disabled]=\"getNodeDisabled(node)\"\n [class.ax-tree-view-node-loading]=\"getNodeLoading(node)\"\n role=\"treeitem\"\n [attr.aria-level]=\"getNodeAriaLevel(0)\"\n [attr.aria-expanded]=\"getNodeAriaExpanded(node)\"\n [attr.aria-selected]=\"getNodeAriaSelected(node)\"\n [attr.aria-disabled]=\"getNodeDisabled(node) || (isLeafOnlyMode() && !isLeafNode(node)) ? 'true' : null\"\n >\n <div\n class=\"ax-tree-view-node-content\"\n [axDropList]=\"dragMode() !== 'none'\"\n [id]=\"'ax-tree-view-node-drop-' + getNodeId(node)\"\n [attr.data-node-id]=\"getNodeId(node)\"\n [attr.data-tree-node-id]=\"getNodeId(node)\"\n [attr.data-drop-type]=\"'onto-node'\"\n dropListGroup=\"ax-tree-view-nodes\"\n (dropListDropped)=\"onDropOntoNode($event, node)\"\n (click)=\"\n (selectMode() === 'single' || (selectMode() === 'multiple' && checkOnClick())) && onNodeClick(node, $event)\n \"\n (focus)=\"onNodeFocus(getNodeId(node))\"\n [tabindex]=\"isNodeFocused(getNodeId(node)) ? 0 : -1\"\n >\n @if (dragMode() === 'handler') {\n <span class=\"ax-tree-view-drag-handle\" axDragHandle title=\"Drag to reorder\"> \u22EE\u22EE </span>\n }\n <ax-button\n class=\"ax-tree-view-expand-toggle ax-sm\"\n (onClick)=\"toggleNode(node, $any($event))\"\n [class.ax-tree-view-has-children]=\"shouldShowExpandToggle(node)\"\n [class.ax-tree-view-expanded]=\"getNodeExpanded(node)\"\n [disabled]=\"getNodeDisabled(node) || getNodeLoading(node)\"\n [style.visibility]=\"shouldShowExpandToggle(node) ? 'visible' : 'hidden'\"\n >\n @if (getNodeLoading(node)) {\n <ax-icon>\n <i class=\"fa-solid fa-spinner fa-spin ax-tree-view-loading-spinner\"></i>\n </ax-icon>\n } @else {\n <ax-icon>\n <i\n [class]=\"getNodeExpanded(node) ? directionExpandedIcon() : directionCollapsedIcon()\"\n class=\"ax-tree-view-toggle-icon\"\n ></i>\n </ax-icon>\n }\n </ax-button>\n @if (nodeTemplate()) {\n <ng-container\n [ngTemplateOutlet]=\"nodeTemplate()!\"\n [ngTemplateOutletContext]=\"getTemplateContext(node, 0)\"\n ></ng-container>\n } @else {\n @if (shouldShowCheckboxForNode(node)) {\n <ax-check-box\n class=\"ax-tree-view-checkbox\"\n [ngModel]=\"getNodeIndeterminate(node) ? null : getNodeSelected(node) || false\"\n [indeterminate]=\"getNodeIndeterminate(node) || false\"\n (onValueChanged)=\"toggleSelection(node, $event)\"\n ></ax-check-box>\n }\n @if (showIcons() && getNodeIcon(node)) {\n <i [class]=\"getNodeIcon(node)\" class=\"ax-tree-view-node-icon\"></i>\n }\n <span\n class=\"ax-tree-view-node-label\"\n [axTooltip]=\"getNodeTooltip(node) || ''\"\n [axTooltipDisabled]=\"!getNodeTooltip(node)\"\n [axTooltipPlacement]=\"'top-start'\"\n >\n {{ getNodeTitle(node) }}\n </span>\n @if (\n showChildrenBadge() &&\n (getNodeChildrenCount(node) || (getNodeChildren(node) && getNodeChildren(node)!.length > 0))\n ) {\n <span class=\"ax-tree-view-node-badge\">\n <ax-badge\n class=\"ax-tree-view-children-badge\"\n [text]=\"(getNodeChildrenCount(node) ?? getNodeChildren(node)?.length ?? 0).toString()\"\n ></ax-badge>\n </span>\n }\n }\n </div>\n </div>\n @if (getNodeExpanded(node) && getNodeChildren(node) && getNodeChildren(node)!.length > 0) {\n <div class=\"ax-tree-view-children\" role=\"group\">\n <ng-container\n [ngTemplateOutlet]=\"childrenList\"\n [ngTemplateOutletContext]=\"{ children: getNodeChildren(node), parent: node, level: 1 }\"\n ></ng-container>\n </div>\n }\n }\n }\n</div>\n\n<!-- Recursive children template -->\n<ng-template #childrenList let-children=\"children\" let-parent=\"parent\" let-level=\"level\">\n <div\n [axDropList]=\"dragMode() !== 'none'\"\n [id]=\"getListId(parent)\"\n [attr.data-node-id]=\"getNodeId(parent)\"\n dropListGroup=\"ax-tree-view-nodes\"\n (dropListDropped)=\"onDrop($event, parent)\"\n class=\"ax-tree-view-drop-list\"\n role=\"group\"\n >\n @for (node of children; track getNodeId(node)) {\n @if (getNodeHidden(node) !== true) {\n <div\n [axDrag]=\"dragMode() !== 'none'\"\n [dragDisabled]=\"getNodeDisabled(node)\"\n [dragData]=\"node\"\n class=\"ax-tree-view-node\"\n [class.ax-tree-view-node-selected]=\"getNodeSelected(node)\"\n [class.ax-tree-view-node-disabled]=\"getNodeDisabled(node)\"\n [class.ax-tree-view-node-loading]=\"getNodeLoading(node)\"\n [class.ax-tree-view-node-focused]=\"isNodeFocused(getNodeId(node))\"\n role=\"treeitem\"\n [attr.aria-level]=\"getNodeAriaLevel(level)\"\n [attr.aria-expanded]=\"getNodeAriaExpanded(node)\"\n [attr.aria-selected]=\"getNodeAriaSelected(node)\"\n [attr.aria-disabled]=\"getNodeDisabled(node) ? 'true' : null\"\n >\n <div\n class=\"ax-tree-view-node-content\"\n [axDropList]=\"dragMode() !== 'none'\"\n [id]=\"'ax-tree-view-node-drop-' + getNodeId(node)\"\n [attr.data-node-id]=\"getNodeId(node)\"\n [attr.data-tree-node-id]=\"getNodeId(node)\"\n [attr.data-drop-type]=\"'onto-node'\"\n dropListGroup=\"ax-tree-view-nodes\"\n (dropListDropped)=\"onDropOntoNode($event, node)\"\n (click)=\"\n (selectMode() === 'single' || (selectMode() === 'multiple' && checkOnClick())) &&\n onNodeClick(node, $event)\n \"\n (focus)=\"onNodeFocus(getNodeId(node))\"\n (blur)=\"focusedNodeId.set(null)\"\n [tabindex]=\"isNodeFocused(getNodeId(node)) ? 0 : -1\"\n >\n @if (dragMode() === 'handler') {\n <span class=\"ax-tree-view-drag-handle\" axDragHandle title=\"Drag to reorder\"> \u22EE\u22EE </span>\n }\n <ax-button\n class=\"ax-tree-view-expand-toggle ax-sm\"\n (onClick)=\"toggleNode(node, $any($event))\"\n [class.ax-tree-view-has-children]=\"shouldShowExpandToggle(node)\"\n [class.ax-tree-view-expanded]=\"getNodeExpanded(node)\"\n [disabled]=\"getNodeDisabled(node) || getNodeLoading(node)\"\n [style.visibility]=\"shouldShowExpandToggle(node) ? 'visible' : 'hidden'\"\n >\n @if (getNodeLoading(node)) {\n <ax-icon>\n <i class=\"fa-solid fa-spinner fa-spin ax-tree-view-loading-spinner\"></i>\n </ax-icon>\n } @else {\n <ax-icon>\n <i\n [class]=\"getNodeExpanded(node) ? directionExpandedIcon() : directionCollapsedIcon()\"\n class=\"ax-tree-view-toggle-icon\"\n ></i>\n </ax-icon>\n }\n </ax-button>\n\n @if (nodeTemplate()) {\n <ng-container\n [ngTemplateOutlet]=\"nodeTemplate()!\"\n [ngTemplateOutletContext]=\"getTemplateContext(node, level)\"\n ></ng-container>\n } @else {\n @if (shouldShowCheckboxForNode(node)) {\n <ax-check-box\n class=\"ax-tree-view-checkbox\"\n [ngModel]=\"getNodeIndeterminate(node) ? null : getNodeSelected(node) || false\"\n [indeterminate]=\"getNodeIndeterminate(node) || false\"\n (onValueChanged)=\"toggleSelection(node, $event)\"\n ></ax-check-box>\n }\n @if (showIcons() && getNodeIcon(node)) {\n <i [class]=\"getNodeIcon(node)\" class=\"ax-tree-view-node-icon\"></i>\n }\n <span\n class=\"ax-tree-view-node-label\"\n [axTooltip]=\"getNodeTooltip(node) || ''\"\n [axTooltipDisabled]=\"!getNodeTooltip(node)\"\n [axTooltipPlacement]=\"'top-start'\"\n >\n {{ getNodeTitle(node) }}\n </span>\n @if (\n showChildrenBadge() &&\n (getNodeChildrenCount(node) || (getNodeChildren(node) && getNodeChildren(node)!.length > 0))\n ) {\n <span class=\"ax-tree-view-node-badge\">\n <ax-badge\n class=\"ax-tree-view-children-badge\"\n [text]=\"(getNodeChildrenCount(node) ?? getNodeChildren(node)?.length ?? 0).toString()\"\n ></ax-badge>\n </span>\n }\n }\n </div>\n </div>\n @if (getNodeExpanded(node) && getNodeChildren(node) && getNodeChildren(node)!.length > 0) {\n <div class=\"ax-tree-view-children\" role=\"group\">\n <ng-container\n [ngTemplateOutlet]=\"childrenList\"\n [ngTemplateOutletContext]=\"{ children: getNodeChildren(node), parent: node, level: level + 1 }\"\n ></ng-container>\n </div>\n }\n }\n }\n </div>\n</ng-template>\n", styles: [".ax-tree-view{display:block;width:100%;--ax-comp-tree-view-indent-size: 12px;--ax-comp-tree-view-node-hover-bg: rgba(var(--ax-sys-color-on-lightest-surface), .04);--ax-comp-tree-view-node-selected-bg: rgba(var(--ax-sys-color-primary-500), .12);--ax-comp-tree-view-node-border-radius: 6px;--ax-comp-tree-view-node-margin: .25rem;--ax-comp-tree-view-line-color: rgba(var(--ax-sys-color-on-lightest-surface), .15);--ax-comp-tree-view-drag-preview-opacity: .9;--ax-comp-tree-view-drag-placeholder-bg: rgba(var(--ax-sys-color-on-lightest-surface), .02);--ax-comp-tree-view-drop-active-bg: rgba(var(--ax-sys-color-primary-500), .08);--ax-comp-tree-view-drop-active-outline: rgba(var(--ax-sys-color-primary-500), .3);--ax-comp-tree-view-content-padding: 0;--ax-comp-tree-view-content-gap: .5rem;--ax-comp-tree-view-drop-list-min-height: 2rem;--ax-comp-tree-view-drag-handle-padding: .25rem;--ax-comp-tree-view-badge-padding: .25rem;--ax-comp-tree-view-expand-toggle-padding: .25rem;--ax-comp-tree-view-outline-offset: 2px;--ax-comp-tree-view-outline-offset-negative: -2px}.ax-tree-view-drop-list{min-height:var(--ax-comp-tree-view-drop-list-min-height)}.ax-tree-view-node{position:relative;margin:var(--ax-comp-tree-view-node-margin) 0;border-radius:var(--ax-comp-tree-view-node-border-radius);cursor:move}.ax-tree-view-node:hover:not(.ax-dragging){background:var(--ax-comp-tree-view-node-hover-bg)}.ax-tree-view-node.ax-tree-view-node-selected{background:var(--ax-comp-tree-view-node-selected-bg)}.ax-tree-view-node.ax-dragging{opacity:var(--ax-comp-tree-view-drag-placeholder-opacity);cursor:grabbing!important}.ax-tree-view-node.ax-drag-placeholder{background:var(--ax-comp-tree-view-drag-placeholder-bg)}.ax-drag-preview{opacity:var(--ax-comp-tree-view-drag-preview-opacity)!important;box-shadow:0 4px 12px rgba(var(--ax-sys-color-on-lightest-surface),.2)!important;cursor:grabbing!important;border:2px dashed currentColor!important}.ax-tree-view-node-content.ax-drop-list-sorting-active{background:var(--ax-comp-tree-view-drop-active-bg);border-radius:var(--ax-comp-tree-view-node-border-radius);outline:2px dashed var(--ax-comp-tree-view-drop-active-outline);outline-offset:var(--ax-comp-tree-view-outline-offset-negative)}.ax-tree-view-node-content{display:flex;align-items:center;gap:var(--ax-comp-tree-view-content-gap);padding:var(--ax-comp-tree-view-content-padding);cursor:pointer;-webkit-user-select:none;user-select:none;outline:none;border-radius:var(--ax-comp-tree-view-node-border-radius)}.ax-tree-view-node-content:focus{outline:none}.ax-tree-view-node-content:focus-visible{outline:2px solid rgba(var(--ax-sys-color-primary-500),.8);outline-offset:var(--ax-comp-tree-view-outline-offset);border-radius:var(--ax-comp-tree-view-node-border-radius)}.ax-tree-view-drag-handle{cursor:grab;opacity:.6;padding:var(--ax-comp-tree-view-drag-handle-padding);padding-inline-start:calc(var(--ax-comp-tree-view-drag-handle-padding) * 2)}.ax-tree-view-drag-handle:hover{opacity:1}.ax-tree-view-drag-handle:active{cursor:grabbing}.ax-tree-view-expand-toggle{background:none;border:none;cursor:pointer;padding:var(--ax-comp-tree-view-expand-toggle-padding);min-width:1.5rem;height:1.5rem}.ax-tree-view-expand-toggle:not(.ax-tree-view-has-children){opacity:0;pointer-events:none}.ax-tree-view-toggle-icon{font-size:.75rem}.ax-tree-view-node-icon{font-size:1.125rem;flex-shrink:0}.ax-tree-view-node-label{flex:1;font-size:.875rem;line-height:1rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ax-tree-view-node-badge{padding:var(--ax-comp-tree-view-badge-padding);padding-inline-end:calc(var(--ax-comp-tree-view-badge-padding) * 1.5)}.ax-tree-view-children{padding-inline-start:var(--ax-tree-view-indent-size, var(--ax-comp-tree-view-indent-size))}.ax-tree-view-node-disabled{opacity:.5;cursor:not-allowed;pointer-events:none}.ax-tree-view-node-loading{opacity:.7}.ax-tree-view-card .ax-tree-view-node{border:1px solid rgba(var(--ax-sys-color-border-lightest-surface),1)}.ax-tree-view-card .ax-tree-view-node.ax-tree-view-node-selected{border:1px solid rgba(var(--ax-sys-color-border-surface),1)}.ax-tree-view-with-lines .ax-tree-view-children{position:relative;padding-inline-start:var(--ax-tree-view-indent-size, var(--ax-comp-tree-view-indent-size))}.ax-tree-view-with-lines .ax-tree-view-children:before{content:\"\";position:absolute;inset-inline-start:var(--ax-tree-view-line-offset, calc(var(--ax-comp-tree-view-indent-size) / 2));top:0;height:calc(100% - .875rem);width:1px;background:var(--ax-tree-view-line-color, var(--ax-comp-tree-view-line-color))}.ax-tree-view-with-lines .ax-tree-view-node{position:relative}.ax-tree-view-with-lines .ax-tree-view-node:before{content:\"\";position:absolute;inset-inline-start:calc(-1 * var(--ax-tree-view-line-offset, calc(var(--ax-comp-tree-view-indent-size) / 2)));top:60%;width:var(--ax-tree-view-line-offset, calc(var(--ax-comp-tree-view-indent-size) / 2));height:1px;background:var(--ax-tree-view-line-color, var(--ax-comp-tree-view-line-color))}.ax-tree-view-with-lines>.ax-tree-view-drop-list>.ax-tree-view-node:before,.ax-tree-view-with-lines>.ax-tree-view-node:before{display:none}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: AXDragDirective, selector: "[axDrag]", inputs: ["axDrag", "dragData", "dragDisabled", "dragTransition", "dragElementClone", "dropZoneGroup", "dragStartDelay", "dragResetOnDblClick", "dragLockAxis", "dragClonedTemplate", "dragCursor", "dragBoundary", "dragTransitionDuration"], outputs: ["dragPositionChanged"] }, { kind: "directive", type: AXDragHandleDirective, selector: "[axDragHandle]" }, { kind: "directive", type: AXDropListDirective, selector: "[axDropList]", inputs: ["axDropList", "sortingDisabled", "dropListGroup", "dropListOrientation"], outputs: ["dropListDropped"], exportAs: ["axDropList"] }, { kind: "directive", type: AXFocusTrapDirective, selector: "[axFocusTrap]" }, { kind: "component", type: 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: AXCheckBoxComponent, selector: "ax-check-box", inputs: ["disabled", "tabIndex", "readonly", "color", "value", "name", "id", "isLoading", "indeterminate"], outputs: ["onBlur", "onFocus", "valueChange", "onValueChanged"] }, { kind: "component", type: AXBadgeComponent, selector: "ax-badge", inputs: ["color", "look", "text"] }, { kind: "component", type: AXDecoratorIconComponent, selector: "ax-icon", inputs: ["icon"] }, { kind: "directive", type: AXTooltipDirective, selector: "[axTooltip]", inputs: ["axTooltipDisabled", "axTooltip", "axTooltipContext", "axTooltipPlacement", "axTooltipOffsetX", "axTooltipOffsetY", "axTooltipOpenAfter", "axTooltipCloseAfter"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
|
|
450
2072
|
}
|
|
451
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
2073
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: AXTreeViewComponent, decorators: [{
|
|
452
2074
|
type: Component,
|
|
453
|
-
args: [{ selector: 'ax-tree-view', encapsulation: ViewEncapsulation.None,
|
|
2075
|
+
args: [{ selector: 'ax-tree-view', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, providers: [AXTreeViewService], imports: [
|
|
2076
|
+
CommonModule,
|
|
2077
|
+
FormsModule,
|
|
2078
|
+
AXDragDirective,
|
|
2079
|
+
AXDragHandleDirective,
|
|
2080
|
+
AXDropListDirective,
|
|
2081
|
+
AXFocusTrapDirective,
|
|
454
2082
|
NgTemplateOutlet,
|
|
455
|
-
|
|
2083
|
+
AXButtonComponent,
|
|
456
2084
|
AXCheckBoxComponent,
|
|
457
|
-
|
|
458
|
-
AXDecoratorGenericComponent,
|
|
2085
|
+
AXBadgeComponent,
|
|
459
2086
|
AXDecoratorIconComponent,
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
2087
|
+
AXTooltipDirective,
|
|
2088
|
+
], host: {
|
|
2089
|
+
class: 'ax-tree-view',
|
|
2090
|
+
'[class.ax-tree-view-default]': "look() === 'default'",
|
|
2091
|
+
'[class.ax-tree-view-card]': "look() === 'card'",
|
|
2092
|
+
'[class.ax-tree-view-with-line]': "look() === 'with-line'",
|
|
2093
|
+
'[class.ax-tree-view-rtl]': 'isRtl',
|
|
2094
|
+
'[style.--ax-tree-view-indent-size]': "indentSize() + 'px'",
|
|
2095
|
+
'[style.--ax-tree-view-line-offset]': "(indentSize() / 2) + 'px'",
|
|
2096
|
+
role: 'tree',
|
|
2097
|
+
'[attr.aria-label]': '"Tree navigation"',
|
|
2098
|
+
'(keydown)': 'handleKeyDown($event)',
|
|
2099
|
+
'(focus)': 'onTreeFocus($event)',
|
|
2100
|
+
'(blur)': 'onTreeBlur($event)',
|
|
2101
|
+
tabindex: '0',
|
|
2102
|
+
}, template: "<!-- Root drop list -->\n<div\n axFocusTrap\n [axDropList]=\"dragMode() !== 'none'\"\n [sortingDisabled]=\"false\"\n [id]=\"getListId()\"\n [attr.data-node-id]=\"null\"\n dropListGroup=\"ax-tree-view-nodes\"\n (dropListDropped)=\"onDrop($event)\"\n class=\"ax-tree-view-drop-list\"\n [class.ax-tree-view-card]=\"look() === 'card'\"\n [class.ax-tree-view-with-lines]=\"look() === 'with-line'\"\n role=\"group\"\n>\n @for (node of nodes(); track getNodeId(node)) {\n @if (getNodeHidden(node) !== true) {\n <div\n [axDrag]=\"dragMode() !== 'none'\"\n [dragDisabled]=\"getNodeDisabled(node)\"\n [dragData]=\"node\"\n class=\"ax-tree-view-node\"\n [class.ax-tree-view-node-selected]=\"getNodeSelected(node)\"\n [class.ax-tree-view-node-disabled]=\"getNodeDisabled(node)\"\n [class.ax-tree-view-node-loading]=\"getNodeLoading(node)\"\n role=\"treeitem\"\n [attr.aria-level]=\"getNodeAriaLevel(0)\"\n [attr.aria-expanded]=\"getNodeAriaExpanded(node)\"\n [attr.aria-selected]=\"getNodeAriaSelected(node)\"\n [attr.aria-disabled]=\"getNodeDisabled(node) || (isLeafOnlyMode() && !isLeafNode(node)) ? 'true' : null\"\n >\n <div\n class=\"ax-tree-view-node-content\"\n [axDropList]=\"dragMode() !== 'none'\"\n [id]=\"'ax-tree-view-node-drop-' + getNodeId(node)\"\n [attr.data-node-id]=\"getNodeId(node)\"\n [attr.data-tree-node-id]=\"getNodeId(node)\"\n [attr.data-drop-type]=\"'onto-node'\"\n dropListGroup=\"ax-tree-view-nodes\"\n (dropListDropped)=\"onDropOntoNode($event, node)\"\n (click)=\"\n (selectMode() === 'single' || (selectMode() === 'multiple' && checkOnClick())) && onNodeClick(node, $event)\n \"\n (focus)=\"onNodeFocus(getNodeId(node))\"\n [tabindex]=\"isNodeFocused(getNodeId(node)) ? 0 : -1\"\n >\n @if (dragMode() === 'handler') {\n <span class=\"ax-tree-view-drag-handle\" axDragHandle title=\"Drag to reorder\"> \u22EE\u22EE </span>\n }\n <ax-button\n class=\"ax-tree-view-expand-toggle ax-sm\"\n (onClick)=\"toggleNode(node, $any($event))\"\n [class.ax-tree-view-has-children]=\"shouldShowExpandToggle(node)\"\n [class.ax-tree-view-expanded]=\"getNodeExpanded(node)\"\n [disabled]=\"getNodeDisabled(node) || getNodeLoading(node)\"\n [style.visibility]=\"shouldShowExpandToggle(node) ? 'visible' : 'hidden'\"\n >\n @if (getNodeLoading(node)) {\n <ax-icon>\n <i class=\"fa-solid fa-spinner fa-spin ax-tree-view-loading-spinner\"></i>\n </ax-icon>\n } @else {\n <ax-icon>\n <i\n [class]=\"getNodeExpanded(node) ? directionExpandedIcon() : directionCollapsedIcon()\"\n class=\"ax-tree-view-toggle-icon\"\n ></i>\n </ax-icon>\n }\n </ax-button>\n @if (nodeTemplate()) {\n <ng-container\n [ngTemplateOutlet]=\"nodeTemplate()!\"\n [ngTemplateOutletContext]=\"getTemplateContext(node, 0)\"\n ></ng-container>\n } @else {\n @if (shouldShowCheckboxForNode(node)) {\n <ax-check-box\n class=\"ax-tree-view-checkbox\"\n [ngModel]=\"getNodeIndeterminate(node) ? null : getNodeSelected(node) || false\"\n [indeterminate]=\"getNodeIndeterminate(node) || false\"\n (onValueChanged)=\"toggleSelection(node, $event)\"\n ></ax-check-box>\n }\n @if (showIcons() && getNodeIcon(node)) {\n <i [class]=\"getNodeIcon(node)\" class=\"ax-tree-view-node-icon\"></i>\n }\n <span\n class=\"ax-tree-view-node-label\"\n [axTooltip]=\"getNodeTooltip(node) || ''\"\n [axTooltipDisabled]=\"!getNodeTooltip(node)\"\n [axTooltipPlacement]=\"'top-start'\"\n >\n {{ getNodeTitle(node) }}\n </span>\n @if (\n showChildrenBadge() &&\n (getNodeChildrenCount(node) || (getNodeChildren(node) && getNodeChildren(node)!.length > 0))\n ) {\n <span class=\"ax-tree-view-node-badge\">\n <ax-badge\n class=\"ax-tree-view-children-badge\"\n [text]=\"(getNodeChildrenCount(node) ?? getNodeChildren(node)?.length ?? 0).toString()\"\n ></ax-badge>\n </span>\n }\n }\n </div>\n </div>\n @if (getNodeExpanded(node) && getNodeChildren(node) && getNodeChildren(node)!.length > 0) {\n <div class=\"ax-tree-view-children\" role=\"group\">\n <ng-container\n [ngTemplateOutlet]=\"childrenList\"\n [ngTemplateOutletContext]=\"{ children: getNodeChildren(node), parent: node, level: 1 }\"\n ></ng-container>\n </div>\n }\n }\n }\n</div>\n\n<!-- Recursive children template -->\n<ng-template #childrenList let-children=\"children\" let-parent=\"parent\" let-level=\"level\">\n <div\n [axDropList]=\"dragMode() !== 'none'\"\n [id]=\"getListId(parent)\"\n [attr.data-node-id]=\"getNodeId(parent)\"\n dropListGroup=\"ax-tree-view-nodes\"\n (dropListDropped)=\"onDrop($event, parent)\"\n class=\"ax-tree-view-drop-list\"\n role=\"group\"\n >\n @for (node of children; track getNodeId(node)) {\n @if (getNodeHidden(node) !== true) {\n <div\n [axDrag]=\"dragMode() !== 'none'\"\n [dragDisabled]=\"getNodeDisabled(node)\"\n [dragData]=\"node\"\n class=\"ax-tree-view-node\"\n [class.ax-tree-view-node-selected]=\"getNodeSelected(node)\"\n [class.ax-tree-view-node-disabled]=\"getNodeDisabled(node)\"\n [class.ax-tree-view-node-loading]=\"getNodeLoading(node)\"\n [class.ax-tree-view-node-focused]=\"isNodeFocused(getNodeId(node))\"\n role=\"treeitem\"\n [attr.aria-level]=\"getNodeAriaLevel(level)\"\n [attr.aria-expanded]=\"getNodeAriaExpanded(node)\"\n [attr.aria-selected]=\"getNodeAriaSelected(node)\"\n [attr.aria-disabled]=\"getNodeDisabled(node) ? 'true' : null\"\n >\n <div\n class=\"ax-tree-view-node-content\"\n [axDropList]=\"dragMode() !== 'none'\"\n [id]=\"'ax-tree-view-node-drop-' + getNodeId(node)\"\n [attr.data-node-id]=\"getNodeId(node)\"\n [attr.data-tree-node-id]=\"getNodeId(node)\"\n [attr.data-drop-type]=\"'onto-node'\"\n dropListGroup=\"ax-tree-view-nodes\"\n (dropListDropped)=\"onDropOntoNode($event, node)\"\n (click)=\"\n (selectMode() === 'single' || (selectMode() === 'multiple' && checkOnClick())) &&\n onNodeClick(node, $event)\n \"\n (focus)=\"onNodeFocus(getNodeId(node))\"\n (blur)=\"focusedNodeId.set(null)\"\n [tabindex]=\"isNodeFocused(getNodeId(node)) ? 0 : -1\"\n >\n @if (dragMode() === 'handler') {\n <span class=\"ax-tree-view-drag-handle\" axDragHandle title=\"Drag to reorder\"> \u22EE\u22EE </span>\n }\n <ax-button\n class=\"ax-tree-view-expand-toggle ax-sm\"\n (onClick)=\"toggleNode(node, $any($event))\"\n [class.ax-tree-view-has-children]=\"shouldShowExpandToggle(node)\"\n [class.ax-tree-view-expanded]=\"getNodeExpanded(node)\"\n [disabled]=\"getNodeDisabled(node) || getNodeLoading(node)\"\n [style.visibility]=\"shouldShowExpandToggle(node) ? 'visible' : 'hidden'\"\n >\n @if (getNodeLoading(node)) {\n <ax-icon>\n <i class=\"fa-solid fa-spinner fa-spin ax-tree-view-loading-spinner\"></i>\n </ax-icon>\n } @else {\n <ax-icon>\n <i\n [class]=\"getNodeExpanded(node) ? directionExpandedIcon() : directionCollapsedIcon()\"\n class=\"ax-tree-view-toggle-icon\"\n ></i>\n </ax-icon>\n }\n </ax-button>\n\n @if (nodeTemplate()) {\n <ng-container\n [ngTemplateOutlet]=\"nodeTemplate()!\"\n [ngTemplateOutletContext]=\"getTemplateContext(node, level)\"\n ></ng-container>\n } @else {\n @if (shouldShowCheckboxForNode(node)) {\n <ax-check-box\n class=\"ax-tree-view-checkbox\"\n [ngModel]=\"getNodeIndeterminate(node) ? null : getNodeSelected(node) || false\"\n [indeterminate]=\"getNodeIndeterminate(node) || false\"\n (onValueChanged)=\"toggleSelection(node, $event)\"\n ></ax-check-box>\n }\n @if (showIcons() && getNodeIcon(node)) {\n <i [class]=\"getNodeIcon(node)\" class=\"ax-tree-view-node-icon\"></i>\n }\n <span\n class=\"ax-tree-view-node-label\"\n [axTooltip]=\"getNodeTooltip(node) || ''\"\n [axTooltipDisabled]=\"!getNodeTooltip(node)\"\n [axTooltipPlacement]=\"'top-start'\"\n >\n {{ getNodeTitle(node) }}\n </span>\n @if (\n showChildrenBadge() &&\n (getNodeChildrenCount(node) || (getNodeChildren(node) && getNodeChildren(node)!.length > 0))\n ) {\n <span class=\"ax-tree-view-node-badge\">\n <ax-badge\n class=\"ax-tree-view-children-badge\"\n [text]=\"(getNodeChildrenCount(node) ?? getNodeChildren(node)?.length ?? 0).toString()\"\n ></ax-badge>\n </span>\n }\n }\n </div>\n </div>\n @if (getNodeExpanded(node) && getNodeChildren(node) && getNodeChildren(node)!.length > 0) {\n <div class=\"ax-tree-view-children\" role=\"group\">\n <ng-container\n [ngTemplateOutlet]=\"childrenList\"\n [ngTemplateOutletContext]=\"{ children: getNodeChildren(node), parent: node, level: level + 1 }\"\n ></ng-container>\n </div>\n }\n }\n }\n </div>\n</ng-template>\n", styles: [".ax-tree-view{display:block;width:100%;--ax-comp-tree-view-indent-size: 12px;--ax-comp-tree-view-node-hover-bg: rgba(var(--ax-sys-color-on-lightest-surface), .04);--ax-comp-tree-view-node-selected-bg: rgba(var(--ax-sys-color-primary-500), .12);--ax-comp-tree-view-node-border-radius: 6px;--ax-comp-tree-view-node-margin: .25rem;--ax-comp-tree-view-line-color: rgba(var(--ax-sys-color-on-lightest-surface), .15);--ax-comp-tree-view-drag-preview-opacity: .9;--ax-comp-tree-view-drag-placeholder-bg: rgba(var(--ax-sys-color-on-lightest-surface), .02);--ax-comp-tree-view-drop-active-bg: rgba(var(--ax-sys-color-primary-500), .08);--ax-comp-tree-view-drop-active-outline: rgba(var(--ax-sys-color-primary-500), .3);--ax-comp-tree-view-content-padding: 0;--ax-comp-tree-view-content-gap: .5rem;--ax-comp-tree-view-drop-list-min-height: 2rem;--ax-comp-tree-view-drag-handle-padding: .25rem;--ax-comp-tree-view-badge-padding: .25rem;--ax-comp-tree-view-expand-toggle-padding: .25rem;--ax-comp-tree-view-outline-offset: 2px;--ax-comp-tree-view-outline-offset-negative: -2px}.ax-tree-view-drop-list{min-height:var(--ax-comp-tree-view-drop-list-min-height)}.ax-tree-view-node{position:relative;margin:var(--ax-comp-tree-view-node-margin) 0;border-radius:var(--ax-comp-tree-view-node-border-radius);cursor:move}.ax-tree-view-node:hover:not(.ax-dragging){background:var(--ax-comp-tree-view-node-hover-bg)}.ax-tree-view-node.ax-tree-view-node-selected{background:var(--ax-comp-tree-view-node-selected-bg)}.ax-tree-view-node.ax-dragging{opacity:var(--ax-comp-tree-view-drag-placeholder-opacity);cursor:grabbing!important}.ax-tree-view-node.ax-drag-placeholder{background:var(--ax-comp-tree-view-drag-placeholder-bg)}.ax-drag-preview{opacity:var(--ax-comp-tree-view-drag-preview-opacity)!important;box-shadow:0 4px 12px rgba(var(--ax-sys-color-on-lightest-surface),.2)!important;cursor:grabbing!important;border:2px dashed currentColor!important}.ax-tree-view-node-content.ax-drop-list-sorting-active{background:var(--ax-comp-tree-view-drop-active-bg);border-radius:var(--ax-comp-tree-view-node-border-radius);outline:2px dashed var(--ax-comp-tree-view-drop-active-outline);outline-offset:var(--ax-comp-tree-view-outline-offset-negative)}.ax-tree-view-node-content{display:flex;align-items:center;gap:var(--ax-comp-tree-view-content-gap);padding:var(--ax-comp-tree-view-content-padding);cursor:pointer;-webkit-user-select:none;user-select:none;outline:none;border-radius:var(--ax-comp-tree-view-node-border-radius)}.ax-tree-view-node-content:focus{outline:none}.ax-tree-view-node-content:focus-visible{outline:2px solid rgba(var(--ax-sys-color-primary-500),.8);outline-offset:var(--ax-comp-tree-view-outline-offset);border-radius:var(--ax-comp-tree-view-node-border-radius)}.ax-tree-view-drag-handle{cursor:grab;opacity:.6;padding:var(--ax-comp-tree-view-drag-handle-padding);padding-inline-start:calc(var(--ax-comp-tree-view-drag-handle-padding) * 2)}.ax-tree-view-drag-handle:hover{opacity:1}.ax-tree-view-drag-handle:active{cursor:grabbing}.ax-tree-view-expand-toggle{background:none;border:none;cursor:pointer;padding:var(--ax-comp-tree-view-expand-toggle-padding);min-width:1.5rem;height:1.5rem}.ax-tree-view-expand-toggle:not(.ax-tree-view-has-children){opacity:0;pointer-events:none}.ax-tree-view-toggle-icon{font-size:.75rem}.ax-tree-view-node-icon{font-size:1.125rem;flex-shrink:0}.ax-tree-view-node-label{flex:1;font-size:.875rem;line-height:1rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ax-tree-view-node-badge{padding:var(--ax-comp-tree-view-badge-padding);padding-inline-end:calc(var(--ax-comp-tree-view-badge-padding) * 1.5)}.ax-tree-view-children{padding-inline-start:var(--ax-tree-view-indent-size, var(--ax-comp-tree-view-indent-size))}.ax-tree-view-node-disabled{opacity:.5;cursor:not-allowed;pointer-events:none}.ax-tree-view-node-loading{opacity:.7}.ax-tree-view-card .ax-tree-view-node{border:1px solid rgba(var(--ax-sys-color-border-lightest-surface),1)}.ax-tree-view-card .ax-tree-view-node.ax-tree-view-node-selected{border:1px solid rgba(var(--ax-sys-color-border-surface),1)}.ax-tree-view-with-lines .ax-tree-view-children{position:relative;padding-inline-start:var(--ax-tree-view-indent-size, var(--ax-comp-tree-view-indent-size))}.ax-tree-view-with-lines .ax-tree-view-children:before{content:\"\";position:absolute;inset-inline-start:var(--ax-tree-view-line-offset, calc(var(--ax-comp-tree-view-indent-size) / 2));top:0;height:calc(100% - .875rem);width:1px;background:var(--ax-tree-view-line-color, var(--ax-comp-tree-view-line-color))}.ax-tree-view-with-lines .ax-tree-view-node{position:relative}.ax-tree-view-with-lines .ax-tree-view-node:before{content:\"\";position:absolute;inset-inline-start:calc(-1 * var(--ax-tree-view-line-offset, calc(var(--ax-comp-tree-view-indent-size) / 2)));top:60%;width:var(--ax-tree-view-line-offset, calc(var(--ax-comp-tree-view-indent-size) / 2));height:1px;background:var(--ax-tree-view-line-color, var(--ax-comp-tree-view-line-color))}.ax-tree-view-with-lines>.ax-tree-view-drop-list>.ax-tree-view-node:before,.ax-tree-view-with-lines>.ax-tree-view-node:before{display:none}\n"] }]
|
|
2103
|
+
}], propDecorators: { datasource: [{ type: i0.Input, args: [{ isSignal: true, alias: "datasource", required: true }] }, { type: i0.Output, args: ["datasourceChange"] }], selectMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectMode", required: false }] }], showCheckbox: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCheckbox", required: false }] }], selectionBehavior: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectionBehavior", required: false }] }], checkOnClick: [{ type: i0.Input, args: [{ isSignal: true, alias: "checkOnClick", required: false }] }], dragMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "dragMode", required: false }] }], dragOperationType: [{ type: i0.Input, args: [{ isSignal: true, alias: "dragOperationType", required: false }] }], showIcons: [{ type: i0.Input, args: [{ isSignal: true, alias: "showIcons", required: false }] }], showChildrenBadge: [{ type: i0.Input, args: [{ isSignal: true, alias: "showChildrenBadge", required: false }] }], expandedIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandedIcon", required: false }] }], collapsedIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsedIcon", required: false }] }], indentSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "indentSize", required: false }] }], look: [{ type: i0.Input, args: [{ isSignal: true, alias: "look", required: false }] }], nodeTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "nodeTemplate", required: false }] }], idField: [{ type: i0.Input, args: [{ isSignal: true, alias: "idField", required: false }] }], titleField: [{ type: i0.Input, args: [{ isSignal: true, alias: "titleField", required: false }] }], tooltipField: [{ type: i0.Input, args: [{ isSignal: true, alias: "tooltipField", required: false }] }], iconField: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconField", required: false }] }], expandedField: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandedField", required: false }] }], selectedField: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedField", required: false }] }], indeterminateField: [{ type: i0.Input, args: [{ isSignal: true, alias: "indeterminateField", required: false }] }], disabledField: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabledField", required: false }] }], hiddenField: [{ type: i0.Input, args: [{ isSignal: true, alias: "hiddenField", required: false }] }], childrenField: [{ type: i0.Input, args: [{ isSignal: true, alias: "childrenField", required: false }] }], childrenCountField: [{ type: i0.Input, args: [{ isSignal: true, alias: "childrenCountField", required: false }] }], dataField: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataField", required: false }] }], onBeforeDrop: [{ type: i0.Output, args: ["onBeforeDrop"] }], onNodeToggle: [{ type: i0.Output, args: ["onNodeToggle"] }], onNodeSelect: [{ type: i0.Output, args: ["onNodeSelect"] }], onSelectionChange: [{ type: i0.Output, args: ["onSelectionChange"] }], onOrderChange: [{ type: i0.Output, args: ["onOrderChange"] }], onMoveChange: [{ type: i0.Output, args: ["onMoveChange"] }], onItemsChange: [{ type: i0.Output, args: ["onItemsChange"] }] } });
|
|
474
2104
|
|
|
475
|
-
const COMPONENT = [AXTreeViewComponent, AXTreeViewItemComponent];
|
|
476
|
-
const MODULES = [
|
|
477
|
-
CommonModule,
|
|
478
|
-
AXCommonModule,
|
|
479
|
-
AXDecoratorModule,
|
|
480
|
-
AXCheckBoxModule,
|
|
481
|
-
AXFormModule,
|
|
482
|
-
FormsModule,
|
|
483
|
-
AXTooltipModule,
|
|
484
|
-
AXLoadingModule,
|
|
485
|
-
];
|
|
486
2105
|
class AXTreeViewModule {
|
|
487
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
488
|
-
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.
|
|
489
|
-
|
|
490
|
-
AXDecoratorModule,
|
|
491
|
-
AXCheckBoxModule,
|
|
492
|
-
AXFormModule,
|
|
493
|
-
FormsModule,
|
|
494
|
-
AXTooltipModule,
|
|
495
|
-
AXLoadingModule], exports: [AXTreeViewComponent, AXTreeViewItemComponent] }); }
|
|
496
|
-
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AXTreeViewModule, imports: [COMPONENT, MODULES] }); }
|
|
2106
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: AXTreeViewModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
|
|
2107
|
+
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.9", ngImport: i0, type: AXTreeViewModule, imports: [AXTreeViewComponent] }); }
|
|
2108
|
+
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: AXTreeViewModule, imports: [AXTreeViewComponent] }); }
|
|
497
2109
|
}
|
|
498
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
2110
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: AXTreeViewModule, decorators: [{
|
|
499
2111
|
type: NgModule,
|
|
500
2112
|
args: [{
|
|
501
|
-
imports: [
|
|
502
|
-
exports: [...COMPONENT],
|
|
2113
|
+
imports: [AXTreeViewComponent],
|
|
503
2114
|
}]
|
|
504
2115
|
}] });
|
|
505
2116
|
|
|
@@ -507,5 +2118,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImpo
|
|
|
507
2118
|
* Generated bundle index. Do not edit.
|
|
508
2119
|
*/
|
|
509
2120
|
|
|
510
|
-
export {
|
|
2121
|
+
export { AXTreeViewComponent, AXTreeViewModule, AXTreeViewService };
|
|
511
2122
|
//# sourceMappingURL=acorex-components-tree-view.mjs.map
|