@acorex/components 20.2.49 → 20.2.51

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.
@@ -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.id === id)
33
+ if (node[idField] === id)
33
34
  return node;
34
- if (node.children) {
35
- const found = this.findNodeById(node.children, id);
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
- if (node.children?.some((child) => child.id === targetNode.id)) {
50
+ const children = node[childrenField];
51
+ if (children?.some((child) => child[idField] === targetId)) {
48
52
  return node;
49
53
  }
50
- if (node.children) {
51
- const found = this.findParentNode(node.children, targetNode);
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
- if (movedNode.id === targetNode.id || this.isNodeDescendantOf(targetNode, movedNode)) {
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
- if (targetNode.id === ancestorNode.id) {
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
- if (!ancestorNode.children || ancestorNode.children.length === 0) {
83
+ const children = ancestorNode[childrenField];
84
+ if (!children || children.length === 0) {
76
85
  return false;
77
86
  }
78
- for (const child of ancestorNode.children) {
79
- if (child.id === targetNode.id) {
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
- if (node.visible !== false && !node.disabled) {
105
+ const hidden = node[hiddenField];
106
+ const disabled = node[disabledField];
107
+ if (hidden !== true && !disabled) {
96
108
  flatList.push({ node, level, parent });
97
- if (node.expanded && node.children) {
98
- traverse(node.children, level + 1, node);
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
- return Boolean(node.children?.length);
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
- return isLazyDataSource && Boolean(node.childrenCount && node.childrenCount > 0 && !this.hasChildren(node));
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.selected = selected;
126
- child.indeterminate = false;
127
- if (child.children) {
128
- this.selectAllChildren(child.children, selected);
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
- if (child.selected && !child.indeterminate) {
156
+ const selected = child[selectedField];
157
+ const indeterminate = child[indeterminateField];
158
+ if (selected && !indeterminate) {
140
159
  selectedCount++;
141
160
  }
142
- if (child.indeterminate) {
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
- if (!parent || !parent.children)
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(parent.children);
178
+ const { allSelected, someSelected } = this.getChildrenSelectionState(parentChildren, selectedField, indeterminateField);
159
179
  if (allSelected) {
160
- parent.selected = true;
161
- parent.indeterminate = false;
180
+ parent[selectedField] = true;
181
+ parent[indeterminateField] = false;
162
182
  }
163
183
  else if (someSelected) {
164
184
  if (intermediateState) {
165
- parent.selected = true;
166
- parent.indeterminate = true;
185
+ parent[selectedField] = true;
186
+ parent[indeterminateField] = true;
167
187
  }
168
188
  else {
169
- parent.selected = false;
170
- parent.indeterminate = false;
189
+ parent[selectedField] = false;
190
+ parent[indeterminateField] = false;
171
191
  }
172
192
  }
173
193
  else {
174
- parent.selected = false;
175
- parent.indeterminate = false;
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.selected = false;
185
- node.indeterminate = false;
186
- if (node.children) {
187
- this.deselectAllNodes(node.children);
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.selected = selected;
197
- node.indeterminate = false;
198
- if (node.children) {
199
- this.setAllSelection(node.children, selected);
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.selected)
231
+ if (node[selectedField])
210
232
  count++;
211
- if (node.children) {
212
- count += this.countSelected(node.children);
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.selected)
245
+ if (node[selectedField])
223
246
  result.push(node);
224
- if (node.children) {
225
- this.collectSelected(node.children, result);
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
- if (nodes[i].selected && !nodes[i].indeterminate) {
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 if (nodes[i].children) {
238
- this.removeSelected(nodes[i].children ?? []);
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
- if (node.children && node.children.length > 0) {
248
- this.updateAllParentStates(node.children, intermediateState);
249
- const { allSelected, someSelected } = this.getChildrenSelectionState(node.children);
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.selected = true;
252
- node.indeterminate = false;
282
+ node[selectedField] = true;
283
+ node[indeterminateField] = false;
253
284
  }
254
285
  else if (someSelected) {
255
286
  if (intermediateState) {
256
- node.selected = true;
257
- node.indeterminate = true;
287
+ node[selectedField] = true;
288
+ node[indeterminateField] = true;
258
289
  }
259
290
  else {
260
- node.selected = false;
261
- node.indeterminate = false;
291
+ node[selectedField] = false;
292
+ node[indeterminateField] = false;
262
293
  }
263
294
  }
264
295
  else {
265
- node.selected = false;
266
- node.indeterminate = false;
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.expanded = expanded;
284
- if (node.children) {
285
- await this.setExpandedState(node.children, expanded, isLazyDataSource, loadNodeChildren);
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
- return node?.children ?? null;
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
- return node ? `${AXTreeViewService.LIST_PREFIX}${node.id}` : AXTreeViewService.ROOT_LIST_ID;
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
@@ -345,13 +381,14 @@ class AXTreeViewService {
345
381
  /**
346
382
  * Get all nodes in a flat array
347
383
  */
348
- getAllNodes(nodes) {
384
+ getAllNodes(nodes, childrenField = 'children') {
349
385
  const allNodes = [];
350
386
  const traverse = (nodeList) => {
351
387
  for (const node of nodeList) {
352
388
  allNodes.push(node);
353
- if (node.children) {
354
- traverse(node.children);
389
+ const children = node[childrenField];
390
+ if (children) {
391
+ traverse(children);
355
392
  }
356
393
  }
357
394
  };
@@ -361,16 +398,16 @@ class AXTreeViewService {
361
398
  /**
362
399
  * Get the path to a node (array of parent nodes from root to node)
363
400
  */
364
- getNodePath(nodes, nodeId) {
401
+ getNodePath(nodes, nodeId, idField = 'id', childrenField = 'children') {
365
402
  const path = [];
366
- const node = this.findNodeById(nodes, nodeId);
403
+ const node = this.findNodeById(nodes, nodeId, idField);
367
404
  if (!node) {
368
405
  return path;
369
406
  }
370
407
  let current = node;
371
408
  while (current) {
372
409
  path.unshift(current);
373
- const parent = this.findParentNode(nodes, current);
410
+ const parent = this.findParentNode(nodes, current, idField, childrenField);
374
411
  current = parent ?? null;
375
412
  }
376
413
  return path;
@@ -378,70 +415,255 @@ class AXTreeViewService {
378
415
  /**
379
416
  * Get the level/depth of a node (0 = root level)
380
417
  */
381
- getNodeLevel(nodes, nodeId) {
382
- const path = this.getNodePath(nodes, nodeId);
418
+ getNodeLevel(nodes, nodeId, idField = 'id', childrenField = 'children') {
419
+ const path = this.getNodePath(nodes, nodeId, idField, childrenField);
383
420
  return path.length > 0 ? path.length - 1 : -1;
384
421
  }
385
422
  /**
386
423
  * Get sibling nodes of a given node
387
424
  */
388
- getSiblings(nodes, nodeId) {
389
- const node = this.findNodeById(nodes, nodeId);
425
+ getSiblings(nodes, nodeId, idField = 'id', childrenField = 'children') {
426
+ const node = this.findNodeById(nodes, nodeId, idField);
390
427
  if (!node) {
391
428
  return [];
392
429
  }
393
- const parent = this.findParentNode(nodes, node);
394
- const siblingsArray = parent?.children ?? nodes;
395
- return siblingsArray.filter((n) => n.id !== nodeId);
430
+ const parent = this.findParentNode(nodes, node, idField, childrenField);
431
+ const siblingsArray = parent?.[childrenField] ?? nodes;
432
+ return siblingsArray.filter((n) => n[idField] !== nodeId);
396
433
  }
397
434
  /**
398
435
  * Clone a node (creates a deep copy)
399
436
  */
400
- cloneNode(node) {
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') {
401
438
  const cloned = {
402
- id: `${node.id}-clone-${Date.now()}`,
403
- label: node.label,
404
- icon: node.icon,
405
- expanded: node.expanded,
406
- selected: false, // Cloned nodes are not selected by default
407
- indeterminate: false,
408
- disabled: node.disabled,
409
- visible: node.visible,
410
- childrenCount: node.childrenCount,
411
- loading: false, // Cloned nodes are not loading
412
- data: node.data ? JSON.parse(JSON.stringify(node.data)) : undefined,
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,
413
450
  };
414
- if (node.children && node.children.length > 0) {
415
- cloned.children = node.children.map((child) => this.cloneNode(child));
416
- cloned.childrenCount = cloned.children.length;
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;
417
455
  }
418
456
  return cloned;
419
457
  }
458
+ // ==================== Node Manipulation ====================
459
+ /**
460
+ * Move a node to a new parent in the tree
461
+ * @param nodes - Root nodes array
462
+ * @param nodeId - The ID of the node to move
463
+ * @param newParentId - The ID of the new parent (undefined for root level)
464
+ * @param index - Optional index to insert at (default: append to end)
465
+ * @param idField - Field name for node ID
466
+ * @param childrenField - Field name for children array
467
+ * @param childrenCountField - Field name for children count
468
+ * @param expandedField - Field name for expanded state
469
+ * @returns Object with success status, moved node, previous parent, new parent, and indices
470
+ */
471
+ moveNode(nodes, nodeId, newParentId, index, idField = 'id', childrenField = 'children', childrenCountField = 'childrenCount', expandedField = 'expanded') {
472
+ const node = this.findNodeById(nodes, nodeId, idField);
473
+ if (!node) {
474
+ return { success: false, previousIndex: -1, currentIndex: -1 };
475
+ }
476
+ // Find current parent
477
+ const currentParent = this.findParentNode(nodes, node, idField, childrenField);
478
+ const currentParentChildren = currentParent
479
+ ? currentParent[childrenField]
480
+ : undefined;
481
+ const currentArray = currentParentChildren ?? nodes;
482
+ // Find and remove from current location
483
+ const currentIndex = currentArray.findIndex((n) => n[idField] === nodeId);
484
+ if (currentIndex === -1) {
485
+ return { success: false, previousIndex: -1, currentIndex: -1 };
486
+ }
487
+ const movedNode = currentArray.splice(currentIndex, 1)[0];
488
+ // Find new parent
489
+ let targetArray;
490
+ let newParent;
491
+ if (newParentId) {
492
+ newParent = this.findNodeById(nodes, newParentId, idField);
493
+ if (!newParent) {
494
+ // Restore node if new parent not found
495
+ currentArray.splice(currentIndex, 0, movedNode);
496
+ return { success: false, previousIndex: currentIndex, currentIndex: -1 };
497
+ }
498
+ // Validate drop target
499
+ if (!this.isValidDropTarget(movedNode, newParent, idField, childrenField)) {
500
+ // Restore node if invalid drop target
501
+ currentArray.splice(currentIndex, 0, movedNode);
502
+ return { success: false, previousIndex: currentIndex, currentIndex: -1 };
503
+ }
504
+ let newParentChildren = newParent[childrenField];
505
+ if (!newParentChildren) {
506
+ newParentChildren = [];
507
+ newParent[childrenField] = newParentChildren;
508
+ }
509
+ targetArray = newParentChildren;
510
+ }
511
+ else {
512
+ targetArray = nodes;
513
+ }
514
+ // Calculate new index before inserting
515
+ const newIndex = index !== undefined && index >= 0 && index <= targetArray.length ? index : targetArray.length;
516
+ // Insert at new location
517
+ if (index !== undefined && index >= 0 && index <= targetArray.length) {
518
+ targetArray.splice(index, 0, movedNode);
519
+ }
520
+ else {
521
+ targetArray.push(movedNode);
522
+ }
523
+ // Update childrenCount
524
+ if (currentParent) {
525
+ const updatedChildren = currentParent[childrenField];
526
+ currentParent[childrenCountField] = updatedChildren?.length ?? 0;
527
+ }
528
+ if (newParent) {
529
+ const updatedChildren = newParent[childrenField];
530
+ newParent[childrenCountField] = updatedChildren?.length ?? 0;
531
+ newParent[expandedField] = true; // Auto-expand new parent
532
+ }
533
+ return {
534
+ success: true,
535
+ movedNode,
536
+ previousParent: currentParent,
537
+ newParent,
538
+ previousIndex: currentIndex,
539
+ currentIndex: newIndex,
540
+ };
541
+ }
542
+ /**
543
+ * Edit/update a node's properties
544
+ * @param nodes - Root nodes array
545
+ * @param nodeId - The ID of the node to edit
546
+ * @param updates - Partial node object with properties to update
547
+ * @param idField - Field name for node ID
548
+ * @param childrenField - Field name for children array
549
+ * @param childrenCountField - Field name for children count
550
+ * @returns The updated node if found, null otherwise
551
+ */
552
+ editNode(nodes, nodeId, updates, idField = 'id', childrenField = 'children', childrenCountField = 'childrenCount') {
553
+ const node = this.findNodeById(nodes, nodeId, idField);
554
+ if (!node) {
555
+ return null;
556
+ }
557
+ // Update node properties
558
+ Object.assign(node, updates);
559
+ // If children array is provided, ensure it exists and update childrenCount
560
+ if (updates[childrenField] !== undefined) {
561
+ const children = updates[childrenField];
562
+ node[childrenField] = children;
563
+ node[childrenCountField] = children?.length;
564
+ }
565
+ return node;
566
+ }
567
+ /**
568
+ * Add a child node to a parent node
569
+ * @param nodes - Root nodes array
570
+ * @param parentId - The ID of the parent node
571
+ * @param childNode - The child node to add
572
+ * @param index - Optional index to insert at (default: append to end)
573
+ * @param idField - Field name for node ID
574
+ * @param childrenField - Field name for children array
575
+ * @param childrenCountField - Field name for children count
576
+ * @param expandedField - Field name for expanded state
577
+ * @returns The parent node if found and child was added, null otherwise
578
+ */
579
+ addChild(nodes, parentId, childNode, index, idField = 'id', childrenField = 'children', childrenCountField = 'childrenCount', expandedField = 'expanded') {
580
+ const parent = this.findNodeById(nodes, parentId, idField);
581
+ if (!parent) {
582
+ return null;
583
+ }
584
+ // Ensure children array exists
585
+ let children = parent[childrenField];
586
+ if (!children) {
587
+ children = [];
588
+ parent[childrenField] = children;
589
+ }
590
+ // Insert or append child
591
+ if (index !== undefined && index >= 0 && index <= children.length) {
592
+ children.splice(index, 0, childNode);
593
+ }
594
+ else {
595
+ children.push(childNode);
596
+ }
597
+ // Update childrenCount
598
+ parent[childrenCountField] = children.length;
599
+ // Auto-expand parent if it was collapsed
600
+ if (!parent[expandedField]) {
601
+ parent[expandedField] = true;
602
+ }
603
+ return parent;
604
+ }
605
+ /**
606
+ * Remove a node from the tree
607
+ * @param nodes - Root nodes array
608
+ * @param nodeId - The ID of the node to remove
609
+ * @param idField - Field name for node ID
610
+ * @param childrenField - Field name for children array
611
+ * @param childrenCountField - Field name for children count
612
+ * @returns The removed node if found, null otherwise
613
+ */
614
+ removeNode(nodes, nodeId, idField = 'id', childrenField = 'children', childrenCountField = 'childrenCount') {
615
+ const node = this.findNodeById(nodes, nodeId, idField);
616
+ if (!node) {
617
+ return null;
618
+ }
619
+ // Find parent to remove from its children array
620
+ const parent = this.findParentNode(nodes, node, idField, childrenField);
621
+ const parentChildren = parent ? parent[childrenField] : undefined;
622
+ const targetArray = parentChildren ?? nodes;
623
+ // Find and remove the node
624
+ const index = targetArray.findIndex((n) => n[idField] === nodeId);
625
+ if (index !== -1) {
626
+ const removed = targetArray.splice(index, 1)[0];
627
+ // Update parent's childrenCount if it exists
628
+ if (parent) {
629
+ const updatedChildren = parent[childrenField];
630
+ parent[childrenCountField] = updatedChildren?.length ?? 0;
631
+ }
632
+ return removed;
633
+ }
634
+ return null;
635
+ }
420
636
  /**
421
637
  * Validate node structure (check for required fields and circular references)
422
638
  */
423
- validateNode(node, visitedIds = new Set()) {
639
+ validateNode(node, visitedIds = new Set(), idField = 'id', titleField = 'title', childrenField = 'children', childrenCountField = 'childrenCount') {
424
640
  const errors = [];
425
- if (!node.id) {
426
- errors.push('Node must have an id');
641
+ const nodeId = node[idField];
642
+ const nodeTitle = node[titleField];
643
+ if (!nodeId) {
644
+ errors.push(`Node must have an ${idField}`);
427
645
  }
428
- if (!node.label) {
429
- errors.push('Node must have a label');
646
+ if (!nodeTitle) {
647
+ errors.push(`Node must have a ${titleField}`);
430
648
  }
431
- if (visitedIds.has(node.id)) {
432
- errors.push(`Circular reference detected: node ${node.id} appears multiple times in the tree`);
649
+ if (nodeId && visitedIds.has(nodeId)) {
650
+ errors.push(`Circular reference detected: node ${nodeId} appears multiple times in the tree`);
433
651
  }
434
- if (node.children) {
652
+ const children = node[childrenField];
653
+ if (children) {
435
654
  const newVisited = new Set(visitedIds);
436
- newVisited.add(node.id);
437
- for (const child of node.children) {
438
- const childValidation = this.validateNode(child, newVisited);
655
+ if (nodeId) {
656
+ newVisited.add(nodeId);
657
+ }
658
+ for (const child of children) {
659
+ const childValidation = this.validateNode(child, newVisited, idField, titleField, childrenField, childrenCountField);
439
660
  if (!childValidation.valid) {
440
- errors.push(...childValidation.errors.map((e) => `Child of ${node.id}: ${e}`));
661
+ errors.push(...childValidation.errors.map((e) => `Child of ${nodeId}: ${e}`));
441
662
  }
442
663
  }
443
- if (node.childrenCount !== undefined && node.childrenCount !== node.children.length) {
444
- errors.push(`Node ${node.id}: childrenCount (${node.childrenCount}) does not match children array length (${node.children.length})`);
664
+ const childrenCount = node[childrenCountField];
665
+ if (childrenCount !== undefined && childrenCount !== children.length) {
666
+ errors.push(`Node ${nodeId}: ${childrenCountField} (${childrenCount}) does not match ${childrenField} array length (${children.length})`);
445
667
  }
446
668
  }
447
669
  return {
@@ -465,36 +687,54 @@ class AXTreeViewComponent {
465
687
  // ==================== Inputs ====================
466
688
  /** Tree data source - can be static array or lazy loading function */
467
689
  this.datasource = model.required(...(ngDevMode ? [{ debugName: "datasource" }] : []));
468
- /** Selection mode: 'single' (click to select) or 'multiple' (checkbox selection) */
469
- this.selectMode = input('multiple', ...(ngDevMode ? [{ debugName: "selectMode" }] : []));
470
- /** Whether to show checkboxes for selection (only applies to multiple mode) */
471
- this.showCheckbox = input(true, ...(ngDevMode ? [{ debugName: "showCheckbox" }] : []));
472
- /** When true, selecting a parent also selects all loaded children (only for multiple mode) */
473
- this.checkChildrenOnSelect = input(true, ...(ngDevMode ? [{ debugName: "checkChildrenOnSelect" }] : []));
474
- /** When true, selecting a child makes parents indeterminate (only for multiple mode) */
475
- this.intermediateState = input(true, ...(ngDevMode ? [{ debugName: "intermediateState" }] : []));
476
- /** When true, clicking on a node toggles its selection (works for both single and multiple modes) */
477
- this.checkOnClick = input(false, ...(ngDevMode ? [{ debugName: "checkOnClick" }] : []));
478
- /** Drag and drop mode: 'none' (disabled), 'handler' (drag handle), 'item' (entire item) */
479
- this.dragMode = input('handler', ...(ngDevMode ? [{ debugName: "dragMode" }] : []));
480
- /** Drag operation type: 'order-only' (reorder only), 'move' (move between parents), 'both' (allow both) */
481
- this.dragOperationType = input('both', ...(ngDevMode ? [{ debugName: "dragOperationType" }] : []));
482
- /** Whether to show icons */
690
+ /** Selection mode: 'single' (click to select) or 'multiple' (checkbox selection). Default: `'single'` */
691
+ this.selectMode = input('single', ...(ngDevMode ? [{ debugName: "selectMode" }] : []));
692
+ /** Whether to show checkboxes for selection (only applies to multiple mode). Default: `false`. When false and selectMode is not 'none', clicking on a node toggles its selection. */
693
+ this.showCheckbox = input(false, ...(ngDevMode ? [{ debugName: "showCheckbox" }] : []));
694
+ /** 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). Default: `'intermediate'` */
695
+ this.selectionBehavior = input('intermediate', ...(ngDevMode ? [{ debugName: "selectionBehavior" }] : []));
696
+ /** Drag area: 'handler' (drag handle), 'item' (entire item). Default: `'handler'` */
697
+ this.dragArea = input('handler', ...(ngDevMode ? [{ debugName: "dragArea" }] : []));
698
+ /** Drag behavior: 'none' (disabled), 'order-only' (reorder only), 'move' (move between parents), 'both' (allow both). Default: `'none'` */
699
+ this.dragBehavior = input('none', ...(ngDevMode ? [{ debugName: "dragBehavior" }] : []));
700
+ /** Whether to show icons. Default: `true` */
483
701
  this.showIcons = input(true, ...(ngDevMode ? [{ debugName: "showIcons" }] : []));
484
- /** Whether to show children count badge */
702
+ /** Whether to show children count badge. Default: `true` */
485
703
  this.showChildrenBadge = input(true, ...(ngDevMode ? [{ debugName: "showChildrenBadge" }] : []));
486
- /** Custom icon for expanded nodes */
704
+ /** Custom icon for expanded nodes. Default: `'fa-solid fa-chevron-down'` */
487
705
  this.expandedIcon = input('fa-solid fa-chevron-down', ...(ngDevMode ? [{ debugName: "expandedIcon" }] : []));
488
- /** Custom icon for collapsed nodes */
706
+ /** Custom icon for collapsed nodes. Default: `'fa-solid fa-chevron-right'` */
489
707
  this.collapsedIcon = input('fa-solid fa-chevron-right', ...(ngDevMode ? [{ debugName: "collapsedIcon" }] : []));
490
- /** Indent size in pixels for each level */
491
- this.indentSize = input(12, ...(ngDevMode ? [{ debugName: "indentSize" }] : []));
492
- /** Node height in pixels */
493
- this.nodeHeight = input('normal', ...(ngDevMode ? [{ debugName: "nodeHeight" }] : []));
494
- /** Visual style variant */
708
+ /** Indent size in pixels for each level. Default: `16` */
709
+ this.indentSize = input(16, ...(ngDevMode ? [{ debugName: "indentSize" }] : []));
710
+ /** Visual style variant. Default: `'default'` */
495
711
  this.look = input('default', ...(ngDevMode ? [{ debugName: "look" }] : []));
496
- /** Custom template for tree items */
497
- this.itemTemplate = input(...(ngDevMode ? [undefined, { debugName: "itemTemplate" }] : []));
712
+ /** Custom template for tree items. Default: `undefined` */
713
+ this.nodeTemplate = input(...(ngDevMode ? [undefined, { debugName: "nodeTemplate" }] : []));
714
+ /** Field name for node ID. Default: `'id'` */
715
+ this.idField = input('id', ...(ngDevMode ? [{ debugName: "idField" }] : []));
716
+ /** Field name for node title. Default: `'title'` */
717
+ this.titleField = input('title', ...(ngDevMode ? [{ debugName: "titleField" }] : []));
718
+ /** Field name for node tooltip. Default: `'tooltip'` */
719
+ this.tooltipField = input('tooltip', ...(ngDevMode ? [{ debugName: "tooltipField" }] : []));
720
+ /** Field name for node icon. Default: `'icon'` */
721
+ this.iconField = input('icon', ...(ngDevMode ? [{ debugName: "iconField" }] : []));
722
+ /** Field name for expanded state. Default: `'expanded'` */
723
+ this.expandedField = input('expanded', ...(ngDevMode ? [{ debugName: "expandedField" }] : []));
724
+ /** Field name for selected state. Default: `'selected'` */
725
+ this.selectedField = input('selected', ...(ngDevMode ? [{ debugName: "selectedField" }] : []));
726
+ /** Field name for indeterminate state. Default: `'indeterminate'` */
727
+ this.indeterminateField = input('indeterminate', ...(ngDevMode ? [{ debugName: "indeterminateField" }] : []));
728
+ /** Field name for disabled state. Default: `'disabled'` */
729
+ this.disabledField = input('disabled', ...(ngDevMode ? [{ debugName: "disabledField" }] : []));
730
+ /** Field name for hidden state. Default: `'hidden'` */
731
+ this.hiddenField = input('hidden', ...(ngDevMode ? [{ debugName: "hiddenField" }] : []));
732
+ /** Field name for children array. Default: `'children'` */
733
+ this.childrenField = input('children', ...(ngDevMode ? [{ debugName: "childrenField" }] : []));
734
+ /** Field name for children count. Default: `'childrenCount'` */
735
+ this.childrenCountField = input('childrenCount', ...(ngDevMode ? [{ debugName: "childrenCountField" }] : []));
736
+ /** Field name for custom data. Default: `'data'` */
737
+ this.dataField = input('data', ...(ngDevMode ? [{ debugName: "dataField" }] : []));
498
738
  // ==================== Outputs ====================
499
739
  /** Emitted before a drop operation - set canceled to true to prevent drop */
500
740
  this.onBeforeDrop = output();
@@ -502,6 +742,8 @@ class AXTreeViewComponent {
502
742
  this.onNodeToggle = output();
503
743
  /** Emitted when a node is selected/deselected */
504
744
  this.onNodeSelect = output();
745
+ /** Emitted when selection changes - returns all currently selected nodes */
746
+ this.onSelectionChange = output();
505
747
  /** Emitted when nodes are reordered within the same parent */
506
748
  this.onOrderChange = output();
507
749
  /** Emitted when a node is moved to a different parent */
@@ -534,19 +776,39 @@ class AXTreeViewComponent {
534
776
  this.isUpdatingFromDatasource = false;
535
777
  /** Computed to check if datasource is a function */
536
778
  this.isLazyDataSource = computed(() => typeof this.datasource() === 'function', ...(ngDevMode ? [{ debugName: "isLazyDataSource" }] : []));
779
+ /** Computed: Returns true when selection is restricted to leaf nodes only */
780
+ this.isLeafOnlyMode = computed(() => this.selectionBehavior() === 'leaf', ...(ngDevMode ? [{ debugName: "isLeafOnlyMode" }] : []));
781
+ /** Computed: Returns true when selecting a parent automatically selects all its children */
782
+ this.cascadesToChildren = computed(() => {
783
+ const behavior = this.selectionBehavior();
784
+ return behavior === 'nested' || behavior === 'intermediate-nested';
785
+ }, ...(ngDevMode ? [{ debugName: "cascadesToChildren" }] : []));
786
+ /** Computed: Returns true when parent nodes show indeterminate state based on children selection */
787
+ this.hasIntermediateState = computed(() => {
788
+ const behavior = this.selectionBehavior();
789
+ return behavior === 'intermediate' || behavior === 'intermediate-nested';
790
+ }, ...(ngDevMode ? [{ debugName: "hasIntermediateState" }] : []));
791
+ /** Computed: Returns true when drag handle should be shown */
792
+ this.shouldShowDragHandle = computed(() => {
793
+ return this.dragArea() === 'handler' && this.dragBehavior() !== 'none';
794
+ }, ...(ngDevMode ? [{ debugName: "shouldShowDragHandle" }] : []));
537
795
  // ==================== Effects ====================
538
796
  /** Effect to handle datasource changes */
539
- this.#datasourceEffect = effect(() => {
540
- if (this.isUpdatingFromDatasource)
797
+ this.#datasourceEffect = effect(async () => {
798
+ if (this.isUpdatingFromDatasource) {
541
799
  return;
800
+ }
542
801
  const ds = this.datasource();
543
802
  if (Array.isArray(ds)) {
544
803
  this.nodes.set([...ds]);
545
804
  }
546
805
  else if (typeof ds === 'function') {
547
- this.loadRootItems(ds).catch((error) => {
806
+ try {
807
+ await this.loadRootItems(ds);
808
+ }
809
+ catch (error) {
548
810
  this.handleError('Failed to load root items', error);
549
- });
811
+ }
550
812
  }
551
813
  }, ...(ngDevMode ? [{ debugName: "#datasourceEffect" }] : []));
552
814
  /** Initialize direction change listener */
@@ -556,6 +818,115 @@ class AXTreeViewComponent {
556
818
  .subscribe((isRtl) => this.isRtl.set(isRtl));
557
819
  });
558
820
  }
821
+ // ==================== Node Property Helpers ====================
822
+ /**
823
+ * Get a property value from a node using the configured field name
824
+ */
825
+ getNodeProp(node, fieldName, defaultValue) {
826
+ return node[fieldName] ?? defaultValue;
827
+ }
828
+ /**
829
+ * Set a property value on a node using the configured field name
830
+ */
831
+ setNodeProp(node, fieldName, value) {
832
+ node[fieldName] = value;
833
+ }
834
+ /**
835
+ * Get node ID
836
+ */
837
+ getNodeId(node) {
838
+ return this.getNodeProp(node, this.idField(), '');
839
+ }
840
+ /**
841
+ * Get node title
842
+ */
843
+ getNodeTitle(node) {
844
+ return this.getNodeProp(node, this.titleField(), '');
845
+ }
846
+ /**
847
+ * Get node tooltip
848
+ */
849
+ getNodeTooltip(node) {
850
+ return this.getNodeProp(node, this.tooltipField(), undefined);
851
+ }
852
+ /**
853
+ * Get node icon
854
+ */
855
+ getNodeIcon(node) {
856
+ return this.getNodeProp(node, this.iconField(), undefined);
857
+ }
858
+ /**
859
+ * Get node expanded state
860
+ */
861
+ getNodeExpanded(node) {
862
+ return this.getNodeProp(node, this.expandedField(), false);
863
+ }
864
+ /**
865
+ * Set node expanded state
866
+ */
867
+ setNodeExpanded(node, value) {
868
+ this.setNodeProp(node, this.expandedField(), value);
869
+ }
870
+ /**
871
+ * Get node selected state
872
+ */
873
+ getNodeSelected(node) {
874
+ return this.getNodeProp(node, this.selectedField(), false);
875
+ }
876
+ /**
877
+ * Set node selected state
878
+ */
879
+ setNodeSelected(node, value) {
880
+ this.setNodeProp(node, this.selectedField(), value);
881
+ }
882
+ /**
883
+ * Get node indeterminate state
884
+ */
885
+ getNodeIndeterminate(node) {
886
+ return this.getNodeProp(node, this.indeterminateField(), false);
887
+ }
888
+ /**
889
+ * Set node indeterminate state
890
+ */
891
+ setNodeIndeterminate(node, value) {
892
+ this.setNodeProp(node, this.indeterminateField(), value);
893
+ }
894
+ /**
895
+ * Get node disabled state
896
+ */
897
+ getNodeDisabled(node) {
898
+ return this.getNodeProp(node, this.disabledField(), false);
899
+ }
900
+ /**
901
+ * Get node hidden state
902
+ */
903
+ getNodeHidden(node) {
904
+ return this.getNodeProp(node, this.hiddenField(), false);
905
+ }
906
+ /**
907
+ * Get node children array
908
+ */
909
+ getNodeChildren(node) {
910
+ return this.getNodeProp(node, this.childrenField(), undefined);
911
+ }
912
+ /**
913
+ * Set node children array
914
+ */
915
+ setNodeChildren(node, value) {
916
+ this.setNodeProp(node, this.childrenField(), value);
917
+ }
918
+ /**
919
+ * Get node children count
920
+ */
921
+ getNodeChildrenCount(node) {
922
+ return this.getNodeProp(node, this.childrenCountField(), undefined);
923
+ }
924
+ /**
925
+ * Set node children count
926
+ */
927
+ setNodeChildrenCount(node, value) {
928
+ this.setNodeProp(node, this.childrenCountField(), value);
929
+ }
559
930
  // ==================== Effects ====================
560
931
  /** Effect to handle datasource changes */
561
932
  #datasourceEffect;
@@ -566,21 +937,21 @@ class AXTreeViewComponent {
566
937
  * Expand all nodes in the tree (with lazy loading support)
567
938
  */
568
939
  async expandAll() {
569
- await this.treeService.setExpandedState(this.nodes(), true, this.isLazyDataSource(), (node) => this.loadNodeChildren(node));
940
+ await this.treeService.setExpandedState(this.nodes(), true, this.isLazyDataSource(), (node) => this.loadNodeChildren(node), this.expandedField(), this.childrenField(), this.childrenCountField());
570
941
  this.refreshNodes();
571
942
  }
572
943
  /**
573
944
  * Collapse all nodes in the tree
574
945
  */
575
946
  collapseAll() {
576
- this.treeService.setExpandedState(this.nodes(), false, this.isLazyDataSource(), (node) => this.loadNodeChildren(node));
947
+ this.treeService.setExpandedState(this.nodes(), false, this.isLazyDataSource(), (node) => this.loadNodeChildren(node), this.expandedField(), this.childrenField(), this.childrenCountField());
577
948
  this.refreshNodes();
578
949
  }
579
950
  /**
580
951
  * Get count of selected nodes
581
952
  */
582
953
  getSelectedCount() {
583
- return this.treeService.countSelected(this.nodes());
954
+ return this.treeService.countSelected(this.nodes(), this.selectedField(), this.childrenField());
584
955
  }
585
956
  /**
586
957
  * Check if any nodes are selected
@@ -593,36 +964,62 @@ class AXTreeViewComponent {
593
964
  */
594
965
  getSelectedNodes() {
595
966
  const selected = [];
596
- this.treeService.collectSelected(this.nodes(), selected);
967
+ this.treeService.collectSelected(this.nodes(), selected, this.selectedField(), this.childrenField());
597
968
  return selected;
598
969
  }
599
970
  /**
600
971
  * Delete selected nodes from the tree
601
972
  */
602
973
  deleteSelected() {
603
- this.treeService.removeSelected(this.nodes());
604
- this.treeService.updateAllParentStates(this.nodes(), this.intermediateState());
974
+ this.treeService.removeSelected(this.nodes(), this.selectedField(), this.indeterminateField(), this.childrenField());
975
+ if (!this.isLeafOnlyMode()) {
976
+ this.treeService.updateAllParentStates(this.nodes(), this.hasIntermediateState(), this.childrenField(), this.selectedField(), this.indeterminateField());
977
+ }
605
978
  this.refreshNodes();
979
+ this.emitSelectionChange();
606
980
  }
607
981
  /**
608
982
  * Select all nodes in the tree
609
983
  */
610
984
  selectAll() {
611
- this.treeService.setAllSelection(this.nodes(), true);
985
+ if (this.selectMode() === 'none') {
986
+ return;
987
+ }
988
+ if (this.isLeafOnlyMode()) {
989
+ // Only select leaf nodes
990
+ const selectLeafs = (nodes) => {
991
+ for (const node of nodes) {
992
+ if (this.isLeafNode(node) && !this.getNodeDisabled(node)) {
993
+ this.setNodeSelected(node, true);
994
+ this.setNodeIndeterminate(node, false);
995
+ }
996
+ const children = this.getNodeChildren(node);
997
+ if (children) {
998
+ selectLeafs(children);
999
+ }
1000
+ }
1001
+ };
1002
+ selectLeafs(this.nodes());
1003
+ }
1004
+ else {
1005
+ this.treeService.setAllSelection(this.nodes(), true, this.selectedField(), this.indeterminateField(), this.childrenField());
1006
+ }
612
1007
  this.refreshNodes();
1008
+ this.emitSelectionChange();
613
1009
  }
614
1010
  /**
615
1011
  * Deselect all nodes in the tree
616
1012
  */
617
1013
  deselectAll() {
618
- this.treeService.setAllSelection(this.nodes(), false);
1014
+ this.treeService.setAllSelection(this.nodes(), false, this.selectedField(), this.indeterminateField(), this.childrenField());
619
1015
  this.refreshNodes();
1016
+ this.emitSelectionChange();
620
1017
  }
621
1018
  /**
622
1019
  * Find a node by ID in the tree
623
1020
  */
624
1021
  findNode(id) {
625
- return this.treeService.findNodeById(this.nodes(), id);
1022
+ return this.treeService.findNodeById(this.nodes(), id, this.idField());
626
1023
  }
627
1024
  /**
628
1025
  * Refresh the tree to trigger change detection
@@ -636,6 +1033,12 @@ class AXTreeViewComponent {
636
1033
  isNodeLoading(nodeId) {
637
1034
  return this.loadingNodes().has(nodeId);
638
1035
  }
1036
+ /**
1037
+ * Get loading state for a node (internal state)
1038
+ */
1039
+ getNodeLoading(node) {
1040
+ return this.loadingNodes().has(this.getNodeId(node));
1041
+ }
639
1042
  /**
640
1043
  * Edit/update a node's properties
641
1044
  * @param nodeId - The ID of the node to edit
@@ -643,22 +1046,12 @@ class AXTreeViewComponent {
643
1046
  * @returns true if node was found and updated, false otherwise
644
1047
  */
645
1048
  editNode(nodeId, updates) {
646
- const node = this.findNode(nodeId);
647
- if (!node) {
648
- return false;
649
- }
650
- // Update node properties
651
- Object.assign(node, updates);
652
- // If children array is provided, ensure it exists
653
- if (updates.children !== undefined) {
654
- node.children = updates.children;
655
- }
656
- // Update childrenCount if children array is provided
657
- if (updates.children !== undefined) {
658
- node.childrenCount = updates.children.length;
1049
+ const updated = this.treeService.editNode(this.nodes(), nodeId, updates, this.idField(), this.childrenField(), this.childrenCountField());
1050
+ if (updated) {
1051
+ this.refreshNodes();
1052
+ return true;
659
1053
  }
660
- this.refreshNodes();
661
- return true;
1054
+ return false;
662
1055
  }
663
1056
  /**
664
1057
  * Add a child node to a parent node
@@ -668,29 +1061,12 @@ class AXTreeViewComponent {
668
1061
  * @returns true if parent was found and child was added, false otherwise
669
1062
  */
670
1063
  addChild(parentId, childNode, index) {
671
- const parent = this.findNode(parentId);
672
- if (!parent) {
673
- return false;
674
- }
675
- // Ensure children array exists
676
- if (!parent.children) {
677
- parent.children = [];
678
- }
679
- // Insert or append child
680
- if (index !== undefined && index >= 0 && index <= parent.children.length) {
681
- parent.children.splice(index, 0, childNode);
682
- }
683
- else {
684
- parent.children.push(childNode);
685
- }
686
- // Update childrenCount
687
- parent.childrenCount = parent.children.length;
688
- // Auto-expand parent if it was collapsed
689
- if (!parent.expanded) {
690
- parent.expanded = true;
1064
+ const parent = this.treeService.addChild(this.nodes(), parentId, childNode, index, this.idField(), this.childrenField(), this.childrenCountField(), this.expandedField());
1065
+ if (parent) {
1066
+ this.refreshNodes();
1067
+ return true;
691
1068
  }
692
- this.refreshNodes();
693
- return true;
1069
+ return false;
694
1070
  }
695
1071
  /**
696
1072
  * Remove a node from the tree
@@ -698,29 +1074,15 @@ class AXTreeViewComponent {
698
1074
  * @returns The removed node if found, null otherwise
699
1075
  */
700
1076
  removeNode(nodeId) {
701
- const node = this.findNode(nodeId);
702
- if (!node) {
703
- return null;
704
- }
705
- // Find parent to remove from its children array
706
- const parent = this.treeService.findParentNode(this.nodes(), node);
707
- const targetArray = parent?.children ?? this.nodes();
708
- // Find and remove the node
709
- const index = targetArray.findIndex((n) => n.id === nodeId);
710
- if (index !== -1) {
711
- const removed = targetArray.splice(index, 1)[0];
712
- // Update parent's childrenCount if it exists
713
- if (parent) {
714
- parent.childrenCount = parent.children?.length ?? 0;
715
- }
1077
+ const removed = this.treeService.removeNode(this.nodes(), nodeId, this.idField(), this.childrenField(), this.childrenCountField());
1078
+ if (removed) {
716
1079
  // Update parent states if needed
717
- if (this.intermediateState()) {
718
- this.treeService.updateAllParentStates(this.nodes(), this.intermediateState());
1080
+ if (this.hasIntermediateState()) {
1081
+ this.treeService.updateAllParentStates(this.nodes(), this.hasIntermediateState(), this.childrenField(), this.selectedField(), this.indeterminateField());
719
1082
  }
720
1083
  this.refreshNodes();
721
- return removed;
722
1084
  }
723
- return null;
1085
+ return removed;
724
1086
  }
725
1087
  /**
726
1088
  * Expand a specific node
@@ -732,13 +1094,13 @@ class AXTreeViewComponent {
732
1094
  if (!node) {
733
1095
  return;
734
1096
  }
735
- const hasChildren = this.treeService.hasChildren(node);
736
- const canLazyLoad = this.treeService.canLazyLoad(node, this.isLazyDataSource());
1097
+ const hasChildren = this.treeService.hasChildren(node, this.childrenField());
1098
+ const canLazyLoad = this.treeService.canLazyLoad(node, this.isLazyDataSource(), this.childrenCountField(), this.childrenField());
737
1099
  if (hasChildren || canLazyLoad) {
738
1100
  if (canLazyLoad) {
739
1101
  await this.loadNodeChildren(node);
740
1102
  }
741
- node.expanded = true;
1103
+ this.setNodeExpanded(node, true);
742
1104
  this.refreshNodes();
743
1105
  this.onNodeToggle.emit({ component: this, node, nativeEvent: new Event('expand') });
744
1106
  }
@@ -752,8 +1114,8 @@ class AXTreeViewComponent {
752
1114
  if (!node) {
753
1115
  return;
754
1116
  }
755
- if (node.expanded) {
756
- node.expanded = false;
1117
+ if (this.getNodeExpanded(node)) {
1118
+ this.setNodeExpanded(node, false);
757
1119
  this.refreshNodes();
758
1120
  this.onNodeToggle.emit({ component: this, node, nativeEvent: new Event('collapse') });
759
1121
  }
@@ -768,7 +1130,7 @@ class AXTreeViewComponent {
768
1130
  if (!node) {
769
1131
  return;
770
1132
  }
771
- if (node.expanded) {
1133
+ if (this.getNodeExpanded(node)) {
772
1134
  this.collapseNode(nodeId);
773
1135
  }
774
1136
  else {
@@ -781,24 +1143,28 @@ class AXTreeViewComponent {
781
1143
  * @returns true if node was found and selected, false otherwise
782
1144
  */
783
1145
  selectNode(nodeId) {
1146
+ if (this.selectMode() === 'none') {
1147
+ return false;
1148
+ }
784
1149
  const node = this.findNode(nodeId);
785
- if (!node || node.disabled) {
1150
+ if (!node || this.getNodeDisabled(node) || !this.canSelectNode(node)) {
786
1151
  return false;
787
1152
  }
788
1153
  const mode = this.selectMode();
789
1154
  if (mode === 'single') {
790
- this.treeService.deselectAllNodes(this.nodes());
791
- node.selected = true;
792
- node.indeterminate = false;
1155
+ this.treeService.deselectAllNodes(this.nodes(), this.selectedField(), this.indeterminateField(), this.childrenField());
1156
+ this.setNodeSelected(node, true);
1157
+ this.setNodeIndeterminate(node, false);
793
1158
  }
794
1159
  else {
795
- node.selected = true;
796
- node.indeterminate = false;
797
- if (this.checkChildrenOnSelect() && node.children) {
798
- this.treeService.selectAllChildren(node.children, true);
1160
+ this.setNodeSelected(node, true);
1161
+ this.setNodeIndeterminate(node, false);
1162
+ const children = this.getNodeChildren(node);
1163
+ if (this.cascadesToChildren() && children && !this.isLeafOnlyMode()) {
1164
+ this.treeService.selectAllChildren(children, true, this.selectedField(), this.indeterminateField(), this.childrenField());
799
1165
  }
800
- if (this.intermediateState()) {
801
- this.treeService.updateParentStates(this.nodes(), node, this.intermediateState());
1166
+ if (this.hasIntermediateState() && !this.isLeafOnlyMode()) {
1167
+ this.treeService.updateParentStates(this.nodes(), node, this.hasIntermediateState(), this.idField(), this.childrenField(), this.selectedField(), this.indeterminateField());
802
1168
  }
803
1169
  }
804
1170
  this.refreshNodes();
@@ -807,6 +1173,7 @@ class AXTreeViewComponent {
807
1173
  node,
808
1174
  isUserInteraction: false,
809
1175
  });
1176
+ this.emitSelectionChange();
810
1177
  return true;
811
1178
  }
812
1179
  /**
@@ -819,13 +1186,14 @@ class AXTreeViewComponent {
819
1186
  if (!node) {
820
1187
  return false;
821
1188
  }
822
- node.selected = false;
823
- node.indeterminate = false;
824
- if (this.checkChildrenOnSelect() && node.children) {
825
- this.treeService.selectAllChildren(node.children, false);
1189
+ this.setNodeSelected(node, false);
1190
+ this.setNodeIndeterminate(node, false);
1191
+ const children = this.getNodeChildren(node);
1192
+ if (this.cascadesToChildren() && children && !this.isLeafOnlyMode()) {
1193
+ this.treeService.selectAllChildren(children, false, this.selectedField(), this.indeterminateField(), this.childrenField());
826
1194
  }
827
- if (this.intermediateState()) {
828
- this.treeService.updateParentStates(this.nodes(), node, this.intermediateState());
1195
+ if (this.hasIntermediateState() && !this.isLeafOnlyMode()) {
1196
+ this.treeService.updateParentStates(this.nodes(), node, this.hasIntermediateState(), this.idField(), this.childrenField(), this.selectedField(), this.indeterminateField());
829
1197
  }
830
1198
  this.refreshNodes();
831
1199
  this.onNodeSelect.emit({
@@ -833,6 +1201,7 @@ class AXTreeViewComponent {
833
1201
  node,
834
1202
  isUserInteraction: false,
835
1203
  });
1204
+ this.emitSelectionChange();
836
1205
  return true;
837
1206
  }
838
1207
  /**
@@ -845,7 +1214,7 @@ class AXTreeViewComponent {
845
1214
  if (!node) {
846
1215
  return null;
847
1216
  }
848
- return this.treeService.findParentNode(this.nodes(), node) ?? null;
1217
+ return this.treeService.findParentNode(this.nodes(), node, this.idField(), this.childrenField()) ?? null;
849
1218
  }
850
1219
  /**
851
1220
  * Get children of a node
@@ -857,7 +1226,7 @@ class AXTreeViewComponent {
857
1226
  if (!node) {
858
1227
  return null;
859
1228
  }
860
- return node.children ?? [];
1229
+ return this.getNodeChildren(node) ?? [];
861
1230
  }
862
1231
  /**
863
1232
  * Get all root nodes
@@ -871,17 +1240,7 @@ class AXTreeViewComponent {
871
1240
  * @returns Array of all nodes in the tree
872
1241
  */
873
1242
  getAllNodes() {
874
- const allNodes = [];
875
- const traverse = (nodes) => {
876
- for (const node of nodes) {
877
- allNodes.push(node);
878
- if (node.children) {
879
- traverse(node.children);
880
- }
881
- }
882
- };
883
- traverse(this.nodes());
884
- return allNodes;
1243
+ return this.treeService.getAllNodes(this.nodes(), this.childrenField());
885
1244
  }
886
1245
  /**
887
1246
  * Get the path to a node (array of parent IDs from root to node)
@@ -889,18 +1248,8 @@ class AXTreeViewComponent {
889
1248
  * @returns Array of node IDs representing the path, or empty array if node not found
890
1249
  */
891
1250
  getNodePath(nodeId) {
892
- const path = [];
893
- const node = this.findNode(nodeId);
894
- if (!node) {
895
- return path;
896
- }
897
- let current = node;
898
- while (current) {
899
- path.unshift(current.id);
900
- const parent = this.treeService.findParentNode(this.nodes(), current);
901
- current = parent ?? null;
902
- }
903
- return path;
1251
+ const nodePath = this.treeService.getNodePath(this.nodes(), nodeId, this.idField(), this.childrenField());
1252
+ return nodePath.map((node) => this.getNodeId(node));
904
1253
  }
905
1254
  /**
906
1255
  * Get the level/depth of a node (0 = root level)
@@ -908,8 +1257,7 @@ class AXTreeViewComponent {
908
1257
  * @returns The level of the node, or -1 if node not found
909
1258
  */
910
1259
  getNodeLevel(nodeId) {
911
- const path = this.getNodePath(nodeId);
912
- return path.length > 0 ? path.length - 1 : -1;
1260
+ return this.treeService.getNodeLevel(this.nodes(), nodeId, this.idField(), this.childrenField());
913
1261
  }
914
1262
  /**
915
1263
  * Programmatically move a node to a new parent
@@ -919,64 +1267,14 @@ class AXTreeViewComponent {
919
1267
  * @returns true if move was successful, false otherwise
920
1268
  */
921
1269
  moveNode(nodeId, newParentId, index) {
922
- const node = this.findNode(nodeId);
923
- if (!node) {
924
- return false;
925
- }
926
- // Find current parent
927
- const currentParent = this.treeService.findParentNode(this.nodes(), node);
928
- const currentArray = currentParent?.children ?? this.nodes();
929
- // Find and remove from current location
930
- const currentIndex = currentArray.findIndex((n) => n.id === nodeId);
931
- if (currentIndex === -1) {
932
- return false;
933
- }
934
- const movedNode = currentArray.splice(currentIndex, 1)[0];
935
- // Find new parent
936
- let targetArray;
937
- let newParent;
938
- if (newParentId) {
939
- newParent = this.findNode(newParentId);
940
- if (!newParent) {
941
- // Restore node if new parent not found
942
- currentArray.splice(currentIndex, 0, movedNode);
943
- return false;
944
- }
945
- // Validate drop target
946
- if (!this.treeService.isValidDropTarget(movedNode, newParent)) {
947
- // Restore node if invalid drop target
948
- currentArray.splice(currentIndex, 0, movedNode);
949
- return false;
950
- }
951
- if (!newParent.children) {
952
- newParent.children = [];
953
- }
954
- targetArray = newParent.children;
955
- }
956
- else {
957
- targetArray = this.nodes();
958
- }
959
- // Calculate new index before inserting
960
- const newIndex = index !== undefined && index >= 0 && index <= targetArray.length ? index : targetArray.length;
961
- // Insert at new location
962
- if (index !== undefined && index >= 0 && index <= targetArray.length) {
963
- targetArray.splice(index, 0, movedNode);
964
- }
965
- else {
966
- targetArray.push(movedNode);
967
- }
968
- // Update childrenCount
969
- if (currentParent) {
970
- currentParent.childrenCount = currentParent.children?.length ?? 0;
971
- }
972
- if (newParent) {
973
- newParent.childrenCount = newParent.children?.length ?? 0;
974
- newParent.expanded = true; // Auto-expand new parent
1270
+ const result = this.treeService.moveNode(this.nodes(), nodeId, newParentId, index, this.idField(), this.childrenField(), this.childrenCountField(), this.expandedField());
1271
+ if (result.success && result.movedNode) {
1272
+ // Emit drop events
1273
+ this.emitDropEvents(result.movedNode, result.previousParent, result.newParent, result.previousIndex, result.currentIndex, false);
1274
+ this.refreshNodes();
1275
+ return true;
975
1276
  }
976
- // Emit drop events
977
- this.emitDropEvents(movedNode, currentParent, newParent, currentIndex, newIndex, false);
978
- this.refreshNodes();
979
- return true;
1277
+ return false;
980
1278
  }
981
1279
  /**
982
1280
  * Clone a node (creates a deep copy)
@@ -988,7 +1286,7 @@ class AXTreeViewComponent {
988
1286
  if (!node) {
989
1287
  return null;
990
1288
  }
991
- return this.treeService.cloneNode(node);
1289
+ 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());
992
1290
  }
993
1291
  /**
994
1292
  * Focus a specific node by ID
@@ -997,7 +1295,7 @@ class AXTreeViewComponent {
997
1295
  */
998
1296
  focusNode(nodeId) {
999
1297
  const node = this.findNode(nodeId);
1000
- if (!node || node.visible === false || node.disabled) {
1298
+ if (!node || this.getNodeHidden(node) === true || this.getNodeDisabled(node)) {
1001
1299
  return false;
1002
1300
  }
1003
1301
  this.focusNodeById(nodeId);
@@ -1011,11 +1309,12 @@ class AXTreeViewComponent {
1011
1309
  const expanded = [];
1012
1310
  const traverse = (nodes) => {
1013
1311
  for (const node of nodes) {
1014
- if (node.expanded) {
1312
+ if (this.getNodeExpanded(node)) {
1015
1313
  expanded.push(node);
1016
1314
  }
1017
- if (node.children) {
1018
- traverse(node.children);
1315
+ const children = this.getNodeChildren(node);
1316
+ if (children) {
1317
+ traverse(children);
1019
1318
  }
1020
1319
  }
1021
1320
  };
@@ -1030,11 +1329,13 @@ class AXTreeViewComponent {
1030
1329
  const collapsed = [];
1031
1330
  const traverse = (nodes) => {
1032
1331
  for (const node of nodes) {
1033
- if (!node.expanded && (node.children?.length || node.childrenCount)) {
1332
+ const children = this.getNodeChildren(node);
1333
+ const childrenCount = this.getNodeChildrenCount(node);
1334
+ if (!this.getNodeExpanded(node) && (children?.length || childrenCount)) {
1034
1335
  collapsed.push(node);
1035
1336
  }
1036
- if (node.children) {
1037
- traverse(node.children);
1337
+ if (children) {
1338
+ traverse(children);
1038
1339
  }
1039
1340
  }
1040
1341
  };
@@ -1048,7 +1349,7 @@ class AXTreeViewComponent {
1048
1349
  */
1049
1350
  isNodeExpanded(nodeId) {
1050
1351
  const node = this.findNode(nodeId);
1051
- return node?.expanded ?? false;
1352
+ return node ? this.getNodeExpanded(node) : false;
1052
1353
  }
1053
1354
  /**
1054
1355
  * Check if a node is selected
@@ -1057,7 +1358,7 @@ class AXTreeViewComponent {
1057
1358
  */
1058
1359
  isNodeSelected(nodeId) {
1059
1360
  const node = this.findNode(nodeId);
1060
- return node?.selected ?? false;
1361
+ return node ? this.getNodeSelected(node) : false;
1061
1362
  }
1062
1363
  /**
1063
1364
  * Check if a node has children
@@ -1066,19 +1367,21 @@ class AXTreeViewComponent {
1066
1367
  */
1067
1368
  hasChildren(nodeId) {
1068
1369
  const node = this.findNode(nodeId);
1069
- return this.treeService.hasChildren(node ?? { id: '', label: '' });
1370
+ return this.treeService.hasChildren(node ?? {}, this.childrenField());
1070
1371
  }
1071
1372
  /**
1072
1373
  * Get template context for a node
1073
1374
  */
1074
1375
  getTemplateContext(node, level = 0) {
1376
+ const children = this.getNodeChildren(node);
1377
+ const childrenCount = this.getNodeChildrenCount(node);
1075
1378
  return {
1076
1379
  $implicit: node,
1077
1380
  node,
1078
1381
  level,
1079
- expanded: node.expanded ?? false,
1080
- childrenCount: node.childrenCount ?? node.children?.length ?? 0,
1081
- loading: node.loading ?? false,
1382
+ expanded: this.getNodeExpanded(node),
1383
+ childrenCount: childrenCount ?? children?.length ?? 0,
1384
+ loading: this.getNodeLoading(node),
1082
1385
  };
1083
1386
  }
1084
1387
  /**
@@ -1094,7 +1397,8 @@ class AXTreeViewComponent {
1094
1397
  * Check if node should show expand toggle
1095
1398
  */
1096
1399
  shouldShowExpandToggle(node) {
1097
- return this.treeService.hasChildren(node) || this.treeService.canLazyLoad(node, this.isLazyDataSource());
1400
+ return (this.treeService.hasChildren(node, this.childrenField()) ||
1401
+ this.treeService.canLazyLoad(node, this.isLazyDataSource(), this.childrenCountField(), this.childrenField()));
1098
1402
  }
1099
1403
  /**
1100
1404
  * Check if checkboxes should be shown (only for multiple mode)
@@ -1102,11 +1406,50 @@ class AXTreeViewComponent {
1102
1406
  shouldShowCheckbox() {
1103
1407
  return this.selectMode() === 'multiple' && this.showCheckbox();
1104
1408
  }
1409
+ /**
1410
+ * Check if a node is a leaf (has no children)
1411
+ * A node is a leaf if it has no loaded children AND no childrenCount (or childrenCount is 0)
1412
+ */
1413
+ isLeafNode(node) {
1414
+ const hasChildren = this.treeService.hasChildren(node, this.childrenField());
1415
+ const childrenCount = this.getNodeChildrenCount(node);
1416
+ const hasChildrenCount = childrenCount && childrenCount > 0;
1417
+ const canLazyLoad = this.treeService.canLazyLoad(node, this.isLazyDataSource(), this.childrenCountField(), this.childrenField());
1418
+ // A node is a leaf if:
1419
+ // 1. It has no loaded children
1420
+ // 2. AND it has no childrenCount (or childrenCount is 0)
1421
+ // 3. AND it cannot be lazy loaded
1422
+ return !hasChildren && !hasChildrenCount && !canLazyLoad;
1423
+ }
1424
+ /**
1425
+ * Check if a node can be selected (considering selectMode and isLeafOnlyMode)
1426
+ */
1427
+ canSelectNode(node) {
1428
+ if (this.selectMode() === 'none') {
1429
+ return false;
1430
+ }
1431
+ if (this.isLeafOnlyMode()) {
1432
+ return this.isLeafNode(node);
1433
+ }
1434
+ return true;
1435
+ }
1436
+ /**
1437
+ * Check if checkbox should be shown for a specific node
1438
+ */
1439
+ shouldShowCheckboxForNode(node) {
1440
+ if (!this.shouldShowCheckbox()) {
1441
+ return false;
1442
+ }
1443
+ if (this.isLeafOnlyMode()) {
1444
+ return this.isLeafNode(node);
1445
+ }
1446
+ return true;
1447
+ }
1105
1448
  /**
1106
1449
  * Generate unique list ID for each node
1107
1450
  */
1108
1451
  getListId(node) {
1109
- return this.treeService.getListId(node);
1452
+ return this.treeService.getListId(node, this.idField());
1110
1453
  }
1111
1454
  /**
1112
1455
  * Check if a node is currently focused
@@ -1127,30 +1470,50 @@ class AXTreeViewComponent {
1127
1470
  if (!this.shouldShowExpandToggle(node)) {
1128
1471
  return null;
1129
1472
  }
1130
- return node.expanded ? 'true' : 'false';
1473
+ return this.getNodeExpanded(node) ? 'true' : 'false';
1131
1474
  }
1132
1475
  /**
1133
1476
  * Get ARIA selected state for a node
1134
1477
  */
1135
1478
  getNodeAriaSelected(node) {
1479
+ if (this.selectMode() === 'none') {
1480
+ return null;
1481
+ }
1482
+ const selected = this.getNodeSelected(node);
1136
1483
  if (this.selectMode() === 'single') {
1137
- return node.selected ? 'true' : 'false';
1484
+ return selected ? 'true' : 'false';
1485
+ }
1486
+ if (this.selectMode() === 'multiple') {
1487
+ return selected ? 'true' : 'false';
1138
1488
  }
1139
1489
  return null;
1140
1490
  }
1491
+ /**
1492
+ * Emit selection change event with all selected nodes
1493
+ */
1494
+ emitSelectionChange() {
1495
+ const selectedNodes = this.getSelectedNodes();
1496
+ this.onSelectionChange.emit({
1497
+ component: this,
1498
+ selectedNodes,
1499
+ });
1500
+ }
1141
1501
  // ==================== Event Handlers ====================
1142
1502
  /**
1143
- * Handle node click - for single selection mode or multiple mode with checkOnClick enabled
1503
+ * Handle node click - for single selection mode or multiple mode when showCheckbox is false
1144
1504
  */
1145
1505
  onNodeClick(node, event) {
1146
- if (node.disabled)
1506
+ if (this.getNodeDisabled(node))
1507
+ return;
1508
+ if (this.selectMode() === 'none')
1509
+ return;
1510
+ if (!this.canSelectNode(node))
1147
1511
  return;
1148
1512
  const mode = this.selectMode();
1149
- const shouldCheckOnClick = this.checkOnClick();
1150
1513
  if (mode === 'single') {
1151
1514
  this.handleSingleSelection(node, event);
1152
1515
  }
1153
- else if (mode === 'multiple' && shouldCheckOnClick) {
1516
+ else if (mode === 'multiple' && !this.showCheckbox()) {
1154
1517
  this.handleMultipleSelection(node, event);
1155
1518
  }
1156
1519
  }
@@ -1158,19 +1521,19 @@ class AXTreeViewComponent {
1158
1521
  * Toggle node expansion state with lazy loading support
1159
1522
  */
1160
1523
  async toggleNode(node, event) {
1161
- if (node.disabled)
1524
+ if (this.getNodeDisabled(node))
1162
1525
  return;
1163
1526
  if (this.isEvent(event) && typeof event.stopPropagation === 'function') {
1164
1527
  event.stopPropagation();
1165
1528
  }
1166
- const hasChildren = this.treeService.hasChildren(node);
1167
- const canLazyLoad = this.treeService.canLazyLoad(node, this.isLazyDataSource());
1529
+ const hasChildren = this.treeService.hasChildren(node, this.childrenField());
1530
+ const canLazyLoad = this.treeService.canLazyLoad(node, this.isLazyDataSource(), this.childrenCountField(), this.childrenField());
1168
1531
  if (hasChildren || canLazyLoad) {
1169
- const willExpand = !node.expanded;
1532
+ const willExpand = !this.getNodeExpanded(node);
1170
1533
  if (willExpand && canLazyLoad) {
1171
1534
  await this.loadNodeChildren(node);
1172
1535
  }
1173
- node.expanded = willExpand;
1536
+ this.setNodeExpanded(node, willExpand);
1174
1537
  this.refreshNodes();
1175
1538
  this.onNodeToggle.emit({ component: this, node, nativeEvent: event });
1176
1539
  }
@@ -1181,17 +1544,22 @@ class AXTreeViewComponent {
1181
1544
  toggleSelection(node, event) {
1182
1545
  if (!event.isUserInteraction)
1183
1546
  return;
1547
+ if (this.selectMode() === 'none')
1548
+ return;
1549
+ if (!this.canSelectNode(node))
1550
+ return;
1184
1551
  const mode = this.selectMode();
1185
1552
  if (mode !== 'multiple')
1186
1553
  return;
1187
1554
  const newValue = event.value === null ? true : event.value;
1188
- node.selected = newValue;
1189
- node.indeterminate = false;
1190
- if (this.checkChildrenOnSelect() && node.children) {
1191
- this.treeService.selectAllChildren(node.children, newValue);
1555
+ this.setNodeSelected(node, newValue);
1556
+ this.setNodeIndeterminate(node, false);
1557
+ const children = this.getNodeChildren(node);
1558
+ if (this.cascadesToChildren() && children && !this.isLeafOnlyMode()) {
1559
+ this.treeService.selectAllChildren(children, newValue, this.selectedField(), this.indeterminateField(), this.childrenField());
1192
1560
  }
1193
- if (this.intermediateState()) {
1194
- this.treeService.updateParentStates(this.nodes(), node, this.intermediateState());
1561
+ if (this.hasIntermediateState() && !this.isLeafOnlyMode()) {
1562
+ this.treeService.updateParentStates(this.nodes(), node, this.hasIntermediateState(), this.idField(), this.childrenField(), this.selectedField(), this.indeterminateField());
1195
1563
  }
1196
1564
  this.refreshNodes();
1197
1565
  this.onNodeSelect.emit({
@@ -1199,12 +1567,13 @@ class AXTreeViewComponent {
1199
1567
  node,
1200
1568
  isUserInteraction: event.isUserInteraction,
1201
1569
  });
1570
+ this.emitSelectionChange();
1202
1571
  }
1203
1572
  /**
1204
1573
  * Handle drop events for tree nodes
1205
1574
  */
1206
1575
  onDrop(event, parentNode) {
1207
- const targetArray = parentNode?.children ?? this.nodes();
1576
+ const targetArray = parentNode ? (this.getNodeChildren(parentNode) ?? []) : this.nodes();
1208
1577
  const isReordering = event.previousContainer === event.container;
1209
1578
  if (isReordering) {
1210
1579
  this.handleReorder(event, targetArray, parentNode);
@@ -1225,14 +1594,18 @@ class AXTreeViewComponent {
1225
1594
  if (!sourceArray)
1226
1595
  return;
1227
1596
  const movedNode = sourceArray[event.previousIndex];
1228
- if (!this.treeService.isValidDropTarget(movedNode, targetNode))
1597
+ if (!this.treeService.isValidDropTarget(movedNode, targetNode, this.idField(), this.childrenField()))
1229
1598
  return;
1230
1599
  if (!this.emitBeforeDropEvent(movedNode, sourceListId, targetNode, event.previousIndex, 0))
1231
1600
  return;
1232
- targetNode.children ??= [];
1601
+ let targetChildren = this.getNodeChildren(targetNode);
1602
+ if (!targetChildren) {
1603
+ targetChildren = [];
1604
+ this.setNodeChildren(targetNode, targetChildren);
1605
+ }
1233
1606
  sourceArray.splice(event.previousIndex, 1);
1234
- targetNode.children.unshift(movedNode);
1235
- targetNode.expanded = true;
1607
+ targetChildren.unshift(movedNode);
1608
+ this.setNodeExpanded(targetNode, true);
1236
1609
  this.emitDropEvents(movedNode, this.findParentByListId(sourceListId), targetNode, event.previousIndex, 0, false);
1237
1610
  this.refreshNodes();
1238
1611
  }
@@ -1247,23 +1620,23 @@ class AXTreeViewComponent {
1247
1620
  */
1248
1621
  onTreeFocus(event) {
1249
1622
  if (event.target === event.currentTarget) {
1250
- const flatList = this.treeService.buildFlatNodeList(this.nodes());
1623
+ const flatList = this.treeService.buildFlatNodeList(this.nodes(), this.hiddenField(), this.disabledField(), this.expandedField(), this.childrenField());
1251
1624
  if (flatList.length > 0) {
1252
1625
  const focusedId = this.focusedNodeId();
1253
1626
  if (focusedId) {
1254
- // Check if the focused node still exists and is visible
1255
- const focusedNode = this.treeService.findNodeById(this.nodes(), focusedId);
1256
- if (focusedNode && focusedNode.visible !== false) {
1627
+ // Check if the focused node still exists and is not hidden
1628
+ const focusedNode = this.treeService.findNodeById(this.nodes(), focusedId, this.idField());
1629
+ if (focusedNode && this.getNodeHidden(focusedNode) !== true) {
1257
1630
  this.focusNodeById(focusedId);
1258
1631
  }
1259
1632
  else {
1260
1633
  // Focused node no longer exists, focus first node
1261
- this.focusNodeById(flatList[0].node.id);
1634
+ this.focusNodeById(this.getNodeId(flatList[0].node));
1262
1635
  }
1263
1636
  }
1264
1637
  else {
1265
1638
  // No node is focused, focus first node
1266
- this.focusNodeById(flatList[0].node.id);
1639
+ this.focusNodeById(this.getNodeId(flatList[0].node));
1267
1640
  }
1268
1641
  }
1269
1642
  }
@@ -1280,11 +1653,13 @@ class AXTreeViewComponent {
1280
1653
  * Handle keyboard navigation
1281
1654
  */
1282
1655
  handleKeyDown(event) {
1283
- const flatList = this.treeService.buildFlatNodeList(this.nodes());
1656
+ const flatList = this.treeService.buildFlatNodeList(this.nodes(), this.hiddenField(), this.disabledField(), this.expandedField(), this.childrenField());
1284
1657
  if (flatList.length === 0)
1285
1658
  return;
1286
1659
  const currentFocused = this.getFocusedNode();
1287
- let currentIndex = currentFocused ? flatList.findIndex((item) => item.node.id === currentFocused.id) : -1;
1660
+ let currentIndex = currentFocused
1661
+ ? flatList.findIndex((item) => this.getNodeId(item.node) === this.getNodeId(currentFocused))
1662
+ : -1;
1288
1663
  if (currentIndex === -1 && event.target === event.currentTarget) {
1289
1664
  currentIndex = 0;
1290
1665
  }
@@ -1297,7 +1672,7 @@ class AXTreeViewComponent {
1297
1672
  if (navigationResult.targetIndex !== null &&
1298
1673
  navigationResult.targetIndex >= 0 &&
1299
1674
  navigationResult.targetIndex < flatList.length) {
1300
- this.focusNodeById(flatList[navigationResult.targetIndex].node.id);
1675
+ this.focusNodeById(this.getNodeId(flatList[navigationResult.targetIndex].node));
1301
1676
  }
1302
1677
  }
1303
1678
  }
@@ -1320,32 +1695,34 @@ class AXTreeViewComponent {
1320
1695
  * Load children for a node using lazy loading
1321
1696
  */
1322
1697
  async loadNodeChildren(node) {
1323
- if (!this.isLazyDataSource() || node.loading)
1324
- return;
1325
- if (this.treeService.hasChildren(node) || !node.childrenCount || node.childrenCount === 0) {
1698
+ const nodeId = this.getNodeId(node);
1699
+ if (!this.isLazyDataSource() || this.loadingNodes().has(nodeId))
1326
1700
  return;
1701
+ if (this.treeService.hasChildren(node, this.childrenField())) {
1702
+ const childrenCount = this.getNodeChildrenCount(node);
1703
+ if (!childrenCount || childrenCount === 0) {
1704
+ return;
1705
+ }
1327
1706
  }
1328
1707
  const ds = this.datasource();
1329
1708
  if (typeof ds !== 'function')
1330
1709
  return;
1331
1710
  try {
1332
- node.loading = true;
1333
- this.loadingNodes.update((set) => new Set(set).add(node.id));
1711
+ this.loadingNodes.update((set) => new Set(set).add(nodeId));
1334
1712
  this.refreshNodes();
1335
- const result = ds(node.id);
1713
+ const result = ds(nodeId);
1336
1714
  const children = result instanceof Promise ? await result : result;
1337
- node.children = children;
1338
- node.childrenCount = children.length;
1715
+ this.setNodeChildren(node, children);
1716
+ this.setNodeChildrenCount(node, children.length);
1339
1717
  }
1340
1718
  catch (error) {
1341
1719
  this.handleError('Failed to load children', error);
1342
- node.childrenCount = 0;
1720
+ this.setNodeChildrenCount(node, 0);
1343
1721
  }
1344
1722
  finally {
1345
- node.loading = false;
1346
1723
  this.loadingNodes.update((set) => {
1347
1724
  const newSet = new Set(set);
1348
- newSet.delete(node.id);
1725
+ newSet.delete(nodeId);
1349
1726
  return newSet;
1350
1727
  });
1351
1728
  this.refreshNodes();
@@ -1375,11 +1752,12 @@ class AXTreeViewComponent {
1375
1752
  */
1376
1753
  ensureNewArrayReferences(nodes) {
1377
1754
  for (const node of nodes) {
1378
- if (node.children && node.children.length > 0) {
1755
+ const children = this.getNodeChildren(node);
1756
+ if (children && children.length > 0) {
1379
1757
  // Create new array reference for children
1380
- node.children = [...node.children];
1758
+ this.setNodeChildren(node, [...children]);
1381
1759
  // Recursively process nested children
1382
- this.ensureNewArrayReferences(node.children);
1760
+ this.ensureNewArrayReferences(children);
1383
1761
  }
1384
1762
  }
1385
1763
  }
@@ -1387,9 +1765,9 @@ class AXTreeViewComponent {
1387
1765
  * Handle single selection mode
1388
1766
  */
1389
1767
  handleSingleSelection(node, event) {
1390
- this.treeService.deselectAllNodes(this.nodes());
1391
- node.selected = true;
1392
- node.indeterminate = false;
1768
+ this.treeService.deselectAllNodes(this.nodes(), this.selectedField(), this.indeterminateField(), this.childrenField());
1769
+ this.setNodeSelected(node, true);
1770
+ this.setNodeIndeterminate(node, false);
1393
1771
  this.refreshNodes();
1394
1772
  this.onNodeSelect.emit({
1395
1773
  component: this,
@@ -1397,19 +1775,21 @@ class AXTreeViewComponent {
1397
1775
  nativeEvent: event,
1398
1776
  isUserInteraction: true,
1399
1777
  });
1778
+ this.emitSelectionChange();
1400
1779
  }
1401
1780
  /**
1402
- * Handle multiple selection mode with checkOnClick
1781
+ * Handle multiple selection mode when showCheckbox is false (click to toggle)
1403
1782
  */
1404
1783
  handleMultipleSelection(node, event) {
1405
- const newValue = !node.selected;
1406
- node.selected = newValue;
1407
- node.indeterminate = false;
1408
- if (this.checkChildrenOnSelect() && node.children) {
1409
- this.treeService.selectAllChildren(node.children, newValue);
1784
+ const newValue = !this.getNodeSelected(node);
1785
+ this.setNodeSelected(node, newValue);
1786
+ this.setNodeIndeterminate(node, false);
1787
+ const children = this.getNodeChildren(node);
1788
+ if (this.cascadesToChildren() && children && !this.isLeafOnlyMode()) {
1789
+ this.treeService.selectAllChildren(children, newValue, this.selectedField(), this.indeterminateField(), this.childrenField());
1410
1790
  }
1411
- if (this.intermediateState()) {
1412
- this.treeService.updateParentStates(this.nodes(), node, this.intermediateState());
1791
+ if (this.hasIntermediateState() && !this.isLeafOnlyMode()) {
1792
+ this.treeService.updateParentStates(this.nodes(), node, this.hasIntermediateState(), this.idField(), this.childrenField(), this.selectedField(), this.indeterminateField());
1413
1793
  }
1414
1794
  this.refreshNodes();
1415
1795
  this.onNodeSelect.emit({
@@ -1418,30 +1798,33 @@ class AXTreeViewComponent {
1418
1798
  nativeEvent: event,
1419
1799
  isUserInteraction: true,
1420
1800
  });
1801
+ this.emitSelectionChange();
1421
1802
  }
1422
1803
  /**
1423
1804
  * Get array reference by drop list ID
1424
1805
  */
1425
1806
  getArrayByListId(listId) {
1426
- return this.treeService.getArrayByListId(this.nodes(), listId);
1807
+ return this.treeService.getArrayByListId(this.nodes(), listId, this.idField(), this.childrenField());
1427
1808
  }
1428
1809
  /**
1429
1810
  * Find parent node by list ID
1430
1811
  */
1431
1812
  findParentByListId(listId) {
1432
- return this.treeService.findParentByListId(this.nodes(), listId);
1813
+ return this.treeService.findParentByListId(this.nodes(), listId, this.idField());
1433
1814
  }
1434
1815
  /**
1435
- * Check if move operation is allowed based on dragOperationType
1816
+ * Check if move operation is allowed based on dragBehavior
1436
1817
  */
1437
1818
  canMoveToParent() {
1438
- return this.dragOperationType() !== 'order-only';
1819
+ const behavior = this.dragBehavior();
1820
+ return behavior !== 'none' && behavior !== 'order-only';
1439
1821
  }
1440
1822
  /**
1441
- * Check if reorder operation is allowed based on dragOperationType
1823
+ * Check if reorder operation is allowed based on dragBehavior
1442
1824
  */
1443
1825
  canReorder() {
1444
- return this.dragOperationType() !== 'move';
1826
+ const behavior = this.dragBehavior();
1827
+ return behavior !== 'none' && behavior !== 'move';
1445
1828
  }
1446
1829
  /**
1447
1830
  * Handle reordering within the same list */
@@ -1514,7 +1897,7 @@ class AXTreeViewComponent {
1514
1897
  const focusedId = this.focusedNodeId();
1515
1898
  if (!focusedId)
1516
1899
  return null;
1517
- return this.treeService.findNodeById(this.nodes(), focusedId);
1900
+ return this.treeService.findNodeById(this.nodes(), focusedId, this.idField());
1518
1901
  }
1519
1902
  /**
1520
1903
  * Set focus to a node by ID
@@ -1558,14 +1941,14 @@ class AXTreeViewComponent {
1558
1941
  break;
1559
1942
  case 'ArrowLeft':
1560
1943
  if (currentFocused) {
1561
- if (currentFocused.expanded && this.shouldShowExpandToggle(currentFocused)) {
1944
+ if (this.getNodeExpanded(currentFocused) && this.shouldShowExpandToggle(currentFocused)) {
1562
1945
  this.toggleNode(currentFocused, event);
1563
1946
  return { handled: true, shouldPreventDefault: true, targetIndex: null };
1564
1947
  }
1565
1948
  else {
1566
1949
  const currentItem = flatList[currentIndex];
1567
1950
  if (currentItem?.parent) {
1568
- targetIndex = flatList.findIndex((item) => item.node.id === currentItem.parent.id);
1951
+ targetIndex = flatList.findIndex((item) => this.getNodeId(item.node) === this.getNodeId(currentItem.parent));
1569
1952
  }
1570
1953
  else {
1571
1954
  shouldPreventDefault = false;
@@ -1578,22 +1961,21 @@ class AXTreeViewComponent {
1578
1961
  break;
1579
1962
  case 'ArrowRight':
1580
1963
  if (currentFocused) {
1581
- if (!currentFocused.expanded && this.shouldShowExpandToggle(currentFocused)) {
1964
+ if (!this.getNodeExpanded(currentFocused) && this.shouldShowExpandToggle(currentFocused)) {
1582
1965
  this.toggleNode(currentFocused, event);
1583
1966
  return { handled: true, shouldPreventDefault: true, targetIndex: null };
1584
1967
  }
1585
- else if (currentFocused.expanded &&
1586
- this.treeService.hasChildren(currentFocused) &&
1587
- currentFocused.children) {
1588
- const children = currentFocused.children;
1589
- if (children.length > 0) {
1968
+ else if (this.getNodeExpanded(currentFocused) &&
1969
+ this.treeService.hasChildren(currentFocused, this.childrenField())) {
1970
+ const children = this.getNodeChildren(currentFocused);
1971
+ if (children && children.length > 0) {
1590
1972
  const firstChild = children[0];
1591
- targetIndex = flatList.findIndex((item) => item.node.id === firstChild.id);
1973
+ targetIndex = flatList.findIndex((item) => this.getNodeId(item.node) === this.getNodeId(firstChild));
1592
1974
  if (targetIndex === -1) {
1593
- const updatedFlatList = this.treeService.buildFlatNodeList(this.nodes());
1594
- targetIndex = updatedFlatList.findIndex((item) => item.node.id === firstChild.id);
1975
+ const updatedFlatList = this.treeService.buildFlatNodeList(this.nodes(), this.hiddenField(), this.disabledField(), this.expandedField(), this.childrenField());
1976
+ targetIndex = updatedFlatList.findIndex((item) => this.getNodeId(item.node) === this.getNodeId(firstChild));
1595
1977
  if (targetIndex >= 0 && targetIndex < updatedFlatList.length) {
1596
- this.focusNodeById(updatedFlatList[targetIndex].node.id);
1978
+ this.focusNodeById(this.getNodeId(updatedFlatList[targetIndex].node));
1597
1979
  return { handled: true, shouldPreventDefault: true, targetIndex: null };
1598
1980
  }
1599
1981
  }
@@ -1618,7 +2000,7 @@ class AXTreeViewComponent {
1618
2000
  break;
1619
2001
  case ' ':
1620
2002
  case 'Space':
1621
- if (currentFocused && this.selectMode() === 'multiple') {
2003
+ if (currentFocused && this.selectMode() !== 'none' && this.canSelectNode(currentFocused)) {
1622
2004
  event.preventDefault();
1623
2005
  this.handleSpaceKeySelection(currentFocused, event);
1624
2006
  return { handled: true, shouldPreventDefault: true, targetIndex: null };
@@ -1626,7 +2008,7 @@ class AXTreeViewComponent {
1626
2008
  shouldPreventDefault = false;
1627
2009
  break;
1628
2010
  case 'Enter':
1629
- if (currentFocused) {
2011
+ if (currentFocused && this.canSelectNode(currentFocused)) {
1630
2012
  event.preventDefault();
1631
2013
  this.handleEnterKeySelection(currentFocused, event);
1632
2014
  return { handled: true, shouldPreventDefault: true, targetIndex: null };
@@ -1635,7 +2017,7 @@ class AXTreeViewComponent {
1635
2017
  break;
1636
2018
  default:
1637
2019
  if ((event.ctrlKey || event.metaKey) && event.key === 'Enter') {
1638
- if (currentFocused && this.selectMode() === 'multiple') {
2020
+ if (currentFocused && this.selectMode() === 'multiple' && this.canSelectNode(currentFocused)) {
1639
2021
  event.preventDefault();
1640
2022
  this.handleCtrlEnterSelection(currentFocused, event);
1641
2023
  return { handled: true, shouldPreventDefault: true, targetIndex: null };
@@ -1649,16 +2031,31 @@ class AXTreeViewComponent {
1649
2031
  }
1650
2032
  /**
1651
2033
  * Handle Space key selection
2034
+ * In single mode: replaces selection (deselects all, selects this node)
2035
+ * In multiple mode: toggles selection
1652
2036
  */
1653
2037
  handleSpaceKeySelection(node, event) {
1654
- const newValue = !node.selected;
1655
- node.selected = newValue;
1656
- node.indeterminate = false;
1657
- if (this.checkChildrenOnSelect() && node.children) {
1658
- this.treeService.selectAllChildren(node.children, newValue);
2038
+ if (!this.canSelectNode(node))
2039
+ return;
2040
+ const mode = this.selectMode();
2041
+ if (mode === 'single') {
2042
+ // Single mode: replace selection
2043
+ this.treeService.deselectAllNodes(this.nodes(), this.selectedField(), this.indeterminateField(), this.childrenField());
2044
+ this.setNodeSelected(node, true);
2045
+ this.setNodeIndeterminate(node, false);
1659
2046
  }
1660
- if (this.intermediateState()) {
1661
- this.treeService.updateParentStates(this.nodes(), node, this.intermediateState());
2047
+ else {
2048
+ // Multiple mode: toggle selection
2049
+ const newValue = !this.getNodeSelected(node);
2050
+ this.setNodeSelected(node, newValue);
2051
+ this.setNodeIndeterminate(node, false);
2052
+ const children = this.getNodeChildren(node);
2053
+ if (this.cascadesToChildren() && children && !this.isLeafOnlyMode()) {
2054
+ this.treeService.selectAllChildren(children, newValue, this.selectedField(), this.indeterminateField(), this.childrenField());
2055
+ }
2056
+ if (this.hasIntermediateState() && !this.isLeafOnlyMode()) {
2057
+ this.treeService.updateParentStates(this.nodes(), node, this.hasIntermediateState(), this.idField(), this.childrenField(), this.selectedField(), this.indeterminateField());
2058
+ }
1662
2059
  }
1663
2060
  this.refreshNodes();
1664
2061
  this.onNodeSelect.emit({
@@ -1667,21 +2064,30 @@ class AXTreeViewComponent {
1667
2064
  nativeEvent: event,
1668
2065
  isUserInteraction: true,
1669
2066
  });
2067
+ this.emitSelectionChange();
1670
2068
  }
1671
2069
  /**
1672
2070
  * Handle Enter key selection
2071
+ * In single mode: replaces selection (deselects all, selects this node)
2072
+ * In multiple mode: replaces selection (deselects all, selects this node and respects selectionBehavior)
1673
2073
  */
1674
2074
  handleEnterKeySelection(node, event) {
2075
+ if (!this.canSelectNode(node))
2076
+ return;
1675
2077
  const mode = this.selectMode();
1676
- this.treeService.deselectAllNodes(this.nodes());
1677
- node.selected = true;
1678
- node.indeterminate = false;
2078
+ // Enter key always replaces selection (deselects all first)
2079
+ this.treeService.deselectAllNodes(this.nodes(), this.selectedField(), this.indeterminateField(), this.childrenField());
2080
+ this.setNodeSelected(node, true);
2081
+ this.setNodeIndeterminate(node, false);
1679
2082
  if (mode === 'multiple') {
1680
- if (this.checkChildrenOnSelect() && node.children) {
1681
- this.treeService.selectAllChildren(node.children, true);
2083
+ const children = this.getNodeChildren(node);
2084
+ // Respect selectionBehavior: cascade to children if enabled
2085
+ if (this.cascadesToChildren() && children && !this.isLeafOnlyMode()) {
2086
+ this.treeService.selectAllChildren(children, true, this.selectedField(), this.indeterminateField(), this.childrenField());
1682
2087
  }
1683
- if (this.intermediateState()) {
1684
- this.treeService.updateParentStates(this.nodes(), node, this.intermediateState());
2088
+ // Respect selectionBehavior: update parent states if intermediate mode
2089
+ if (this.hasIntermediateState() && !this.isLeafOnlyMode()) {
2090
+ this.treeService.updateParentStates(this.nodes(), node, this.hasIntermediateState(), this.idField(), this.childrenField(), this.selectedField(), this.indeterminateField());
1685
2091
  }
1686
2092
  }
1687
2093
  this.refreshNodes();
@@ -1691,19 +2097,23 @@ class AXTreeViewComponent {
1691
2097
  nativeEvent: event,
1692
2098
  isUserInteraction: true,
1693
2099
  });
2100
+ this.emitSelectionChange();
1694
2101
  }
1695
2102
  /**
1696
2103
  * Handle Ctrl/Cmd + Enter key selection
1697
2104
  */
1698
2105
  handleCtrlEnterSelection(node, event) {
1699
- const newValue = !node.selected;
1700
- node.selected = newValue;
1701
- node.indeterminate = false;
1702
- if (this.checkChildrenOnSelect() && node.children) {
1703
- this.treeService.selectAllChildren(node.children, newValue);
2106
+ if (!this.canSelectNode(node))
2107
+ return;
2108
+ const newValue = !this.getNodeSelected(node);
2109
+ this.setNodeSelected(node, newValue);
2110
+ this.setNodeIndeterminate(node, false);
2111
+ const children = this.getNodeChildren(node);
2112
+ if (this.cascadesToChildren() && children && !this.isLeafOnlyMode()) {
2113
+ this.treeService.selectAllChildren(children, newValue, this.selectedField(), this.indeterminateField(), this.childrenField());
1704
2114
  }
1705
- if (this.intermediateState()) {
1706
- this.treeService.updateParentStates(this.nodes(), node, this.intermediateState());
2115
+ if (this.hasIntermediateState() && !this.isLeafOnlyMode()) {
2116
+ this.treeService.updateParentStates(this.nodes(), node, this.hasIntermediateState(), this.idField(), this.childrenField(), this.selectedField(), this.indeterminateField());
1707
2117
  }
1708
2118
  this.refreshNodes();
1709
2119
  this.onNodeSelect.emit({
@@ -1712,6 +2122,7 @@ class AXTreeViewComponent {
1712
2122
  nativeEvent: event,
1713
2123
  isUserInteraction: true,
1714
2124
  });
2125
+ this.emitSelectionChange();
1715
2126
  }
1716
2127
  /**
1717
2128
  * Type guard to check if value is an Event
@@ -1731,7 +2142,7 @@ class AXTreeViewComponent {
1731
2142
  }
1732
2143
  }
1733
2144
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: AXTreeViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1734
- 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 }); }
2145
+ 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 }, dragArea: { classPropertyName: "dragArea", publicName: "dragArea", isSignal: true, isRequired: false, transformFunction: null }, dragBehavior: { classPropertyName: "dragBehavior", publicName: "dragBehavior", 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]=\"dragBehavior() !== '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]=\"dragBehavior() !== '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]=\"dragBehavior() !== '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' && !showCheckbox())) && onNodeClick(node, $event)\n \"\n (focus)=\"onNodeFocus(getNodeId(node))\"\n [tabindex]=\"isNodeFocused(getNodeId(node)) ? 0 : -1\"\n >\n @if (shouldShowDragHandle()) {\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]=\"dragBehavior() !== '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]=\"dragBehavior() !== '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]=\"dragBehavior() !== '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' && !showCheckbox())) &&\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 (shouldShowDragHandle()) {\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 }); }
1735
2146
  }
1736
2147
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: AXTreeViewComponent, decorators: [{
1737
2148
  type: Component,
@@ -1747,22 +2158,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImpor
1747
2158
  AXCheckBoxComponent,
1748
2159
  AXBadgeComponent,
1749
2160
  AXDecoratorIconComponent,
2161
+ AXTooltipDirective,
1750
2162
  ], host: {
1751
2163
  class: 'ax-tree-view',
2164
+ role: 'tree',
2165
+ tabindex: '0',
1752
2166
  '[class.ax-tree-view-default]': "look() === 'default'",
1753
2167
  '[class.ax-tree-view-card]': "look() === 'card'",
1754
2168
  '[class.ax-tree-view-with-line]': "look() === 'with-line'",
1755
2169
  '[class.ax-tree-view-rtl]': 'isRtl',
1756
2170
  '[style.--ax-tree-view-indent-size]': "indentSize() + 'px'",
1757
2171
  '[style.--ax-tree-view-line-offset]': "(indentSize() / 2) + 'px'",
1758
- role: 'tree',
1759
2172
  '[attr.aria-label]': '"Tree navigation"',
1760
2173
  '(keydown)': 'handleKeyDown($event)',
1761
2174
  '(focus)': 'onTreeFocus($event)',
1762
2175
  '(blur)': 'onTreeBlur($event)',
1763
- tabindex: '0',
1764
- }, 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"] }]
1765
- }], 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 }] }], checkChildrenOnSelect: [{ type: i0.Input, args: [{ isSignal: true, alias: "checkChildrenOnSelect", required: false }] }], intermediateState: [{ type: i0.Input, args: [{ isSignal: true, alias: "intermediateState", 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 }] }], nodeHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "nodeHeight", required: false }] }], look: [{ type: i0.Input, args: [{ isSignal: true, alias: "look", required: false }] }], itemTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemTemplate", required: false }] }], onBeforeDrop: [{ type: i0.Output, args: ["onBeforeDrop"] }], onNodeToggle: [{ type: i0.Output, args: ["onNodeToggle"] }], onNodeSelect: [{ type: i0.Output, args: ["onNodeSelect"] }], onOrderChange: [{ type: i0.Output, args: ["onOrderChange"] }], onMoveChange: [{ type: i0.Output, args: ["onMoveChange"] }], onItemsChange: [{ type: i0.Output, args: ["onItemsChange"] }] } });
2176
+ }, template: "<!-- Root drop list -->\n<div\n axFocusTrap\n [axDropList]=\"dragBehavior() !== '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]=\"dragBehavior() !== '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]=\"dragBehavior() !== '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' && !showCheckbox())) && onNodeClick(node, $event)\n \"\n (focus)=\"onNodeFocus(getNodeId(node))\"\n [tabindex]=\"isNodeFocused(getNodeId(node)) ? 0 : -1\"\n >\n @if (shouldShowDragHandle()) {\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]=\"dragBehavior() !== '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]=\"dragBehavior() !== '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]=\"dragBehavior() !== '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' && !showCheckbox())) &&\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 (shouldShowDragHandle()) {\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"] }]
2177
+ }], 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 }] }], dragArea: [{ type: i0.Input, args: [{ isSignal: true, alias: "dragArea", required: false }] }], dragBehavior: [{ type: i0.Input, args: [{ isSignal: true, alias: "dragBehavior", 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"] }] } });
1766
2178
 
1767
2179
  class AXTreeViewModule {
1768
2180
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: AXTreeViewModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }