@acorex/components 21.0.0-next.38 → 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/calendar/index.d.ts +4 -0
- package/fesm2022/acorex-components-calendar.mjs +15 -0
- package/fesm2022/acorex-components-calendar.mjs.map +1 -1
- package/fesm2022/acorex-components-data-pager.mjs +4 -1
- package/fesm2022/acorex-components-data-pager.mjs.map +1 -1
- package/fesm2022/acorex-components-number-box.mjs +6 -5
- package/fesm2022/acorex-components-number-box.mjs.map +1 -1
- package/fesm2022/acorex-components-tree-view.mjs +1099 -214
- package/fesm2022/acorex-components-tree-view.mjs.map +1 -1
- package/number-box/index.d.ts +1 -1
- package/package.json +3 -3
- package/tree-view/index.d.ts +368 -41
|
@@ -4,6 +4,7 @@ import { AXBadgeComponent } from '@acorex/components/badge';
|
|
|
4
4
|
import { AXButtonComponent } from '@acorex/components/button';
|
|
5
5
|
import { AXCheckBoxComponent } from '@acorex/components/check-box';
|
|
6
6
|
import { AXDecoratorIconComponent } from '@acorex/components/decorators';
|
|
7
|
+
import { AXTooltipDirective } from '@acorex/components/tooltip';
|
|
7
8
|
import { AXPlatform } from '@acorex/core/platform';
|
|
8
9
|
import * as i1 from '@angular/common';
|
|
9
10
|
import { CommonModule, NgTemplateOutlet } from '@angular/common';
|
|
@@ -27,12 +28,13 @@ class AXTreeViewService {
|
|
|
27
28
|
/**
|
|
28
29
|
* Find a node by ID in the tree
|
|
29
30
|
*/
|
|
30
|
-
findNodeById(nodes, id) {
|
|
31
|
+
findNodeById(nodes, id, idField = 'id') {
|
|
31
32
|
for (const node of nodes) {
|
|
32
|
-
if (node
|
|
33
|
+
if (node[idField] === id)
|
|
33
34
|
return node;
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
const children = node['children'];
|
|
36
|
+
if (children) {
|
|
37
|
+
const found = this.findNodeById(children, id, idField);
|
|
36
38
|
if (found)
|
|
37
39
|
return found;
|
|
38
40
|
}
|
|
@@ -42,13 +44,15 @@ class AXTreeViewService {
|
|
|
42
44
|
/**
|
|
43
45
|
* Find parent node of a given node
|
|
44
46
|
*/
|
|
45
|
-
findParentNode(nodes, targetNode) {
|
|
47
|
+
findParentNode(nodes, targetNode, idField = 'id', childrenField = 'children') {
|
|
48
|
+
const targetId = targetNode[idField];
|
|
46
49
|
for (const node of nodes) {
|
|
47
|
-
|
|
50
|
+
const children = node[childrenField];
|
|
51
|
+
if (children?.some((child) => child[idField] === targetId)) {
|
|
48
52
|
return node;
|
|
49
53
|
}
|
|
50
|
-
if (
|
|
51
|
-
const found = this.findParentNode(
|
|
54
|
+
if (children) {
|
|
55
|
+
const found = this.findParentNode(children, targetNode, idField, childrenField);
|
|
52
56
|
if (found)
|
|
53
57
|
return found;
|
|
54
58
|
}
|
|
@@ -59,8 +63,10 @@ class AXTreeViewService {
|
|
|
59
63
|
* Check if targetNode is a descendant of ancestorNode (or the same node)
|
|
60
64
|
* Prevents circular references by checking if target exists in ancestor's children tree
|
|
61
65
|
*/
|
|
62
|
-
isValidDropTarget(movedNode, targetNode) {
|
|
63
|
-
|
|
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)) {
|
|
64
70
|
return false;
|
|
65
71
|
}
|
|
66
72
|
return true;
|
|
@@ -68,18 +74,22 @@ class AXTreeViewService {
|
|
|
68
74
|
/**
|
|
69
75
|
* Check if targetNode is a descendant of ancestorNode
|
|
70
76
|
*/
|
|
71
|
-
isNodeDescendantOf(targetNode, ancestorNode) {
|
|
72
|
-
|
|
77
|
+
isNodeDescendantOf(targetNode, ancestorNode, idField = 'id', childrenField = 'children') {
|
|
78
|
+
const targetId = targetNode[idField];
|
|
79
|
+
const ancestorId = ancestorNode[idField];
|
|
80
|
+
if (targetId === ancestorId) {
|
|
73
81
|
return true;
|
|
74
82
|
}
|
|
75
|
-
|
|
83
|
+
const children = ancestorNode[childrenField];
|
|
84
|
+
if (!children || children.length === 0) {
|
|
76
85
|
return false;
|
|
77
86
|
}
|
|
78
|
-
for (const child of
|
|
79
|
-
|
|
87
|
+
for (const child of children) {
|
|
88
|
+
const childId = child[idField];
|
|
89
|
+
if (childId === targetId) {
|
|
80
90
|
return true;
|
|
81
91
|
}
|
|
82
|
-
if (this.isNodeDescendantOf(targetNode, child)) {
|
|
92
|
+
if (this.isNodeDescendantOf(targetNode, child, idField, childrenField)) {
|
|
83
93
|
return true;
|
|
84
94
|
}
|
|
85
95
|
}
|
|
@@ -88,14 +98,18 @@ class AXTreeViewService {
|
|
|
88
98
|
/**
|
|
89
99
|
* Build a flat list of all visible focusable nodes
|
|
90
100
|
*/
|
|
91
|
-
buildFlatNodeList(nodes) {
|
|
101
|
+
buildFlatNodeList(nodes, hiddenField = 'hidden', disabledField = 'disabled', expandedField = 'expanded', childrenField = 'children') {
|
|
92
102
|
const flatList = [];
|
|
93
103
|
const traverse = (nodeList, level, parent) => {
|
|
94
104
|
for (const node of nodeList) {
|
|
95
|
-
|
|
105
|
+
const hidden = node[hiddenField];
|
|
106
|
+
const disabled = node[disabledField];
|
|
107
|
+
if (hidden !== true && !disabled) {
|
|
96
108
|
flatList.push({ node, level, parent });
|
|
97
|
-
|
|
98
|
-
|
|
109
|
+
const expanded = node[expandedField];
|
|
110
|
+
const children = node[childrenField];
|
|
111
|
+
if (expanded && children) {
|
|
112
|
+
traverse(children, level + 1, node);
|
|
99
113
|
}
|
|
100
114
|
}
|
|
101
115
|
}
|
|
@@ -107,39 +121,44 @@ class AXTreeViewService {
|
|
|
107
121
|
/**
|
|
108
122
|
* Check if node has children
|
|
109
123
|
*/
|
|
110
|
-
hasChildren(node) {
|
|
111
|
-
|
|
124
|
+
hasChildren(node, childrenField = 'children') {
|
|
125
|
+
const children = node[childrenField];
|
|
126
|
+
return Boolean(children?.length);
|
|
112
127
|
}
|
|
113
128
|
/**
|
|
114
129
|
* Check if node can be lazy loaded
|
|
115
130
|
*/
|
|
116
|
-
canLazyLoad(node, isLazyDataSource) {
|
|
117
|
-
|
|
131
|
+
canLazyLoad(node, isLazyDataSource, childrenCountField = 'childrenCount', childrenField = 'children') {
|
|
132
|
+
const childrenCount = node[childrenCountField];
|
|
133
|
+
return isLazyDataSource && Boolean(childrenCount && childrenCount > 0 && !this.hasChildren(node, childrenField));
|
|
118
134
|
}
|
|
119
135
|
// ==================== Selection Management ====================
|
|
120
136
|
/**
|
|
121
137
|
* Recursively select/deselect all children
|
|
122
138
|
*/
|
|
123
|
-
selectAllChildren(children, selected) {
|
|
139
|
+
selectAllChildren(children, selected, selectedField = 'selected', indeterminateField = 'indeterminate', childrenField = 'children') {
|
|
124
140
|
for (const child of children) {
|
|
125
|
-
child
|
|
126
|
-
child
|
|
127
|
-
|
|
128
|
-
|
|
141
|
+
child[selectedField] = selected;
|
|
142
|
+
child[indeterminateField] = false;
|
|
143
|
+
const childChildren = child[childrenField];
|
|
144
|
+
if (childChildren) {
|
|
145
|
+
this.selectAllChildren(childChildren, selected, selectedField, indeterminateField, childrenField);
|
|
129
146
|
}
|
|
130
147
|
}
|
|
131
148
|
}
|
|
132
149
|
/**
|
|
133
150
|
* Get selection state of children
|
|
134
151
|
*/
|
|
135
|
-
getChildrenSelectionState(children) {
|
|
152
|
+
getChildrenSelectionState(children, selectedField = 'selected', indeterminateField = 'indeterminate') {
|
|
136
153
|
let selectedCount = 0;
|
|
137
154
|
let indeterminateCount = 0;
|
|
138
155
|
for (const child of children) {
|
|
139
|
-
|
|
156
|
+
const selected = child[selectedField];
|
|
157
|
+
const indeterminate = child[indeterminateField];
|
|
158
|
+
if (selected && !indeterminate) {
|
|
140
159
|
selectedCount++;
|
|
141
160
|
}
|
|
142
|
-
if (
|
|
161
|
+
if (indeterminate) {
|
|
143
162
|
indeterminateCount++;
|
|
144
163
|
}
|
|
145
164
|
}
|
|
@@ -151,65 +170,69 @@ class AXTreeViewService {
|
|
|
151
170
|
/**
|
|
152
171
|
* Update parent node states based on children selection (with intermediate state support)
|
|
153
172
|
*/
|
|
154
|
-
updateParentStates(nodes, changedNode, intermediateState) {
|
|
155
|
-
const parent = this.findParentNode(nodes, changedNode);
|
|
156
|
-
|
|
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)
|
|
157
177
|
return;
|
|
158
|
-
const { allSelected, someSelected } = this.getChildrenSelectionState(
|
|
178
|
+
const { allSelected, someSelected } = this.getChildrenSelectionState(parentChildren, selectedField, indeterminateField);
|
|
159
179
|
if (allSelected) {
|
|
160
|
-
parent
|
|
161
|
-
parent
|
|
180
|
+
parent[selectedField] = true;
|
|
181
|
+
parent[indeterminateField] = false;
|
|
162
182
|
}
|
|
163
183
|
else if (someSelected) {
|
|
164
184
|
if (intermediateState) {
|
|
165
|
-
parent
|
|
166
|
-
parent
|
|
185
|
+
parent[selectedField] = true;
|
|
186
|
+
parent[indeterminateField] = true;
|
|
167
187
|
}
|
|
168
188
|
else {
|
|
169
|
-
parent
|
|
170
|
-
parent
|
|
189
|
+
parent[selectedField] = false;
|
|
190
|
+
parent[indeterminateField] = false;
|
|
171
191
|
}
|
|
172
192
|
}
|
|
173
193
|
else {
|
|
174
|
-
parent
|
|
175
|
-
parent
|
|
194
|
+
parent[selectedField] = false;
|
|
195
|
+
parent[indeterminateField] = false;
|
|
176
196
|
}
|
|
177
|
-
this.updateParentStates(nodes, parent, intermediateState);
|
|
197
|
+
this.updateParentStates(nodes, parent, intermediateState, idField, childrenField, selectedField, indeterminateField);
|
|
178
198
|
}
|
|
179
199
|
/**
|
|
180
200
|
* Recursively deselect all nodes
|
|
181
201
|
*/
|
|
182
|
-
deselectAllNodes(nodes) {
|
|
202
|
+
deselectAllNodes(nodes, selectedField = 'selected', indeterminateField = 'indeterminate', childrenField = 'children') {
|
|
183
203
|
for (const node of nodes) {
|
|
184
|
-
node
|
|
185
|
-
node
|
|
186
|
-
|
|
187
|
-
|
|
204
|
+
node[selectedField] = false;
|
|
205
|
+
node[indeterminateField] = false;
|
|
206
|
+
const children = node[childrenField];
|
|
207
|
+
if (children) {
|
|
208
|
+
this.deselectAllNodes(children, selectedField, indeterminateField, childrenField);
|
|
188
209
|
}
|
|
189
210
|
}
|
|
190
211
|
}
|
|
191
212
|
/**
|
|
192
213
|
* Recursively set selection state for all nodes
|
|
193
214
|
*/
|
|
194
|
-
setAllSelection(nodes, selected) {
|
|
215
|
+
setAllSelection(nodes, selected, selectedField = 'selected', indeterminateField = 'indeterminate', childrenField = 'children') {
|
|
195
216
|
for (const node of nodes) {
|
|
196
|
-
node
|
|
197
|
-
node
|
|
198
|
-
|
|
199
|
-
|
|
217
|
+
node[selectedField] = selected;
|
|
218
|
+
node[indeterminateField] = false;
|
|
219
|
+
const children = node[childrenField];
|
|
220
|
+
if (children) {
|
|
221
|
+
this.setAllSelection(children, selected, selectedField, indeterminateField, childrenField);
|
|
200
222
|
}
|
|
201
223
|
}
|
|
202
224
|
}
|
|
203
225
|
/**
|
|
204
226
|
* Recursively count selected nodes
|
|
205
227
|
*/
|
|
206
|
-
countSelected(nodes) {
|
|
228
|
+
countSelected(nodes, selectedField = 'selected', childrenField = 'children') {
|
|
207
229
|
let count = 0;
|
|
208
230
|
for (const node of nodes) {
|
|
209
|
-
if (node
|
|
231
|
+
if (node[selectedField])
|
|
210
232
|
count++;
|
|
211
|
-
|
|
212
|
-
|
|
233
|
+
const children = node[childrenField];
|
|
234
|
+
if (children) {
|
|
235
|
+
count += this.countSelected(children, selectedField, childrenField);
|
|
213
236
|
}
|
|
214
237
|
}
|
|
215
238
|
return count;
|
|
@@ -217,53 +240,61 @@ class AXTreeViewService {
|
|
|
217
240
|
/**
|
|
218
241
|
* Recursively collect selected nodes
|
|
219
242
|
*/
|
|
220
|
-
collectSelected(nodes, result) {
|
|
243
|
+
collectSelected(nodes, result, selectedField = 'selected', childrenField = 'children') {
|
|
221
244
|
for (const node of nodes) {
|
|
222
|
-
if (node
|
|
245
|
+
if (node[selectedField])
|
|
223
246
|
result.push(node);
|
|
224
|
-
|
|
225
|
-
|
|
247
|
+
const children = node[childrenField];
|
|
248
|
+
if (children) {
|
|
249
|
+
this.collectSelected(children, result, selectedField, childrenField);
|
|
226
250
|
}
|
|
227
251
|
}
|
|
228
252
|
}
|
|
229
253
|
/**
|
|
230
254
|
* Recursively remove selected nodes
|
|
231
255
|
*/
|
|
232
|
-
removeSelected(nodes) {
|
|
256
|
+
removeSelected(nodes, selectedField = 'selected', indeterminateField = 'indeterminate', childrenField = 'children') {
|
|
233
257
|
for (let i = nodes.length - 1; i >= 0; i--) {
|
|
234
|
-
|
|
258
|
+
const node = nodes[i];
|
|
259
|
+
const selected = node[selectedField];
|
|
260
|
+
const indeterminate = node[indeterminateField];
|
|
261
|
+
if (selected && !indeterminate) {
|
|
235
262
|
nodes.splice(i, 1);
|
|
236
263
|
}
|
|
237
|
-
else
|
|
238
|
-
|
|
264
|
+
else {
|
|
265
|
+
const children = node[childrenField];
|
|
266
|
+
if (children) {
|
|
267
|
+
this.removeSelected(children, selectedField, indeterminateField, childrenField);
|
|
268
|
+
}
|
|
239
269
|
}
|
|
240
270
|
}
|
|
241
271
|
}
|
|
242
272
|
/**
|
|
243
273
|
* Recursively update all parent states in the tree (used after deletion)
|
|
244
274
|
*/
|
|
245
|
-
updateAllParentStates(nodes, intermediateState) {
|
|
275
|
+
updateAllParentStates(nodes, intermediateState, childrenField = 'children', selectedField = 'selected', indeterminateField = 'indeterminate') {
|
|
246
276
|
for (const node of nodes) {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
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);
|
|
250
281
|
if (allSelected) {
|
|
251
|
-
node
|
|
252
|
-
node
|
|
282
|
+
node[selectedField] = true;
|
|
283
|
+
node[indeterminateField] = false;
|
|
253
284
|
}
|
|
254
285
|
else if (someSelected) {
|
|
255
286
|
if (intermediateState) {
|
|
256
|
-
node
|
|
257
|
-
node
|
|
287
|
+
node[selectedField] = true;
|
|
288
|
+
node[indeterminateField] = true;
|
|
258
289
|
}
|
|
259
290
|
else {
|
|
260
|
-
node
|
|
261
|
-
node
|
|
291
|
+
node[selectedField] = false;
|
|
292
|
+
node[indeterminateField] = false;
|
|
262
293
|
}
|
|
263
294
|
}
|
|
264
295
|
else {
|
|
265
|
-
node
|
|
266
|
-
node
|
|
296
|
+
node[selectedField] = false;
|
|
297
|
+
node[indeterminateField] = false;
|
|
267
298
|
}
|
|
268
299
|
}
|
|
269
300
|
}
|
|
@@ -272,17 +303,18 @@ class AXTreeViewService {
|
|
|
272
303
|
/**
|
|
273
304
|
* Recursively set expanded state (with lazy loading)
|
|
274
305
|
*/
|
|
275
|
-
async setExpandedState(nodes, expanded, isLazyDataSource, loadNodeChildren) {
|
|
306
|
+
async setExpandedState(nodes, expanded, isLazyDataSource, loadNodeChildren, expandedField = 'expanded', childrenField = 'children', childrenCountField = 'childrenCount') {
|
|
276
307
|
for (const node of nodes) {
|
|
277
|
-
const hasChildren = this.hasChildren(node);
|
|
278
|
-
const canLazyLoad = this.canLazyLoad(node, isLazyDataSource);
|
|
308
|
+
const hasChildren = this.hasChildren(node, childrenField);
|
|
309
|
+
const canLazyLoad = this.canLazyLoad(node, isLazyDataSource, childrenCountField, childrenField);
|
|
279
310
|
if (hasChildren || canLazyLoad) {
|
|
280
311
|
if (expanded && canLazyLoad) {
|
|
281
312
|
await loadNodeChildren(node);
|
|
282
313
|
}
|
|
283
|
-
node
|
|
284
|
-
|
|
285
|
-
|
|
314
|
+
node[expandedField] = expanded;
|
|
315
|
+
const children = node[childrenField];
|
|
316
|
+
if (children) {
|
|
317
|
+
await this.setExpandedState(children, expanded, isLazyDataSource, loadNodeChildren, expandedField, childrenField, childrenCountField);
|
|
286
318
|
}
|
|
287
319
|
}
|
|
288
320
|
}
|
|
@@ -291,23 +323,24 @@ class AXTreeViewService {
|
|
|
291
323
|
/**
|
|
292
324
|
* Get array reference by drop list ID
|
|
293
325
|
*/
|
|
294
|
-
getArrayByListId(nodes, listId) {
|
|
326
|
+
getArrayByListId(nodes, listId, idField = 'id', childrenField = 'children') {
|
|
295
327
|
if (listId === AXTreeViewService.ROOT_LIST_ID) {
|
|
296
328
|
return nodes;
|
|
297
329
|
}
|
|
298
330
|
if (listId.startsWith(AXTreeViewService.NODE_DROP_PREFIX)) {
|
|
299
331
|
const nodeId = listId.replace(AXTreeViewService.NODE_DROP_PREFIX, '');
|
|
300
|
-
const node = this.findNodeById(nodes, nodeId);
|
|
332
|
+
const node = this.findNodeById(nodes, nodeId, idField);
|
|
301
333
|
return node ? [node] : null;
|
|
302
334
|
}
|
|
303
335
|
const nodeId = listId.replace(AXTreeViewService.LIST_PREFIX, '');
|
|
304
|
-
const node = this.findNodeById(nodes, nodeId);
|
|
305
|
-
|
|
336
|
+
const node = this.findNodeById(nodes, nodeId, idField);
|
|
337
|
+
const children = node?.[childrenField];
|
|
338
|
+
return children ?? null;
|
|
306
339
|
}
|
|
307
340
|
/**
|
|
308
341
|
* Find parent node by list ID
|
|
309
342
|
*/
|
|
310
|
-
findParentByListId(nodes, listId) {
|
|
343
|
+
findParentByListId(nodes, listId, idField = 'id') {
|
|
311
344
|
if (listId === AXTreeViewService.ROOT_LIST_ID) {
|
|
312
345
|
return undefined;
|
|
313
346
|
}
|
|
@@ -315,13 +348,16 @@ class AXTreeViewService {
|
|
|
315
348
|
? AXTreeViewService.NODE_DROP_PREFIX
|
|
316
349
|
: AXTreeViewService.LIST_PREFIX;
|
|
317
350
|
const nodeId = listId.replace(prefix, '');
|
|
318
|
-
return this.findNodeById(nodes, nodeId) ?? undefined;
|
|
351
|
+
return this.findNodeById(nodes, nodeId, idField) ?? undefined;
|
|
319
352
|
}
|
|
320
353
|
/**
|
|
321
354
|
* Generate unique list ID for each node
|
|
322
355
|
*/
|
|
323
|
-
getListId(node) {
|
|
324
|
-
|
|
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}`;
|
|
325
361
|
}
|
|
326
362
|
/**
|
|
327
363
|
* Get root list ID constant
|
|
@@ -341,6 +377,122 @@ class AXTreeViewService {
|
|
|
341
377
|
getListPrefix() {
|
|
342
378
|
return AXTreeViewService.LIST_PREFIX;
|
|
343
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
|
+
}
|
|
344
496
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: AXTreeViewService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
345
497
|
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: AXTreeViewService }); }
|
|
346
498
|
}
|
|
@@ -361,10 +513,8 @@ class AXTreeViewComponent {
|
|
|
361
513
|
this.selectMode = input('multiple', ...(ngDevMode ? [{ debugName: "selectMode" }] : []));
|
|
362
514
|
/** Whether to show checkboxes for selection (only applies to multiple mode) */
|
|
363
515
|
this.showCheckbox = input(true, ...(ngDevMode ? [{ debugName: "showCheckbox" }] : []));
|
|
364
|
-
/**
|
|
365
|
-
this.
|
|
366
|
-
/** When true, selecting a child makes parents indeterminate (only for multiple mode) */
|
|
367
|
-
this.intermediateState = input(true, ...(ngDevMode ? [{ debugName: "intermediateState" }] : []));
|
|
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" }] : []));
|
|
368
518
|
/** When true, clicking on a node toggles its selection (works for both single and multiple modes) */
|
|
369
519
|
this.checkOnClick = input(false, ...(ngDevMode ? [{ debugName: "checkOnClick" }] : []));
|
|
370
520
|
/** Drag and drop mode: 'none' (disabled), 'handler' (drag handle), 'item' (entire item) */
|
|
@@ -380,13 +530,35 @@ class AXTreeViewComponent {
|
|
|
380
530
|
/** Custom icon for collapsed nodes */
|
|
381
531
|
this.collapsedIcon = input('fa-solid fa-chevron-right', ...(ngDevMode ? [{ debugName: "collapsedIcon" }] : []));
|
|
382
532
|
/** Indent size in pixels for each level */
|
|
383
|
-
this.indentSize = input(
|
|
384
|
-
/** Node height in pixels */
|
|
385
|
-
this.nodeHeight = input('normal', ...(ngDevMode ? [{ debugName: "nodeHeight" }] : []));
|
|
533
|
+
this.indentSize = input(16, ...(ngDevMode ? [{ debugName: "indentSize" }] : []));
|
|
386
534
|
/** Visual style variant */
|
|
387
535
|
this.look = input('default', ...(ngDevMode ? [{ debugName: "look" }] : []));
|
|
388
536
|
/** Custom template for tree items */
|
|
389
|
-
this.
|
|
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" }] : []));
|
|
390
562
|
// ==================== Outputs ====================
|
|
391
563
|
/** Emitted before a drop operation - set canceled to true to prevent drop */
|
|
392
564
|
this.onBeforeDrop = output();
|
|
@@ -394,6 +566,8 @@ class AXTreeViewComponent {
|
|
|
394
566
|
this.onNodeToggle = output();
|
|
395
567
|
/** Emitted when a node is selected/deselected */
|
|
396
568
|
this.onNodeSelect = output();
|
|
569
|
+
/** Emitted when selection changes - returns all currently selected nodes */
|
|
570
|
+
this.onSelectionChange = output();
|
|
397
571
|
/** Emitted when nodes are reordered within the same parent */
|
|
398
572
|
this.onOrderChange = output();
|
|
399
573
|
/** Emitted when a node is moved to a different parent */
|
|
@@ -426,19 +600,35 @@ class AXTreeViewComponent {
|
|
|
426
600
|
this.isUpdatingFromDatasource = false;
|
|
427
601
|
/** Computed to check if datasource is a function */
|
|
428
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" }] : []));
|
|
429
615
|
// ==================== Effects ====================
|
|
430
616
|
/** Effect to handle datasource changes */
|
|
431
|
-
this.#datasourceEffect = effect(() => {
|
|
432
|
-
if (this.isUpdatingFromDatasource)
|
|
617
|
+
this.#datasourceEffect = effect(async () => {
|
|
618
|
+
if (this.isUpdatingFromDatasource) {
|
|
433
619
|
return;
|
|
620
|
+
}
|
|
434
621
|
const ds = this.datasource();
|
|
435
622
|
if (Array.isArray(ds)) {
|
|
436
623
|
this.nodes.set([...ds]);
|
|
437
624
|
}
|
|
438
625
|
else if (typeof ds === 'function') {
|
|
439
|
-
|
|
626
|
+
try {
|
|
627
|
+
await this.loadRootItems(ds);
|
|
628
|
+
}
|
|
629
|
+
catch (error) {
|
|
440
630
|
this.handleError('Failed to load root items', error);
|
|
441
|
-
}
|
|
631
|
+
}
|
|
442
632
|
}
|
|
443
633
|
}, ...(ngDevMode ? [{ debugName: "#datasourceEffect" }] : []));
|
|
444
634
|
/** Initialize direction change listener */
|
|
@@ -448,6 +638,115 @@ class AXTreeViewComponent {
|
|
|
448
638
|
.subscribe((isRtl) => this.isRtl.set(isRtl));
|
|
449
639
|
});
|
|
450
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
|
+
}
|
|
451
750
|
// ==================== Effects ====================
|
|
452
751
|
/** Effect to handle datasource changes */
|
|
453
752
|
#datasourceEffect;
|
|
@@ -458,21 +757,21 @@ class AXTreeViewComponent {
|
|
|
458
757
|
* Expand all nodes in the tree (with lazy loading support)
|
|
459
758
|
*/
|
|
460
759
|
async expandAll() {
|
|
461
|
-
await this.treeService.setExpandedState(this.nodes(), true, this.isLazyDataSource(), (node) => this.loadNodeChildren(node));
|
|
760
|
+
await this.treeService.setExpandedState(this.nodes(), true, this.isLazyDataSource(), (node) => this.loadNodeChildren(node), this.expandedField(), this.childrenField(), this.childrenCountField());
|
|
462
761
|
this.refreshNodes();
|
|
463
762
|
}
|
|
464
763
|
/**
|
|
465
764
|
* Collapse all nodes in the tree
|
|
466
765
|
*/
|
|
467
766
|
collapseAll() {
|
|
468
|
-
this.treeService.setExpandedState(this.nodes(), false, this.isLazyDataSource(), (node) => this.loadNodeChildren(node));
|
|
767
|
+
this.treeService.setExpandedState(this.nodes(), false, this.isLazyDataSource(), (node) => this.loadNodeChildren(node), this.expandedField(), this.childrenField(), this.childrenCountField());
|
|
469
768
|
this.refreshNodes();
|
|
470
769
|
}
|
|
471
770
|
/**
|
|
472
771
|
* Get count of selected nodes
|
|
473
772
|
*/
|
|
474
773
|
getSelectedCount() {
|
|
475
|
-
return this.treeService.countSelected(this.nodes());
|
|
774
|
+
return this.treeService.countSelected(this.nodes(), this.selectedField(), this.childrenField());
|
|
476
775
|
}
|
|
477
776
|
/**
|
|
478
777
|
* Check if any nodes are selected
|
|
@@ -485,36 +784,62 @@ class AXTreeViewComponent {
|
|
|
485
784
|
*/
|
|
486
785
|
getSelectedNodes() {
|
|
487
786
|
const selected = [];
|
|
488
|
-
this.treeService.collectSelected(this.nodes(), selected);
|
|
787
|
+
this.treeService.collectSelected(this.nodes(), selected, this.selectedField(), this.childrenField());
|
|
489
788
|
return selected;
|
|
490
789
|
}
|
|
491
790
|
/**
|
|
492
791
|
* Delete selected nodes from the tree
|
|
493
792
|
*/
|
|
494
793
|
deleteSelected() {
|
|
495
|
-
this.treeService.removeSelected(this.nodes());
|
|
496
|
-
|
|
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
|
+
}
|
|
497
798
|
this.refreshNodes();
|
|
799
|
+
this.emitSelectionChange();
|
|
498
800
|
}
|
|
499
801
|
/**
|
|
500
802
|
* Select all nodes in the tree
|
|
501
803
|
*/
|
|
502
804
|
selectAll() {
|
|
503
|
-
|
|
805
|
+
if (this.selectMode() === 'none') {
|
|
806
|
+
return;
|
|
807
|
+
}
|
|
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
|
+
}
|
|
504
827
|
this.refreshNodes();
|
|
828
|
+
this.emitSelectionChange();
|
|
505
829
|
}
|
|
506
830
|
/**
|
|
507
831
|
* Deselect all nodes in the tree
|
|
508
832
|
*/
|
|
509
833
|
deselectAll() {
|
|
510
|
-
this.treeService.setAllSelection(this.nodes(), false);
|
|
834
|
+
this.treeService.setAllSelection(this.nodes(), false, this.selectedField(), this.indeterminateField(), this.childrenField());
|
|
511
835
|
this.refreshNodes();
|
|
836
|
+
this.emitSelectionChange();
|
|
512
837
|
}
|
|
513
838
|
/**
|
|
514
839
|
* Find a node by ID in the tree
|
|
515
840
|
*/
|
|
516
841
|
findNode(id) {
|
|
517
|
-
return this.treeService.findNodeById(this.nodes(), id);
|
|
842
|
+
return this.treeService.findNodeById(this.nodes(), id, this.idField());
|
|
518
843
|
}
|
|
519
844
|
/**
|
|
520
845
|
* Refresh the tree to trigger change detection
|
|
@@ -528,17 +853,479 @@ class AXTreeViewComponent {
|
|
|
528
853
|
isNodeLoading(nodeId) {
|
|
529
854
|
return this.loadingNodes().has(nodeId);
|
|
530
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
|
+
}
|
|
531
1316
|
/**
|
|
532
1317
|
* Get template context for a node
|
|
533
1318
|
*/
|
|
534
1319
|
getTemplateContext(node, level = 0) {
|
|
1320
|
+
const children = this.getNodeChildren(node);
|
|
1321
|
+
const childrenCount = this.getNodeChildrenCount(node);
|
|
535
1322
|
return {
|
|
536
1323
|
$implicit: node,
|
|
537
1324
|
node,
|
|
538
1325
|
level,
|
|
539
|
-
expanded: node
|
|
540
|
-
childrenCount:
|
|
541
|
-
loading: node
|
|
1326
|
+
expanded: this.getNodeExpanded(node),
|
|
1327
|
+
childrenCount: childrenCount ?? children?.length ?? 0,
|
|
1328
|
+
loading: this.getNodeLoading(node),
|
|
542
1329
|
};
|
|
543
1330
|
}
|
|
544
1331
|
/**
|
|
@@ -554,7 +1341,8 @@ class AXTreeViewComponent {
|
|
|
554
1341
|
* Check if node should show expand toggle
|
|
555
1342
|
*/
|
|
556
1343
|
shouldShowExpandToggle(node) {
|
|
557
|
-
return this.treeService.hasChildren(node
|
|
1344
|
+
return (this.treeService.hasChildren(node, this.childrenField()) ||
|
|
1345
|
+
this.treeService.canLazyLoad(node, this.isLazyDataSource(), this.childrenCountField(), this.childrenField()));
|
|
558
1346
|
}
|
|
559
1347
|
/**
|
|
560
1348
|
* Check if checkboxes should be shown (only for multiple mode)
|
|
@@ -562,11 +1350,50 @@ class AXTreeViewComponent {
|
|
|
562
1350
|
shouldShowCheckbox() {
|
|
563
1351
|
return this.selectMode() === 'multiple' && this.showCheckbox();
|
|
564
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
|
+
}
|
|
565
1392
|
/**
|
|
566
1393
|
* Generate unique list ID for each node
|
|
567
1394
|
*/
|
|
568
1395
|
getListId(node) {
|
|
569
|
-
return this.treeService.getListId(node);
|
|
1396
|
+
return this.treeService.getListId(node, this.idField());
|
|
570
1397
|
}
|
|
571
1398
|
/**
|
|
572
1399
|
* Check if a node is currently focused
|
|
@@ -587,23 +1414,44 @@ class AXTreeViewComponent {
|
|
|
587
1414
|
if (!this.shouldShowExpandToggle(node)) {
|
|
588
1415
|
return null;
|
|
589
1416
|
}
|
|
590
|
-
return node
|
|
1417
|
+
return this.getNodeExpanded(node) ? 'true' : 'false';
|
|
591
1418
|
}
|
|
592
1419
|
/**
|
|
593
1420
|
* Get ARIA selected state for a node
|
|
594
1421
|
*/
|
|
595
1422
|
getNodeAriaSelected(node) {
|
|
1423
|
+
if (this.selectMode() === 'none') {
|
|
1424
|
+
return null;
|
|
1425
|
+
}
|
|
1426
|
+
const selected = this.getNodeSelected(node);
|
|
596
1427
|
if (this.selectMode() === 'single') {
|
|
597
|
-
return
|
|
1428
|
+
return selected ? 'true' : 'false';
|
|
1429
|
+
}
|
|
1430
|
+
if (this.selectMode() === 'multiple') {
|
|
1431
|
+
return selected ? 'true' : 'false';
|
|
598
1432
|
}
|
|
599
1433
|
return null;
|
|
600
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
|
+
});
|
|
1444
|
+
}
|
|
601
1445
|
// ==================== Event Handlers ====================
|
|
602
1446
|
/**
|
|
603
1447
|
* Handle node click - for single selection mode or multiple mode with checkOnClick enabled
|
|
604
1448
|
*/
|
|
605
1449
|
onNodeClick(node, event) {
|
|
606
|
-
if (node
|
|
1450
|
+
if (this.getNodeDisabled(node))
|
|
1451
|
+
return;
|
|
1452
|
+
if (this.selectMode() === 'none')
|
|
1453
|
+
return;
|
|
1454
|
+
if (!this.canSelectNode(node))
|
|
607
1455
|
return;
|
|
608
1456
|
const mode = this.selectMode();
|
|
609
1457
|
const shouldCheckOnClick = this.checkOnClick();
|
|
@@ -618,19 +1466,19 @@ class AXTreeViewComponent {
|
|
|
618
1466
|
* Toggle node expansion state with lazy loading support
|
|
619
1467
|
*/
|
|
620
1468
|
async toggleNode(node, event) {
|
|
621
|
-
if (node
|
|
1469
|
+
if (this.getNodeDisabled(node))
|
|
622
1470
|
return;
|
|
623
1471
|
if (this.isEvent(event) && typeof event.stopPropagation === 'function') {
|
|
624
1472
|
event.stopPropagation();
|
|
625
1473
|
}
|
|
626
|
-
const hasChildren = this.treeService.hasChildren(node);
|
|
627
|
-
const canLazyLoad = this.treeService.canLazyLoad(node, this.isLazyDataSource());
|
|
1474
|
+
const hasChildren = this.treeService.hasChildren(node, this.childrenField());
|
|
1475
|
+
const canLazyLoad = this.treeService.canLazyLoad(node, this.isLazyDataSource(), this.childrenCountField(), this.childrenField());
|
|
628
1476
|
if (hasChildren || canLazyLoad) {
|
|
629
|
-
const willExpand = !node
|
|
1477
|
+
const willExpand = !this.getNodeExpanded(node);
|
|
630
1478
|
if (willExpand && canLazyLoad) {
|
|
631
1479
|
await this.loadNodeChildren(node);
|
|
632
1480
|
}
|
|
633
|
-
node
|
|
1481
|
+
this.setNodeExpanded(node, willExpand);
|
|
634
1482
|
this.refreshNodes();
|
|
635
1483
|
this.onNodeToggle.emit({ component: this, node, nativeEvent: event });
|
|
636
1484
|
}
|
|
@@ -641,17 +1489,22 @@ class AXTreeViewComponent {
|
|
|
641
1489
|
toggleSelection(node, event) {
|
|
642
1490
|
if (!event.isUserInteraction)
|
|
643
1491
|
return;
|
|
1492
|
+
if (this.selectMode() === 'none')
|
|
1493
|
+
return;
|
|
1494
|
+
if (!this.canSelectNode(node))
|
|
1495
|
+
return;
|
|
644
1496
|
const mode = this.selectMode();
|
|
645
1497
|
if (mode !== 'multiple')
|
|
646
1498
|
return;
|
|
647
1499
|
const newValue = event.value === null ? true : event.value;
|
|
648
|
-
node
|
|
649
|
-
node
|
|
650
|
-
|
|
651
|
-
|
|
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());
|
|
652
1505
|
}
|
|
653
|
-
if (this.
|
|
654
|
-
this.treeService.updateParentStates(this.nodes(), node, this.
|
|
1506
|
+
if (this.hasIntermediateState() && !this.isLeafOnlyMode()) {
|
|
1507
|
+
this.treeService.updateParentStates(this.nodes(), node, this.hasIntermediateState(), this.idField(), this.childrenField(), this.selectedField(), this.indeterminateField());
|
|
655
1508
|
}
|
|
656
1509
|
this.refreshNodes();
|
|
657
1510
|
this.onNodeSelect.emit({
|
|
@@ -659,12 +1512,13 @@ class AXTreeViewComponent {
|
|
|
659
1512
|
node,
|
|
660
1513
|
isUserInteraction: event.isUserInteraction,
|
|
661
1514
|
});
|
|
1515
|
+
this.emitSelectionChange();
|
|
662
1516
|
}
|
|
663
1517
|
/**
|
|
664
1518
|
* Handle drop events for tree nodes
|
|
665
1519
|
*/
|
|
666
1520
|
onDrop(event, parentNode) {
|
|
667
|
-
const targetArray = parentNode
|
|
1521
|
+
const targetArray = parentNode ? (this.getNodeChildren(parentNode) ?? []) : this.nodes();
|
|
668
1522
|
const isReordering = event.previousContainer === event.container;
|
|
669
1523
|
if (isReordering) {
|
|
670
1524
|
this.handleReorder(event, targetArray, parentNode);
|
|
@@ -685,14 +1539,18 @@ class AXTreeViewComponent {
|
|
|
685
1539
|
if (!sourceArray)
|
|
686
1540
|
return;
|
|
687
1541
|
const movedNode = sourceArray[event.previousIndex];
|
|
688
|
-
if (!this.treeService.isValidDropTarget(movedNode, targetNode))
|
|
1542
|
+
if (!this.treeService.isValidDropTarget(movedNode, targetNode, this.idField(), this.childrenField()))
|
|
689
1543
|
return;
|
|
690
1544
|
if (!this.emitBeforeDropEvent(movedNode, sourceListId, targetNode, event.previousIndex, 0))
|
|
691
1545
|
return;
|
|
692
|
-
|
|
1546
|
+
let targetChildren = this.getNodeChildren(targetNode);
|
|
1547
|
+
if (!targetChildren) {
|
|
1548
|
+
targetChildren = [];
|
|
1549
|
+
this.setNodeChildren(targetNode, targetChildren);
|
|
1550
|
+
}
|
|
693
1551
|
sourceArray.splice(event.previousIndex, 1);
|
|
694
|
-
|
|
695
|
-
targetNode
|
|
1552
|
+
targetChildren.unshift(movedNode);
|
|
1553
|
+
this.setNodeExpanded(targetNode, true);
|
|
696
1554
|
this.emitDropEvents(movedNode, this.findParentByListId(sourceListId), targetNode, event.previousIndex, 0, false);
|
|
697
1555
|
this.refreshNodes();
|
|
698
1556
|
}
|
|
@@ -707,18 +1565,25 @@ class AXTreeViewComponent {
|
|
|
707
1565
|
*/
|
|
708
1566
|
onTreeFocus(event) {
|
|
709
1567
|
if (event.target === event.currentTarget) {
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
if
|
|
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) {
|
|
715
1575
|
this.focusNodeById(focusedId);
|
|
716
1576
|
}
|
|
717
1577
|
else {
|
|
718
|
-
|
|
1578
|
+
// Focused node no longer exists, focus first node
|
|
1579
|
+
this.focusNodeById(this.getNodeId(flatList[0].node));
|
|
719
1580
|
}
|
|
720
1581
|
}
|
|
721
|
-
|
|
1582
|
+
else {
|
|
1583
|
+
// No node is focused, focus first node
|
|
1584
|
+
this.focusNodeById(this.getNodeId(flatList[0].node));
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
722
1587
|
}
|
|
723
1588
|
}
|
|
724
1589
|
/**
|
|
@@ -733,11 +1598,13 @@ class AXTreeViewComponent {
|
|
|
733
1598
|
* Handle keyboard navigation
|
|
734
1599
|
*/
|
|
735
1600
|
handleKeyDown(event) {
|
|
736
|
-
const flatList = this.treeService.buildFlatNodeList(this.nodes());
|
|
1601
|
+
const flatList = this.treeService.buildFlatNodeList(this.nodes(), this.hiddenField(), this.disabledField(), this.expandedField(), this.childrenField());
|
|
737
1602
|
if (flatList.length === 0)
|
|
738
1603
|
return;
|
|
739
1604
|
const currentFocused = this.getFocusedNode();
|
|
740
|
-
let currentIndex = currentFocused
|
|
1605
|
+
let currentIndex = currentFocused
|
|
1606
|
+
? flatList.findIndex((item) => this.getNodeId(item.node) === this.getNodeId(currentFocused))
|
|
1607
|
+
: -1;
|
|
741
1608
|
if (currentIndex === -1 && event.target === event.currentTarget) {
|
|
742
1609
|
currentIndex = 0;
|
|
743
1610
|
}
|
|
@@ -750,7 +1617,7 @@ class AXTreeViewComponent {
|
|
|
750
1617
|
if (navigationResult.targetIndex !== null &&
|
|
751
1618
|
navigationResult.targetIndex >= 0 &&
|
|
752
1619
|
navigationResult.targetIndex < flatList.length) {
|
|
753
|
-
this.focusNodeById(flatList[navigationResult.targetIndex].node
|
|
1620
|
+
this.focusNodeById(this.getNodeId(flatList[navigationResult.targetIndex].node));
|
|
754
1621
|
}
|
|
755
1622
|
}
|
|
756
1623
|
}
|
|
@@ -773,32 +1640,34 @@ class AXTreeViewComponent {
|
|
|
773
1640
|
* Load children for a node using lazy loading
|
|
774
1641
|
*/
|
|
775
1642
|
async loadNodeChildren(node) {
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
if (this.treeService.hasChildren(node) || !node.childrenCount || node.childrenCount === 0) {
|
|
1643
|
+
const nodeId = this.getNodeId(node);
|
|
1644
|
+
if (!this.isLazyDataSource() || this.loadingNodes().has(nodeId))
|
|
779
1645
|
return;
|
|
1646
|
+
if (this.treeService.hasChildren(node, this.childrenField())) {
|
|
1647
|
+
const childrenCount = this.getNodeChildrenCount(node);
|
|
1648
|
+
if (!childrenCount || childrenCount === 0) {
|
|
1649
|
+
return;
|
|
1650
|
+
}
|
|
780
1651
|
}
|
|
781
1652
|
const ds = this.datasource();
|
|
782
1653
|
if (typeof ds !== 'function')
|
|
783
1654
|
return;
|
|
784
1655
|
try {
|
|
785
|
-
|
|
786
|
-
this.loadingNodes.update((set) => new Set(set).add(node.id));
|
|
1656
|
+
this.loadingNodes.update((set) => new Set(set).add(nodeId));
|
|
787
1657
|
this.refreshNodes();
|
|
788
|
-
const result = ds(
|
|
1658
|
+
const result = ds(nodeId);
|
|
789
1659
|
const children = result instanceof Promise ? await result : result;
|
|
790
|
-
node
|
|
791
|
-
node
|
|
1660
|
+
this.setNodeChildren(node, children);
|
|
1661
|
+
this.setNodeChildrenCount(node, children.length);
|
|
792
1662
|
}
|
|
793
1663
|
catch (error) {
|
|
794
1664
|
this.handleError('Failed to load children', error);
|
|
795
|
-
node
|
|
1665
|
+
this.setNodeChildrenCount(node, 0);
|
|
796
1666
|
}
|
|
797
1667
|
finally {
|
|
798
|
-
node.loading = false;
|
|
799
1668
|
this.loadingNodes.update((set) => {
|
|
800
1669
|
const newSet = new Set(set);
|
|
801
|
-
newSet.delete(
|
|
1670
|
+
newSet.delete(nodeId);
|
|
802
1671
|
return newSet;
|
|
803
1672
|
});
|
|
804
1673
|
this.refreshNodes();
|
|
@@ -828,11 +1697,12 @@ class AXTreeViewComponent {
|
|
|
828
1697
|
*/
|
|
829
1698
|
ensureNewArrayReferences(nodes) {
|
|
830
1699
|
for (const node of nodes) {
|
|
831
|
-
|
|
1700
|
+
const children = this.getNodeChildren(node);
|
|
1701
|
+
if (children && children.length > 0) {
|
|
832
1702
|
// Create new array reference for children
|
|
833
|
-
node
|
|
1703
|
+
this.setNodeChildren(node, [...children]);
|
|
834
1704
|
// Recursively process nested children
|
|
835
|
-
this.ensureNewArrayReferences(
|
|
1705
|
+
this.ensureNewArrayReferences(children);
|
|
836
1706
|
}
|
|
837
1707
|
}
|
|
838
1708
|
}
|
|
@@ -840,9 +1710,9 @@ class AXTreeViewComponent {
|
|
|
840
1710
|
* Handle single selection mode
|
|
841
1711
|
*/
|
|
842
1712
|
handleSingleSelection(node, event) {
|
|
843
|
-
this.treeService.deselectAllNodes(this.nodes());
|
|
844
|
-
node
|
|
845
|
-
node
|
|
1713
|
+
this.treeService.deselectAllNodes(this.nodes(), this.selectedField(), this.indeterminateField(), this.childrenField());
|
|
1714
|
+
this.setNodeSelected(node, true);
|
|
1715
|
+
this.setNodeIndeterminate(node, false);
|
|
846
1716
|
this.refreshNodes();
|
|
847
1717
|
this.onNodeSelect.emit({
|
|
848
1718
|
component: this,
|
|
@@ -850,19 +1720,21 @@ class AXTreeViewComponent {
|
|
|
850
1720
|
nativeEvent: event,
|
|
851
1721
|
isUserInteraction: true,
|
|
852
1722
|
});
|
|
1723
|
+
this.emitSelectionChange();
|
|
853
1724
|
}
|
|
854
1725
|
/**
|
|
855
1726
|
* Handle multiple selection mode with checkOnClick
|
|
856
1727
|
*/
|
|
857
1728
|
handleMultipleSelection(node, event) {
|
|
858
|
-
const newValue = !node
|
|
859
|
-
node
|
|
860
|
-
node
|
|
861
|
-
|
|
862
|
-
|
|
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());
|
|
863
1735
|
}
|
|
864
|
-
if (this.
|
|
865
|
-
this.treeService.updateParentStates(this.nodes(), node, this.
|
|
1736
|
+
if (this.hasIntermediateState() && !this.isLeafOnlyMode()) {
|
|
1737
|
+
this.treeService.updateParentStates(this.nodes(), node, this.hasIntermediateState(), this.idField(), this.childrenField(), this.selectedField(), this.indeterminateField());
|
|
866
1738
|
}
|
|
867
1739
|
this.refreshNodes();
|
|
868
1740
|
this.onNodeSelect.emit({
|
|
@@ -871,18 +1743,19 @@ class AXTreeViewComponent {
|
|
|
871
1743
|
nativeEvent: event,
|
|
872
1744
|
isUserInteraction: true,
|
|
873
1745
|
});
|
|
1746
|
+
this.emitSelectionChange();
|
|
874
1747
|
}
|
|
875
1748
|
/**
|
|
876
1749
|
* Get array reference by drop list ID
|
|
877
1750
|
*/
|
|
878
1751
|
getArrayByListId(listId) {
|
|
879
|
-
return this.treeService.getArrayByListId(this.nodes(), listId);
|
|
1752
|
+
return this.treeService.getArrayByListId(this.nodes(), listId, this.idField(), this.childrenField());
|
|
880
1753
|
}
|
|
881
1754
|
/**
|
|
882
1755
|
* Find parent node by list ID
|
|
883
1756
|
*/
|
|
884
1757
|
findParentByListId(listId) {
|
|
885
|
-
return this.treeService.findParentByListId(this.nodes(), listId);
|
|
1758
|
+
return this.treeService.findParentByListId(this.nodes(), listId, this.idField());
|
|
886
1759
|
}
|
|
887
1760
|
/**
|
|
888
1761
|
* Check if move operation is allowed based on dragOperationType
|
|
@@ -967,7 +1840,7 @@ class AXTreeViewComponent {
|
|
|
967
1840
|
const focusedId = this.focusedNodeId();
|
|
968
1841
|
if (!focusedId)
|
|
969
1842
|
return null;
|
|
970
|
-
return this.treeService.findNodeById(this.nodes(), focusedId);
|
|
1843
|
+
return this.treeService.findNodeById(this.nodes(), focusedId, this.idField());
|
|
971
1844
|
}
|
|
972
1845
|
/**
|
|
973
1846
|
* Set focus to a node by ID
|
|
@@ -1011,14 +1884,14 @@ class AXTreeViewComponent {
|
|
|
1011
1884
|
break;
|
|
1012
1885
|
case 'ArrowLeft':
|
|
1013
1886
|
if (currentFocused) {
|
|
1014
|
-
if (currentFocused
|
|
1887
|
+
if (this.getNodeExpanded(currentFocused) && this.shouldShowExpandToggle(currentFocused)) {
|
|
1015
1888
|
this.toggleNode(currentFocused, event);
|
|
1016
1889
|
return { handled: true, shouldPreventDefault: true, targetIndex: null };
|
|
1017
1890
|
}
|
|
1018
1891
|
else {
|
|
1019
1892
|
const currentItem = flatList[currentIndex];
|
|
1020
1893
|
if (currentItem?.parent) {
|
|
1021
|
-
targetIndex = flatList.findIndex((item) => item.node
|
|
1894
|
+
targetIndex = flatList.findIndex((item) => this.getNodeId(item.node) === this.getNodeId(currentItem.parent));
|
|
1022
1895
|
}
|
|
1023
1896
|
else {
|
|
1024
1897
|
shouldPreventDefault = false;
|
|
@@ -1031,22 +1904,21 @@ class AXTreeViewComponent {
|
|
|
1031
1904
|
break;
|
|
1032
1905
|
case 'ArrowRight':
|
|
1033
1906
|
if (currentFocused) {
|
|
1034
|
-
if (!currentFocused
|
|
1907
|
+
if (!this.getNodeExpanded(currentFocused) && this.shouldShowExpandToggle(currentFocused)) {
|
|
1035
1908
|
this.toggleNode(currentFocused, event);
|
|
1036
1909
|
return { handled: true, shouldPreventDefault: true, targetIndex: null };
|
|
1037
1910
|
}
|
|
1038
|
-
else if (currentFocused
|
|
1039
|
-
this.treeService.hasChildren(currentFocused)
|
|
1040
|
-
currentFocused
|
|
1041
|
-
|
|
1042
|
-
if (children.length > 0) {
|
|
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) {
|
|
1043
1915
|
const firstChild = children[0];
|
|
1044
|
-
targetIndex = flatList.findIndex((item) => item.node
|
|
1916
|
+
targetIndex = flatList.findIndex((item) => this.getNodeId(item.node) === this.getNodeId(firstChild));
|
|
1045
1917
|
if (targetIndex === -1) {
|
|
1046
|
-
const updatedFlatList = this.treeService.buildFlatNodeList(this.nodes());
|
|
1047
|
-
targetIndex = updatedFlatList.findIndex((item) => item.node
|
|
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));
|
|
1048
1920
|
if (targetIndex >= 0 && targetIndex < updatedFlatList.length) {
|
|
1049
|
-
this.focusNodeById(updatedFlatList[targetIndex].node
|
|
1921
|
+
this.focusNodeById(this.getNodeId(updatedFlatList[targetIndex].node));
|
|
1050
1922
|
return { handled: true, shouldPreventDefault: true, targetIndex: null };
|
|
1051
1923
|
}
|
|
1052
1924
|
}
|
|
@@ -1071,7 +1943,7 @@ class AXTreeViewComponent {
|
|
|
1071
1943
|
break;
|
|
1072
1944
|
case ' ':
|
|
1073
1945
|
case 'Space':
|
|
1074
|
-
if (currentFocused && this.selectMode() === 'multiple') {
|
|
1946
|
+
if (currentFocused && this.selectMode() === 'multiple' && this.canSelectNode(currentFocused)) {
|
|
1075
1947
|
event.preventDefault();
|
|
1076
1948
|
this.handleSpaceKeySelection(currentFocused, event);
|
|
1077
1949
|
return { handled: true, shouldPreventDefault: true, targetIndex: null };
|
|
@@ -1079,7 +1951,7 @@ class AXTreeViewComponent {
|
|
|
1079
1951
|
shouldPreventDefault = false;
|
|
1080
1952
|
break;
|
|
1081
1953
|
case 'Enter':
|
|
1082
|
-
if (currentFocused) {
|
|
1954
|
+
if (currentFocused && this.canSelectNode(currentFocused)) {
|
|
1083
1955
|
event.preventDefault();
|
|
1084
1956
|
this.handleEnterKeySelection(currentFocused, event);
|
|
1085
1957
|
return { handled: true, shouldPreventDefault: true, targetIndex: null };
|
|
@@ -1088,7 +1960,7 @@ class AXTreeViewComponent {
|
|
|
1088
1960
|
break;
|
|
1089
1961
|
default:
|
|
1090
1962
|
if ((event.ctrlKey || event.metaKey) && event.key === 'Enter') {
|
|
1091
|
-
if (currentFocused && this.selectMode() === 'multiple') {
|
|
1963
|
+
if (currentFocused && this.selectMode() === 'multiple' && this.canSelectNode(currentFocused)) {
|
|
1092
1964
|
event.preventDefault();
|
|
1093
1965
|
this.handleCtrlEnterSelection(currentFocused, event);
|
|
1094
1966
|
return { handled: true, shouldPreventDefault: true, targetIndex: null };
|
|
@@ -1104,14 +1976,17 @@ class AXTreeViewComponent {
|
|
|
1104
1976
|
* Handle Space key selection
|
|
1105
1977
|
*/
|
|
1106
1978
|
handleSpaceKeySelection(node, event) {
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
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());
|
|
1112
1987
|
}
|
|
1113
|
-
if (this.
|
|
1114
|
-
this.treeService.updateParentStates(this.nodes(), node, this.
|
|
1988
|
+
if (this.hasIntermediateState() && !this.isLeafOnlyMode()) {
|
|
1989
|
+
this.treeService.updateParentStates(this.nodes(), node, this.hasIntermediateState(), this.idField(), this.childrenField(), this.selectedField(), this.indeterminateField());
|
|
1115
1990
|
}
|
|
1116
1991
|
this.refreshNodes();
|
|
1117
1992
|
this.onNodeSelect.emit({
|
|
@@ -1120,21 +1995,25 @@ class AXTreeViewComponent {
|
|
|
1120
1995
|
nativeEvent: event,
|
|
1121
1996
|
isUserInteraction: true,
|
|
1122
1997
|
});
|
|
1998
|
+
this.emitSelectionChange();
|
|
1123
1999
|
}
|
|
1124
2000
|
/**
|
|
1125
2001
|
* Handle Enter key selection
|
|
1126
2002
|
*/
|
|
1127
2003
|
handleEnterKeySelection(node, event) {
|
|
2004
|
+
if (!this.canSelectNode(node))
|
|
2005
|
+
return;
|
|
1128
2006
|
const mode = this.selectMode();
|
|
1129
|
-
this.treeService.deselectAllNodes(this.nodes());
|
|
1130
|
-
node
|
|
1131
|
-
node
|
|
2007
|
+
this.treeService.deselectAllNodes(this.nodes(), this.selectedField(), this.indeterminateField(), this.childrenField());
|
|
2008
|
+
this.setNodeSelected(node, true);
|
|
2009
|
+
this.setNodeIndeterminate(node, false);
|
|
1132
2010
|
if (mode === 'multiple') {
|
|
1133
|
-
|
|
1134
|
-
|
|
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());
|
|
1135
2014
|
}
|
|
1136
|
-
if (this.
|
|
1137
|
-
this.treeService.updateParentStates(this.nodes(), node, this.
|
|
2015
|
+
if (this.hasIntermediateState() && !this.isLeafOnlyMode()) {
|
|
2016
|
+
this.treeService.updateParentStates(this.nodes(), node, this.hasIntermediateState(), this.idField(), this.childrenField(), this.selectedField(), this.indeterminateField());
|
|
1138
2017
|
}
|
|
1139
2018
|
}
|
|
1140
2019
|
this.refreshNodes();
|
|
@@ -1144,19 +2023,23 @@ class AXTreeViewComponent {
|
|
|
1144
2023
|
nativeEvent: event,
|
|
1145
2024
|
isUserInteraction: true,
|
|
1146
2025
|
});
|
|
2026
|
+
this.emitSelectionChange();
|
|
1147
2027
|
}
|
|
1148
2028
|
/**
|
|
1149
2029
|
* Handle Ctrl/Cmd + Enter key selection
|
|
1150
2030
|
*/
|
|
1151
2031
|
handleCtrlEnterSelection(node, event) {
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
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());
|
|
1157
2040
|
}
|
|
1158
|
-
if (this.
|
|
1159
|
-
this.treeService.updateParentStates(this.nodes(), node, this.
|
|
2041
|
+
if (this.hasIntermediateState() && !this.isLeafOnlyMode()) {
|
|
2042
|
+
this.treeService.updateParentStates(this.nodes(), node, this.hasIntermediateState(), this.idField(), this.childrenField(), this.selectedField(), this.indeterminateField());
|
|
1160
2043
|
}
|
|
1161
2044
|
this.refreshNodes();
|
|
1162
2045
|
this.onNodeSelect.emit({
|
|
@@ -1165,6 +2048,7 @@ class AXTreeViewComponent {
|
|
|
1165
2048
|
nativeEvent: event,
|
|
1166
2049
|
isUserInteraction: true,
|
|
1167
2050
|
});
|
|
2051
|
+
this.emitSelectionChange();
|
|
1168
2052
|
}
|
|
1169
2053
|
/**
|
|
1170
2054
|
* Type guard to check if value is an Event
|
|
@@ -1184,7 +2068,7 @@ class AXTreeViewComponent {
|
|
|
1184
2068
|
}
|
|
1185
2069
|
}
|
|
1186
2070
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: AXTreeViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1187
|
-
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 }, checkChildrenOnSelect: { classPropertyName: "checkChildrenOnSelect", publicName: "checkChildrenOnSelect", isSignal: true, isRequired: false, transformFunction: null }, intermediateState: { classPropertyName: "intermediateState", publicName: "intermediateState", 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 }, nodeHeight: { classPropertyName: "nodeHeight", publicName: "nodeHeight", isSignal: true, isRequired: false, transformFunction: null }, look: { classPropertyName: "look", publicName: "look", isSignal: true, isRequired: false, transformFunction: null }, itemTemplate: { classPropertyName: "itemTemplate", publicName: "itemTemplate", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { datasource: "datasourceChange", onBeforeDrop: "onBeforeDrop", onNodeToggle: "onNodeToggle", onNodeSelect: "onNodeSelect", 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 [class.ax-tree-view-compact]=\"nodeHeight() === 'compact'\"\n [class.ax-tree-view-comfortable]=\"nodeHeight() === 'comfortable'\"\n role=\"group\"\n>\n @for (node of nodes(); track node.id) {\n @if (node.visible !== false) {\n <div\n [axDrag]=\"dragMode() !== 'none'\"\n [dragDisabled]=\"node.disabled\"\n [dragData]=\"node\"\n class=\"ax-tree-view-node\"\n [class.ax-tree-view-node-selected]=\"node.selected\"\n [class.ax-tree-view-node-disabled]=\"node.disabled\"\n [class.ax-tree-view-node-loading]=\"node.loading\"\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]=\"node.disabled ? 'true' : null\"\n >\n <div\n class=\"ax-tree-view-node-content\"\n [axDropList]=\"dragMode() !== 'none'\"\n [id]=\"'ax-tree-view-node-drop-' + node.id\"\n [attr.data-node-id]=\"node.id\"\n [attr.data-tree-node-id]=\"node.id\"\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(node.id)\"\n [tabindex]=\"isNodeFocused(node.id) ? 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]=\"node.expanded\"\n [disabled]=\"node.disabled || node.loading\"\n [style.visibility]=\"shouldShowExpandToggle(node) ? 'visible' : 'hidden'\"\n >\n @if (node.loading) {\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]=\"node.expanded ? directionExpandedIcon() : directionCollapsedIcon()\"\n class=\"ax-tree-view-toggle-icon\"\n ></i>\n </ax-icon>\n }\n </ax-button>\n @if (itemTemplate()) {\n <ng-container\n [ngTemplateOutlet]=\"itemTemplate()!\"\n [ngTemplateOutletContext]=\"getTemplateContext(node, 0)\"\n ></ng-container>\n } @else {\n @if (shouldShowCheckbox()) {\n <ax-check-box\n class=\"ax-tree-view-checkbox\"\n [ngModel]=\"node.indeterminate ? null : node.selected || false\"\n [indeterminate]=\"node.indeterminate || false\"\n (onValueChanged)=\"toggleSelection(node, $event)\"\n ></ax-check-box>\n }\n @if (showIcons() && node.icon) {\n <i [class]=\"node.icon\" class=\"ax-tree-view-node-icon\"></i>\n }\n <span class=\"ax-tree-view-node-label\">{{ node.label }}</span>\n @if (showChildrenBadge() && (node.childrenCount || (node.children && node.children.length > 0))) {\n <ax-badge\n class=\"ax-tree-view-children-badge\"\n [text]=\"(node.childrenCount ?? node.children?.length ?? 0).toString()\"\n ></ax-badge>\n }\n }\n </div>\n </div>\n @if (node.expanded && node.children && node.children.length > 0) {\n <div class=\"ax-tree-view-children\" role=\"group\">\n <ng-container\n [ngTemplateOutlet]=\"childrenList\"\n [ngTemplateOutletContext]=\"{ children: node.children, 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]=\"parent.id\"\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 node.id) {\n @if (node.visible !== false) {\n <div\n [axDrag]=\"dragMode() !== 'none'\"\n [dragDisabled]=\"node.disabled\"\n [dragData]=\"node\"\n class=\"ax-tree-view-node\"\n [class.ax-tree-view-node-selected]=\"node.selected\"\n [class.ax-tree-view-node-disabled]=\"node.disabled\"\n [class.ax-tree-view-node-loading]=\"node.loading\"\n [class.ax-tree-view-node-focused]=\"isNodeFocused(node.id)\"\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]=\"node.disabled ? 'true' : null\"\n >\n <div\n class=\"ax-tree-view-node-content\"\n [style.padding-inline-start.px]=\"getNodePaddingInline(level)\"\n [axDropList]=\"dragMode() !== 'none'\"\n [id]=\"'ax-tree-view-node-drop-' + node.id\"\n [attr.data-node-id]=\"node.id\"\n [attr.data-tree-node-id]=\"node.id\"\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(node.id)\"\n (blur)=\"focusedNodeId.set(null)\"\n [tabindex]=\"isNodeFocused(node.id) ? 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]=\"node.expanded\"\n [disabled]=\"node.disabled || node.loading\"\n [style.visibility]=\"shouldShowExpandToggle(node) ? 'visible' : 'hidden'\"\n >\n @if (node.loading) {\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]=\"node.expanded ? directionExpandedIcon() : directionCollapsedIcon()\"\n class=\"ax-tree-view-toggle-icon\"\n ></i>\n </ax-icon>\n }\n </ax-button>\n\n @if (itemTemplate()) {\n <ng-container\n [ngTemplateOutlet]=\"itemTemplate()!\"\n [ngTemplateOutletContext]=\"getTemplateContext(node, level)\"\n ></ng-container>\n } @else {\n @if (shouldShowCheckbox()) {\n <ax-check-box\n class=\"ax-tree-view-checkbox\"\n [ngModel]=\"node.indeterminate ? null : node.selected || false\"\n [indeterminate]=\"node.indeterminate || false\"\n (onValueChanged)=\"toggleSelection(node, $event)\"\n ></ax-check-box>\n }\n @if (showIcons() && node.icon) {\n <i [class]=\"node.icon\" class=\"ax-tree-view-node-icon\"></i>\n }\n <span class=\"ax-tree-view-node-label\">{{ node.label }}</span>\n @if (showChildrenBadge() && (node.childrenCount || (node.children && node.children.length > 0))) {\n <ax-badge\n class=\"ax-tree-view-children-badge\"\n [text]=\"(node.childrenCount ?? node.children?.length ?? 0).toString()\"\n ></ax-badge>\n }\n }\n </div>\n </div>\n @if (node.expanded && node.children && node.children.length > 0) {\n <div class=\"ax-tree-view-children\" role=\"group\">\n <ng-container\n [ngTemplateOutlet]=\"childrenList\"\n [ngTemplateOutletContext]=\"{ children: node.children, 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-expand-toggle-padding: .25rem;--ax-comp-tree-view-card-node-margin: .5rem;--ax-comp-tree-view-card-content-padding: 1rem;--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-compact .ax-tree-view-node-content{padding:var(--ax-comp-tree-view-content-padding, .25rem .5rem);gap:var(--ax-comp-tree-view-content-gap, .375rem);font-size:.8125rem}.ax-tree-view-comfortable .ax-tree-view-node-content{padding:var(--ax-comp-tree-view-content-padding, .75rem .625rem);gap:var(--ax-comp-tree-view-content-gap, .625rem);font-size:.9375rem}.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);border:1px solid transparent;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);border-color:currentColor}.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:1px solid transparent;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)}.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-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);margin:var(--ax-comp-tree-view-card-node-margin) 0}.ax-tree-view-card .ax-tree-view-node-content{padding:var(--ax-comp-tree-view-card-content-padding)}.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"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
|
|
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 }); }
|
|
1188
2072
|
}
|
|
1189
2073
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: AXTreeViewComponent, decorators: [{
|
|
1190
2074
|
type: Component,
|
|
@@ -1200,6 +2084,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImpor
|
|
|
1200
2084
|
AXCheckBoxComponent,
|
|
1201
2085
|
AXBadgeComponent,
|
|
1202
2086
|
AXDecoratorIconComponent,
|
|
2087
|
+
AXTooltipDirective,
|
|
1203
2088
|
], host: {
|
|
1204
2089
|
class: 'ax-tree-view',
|
|
1205
2090
|
'[class.ax-tree-view-default]': "look() === 'default'",
|
|
@@ -1214,8 +2099,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImpor
|
|
|
1214
2099
|
'(focus)': 'onTreeFocus($event)',
|
|
1215
2100
|
'(blur)': 'onTreeBlur($event)',
|
|
1216
2101
|
tabindex: '0',
|
|
1217
|
-
}, 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 [class.ax-tree-view-compact]=\"nodeHeight() === 'compact'\"\n [class.ax-tree-view-comfortable]=\"nodeHeight() === 'comfortable'\"\n role=\"group\"\n>\n @for (node of nodes(); track node.id) {\n @if (node.visible !== false) {\n <div\n [axDrag]=\"dragMode() !== 'none'\"\n [dragDisabled]=\"node.disabled\"\n [dragData]=\"node\"\n class=\"ax-tree-view-node\"\n [class.ax-tree-view-node-selected]=\"node.selected\"\n [class.ax-tree-view-node-disabled]=\"node.disabled\"\n [class.ax-tree-view-node-loading]=\"node.loading\"\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]=\"node.disabled ? 'true' : null\"\n >\n <div\n class=\"ax-tree-view-node-content\"\n [axDropList]=\"dragMode() !== 'none'\"\n [id]=\"'ax-tree-view-node-drop-' + node.id\"\n [attr.data-node-id]=\"node.id\"\n [attr.data-tree-node-id]=\"node.id\"\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(node.id)\"\n [tabindex]=\"isNodeFocused(node.id) ? 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]=\"node.expanded\"\n [disabled]=\"node.disabled || node.loading\"\n [style.visibility]=\"shouldShowExpandToggle(node) ? 'visible' : 'hidden'\"\n >\n @if (node.loading) {\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]=\"node.expanded ? directionExpandedIcon() : directionCollapsedIcon()\"\n class=\"ax-tree-view-toggle-icon\"\n ></i>\n </ax-icon>\n }\n </ax-button>\n @if (itemTemplate()) {\n <ng-container\n [ngTemplateOutlet]=\"itemTemplate()!\"\n [ngTemplateOutletContext]=\"getTemplateContext(node, 0)\"\n ></ng-container>\n } @else {\n @if (shouldShowCheckbox()) {\n <ax-check-box\n class=\"ax-tree-view-checkbox\"\n [ngModel]=\"node.indeterminate ? null : node.selected || false\"\n [indeterminate]=\"node.indeterminate || false\"\n (onValueChanged)=\"toggleSelection(node, $event)\"\n ></ax-check-box>\n }\n @if (showIcons() && node.icon) {\n <i [class]=\"node.icon\" class=\"ax-tree-view-node-icon\"></i>\n }\n <span class=\"ax-tree-view-node-label\">{{ node.label }}</span>\n @if (showChildrenBadge() && (node.childrenCount || (node.children && node.children.length > 0))) {\n <ax-badge\n class=\"ax-tree-view-children-badge\"\n [text]=\"(node.childrenCount ?? node.children?.length ?? 0).toString()\"\n ></ax-badge>\n }\n }\n </div>\n </div>\n @if (node.expanded && node.children && node.children.length > 0) {\n <div class=\"ax-tree-view-children\" role=\"group\">\n <ng-container\n [ngTemplateOutlet]=\"childrenList\"\n [ngTemplateOutletContext]=\"{ children: node.children, 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]=\"parent.id\"\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 node.id) {\n @if (node.visible !== false) {\n <div\n [axDrag]=\"dragMode() !== 'none'\"\n [dragDisabled]=\"node.disabled\"\n [dragData]=\"node\"\n class=\"ax-tree-view-node\"\n [class.ax-tree-view-node-selected]=\"node.selected\"\n [class.ax-tree-view-node-disabled]=\"node.disabled\"\n [class.ax-tree-view-node-loading]=\"node.loading\"\n [class.ax-tree-view-node-focused]=\"isNodeFocused(node.id)\"\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]=\"node.disabled ? 'true' : null\"\n >\n <div\n class=\"ax-tree-view-node-content\"\n [style.padding-inline-start.px]=\"getNodePaddingInline(level)\"\n [axDropList]=\"dragMode() !== 'none'\"\n [id]=\"'ax-tree-view-node-drop-' + node.id\"\n [attr.data-node-id]=\"node.id\"\n [attr.data-tree-node-id]=\"node.id\"\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(node.id)\"\n (blur)=\"focusedNodeId.set(null)\"\n [tabindex]=\"isNodeFocused(node.id) ? 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]=\"node.expanded\"\n [disabled]=\"node.disabled || node.loading\"\n [style.visibility]=\"shouldShowExpandToggle(node) ? 'visible' : 'hidden'\"\n >\n @if (node.loading) {\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]=\"node.expanded ? directionExpandedIcon() : directionCollapsedIcon()\"\n class=\"ax-tree-view-toggle-icon\"\n ></i>\n </ax-icon>\n }\n </ax-button>\n\n @if (itemTemplate()) {\n <ng-container\n [ngTemplateOutlet]=\"itemTemplate()!\"\n [ngTemplateOutletContext]=\"getTemplateContext(node, level)\"\n ></ng-container>\n } @else {\n @if (shouldShowCheckbox()) {\n <ax-check-box\n class=\"ax-tree-view-checkbox\"\n [ngModel]=\"node.indeterminate ? null : node.selected || false\"\n [indeterminate]=\"node.indeterminate || false\"\n (onValueChanged)=\"toggleSelection(node, $event)\"\n ></ax-check-box>\n }\n @if (showIcons() && node.icon) {\n <i [class]=\"node.icon\" class=\"ax-tree-view-node-icon\"></i>\n }\n <span class=\"ax-tree-view-node-label\">{{ node.label }}</span>\n @if (showChildrenBadge() && (node.childrenCount || (node.children && node.children.length > 0))) {\n <ax-badge\n class=\"ax-tree-view-children-badge\"\n [text]=\"(node.childrenCount ?? node.children?.length ?? 0).toString()\"\n ></ax-badge>\n }\n }\n </div>\n </div>\n @if (node.expanded && node.children && node.children.length > 0) {\n <div class=\"ax-tree-view-children\" role=\"group\">\n <ng-container\n [ngTemplateOutlet]=\"childrenList\"\n [ngTemplateOutletContext]=\"{ children: node.children, 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-expand-toggle-padding: .25rem;--ax-comp-tree-view-card-node-margin: .5rem;--ax-comp-tree-view-card-content-padding: 1rem;--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-compact .ax-tree-view-node-content{padding:var(--ax-comp-tree-view-content-padding, .25rem .5rem);gap:var(--ax-comp-tree-view-content-gap, .375rem);font-size:.8125rem}.ax-tree-view-comfortable .ax-tree-view-node-content{padding:var(--ax-comp-tree-view-content-padding, .75rem .625rem);gap:var(--ax-comp-tree-view-content-gap, .625rem);font-size:.9375rem}.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);border:1px solid transparent;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);border-color:currentColor}.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:1px solid transparent;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)}.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-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);margin:var(--ax-comp-tree-view-card-node-margin) 0}.ax-tree-view-card .ax-tree-view-node-content{padding:var(--ax-comp-tree-view-card-content-padding)}.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"] }]
|
|
1218
|
-
}], 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 }] }],
|
|
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"] }] } });
|
|
1219
2104
|
|
|
1220
2105
|
class AXTreeViewModule {
|
|
1221
2106
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: AXTreeViewModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
|